1 实验目的
了解轮廓检测的原理,检测出图像中的圆。
2 实验设备
安装了python和pychrm的电脑一台。
3 实验内容
包含图片的导入、检测图像中的圆,显示图片。
4 实验原理
霍夫圆检测:
图形可以用一些参数进行表示,标准霍夫变换的原理就是把图像空间转换成参数空间(即霍夫空间),例如霍夫变换的直线检测就是在距离-角度空间内进行检测。圆可以表示成:
其中a和b表示圆心坐标,r表示圆半径,因此霍夫变换的圆检测就是在这三个参数组成的三维空间内进行检测。原则上,霍夫变换可以检测任何形状。但复杂的形状需要的参数就多,霍夫空间的维数就多,因此在程序实现上所需的内存空间以及运行效率上都不利于把标准霍夫变换应用于实际复杂图形的检测中。所以一些改进的霍夫变换就相继提出,它们的基本原理就是尽可能减小霍夫空间的维数。
示例图
有一系列的点,它们是在同一个圆上,这里已经把圆的轮廓给出来了,这里只画了圆的一半,但已经足够说明问题了。要检测这个图像中的圆应该怎么办?还是用同样的思考方式,来个空间转换。将上面圆的方程重新写一下:
这时有三个参数,圆心(a,b),半径r。
过一点的圆有多少个?无数个。以上图的第1个点(−2,1)为例,把它代入方程
得到:,这个方程描述的圆都经过点(−2,1)(-2,1)(−2,1),它们在参数空间上看起来是这样的:
示例图
这里r取的是[1,2,3,4,5],没取成无限的,要是取成无限了,图就没有办法看了。每一个参数空间上的圈圈上的点都对应于图像空间上经过点(−2,1)的一个圆,如图这个点:
示例图
它代表了什么意思呢?它代表了一个经过点(−2,1)的一个圆,圆心为(−2,0),半径为1(图上的半径),这样是不是就清楚了一些?从三维空间来看就是这样(为了能看得清楚,只画了半径为1,3,5的三个圆):
示例图
上图是图像空间中一个点对应在参数空间的曲线,将所有点对应在参数空间的曲线都画出来就是这样的(同样为了看的清楚,只取了3个半径,分别为1,3,5):
示例图
其实我们的目的就是在参数空间找聚集点,思想和检测直线是一样的,找参数空间中曲线交点最多的位置,可以看到在r=3的时候所有圆都相交于同一个点,这个点对应的圆就是图像空间中经过所有点的那个圆,这样就把圆给检测出来了。还可以看出,当r离真实值(r=3)越远时,圆的相交的就越不明显。
HoughCircles函数实现了圆形检测,它使用的算法也是改进的霍夫变换——霍夫变换。也就是把霍夫变换分为两个阶段,从而减小了霍夫空间的维数。第一阶段用于检测圆心,第二阶段从圆心推导出圆半径。检测圆心的原理是圆心是它所在圆周所有法线的交汇处,因此只要找到这个交点,即可确定圆心,该方法所用的霍夫空间与图像空间的性质相同,因此它仅仅是二维空间。检测圆半径的方法是从圆心到圆周上的任意一点的距离(即半径)是相同,只要确定一个阈值,只要相同距离的数量大于该阈值,我们就认为该距离就是该圆心所对应的圆半径,该方法只需要计算半径直方图,不使用霍夫空间。圆心和圆半径都得到了,那么通过公式得到一个圆形。从上面的分析可以看出,霍夫变换把标准霍夫变换的三维霍夫空间缩小为二维霍夫空间,因此无论在内存的使用上还是在运行效率上,霍夫变换都远远优于标准霍夫变换。但该算法有一个不足之处就是由于圆半径的检测完全取决于圆心的检测,因此如果圆心检测出现偏差,那么圆半径的检测肯定也是错误的。
霍夫变换的具体步骤为:
第一阶段:检测圆心
1、对输入图像边缘检测;
2、计算图形的梯度,并确定圆周线,其中圆周的梯度就是它的法线;
3、在二维霍夫空间内,绘出所有图形的梯度直线,某坐标点上累加和的值越大,说明在该点上直线相交的次数越多,也就是越有可能是圆心;
4、在霍夫空间的4邻域内进行非最大值抑制;
5、设定一个阈值,霍夫空间内累加和大于该阈值的点就对应于圆心。
第二阶段:检测圆半径
1、计算某一个圆心到所有圆周线的距离,这些距离中就有该圆心所对应的圆的半径的值,这些半径值当然是相等的,并且这些圆半径的数量要远远大于其他距离值相等的数量;
2、设定两个阈值,定义为最大半径和最小半径,保留距离在这两个半径之间的值,这意味着我们检测的圆不能太大,也不能太小;
3、对保留下来的距离进行排序;
4、找到距离相同的那些值,并计算相同值的数量;
5、设定一个阈值,只有相同值的数量大于该阈值,才认为该值是该圆心对应的圆半径;
6、对每一个圆心,完成上面的2.1~2.5步骤,得到所有的圆半径。
霍夫圆环检测的函数:
cv2.HoughCircles(image, method, dp, minDist, circles=None, param1=None, param2=None, minRadius=None, maxRadius=None)
参数:
image:8位,单通道图像。如果使用彩色图像,需要先转换为灰度图像。
method:定义检测图像中圆的方法。目前唯一实现的方法是cv2.HOUGH_GRADIENT。
dp:累加器分辨率与图像分辨率的反比。dp获取越大,累加器数组越小。
minDist:检测到的圆的中心,(x,y)坐标之间的最小距离。如果minDist太小,则可能导致检测到多个相邻的圆。如果minDist太大,则可能导致很多圆检测不到。
param1:用于处理边缘检测的梯度值方法。
param2:cv2.HOUGH_GRADIENT方法的累加器阈值。阈值越小,检测到的圈子越多。
minRadius:半径的最小大小(以像素为单位)。
maxRadius:半径的最大大小(以像素为单位)。
在进行圆检测之前我们要先消除图像的噪声,消除图像的噪声通常用形态学操作:腐蚀与膨胀。比如腐蚀就是“变瘦”,膨胀就是“变胖”。形态学操作一般作用于二值化图,来连接相邻的元素或分离成独立的元素。腐蚀和膨胀是针对图片中的白色部分。
膨胀:
膨胀就是求局部最大值的操作。按数学方面来说,膨胀或者腐蚀操作就是将图像(或图像的一部分区域,我们称之为A)与核(我们称之为B)进行卷积。
核可以是任何的形状和大小,它拥有一个单独定义出来的参考点,我们称其为锚点(anchorpoint)。多数情况下,核是一个小的中间带有参考点和实心正方形或者圆盘,其实,我们可以把核视为模板或者掩码。而膨胀就是求局部最大值的操作,核B与图形卷积,即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素。这样就会使图像中的高亮区域逐渐增长。如图1.1.6所示,这就是膨胀操作的初衷。
膨胀示意图
函数cv2.dilate进行膨胀操作
cv2.dilate(img,kernel,iterations = 1)
腐蚀:
膨胀相反,腐蚀就是求局部最小值的操作。
腐蚀示意图
右图比左图小一圈。
腐蚀可以理解为B的中心(锚点)沿着A的内边界走了一圈。腐蚀也是对高亮部分而言,A区域之外的部分 < A的高亮像素,所里里面被外面取代。A中能完全包含B的像素被留下来了。腐蚀可以简单理解为消除物体A所有边界点的过程。
函数cv2.erode进行腐蚀操作
cv2.erode(img,kernel,iterations = 1)
开运算: 开运算 = 先腐蚀运算,再膨胀运算(看上去把细微连在一起的两块目标分开了) 开运算的效果图如下图所示:
开运算总结:
(1)开运算能够除去孤立的小点,毛刺和小桥,而总的位置和形状不便。
(2)开运算是一个基于几何运算的滤波器。
(3)结构元素大小的不同将导致滤波效果的不同。
(4)不同的结构元素的选择导致了不同的分割,即提取出不同的特征。
cv2.morphologyEx() :先腐蚀再膨胀,有助于消除噪音。注意第二个参数cv2.MORPH_OPEN是开运算。
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
闭运算:
闭运算 = 先膨胀运算,再腐蚀运算(看上去将两个细微连接的图块封闭在一起) 闭运算的效果图如下图所示:
闭操作
闭运算总结: (1)闭运算能够填平小湖(即小孔),弥合小裂缝,而总的位置和形状不变。 (2)闭运算是通过填充图像的凹角来滤波图像的。 (3)结构元素大小的不同将导致滤波效果的不同。 (4)不同结构元素的选择导致了不同的分割。
cv2.morphologyEx() :先膨胀后腐蚀,用于消除前景对象内的小孔或对象上的小黑点。第二个参数是cv2.MORPH_CLOSE进行闭运算。
cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
绘制圆函数cv2.circle()
该函数绘制出圆,指定圆心和半径
cv2.circle(img,(60,60),30,(0,0,213),-1)
参数:
img:图像
color:需要传入的颜色
thickness:线条的粗细,默认值是1
linetype:线条的类型,8 连接,抗锯齿等。默认情况是 8 连接。cv2.LINE_AA 为抗锯齿,这样看起来会非常平滑。
5 实验步骤
打开pycharm。
我们右击相应的文件目录,选择new--->点击Python File,然后输入新建的文件名,点击确定,相应的.py文件就建好了,可以进行编写代码了。
ipoert cv2
读取图片并转换为灰度图像。
img = cv2.imread("..\images\water_coins.jpg")
用中值滤波器消除图像噪声,滤波核的尺寸大小为5。
img=cv2.medianBlur(img,5) ##中值滤波
转成灰度图像。
cimg=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)##灰度图
HoughCircles圆环检测。
参数:
cimg:灰度图像;
cv2.HOUGH_GRADIENT:检测图像中圆的方法目前唯一实现的方法cv2.HOUGH_GRADIENT。
1 :累加器分辨率与图像分辨率的反比。dp获取越大,累加器数组越小
20 :检测到的圆的中心,(x,y)坐标之间的最小距离。
param1:用于处理边缘检测的梯度值方法。
param2:cv2.HOUGH_GRADIENT方法的累加器阈值。阈值越小,检测到的圈子越多。
minRadius:半径的最小大小。 maxRadius:半径的最大大小。
HoughCircles函数返回的是圆心坐标和半径。
circles=cv2.HoughCircles(cimg,cv2.HOUGH_GRADIENT,1,20,param1=50,param2=30,minRadius=5,maxRadius=30)
利用numpy的around函数进行四舍五入操作。
circles = np.uint16(np.around(circles))
根据检测到圆的信息,画出每一个圆。
for i in circles[0,:]:
##circle画圆,参数:原图,圆心,半径,颜色,线大小
cv2.circle(cimg,(i[0],i[1]),i[2],(0,0,255),2)
##画圆心
cv2.circle(cimg,(i[0],i[1]),1,(0,0,255),3)
显示检测到的图像。
cv2.imshow('aaa',cimg)
cv2.waitKey(0)
cv2.destroyAllWindows()
运行代码得到效果图。
运行结果
有圆没检测出来,需要调参数,这里就不在一一演示了