1. Canny边缘检测简介
Canny边缘检测算法是一种非常经典的图像处理算法,常用于计算机视觉任务中边缘检测方面。它可以有效地检测出图像中的边缘,具有较好的鲁棒性和精度。Canny边缘检测算法是由John Canny在1986年提出的,其设计的目的是使得边缘检测能够更加准确地定位真实边缘,同时有效地抑制噪声。
2. Canny边缘检测原理
2.1 介绍
Canny边缘检测算法的原理非常复杂,不过可以简单地概括为以下四个步骤:
使用高斯滤波器对图像进行平滑处理,以降低噪声的干扰
计算图像中每个像素点的梯度大小和方向
对梯度幅值进行非极大值抑制处理,以抑制非边缘响应
使用双阈值算法对梯度幅值进行阈值化处理,从而得到图像中的真实边缘
2.2 具体实现
下面我们来具体解释一下Canny边缘检测算法的实现过程:
高斯滤波器处理
在实现Canny边缘检测算法之前,我们需要用高斯滤波器对图像进行平滑处理。原因是图像中可能存在不可忽略的噪声,而这些噪声会对边缘检测的结果产生影响。高斯滤波器可以在滤波的同时对图像进行模糊化处理,从而去除噪声。实现代码如下:
import cv2
img = cv2.imread('image.jpg')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gaussian_img = cv2.GaussianBlur(gray_img, (3, 3), 0)
在代码中,我们首先读取一张图像,并将其转换为灰度图像。然后使用cv2.GaussianBlur()函数对灰度图像进行高斯滤波处理,其中(3, 3)表示高斯核的大小,0表示高斯核的方差。
计算梯度大小和方向
在进行边缘检测之前,我们需要计算图像中每个像素点的梯度大小和方向。梯度的大小定义为像素值变化最大的方向的导数,而梯度的方向则指示了像素值变化最大的方向。我们可以通过Sobel算子来计算梯度大小和方向。实现代码如下:
import numpy as np
x_gradient = cv2.Sobel(gaussian_img, cv2.CV_64F, 1, 0, ksize=3)
y_gradient = cv2.Sobel(gaussian_img, cv2.CV_64F, 0, 1, ksize=3)
magnitude = np.sqrt(np.square(x_gradient) + np.square(y_gradient)) # 梯度大小
magnitude = cv2.normalize(magnitude, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
theta = np.arctan2(y_gradient, x_gradient) * 180 / np.pi # 梯度方向
在代码中,我们分别使用Sobel算子计算水平方向和竖直方向的梯度。然后使用np.sqrt()计算梯度大小,使用np.arctan2()计算梯度方向。最后使用cv2.normalize()函数将梯度大小归一化到0-255范围内,并将其转换为uint8类型以便后续处理。
非极大值抑制
在计算梯度大小和方向之后,我们需要对梯度幅值进行非极大值抑制处理。主要的目的是抑制非边缘响应,保留真实的边缘。具体操作是:对每一个像素点,根据其梯度方向,选择其梯度方向上相邻两个像素点的梯度幅值进行比较,若该像素点梯度幅值不是其梯度方向上的最大值,则将该像素点梯度幅值设为0。实现代码如下:
def non_maximum_suppression(magnitude, theta):
rows, cols = magnitude.shape
suppressed = np.zeros((rows, cols))
angle = theta % 180
for i in range(1, rows-1):
for j in range(1, cols-1):
grad = magnitude[i, j]
if grad == 0:
suppressed[i, j] = 0
else:
dx = magnitude[i, j+1] - magnitude[i, j-1]
dy = magnitude[i+1, j] - magnitude[i-1, j]
if abs(dx) > abs(dy):
tangent = np.abs(dy/dx)
if angle[i, j] < 45:
x0, y0 = i, j+1
x1, y1 = i+1, j+1
else:
x0, y0 = i+1, j
x1, y1 = i+1, j-1
else:
tangent = np.abs(dx/dy)
if angle[i, j] < 45:
x0, y0 = i+1, j
x1, y1 = i+1, j-1
else:
x0, y0 = i, j+1
x1, y1 = i+1, j+1
if grad >= magnitude[x0, y0] and grad >= magnitude[x1, y1]:
suppressed[i, j] = grad
else:
suppressed[i, j] = 0
return suppressed
suppressed = non_maximum_suppression(magnitude, theta)
在代码中,我们首先定义了一个non_maximum_suppression()函数,其输入参数为梯度大小和梯度方向的矩阵。然后在函数中,我们遍历每个像素点,在其梯度方向上选择相邻两个像素点的梯度幅值进行比较,从而得到该像素点处的非极大值抑制结果。最后返回抑制结果矩阵。
双阈值算法
在进行完非极大值抑制之后,我们需要使用双阈值算法来确定图像中的真实边缘。双阈值算法将梯度幅值分成两个阈值范围,大于高阈值的被当作强边缘,小于低阈值的被舍弃,介于两个阈值之间的根据连通性被当作弱边缘或噪声,若弱边缘与强边缘连通,则最终认为是真实边缘。实现代码如下:
def thresholding(img, low_threshold, high_threshold):
strong_edges = (img >= high_threshold)
thresholded = np.zeros_like(img)
thresholded[strong_edges] = 255
weak_edges = (img >= low_threshold) & (img < high_threshold)
rows, cols = img.shape
for i in range(1, rows-1):
for j in range(1, cols-1):
if weak_edges[i, j]:
if np.any(strong_edges[i-1:i+2, j-1:j+2]):
thresholded[i, j] = 255
else:
thresholded[i, j] = 0
return thresholded
low_threshold = 20
high_threshold = 40
thresholded = thresholding(suppressed, low_threshold, high_threshold)
在代码中,我们首先定义了一个threshholding()函数,其输入参数为抑制结果矩阵、低阈值和高阈值。在函数中,我们将大于高阈值的像素点标记为强边缘,小于低阈值的像素点舍弃,介于两个阈值之间但未经过非极大值抑制的像素点标记为噪声,其余标记为弱边缘。然后对于每一个弱边缘像素点,我们检查其8邻域是否存在强边缘像素点,若存在,则将其标记为真实边缘,否则舍弃它。
3. Canny边缘检测实现
上述是Canny边缘检测算法的基本原理及实现过程,以下是使用OpenCV Python库来实现Canny边缘检测的完整代码:
import cv2
import numpy as np
def non_maximum_suppression(magnitude, theta):
rows, cols = magnitude.shape
suppressed = np.zeros((rows, cols))
angle = theta % 180
for i in range(1, rows-1):
for j in range(1, cols-1):
grad = magnitude[i, j]
if grad == 0:
suppressed[i, j] = 0
else:
dx = magnitude[i, j+1] - magnitude[i, j-1]
dy = magnitude[i+1, j] - magnitude[i-1, j]
if abs(dx) > abs(dy):
tangent = np.abs(dy/dx)
if angle[i, j] < 45:
x0, y0 = i, j+1
x1, y1 = i+1, j+1
else:
x0, y0 = i+1, j
x1, y1 = i+1, j-1
else:
tangent = np.abs(dx/dy)
if angle[i, j] < 45:
x0, y0 = i+1, j
x1, y1 = i+1, j-1
else:
x0, y0 = i, j+1
x1, y1 = i+1, j+1
if grad >= magnitude[x0, y0] and grad >= magnitude[x1, y1]:
suppressed[i, j] = grad
else:
suppressed[i, j] = 0
return suppressed
def thresholding(img, low_threshold, high_threshold):
strong_edges = (img >= high_threshold)
thresholded = np.zeros_like(img)
thresholded[strong_edges] = 255
weak_edges = (img >= low_threshold) & (img < high_threshold)
rows, cols = img.shape
for i in range(1, rows-1):
for j in range(1, cols-1):
if weak_edges[i, j]:
if np.any(strong_edges[i-1:i+2, j-1:j+2]):
thresholded[i, j] = 255
else:
thresholded[i, j] = 0
return thresholded
img = cv2.imread('image.jpg')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gaussian_img = cv2.GaussianBlur(gray_img, (3, 3), 0)
x_gradient = cv2.Sobel(gaussian_img, cv2.CV_64F, 1, 0, ksize=3)
y_gradient = cv2.Sobel(gaussian_img, cv2.CV_64F, 0, 1, ksize=3)
magnitude = np.sqrt(np.square(x_gradient) + np.square(y_gradient))
magnitude = cv2.normalize(magnitude, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
theta = np.arctan2(y_gradient, x_gradient) * 180 / np.pi
suppressed = non_maximum_suppression(magnitude, theta)
low_threshold = 20
high_threshold = 40
thresholded = thresholding(suppressed, low_threshold, high_threshold)
cv2.imshow('Original Image', img)
cv2.imshow('Canny Edge Detection', thresholded)
cv2.waitKey(0)
cv2.destroyAllWindows()
在代码中,我们首先读取了一张图像,并将其转换为灰度图像。然后进行高斯滤波处理,计算梯度大小和方向,进行非极大值抑制,最后进行双阈值算法处理,得到图像中的真实边缘。最后使用cv2.imshow()函数显示原始图像和检测出的边缘图像,并使用cv2.waitKey()函数等待用户的按键输入,最后使用cv2.destroyAllWindows()函数关闭所有窗口。
4. 总结
在本篇文章中,我们详细介绍了Canny边缘检测算法的基本原理及实现过程。Canny边缘检测算法可以有效地检测出图像中的边缘,具有较好的鲁棒性和精度,在计算机视觉任务中得到了广泛的应用。同时,我们也借此机会学习了使用OpenCV Python库实现Canny边缘检测的方法,并实现了一个简单的边缘检测应用。希望本篇文章对大家学习Canny边缘检测算法有所帮助。