使用Python进行图片的边缘检测
图片边缘检测是计算机视觉中的一项重要任务,它可以帮助我们识别物体的边缘并进行形状分析,实现图像分割、目标跟踪、文本识别等应用。在本篇文章中,我们将使用Python实现基于Canny算法的图片边缘检测,并探讨一些相关的基础概念和关键技术。
一、Canny算法简介
Canny算法是一种常用的边缘检测算法,它由John F.?Canny于1986年提出。该算法被广泛应用于数字图像处理中,因其对噪声具有很好的抵抗力、对细节进行保留和检测率高等优点,被认为是一种效果最好的边缘检测算法之一。
Canny算法的主要思想是:在图像中寻找像素点值变化最快的地方,并把它们作为边缘进行标记。该算法的基本步骤如下:
1. 对原始图像进行高斯平滑滤波,以消除噪声。
2. 计算图像的梯度(一阶偏导数),并求得每个像素点的梯度大小和梯度方向。
3. 非极大值抑制:对每个像素点,在梯度方向上进行插值,比较其与相邻两个像素点的大小,若不是局部最大值,则舍弃。
4. 双阈值处理:根据前景和背景的灰度值之间的差异,将像素点分为强边缘、弱边缘和非边缘三类。强边缘保留,弱边缘按照是否与强边缘连通决定保留或删除,非边缘舍弃。
二、Python实现
Python作为一门兼具简洁与强大的编程语言,拥有大量的数字图像处理相关库和工具,例如Pillow、opencv-python、scikit-image等,可以快速方便地实现图片边缘检测算法。
在本文中,我们使用Python的opencv-python库来实现Canny算法的图片边缘检测。首先需要使用pip进行安装,在终端输入以下命令:
pip install opencv-python
安装完成后,我们就可以开始编写边缘检测的代码了。
1. 导入库和加载图片
在Python中,我们需要先导入需要使用的库和工具,包括:
- opencv-python库:一款基于C++编写的开源计算机视觉库,提供许多常用的图像处理和计算方法。
- numpy库:一款强大的数据处理工具,提供高效的数组处理和科学计算。
在导入库后,我们需要使用cv2.imread()函数读取需要进行处理的图片,并使用cv2.imshow()、cv2.waitKey()和cv2.destroyAllWindows()函数显示和关闭图像窗口。以下是完整的代码:
import cv2
import numpy as np
image = cv2.imread('lena.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imshow('Original Image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
其中,cv2.IMREAD_GRAYSCALE表示将图片以灰度模式加载,这样能够加快计算速度和减少噪声。
2. 高斯平滑处理
为了减少噪声,并使得后续的梯度计算更加准确,我们需要对原始图像进行高斯平滑处理。使用cv2.GaussianBlur()函数可以实现高斯平滑滤波。
该函数的参数包括:
- src:读取的原始图片。
- ksize:高斯核的大小,通常为奇数。
- sigmaX:x方向平滑的方差。
- sigmaY:y方向平滑的方差。
以下是完整的代码:
blur = cv2.GaussianBlur(image, (3,3), 0)
cv2.imshow('Blurred Image', blur)
cv2.waitKey(0)
cv2.destroyAllWindows()
其中,(3,3)表示高斯核的大小为3x3,0表示sigmaX和sigmaY都采用默认值,即根据高斯核的大小自动计算得到。
3. 计算梯度和方向
当图像完成高斯平滑之后,我们需要计算图像的梯度和方向,以便后续进行非极大值抑制和双阈值处理。opencv-python提供了cv2.Sobel()函数和cv2.magnitude()函数来实现这一步骤。
cv2.Sobel()函数的参数包括:
- src:读取的经过高斯平滑之后的图片。
- ddepth:目标图像的深度。
- dx:1代表水平方向,0代表垂直方向,-1代表同时计算两个方向。
- dy:1代表垂直方向,0代表水平方向,-1代表同时计算两个方向。
- ksize:Sobel算子的大小。
- scale:尺度因子,默认为1。
- delta:平移因子,默认为0。
cv2.magnitude()函数的参数包括:
- x:计算得到的x轴方向梯度。
- y:计算得到的y轴方向梯度。
以下是完整的代码:
sobelx = cv2.Sobel(blur, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(blur, cv2.CV_64F, 0, 1, ksize=3)
magnitude = cv2.magnitude(sobelx, sobely)
cv2.imshow('Magnitute Image', magnitude)
cv2.waitKey(0)
cv2.destroyAllWindows()
其中,使用cv2.CV_64F作为ddepth参数表示计算得到的梯度值的数据类型是64位浮点数,可以比较准确地反映每个像素点的梯度变化情况。
4. 非极大值抑制
为了避免算法在强边缘和弱边缘之间产生混淆,我们需要对每个像素点进行非极大值抑制处理,使得每个像素点的梯度方向上的值都是相对最大值。
我们可以通过以下步骤实现非极大值抑制:
- 计算每个像素点的梯度方向,通常将它们分成四个方向:0°、45°、90°和135°。
- 对于每个像素点,根据计算出的梯度方向,在相应方向上取得两个邻近像素点。
- 比较当前像素点的梯度值与两个邻近像素点的梯度值,若比它们都大,则保留该点,否则舍弃。
opencv-python提供了cv2.phase()函数可以计算得到每个像素点的梯度方向,并可以使用numpy的数组切片和比较操作实现非极大值抑制。以下是完整的代码:
angles = cv2.phase(sobelx, sobely, angleInDegrees=True)
rows, cols = magnitude.shape
for i in range(1, rows-1):
for j in range(1, cols-1):
value = magnitude[i, j]
angle = angles[i, j]
if (0 <= angle < 22.5) or (157.5 <= angle <= 180):
adjacent_pixels = [magnitude[i, j-1], magnitude[i, j+1]]
elif (22.5 <= angle < 67.5):
adjacent_pixels = [magnitude[i-1, j-1], magnitude[i+1, j+1]]
elif (67.5 <= angle < 112.5):
adjacent_pixels = [magnitude[i-1, j], magnitude[i+1, j]]
else:
adjacent_pixels = [magnitude[i-1, j+1], magnitude[i+1, j-1]]
if value >= max(adjacent_pixels):
magnitude[i, j] = value
else:
magnitude[i, j] = 0
cv2.imshow('NMS Image', magnitude)
cv2.waitKey(0)
cv2.destroyAllWindows()
其中,使用了angleInDegrees=True参数指定了计算出的角度是以度为单位的,方便我们理解和处理。
5. 双阈值处理
经过上述步骤之后,我们可以得到已经去除了噪声和冗余的边缘图。但是,我们需要对图像进行双阈值处理,以便得到更加精准的结果。
双阈值处理是指根据图像的灰度值将像素点分为三类:强边缘、弱边缘和非边缘。默认的阈值比例是1:3,即对于强边缘,像素点灰度值高于一个较高的阈值;对于弱边缘,像素点灰度值在高低阈值之间;对于非边缘,像素点灰度值低于一个较低的阈值。
使用cv2.threshold()函数可以实现双阈值处理,或者使用cv2.Canny()函数可以同时完成高斯平滑、梯度计算、非极大值抑制和双阈值处理。以下是完整的代码:
low_threshold = 0.4 * np.max(magnitude)
high_threshold = 0.6 * np.max(magnitude)
strong_edge = 255
weak_edge = 50
threshold, threshold_image = cv2.threshold(magnitude, low_threshold, 255, cv2.THRESH_BINARY)
threshold_max, threshold2_image = cv2.threshold(magnitude, high_threshold, 255, cv2.THRESH_BINARY)
threshold2_image[magnitude < low_threshold] = 0
hysteresis_image = np.zeros_like(threshold_image)
rows, cols = magnitude.shape
for i in range(1, rows-1):
for j in range(1, cols-1):
if threshold_image[i, j] == strong_edge:
hysteresis_image[i, j] = strong_edge
elif threshold_image[i, j] == weak_edge:
if np.max(hysteresis_image[i-1:i+1, j-1:j+1]) == strong_edge:
hysteresis_image[i, j] = strong_edge
else:
hysteresis_image[i, j] = 0
cv2.imshow('Canny Image', hysteresis_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
其中,使用0.4和0.6分别作为低阈值和高阈值的比例,可以最大化保留图像的边缘信息而不产生假阳性和假阴性。同时,我们需要设定强边缘和弱边缘的灰度值,以便对图像进行分类处理。可以使用np.zeros_like()函数创建一个与原始图像大小和类型相同的零矩阵,并使用矩阵运算和掩模操作对像素点进行分类处理。
三、总结
本篇文章介绍了边缘检测的基本原理,重点介绍了Canny算法,并使用Python的opencv-python库实现了Canny算法。在实践中,我们发现边缘检测算法的实现需要深入理解数学和计算机视觉等相关领域的知识,可以结合实际业务需求和使用场景进行优化和改进。同时,我们也需要注意算法执行时间和计算资源的消耗,以便更好地平衡算法的准确性和效率。
参考文献:
[1] Canny J. A computational approach to edge detection[C]//Readings in computer vision. Elsevier, 1987: 184-203.
[2] Sobel I. An Isotropic 3x3 Image Gradient Operator[C]. Image Processing, 1990. Proceedings. ICIP-90., First IEEE International Conference on. IEEE, 1990: 27-30.
[3] https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_canny/py_canny.html
[4] https://docs.opencv.org/master/ d6/d10/sobel_8cpp.html
[5] https://github.com/opencv/opencv/blob/master/modules/imgproc/src/canny.cpp