C++ 使用OpenCV(基于Hough变换或其他特征)编写鲁棒(颜色和大小不变)圆检测
我编写了以下非常简单的python代码来查找图像中的圆:C++ 使用OpenCV(基于Hough变换或其他特征)编写鲁棒(颜色和大小不变)圆检测,c++,python,c,opencv,computer-vision,C++,Python,C,Opencv,Computer Vision,我编写了以下非常简单的python代码来查找图像中的圆: import cv import numpy as np WAITKEY_DELAY_MS = 10 STOP_KEY = 'q' cv.NamedWindow("image - press 'q' to quit", cv.CV_WINDOW_AUTOSIZE); cv.NamedWindow("post-process", cv.CV_WINDOW_AUTOSIZE); key_pressed = False while key
import cv
import numpy as np
WAITKEY_DELAY_MS = 10
STOP_KEY = 'q'
cv.NamedWindow("image - press 'q' to quit", cv.CV_WINDOW_AUTOSIZE);
cv.NamedWindow("post-process", cv.CV_WINDOW_AUTOSIZE);
key_pressed = False
while key_pressed != STOP_KEY:
# grab image
orig = cv.LoadImage('circles3.jpg')
# create tmp images
grey_scale = cv.CreateImage(cv.GetSize(orig), 8, 1)
processed = cv.CreateImage(cv.GetSize(orig), 8, 1)
cv.Smooth(orig, orig, cv.CV_GAUSSIAN, 3, 3)
cv.CvtColor(orig, grey_scale, cv.CV_RGB2GRAY)
# do some processing on the grey scale image
cv.Erode(grey_scale, processed, None, 10)
cv.Dilate(processed, processed, None, 10)
cv.Canny(processed, processed, 5, 70, 3)
cv.Smooth(processed, processed, cv.CV_GAUSSIAN, 15, 15)
storage = cv.CreateMat(orig.width, 1, cv.CV_32FC3)
# these parameters need to be adjusted for every single image
HIGH = 50
LOW = 140
try:
# extract circles
cv.HoughCircles(processed, storage, cv.CV_HOUGH_GRADIENT, 2, 32.0, HIGH, LOW)
for i in range(0, len(np.asarray(storage))):
print "circle #%d" %i
Radius = int(np.asarray(storage)[i][0][2])
x = int(np.asarray(storage)[i][0][0])
y = int(np.asarray(storage)[i][0][1])
center = (x, y)
# green dot on center and red circle around
cv.Circle(orig, center, 1, cv.CV_RGB(0, 255, 0), -1, 8, 0)
cv.Circle(orig, center, Radius, cv.CV_RGB(255, 0, 0), 3, 8, 0)
cv.Circle(processed, center, 1, cv.CV_RGB(0, 255, 0), -1, 8, 0)
cv.Circle(processed, center, Radius, cv.CV_RGB(255, 0, 0), 3, 8, 0)
except:
print "nothing found"
pass
# show images
cv.ShowImage("image - press 'q' to quit", orig)
cv.ShowImage("post-process", processed)
cv_key = cv.WaitKey(WAITKEY_DELAY_MS)
key_pressed = chr(cv_key & 255)
从以下两个示例中可以看出,“圆查找质量”差异很大:
案例1:
案例2:
Case1和Case2基本上是相同的图像,但算法仍然检测不同的圆。如果我用不同大小的圆来呈现算法,圆检测甚至可能完全失败。这主要是由于HIGH
和LOW
参数需要针对每个新图片分别进行调整
因此,我的问题是:使该算法更健壮的各种可能性是什么?它应该是大小和颜色不变的,以便检测具有不同颜色和不同大小的不同圆。也许使用Hough变换不是最好的方法?有更好的方法吗?如您所知,Hough变换使用“模型”在(通常)边缘检测图像中查找某些特征。在
霍夫圆
的情况下,该模型是一个完美的圆。这意味着可能不存在一个参数组合,它可以在不增加误报数量的情况下检测图片中更不稳定和椭圆形状的圆。另一方面,由于潜在的投票机制,非封闭的完美圆或带有“凹痕”的完美圆可能会持续出现。因此,根据您的预期输出,您可能希望也可能不希望使用此方法
也就是说,我看到一些东西可能会帮助您实现此功能:
HoughCircles
在内部调用Canny
,所以我想你可以不调用它param1
(您称之为HIGH
)通常在值200左右初始化。它用作对Canny
的内部调用的参数:cv.Canny(processed,cannied,HIGH,HIGH/2)
。您可以这样运行Canny
查看设置HIGH
如何影响通过Hough变换处理的图像
param2
(您称之为LOW
)通常围绕值100进行初始化。它是Hough变换累加器的投票阈值。将其设置得越高意味着假阴性越多,假阳性越多。我相信这是第一个你想摆弄的
更新re:filled circles:使用Hough变换找到圆形状后,可以通过采样边界颜色并将其与假定圆内的一个或多个点进行比较来测试它们是否已填充。或者,您可以将假定圆内的一个或多个点与给定的背景颜色进行比较。如果前一个比较成功,或者如果替代比较失败,则填充圆圈。查看您的代码,我注意到以下几点:
- 灰度转换。我明白你为什么这么做,但要知道你在扔东西 把信息放在那里。正如您在“后期处理”图像中看到的,您的黄色圆圈是 与背景强度相同,只是颜色不同
- 去除噪声后的边缘检测(erae/放大)。这是不必要的;坎尼应该处理好这件事
- Canny边缘检测。你的“开放”圆有两条边,一条内边和一条外边。由于它们非常接近,Canny gauss滤波器可能会将它们相加。如果没有,两条边就会靠得很近。也就是说,在Canny之前,你有开放和充满的圆圈。之后,分别有0/2和1条边。由于Hough再次调用Canny,在第一种情况下,两条边可能会一起平滑(取决于初始宽度),这就是为什么核心Hough算法可以同样处理开放圆和填充圆
HSV
变换,平滑一个副本,然后取原始副本和平滑副本之间的差值。这将获得每个点的dH、dS、dV
值(色调、饱和度、值的局部变化)。正方形并添加以获得一维图像,所有边缘(内部和外部)附近都有峰值
我的第二个建议是本地正常化,但我不确定这是否必要。这个想法是,你并不特别关心你得到的边缘信号的确切值,它实际上应该是二进制的(不管是不是边缘)。因此,可以通过除以局部平均值(其中局部值的大小与边缘大小的大小相同)来规范化每个值 好的,看看这些图像。我建议使用
**活动轮廓**
- 活动轮廓的好处在于,它们几乎完全适合任何给定的形状。无论是正方形还是三角形,在你的情况下,他们都是完美的候选人
- 如果你能提取出圆的中心,那就太好了。活动轮廓始终需要一个起点,可以从该起点开始增长或收缩以适应。无需始终将中心与中心对齐。稍微偏移一下还是可以的
- 在你的例子中,如果你让轮廓从中心向外生长,它们将停留在圆的边界上
- 请注意,增长或收缩的活动轮廓使用,这意味着您可以设置轮廓的方向,向内或向外
- 您可能需要使用灰度级的渐变图像。但你也可以试试颜色。如果有用的话李>
- 如果你不做公关
import cv import numpy as np # grab image orig = cv.LoadImage('circles3.jpg') # create tmp images grey_scale = cv.CreateImage(cv.GetSize(orig), 8, 1) processed = cv.CreateImage(cv.GetSize(orig), 8, 1) cv.Smooth(orig, orig, cv.CV_GAUSSIAN, 3, 3) cv.CvtColor(orig, grey_scale, cv.CV_RGB2GRAY) # do some processing on the grey scale image cv.Erode(grey_scale, processed, None, 10) cv.Dilate(processed, processed, None, 10) cv.Canny(processed, processed, 5, 70, 3) cv.Smooth(processed, processed, cv.CV_GAUSSIAN, 15, 15) #storage = cv.CreateMat(orig.width, 1, cv.CV_32FC3) storage = cv.CreateMemStorage(0) contours = cv.FindContours(processed, storage, cv.CV_RETR_EXTERNAL) # N.B. 'processed' image is modified by this! #contours = cv.ApproxPoly (contours, storage, cv.CV_POLY_APPROX_DP, 3, 1) # If you wanted to reduce the number of points... cv.DrawContours (orig, contours, cv.RGB(0,255,0), cv.RGB(255,0,0), 2, 3, cv.CV_AA, (0, 0)) def contour_iterator(contour): while contour: yield contour contour = contour.h_next() for c in contour_iterator(contours): # Number of points must be more than or equal to 6 for cv.FitEllipse2 if len(c) >= 6: # Copy the contour into an array of (x,y)s PointArray2D32f = cv.CreateMat(1, len(c), cv.CV_32FC2) for (i, (x, y)) in enumerate(c): PointArray2D32f[0, i] = (x, y) # Fits ellipse to current contour. (center, size, angle) = cv.FitEllipse2(PointArray2D32f) # Convert ellipse data from float to integer representation. center = (cv.Round(center[0]), cv.Round(center[1])) size = (cv.Round(size[0] * 0.5), cv.Round(size[1] * 0.5)) # Draw ellipse cv.Ellipse(orig, center, size, angle, 0, 360, cv.RGB(255,0,0), 2,cv.CV_AA, 0) # show images cv.ShowImage("image - press 'q' to quit", orig) #cv.ShowImage("post-process", processed) cv.WaitKey(-1)
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)
import cv2 import math import numpy as np d_red = cv2.cv.RGB(150, 55, 65) l_red = cv2.cv.RGB(250, 200, 200) orig = cv2.imread("c.jpg") img = orig.copy() img2 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) detector = cv2.FeatureDetector_create('MSER') fs = detector.detect(img2) fs.sort(key = lambda x: -x.size) def supress(x): for f in fs: distx = f.pt[0] - x.pt[0] disty = f.pt[1] - x.pt[1] dist = math.sqrt(distx*distx + disty*disty) if (f.size > x.size) and (dist<f.size/2): return True sfs = [x for x in fs if not supress(x)] for f in sfs: cv2.circle(img, (int(f.pt[0]), int(f.pt[1])), int(f.size/2), d_red, 2, cv2.CV_AA) cv2.circle(img, (int(f.pt[0]), int(f.pt[1])), int(f.size/2), l_red, 1, cv2.CV_AA) h, w = orig.shape[:2] vis = np.zeros((h, w*2+5), np.uint8) vis = cv2.cvtColor(vis, cv2.COLOR_GRAY2BGR) vis[:h, :w] = orig vis[:h, w+5:w*2+5] = img cv2.imshow("image", vis) cv2.imwrite("c_o.jpg", vis) cv2.waitKey() cv2.destroyAllWindows()
P = {p1, ..., pn} P: search space p1...pn: pixels
E(P) : enumerates the search space V(p) : checks whether the item/pixel has the desirable properties, the items passing the check are added to the output list