1. Canny算子:边缘检测的黄金标准
第一次接触Canny算子是在研究生时期的计算机视觉课上。当时教授在黑板上画了一条波浪线,说:"这就是边缘检测要解决的问题——从噪声中找到真实的信号。"十年过去了,我依然清晰地记得第一次用Python实现Canny算子时那种豁然开朗的感觉。
Canny算子由John Canny在1986年提出,至今仍是工业界最常用的边缘检测算法。它之所以能经久不衰,关键在于其设计哲学:不是简单地寻找图像中的强度变化,而是通过一套严谨的流程,在检出率和准确率之间取得最佳平衡。就像一位经验丰富的侦探,Canny不会放过任何线索,但也不会被假线索误导。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Canny算子的五步流程详解
2.1 高斯滤波:为图像降噪
任何边缘检测的第一步都是降噪。图像中的噪声就像侦探案发现场的无关指纹,会干扰真正的线索。高斯滤波使用一个二维高斯核与图像进行卷积,有效平滑噪声。
python复制import cv2
import numpy as np
def gaussian_kernel(size, sigma=1):
"""生成二维高斯核"""
kernel = np.fromfunction(
lambda x, y: (1/(2*np.pi*sigma**2)) *
np.exp(-((x-(size-1)/2)**2 + (y-(size-1)/2)**2)/(2*sigma**2)),
(size, size)
)
return kernel / np.sum(kernel)
# 示例:5x5高斯核
kernel = gaussian_kernel(5, sigma=1.4)
print(kernel)
注意:σ值的选择至关重要。σ太小,噪声过滤不彻底;σ太大,边缘会过度模糊。实践中,1.0-2.0是常用范围。
2.2 计算梯度:寻找边缘候选
梯度计算相当于测量图像中每个像素点的"坡度"。我们使用Sobel算子分别计算x和y方向的梯度:
python复制def sobel_filters(img):
"""使用Sobel算子计算梯度"""
Kx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], np.float32)
Ky = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]], np.float32)
Ix = cv2.filter2D(img, -1, Kx)
Iy = cv2.filter2D(img, -1, Ky)
G = np.hypot(Ix, Iy) # 梯度幅值
G = G / G.max() * 255 # 归一化
theta = np.arctan2(Iy, Ix) # 梯度方向
return G, theta
梯度方向需要量化为四个主要方向(0°, 45°, 90°, 135°),这是为后续的非极大值抑制做准备。
2.3 非极大值抑制:细化边缘
这是Canny算子的核心步骤。想象你正在山脊上行走,非极大值抑制就是只保留山脊最高点的路径:
python复制def non_max_suppression(G, theta):
"""非极大值抑制"""
M, N = G.shape
Z = np.zeros((M,N), dtype=np.float32)
angle = theta * 180. / np.pi
angle[angle < 0] += 180
for i in range(1,M-1):
for j in range(1,N-1):
try:
q = 255
r = 255
# 0度方向
if (0 <= angle[i,j] < 22.5) or (157.5 <= angle[i,j] <= 180):
q = G[i, j+1]
r = G[i, j-1]
# 45度方向
elif (22.5 <= angle[i,j] < 67.5):
q = G[i+1, j-1]
r = G[i-1, j+1]
# 90度方向
elif (67.5 <= angle[i,j] < 112.5):
q = G[i+1, j]
r = G[i-1, j]
# 135度方向
elif (112.5 <= angle[i,j] < 157.5):
q = G[i-1, j-1]
r = G[i+1, j+1]
if (G[i,j] >= q) and (G[i,j] >= r):
Z[i,j] = G[i,j]
else
