打开 CV 平凡圆检测——如何得到最小二乘而不是轮廓?

Posted

技术标签:

【中文标题】打开 CV 平凡圆检测——如何得到最小二乘而不是轮廓?【英文标题】:Open CV trivial circle detection -- how to get least squares instead of a contour? 【发布时间】:2020-06-02 23:08:22 【问题描述】:

我的目标是通过显微镜准确测量孔的直径。工作流程是:拍摄图像,进行拟合,拟合,将半径(以像素为单位)转换为 mm,写入 csv

这是我用于测量孔直径的图像处理脚本的输出。我遇到了一个问题,我的圆形拟合似乎优先匹配轮廓,而不是最小二乘法。

我也可以在这样的情况下平均多次拟合:

我的问题是我喜欢快速扫描以确保圆圈适合。权衡是我拥有的拟合越多,拟合越现实,我拥有的越少就越容易确保数字正确。我的圈子并不总是像这个圈子那样漂亮和圆,所以这对我很重要。

如果您可以看一下并告诉我如何在大约 5 个圆上执行更多最小二乘法,这是我的脚本拟合圆的部分。我不想使用最小圆检测,因为流体正在流过这个孔,所以我希望它更像是水力直径——谢谢!

(thresh, blackAndWhiteImage0) = cv2.threshold(img0, 100, 255, cv2.THRESH_BINARY) #make black + white 
median0 = cv2.medianBlur(blackAndWhiteImage0, 151) #get rid of noise 
circles0 = cv2.HoughCircles(median0,cv2.HOUGH_GRADIENT,1,minDist=5,param1= 25, param2=10, minRadius=min_radius_small,maxRadius=max_radius_small) #fit circles to image

【问题讨论】:

minEnclosureCircle 和 RANSAC 圆检测都应该适合您的用例 “更像是水力直径”——据我所知,这是 4 倍面积/周长。为此,您只需要孔的轮廓(无论如何,它不是一个精确的圆,因为它看起来)。你检查过这种方法吗? 【参考方案1】:

这是另一种拟合圆的方法,方法是使用连接组件从二进制图像中获取等效圆心和半径,并使用 Python/OpenCV/Skimage 从该图像中绘制圆。

输入:

import cv2
import numpy as np
from skimage import measure

# load image and set the bounds
img = cv2.imread("dark_circle.png")

# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# blur
blur = cv2.GaussianBlur(gray, (3,3), 0)

# threshold
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# apply morphology open with a circular shaped kernel
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
binary = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)

# find contour and draw on input (for comparison with circle)
cnts = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
c = cnts[0]
result = img.copy()
cv2.drawContours(result, [c], -1, (0, 255, 0), 1)

# find radius and center of equivalent circle from binary image and draw circle
# see https://scikit-image.org/docs/dev/api/skimage.measure.html#skimage.measure.regionprops
# Note: this should be the same as getting the centroid and area=cv2.CC_STAT_AREA from cv2.connectedComponentsWithStats and computing radius = 0.5*sqrt(4*area/pi) or approximately from the area of the contour and computed centroid via image moments.
regions = measure.regionprops(binary)
circle = regions[0]
yc, xc = circle.centroid
radius = circle.equivalent_diameter / 2.0
print("radius =",radius, "  center =",xc,",",yc)
xx = int(round(xc))
yy = int(round(yc))
rr = int(round(radius))
cv2.circle(result, (xx,yy), rr, (0, 0, 255), 1)

# write result to disk
cv2.imwrite("dark_circle_fit.png", result)

# display it
cv2.imshow("image", img)
cv2.imshow("thresh", thresh)
cv2.imshow("binary", binary)
cv2.imshow("result", result)
cv2.waitKey(0)

结果显示轮廓(绿色)与圆形拟合(红色)相比:

圆半径和圆心:

radius = 117.6142467296168   center = 220.2169911178609 , 150.26823599797507

最小二乘拟合方法(在轮廓点和圆之间)可以使用 Scipy 获得。例如,参见:

https://gist.github.com/lorenzoriano/6799568

https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html

【讨论】:

对我来说,最小二乘与质心之间的误差将小于我将像素转换为 mm 的误差。感谢您的解决方案,我认为这比我的脚本更强大【参考方案2】:

我建议像nathancy's answer 一样计算一个掩码,然后简单地计算他计算的掩码opening 中的像素数(这是对孔面积的无偏估计),然后翻译使用radius = sqrt(area/pi) 将区域设置为半径。这将为您提供与孔面积相同的圆的半径,并对应于一种获得最佳拟合圆的方法。

