OpenCV:在图像中拟合单个圆(使用Python)

5

我有一张这样的图片:

enter image description here

我需要将椭圆拟合到黑色区域(注意:必须是椭圆,而不是圆)。在OpenCV中,最好的方法是什么?到目前为止,我的第一步是对其应用自适应(Otsu)阈值,结果如下:

enter image description here

但我不确定接下来该怎么做。我正在用Python编写应用程序,但更多的是在寻找算法设计。

根据回复/评论进行编辑:

好的,所以我已经尝试了形态学。根据OpenCV文档,我对其进行了3次“闭合”操作(膨胀,然后腐蚀),以去除小颗粒,结果如下:

enter image description here

然后,为了将其扩展回原始形状附近,我进行了3次迭代的“开”操作(先腐蚀,再膨胀),结果如下:

enter image description here

从这里开始,我进行了Canny边缘检测,结果如下:

enter image description here

现在,我在它上面使用了findContours,但遇到了一个问题。它在边缘找到了数十个轮廓,每个轮廓都是沿着周长的短线段。这意味着,即使我取最大尺寸的轮廓,它可能只代表周长的10%,这不足以准确拟合椭圆。这就是为什么@Demi-Lune建议的其他问题对我不起作用的原因; 它们都有非常清晰,锐利的边缘,并且findContours找到了一个很好的单一轮廓,覆盖了每个形状的整个周长,但这并不适用于我的混乱图像。那么,从这里拟合椭圆的最佳方法是什么?

你尝试过访问 https://dev59.com/wp_ha4cB1Zd3GeqPtRmW 或者 https://dev59.com/iJPfa4cB1Zd3GeqPFZo8 吗? - Demi-Lune
@Demi-Lune,请看我在问题中的编辑。基本上,那些问题具有漂亮干净的形状和清晰定义的边缘,而findContours会为这些找到漂亮的单个轮廓;但对于我的混乱图像,这种方法行不通。 - Jordan
找到黑色点的最小面积矩形。它代表了你的椭圆。 - Miki
只有在圆圈外没有杂点的情况下才能奏效,对吧?对于这个特定图像是这种情况,但并不普遍(我处理的一些图像可能会在形态学处理后仍然有时有一些点散落在圆圈外面)。另外,如果圆圈的一侧被“压扁”了(所以它看起来更像一个“D”),那么它将适合比最佳椭圆更小的椭圆。 - Jordan
#1 正确。 #2 “最佳”是根据度量标准而定的……“最小二乘误差”和“与边界点重叠更多”会产生不同的结果。 - Miki
2个回答

5

如果对象具有圆形,则使用cv2.minEnclosingCircle是很好的选择。否则,可以使用cv2.fitEllipse来找到最适合该对象的椭圆形。记得在黑色背景中使用白色对象进行轮廓查找。

import cv2
import numpy as np

img = cv2.imread("1.jpg")

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_,thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
thresh = cv2.bitwise_not(thresh)

element = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(5, 5))

morph_img = thresh.copy()
cv2.morphologyEx(src=thresh, op=cv2.MORPH_CLOSE, kernel=element, dst=morph_img)

contours,_ = cv2.findContours(morph_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

areas = [cv2.contourArea(c) for c in contours]
sorted_areas = np.sort(areas)

#bounding box (red)
cnt=contours[areas.index(sorted_areas[-1])] #the biggest contour
r = cv2.boundingRect(cnt)
cv2.rectangle(img,(r[0],r[1]),(r[0]+r[2],r[1]+r[3]),(0,0,255),2)

#min circle (green)
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
cv2.circle(img,center,radius,(0,255,0),2)

#fit ellipse (blue)
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(img,ellipse,(255,0,0),2)


cv2.imshow("morph_img",morph_img)
cv2.imshow("img", img)
cv2.waitKey()

enter image description here enter image description here


谢谢,这个效果相当不错。假设我想要对 minBoundingRectangle 函数(具体来说是内切椭圆)和 fitEllipse 函数的结果进行“平均”,我该怎么做呢? - Jordan

1
为什么不像“关闭”然后“打开”一样做事情来清理所有混乱。
原始图像:

Raw image

Otsu :

Otsu

关闭 + 打开; 两者都使用 7x7 的内核; 二进制图像现在更加美观和清晰。

Close + Open

只检测到一个轮廓:

One contour

椭圆指的是:(请注意你的图像是圆形,因此椭圆应该呈圆形状)

Ellipse


谢谢@Vu。现在,假设二进制阈值化、形态变换后的图像是实心的,但是有一个“凸起”在侧面(一个尖锐的突起)。如果我在这个上使用fitEllipse,那么这个凸起将影响拟合并使其更加椭圆形,但我需要一种解决方案,它最大化拟合点的数量,而不是最小二乘法。也就是说,它应该尽可能地匹配形状的轮廓。 - Jordan
不明白你在说什么。你应该添加一些图片来澄清你的问题。 - Vu Gia Truong

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