4. Canny边缘检测 - cv2.Canny()
4.1 cv2.Canny() 用法简介
Canny边缘检测算法是John F.Canny在1986年开发的一个多级边缘检测算法。
Canny边缘检测算法通过像素的梯度变化寻找图像的边缘,最终可以绘制出十分精细的二值边缘图像
edges = cv2.Canny(image, threshold1, threshold2, apertureSize=None, L2gradient=None)
其中
- image 即原图像
- threshold1 第一个阈值,一般为最小阈值
- threshold2 第二个阈值,一般为最大阈值
- apertureSize Sobel算子的孔径大小
- L2gradient 计算图像梯度的标识。默认为False。为True时采用更精准的算法进行计算。
关于这两个阈值怎么用,这涉及到了算法的底层逻辑,还请自行探索。这里一种可以接受的解释是:低于阈值1的像素点,会被认为不构成边缘,而高于阈值2的像素点,会被认为构成边缘。
最后返回值edges是一个二值的灰度图像。
4.2 代码示例
下边对test1.jpg以三组不同的阈值来做Canny边缘检测,根据处理结果感受算法效果:
- 当阈值为 10-50 时
import cv2 img = cv2.imread("test1.jpg") r1 = cv2.Canny(img, 10, 50) cv2.imshow("r1", r1) cv2.waitKey() cv2.destroyAllWindows()
- 当阈值为100-200时
import cv2 img = cv2.imread("test1.jpg") r2 = cv2.Canny(img, 100, 200) cv2.imshow("r2", r2) cv2.waitKey() cv2.destroyAllWindows()
- 当阈值为400-600时
import cv2 img = cv2.imread("test1.jpg") r3 = cv2.Canny(img, 400, 600) cv2.imshow("r3", r3) cv2.waitKey() cv2.destroyAllWindows()
5. 霍夫变换
5.1 概述
霍夫变换是一种特征检测,通过霍夫变换可以检测出图像中存在的特殊的形状。比如,直线,圆等。
霍夫变换检测直线时,算法有两个,
- 一个是cv2.HoughLines() 方法,用于检测无限延长的直线;
- 另一个是cv2.HoughLinesP() 方法,用于检测线段。
霍夫变换检测圆,使用的是**cv2.HoughCircles()**方法。
使用这三个方法前,都要先对图像进行降噪处理(使用滤波器),以去除干扰。
5.2 cv2.HoughLines() 检测直线
cv2.HoughLines()语法如下:
lines = cv.HoughLines( image, rho, theta, threshold[,srn][,stn])
其中
- image 即原图像
- rho 指的是搜索直线使用的半径步长,其值为1时表示检测所有。(即极坐标中的ρ)
- theta 指的是搜索直线的角度,值为π/180°时,表示检测所有角度。(即极坐标中的θ)
- threshold 指的是阈值,点的数量(也称投票数)。因为直线的长度取决于直线上的点的数量。所以如果达不到这个长度,就不会被判定为直线。同理,当该阈值越小,检测出的直线也就越多。
- srn 对于多尺度霍夫变换,srn表示对rho的 距离分辨率 的除数
- stn 对于多尺度霍夫变换,stn表示对theta的 距离分辨率 的除数
- min_theat 对于标准和多尺度Hough变换,检查线条的最小角度。必须介于0和最大θ之间。
- max_theat 对于标准和多尺度Hough变换,检查线条的最大角度。必须介于min_theta和CV_PI之间。
返回值lines,是一个数组,shape为(n, 1, 2),n表示检测出的所有线段数目,每个线段用极坐标(ρ, θ)表示。
以此跨海大桥图(test2.jpg)为例,对其使用cv2.HoughLines()方法,并绘制直线:
import cv2 import numpy as np img = cv2.imread("test2.jpg") o = img.copy() # 使用中值滤波进行降噪 o = cv2.medianBlur(o, 5) # 从彩色图像变成单通道灰度图像 gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY) # 绘制边缘图像 binary = cv2.Canny(o, 50, 150) # 检测直线 不限步长,不限角度,至少50个点确定一条线 lines = cv2.HoughLines(binary, 1, np.pi / 180, 50) # print(lines) # print(lines.shape) for line in lines: rho, theta = line[0] 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), (0, 0, 255), 2) cv2.imshow("img", img) cv2.waitKey() cv2.destroyAllWindows()
识别效果如下,可以自行调节参数进行改良,并在这个过程中具体感受每个参数的作用。
当选择至少50个点确定一条线时,一共检测出168根直线:
当选择至少100个点确定一条线时,一共检测出35根直线:
当选择至少300个点确定一条直线时,符合要求的直线有3根:
当选择至少380个点确定一条直线时,这样的直线还剩一根:
相比检测直线,检测线段的cv2.HoughLinesP()相对要更常用些。
5.3 cv2.HoughLinesP() 检测线段
lines = cv2.HoughLinesP(image, rho, theta, threshold, minLineLength=None, maxLineGap=None)
检测线段
- image 原图
- rho 检测直线使用的半径步长,值为1时表示所有可能的半径步长
- theta 搜索直线的角度
- threshold 阈值,该值越小,检测出的直线越多。
- minLineLength表示线段的最小长度,小于该长度的线段不会被记录在结果中。值越大线段越少。
- maxLineGap 表示允许将同一行的点连接起来的最大距离。 值越大线段越多。
返回值lines,是一个数组,shape为(n, 1, 4),n表示检测出的所有线段数目,4指的是每个线段的两端端点的笛卡尔坐标(x, y) 坐标的四个点。
其中minLineLength(最小线段长度)和maxLineGap(最小线段距离)两个参数,都是越大,识别的线段越少。
import cv2 import numpy as np img = cv2.imread("test2.jpg") o = img.copy() # 使用中值滤波进行降噪 o = cv2.medianBlur(o, 5) # 从彩色图像变成单通道灰度图像 gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY) # 绘制边缘图像 binary = cv2.Canny(o, 50, 150) # 检测线段,不限步长,不限角度,至少100个点确定一条线。最大将距离为200的线段连城一条线。 lines = cv2.HoughLinesP(binary, 1, np.pi / 180, 50, minLineLength=100, maxLineGap=200) print(lines.shape) for line in lines: x1, y1, x2, y2 = line[0] cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imshow("canny", binary) cv2.imshow("img", img) cv2.waitKey() cv2.destroyAllWindows()
二值化边缘图案效果:
描绘线段效果(minLineLength=100)至少100个点确定一条线段(上边代码),最大将距离为200的线段连接起来
共描绘了23条线:
描绘线段效果(minLineLength=500)至少500个点确定一条线段,最大将距离为200的线段连接起来
共描绘了6条线段:
描绘线段效果(minLineLength=720)至少720个点确定一条线段,最大将距离为200的线段连接起来
共描绘了一条线:
5.4 cv2.检测圆 - HoughCircles()
circles = HoughCircles(image, method, dp, minDist, circles=None, param1=None, param2=None, minRadius=None, maxRadius=None)
其中
- image 原图像(降噪、灰度处理后的)
- method 检测方法。
- dp 累加器分辨率与原图分辨率之比的倒数。值为1时累加器与原图像有着相同的分辨率。通常选择1作为参数。(值为2时则累加器的分辨率是原图像的一半)
- minDist 圆心之间的最小距离
- param1 参数1,表示Canny边缘检测的最大阈值。是可选参数。
- param2 参数2,表示检测结果投票数。即至少多少个点确定一个圆。值越大,识别的圆越少,约精准。是可选参数。
- minRadius 圆环的最小半径(可选参数)
- maxRadius 圆环的最大半径(可选参数)
返回值circles是一个数组,数组内是所有检测出的圆环,shape为(n,1,3)。其中3表示圆心的x坐标,圆心的y坐标和半径长度三个指标。
了解完语法,
接下来我们来检测下图(test3.jpg)中的客家土楼中的圆形。
import cv2 import numpy as np img = cv2.imread("test3.jpg") # 使用中值滤波进行降噪 o = cv2.medianBlur(img, 11) # 从彩色图像变成单通道灰度图像 gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY) # 展示灰度图像 cv2.imshow('gray', gray) # 检测圆环,圆心最小间距为50,Canny最大阈值为40,投票数超过63。最小半径为10,最大半径为50 circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 50, param1=40, param2=63, minRadius=10, maxRadius=100) # 将数组元素四舍五入成整数 circles = np.uint(np.around(circles)) # 遍历圆环结果 for c in circles[0]: # 圆心横坐标、纵坐标和圆半径 x, y, r = c # 绘制圆环 cv2.circle(img, (x, y), r, (0, 0, 255), 3) # 绘制圆心 cv2.circle(img, (x, y), 2, (0, 0, 255), 3) cv2.imshow("img", img) cv2.waitKey() cv2.destroyAllWindows()
降过噪的灰度图像如下:
识别结果呈现如下,如图成功识别出了图中所有土楼的圆形: