目录
Canny边缘检测算法原理与实现
Canny边缘检测是计算机视觉中最经典的边缘检测算法之一,由John F. Canny在1986年提出。下面我将详细介绍其原理,并提供Python实现代码。
算法原理
Canny边缘检测包含以下5个步骤:
-
高斯滤波:使用高斯滤波器平滑图像,减少噪声影响
-
计算梯度:使用Sobel算子计算图像的梯度幅值和方向
-
非极大值抑制:保留梯度幅值局部最大值,细化边缘
-
双阈值检测:使用高低阈值确定强边缘和弱边缘
-
边缘连接:通过滞后阈值处理连接边缘
Python实现代码
关键步骤详解
-
高斯滤波:
-
使用高斯核平滑图像,减少噪声影响
-
高斯核大小影响边缘检测的敏感度
-
-
梯度计算:
-
使用Sobel算子计算x和y方向的梯度
-
梯度幅值:
G = sqrt(Gx² + Gy²) -
梯度方向:
θ = arctan(Gy/Gx)
-
-
非极大值抑制:
-
检查每个像素在梯度方向上的邻域
-
只保留梯度幅值最大的像素,细化边缘
-
-
双阈值检测:
-
高阈值:确定强边缘
-
低阈值:确定弱边缘
-
典型高低阈值比为1:2或1:3
-
-
边缘连接:
-
弱边缘只有在连接到强边缘时才保留
-
确保边缘的连续性
-
算法特点
-
优点:
-
低错误率:尽可能少地检测非边缘点
-
高定位性:检测的边缘点尽可能接近真实边缘
-
最小响应:边缘点尽可能少,每个边缘只检测一次
-
-
参数选择:
-
高斯核大小:影响平滑程度,通常5x5
-
高低阈值:需要根据图像内容调整
-
Sobel核大小:通常3x3
-
这个实现展示了Canny边缘检测的核心原理,并与OpenCV的实现进行了比较。实际应用中,通常直接使用OpenCV的cv2.Canny()函数,因为它经过了高度优化。
实现代码
python
import numpy as np
import cv2
import matplotlib.pyplot as plt
def canny_edge_detector(image, low_threshold=50, high_threshold=150, kernel_size=5):
"""
手动实现Canny边缘检测算法
参数:
image: 输入灰度图像
low_threshold: 低阈值
high_threshold: 高阈值
kernel_size: 高斯核大小
返回:
edges: 边缘检测结果
"""
# 步骤1: 高斯滤波
blurred = cv2.GaussianBlur(image, (kernel_size, kernel_size), 0)
# 步骤2: 使用Sobel算子计算梯度
grad_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
grad_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)
# 计算梯度幅值和方向
grad_magnitude = np.sqrt(grad_x**2 + grad_y**2)
grad_direction = np.arctan2(grad_y, grad_x) * 180 / np.pi
# 将角度规范化到0-180度
grad_direction = np.mod(grad_direction, 180)
# 步骤3: 非极大值抑制
suppressed = non_maximum_suppression(grad_magnitude, grad_direction)
# 步骤4和5: 双阈值检测和边缘连接
edges = hysteresis_thresholding(suppressed, low_threshold, high_threshold)
return edges
def non_maximum_suppression(grad_mag, grad_dir):
"""
非极大值抑制
"""
height, width = grad_mag.shape
suppressed = np.zeros_like(grad_mag)
for i in range(1, height-1):
for j in range(1, width-1):
angle = grad_dir[i, j]
mag = grad_mag[i, j]
# 将角度分类到最近的45度倍数
if (0 <= angle < 22.5) or (157.5 <= angle <= 180):
neighbor1 = grad_mag[i, j+1]
neighbor2 = grad_mag[i, j-1]
elif 22.5 <= angle < 67.5:
neighbor1 = grad_mag[i+1, j-1]
neighbor2 = grad_mag[i-1, j+1]
elif 67.5 <= angle < 112.5:
neighbor1 = grad_mag[i+1, j]
neighbor2 = grad_mag[i-1, j]
else: # 112.5 <= angle < 157.5
neighbor1 = grad_mag[i-1, j-1]
neighbor2 = grad_mag[i+1, j+1]
# 如果当前点是局部最大值,则保留
if mag >= neighbor1 and mag >= neighbor2:
suppressed[i, j] = mag
return suppressed
def hysteresis_thresholding(image, low_threshold, high_threshold):
"""
双阈值检测和边缘连接
"""
height, width = image.shape
result = np.zeros_like(image, dtype=np.uint8)
# 标记强边缘(>=高阈值)和弱边缘(>=低阈值且<高阈值)
strong_edges = (image >= high_threshold)
weak_edges = (image >= low_threshold) & (image < high_threshold)
# 强边缘直接保留
result[strong_edges] = 255
# 对于弱边缘,如果连接到强边缘则保留
for i in range(1, height-1):
for j in range(1, width-1):
if weak_edges[i, j]:
# 检查8邻域是否有强边缘
if np.any(strong_edges[i-1:i+2, j-1:j+2]):
result[i, j] = 255
return result
def compare_with_opencv(image_path='test_image.png'):
"""
比较手动实现和OpenCV实现的Canny边缘检测
"""
# 读取图像
if image_path == 'test_image.png':
# 如果没有图片,创建一个简单的测试图像
img = np.zeros((300, 400), dtype=np.uint8)
cv2.line(img, (50, 50), (350, 50), 255, 2) # 水平线
cv2.line(img, (50, 100), (350, 200), 255, 2) # 斜线
cv2.line(img, (200, 50), (200, 250), 255, 2) # 垂直线
cv2.line(img, (100, 200), (300, 200), 255, 2) # 另一条水平线
else:
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 手动实现
manual_edges = canny_edge_detector(img)
# OpenCV实现
opencv_edges = cv2.Canny(img, 50, 150)
# 可视化比较
plt.figure(figsize=(15, 5))
plt.subplot(1, 3, 1)
plt.imshow(img, cmap='gray')
plt.title('原始图像')
plt.axis('off')
plt.subplot(1, 3, 2)
plt.imshow(manual_edges, cmap='gray')
plt.title('手动实现Canny')
plt.axis('off')
plt.subplot(1, 3, 3)
plt.imshow(opencv_edges, cmap='gray')
plt.title('OpenCV Canny')
plt.axis('off')
plt.tight_layout()
plt.show()
# 测试代码
if __name__ == "__main__":
compare_with_opencv()
运行输出
