3. PCB板焊点缺陷检测

任务说明

本案例针对PCB板焊点图像,通过图像分割与形态学操作,识别两类常见缺陷:桥接(相邻焊点间多余焊料连通)与缺焊(焊盘上无焊料或焊料严重不足)。输入为单张PCB焊点灰度图,输出为标注缺陷位置的图像及缺陷统计结果。

实现方案

采用OpenCV实现完整处理流水线:阈值分割提取焊料区域 → 连通域分析定位各焊点 → 形态学开运算分离桥接 → 面积与形状判据区分正常/桥接/缺焊。核心思路:正常焊点近似圆形且面积在合理区间;桥接焊点经开运算后面积显著减小;缺焊焊盘区域无焊料覆盖。

完整代码

import cv2
import numpy as np

def detect_solder_defects(img_path, min_area=80, max_area=800, circularity_thresh=0.6):
    """
    检测PCB焊点缺陷(桥接/缺焊)
    返回: 标注图, 缺陷统计字典
    """
    # 1. 读取与预处理
    img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
    if img is None:
        raise FileNotFoundError(f"无法读取图像: {img_path}")
    # 反色:焊料为亮色,背景为暗色
    _, binary = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # 2. 形态学去噪(去除孤立小噪点)
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
    binary_clean = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=1)

    # 3. 连通域分析(原始焊料区域)
    num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(binary_clean, 8)

    # 4. 焊盘位置估计:对原图做闭运算填充焊盘区域(用于缺焊判断)
    close_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15))
    pad_region = cv2.morphologyEx(binary_clean, cv2.MORPH_CLOSE, close_kernel, iterations=2)

    # 5. 形态学开运算(分离桥接焊点)
    open_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
    binary_opened = cv2.morphologyEx(binary_clean, cv2.MORPH_OPEN, open_kernel, iterations=2)
    num_labels_op, labels_op, stats_op, _ = cv2.connectedComponentsWithStats(binary_opened, 8)

    # 6. 结果标注
    result = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    defects = {"bridge": 0, "missing": 0, "normal": 0}

    # 6.1 遍历原始连通域,判断桥接与正常
    for i in range(1, num_labels):
        x, y, w, h, area = stats[i]
        if area < min_area:
            continue  # 忽略小噪点

        # 计算圆形度
        contour, _ = cv2.findContours((labels == i).astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        if not contour:
            continue
        perimeter = cv2.arcLength(contour[0], True)
        circularity = 4 * np.pi * area / (perimeter * perimeter) if perimeter > 0 else 0

        # 判断桥接:开运算后该区域分裂成多个连通域,或面积显著减小
        opened_area = np.sum(labels_op[labels == i] > 0)
        if opened_area < area * 0.7 or circularity < circularity_thresh:
            defects["bridge"] += 1
            cv2.rectangle(result, (x, y), (x + w, y + h), (0, 0, 255), 2)  # 红色框
            cv2.putText(result, "bridge", (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
        else:
            defects["normal"] += 1
            cv2.rectangle(result, (x, y), (x + w, y + h), (0, 255, 0), 2)  # 绿色框

    # 6.2 缺焊检测:焊盘区域(闭运算结果)中无焊料覆盖的部分
    pad_labels, pad_stats = np.unique(labels[pad_region > 0], return_counts=True)
    for i in range(1, num_labels):
        x, y, w, h, area = stats[i]
        if area < min_area:
            continue
        # 该连通域是否覆盖了对应焊盘的大部分
        pad_mask = (pad_region > 0) & (labels == i)
        if np.sum(pad_mask) > 0:
            pad_area = np.sum(pad_mask)
            if area / pad_area < 0.3:  # 焊料面积不足焊盘30%
                defects["missing"] += 1
                cv2.rectangle(result, (x, y), (x + w, y + h), (255, 0, 0), 2)  # 蓝色框
                cv2.putText(result, "missing", (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)

    return result, defects

if __name__ == "__main__":
    # 运行示例(需准备pcb_solder.png测试图)
    img_path = "pcb_solder.png"
    result_img, stats = detect_solder_defects(img_path)
    print(f"缺陷统计: {stats}")
    cv2.imwrite("defect_result.png", result_img)
    print("标注结果已保存至 defect_result.png")

运行与验证

依赖安装:

pip install opencv-python numpy

运行方式:将代码保存为 pcb_defect_detect.py,准备一张包含正常焊点、桥接和缺焊的PCB灰度图命名为 pcb_solder.png 置于同目录,执行:

python pcb_defect_detect.py

通过条件:

  1. 程序无报错退出,控制台输出缺陷统计字典;
  2. 生成的 defect_result.png 中:桥接缺陷被红色框标注、缺焊被蓝色框标注、正常焊点为绿色框;
  3. 对已知缺陷数量的测试图,统计结果与人工标注一致(允许±1个像素级误差)。

参数调优提示:若检测效果不佳,优先调整 min_area/max_area 适配焊点实际像素尺寸;桥接误判时增大开运算核尺寸(当前为5×5);缺焊漏检时增大闭运算核尺寸(当前为15×15)以更完整覆盖焊盘。

常见问题

  • 图像为彩色图:需先转灰度,可在读取后添加 img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY);
  • 焊料为暗色背景为亮色:需反转二值化结果,将 cv2.THRESH_BINARY 改为 cv2.THRESH_BINARY_INV;
  • 焊点大小差异大:建议按实际图像分辨率等比缩放 min_area 与 max_area,或改用自适应阈值分割。