如何识别二进制图中由线连接的孔
How to identify holes connected by lines in binary graph
我从这个问题中投票最高的作者的回答中挑选了一段代码:
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;
我知道它使用 floodfill 算法来尝试填充孔洞,我已经在另一个示例图像上进行了测试:
它通过检测所有 9 个孔非常有效。
然而,我尝试了另一个稍微复杂的图像:
这次不行,它会用白色填充整个图,它检测到的孔数是1700。
我想我可能缺乏大量的形态学知识,但我想也许我应该先对失败的图像做 "shirnking",然后再将其插入作者的代码?
专家能否与我分享一些想法,因为我在 google 上找不到非常相似的图表来检测漏洞。那么二值图中两个孔用白色路径相连的孔有什么特别之处呢?先谢谢了!
您的图像有问题,图像的 3 个边周围有一个细白条。这个栏也连接到左边的 4 个白色矩形,它创建了一个额外的封闭 contour/level,混淆了我猜的 'floodfill'。
我个人不太喜欢用'floodfill'的方法来解决等高线内找洞的问题。我更喜欢使用带有 'hierarchy' 选项的 'findcontour' 方法。请看一下here。乍一看,它可能看起来有点复杂,但它提供了我们需要的所有信息。
您要查找的孔具有两个属性:
- 它们是child轮廓(一个洞)
- 它们内部没有其他轮廓(不是 parent)
寻找这些洞的代码是:
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
另一种简单的方法是使用轮廓区域过滤。这个想法是 find contours then perform contour area filtering with 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()
我从这个问题中投票最高的作者的回答中挑选了一段代码:
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;
我知道它使用 floodfill 算法来尝试填充孔洞,我已经在另一个示例图像上进行了测试:
它通过检测所有 9 个孔非常有效。
然而,我尝试了另一个稍微复杂的图像:
这次不行,它会用白色填充整个图,它检测到的孔数是1700。
我想我可能缺乏大量的形态学知识,但我想也许我应该先对失败的图像做 "shirnking",然后再将其插入作者的代码?
专家能否与我分享一些想法,因为我在 google 上找不到非常相似的图表来检测漏洞。那么二值图中两个孔用白色路径相连的孔有什么特别之处呢?先谢谢了!
您的图像有问题,图像的 3 个边周围有一个细白条。这个栏也连接到左边的 4 个白色矩形,它创建了一个额外的封闭 contour/level,混淆了我猜的 'floodfill'。
我个人不太喜欢用'floodfill'的方法来解决等高线内找洞的问题。我更喜欢使用带有 'hierarchy' 选项的 'findcontour' 方法。请看一下here。乍一看,它可能看起来有点复杂,但它提供了我们需要的所有信息。
您要查找的孔具有两个属性:
- 它们是child轮廓(一个洞)
- 它们内部没有其他轮廓(不是 parent)
寻找这些洞的代码是:
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
另一种简单的方法是使用轮廓区域过滤。这个想法是 find contours then perform contour area filtering with 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()