打开 CV 平凡圆检测——如何获得最小二乘而不是轮廓?
Open CV trivial circle detection -- how to get least squares instead of a contour?
我的目标是通过显微镜准确测量孔的直径。工作流程是:拍摄图像,拟合过程,拟合,将像素半径转换为毫米,写入 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
我的一个建议是查看 cv2.fitEllipse()
希望大家可以利用椭圆的长宽比witdth/height来分辨出奇数
一种方法是Gaussian blur
然后 Otsu's threshold the image to obtain a binary image. From here, we perform morphological opening with a elliptical shaped kernel. This step will effectively remove the tiny noise particles. To obtain a nice estimate of the circle, we find contours and use 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()
我建议像 那样计算一个蒙版,然后简单地计算他计算的蒙版 opening
中的像素数(这是对孔面积的无偏估计),然后使用 radius = sqrt(area/pi)
将该区域转换为半径。这将为您提供与孔面积相同的圆的半径,并对应一种获得最佳拟合圆的方法。
获得最佳拟合圆的另一种方法是获取孔的轮廓(在 nethancy 的回答中由 cv.findContours
在 cnts
中返回),finding its centroid,然后计算每个顶点到质心的平均距离。这将近似*对应于圆与孔周长的最小二乘法拟合。
* 我说近似是因为等高线的顶点是等高线的近似值,这些顶点之间的距离很可能是不均匀的。不过误差应该很小。
这是使用 DIPlib 的代码示例(披露:我是作者)(注意:下面的 import PyDIP
语句要求您安装 DIPlib,而不能使用 pip
安装,在 GitHub 页面上有 Windows 的二进制版本,否则您需要从源代码构建它。
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'
,其中 returns 每个边界像素与质心之间的距离的最大值、平均值、最小值和标准差。从 'Radius'
,我们取第二个值,平均半径。
这输出:
Method 1: 7.227900647539411 mm
Method 2: 7.225178113501325 mm
但请注意,我分配了一个随机像素大小(每像素 0.01 毫米),您需要填写正确的像素到毫米的转换值。
请注意这两个估计非常接近。这两种方法都是很好的、无偏估计。第一种方法在计算上更便宜。
这是另一种拟合圆的方法,方法是使用连通分量从二值图像中获取等效的圆心和半径,然后使用 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
我的目标是通过显微镜准确测量孔的直径。工作流程是:拍摄图像,拟合过程,拟合,将像素半径转换为毫米,写入 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
我的一个建议是查看 cv2.fitEllipse()
希望大家可以利用椭圆的长宽比witdth/height来分辨出奇数
一种方法是Gaussian blur
然后 Otsu's threshold the image to obtain a binary image. From here, we perform morphological opening with a elliptical shaped kernel. This step will effectively remove the tiny noise particles. To obtain a nice estimate of the circle, we find contours and use 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()
我建议像 opening
中的像素数(这是对孔面积的无偏估计),然后使用 radius = sqrt(area/pi)
将该区域转换为半径。这将为您提供与孔面积相同的圆的半径,并对应一种获得最佳拟合圆的方法。
获得最佳拟合圆的另一种方法是获取孔的轮廓(在 nethancy 的回答中由 cv.findContours
在 cnts
中返回),finding its centroid,然后计算每个顶点到质心的平均距离。这将近似*对应于圆与孔周长的最小二乘法拟合。
* 我说近似是因为等高线的顶点是等高线的近似值,这些顶点之间的距离很可能是不均匀的。不过误差应该很小。
这是使用 DIPlib 的代码示例(披露:我是作者)(注意:下面的 import PyDIP
语句要求您安装 DIPlib,而不能使用 pip
安装,在 GitHub 页面上有 Windows 的二进制版本,否则您需要从源代码构建它。
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'
,其中 returns 每个边界像素与质心之间的距离的最大值、平均值、最小值和标准差。从 'Radius'
,我们取第二个值,平均半径。
这输出:
Method 1: 7.227900647539411 mm
Method 2: 7.225178113501325 mm
但请注意,我分配了一个随机像素大小(每像素 0.01 毫米),您需要填写正确的像素到毫米的转换值。
请注意这两个估计非常接近。这两种方法都是很好的、无偏估计。第一种方法在计算上更便宜。
这是另一种拟合圆的方法,方法是使用连通分量从二值图像中获取等效的圆心和半径,然后使用 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