Python OpenCV 孔洞检测与缺陷分析详解与实战

一、什么是孔洞检测

孔洞(Hole)是目标内部被背景包围的区域。在工业视觉中,孔洞通常代表产品结构特征或缺陷。

典型应用:

  • 通孔检测
  • 铸件气孔检测
  • 注塑件缺料检测
  • 密封圈破损检测
  • 锂电极片缺陷检测

二、孔洞检测原理

常见方法:

  1. 轮廓层级(Hierarchy)
  2. FloodFill 填充法
  3. 连通域分析
  4. 形态学填充
  5. 面积差法

推荐:

  • 已知外轮廓:Hierarchy
  • 二值图检测:FloodFill
  • 统计:ConnectedComponents

三、方法一:FloodFill孔洞检测

思路:

text 复制代码
二值图
 ↓
边界FloodFill
 ↓
反色
 ↓
与原图OR
 ↓
孔洞位置
python 复制代码
import cv2
import numpy as np


def detect_holes(image_path):
    # 1. 读取图像并转为灰度图
    img = cv2.imread(image_path)
    if img is None:
        print("错误:无法读取图像,请检查路径。")
        return
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 2. Otsu 自动二值化
    _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # 【关键】确保背景是黑色(0),目标前景是白色(255)
    # 如果图像左上角(0,0)是白色,说明前景背景反了,需要反转
    if binary[0, 0] == 255:
        binary = cv2.bitwise_not(binary)

    # ==========================================
    # 方法一:FloodFill (漫水填充法) 提取孔洞
    # ==========================================
    fill = binary.copy()
    # mask 的长宽必须比原图各多 2 个像素
    mask = np.zeros((fill.shape[0] + 2, fill.shape[1] + 2), np.uint8)

    # 从 (0,0) 开始填充外部背景为白色
    cv2.floodFill(fill, mask, (0, 0), 255)

    # 反色后与原图合并,得到"实心目标"
    holes_filled = cv2.bitwise_or(binary, cv2.bitwise_not(fill))

    # 用"实心目标"减去"原图",剩下的就是纯粹的孔洞
    real_holes_flood = cv2.subtract(holes_filled, binary)

    # ==========================================
    # 方法二:轮廓层级法 (Hierarchy) 提取孔洞 (工业推荐)
    # 优点:不受孔洞是否连通边缘的影响,更精准
    # ==========================================
    # RETR_CCOMP 会建立两层层级:外层轮廓和内层孔洞
    contours, hierarchy = cv2.findContours(binary, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)

    real_holes_hierarchy = np.zeros_like(binary)
    if hierarchy is not None:
        for i, h in enumerate(hierarchy[0]):
            # h[3] 代表父轮廓索引。如果不为 -1,说明它是被包围的内部孔洞
            if h[3] != -1:
                cv2.drawContours(real_holes_hierarchy, contours, i, 255, -1)

    # ==========================================
    # 3. 在原图上绘制检测结果 (以轮廓层级法为例)
    # ==========================================
    result_img = img.copy()
    defect_count = 0

    # 在孔洞掩膜上寻找轮廓,方便计算面积并过滤噪声
    hole_cnts, _ = cv2.findContours(real_holes_hierarchy, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    for cnt in hole_cnts:
        area = cv2.contourArea(cnt)
        # 过滤掉面积小于 20 像素的微小噪点
        if area < 20:
            continue

        defect_count += 1
        x, y, w, h = cv2.boundingRect(cnt)
        # 绘制红色矩形框
        cv2.rectangle(result_img, (x, y), (x + w, y + h), (0, 0, 255), 2)
        # 标注面积
        cv2.putText(result_img, f"Area:{area:.0f}", (x, y - 5),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)

    print(f"检测到的孔洞/缺陷数量: {defect_count}")

    # 4. 可视化展示
    cv2.imshow("1. Original Binary", binary)
    cv2.imshow("2. FloodFill Holes", real_holes_flood)
    cv2.imshow("3. Hierarchy Holes (Recommended)", real_holes_hierarchy)
    cv2.imshow("4. Final Detection Result", result_img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == "__main__":
    detect_holes("test.png")

优点:

  • 简单
  • 速度快
  • 不依赖轮廓

四、方法二:轮廓层级检测孔洞

python 复制代码
contours, hierarchy = cv2.findContours(
    binary,
    cv2.RETR_TREE,
    cv2.CHAIN_APPROX_SIMPLE
)

for i, h in enumerate(hierarchy[0]):
    parent = h[3]
    if parent != -1:
        cv2.drawContours(img, contours, i, (0, 0, 255), 2)

说明:

  • parent = -1:外轮廓
  • parent >= 0:孔洞

适合孔检测。


五、方法三:连通域分析

python 复制代码
num, labels, stats, centers = cv2.connectedComponentsWithStats(binary)

for i in range(1, num):
    area = stats[i, cv2.CC_STAT_AREA]
    if area < 100:
        continue

可用于:

  • 缺陷数量统计
  • Blob分析
  • 异物检测

六、方法四:形态学检测

利用闭运算填充孔洞。

python 复制代码
kernel = cv2.getStructuringElement(
    cv2.MORPH_ELLIPSE,
    (9, 9)
)

close = cv2.morphologyEx(
    binary,
    cv2.MORPH_CLOSE,
    kernel
)

holes = cv2.subtract(close, binary)

适合小孔检测。


七、缺陷分析

检测完成后可分析:

python 复制代码
area = cv2.contourArea(cnt)
length = cv2.arcLength(cnt, True)

x, y, w, h = cv2.boundingRect(cnt)

ratio = w / h

常用指标:

  • 面积
  • 长宽比
  • 圆度
  • 外接矩形
  • 数量

八、工业案例

  • 孔检测: 统计所有孔数量。
  • 铸件气孔: 过滤面积小于20像素噪声。
  • 密封圈: 检测内部是否断裂。
  • Connector: 检测定位孔是否存在。

九、完整案例

python 复制代码
import cv2
import numpy as np

img = cv2.imread("part.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

fill = binary.copy()
mask = np.zeros((fill.shape[0] + 2, fill.shape[1] + 2), np.uint8)
cv2.floodFill(fill, mask, (0, 0), 255)

holes = cv2.bitwise_not(fill)

cnts, _ = cv2.findContours(
    holes,
    cv2.RETR_EXTERNAL,
    cv2.CHAIN_APPROX_SIMPLE
)

for c in cnts:
    if cv2.contourArea(c) < 30:
        continue
    x, y, w, h = cv2.boundingRect(c)
    cv2.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 2)

cv2.imshow("result", img)
cv2.waitKey()

十、常见问题

Q:为什么检测不到孔洞?

  • 二值化错误
  • 前景背景反色
  • 孔洞与边界连通

Q:为什么孔洞很多?

  • 噪声未去除
  • 阈值不合理

建议: 增加开运算、面积过滤。


十一、总结

方法 优点 场景
FloodFill 简单快速 通用
Hierarchy 最准确 Connector
连通域 统计方便 空洞检测
形态学 小孔检测 缺陷检测

建议: 工业视觉中采用 二值化 + 形态学 + FloodFill/Hierarchy + 面积过滤 组合,可获得稳定的孔洞检测效果。

相关推荐
hsg772 小时前
简述:Rust、GeoRust、自主研发GIS平台
开发语言·后端·rust
承渊政道2 小时前
飞算Java炫技赛:Java×Unity数字孪生园区平台的从零落地记录
java·开发语言·unity·技术分享·vibe coding·飞算javaai·飞算java ai炫技赛
wdfk_prog8 小时前
嵌入式面试真题第 10 题:高优化等级下共享状态可见性、内存模型与系统级同步设计
java·linux·开发语言·面试·职场和发展·架构·c
闻道且行之10 小时前
TurboOCR:基于PP-OCRv6的极速Windows离线OCR工具,深度解析3.4GB依赖背后的技术架构
c++·人工智能·python·qt·机器学习·ocr
许彰午11 小时前
95_Python内存管理与垃圾回收
开发语言·python
多加点辣也没关系12 小时前
JavaScript|第13章:原始类型的方法
开发语言·javascript·ecmascript
এ慕ོ冬℘゜12 小时前
深入理解 JavaScript 事件体系:Window、鼠标与键盘事件详解
开发语言·javascript·okhttp
骄阳如火12 小时前
Python 性能深度剖析:从“被诟病的慢”到“Rust 重塑”的拐点
python
满怀冰雪12 小时前
03-第一个 Paddle 程序:Tensor 创建、计算与设备管理
人工智能·python·paddle