1. 基于OpenCV的金属表面划痕检测

任务目标

构建一个基于OpenCV的金属表面划痕检测程序。输入为金属表面灰度图像,输出为标记了划痕的彩色图像,并在终端打印每条划痕的位置(外接矩形中心)与长度(像素)。

实现思路

划痕在金属表面通常表现为低灰度值的细长暗线。采用以下处理链:高斯模糊降噪 → 顶帽变换增强暗细节 → 二值化 → 形态学闭运算连接断裂 → 连通域分析。通过连通域的外接矩形长宽比与面积筛选,排除点状噪声和块状污渍,保留细长划痕。

完整代码

import cv2
import numpy as np

def detect_scratches(image_path):
    # 读取图像
    img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
    if img is None:
        raise ValueError(f"无法读取图像: {image_path}")

    # 1. 高斯模糊降噪
    blurred = cv2.GaussianBlur(img, (5, 5), 0)

    # 2. 顶帽变换:原图 - 开运算,提取暗背景下的细小暗划痕
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15))
    tophat = cv2.morphologyEx(blurred, cv2.MORPH_TOPHAT, kernel)

    # 3. 自适应阈值二值化(暗区域为白色前景)
    _, binary = cv2.threshold(tophat, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # 4. 闭运算连接断裂的划痕段
    close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
    closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, close_kernel, iterations=2)

    # 5. 连通域分析
    num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(closed, connectivity=8)

    # 6. 筛选划痕:面积适中 + 长宽比大(细长)
    result = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    scratches = []
    min_area = 50
    max_area = 5000
    min_ratio = 3.0

    for i in range(1, num_labels):  # 跳过背景
        x, y, w, h, area = stats[i]
        if area < min_area or area > max_area:
            continue
        # 长宽比:保证划痕方向上的长度远大于宽度
        long_side = max(w, h)
        short_side = min(w, h)
        if short_side == 0:
            continue
        ratio = long_side / short_side
        if ratio < min_ratio:
            continue

        # 记录划痕信息
        cx, cy = centroids[i]
        length = long_side
        scratches.append({
            "center": (int(cx), int(cy)),
            "length": int(length),
            "bbox": (x, y, w, h)
        })

        # 在结果图上绘制
        cv2.rectangle(result, (x, y), (x + w, y + h), (0, 0, 255), 2)
        cv2.circle(result, (int(cx), int(cy)), 3, (0, 255, 0), -1)

    return result, scratches

if __name__ == "__main__":
    import sys
    if len(sys.argv) != 2:
        print("用法: python scratch_detect.py <图像路径>")
        sys.exit(1)

    output_img, scratch_list = detect_scratches(sys.argv[1])

    # 保存结果
    out_path = "scratches_result.jpg"
    cv2.imwrite(out_path, output_img)
    print(f"结果图像已保存: {out_path}")

    # 打印检测结果
    print(f"检测到 {len(scratch_list)} 条划痕:")
    for idx, s in enumerate(scratch_list, 1):
        print(f"  划痕{idx}: 位置={s['center']}, 长度={s['length']}px")

运行方式

pip install opencv-python numpy
python scratch_detect.py metal_surface.jpg

验证方法

准备一张包含明显划痕的金属表面灰度图(建议分辨率不低于300×300,划痕宽度2~10像素)。运行程序后:

  1. 检查生成的 scratches_result.jpg:红色矩形应框住划痕主体,绿色圆点标记划痕中心;
  2. 终端输出的划痕数量与目视结果一致,位置坐标落在划痕实际所在区域;
  3. 长度值(像素)与图像中划痕的像素跨度大致相符。

若检测效果不理想,可优先调整 min_ratio(划痕越细长值越大)和 min_area(过滤小噪点)。

常见问题

  • 检测不到划痕:划痕过浅时,降低顶帽变换的核尺寸(如改为11×11);或改用更小的 min_area。
  • 误检块状区域:提高 min_ratio 至5.0以上,或增大 min_area 过滤小污点。
  • 划痕断裂为多段:增大闭运算的迭代次数或核尺寸(如5×5)。