Python OpenCV霍夫变换:直线与圆的检测原理及实现
本文深入解析OpenCV中霍夫变换(Hough Transform)的原理,涵盖直线与圆的检测机制。详细讲解cv2.HoughLines()与cv2.HoughLinesP()在直线检测中的应用,以及霍夫梯度法在圆检测中的优化策略。结合Python代码示例,展示如何调整参数以高效识别图像中的几何形状。
霍夫变换概述
在数字图像领域,经常存在具有特定几何形状的图形,例如检测道路边缘的直线或人眼的圆形轮廓。为了准确提取这些特定图形,霍夫变换(Hough Transform)提供了一种有效的检测工具。
霍夫变换的核心原理是将特定图形上的点映射到一组参数空间中。通过统计参数空间中的累加结果,寻找极大值对应的解,该解即对应所求几何图形的参数。例如,直线检测可获得斜率k与截距b,圆检测则可获得圆心坐标与半径。
直线检测原理
霍夫变换的核心难点在于从原始空间到参数空间的变换过程。以直线检测为例,假设存在一条直线L,原点到该直线的垂直距离为ρ,垂线与x轴的夹角为θ。该直线由方程 ρ = xcosθ + ysinθ 唯一确定,具体几何关系如图所示:
在极坐标系下,上述直线仅对应唯一的 (ρ,θ) 参数对。若改变任一参数,映射回空间域的直线形态将发生变化。进一步观察该空间直线上所有点的映射情况,会发现这些点在极坐标系下均对应于 (ρ,θ) 表示的直线。然而,空间中的任意一点也可能属于其他多条由不同 (ρ,θ) 表示的直线,例如经过该点的任意两条直线:
由此可见,空间中的一个点在极坐标系下可能对应多条直线,具体数量取决于角度θ的步长设置。假设θ从0到360度(0至2π)变化,若每隔10度取一个值,则一圈共选取36个值,对应36条直线及36个极坐标对。将这些极坐标对绘制在坐标系中(以θ为横轴),由于θ确定且点坐标固定,对应的ρ值也是唯一的,因此该点在极坐标下对应的 (ρ,θ) 曲线呈现一个周期的形态:
前述内容仅讨论单个点在极坐标系下的参数对。若将图像中所有点都进行此类映射,并将它们绘制在同一坐标系下,结果会如何?为便于说明,假设在该直线上选取3个点进行绘制:
观察可知,对于每个点,在极坐标系下均存在一个周期曲线表示经过该点的直线。上述三条极坐标曲线共同经过一个交点。需明确的是,极坐标中的每个点对 (ρ,θ) 在空间坐标系中均对应一条直线。该交点的含义是:在空间坐标系中,存在一条直线同时经过点1、点2和点3,即这三点共线。反之观察极坐标曲线,只需找到交点最多的点,将其映射回空间域,即为所求直线。选择交点最多的点是因为,当空间中存在大量点时,虽然可能出现多个曲线交点,但属于同一直线上的所有点汇聚形成的交点,其曲线相交次数必然最多。
算法优化与步骤
进一步分析该算法。霍夫变换本质上是参数映射变换。对图像中的每个点进行映射,且由于 (ρ,θ) 存在步长,一个点会映射成多个 (ρ,θ) 对。以θ的步长为例,步长越大,映射的对数越少;反之则越多。然而,映射后的点对需计算交点。前述曲线是连续的,但实际因θ步长的存在,曲线呈现离散状态(如前图所示)。当θ步长较大时,难以产生大量交点,因为离散曲线相交困难。因此,θ步长不宜过大,理论上越小效果越好,因为越接近连续曲线,越容易相交。但步长过小会导致内存需求激增及计算量加大。例如,假设处理一副100×100的图像(共10000个像素点),若角度步长为10度(映射36组参数),总映射次数达360000次,且每次映射均需计算,耗时巨大。因此,必须对算法进行改进。首先优化图像预处理,无需计算所有点,只需提取轮廓(通常使用Canny算子生成黑白二值图像,白色为轮廓)。在映射时,仅对轮廓上的点进行参数空间变换。由于直线或圆形检测均依赖鲜明轮廓,需变换的点可能从10000个降至约1000个,这也是为何许多霍夫变换示例中先提取轮廓并转为二值图像的原因。
基于上述分析,霍夫变换的算法步骤如下: (1)对参数空间 (ρ,θ) 进行量化,初始化一个二维矩阵M,M(ρ,θ) 作为累加器。 (2)对图像边界上的每个点进行变换,确定其所属的 (ρ,θ) 组,并将该组对应的累加器数值加1。此处需变换的点即为经过边缘提取后的图像数据。 (3)所有点处理完毕后,分析得到的 M(ρ,θ)。设置阈值T,当 M(ρ,θ) > T 时,认为存在一条有意义的直线。对应的 M(ρ,θ) 即为该直线的参数。阈值T的具体数值需通过实验确定,直至效果合适。 (4)利用求得的 M(ρ,θ) 及点 (x,y) 计算并绘制直线。
OpenCV直线检测实现
以上阐述了霍夫变换的最底层原理。理论上可手动编写程序实现,但如前所述,霍夫变换耗时耗力,手动编写循环效率通常较低。曾使用Matlab实现过该算法,实际霍夫变换示例可参考如下代码:
function mean_circle = hough_circle(BW,step_r,step_angle,r_min,r_max,p)
%------------------------------算法概述-----------------------------
% 该算法通过a = x-r*cos(angle),b = y-r*sin(angle)将圆图像中的边缘点
% 映射到参数空间(a,b,r)中,由于是数字图像且采取极坐标,angle和r都取
% 一定的范围和步长,这样通过两重循环(angle循环和r循环)即可将原图像
% 空间的点映射到参数空间中,再在参数空间(即一个由许多小立方体组成的
% 大立方体)中寻找圆心,然后求出半径坐标。
%-------------------------------------------------------------------
%------------------------------输入参数-----------------------------
% BW:二值图像;
% step_r:检测的圆半径步长
% step_angle:角度步长,单位为弧度 :各度计算 1° = 0.0174
% 2° = 0.035
% 3° = 0.0524
% 4° = 0.0698
% 5° = 0.0872
% r_min:最小圆半径
% r_max:最大圆半径
% p:以p*hough_space的最大值为阈值,p取0,1之间的数
%-------------------------------------------------------------------
% --------对半径的大小范围规定问题--------
% ------ 实验中发现:外轮廓的半径范围在220~260之间
% 内轮廓的半径范围 60~80之间
% Note:: &&&&&&&&&&&当图像改变时半径范围需要改变&&&&&&&&&&&&
% question: 半径的范围差超过50将会显示内存不足,注意方案办法
%------------------------------输出参数-----------------------------
% hough_space:参数空间,h(a,b,r)表示圆心在(a,b)半径为r的圆上的点数
% hough_circl:二值图像,检测到的圆
% para:检测到的所有圆的圆心、半径
% mean_circle : 返回检测到的圆的平均位置及大小
%-------------------------------------------------------------------
[m,n] = size(BW); %取大小
size_r = round((r_max-r_min)/step_r)+1; %半径增加,循环次数
size_angle = round(2*pi/step_angle); %角度增加,循环次数
hough_space = zeros(m,n,size_r); %hough空间
[rows,cols] = find(BW);%把要检测的点存起来,只有白色(边缘)点需要变换
ecount = size(rows); %检测的点的个数
tic %%%% 计时开始位置
% Hough变换
% 将图像空间(x,y)对应到参数空间(a,b,r)
% a = x-r*cos(angle)
% b = y-r*sin(angle)
for i=1:ecount %点个数循环
for r=1:size_r %单个点在所有半径空间内检测
for k=1:size_angle %单个点在半径一定的所在圆内检测
a = round(rows(i)-(r_min+(r-1)*step_r)*cos(k*step_angle));
b = round(cols(i)-(r_min+(r-1)*step_r)*sin(k*step_angle));
if(a>0&a<=m&b>0&b<=n) %对应到某个圆上,记录之
hough_space(a,b,r) = hough_space(a,b,r)+1;
end
end
end
end
% 搜索超过阈值的聚集点
max_para = max(max(max(hough_space)));%找到最大值所在圆参数
index = find(hough_space>=max_para*p);%索引在一定范围内的圆参数
length = size(index);
toc %%%% 计时结束位置,通过计时观察运行效率,hough变换的一大缺点就是耗时
% 将索引结果转换为对应的行列(圆心)和半径大小
% 理解三维矩阵在内存中的存储方式可以理解公式的原理
for k=1:length
par3 = floor(index(k)/(m*n))+1;
par2 = floor((index(k)-(par3-1)*(m*n))/m)+1;%转换为圆心的y值
par1 = index(k)-(par3-1)*(m*n)-(par2-1)*m;%转换为圆心的x值
par3 = r_min+(par3-1)*step_r; %转化为圆的半径
%储存在一起
para(:,k) = [par1,par2,par3]';
end
% 为提高准确性,求取一个大致的平均位置(而不是直接采用的最大值)
mean_circle = round(mean(para')');
在实际应用中,无需手动编写算法,OpenCV已集成霍夫变换函数,调用效率高且简便。 OpenCV中检测直线的函数包括 cv2.HoughLines() 和 cv2.HoughLinesP()。 函数 cv2.HoughLines() 的返回值包含三个元素(OpenCV 3.0版本),实际为二维矩阵,表示上述的 (ρ,θ)。其中 ρ 的单位为像素长度(即直线到图像原点(0,0)的距离),θ 的单位为弧度。该函数有四个输入参数:第一个为二值图像(经Canny变换后的图像);第二、三个参数分别为 ρ 和 θ 的精度(可理解为步长);第四个参数为阈值T,仅当累加器中的值高于T时才认为是一条直线。通过自行绘图实验验证:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('line.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#灰度图像
#open to see how to use: cv2.Canny
#http://blog.csdn.net/on2way/article/details/46851451
edges = cv2.Canny(gray,50,200)
plt.subplot(121),plt.imshow(edges,'gray')
plt.xticks([]),plt.yticks([])
#hough transform
lines = cv2.HoughLines(edges,1,np.pi/180,160)
lines1 = lines[:,0,:]#提取为为二维
for rho,theta in lines1[:]:
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
cv2.line(img,(x1,y1),(x2,y2),(255,0,0),1)
plt.subplot(122),plt.imshow(img,)
plt.xticks([]),plt.yticks([])

测试新图像,不断调整 cv2.HoughLines 的最后一个阈值参数至合理范围,结果如下:
可见检测结果良好。
函数 cv2.HoughLinesP() 为概率霍夫直线检测。原理上,霍夫变换耗时耗力,即使经过Canny转换,点的数量依然庞大。为此,采用概率选择机制,并非计算所有点,而是随机选取部分点进行计算,相当于降采样。此时阈值设置也需相应降低。在参数输入输出方面,该函数多输入两个参数:minLineLength(线的最短长度,短于此值的被忽略)和 maxLineGap(两条直线之间的最大间隔,小于此值视为一条直线)。输出也发生变化,不再返回直线参数,而是直接返回直线端点的坐标位置,从而省去了从参数空间到图像实际坐标点转换的for循环步骤。
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('room.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#灰度图像
#open to see how to use: cv2.Canny
#http://blog.csdn.net/on2way/article/details/46851451
edges = cv2.Canny(gray,50,200)
plt.subplot(121),plt.imshow(edges,'gray')
plt.xticks([]),plt.yticks([])
#hough transform
lines = cv2.HoughLinesP(edges,1,np.pi/180,30,minLineLength=60,maxLineGap=10)
lines1 = lines[:,0,:]#提取为二维
for x1,y1,x2,y2 in lines1[:]:
cv2.line(img,(x1,y1),(x2,y2),(255,0,0),1)
plt.subplot(122),plt.imshow(img,)
plt.xticks([]),plt.yticks([])
可见该方法效果更佳,速度更快,调整参数获得较好效果即可。
圆的检测与优化
直线检测讲解完毕,接下来讨论圆环的检测。圆的数学表达式为:

确定一个圆需要三个参数,因此需通过三层循环实现(比直线检测多一层),将图像上的所有点映射到三维参数空间。其余步骤相同,即寻找参数空间累加器的最大值(或大于某一阈值的值)。理论上,圆的检测比直线更耗时。然而,OpenCV对此进行了优化,采用霍夫梯度法(Hough Gradient Method),感兴趣者可深入研究。我们只需知晓其能优化算法效率即可。关于函数参数输入输出:
cv2.HoughCircles(image, method, dp, minDist, circles, param1, param2, minRadius, maxRadius)
此时输入为灰度图像,同时最好规定检测圆的最大和最小半径,避免盲目检测以节省时间和空间。输出为三个参数空间矩阵。
以实际的人眼图像为例,调整 minRadius 和 maxRadius 至大圆范围进行检测,结果如下:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('eye.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#灰度图像
plt.subplot(121),plt.imshow(gray,'gray')
plt.xticks([]),plt.yticks([])
#hough transform
circles1 = cv2.HoughCircles(gray,cv2.HOUGH_GRADIENT,1,
100,param1=100,param2=30,minRadius=200,maxRadius=300)
circles = circles1[0,:,:]#提取为二维
circles = np.uint16(np.around(circles))#四舍五入,取整
for i in circles[:]:
cv2.circle(img,(i[0],i[1]),i[2],(255,0,0),5)#画圆
cv2.circle(img,(i[0],i[1]),2,(255,0,255),10)#画圆心
plt.subplot(122),plt.imshow(img)
plt.xticks([]),plt.yticks([])

将半径范围调小,检测内圆:
至此,圆的检测过程介绍完毕。
到此,关于Python下OpenCV使用霍夫变换检测直线与圆的文章介绍结束。更多相关OpenCV霍夫变换检测直线与圆的内容,请搜索本站以前的文章或继续浏览下面的相关文章。希望大家以后多多支持本站!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













