2. 基于空间矩的亚像素边缘检测
任务定义
本案例实现基于空间矩(Spatial Moments)的亚像素边缘检测算法。输入为一张包含合成矩形目标的灰度图像,算法输出每个边缘点的亚像素坐标,并与传统Canny边缘检测得到的整数像素坐标进行对比,量化亚像素定位的精度提升。
空间矩亚像素边缘原理
空间矩方法利用边缘邻域内灰度分布的矩信息来估计边缘的精确位置。对于理想阶跃边缘,其归一化空间矩满足闭合解。实际实现中,使用5×5卷积核计算图像的空间矩M00、M10、M01、M20、M02,进而求解边缘法线方向角θ和边缘到像素中心的距离l,最终得到亚像素坐标偏移量。
代码实现
import numpy as np
import cv2
from scipy.ndimage import convolve
# ---------- 空间矩核(5x5) ----------
M00_kernel = np.array([
[0.0196, 0.0392, 0.0392, 0.0392, 0.0196],
[0.0392, 0.0784, 0.0784, 0.0784, 0.0392],
[0.0392, 0.0784, 0.0784, 0.0784, 0.0392],
[0.0392, 0.0784, 0.0784, 0.0784, 0.0392],
[0.0196, 0.0392, 0.0392, 0.0392, 0.0196]
])
M10_kernel = np.array([
[-0.0147, -0.0294, 0, 0.0294, 0.0147],
[-0.0294, -0.0588, 0, 0.0588, 0.0294],
[-0.0294, -0.0588, 0, 0.0588, 0.0294],
[-0.0294, -0.0588, 0, 0.0588, 0.0294],
[-0.0147, -0.0294, 0, 0.0294, 0.0147]
])
M01_kernel = M10_kernel.T
M20_kernel = np.array([
[0.0147, 0.0294, 0, -0.0294, -0.0147],
[0.0294, 0.0588, 0, -0.0588, -0.0294],
[0.0294, 0.0588, 0, -0.0588, -0.0294],
[0.0294, 0.0588, 0, -0.0588, -0.0294],
[0.0147, 0.0294, 0, -0.0294, -0.0147]
])
M02_kernel = M20_kernel.T
def spatial_moment_subpixel(gray_img, edge_mask):
"""输入灰度图和整数像素边缘掩码,返回亚像素坐标列表"""
gray = gray_img.astype(np.float64)
m00 = convolve(gray, M00_kernel, mode='constant')
m10 = convolve(gray, M10_kernel, mode='constant')
m01 = convolve(gray, M01_kernel, mode='constant')
m20 = convolve(gray, M20_kernel, mode='constant')
m02 = convolve(gray, M02_kernel, mode='constant')
subpixel_points = []
ys, xs = np.nonzero(edge_mask)
for y, x in zip(ys, xs):
# 归一化矩
if abs(m00[y, x]) < 1e-6:
continue
m10_n = m10[y, x] / m00[y, x]
m01_n = m01[y, x] / m00[y, x]
m20_n = m20[y, x] / m00[y, x]
m02_n = m02[y, x] / m00[y, x]
# 边缘法线方向角
theta = 0.5 * np.arctan2(2 * m01_n, m10_n - m02_n)
# 边缘到中心距离(归一化)
l = m10_n * np.cos(theta) + m01_n * np.sin(theta)
# 亚像素偏移(像素单位)
dx = l * np.cos(theta)
dy = l * np.sin(theta)
subpixel_points.append((x + dx, y + dy))
return subpixel_points
# ---------- 生成合成测试图像 ----------
img = np.zeros((200, 300), dtype=np.uint8)
cv2.rectangle(img, (80, 60), (220, 140), 255, -1) # 白色矩形
# 添加轻微高斯噪声模拟真实场景
noise = np.random.normal(0, 5, img.shape).astype(np.int16)
img_noisy = np.clip(img.astype(np.int16) + noise, 0, 255).astype(np.uint8)
# ---------- 传统Canny边缘(整数像素) ----------
edges = cv2.Canny(img_noisy, 50, 150)
# ---------- 空间矩亚像素提取 ----------
subpixel_pts = spatial_moment_subpixel(img_noisy, edges)
# ---------- 精度评估(使用无噪声真值) ----------
# 真实边缘:矩形左边界x=80,右边界x=220,上边界y=60,下边界y=140
true_edges = []
for y in range(60, 141):
true_edges.append((80.0, float(y)))
true_edges.append((220.0, float(y)))
for x in range(80, 221):
true_edges.append((float(x), 60.0))
true_edges.append((float(x), 140.0))
def match_error(points, true_points, threshold=1.5):
"""计算每个检测点到最近真值点的距离误差"""
true_arr = np.array(true_points)
errors = []
for pt in points:
dists = np.sqrt((true_arr[:, 0] - pt[0])**2 + (true_arr[:, 1] - pt[1])**2)
min_dist = np.min(dists)
if min_dist < threshold:
errors.append(min_dist)
return errors
# Canny整数像素误差
canny_pts = [(float(x), float(y)) for y, x in np.nonzero(edges)]
canny_errors = match_error(canny_pts, true_edges)
# 空间矩亚像素误差
subpixel_errors = match_error(subpixel_pts, true_edges)
# ---------- 输出统计 ----------
print(f"Canny检测点数: {len(canny_pts)}")
print(f"空间矩亚像素点数: {len(subpixel_pts)}")
if canny_errors:
print(f"Canny平均误差: {np.mean(canny_errors):.3f} px")
if subpixel_errors:
print(f"空间矩平均误差: {np.mean(subpixel_errors):.3f} px")
运行与验证
依赖:numpy、opencv-python、scipy
运行方式:直接执行上述Python脚本。程序自动生成含噪矩形图像,分别提取Canny整数边缘和空间矩亚像素边缘,并计算与真实边缘的定位误差。
预期输出与判定标准
由于噪声随机性,每次运行数值略有波动,但统计趋势稳定。预期输出示例:
Canny检测点数: 约800~900
空间矩亚像素点数: 约800~900
Canny平均误差: 约0.8~1.2 px
空间矩平均误差: 约0.2~0.5 px
判定通过标准:空间矩亚像素边缘的平均定位误差显著小于Canny整数像素误差(通常降低50%以上),且检测点数与Canny基本一致,证明亚像素精度提升有效。
关键说明
本实现使用合成图像验证算法正确性,空间矩核系数基于理想阶跃边缘模型推导。实际应用中,若边缘灰度变化非理想阶跃(如渐变边缘),可先对图像做锐化预处理。代码中噪声标准差设为5,可根据需要调整以测试算法鲁棒性。误差匹配阈值1.5像素用于排除误检点对统计的干扰,确保对比公平。