I have a basic implementation of hole-filling filter as shown below:
#include <iostream>
#include <opencv2/opencv.hpp>
int main(int argc, char** argv)
{
// please note that the depthImg is (720 x 576) 8UC1
// let's make a smaller one for testing
uchar flatten[6 * 8] = { 140, 185, 48, 235, 201, 192, 131, 57,
55, 87, 82, 0, 6, 201, 0, 38,
6, 239, 82, 142, 46, 33, 172, 72,
133, 0, 232, 226, 66, 59, 10, 204,
214, 123, 202, 100, 0, 32, 6, 147,
105, 191, 50, 21, 87, 117, 118, 244};
cv::Mat depthImg = cv::Mat(6, 8, CV_8UC1, flatten);
// please ignore the border pixels in this case
for (int i = 1; i < depthImg.cols - 1; i++) {
for (int j = 1; j < depthImg.rows - 1; j++) {
unsigned short sumNonZeroAdjs = 0;
uchar countNonZeroAdjs = 0;
if (depthImg.at<uchar>(j, i) == 0) {
uchar iMinus1 = depthImg.at<uchar>(j, i - 1);
uchar iPlus1 = depthImg.at<uchar>(j, i + 1);
uchar jMinus1 = depthImg.at<uchar>(j - 1, i);
uchar jPlus1 = depthImg.at<uchar>(j + 1, i);
if (iMinus1 != 0) {
sumNonZeroAdjs += iMinus1;
countNonZeroAdjs++;
}
if (iPlus1 != 0) {
sumNonZeroAdjs += iPlus1;
countNonZeroAdjs++;
}
if (jMinus1 != 0) {
sumNonZeroAdjs += jMinus1;
countNonZeroAdjs++;
}
if (jPlus1 != 0) {
sumNonZeroAdjs += jPlus1;
countNonZeroAdjs++;
}
depthImg.at<uchar>(j, i) = sumNonZeroAdjs / countNonZeroAdjs;
}
}
}
std::cout << depthImg << std::endl;
return 0;
}
// prints the following:
[140, 185, 48, 235, 201, 192, 131, 57;
55, 87, 82, 116, 6, 201, 135, 38;
6, 239, 82, 142, 46, 33, 172, 72;
133, 181, 232, 226, 66, 59, 10, 204;
214, 123, 202, 100, 71, 32, 6, 147;
105, 191, 50, 21, 87, 117, 118, 244]
The above filter computes an average of adjacent pixels to fill the 0 pixels. The output from this implementation is satisfactory. However, as we can see, the above prototype is not elegant and painfully slow.
I am looking for a similar logic (using adjacent pixels to fill 0 pixels) but faster (execution time) hole-filling filter inbuilt in OpenCV
PS: I am using OpenCV v4.2.0 on Ubuntu 20.04 LTS.
Update 1
Based on the suggestions, I designed pointer style access. Complete code is shown below:
#include <iostream>
#include <opencv2/opencv.hpp>
void inPlaceHoleFillingExceptBorderPtrStyle(cv::Mat& img) {
typedef uchar T;
T* ptr = img.data;
size_t elemStep = img.step / sizeof(T);
for (int i = 1; i < img.rows - 1; i++) {
for (int j = 1; j < img.cols - 1; j++) {
T& curr = ptr[i * elemStep + j];
if (curr != 0) {
continue;
}
ushort sumNonZeroAdjs = 0;
uchar countNonZeroAdjs = 0;
T iM1 = ptr[(i - 1) * elemStep + j];
T iP1 = ptr[(i + 1) * elemStep + j];
T jM1 = ptr[i * elemStep + (j - 1)];
T jP1 = ptr[i * elemStep + (j + 1)];
if (iM1 != 0) {
sumNonZeroAdjs += iM1;
countNonZeroAdjs++;
}
if (iP1 != 0) {
sumNonZeroAdjs += iP1;
countNonZeroAdjs++;
}
if (jM1 != 0) {
sumNonZeroAdjs += jM1;
countNonZeroAdjs++;
}
if (jP1 != 0) {
sumNonZeroAdjs += jP1;
countNonZeroAdjs++;
}
if (countNonZeroAdjs > 0) {
curr = sumNonZeroAdjs / countNonZeroAdjs;
}
}
}
}
void inPlaceHoleFillingExceptBorder(cv::Mat& img) {
typedef uchar T;
for (int i = 1; i < img.cols - 1; i++) {
for (int j = 1; j < img.rows - 1; j++) {
ushort sumNonZeroAdjs = 0;
uchar countNonZeroAdjs = 0;
if (img.at<T>(j, i) != 0) {
continue;
}
T iM1 = img.at<T>(j, i - 1);
T iP1 = img.at<T>(j, i + 1);
T jM1 = img.at<T>(j - 1, i);
T jP1 = img.at<T>(j + 1, i);
if (iM1 != 0) {
sumNonZeroAdjs += iM1;
countNonZeroAdjs++;
}
if (iP1 != 0) {
sumNonZeroAdjs += iP1;
countNonZeroAdjs++;
}
if (jM1 != 0) {
sumNonZeroAdjs += jM1;
countNonZeroAdjs++;
}
if (jP1 != 0) {
sumNonZeroAdjs += jP1;
countNonZeroAdjs++;
}
if (countNonZeroAdjs > 0) {
img.at<T>(j, i) = sumNonZeroAdjs / countNonZeroAdjs;
}
}
}
}
int main(int argc, char** argv) {
// please note that the img is (720 x 576) 8UC1
// let's make a smaller one for testing
// clang-format off
uchar flatten[6 * 8] = { 140, 185, 48, 235, 201, 192, 131, 57,
55, 87, 82, 0, 6, 201, 0, 38,
6, 239, 82, 142, 46, 33, 172, 72,
133, 0, 232, 226, 66, 59, 10, 204,
214, 123, 202, 100, 0, 32, 6, 147,
105, 191, 50, 21, 87, 117, 118, 244};
// clang-format on
cv::Mat img = cv::Mat(6, 8, CV_8UC1, flatten);
cv::Mat img1 = img.clone();
cv::Mat img2 = img.clone();
inPlaceHoleFillingExceptBorderPtrStyle(img1);
inPlaceHoleFillingExceptBorder(img2);
return 0;
}
/*** expected output
[140, 185, 48, 235, 201, 192, 131, 57;
55, 87, 82, 116, 6, 201, 135, 38;
6, 239, 82, 142, 46, 33, 172, 72;
133, 181, 232, 226, 66, 59, 10, 204;
214, 123, 202, 100, 71, 32, 6, 147;
105, 191, 50, 21, 87, 117, 118, 244]
***/
Update 2
Based on the suggestion, the point style code is further improved as shown below:
void inPlaceHoleFillingExceptBorderImpv(cv::Mat& img) {
typedef uchar T;
size_t elemStep = img.step1();
const size_t margin = 1;
for (size_t i = margin; i < img.rows - margin; ++i) {
T* ptr = img.data + i * elemStep;
for (size_t j = margin; j < img.cols - margin; ++j, ++ptr) {
T& curr = ptr[margin];
if (curr != 0) {
continue;
}
T& north = ptr[margin - elemStep];
T& south = ptr[margin + elemStep];
T& east = ptr[margin + 1];
T& west = ptr[margin - 1];
ushort sumNonZeroAdjs = 0;
uchar countNonZeroAdjs = 0;
if (north != 0) {
sumNonZeroAdjs += north;
countNonZeroAdjs++;
}
if (south != 0) {
sumNonZeroAdjs += south;
countNonZeroAdjs++;
}
if (east != 0) {
sumNonZeroAdjs += east;
countNonZeroAdjs++;
}
if (west != 0) {
sumNonZeroAdjs += west;
countNonZeroAdjs++;
}
if (countNonZeroAdjs > 0) {
curr = sumNonZeroAdjs / countNonZeroAdjs;
}
}
}
}
2
Answers
@QuangHoang advised a fantastic kernel-based approach. Unfortunately, the result is not matching with the expected output.
Therefore, based on various comments from @CrisLuengo, I managed to design time efficient version of the filter, as shown below:
There are three parts: 1) find the zeros, 2) find the mean, and 3) fill the found zeros with mean. So:
Note I just realize that this is plainly taking the average, not the non-zero average. For that operation, you might want to do two filters, one for the sum, one for the non-zero counts, then divide the two: