【问题标题】:Hole-Filling Filter in OpenCV C++OpenCV C++ 中的填孔过滤器
【发布时间】:2022-06-11 13:37:15
【问题描述】:

我有一个填孔过滤器的基本实现,如下所示:

#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]

上述过滤器计算相邻像素的平均值以填充 0 像素。这个实现的输出是令人满意的。然而,正如我们所见,上面的原型并不优雅,而且速度慢得令人痛苦。

我正在寻找类似的逻辑(使用相邻像素填充 0 像素)但更快(执行时间)OpenCV 中内置的孔填充过滤器

PS:我在 Ubuntu 20.04 LTS 上使用 OpenCV v4.2.0。

更新 1

根据建议,我设计了指针样式访问。完整代码如下:

#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]
***/

更新 2

根据建议,点样式代码进一步改进如下:


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;
      }
    }
  }
}

【问题讨论】:

  • 我可以要求简要解释一下收到否决票吗?
  • 你已经在 Python 中得到了一个有效的实现。现在你想要一个类似但不同的 c++,用勺子喂给你?我误解了吗?自己懒惰地把它重新写成c++。如果/当您遇到困难时,请提出具体问题。 (没有投反对票,但很想)
  • 好的!如果把上面的 sn-p 写成 C++ 让你开心,我很快就会去做。带来不便敬请谅解。请稍候。
  • @enhzflep:我添加了具有相同概念的 CPP 代码。请看一看。显然,迭代这样的图像并不好。不确定inpainting 是否可以提供帮助。
  • “脏孔填充过滤器”?

标签: c++ opencv image-processing


【解决方案1】:

分为三个部分:1) 找到零点,2) 找到平均值,以及 3) 用平均值填充找到的零点。所以:

/****
 * in-place fill zeros with the mean of the surrounding neighborhoods
 ***/
void fillHoles(Mat gray){    
    // find the zeros
    Mat mask = (gray == 0);
    
    // find the mean with filter2d
    Mat kernel = (Mat_<double>(3,3) << 
    1/8, 1/8, 1/8
    1/8, 0  , 1/8
    1/8, 1/8, 1/8
    );
    Mat avg;
    cv::filter2d(gray, avg, CV_8U, kernel)
    
    // then fill the zeros, only where indicated by `mask`
    cv::bitwise_or(gray, avg, gray, mask);

}

注意我只是意识到这显然是取平均值,而不是非零平均值。对于该操作,您可能需要执行两个过滤器,一个用于求和,一个用于非零计数,然后将两者相除:

// find the neighborhood sum with filter2d
Mat kernel = (Mat_<double>(3,3) << 
1, 1, 1
1, 0, 1
1, 1, 1
);
Mat sums;
cv::filter2d(gray, sums, CV_64F, kernel);

// find the neighborhood count with filter2d
Mat counts;
cv::filter2d(gray!=0, counts, CV_64F, kernel);    
counts /= 255; // because gray!=0 returns 255 where true

// force counts to 1 if 0, so we can divide later
cv::max(counts, 1, counts);

Mat out;
cv::divide(sums, counts, out);

out.convertTo(gray, CV_8U);

【讨论】:

  • 非常感谢。你能解释一下每一步吗?例如,请问您是如何决定/设计 3x3 内核等的。
  • 您可以在此处阅读有关内核的信息:docs.opencv.org/3.4/d4/dbd/tutorial_filter_2d.html
  • 我刚刚用非零邻居的平均值更新了答案。您可能还想检查非零除法。
  • 非常感谢。我用我的虚拟输入矩阵编译了代码。不幸的是,输出与我的输出不匹配。很抱歉给您带来不便,请您再看一遍好吗?我也在帖子中添加了“更新”部分。
猜你喜欢
  • 2017-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-21
  • 1970-01-01
  • 2012-03-04
相关资源
最近更新 更多