一、霍夫变换
霍夫变换是图像处理中的一种技术,主要用于检测图像中的直线、圆或其他形状。其基本思想就是将图像空间中的点映射到参数空间中,通过在参数空间中寻找累计最大值来实现对特定形状的检测。
二、 霍夫直线变换
那么对于一个二值化后的图形来说,其中的每一个目标像素点(这里假设目标像素点为白色像素点)都 对应了霍夫空间的一条直线,当霍夫空间中有两条直线相交时,就代表了直角坐标系中某两个点可以构 成一条直线。而当霍夫空间中有很多条线相交于一点时,说明直角坐标系中有很多点能构成一条直线, 也就意味着这些点共线,因此我们就可以通过检测霍夫空间中有最多直线相交的交点来找到直角坐标系 中的直线。
根据上图,霍夫空间在极坐标系下,一点可以产生一条三角函数曲线,而多条这样的曲线可能会相交于 同一点。因此,我们可以通过设定一个阈值,来检测霍夫空间中的三角函数曲线相交的次数。如果一个 交点的三角函数曲线相交次数超过阈值,那么这个交点所代表的直线就可能是我们寻找的目标直线。
具体步骤为:
1. 建立二维累加数组(也成为累加器),并将其全部初始化为0。
2. 把图像中边缘检测所得到的点的坐标带入直线的极坐标方程中,求得每个点所对应的关于 和 的方 程。
3. 不断的将 的值带入方程中,求得对应的值,再累加器中对应的位置上进行加1。
4. 重复步骤3直到所有的点的方程都带入过所有的值为止。
5. 处理完毕后,找到累加器中的最大值所对应的和
,即是我们要找的直线方程所对应的
和
。
三、统计概率霍夫直线变换
霍夫直线变换(Probabilistic Hough Transform),是一种改进的霍夫变换,它在获取到直线之后,会检测原图中在该直线上的点,并获取到 两侧的端点坐标,然后通过两个点的坐标来计算该直线的长度,通过直线长度与最短长度阈值的比较来 决定该直线要不要被保留。
四、霍夫圆变换
我们只需要将图像中检测到的边缘点的坐标带入到参数空间中,去生成对应的圆锥,然后求取圆锥的交 点即可。但是这种传统的方法有一个很大的缺陷,那就是参数空间的数据从二维变成了三维,数据量变 大了很多,计算量也会大很多,所以在其基础上做了改进,就有了霍夫梯度法。
霍夫梯度法利用了边缘点的梯度做了一次降维的操作,将数据从三维重新降到了二维,方便我们去计 算。它主要分为两个步骤,首先是先去寻找圆心,然后再去寻找半径。
由于我们的检测是基于边缘检测所得到的点来进行的,而进行边缘检测时就能得到边缘点的梯度方向。 对于圆这种特殊的形状来说,其边缘点的梯度方向会指向圆心, 所以我们可以利用边缘点的梯度方向做 一条经过该边缘点的直线。如果将所有边缘点都这么操作的话,最后这些直线相交次数最多的地方就是 圆心所在的地方。这个操作仅仅只是考虑了圆心的坐标所在,并没有考虑半径,所以还是一次二维空间 下的操作。
找到了圆心所在之后,计算所有的边缘点与圆心的距离,得到的结果相同的次数最多的结果就是半径。 只需要不断的去带入不同的半径的值,就可以得到最终的圆的参数,及圆心和半径。
这里要注意的是,圆的半径并不是无限大的,由于我们是要检测一张图片中的圆的存在,假如图片的大 小是一个正方形的存在,那么圆的直径不可能超过正方形的长度;如果图片的大小是一个矩形,那么圆 的直径不可能超过矩形最短边的长度。也就是说图片中的圆最大也就是一个基于图片大小的内切圆,所 以第二步寻找半径的过程中不会让半径进行无限的增大。
其具体步骤为:
1. 首先去寻找圆心所在,所以构建一个关于(a,b)的二维累加器,并初始化为0。
2. 计算所有边缘点的梯度角的正切值。
3. 对于一个边缘点,将其正切值带入a关于b的表达式中,得到表达式的结果后,不断带入a的值并计 算出每一个对应的b值。
4. 将得到的每一对(a,b)在累加器中对应的位置进行加一,进行统计。
5. 重复步骤3、步骤4,直到所有的边缘点处理完毕。
6. 找到累加器中值最大的区域所对应的a和b的值,那么此时的(a,b)就是我们要找的圆的圆心坐 标。
7. 得到圆心之后,计算所有的边缘点与圆心之间的距离,找到距离出现次数最高的结果,该结果就是 以(a,b)为圆心的圆的半径。
五、霍夫变换
5.1、霍夫直线变换
导入模块
import cv2
import numpy as np
输入图像
img=cv2.imread('img_1.png')
灰度化
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
边缘检测
img_canny=cv2.Canny(img_gray,30,70)
霍夫直线变换
lines=cv2.HoughLines(img_canny,0.8,np.pi/180,120)
创建模板
img_res=np.zeros(img_shape,dtype=np.uint8)
在模板上绘图
for i in lines:
rho,theta=i[0]
cos_theta=np.cos(theta)
sin_theta=np.sin(theta)
x1,x2=0,img_shape[1]
y1=int((rho-x1*cos_theta)/sin_theta)
y2 = int((rho - x2 * cos_theta) / sin_theta)
cv2.line(img_res,(x1,y1),(x2,y2),color=(0,0,255))
输出图像
cv2.imshow('img_res',img_res)
cv2.waitKey(0)
完整代码
import cv2
import numpy as np
# 读取输入图像
img = cv2.imread('img_1.png')
img_shape = img.shape # 获取图像形状(高度, 宽度, 通道数)
# 将图像转换为灰度图
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 使用 Canny 算子检测边缘,参数为低阈值和高阈值
img_canny = cv2.Canny(img_gray, 30, 70)
# 使用 Hough 变换检测线条
# 参数:输入边缘图像,ρ的精度(0.8 像素),θ的精度(以弧度为单位),阈值(最小投票数)
lines = cv2.HoughLines(img_canny, 0.8, np.pi/180, 120)
# 创建一个空图像,用于存放检测到的线条
img_res = np.zeros(img_shape, dtype=np.uint8)
# 遍历检测到的线条
for i in lines:
rho, theta = i[0] # 获取线条的极坐标表示(ρ 和 θ)
cos_theta = np.cos(theta) # 计算 cos(θ)
sin_theta = np.sin(theta) # 计算 sin(θ)
# 设定线条的起始点和结束点
x1, x2 = 0, img_shape[1] # 线条的起点 x1 = 0,终点 x2 = 图像宽度
# 计算对应的 y1 和 y2 值
y1 = int((rho - x1 * cos_theta) / sin_theta) # 计算起点 y 坐标
y2 = int((rho - x2 * cos_theta) / sin_theta) # 计算终点 y 坐标
# 在结果图像上绘制检测到的线条
cv2.line(img_res, (x1, y1), (x2, y2), color=(0, 0, 255)) # 线条颜色为红色
# 显示结果图像
cv2.imshow('img_res', img_res)
cv2.waitKey(0) # 等待按键操作
cv2.destroyAllWindows() # 关闭所有 OpenCV 窗口
5.2、统计概率霍夫直线变换
导入模块
import cv2
import numpy as np
输入图像
img=cv2.imread('img_1.png')
灰度化
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
边缘检测
img_canny=cv2.Canny(img_gray,30,70)
统计概率霍夫直线变换
lines=cv2.HoughLinesP(img_canny,0.8,np.pi/180,90,minLineLength=50,maxLineGap=10)
创建模板
img_res=np.zeros(img_shape,dtype=np.uint8)
在模板上绘图
for i in lines:
x1,y1,x2,y2=i[0]
cv2.line(img_res,(x1,y1),(x2,y2),color=(0,0,255))
输出图像
cv2.imshow('img_res',img_res)
cv2.waitKey(0)
完整代码
import cv2
import numpy as np
# 读取输入图像
img = cv2.imread('img_1.png') # 从文件加载图像
img_shape = img.shape # 获取图像的形状(高度, 宽度, 通道数)
# 将图像转换为灰度图
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 使用 Canny 算子检测边缘,参数为低阈值和高阈值
img_canny = cv2.Canny(img_gray, 30, 70)
# 使用 Hough 变换提取线段(HoughLinesP),该方法更适用于检测短线段
# 参数:输入边缘图像,ρ的精度(0.8 像素),θ的精度(以弧度为单位),
# 阈值(最小投票数),最小线段长度(50 像素),最大线段间隙(10 像素)
lines = cv2.HoughLinesP(img_canny, 0.8, np.pi/180, 90, minLineLength=50, maxLineGap=10)
# 创建一个空图像,用于存放检测到的线段
img_res = np.zeros(img_shape, dtype=np.uint8) # 初始化带有 zeros 的相同形状的图像
# 遍历检测到的线段
for i in lines:
x1, y1, x2, y2 = i[0] # 获取每条线段的起始点 (x1, y1) 和结束点 (x2, y2)
# 在结果图像上绘制检测到的线段
cv2.line(img_res, (x1, y1), (x2, y2), color=(0, 0, 255)) # 线条颜色为红色
# 显示结果图像
cv2.imshow('img_res', img_res) # 在窗口中显示结果图像
cv2.waitKey(0) # 等待用户按键,任何键按下后关闭窗口
cv2.destroyAllWindows() # 关闭所有 OpenCV 创建的窗口
5.3、霍夫圆变换
导入模块
import cv2
import numpy as np
输入图像
img=cv2.imread('img_1.png')
灰度化
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
边缘检测
img_canny=cv2.Canny(img_gray,30,70)
霍夫圆变换
circels=cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT_ALT,1.5,20,param1=30,param2=0.9)
circels=np.uint(np.around(circels))
创建模板
img_res=np.zeros(img_shape,dtype=np.uint8)
在模板上绘图
for i in circels:
x,y,circel=i[0]
cv2.circle(img_res,(x,y),circel,(0,0,255),thickness=2)
输出图像
cv2.imshow('img_res',img_res)
cv2.waitKey(0)
完整代码
import cv2
import numpy as np
# 读取输入图像
img = cv2.imread('img_1.png') # 从文件中加载图像
img_shape = img.shape # 获取图像的形状(高度, 宽度, 通道数)
# 将图像转换为灰度图
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 使用 Hough 圆变换检测圆
# 参数:输入图像,检测方法(HOUGH_GRADIENT_ALT),比例(1.5),最小距离(20),
# 参数1(边缘检测的高阈值,param1),参数2(中心检测的阈值,param2)
circels = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT_ALT, 1.5, 20, param1=30, param2=0.9)
# 如果检测到圆,进行转化和四舍五入以获得整数坐标
if circels is not None: # 确保检测到了圆
circels = np.uint16(np.around(circels)) # 转换为无符号整数并四舍五入
# 创建一个空图像,用于存放检测到的圆
img_res = np.zeros(img_shape, dtype=np.uint8) # 初始化带有 zeros 的相同形状的图像
# 遍历检测到的圆
for i in circels:
x, y, circel = i[0] # 获取每个圆的中心坐标 (x, y) 和半径 circel
# 在结果图像上绘制检测到的圆
cv2.circle(img_res, (x, y), circel, (0, 0, 255), thickness=2) # 颜色为红色,厚度为2
# 显示结果图像
cv2.imshow('img_res', img_res) # 在窗口中显示结果图像
cv2.waitKey(0) # 等待用户按键,任何键按下后关闭窗口
cv2.destroyAllWindows() # 关闭所有 OpenCV 创建的窗口
六、库函数
6.1、Canny()
使用 Canny 算法查找图像中的边缘 [48] 。
该函数在输入图像中查找边缘,并使用 Canny 算法在输出映射边缘中标记它们。threshold1 和 threshold2 之间的最小值用于 Edge 链接。最大值用于查找强边缘的初始分段。查看 http://en.wikipedia.org/wiki/Canny_edge_detector
cv.Canny( image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]] ) -> edges
cv.Canny( dx, dy, threshold1, threshold2[, edges[, L2gradient]] ) -> edges
方法 | 描述 |
---|---|
image | 8 位输入图像。 |
edges | 输出边缘映射;单通道 8 位图像,其大小与 图像 相同。 |
threshold1 | 磁滞过程的第一个阈值。 |
threshold2 | 滞后程序的第二个阈值。 |
apertureSize | 孔径大小。 |
L2gradient | ![]() |
6.2、HoughLines()
使用标准 Hough 变换在二进制图像中查找线条。
该函数实现用于线检测的标准或标准多尺度 Hough 变换算法。有关霍夫变换的良好解释,请参见 http://homepages.inf.ed.ac.uk/rbf/HIPR2/hough.htm。
cv.HoughLines( image, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta[, use_edgeval]]]]]] ) -> lines
方法 | 描述 |
---|---|
image | 8 位、单通道二进制源图像。该函数可以修改图像。 |
lines | 线的输出向量。每条线都由 2 或 3 个元素向量表示,或 ,其中 是距坐标原点(图像左上角)的距离,是以弧度 ( ) 为单位的线旋转角度,是累加器的值。 |
rho | 累加器的距离分辨率(以像素为单位)。 |
theta | 累加器的角度分辨率(以弧度为单位)。 |
threshold | 二值化参数 |
srn | 对于多尺度 Hough 变换,它是距离分辨率 rho 的除数。粗略累加器距离分辨率为 rho,精确累加器分辨率为 rho/srn。如果 srn=0 和 stn=0,则使用经典霍夫变换。否则,这两个参数都应该为正。 |
stn | 对于多尺度 Hough 变换,它是距离分辨率 theta 的除数。 |
min_theta | 对于标准和多比例霍夫变换,检查线条的最小角度。必须介于 0 和 max_theta 之间。 |
max_theta | 对于标准和多尺度霍夫变换,角度的上限。必须介于 min_theta 和 CV_PI 之间。蓄能器中的实际最大角度可能略小于 max_theta,具体取决于参数 min_theta 和 theta。 |
use_edgeval | 如果要使用加权 Hough 变换,则为 True。 |
6.3、line()
绘制连接两点的线段。
函数 line 在图像中的 pt1 和 pt2 点之间绘制线段。该线被图像边界剪切。对于具有整数坐标的非抗锯齿线,使用 8 连通或 4 连通 Bresenham 算法。粗线绘制有圆角结尾。抗锯齿线是使用高斯过滤绘制的。
cv.line( img, pt1, pt2, color[, thickness[, lineType[, shift]]] ) -> img
方法 | 描述 |
---|---|
img | 图像 |
pt1 | 线段的第一个点。 |
pt2 | 线段的第二个点。 |
color | 线条颜色。 |
thickness | 线条粗细。 |
lineType | 线路的类型。请参见 LineTypes |
shift | 点坐标中的小数位数 |
6.4、HoughLinesP()
使用概率 Hough 变换在二进制图像中查找线段。
该函数实现了用于线检测的概率 Hough 变换算法
cv.HoughLinesP( image, rho, theta, threshold[, lines[, minLineLength[, maxLineGap]]] ) -> lines
方法 | 描述 |
---|---|
image | 8 位、单通道二进制源图像。该函数可以修改图像。 |
lines | 线的输出向量。每条线都由 2 或 3 个元素向量表示,或 ,其中 是距坐标原点(图像左上角)的距离,是以弧度 ( ) 为单位的线旋转角度,是累加器的值。 |
rho | 累加器的距离分辨率(以像素为单位)。 |
theta | 累加器的角度分辨率(以弧度为单位)。 |
threshold | 二值化参数 |
minLineLength | 最小行长。短于此值的线段将被拒绝。 |
maxLineGap | 同一条线上的点之间允许的最大间隙以链接它们。 |
6.5、HoughCircles()
使用 Hough 变换在灰度图像中查找圆圈。
该函数使用修改 Hough 变换在灰度图像中查找圆圈。
cv.HoughCircles( image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]] ) -> circles
注意
通常,该函数可以很好地检测圆心。但是,它可能无法找到正确的半径。如果您知道,可以通过指定半径范围( minRadius 和 maxRadius )来协助该函数。或者,在HOUGH_GRADIENT方法的情况下,您可以将 maxRadius 设置为负数,以便仅返回中心而不进行半径搜索,并使用其他过程找到正确的半径。
方法 | 描述 |
---|---|
image | 8 位、单通道、灰度输入图像 |
circles | 找到的圆的输出向量 |
method | 检测方法,请参阅 HoughModes。可用的方法包括 HOUGH_GRADIENT 和 HOUGH_GRADIENT_ALT。 |
dp | 累加器分辨率与图像分辨率的反比。例如, 如果 dp=1 ,则累加器的分辨率与输入图像相同。如果 dp=2 ,则累加器的宽度和高度只有一半。对于 HOUGH_GRADIENT_ALT,建议的值为 dp=1.5,除非需要检测一些非常小的圆圈。 |
minDist | 检测到的圆的中心之间的最小距离。如果参数太小,则除了 true 圆圈外,还可能会错误地检测到多个相邻圆圈。如果它太大,可能会错过一些圆圈。 |
param1 | 第一个特定于方法的参数。在 HOUGH_GRADIENT 和 HOUGH_GRADIENT_ALT 的情况下,它是传递给 Canny 边缘检测器的两个阈值中的较高阈值(较低的阈值小两倍)。请注意,HOUGH_GRADIENT_ALT 使用 Scharr 算法来计算图像导数,因此阈值通常应更高,例如 300 或正常曝光和对比度的图像。 |
param2 | 第二个特定于方法的参数。如果是 HOUGH_GRADIENT,则它是检测阶段圆心的累加器阈值。它越小,检测到的假圆圈就越多。与较大的 accumulator 值相对应的圆圈将首先返回。在 HOUGH_GRADIENT_ALT 算法的情况下,这是圆 “perfectness” 度量。它越接近 1,算法选择的形状越好。在大多数情况下,0.9 应该没问题。如果您想更好地检测小圆圈,您可以将其降低到 0.85、0.8 甚至更低。但随后也要尝试限制搜索范围 [minRadius, maxRadius] 以避免出现许多假圆圈。 |
minRadius | 最小圆半径。 |
maxRadius | 最大圆半径。如果 <= 0,则使用最大图像尺寸。如果< 0,则 HOUGH_GRADIENT 返回中心,而不查找半径。HOUGH_GRADIENT_ALT始终计算圆半径。 |
6.6、circle()
绘制一个圆。
函数 cv::circle 绘制一个具有给定圆心和半径的简单圆或实心圆。
cv.circle( img, center, radius, color[, thickness[, lineType[, shift]]] ) -> img
方法 | 描述 |
---|---|
img | 绘制圆的图像。 |
center | 圆心。 |
radius | 圆的半径。 |
color | 圆形颜色。 |
thickness | 圆轮廓的粗细(如果为正)。负值(如 FILLED)表示要绘制实心圆。 |
lineType | 圆边界的类型。请参阅线型 |
shift | center 坐标和 radius 值中的小数位数。 |