c++ - OpenCV C++ 中的孔填充滤波器

标签 c++ opencv image-processing

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

#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 像素。此实现的输出令人满意。然而,正如我们所看到的,上面的原型(prototype)并不优雅,而且速度慢得令人痛苦。

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

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

最佳答案

共有三个部分: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);

关于c++ - OpenCV C++ 中的孔填充滤波器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72559738/

相关文章:

c++ - 在C++中返回IO对象的目的是什么?

c++ - 迭代第二个循环,在 CUDA 中减少总和

c++ - 从指针转换为引用

c++ - 如何在 C/C++ 中存储可变长度字符串

OpenCV distanceTransform 返回意外结果(空 Mat 或原始图像)

c++ - 如何从资源位图文件中为 directshow 滤镜加载图像数据?

python - Windows下的opencv编解码器

python - 是否有在图像上应用置换贴图/矩阵的功能?

c - 开放CV!如何捕捉视频?

python - 如何在 Tensorflow 中为预取数据集绘制混淆矩阵