ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Python零件表面缺陷检测源码实战:从划痕识别到产线部署

Python零件表面缺陷检测源码实战:从划痕识别到产线部署 简介这份资源面向计算机、人工智能、自动化等专业的在校学生、教师及企业开发者提供一套基于Python实现的工具零件表面高精度缺陷检测源码可用于毕业设计、课程作业、项目立项演示或算法进阶学习。压缩包共8个文件约10KB以3个Python脚本为核心分别承担模型训练与测试推理任务另含requirements.txt依赖清单、setup.py与setup.cfg打包配置、MANIFEST.in资源声明及.gitignore版本忽略文件结构精简、开箱即用。项目代码均经实际运行验证功能完整适合小白快速上手也便于有基础的读者在此基础上修改扩展实现自定义检测功能。目前已有386人学习关注可作为缺陷检测方向的入门参考与二次开发起点。1. 从一张螺栓照片说起Python 缺陷检测源码到底能跑出什么产线上一个 M6 螺栓镀锌层上有一道 0.2 mm 的划痕人眼在日光灯下盯三秒才能勉强分辨而客户给的验收标准是「不允许」。这种场景下基于 Python 实现的各种工具零件表面高精度缺陷检测源码解决的正是把「人眼勉强分辨」变成「相机 算法稳定复现」的问题。它面向的是手里有工业相机、有零件图像、但缺一套能跑通检测流程的工程师——不是让你从零训一个 ResNet而是给你一套能改参数、能换零件、能接产线的骨架。热词里 python、缺陷检测、源码三个词恰好对应了这套东西的三层语言选型、任务定义、可复用资产。下面我按自己落地过的路径把这条路拆开讲清楚。2. 零件表面缺陷检测的任务边界与 Python 选型理由2.1 先分清三类缺陷别拿分类模型硬套工具零件表面的缺陷按形态大致分三类第一类是划痕、裂纹这种细长线状缺陷宽度可能只有几个像素第二类是凹坑、麻点这种局部灰度突变第三类是污渍、氧化色差这种大面积但对比度低的区域。很多新手一上来就找一个图像分类网络把「合格/不合格」当二分类做结果划痕在 224×224 缩图后直接消失模型学到的全是背景纹理。正确做法是线状缺陷走分割 形态学路线局部突变走局部对比度 阈值路线色差类走颜色空间变换 区域统计路线。源码包里如果同时提供了这几条分支说明作者是懂现场的如果只有一条分类分支你得自己补。2.2 为什么是 Python而不是 C 或 Halcon现场很多人会问产线要实时Python 不是慢吗我的经验是缺陷检测的瓶颈在成像和算法逻辑不在语言本身。一张 500 万像素的图OpenCV 的 C 底层做高斯滤波、Canny、形态学Python 只是调用层单帧耗时通常在 2060 ms够 1530 FPS 的产线用。真正拖慢的是你写了个 Python 循环逐像素遍历——那是写法问题不是语言问题。选 Python 的核心理由是调参快、可视化快、和上下游相机 SDK、PLC、MES胶水代码好写。Halcon 精度高但授权贵、移植死C 快但改一个阈值要重新编译。源码类项目用 Python本质是给你一个能快速试错的实验台。2.3 一套可复用的源码骨架应该长什么样不管源码包具体怎么组织一个能落地的零件缺陷检测项目目录结构我一般会整理成下面这样。你可以拿这个结构去对照手里的源码缺哪块补哪块。defect_detection/ ├── config/ │ └── params.yaml # 阈值、ROI、相机参数全放这里 ├── data/ │ ├── raw/ # 原始采集图按零件型号分文件夹 │ └── labeled/ # 标注后的掩膜图 ├── src/ │ ├── capture.py # 相机取流、触发、存图 │ ├── preprocess.py # 去噪、光照校正、ROI 裁剪 │ ├── detect_scratch.py # 线状缺陷分支 │ ├── detect_pit.py # 局部突变分支 │ ├── detect_stain.py # 色差分支 │ └── judge.py # 综合判定 输出结果 ├── models/ # 如果用了深度学习权重放这里 └── main.py # 主流程入口这个结构的价值在于参数和代码分离。现场换一个零件型号你只改params.yaml里的 ROI 和阈值不用动 Python 文件。我见过太多项目把阈值硬编码在函数里换型时改到崩溃。源码如果没做这层分离第一件事就是把它拆出来。3. 用 OpenCV 把线状划痕检测跑通的最小闭环3.1 光照校正别让光源背锅划痕检测翻车十次有六次是光照不均。同一张图左边亮右边暗全局阈值一卡暗区全是「缺陷」。所以第一步不是检测是平场校正。常见做法是拍一张无缺陷的标准件图作为参考用当前图除以参考图把光照的乘性影响消掉。import cv2 import numpy as np def flat_field_correction(img, ref): img: 当前待检图, BGR 或灰度 ref: 无缺陷标准件图, 与 img 同尺寸同通道 返回校正后的图, 光照不均被压制 img_f img.astype(np.float32) ref_f ref.astype(np.float32) # 加 1 防止除零, 参考图均值归一化到 1 附近 ref_norm ref_f / (ref_f.mean() 1e-6) corrected img_f / (ref_norm 1e-6) # 截断回 0-255 corrected np.clip(corrected, 0, 255).astype(np.uint8) return corrected逻辑说明ref_norm把参考图变成一张「光照分布图」均值归一到 1这样除法只消除光照的乘性分量不改变缺陷的相对对比度。参数上1e-6是防除零的保险别省。注意参考图必须和待检图同一光源、同一曝光、同一工位拍换光源就得重拍参考图这是血泪经验。3.2 线状缺陷增强黑帽变换比 Canny 更稳划痕是暗的细线在亮背景上。直接 Canny 会把零件边缘、纹理全提出来噪声爆炸。我一般用黑帽变换闭运算减原图它专门提取「比周围暗的细结构」。def enhance_scratch(gray, ksize15): gray: 灰度图 ksize: 结构元素尺寸, 要略大于划痕宽度 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (ksize, ksize)) # 闭运算: 先膨胀后腐蚀, 填掉暗的细线 closed cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel) # 闭运算结果 - 原图 被填掉的暗细节 blackhat cv2.subtract(closed, gray) return blackhat参数说明ksize是这套方法里最关键的参数。它必须大于划痕宽度、小于零件正常纹理间距。比如划痕宽 3 像素纹理间距 20 像素ksize取 15 左右。取小了划痕提不出来取大了把正常凹槽也当缺陷。这个值我一般用一组已知合格/不合格样本扫一遍看哪个值下两类样本的黑帽响应分得最开。3.3 阈值分割与形态学过滤把「疑似」变成「确认」黑帽图出来后用自适应阈值或 Otsu 二值化再按面积、长宽比过滤掉噪点。def segment_and_filter(blackhat, min_area30, max_area5000, min_ratio3.0): blackhat: 黑帽增强图 min_area/max_area: 缺陷面积范围(像素) min_ratio: 最小长宽比, 划痕是细长的 _, binary cv2.threshold(blackhat, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) # 开运算去掉孤立噪点 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) binary cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) defects [] for cnt in contours: area cv2.contourArea(cnt) if area min_area or area max_area: continue rect cv2.minAreaRect(cnt) w, h rect[1] if min(w, h) 1: continue ratio max(w, h) / min(w, h) if ratio min_ratio: continue defects.append(cnt) return defects逻辑说明Otsu 自动找阈值适合缺陷和背景对比度稳定的场景min_area滤掉灰尘噪点max_area滤掉整块阴影min_ratio是划痕的形态学特征——细长。这三个参数没有万能值必须拿你现场的图统计。我一般会跑一批已知结果画面积和长宽比的散点图找合格/不合格的分界线。4. 凹坑、麻点与色差缺陷的分支处理4.1 局部对比度法抓凹坑凹坑和划痕不同它是局部区域的灰度整体偏低形状接近圆形或椭圆。黑帽变换对细线敏感对块状凹坑响应弱。这时候用局部均值对比更直接算一张大核高斯模糊图作为「背景」原图减背景凹坑会变成明显的负值区域。def detect_pit(gray, blur_ksize51, thresh-15): gray: 灰度图 blur_ksize: 背景估计核, 要远大于凹坑尺寸 thresh: 差值阈值, 负值表示比背景暗 background cv2.GaussianBlur(gray, (blur_ksize, blur_ksize), 0) diff gray.astype(np.int16) - background.astype(np.int16) # 只保留比背景暗且超过阈值的区域 mask (diff thresh).astype(np.uint8) * 255 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) return mask参数说明blur_ksize决定「背景」的尺度必须比凹坑大 35 倍否则凹坑会被算进背景里差值消失。thresh取负值绝对值越大越严格。这个方法的坑在于零件边缘处背景估计会失真所以必须先做 ROI 裁剪把检测区限制在零件表面内部离边缘留 1020 像素。4.2 颜色空间变换处理氧化和污渍色差类缺陷在 RGB 里对比度低换到 HSV 或 Lab 空间往往一下就出来了。比如镀锌层的轻微发黄在 RGB 里 R 和 G 差几个灰度级但在 HSV 的 H 通道上差异被放大。def detect_stain(bgr, h_tol8, s_min40): bgr: 原始彩色图 h_tol: 色调偏离标准的容差 s_min: 最小饱和度, 滤掉灰色区域 hsv cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV) h, s, v cv2.split(hsv) # 假设标准件色调在 h_ref 附近, 这里用中位数估计 h_ref int(np.median(h)) # 色调偏离超过容差 且 饱和度足够 mask ((np.abs(h.astype(np.int16) - h_ref) h_tol) (s s_min)).astype(np.uint8) * 255 return mask逻辑说明h_ref用中位数估计标准色调是因为大部分区域是合格的中位数稳健。h_tol和s_min要按你的零件和光源调。注意 HSV 的 H 通道是环形的0 和 179 相邻如果标准色调在红色附近直接做差会出错需要先做环形距离计算。这是很多人第一次写会翻车的地方。4.3 多分支结果融合与判定逻辑三条分支各自输出掩膜后不能简单相加否则一个缺陷被多个分支重复检出面积统计就乱了。我一般用优先级 去重先跑划痕分支检出的区域从后续分支的输入里屏蔽掉再跑凹坑最后跑色差。判定时按客户标准来比如「划痕长度 2 mm 判废凹坑面积 0.5 mm² 判废色差面积占比 3% 判废」。def judge(defects, calib_mm_per_px, rules): defects: 各分支检出的轮廓列表, 带类型标签 calib_mm_per_px: 标定系数, 像素转毫米 rules: 判定规则字典 results [] for dtype, cnts in defects.items(): for cnt in cnts: area_px cv2.contourArea(cnt) area_mm2 area_px * (calib_mm_per_px ** 2) rect cv2.minAreaRect(cnt) length_mm max(rect[1]) * calib_mm_per_px ok True if dtype scratch and length_mm rules[scratch_max_len]: ok False if dtype pit and area_mm2 rules[pit_max_area]: ok False results.append({type: dtype, area_mm2: area_mm2, length_mm: length_mm, pass: ok}) return results参数说明calib_mm_per_px必须用标定板实测不能拍脑袋。我见过有人用镜头焦距和物距估算误差 20% 以上判定边界全乱。标定一次十分钟别省。5. 避坑与排查零件缺陷检测现场最容易翻车的五件事5.1 现象同一零件两次拍照结果不一致原因相机触发和光源不同步或者曝光时间设成了自动。产线震动导致零件位置微移ROI 跟着偏缺陷就时有时无。解决相机切手动曝光、手动增益光源用频闪同步触发零件用机械定位或视觉定位先校正 ROI。源码里如果有capture.py检查它是不是每帧都重新算 ROI——应该只在换型时算一次之后固定。5.2 现象阈值调好了换一批零件全误报原因零件批次间表面粗糙度、镀层颜色有差异或者光源老化导致亮度漂移。固定阈值扛不住这种漂移。解决把绝对阈值改成相对阈值比如用当前图的背景均值做基准阈值 背景均值 × 系数。或者每班次开机时用标准件做一次自校准更新参考图和基准值。源码里params.yaml如果只有固定数字加一个auto_calib: true开关。5.3 现象检测速度跟不上产线节拍原因Python 层做了逐像素循环或者对全图做了高分辨率的高斯模糊。500 万像素图做 ksize51 的高斯单帧就要上百毫秒。解决先裁 ROI 再处理ROI 外的像素不参与计算大核模糊用cv2.resize降采样后再模糊再升回来速度能快 510 倍多分支能并行的用多进程。实测 500 万像素裁到 100 万 ROI三条分支总耗时能压到 40 ms 以内。5.4 现象细小划痕漏检调低阈值后噪点爆炸原因划痕对比度接近噪声水平单靠阈值分不开。这是成像问题不是算法问题。解决先改善成像——加低角度暗场光源划痕会散射出亮线对比度提升一个数量级或者换更高分辨率的镜头让划痕占更多像素。算法上可以用多帧平均降噪但产线动件不适合。我的经验是成像能解决的别硬用算法补补出来的都是玄学。5.5 现象源码跑通了但接不上产线 PLC原因源码只做了单张图检测没有触发、没有结果输出协议、没有异常处理。解决补三块——相机触发接口硬触发或软触发、结果输出IO 信号或 TCP/Modbus 给 PLC、异常兜底相机掉线、图像超时、检测异常时输出「待人工复检」而不是「合格」。最后这条最重要检测系统最怕把不合格判成合格放过去宁可误报不可漏报。6. 把源码变成产线资产的三个进阶习惯第一建立缺陷样本库。每检出一个缺陷自动存图 存参数 存判定结果按型号和缺陷类型归档。三个月后你就有几百张真实样本这时候再回头看阈值合不合理、要不要上深度学习才有依据。我一般会在main.py里加一个save_defect开关只存判定为不合格的图一天也就几十张不占空间。第二参数版本化。params.yaml每次改动都 git commitcommit message 写清楚「换了什么零件、调了哪个参数、效果如何」。现场调参最怕「昨天还好好的今天怎么不行了」有版本记录就能回溯。这个习惯看起来笨但救过我两次。第三定期用标准件验证。每天开机跑一遍标准件一个已知合格、一个已知带缺陷确认系统没漂。标准件要妥善保管别磕碰。这一步花两分钟能避免整班次的误判。下面这张表是我对不同缺陷类型推荐的处理路线和关键参数可以直接拿去对照手里的源码缺陷类型推荐路线关键参数典型耗时(100万像素)划痕/裂纹黑帽变换 形态学过滤ksize, min_ratio1525 ms凹坑/麻点局部均值对比 阈值blur_ksize, thresh1020 ms氧化/污渍HSV/Lab 变换 区域统计h_tol, s_min815 ms综合判定优先级去重 标定换算calib_mm_per_px 5 ms最后说个我自己的教训早期做螺栓检测为了赶进度把阈值硬编码在函数里结果客户换了三种规格的螺栓我改了三天代码。后来所有项目第一件事就是把参数抽出来哪怕只有五个参数。这个习惯比任何算法技巧都值钱。希望帮到你。本文还有配套的精品资源点击获取
返回列表