如何使用OpenCV获取金属、闪亮物体的轮廓

5
我正在尝试找到类似下图所示的金属光泽物体的轮廓:

enter image description here

我已经使用了OpenCV中的Canny算法来获取图像的轮廓,但是结果(如下所示)并没有完全绘制出原始图像的轮廓。在右下方有一个大间隙。

enter image description here

我请求任何可以帮助我改进轮廓的资源,使其连续并且(非常接近)原始图像形状的类型。

3个回答

3

一个简单的方法是先应用大范围的高斯模糊以平滑图像,然后进行自适应阈值处理。在假设物体是图像中最大的东西的前提下,我们可以找到轮廓并使用轮廓面积过滤以获取最大的轮廓。

二值化图像

enter image description here

结果

enter image description here

代码

import cv2
import numpy as np

# Load image, convert to grayscale, Gaussian Blur, adaptive threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (13,13), 0)
thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,51,7)

# Morph close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=1)

# Find contours, sort for largest contour, draw contour
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
for c in cnts:
    cv2.drawContours(image, [c], -1, (36,255,12), 2)
    break

cv2.imshow('thresh', thresh)
cv2.imshow('image', image)
cv2.waitKey()

嗨@nathancy。代码输出与您在此提供的输出不同。有什么区别? - jsibs
我正在使用您提供的输入图像和完全相同的代码。我得到了相同的正确输出。我正在使用 python 3.7.4numpy==1.14.5,Windows 10,opencv-python==4.2.0.32 - nathancy

3
在Python/OpenCV中,您可以通过以下步骤实现:
  • 读取输入
  • 转换为HSV颜色空间并提取饱和度通道(因为灰色没有饱和度,而绿色有)
  • 模糊图像以减少噪声
  • 阈值处理
  • 应用形态学闭操作来填充闪亮物体的内部空洞
  • 查找轮廓并过滤最大的轮廓(虽然应该只有一个)
  • 在输入图像上绘制轮廓
  • 保存结果

输入:

enter image description here

import cv2
import numpy as np

# read input
img = cv2.imread('shiny.jpg')

# convert to hsv and get saturation channel
sat = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)[:,:,1]

# do a little Gaussian filtering
blur = cv2.GaussianBlur(sat, (3,3), 0)


# threshold and invert to create initial mask
mask = 255 - cv2.threshold(blur, 100, 255, cv2.THRESH_BINARY)[1]

# apply morphology close to fill interior regions in mask
kernel = np.ones((15,15), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)


# get outer contours from inverted mask and get the largest (presumably only one due to morphology filtering)
cntrs = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cntrs = cntrs[0] if len(cntrs) == 2 else cntrs[1]
result = img.copy()
area_thresh = 0
for c in cntrs:
    area = cv2.contourArea(c)
    if area > area_thresh:
        area = area_thresh
        big_contour = c

# draw largest contour
cv2.drawContours(result, [big_contour], -1, (0,0,255), 2)


# write result to disk
cv2.imwrite("shiny_mask.png", mask)
cv2.imwrite("shiny_outline.png", result)

# display it
cv2.imshow("IMAGE", img)
cv2.imshow("MASK", mask)
cv2.imshow("RESULT", result)
cv2.waitKey(0)


阈值和过滤器掩码:

enter image description here

结果:

enter image description here

另一种方法是使用cv2.inRange()函数对绿色进行阈值处理。


2
这是另一个可能的解决方案,用C++实现并使用k-means作为主要分割方法。这种分割背后的思想是,k-means(一种聚类方法)将分组相似值的颜色。在这里,我将k-means设置为查找2种颜色的聚类:背景色和前景色。
让我们看一下代码:
std::string imageName = "C://opencvImages/LSl42.jpg";
cv::Mat testImage =  cv::imread( imageName );
//apply Gaussian Blur to smooth out the input:
cv::GaussianBlur( testImage, testImage, cv::Size(3,3), 0, 0 );

您的图像有噪点(高频)背景。您可以将其稍微模糊化以获得更平滑的渐变并改善分割。我使用标准内核大小为3 x 3的高斯模糊进行了处理。查看一下输入图像和平滑后图像之间的区别:

enter image description here

非常酷。现在,我可以将这个图像传递给K-means。 imageQuantization是从这里获取的函数,它基于K-means实现分割。正如我所提到的,它可以将类似值的颜色分组成簇。那非常方便!让我们将颜色分为2组:前景对象和背景。
int segmentationClusters = 2; //total number of clusters in which the input will be segmented...
int iterations = 5; // k-means iterations
cv::Mat segmentedImage = imageQuantization( testImage, segmentationClusters, iterations );

结果:

enter image description here

相当不错,是吧?
您可以直接在这个图像上应用边缘检测,但我想使用一点形态学来改进它。我首先将图像转换为灰度图像,应用Outsu的阈值处理,然后执行形态学闭合操作:
//compute grayscale image of the segmented output:
cv::Mat grayImage;
cv::cvtColor( segmentedImage, grayImage, cv::COLOR_RGB2GRAY );

//get binary image via Otsu:
cv::Mat binImage;
cv::threshold( grayImage, binImage, 0, 255, cv::THRESH_OTSU );

//Perform a morphological closing to lose up holes in the target blob:
cv::Mat SE = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(3, 3) );
cv::morphologyEx( binImage, binImage, cv::MORPH_CLOSE, SE, cv::Point(-1,-1), 10 );

我使用了一个尺寸为3x3的矩形结构元素和10次闭运算,这是结果:

enter image description here

接下来,使用Canny边缘检测器检测边缘:
cv::Mat testEdges;
//setup lower and upper thresholds for Canny’s edge detection:
float lowerThreshold = 30;
float upperThreshold = 3 * lowerThreshold;
cv::Canny( binImage, testEdges, lowerThreshold, upperThreshold );

最后,获取blob的轮廓:
std::vector<std::vector<cv::Point> > contours;
std::vector<cv::Vec4i> hierarchy;

cv::findContours( testEdges, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0) );

for( int i = 0; i< contours.size(); i++ )
{
 cv::Scalar color = cv::Scalar( 0,255,0 );
 cv::drawContours( resizedImage, contours, i, color, 2, 8, hierarchy, 0, cv::Point() );
}

我得到的最终结果是:

enter image description here

想要通过扩大轮廓来改善结果吗?在将图像传递给Canny边缘检测之前,尝试使用几次迭代对二值图像进行膨胀。这是一个测试,将图像膨胀5次:

enter image description here


当你说“膨胀”时,是否指的是morphologyEx方法上的迭代次数? - jsibs
@jsibs 是的。该方法有一个名为“iterations”的参数,用于指定操作将被应用多少次。对于最后一张图像,我将迭代次数设置为5。 - stateMachine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接