这是一个很大的建模问题。我有以下建议/想法:
- 将图像分割为RGB,然后进行处理。
- 预处理。
- 动态参数搜索。
- 添加约束。
- 确保您要检测的内容。
更详细地:
1:如其他答案所述,直接转换为灰度会浪费太多信息-亮度与背景相似的任何圆圈都会丢失。更好地考虑隔离或在不同色彩空间中使用色彩通道。这里有两种方法:在每个预处理通道上HoughCircles
执行HoughCircles
,然后组合结果,或者处理通道,然后组合它们,然后操作HoughCircles
。在下面的尝试中,我尝试了第二种方法,即拆分为RGB通道,进行处理然后进行合并。为避免合并时图像过饱和,我使用cv.And
为避免此问题(在此阶段,我的圈子始终是白色背景上的黑环/光盘)。
2:预处理非常棘手,通常最好配合使用。我使用了AdaptiveThreshold
,它是一种非常强大的卷积方法,可以通过基于像素的局部平均值对像素进行阈值处理来增强图像中的边缘(类似的过程也发生在哺乳动物视觉系统的早期路径中)。这也很有用,因为它可以减少一些噪音。我只使用了一次dilate/erode
。我保留了其他参数的设置方式。在HoughCircles
确实有助于找到“实心圆”之前,似乎使用Canny
,因此最好将其保留。这种预处理非常繁重,并且在使用更多“血圆”时可能导致误报,但在我们的情况下这也许是可取的?
3:正如您已经注意到的,实际上需要从docs中为每个图像调整HoughCircles参数param2
(您的参数LOW
)以获得最佳解决方案:
它越小,可能会检测到更多的假圆圈。
麻烦的是,每个图像的最佳位置都会有所不同。我认为最好的方法是设置一个条件,并搜索不同的param2
值,直到满足该条件为止。您的图像显示了不重叠的圆,并且当param2
太低时,我们通常会得到重叠圆的负载。因此,我建议搜索:
不重叠且不包含圆圈的最大数量
因此,我们一直用不同的param2
值调用HoughCircles,直到满足为止。我在下面的示例中这样做,只是增加param2
直到达到阈值假设。如果执行二进制搜索来查找何时满足,它将更快(并且相当容易做到),但是您需要谨慎对待异常处理,因为opencv经常会为无辜的param2
值抛出错误(至少在我的安装)。我们非常有用的另一种条件是圈数。
4:我们可以在模型中添加更多约束吗?我们可以告诉模型更多的内容,这可以使我们轻松完成检测圆环的任务。例如,我们是否知道:
- 圈数。 -甚至上限或下限也是有帮助的。
- 圆圈,背景或“非圆圈”的可能颜色。
- 他们的大小。
- 它们可以在图像中的位置。
5:图像中的某些斑点只能被轻松地称为圆圈!考虑第二个图像中的两个“非圆形斑点”,我的代码找不到它们(好!),但是...如果我“ photoshop”它们,使其更加圆形,我的代码可以找到它们...也许如果您想检测不是圆的东西,可以使用另一种方法,例如Tim Lukins
( Tim Lukins
。
问题
通过进行大量预处理AdaptiveThresholding
和'Canny',图像中的特征可能会出现很多失真,这可能会导致错误的圆形检测或错误的半径报告。例如,处理后的大型实心圆盘可能会出现一个环,因此HughesCircles可能会找到内环。此外,甚至文档也指出:
...通常该功能可以很好地检测圆的中心,但是可能无法找到正确的半径。
如果您需要更精确的半径检测,建议使用以下方法(未实现):
- 在原始图像上,从扩大的十字形中报告的圆心的光线轨迹开始(4线:上/下/左/右)
- 在每个RGB通道中分别进行此操作
- 以明智的方式(例如,根据需要翻转,偏移,缩放等)将每个射线的每个通道的此信息组合起来
- 对每条光线的前几个像素取平均值,用它来检测光线发生明显偏离的位置。
- 这4个点是圆周上点的估计。
- 使用这四个估计来确定更准确的半径和中心位置(!)。
- 这可以通过使用扩展环而不是四射线来概括。
结果
最后的代码在很多时候都做得很好,这些示例是通过如下代码完成的:
检测第一张图片中的所有圈子:
在应用Canny滤镜之前,预处理后的图像的外观(不同的色环非常明显):
检测第二张图像中除了两个(斑点)以外的所有图像:
更改后的第二张图像(斑点是圆形的,大椭圆形变得更圆了,从而改善了检测),所有检测到:
在这幅康定斯基绘画中的中心检测方面做得很好(由于边界条件,我找不到同心环)。
码:
import cv
import numpy as np
output = cv.LoadImage('case1.jpg')
orig = cv.LoadImage('case1.jpg')
# create tmp images
rrr=cv.CreateImage((orig.width,orig.height), cv.IPL_DEPTH_8U, 1)
ggg=cv.CreateImage((orig.width,orig.height), cv.IPL_DEPTH_8U, 1)
bbb=cv.CreateImage((orig.width,orig.height), cv.IPL_DEPTH_8U, 1)
processed = cv.CreateImage((orig.width,orig.height), cv.IPL_DEPTH_8U, 1)
storage = cv.CreateMat(orig.width, 1, cv.CV_32FC3)
def channel_processing(channel):
pass
cv.AdaptiveThreshold(channel, channel, 255, adaptive_method=cv.CV_ADAPTIVE_THRESH_MEAN_C, thresholdType=cv.CV_THRESH_BINARY, blockSize=55, param1=7)
#mop up the dirt
cv.Dilate(channel, channel, None, 1)
cv.Erode(channel, channel, None, 1)
def inter_centre_distance(x1,y1,x2,y2):
return ((x1-x2)**2 + (y1-y2)**2)**0.5
def colliding_circles(circles):
for index1, circle1 in enumerate(circles):
for circle2 in circles[index1+1:]:
x1, y1, Radius1 = circle1[0]
x2, y2, Radius2 = circle2[0]
#collision or containment:
if inter_centre_distance(x1,y1,x2,y2) < Radius1 + Radius2:
return True
def find_circles(processed, storage, LOW):
try:
cv.HoughCircles(processed, storage, cv.CV_HOUGH_GRADIENT, 2, 32.0, 30, LOW)#, 0, 100) great to add circle constraint sizes.
except:
LOW += 1
print 'try'
find_circles(processed, storage, LOW)
circles = np.asarray(storage)
print 'number of circles:', len(circles)
if colliding_circles(circles):
LOW += 1
storage = find_circles(processed, storage, LOW)
print 'c', LOW
return storage
def draw_circles(storage, output):
circles = np.asarray(storage)
print len(circles), 'circles found'
for circle in circles:
Radius, x, y = int(circle[0][2]), int(circle[0][0]), int(circle[0][1])
cv.Circle(output, (x, y), 1, cv.CV_RGB(0, 255, 0), -1, 8, 0)
cv.Circle(output, (x, y), Radius, cv.CV_RGB(255, 0, 0), 3, 8, 0)
#split image into RGB components
cv.Split(orig,rrr,ggg,bbb,None)
#process each component
channel_processing(rrr)
channel_processing(ggg)
channel_processing(bbb)
#combine images using logical 'And' to avoid saturation
cv.And(rrr, ggg, rrr)
cv.And(rrr, bbb, processed)
cv.ShowImage('before canny', processed)
# cv.SaveImage('case3_processed.jpg',processed)
#use canny, as HoughCircles seems to prefer ring like circles to filled ones.
cv.Canny(processed, processed, 5, 70, 3)
#smooth to reduce noise a bit more
cv.Smooth(processed, processed, cv.CV_GAUSSIAN, 7, 7)
cv.ShowImage('processed', processed)
#find circles, with parameter search
storage = find_circles(processed, storage, 100)
draw_circles(storage, output)
# show images
cv.ShowImage("original with circles", output)
cv.SaveImage('case1.jpg',output)
cv.WaitKey(0)
0
我编写了以下非常简单的python代码以在图像中查找圆:
从以下两个示例中可以看出,“发现圆的质量”变化很大:
情况1:
案例2:
Case1和Case2基本是同一张图片,但是算法仍然可以检测到不同的圆圈。如果我向算法显示大小不同的圆圈的图像,则圆圈检测甚至可能完全失败。这主要是由于需要针对每个新图片分别调整
HIGH
和LOW
参数。因此,我的问题是:使该算法更强大的各种可能性是什么?它应该是大小和颜色不变的,以便检测到具有不同颜色和大小的不同圆圈。也许使用霍夫变换不是做事的最佳方法?有更好的方法吗?