获得最佳拟合圆的另一种方法是获取孔的轮廓(如cv.findContours 在 nethancy 的答案中返回的 cnts),finding its centroid,然后计算每个顶点到质心。这将大约*对应于圆与孔周长的最小二乘拟合。

* 我说大概是因为轮廓的顶点是轮廓的近似值,并且这些顶点之间的距离可能不均匀。不过错误应该很小。


这是使用DIPlib 的代码示例(披露:我是作者)(注意:下面的import PyDIP 语句要求您安装DIPlib,您不能使用pip 安装它,Windows 有二进制版本在 GitHub 页面上,否则您需要从源代码构建它)。

import PyDIP as dip
import imageio
import math

img = imageio.imread('https://i.stack.imgur.com/szvc2.jpg')
img = dip.Image(img[:,2600:-1])
img.SetPixelSize(0.01, 'mm')      # Use your actual values!
bin = ~dip.OtsuThreshold(dip.Gauss(img, [3]))
bin = dip.Opening(bin, 25)
#dip.Overlay(img, bin - dip.BinaryErosion(bin, 1, 3)).Show()

msr = dip.MeasurementTool.Measure(dip.Label(bin), features=['Size', 'Radius'])
#print(msr)

print('Method 1:', math.sqrt(msr[1]['Size'][0] / 3.14), 'mm')
print('Method 2:', msr[1]['Radius'][1], 'mm')

MeasurementTool.Measure 函数计算'Size',即面积;和'Radius',它返回每个边界像素和质心之间距离的最大值、平均值、最小值和标准差。从'Radius',我们取第二个值,平均半径。

这个输出:

Method 1: 7.227900647539411 mm
Method 2: 7.225178113501325 mm

但请注意,我分配了一个随机像素大小(每像素 0.01 毫米),您需要填写正确的像素到毫米的转换值。

请注意这两个估计值非常接近。这两种方法都是好的、无偏的估计。第一种方法的计算成本更低。

【讨论】:

【参考方案3】:

我的一个建议是查看cv2.fitEllipse()

希望您可以使用椭圆宽度/高度之间的纵横比来区分奇数。

【讨论】:

那么我的“直径”就可以是 sqr(a*b) 看看这里:docs.opencv.org/2.4/modules/imgproc/doc/… 看来我只能用一组点来做到这一点 是的:分割图像后(过滤它以便很容易地将孔(前景)与背景分开),您使用findContours 来获取这些点。在您的情况下,您将需要简化的仅外部轮廓(RETR_EXTERNAL)。我将执行以下操作:1. 获取我在上面链接的示例代码,2. 将图像换成你的,3. 更改 findContours 参数(和其他参数)以最适合你的图像。完成后,使用更多图像进行测试并调整稳健性。 HTH【参考方案4】:

一种方法是Gaussian blur 然后Otsu's threshold图像得到二值图像。从这里开始,我们使用elliptical shaped kernel 执行morphological opening。此步骤将有效去除微小的噪声粒子。为了得到一个很好的圆估计,我们找到轮廓并使用cv2.minEnclosingCircle(),它也给了我们中心点和半径。这是一个可视化:

输入图像(截图)

二值图像

变形打开

结果-> 带半径的结果

Radius: 122.11396026611328

从这里您可以根据您的校准比例将半径(以像素为单位)转换为毫米

代码

import cv2
import numpy as np

# Load image, convert to grayscale, Gaussian blur, then Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Morph open with a elliptical shaped kernel
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)

# Find contours and draw minimum enclosing circle
cnts = cv2.findContours(opening, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    ((x, y), r) = cv2.minEnclosingCircle(c)
    cv2.circle(image, (int(x), int(y)), int(r), (36, 255, 12), 2)
    print('Radius: '.format(r))

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

【讨论】:

您对我的输出与我的问题相同,因为圆圈内的“白色”空间不等于圆圈外的“黑色”空间。向底部它们大致相等,然后随着您向上移动圆圈,空白区域越来越大。这有意义吗?

以上是关于打开 CV 平凡圆检测——如何得到最小二乘而不是轮廓?的主要内容,如果未能解决你的问题,请参考以下文章

数据拟合:最小二乘二维圆拟合的C++实现(另一种方法)

使用 OpenCV 进行圆检测

CV 霍夫圆参数检测圆

最小二乘法拟合圆 转

opencv入门项目——车道线检测

线性代数之——最小二乘