4

我从这个问题的投票最多的作者的回答中挑选了一个代码段:

https://answers.opencv.org/question/9863/fill-holes-of-a-binary-image/

将其重新格式化为:

cv::Mat image = cv::imread("image.jpg", 0);

cv::Mat image_thresh;
cv::threshold(image, image_thresh, 125, 255, cv::THRESH_BINARY);

// Loop through the border pixels and if they're black, floodFill from there
cv::Mat mask;
image_thresh.copyTo(mask);
for (int i = 0; i < mask.cols; i++) {
 if (mask.at<char>(0, i) == 0) {
     cv::floodFill(mask, cv::Point(i, 0), 255, 0, 10, 10);
 }   
  if (mask.at<char>(mask.rows-1, i) == 0) {

     cv::floodFill(mask, cv::Point(i, mask.rows-1), 255, 0, 10, 10);
   }
 }

 for (int i = 0; i < mask.rows; i++) {

  if (mask.at<char>(i, 0) == 0) {
    cv::floodFill(mask, cv::Point(0, i), 255, 0, 10, 10);
  }

  if (mask.at<char>(i, mask.cols-1) == 0) {
     cv::floodFill(mask, cv::Point(mask.cols-1, i), 255, 0, 10, 10);
  } 
}


 // Compare mask with original.
cv::Mat newImage;
image.copyTo(newImage);
for (int row = 0; row < mask.rows; ++row) {
 for (int col = 0; col < mask.cols; ++col) {
    if (mask.at<char>(row, col) == 0) {
        newImage.at<char>(row, col) = 255;
    }           
 }
}

cv::imshow("filled image", mask);
cv::imshow("Final image", newImage);
cv::imwrite("final.jpg", newImage);
cv::waitKey(0);

return 0;

我知道它使用洪水填充算法来尝试填充孔,并且我已经在另一个示例图像上进行了测试:

在此处输入图像描述

通过检测所有 9 个孔,它的效果非常好。

但是,我尝试了另一个稍微复杂的图像:

在此处输入图像描述

这一次它不起作用,它将用白色填充整个图形,它检测到的孔数为 1700。

我想我可能在这里缺乏大量的形态学知识,但我认为也许我应该先对失败的图像进行“缩小”,然后再将其插入作者的代码中?

专家能否与我分享一些想法,因为我在谷歌上找不到非常相似的孔检测图表。那么当两个孔与二进制图像中的白色路径连接时,孔有什么特别之处?提前谢谢!

4

2 回答 2

4

您的图像存在问题,图像的 3 个侧面周围有一个细白条。该条还连接到左侧的 4 个白色矩形,它创建了一个额外的封闭轮廓/级别,这混淆了我猜的“填充”。

我个人不喜欢使用'floodfill'方法来解决在轮廓内寻找洞的问题。我更喜欢使用带有 'hierarchy' 选项的 'findcontour' 方法。请在此处查看。乍一看,它可能看起来有点复杂,但它提供了我们需要的所有信息。

您正在寻找的孔有两个属性:

  1. 它们是子轮廓(一个孔)
  2. 它们内部没有其他轮廓(不是父级)

查找这些漏洞的代码是:

auto image = cv::imread(in_img_path, cv::ImreadModes::IMREAD_GRAYSCALE);
cv::threshold(image, image, 128, 255, cv::THRESH_OTSU);
std::vector<std::vector<cv::Point>> contours, selected_contours;
std::vector<cv::Vec4i> hierarchy;   
cv::findContours(image, contours, hierarchy, cv::RetrievalModes::RETR_TREE, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++) {
    if (hierarchy[i][2] == -1 && hierarchy[i][3] != -1) //the contour has no children but has a parent
        selected_contours.emplace_back(std::move(contours[i]));
}
cv::Mat drawing_image(image.size(), image.type(), cv::Scalar::all(0));

for (int i = 0; i < selected_contours.size(); i++) {
    cv::drawContours(drawing_image, selected_contours, i, cv::Scalar(255), 1);
}

编辑: 我试过了,在这种情况下,第一次检查似乎是多余的。以下条件是充分的:

if (hierarchy[i][3] != -1) // the contour has a parent

孔数(selected_contours 的大小)为:71

'drawing_image' 看起来像这样: 显示发现孔的图像

于 2020-02-29T11:47:07.537 回答
1

另一种简单的方法是使用轮廓区域过滤。这个想法是找到轮廓cv2.contourArea,然后使用最小阈值区域执行轮廓区域过滤。如果轮廓通过此阈值过滤器,则我们将其视为有效轮廓。这是阈值为 的结果500。您可能需要根据您的图像进行更改。


我用 Python 实现了它,但您可以轻松地将相同的方法应用到 C++ 中

import cv2
import numpy as np

# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.png')
mask = np.zeros(image.shape[:2], dtype=np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Find contours and filter using contour area
cnts = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    area = cv2.contourArea(c)
    if area < 500:
        cv2.drawContours(image,[c],0,(36,255,12),-1)
        cv2.drawContours(mask,[c],0,255,1)

cv2.imshow('mask', mask)
cv2.imshow('image', image)
cv2.waitKey()
于 2020-03-03T01:34:50.880 回答