3. 边缘检测算法对比与缺陷轮廓提取

任务目标

本案例使用OpenCV对一张包含划痕缺陷的金属表面图像,分别应用Sobel、Laplacian和Canny三种边缘检测算法,提取缺陷轮廓,并通过轮廓面积与人工标注真值对比,量化评估各算法的检测准确性。

环境依赖

Python 3.8+,安装OpenCV和NumPy:

pip install opencv-python numpy

实现代码

以下代码完整实现缺陷图像生成、三种边缘检测、轮廓提取与准确性评估:

import cv2
import numpy as np

# 1. 生成含划痕缺陷的模拟图像(真实场景可替换为实际采集图像)
img = np.full((200, 300), 200, dtype=np.uint8)
cv2.line(img, (50, 80), (180, 120), 30, 3)          # 划痕缺陷
cv2.circle(img, (220, 60), 15, 30, -1)              # 凹坑缺陷
cv2.rectangle(img, (100, 150), (160, 180), 30, 2)   # 边缘裂纹
# 添加噪声模拟真实环境
noise = np.random.normal(0, 8, img.shape).astype(np.int16)
img = np.clip(img.astype(np.int16) + noise, 0, 255).astype(np.uint8)

# 人工标注真值(缺陷区域掩码)
gt_mask = np.zeros_like(img, dtype=np.uint8)
cv2.line(gt_mask, (50, 80), (180, 120), 255, 5)
cv2.circle(gt_mask, (220, 60), 18, 255, -1)
cv2.rectangle(gt_mask, (100, 150), (160, 180), 255, 4)
gt_area = np.sum(gt_mask > 0)

# 2. 三种边缘检测算法
algorithms = {}

# Sobel(X+Y方向梯度幅值)
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobel = np.sqrt(sobel_x**2 + sobel_y**2)
algorithms['Sobel'] = cv2.normalize(sobel, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)

# Laplacian
laplacian = cv2.Laplacian(img, cv2.CV_64F, ksize=3)
algorithms['Laplacian'] = cv2.normalize(laplacian, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)

# Canny(自动计算高低阈值)
median = np.median(img)
low = int(max(0, 0.66 * median))
high = int(min(255, 1.33 * median))
algorithms['Canny'] = cv2.Canny(img, low, high)

# 3. 轮廓提取与面积计算
def extract_contour_area(edge_map):
    # 二值化并闭运算连接断裂轮廓
    _, binary = cv2.threshold(edge_map, 50, 255, cv2.THRESH_BINARY)
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
    closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
    contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    # 合并所有轮廓面积
    total_area = sum(cv2.contourArea(c) for c in contours)
    return total_area, closed

results = {}
for name, edge in algorithms.items():
    area, vis = extract_contour_area(edge)
    results[name] = area

# 4. 评估:面积误差率与IoU
print(f"{'算法':<10} {'检测面积':>8} {'面积误差率':>10} {'IoU':>8}")
print(f"{'真值':<10} {gt_area:>8} {'-':>10} {'-':>8}")

for name, area in results.items():
    err = abs(area - gt_area) / gt_area * 100
    # 计算IoU(使用闭运算后的掩码与真值比较)
    _, edge_binary = cv2.threshold(algorithms[name], 50, 255, cv2.THRESH_BINARY)
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
    pred_mask = cv2.morphologyEx(edge_binary, cv2.MORPH_CLOSE, kernel)
    pred_mask = cv2.dilate(pred_mask, kernel, iterations=2)  # 膨胀以覆盖边缘邻域
    intersection = np.logical_and(pred_mask > 0, gt_mask > 0).sum()
    union = np.logical_or(pred_mask > 0, gt_mask > 0).sum()
    iou = intersection / union if union > 0 else 0
    print(f"{name:<10} {area:>8.1f} {err:>9.1f}% {iou:>8.3f}")

# 5. 可视化对比(可选保存)
cv2.imwrite('sobel_result.jpg', algorithms['Sobel'])
cv2.imwrite('laplacian_result.jpg', algorithms['Laplacian'])
cv2.imwrite('canny_result.jpg', algorithms['Canny'])

运行与验证

直接运行脚本即可在控制台输出三种算法的面积误差率与IoU指标。判定标准:

  • Canny应获得最低的面积误差率(通常<15%)和最高的IoU(通常>0.6),因其具备非极大值抑制和双阈值连接,对噪声鲁棒且边缘连续;
  • Sobel对噪声敏感,检测面积偏大,误差率中等;
  • Laplacian对噪声最敏感,会产生大量伪边缘,面积误差率最高,IoU最低。

若实际运行结果中Canny的IoU低于0.4,可调整Canny阈值范围或增大闭运算核尺寸以改善轮廓连续性。生成的三张结果图可直观观察边缘检测质量差异。

三种边缘检测算法对缺陷轮廓的提取效果对比 真值轮廓 Sobel Laplacian Canny 原始含缺陷图像 Sobel边缘 Laplacian边缘 Canny边缘(最清晰) 准确性排序: Canny(IoU最高) > Sobel(中等) > Laplacian(最差,伪边缘多)

常见问题

  • 轮廓断裂:若检测出的轮廓不闭合导致面积偏小,可增大闭运算核尺寸或增加膨胀迭代次数;
  • Sobel/Laplacian噪声大:可先对图像做高斯模糊(cv2.GaussianBlur)预处理,但会略微损失细微信号;
  • Canny阈值选择:代码使用中值自动计算阈值,若缺陷对比度低可手动设置更低的low值。