本文要讨论的问题来源于工程实际,摄像头去拍圆形标记点得到一张图像,已知标记圆的半径范围(rmin,rmax),需要识别出圆心坐标和半径。采用霍夫圆变换可以很好的实现这个功能,且具有广泛的适应性(就是指在大多数情况下都能识别出圆,成功率高)。基本思路是先对圆进行边缘检测,然后对于边缘检测图像进行霍夫圆检测。
假设圆的坐标假设为:
现在已知圆上的一系列点(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,其对应的圆心即为识别出来的圆心。
边沿检测算法使用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
(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。相比色块追踪法需要消耗更多内存,还需要消耗更多的运算时间。色块追踪法的优势是消耗内存少,运算速度快,但是识别成功率低于霍夫法。