本文要讨论的问题来源于工程实际,摄像头去拍圆形标记点得到一张图像,已知标记圆的半径范围(rmin,rmax),需要识别出圆心坐标和半径。采用霍夫圆变换可以很好的实现这个功能,且具有广泛的适应性(就是指在大多数情况下都能识别出圆,成功率高)。基本思路是先对圆进行边缘检测,然后对于边缘检测图像进行霍夫圆检测。
1 霍夫圆检测算法原理
假设圆的坐标假设为:
现在已知圆上的一系列点(xi,yi),则可知圆心(xc,yc)也位于以(xi,yi)为圆心,半径为r的圆上,以这一系列点为圆心画一系列半径为r的圆,则圆心(xc,yc)将位于这些圆的交点上。如下图所示:
霍夫圆检测的思路就是,对于其中的每一个半径r,做如下处理:
(1)建立一个投票数组SumArr[height][width],其中height为图像高度,width为图像宽度
(2)遍历边缘图像,对其中的每一个边缘点(xi,yi),求出以(xi,yi)为圆心,以r为半径的圆周上的所有像素点,将这些像素点的投票数加1,用伪代码表示为:
for(theta=0;theta<360;theta+=step)
{
angle = theta*PI/180;
cx=xi-r*cos(angle);
cy=xi-r*sin(angle);
if(cx>=0 && cx<width && cy>=0 && cy<height)
sumArr[cy][cx] ++;//坐标点(cx,cy)是潜在的圆心,将其投票数增1
}
(3)统计sumArr数组中的最大值,这个最大值对应的坐标即图中半径为r的圆所在的圆心。
(4)遍历半径范围(rmin,rmax),对于其中每一个半径r,既然可以求出它所对应的投票数candidate,圆心(xc,yc),取其中投票数最大值对应的半径,即为识别出来的圆半径r,其对应的圆心即为识别出来的圆心。
2 canny边沿检测算法
边沿检测算法使用canny边缘检测算法,通过计算图像梯度,
梯度向量大小
由于开平方和根号计算较慢,可以用|gx|+|gy|来代替
计算梯度用sobel算子,sobel算子有俩个:
将图中的每个像素和sobel算子1和算子2做卷积,求出gx和gy,进而求出梯度值M(x,y),利用梯度值来进行滤波。代码实现为:
def convolution(img,y,x,width,height,sobel):
value = [0]*9
if y-1>=0 and x-1>=0 and y-1<height and x-1<width:
value[0] = img[y-1][x-1]
if y-1>=0 and y-1<height:
value[1] = img[y-1][x]
if y-1>=0 and y-1<height and x+1<width:
value[2] = img[y-1][x+1]
if x-1>=0:
value[3] = img[y][x-1]
value[4] = img[y][x]
if x+1<width:
value[5] = img[y][x+1]
if y+1<height and x-1>=0:
value[6] = img[y+1][x-1]
if y+1<height:
value[7] = img[y+1][x]
if y+1<height and x+1<width:
value[8] = img[y+1][x+1]
out = abs(value[0]*sobel[0]+value[1]*sobel[1]+value[2]*sobel[2]+\
value[3]*sobel[3]+value[4]*sobel[4]+value[5]*sobel[5]+\
value[6]*sobel[6]+value[7]*sobel[7]+value[8]*sobel[8])
return int(out)
def mycanny(img,minVal,maxVal):
sobel1 = [-1,-2,-1,0,0,0,1,2,1]
sobel2 = [-1,0,1,-2,0,2,-1,0,1]
width = img.shape[1]
height = img.shape[0]
out = numpy.zeros(img.shape,dtype=numpy.uint8)
for y in range(height):
for x in range(width):
gx = convolution(img,y,x,width,height,sobel1)
gy = convolution(img,y,x,width,height,sobel2)
temp = gx+gy
if temp <0:
temp = 0
elif temp>255:
temp = 255
if temp<200:
out[y][x] = img[y][x]
else:
out[y][x] = temp
return out
2 程序实现步骤
(1) 对原图像进行灰度化处理
(2) 对灰度图进行canny边缘检测
(3) 进行圆霍夫检测
def findCir_R(img,r):
width = img.shape[1]
height = img.shape[0]
sumArr = [0]*(width*height)
for y in range(height):
for x in range(width):
if img[y][x] != 255:
continue
for a in range(0,360,10):
theta = a*math.pi/180
cx = int(x- r*math.cos(theta))
cy = int(y-r*math.sin(theta))
if cx>0 and cx<width and cy>0 and cy<height:
sumArr[cy*width+cx] = sumArr[cy*width+cx]+1
maxVal = 0
cirX = 0
cirY = 0
for y in range(height):
for x in range(width):
if sumArr[y*width+x] > maxVal:
maxVal = sumArr[y*width+x]
cirX = x
cirY = y
return maxVal,cirX,cirY
def findCir(img,rmin,rmax):
maxVal = 0
cirX = 0
cirY = 0
radius = 0
for r in range(rmin,rmax+1,1):
value,cx,cy = findCir_R(img,r)
if value > maxVal:
maxVal = value
cirX = cx
cirY = cy
radius = r
return cirX,cirY,radius
所有代码:
import cv2
import numpy
import os
import math
def show_img(window,img):
cv2.namedWindow(window,0)
cv2.resizeWindow(window,int(img.shape[1]),int(img.shape[0]))
cv2.imshow(window,img)
def findCir_R(img,r):
width = img.shape[1]
height = img.shape[0]
sumArr = [0]*(width*height)
for y in range(height):
for x in range(width):
if img[y][x] != 255:
continue
for a in range(0,360,10):
theta = a*math.pi/180
cx = int(x- r*math.cos(theta))
cy = int(y-r*math.sin(theta))
if cx>0 and cx<width and cy>0 and cy<height:
sumArr[cy*width+cx] = sumArr[cy*width+cx]+1
maxVal = 0
cirX = 0
cirY = 0
for y in range(height):
for x in range(width):
if sumArr[y*width+x] > maxVal:
maxVal = sumArr[y*width+x]
cirX = x
cirY = y
return maxVal,cirX,cirY
def findCir(img,rmin,rmax):
maxVal = 0
cirX = 0
cirY = 0
radius = 0
for r in range(rmin,rmax+1,1):
value,cx,cy = findCir_R(img,r)
if value > maxVal:
maxVal = value
cirX = cx
cirY = cy
radius = r
return cirX,cirY,radius
def convolution(img,y,x,width,height,sobel):
value = [0]*9
if y-1>=0 and x-1>=0 and y-1<height and x-1<width:
value[0] = img[y-1][x-1]
if y-1>=0 and y-1<height:
value[1] = img[y-1][x]
if y-1>=0 and y-1<height and x+1<width:
value[2] = img[y-1][x+1]
if x-1>=0:
value[3] = img[y][x-1]
value[4] = img[y][x]
if x+1<width:
value[5] = img[y][x+1]
if y+1<height and x-1>=0:
value[6] = img[y+1][x-1]
if y+1<height:
value[7] = img[y+1][x]
if y+1<height and x+1<width:
value[8] = img[y+1][x+1]
out = abs(value[0]*sobel[0]+value[1]*sobel[1]+value[2]*sobel[2]+\
value[3]*sobel[3]+value[4]*sobel[4]+value[5]*sobel[5]+\
value[6]*sobel[6]+value[7]*sobel[7]+value[8]*sobel[8])
return int(out)
def mycanny(img,minVal,maxVal):
sobel1 = [-1,-2,-1,0,0,0,1,2,1]
sobel2 = [-1,0,1,-2,0,2,-1,0,1]
width = img.shape[1]
height = img.shape[0]
out = numpy.zeros(img.shape,dtype=numpy.uint8)
for y in range(height):
for x in range(width):
gx = convolution(img,y,x,width,height,sobel1)
gy = convolution(img,y,x,width,height,sobel2)
temp = gx+gy
if temp <0:
temp = 0
elif temp>255:
temp = 255
if temp<200:
out[y][x] = img[y][x]
else:
out[y][x] = temp
return out
img = cv2.imread("D:/pic/cir22.jpg")
show_img("source",img)
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
show_img('gray',gray)
#canny = cv2.Canny(img, 200, 400)
canny = mycanny(gray, 20, 100)
show_img('canny',canny)
#cv2.waitKey(0)
cx,cy,r = findCir(canny,15,30)
print(cx,cy,r)
pic = img.copy()
cv2.circle(pic,(cx,cy),r,(0,0,255))
show_img("result",pic)
cv2.waitKey(0)
程序运行效果:
相比前面文章 《STM32识别圆——色块追踪法》,霍夫圆检测算法识别成功率更高。但是这个算法在进行canny边缘检测时需要额外开辟一块和原图一样大小空间的数组用来存储canny结果,在进行霍夫圆检测时需要开辟投票统计数组sumArr。相比色块追踪法需要消耗更多内存,还需要消耗更多的运算时间。色块追踪法的优势是消耗内存少,运算速度快,但是识别成功率低于霍夫法。