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噪声大:可先对图像做高斯模糊(
cv2.GaussianBlur)预处理,但会略微损失细微信号; - Canny阈值选择:代码使用中值自动计算阈值,若缺陷对比度低可手动设置更低的
low值。