简介基于Python-OpenCV开发的铭牌印刷缺陷视觉检测系统源码面向机器视觉入门者、工业质检与自动化相关开发者用于解决文字模糊、色彩不均、印刷错位、表面划痕等常见缺陷的自动识别问题。源码共22个文件压缩包约36.28MB以8个Python脚本为核心覆盖图像增强、霍夫变换、轮廓检测、仿射变换、KNN分类等模块并配有标准图、待测图以及jpeg/bmp/png多格式样本图片方便直接运行对比另附思路说明txt和项目说明MD帮助理解检测流程与算法思路。资源目前已有104人学习下载。借助这套源码读者可以快速搭建完整的缺陷检测流程掌握基于OpenCV的预处理、特征提取与分类判别方法并可扩展至其他印刷品或零部件外观质检任务。代码中还涉及缺陷信息的记录与结果可视化思路兼具较高的学习与工程复用价值。1. 铭牌印刷缺陷检测用 OpenCV 搭一套能上线的视觉方案做视觉检测的常见需求铭牌上的型号、序列号、LOGO一旦缺笔画、脏污、划痕到了客户手里就是批量退货。人工目检在强光下盯一天漏检率并不低而且人的标准容易漂移。用python-opencv写了一套传统视觉检测系统不依赖深度学习硬件一台普通工控机就能跑。它能识别缺墨、飞墨、脏污、划痕、套印偏移这几类常见的铭牌印刷缺陷并且把缺陷图自动存盘。这份源码适合正在做产线视觉方案、或者想用OpenCV入门缺陷检测的工程师整个算法链路是完整的改改参数就能接到自己的工位上不需要从零开始造轮子。2. 系统设计与检测流水线从相机到缺陷分类的完整流程铭牌检测系统看起来简单真正跑起来要兼顾稳定性和速度。这一章先说清楚系统建立在什么硬件基础上然后给一条可执行的主流程。源码不是把所有逻辑堆在一个文件里而是分成采集、定位、检测、输出四个模块这样现场调试时能单独替换某一段。2.1 硬件假设与图像输入约定系统默认用USB相机或者GigE相机分辨率按1280x720设计光源用环形LED或同轴光。铭牌是金属材质反光强所以要尽量固定曝光和增益不要开自动曝光否则亮度一会高一会低算法阈值会很纠结。源码里输入统一按BGR三通道处理如果你拿到的是灰度图也要先转成三通道再进流程。这样做的好处是后面所有模块不需要关心相机型号只要保证图像尺寸和通道一致。2.2 检测流水线各模块的职责划分整个流程可以分成五个环节图像采集、预处理、定位校正、缺陷检测、结果输出。采集模块负责读帧和丢弃模糊帧预处理负责去噪和光照归一化定位校正负责找到铭牌四角并做透视变换缺陷检测负责差影、连通域分析结果输出负责画框、存盘、统计日志。每个环节的失败处理也不一样比如定位不到铭牌时宁可把原始图存下来人工复核也不要继续往下走否则会把背景全当成缺陷。主循环框架代码如下cap cv2.VideoCapture(0) while True: ok, frame cap.read() if not ok: continue # 预处理去噪、增强 gray preprocess(frame) # 定位铭牌区域得到透视变换矩阵 rect locate_plate(gray) if rect is None: save_for_manual_check(frame) continue # 透视校正得到标准正视图 plate perspective_correct(frame, rect) # 缺陷检测返回缺陷列表和标注图 defects, marked detect_defects(plate) # 输出显示、保存缺陷样本、统计 handle_result(frame, defects, marked)逻辑说明cap用OpenCV的VideoCapture读取如果读取失败直接跳过preprocess拿到灰度图locate_plate返回铭牌四个角点找不到就人工复核perspective_correct把铭牌抠成标准矩形detect_defects返回缺陷轮廓和标注图handle_result根据是否有缺陷决定保存方式。这套流程不涉及深度学习每个函数都能单独跑单元测试。参数说明VideoCapture(0)是默认相机索引多相机时改成0、1、2preprocess的高斯核大小默认(5,5)这个值直接影响后面差分的噪声水平。如果图像分辨率更高比如1920x1080建议把预处理核相应放大。2.3 模板与检测区域的标定方法差影法需要一张标准模板图。源码里提供了一个calibrate.py运行后会弹出画面让你手动框选铭牌的四角和每个待检测的ROI最后生成一个json标定文件。运行时locate_plate先做边缘检测找四边形然后和json里的角点计算单应矩阵。如果现场工位固定也可以直接跳过定位用json里的固定裁剪框但震动稍微大一点就会裁偏所以我还是推荐保留定位步骤。标定文件里几个关键字段如下字段含义格式/单位corners铭牌四角像素坐标4x2 listregions待检测ROI列表list of [x, y, w, h]template_img模板图路径stringexposure建议曝光时间微秒字段说明corners是手动标定的四角locate_plate找到的角点要和它算单应矩阵所以顺序必须一致regions可以包含字符区、logo区、空白区不同区域使用不同的检测参数这一点后面会讲到exposure只是记在文件里提示现场用不会主动改相机参数。3. 核心实现预处理、字符分割与缺陷判据这一章深入到代码层面。铭牌的印刷缺陷主要有缺墨、飞墨、脏污、划痕、套印偏移。把这些缺陷统一成当前图和标准模板的差异就可以用差影法检测。难点在于金属反光导致差异图里全是噪点所以预处理和判据要下功夫。3.1 图像预处理光照矫正与双线性滤波金属铭牌反光严重同一字符在不同角度亮度差很大。如果直接对原图做差分光照梯度会完全淹没印刷缺陷。源码里先转灰度再做高斯滤波然后用一个关键的步骤用形态学闭运算估计背景亮度再用原图除以背景消除不均匀光照。def preprocess(gray): blur cv2.GaussianBlur(gray, (5,5), 0) # 用大核闭运算估计背景照度 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (31,31)) bg cv2.morphologyEx(blur, cv2.MORPH_CLOSE, kernel) # 背景归一化原图与背景之比放大到0-255 normalized cv2.divide(blur, bg, scale255) return normalized逻辑说明闭运算是先膨胀后腐蚀能在局部区域内取到最亮值因为铭牌背景通常比笔画亮所以闭运算结果就是背景照度的估计。cv2.divide做逐像素除法把亮度归一化到0-255这样图像上半边的亮区和下半边的暗区会拉到一个水平线上。这是传统预处理里对抗反光比较有效的一招。参数说明高斯核(5,5)去掉随机噪点核太大会把笔画边缘磨平导致小缺陷漏检。背景估计的椭圆核(31,31)是经验值它必须大于单个字符的笔画宽度比如笔画宽度是10像素那这个核至少要到21但又不能太大否则会吞掉大面积的局部阴影。现场需要针对自己的铭牌做一组核尺寸的实验可以用调试模式看归一化效果。3.2 定位铭牌区域与透视校正定位的目标是找到铭牌四角然后透视校正成标准正视图。源码里用Canny边缘检测加轮廓筛选来定位。def locate_plate(gray): edges cv2.Canny(gray, 50, 150) contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours sorted(contours, keycv2.contourArea, reverseTrue)[:10] for cnt in contours: peri cv2.arcLength(cnt, True) approx cv2.approxPolyDP(cnt, 0.02 * peri, True) if len(approx) 4: return approx.reshape(4,2).astype(np.float32) return None逻辑说明先用Canny得边缘再抽外部轮廓按面积排序取前10个因为铭牌在画面中通常是面积最大的四边形。approxPolyDP把轮廓近似成多边形如果拟合出来正好4个点就认为是铭牌角点。得到角点后还需要和模板角点算单应矩阵这一步在perspective_correct里做。参数说明Canny阈值50和150是低阈值和高阈值低阈值以下不算边缘高阈值以上的算强边缘。如果铭牌周围有复杂的背景纹理需要提高低阈值如果铭牌本身边缘太浅要降低。0.02是approxPolyDP的近似精度值越大多边形角点越少但可能丢掉真实角点值太小边缘上的锯齿会被当成角点所以0.02是一个平衡点。3.3 缺陷检测差分、连通域与阈值判据拿到透视校正后的标准图下一步就是和模板图差分。源码里detect_defects函数是核心它输出缺陷轮廓列表和标注图。def detect_defects(plate): # plate是透视校正后的灰度图 diff cv2.absdiff(plate, template_plate) _, thresh cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY) kernel cv2.getStructuringElement(cv2.MORPH_RECT, (3,3)) thresh cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel) contours, _ cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) defects [] for cnt in contours: area cv2.contourArea(cnt) if area min_area and area max_area: defects.append(cnt) # 标注结果 marked plate.copy() cv2.drawContours(marked, defects, -1, (0,0,255), 2) return defects, marked逻辑说明absdiff是逐像素取亮度差阈值30把差异大于30的像素置为白色其余为黑。白点里既有缺陷也有噪点开运算用3x3矩形核去掉孤立噪点。然后对白色区域做连通域分析计算每个轮廓面积面积在[min_area, max_area]之间的才认为是真缺陷。面积小于min_area的是噪点大于max_area的一般是大面积脏污或者反光带需要单独处理。参数说明阈值30是相对值它对光照归一化后的图像比较稳定但如果换了一组铭牌需要重跑离线统计。min_area和max_area是缺陷判据的核心参数建议按ROI分组设置。比如字符区域的小划痕面积可能只有15像素而logo区域的墨迹面积会到2000像素一个全局面积范围很难同时覆盖所以源码里允许每个ROI单独配参数。3.4 输出结果与保存缺陷样本检测结果不能只是屏幕上画个框还要能回溯。源码里每个被判定为NG的工位都会把原图、标注图、缺陷裁剪图按时间戳存下来同时把缺陷数量、总面积追加到csv日志里。def handle_result(frame, defects, marked): if defects: ts time.strftime(%Y%m%d_%H%M%S) cv2.imwrite(flogs/{ts}_marked.jpg, marked) for i, cnt in enumerate(defects): x,y,w,h cv2.boundingRect(cnt) crop marked[y:yh, x:xw] cv2.imwrite(flogs/{ts}_defect_{i}.jpg, crop) with open(logs/defects.csv, a) as f: f.write(f{ts},{len(defects)},{sum(cv2.contourArea(c) for c in defects)}\n)逻辑说明时间戳精确到秒但同一秒可能有多个工件所以更稳妥的做法是加一个自增序号源码里用的是时间戳加雪花ID这里简化为时间戳。crop保存的是标注后的彩色图方便直接看缺陷形状。csv日志能统计每天的缺陷率还能按时间追溯产线哪一批料出了问题。注意保存路径要提前建好否则imwrite会失败。4. 参数调优与性能优化让检测速度从 500ms 降到 80ms4.1 关键参数表与推荐起始值源码默认给了一组在1280x720下能跑通的参数。我把它们整理成表现场改参数时可以对照着来。参数推荐起始值影响调参方向GaussianBlur kernel(5,5)去噪太大会丢边缘噪点多时稍加大背景估计核(31,31) 椭圆越大越忽略局部细节反光强时加大Canny阈值50/150太低引入纹理太高漏边背景复杂时调高差分阈值30太低误检太高漏检缺陷浅时降低min_area20小于该面积的忽略污点噪声多时调高max_area5000大片脏污不判缺陷灰度污染严重时调低说明这些值是起点不要当金科玉律。我一般会把现场采集到的正常件和NG件各留50张离线调好参数再上在线。特别是min_area很多新手喜欢设得很小结果把相机噪点都当缺陷一天要停十次线。4.2 加速技巧ROI 裁剪、图像金字塔、多线程先说结论如果所有步骤都在全图上做1280x720的图能跑到500ms已经很不错。但要满足工厂节拍必须做三件事ROI裁剪、图像金字塔定位、预计算模板背景。ROI裁剪的意思是铭牌位置固定后我们只处理包含铭牌的那一小块区域而不是全图。图像金字塔用于定位阶段把图缩小一半在小图上找角点找到后把坐标放大两倍回到原图。预计算模板背景是指模板图的背景照度、差分阈值都提前算好在线不再重复计算。if fast_mode: small cv2.resize(gray, (0,0), fx0.5, fy0.5, interpolationcv2.INTER_AREA) rect locate_plate(small) if rect is not None: rect rect * 2 # 坐标放大回原图 plate perspective_correct(gray, rect) else: plate perspective_correct(gray, rect)逻辑说明fast_mode开关在配置文件里打开后先用半分辨率图定位定位成功再把角点坐标乘以2。注意透视校正还是要用原分辨率图做因为角点亚像素偏差放大后可能引入误差所以校正之前可以再做一次亚像素优化。这样做以后定位耗时基本可以忽略。参数说明缩小时用INTER_AREA插值适合缩小场景如果改成放大要用INTER_CUBIC。坐标放大倍数必须和缩小比例严格对应否则定位框会偏移。实际运行还可以把高分辨率图下采样到640x360定位效果差不多速度再快一截。多线程方面建议把采集放在单独线程主线程只处理最新一帧。用queue.Queue(1)队列满就丢弃旧帧保证检测模块拿到的永远是相机抓到的最新一帧而不是积压的旧数据。性能对比表方案耗时1280x720适用场景全图实时背景估计约500ms离线调试ROI预计算背景约150ms一般产线ROI图像金字塔定位约80ms高速产线5. 避坑指南铭牌反光、字符断裂、过检漏检的 5 条血泪经验5.1 反光导致的误检现象检测时金属铭牌表面产生斜向亮斑差分后出现一大块伪缺陷面积经常超过max_area。原因环形光源的入射角度不对或者铭牌表面有轻微弧面光线反射到相机里形成高光。单纯调高差分阈值会压掉反光但也会把浅色字符的细节一起压掉。解决先把光源换成同轴光或加偏振片从物理层面消除反光。如果硬件不好动把背景估计核从(31,31)加大到(63,63)让背景照度更平滑反光残留会被当成背景吸收。注意这一步要重新验证正常件的误检率不要只看NG件改善。5.2 字符断裂与阈值选择的矛盾现象印刷字符本身笔画浅差分后字符被拆成几段每段面积都很小低于min_area缺陷就漏了。原因min_area设得太大或者开运算核太大把细笔画抹掉了。解决不要用全局min_area按ROI单独设置。字符区域的min_area可以设到5像素脏污区域设到50像素。另外对差分图先做一次闭运算核用(5,5)能把断裂的笔画重新连成一条完整的轮廓再去算面积。但闭运算也会把相隔很近的噪声连起来所以只对字符ROI开这个操作其他ROI保持开运算。5.3 模板匹配对位置偏差过于敏感现象工位震动1-2个像素差分图边缘就出现一圈伪差异有时候整条边框都被判定为缺陷。原因角点定位精度不够或者透视校正后图像有细微偏移。OpenCV的findContours返回的是像素级坐标震动会导致角点抖动。解决先用cv2.cornerSubPix把角点优化到亚像素级。另外差分前做一个微小的配准用cv2.findTransformECC或者相位相关能压到0.3像素误差。但配准算法对初始位置敏感配准失败时要做一个相似度校验如果匹配度太低就跳过配准而直接用原始校正图避免引入更大的误检。5.4 相机抖动与模糊现象产线振动导致抓帧模糊差分图上全是细碎噪点误检率飙升。原因曝光时间太长或者相机支架刚性不够。模糊图没有清晰边缘Canny会输出大量随机轮廓。解决缩短曝光时间比如从30ms提到5ms同时把光源亮度调大相机支架换成减震结构。软件层面在采集模块里用Laplacian方差判断图像清晰度方差低于阈值就丢弃这一帧并记录一条模糊帧日志。阈值可以先用100如果很多好帧被丢就降到50。5.5 批量测试时的过拟合现象用自己收集的测试图验证准确率99%一上现场误检率立刻翻几倍。原因测试图库太干净只包含一种光照、一种相机角度没有覆盖现场反光、灰尘、不同的铭牌批次差异。解决把现场跑一周的图全部存下来包括正常件和NG件。然后离线把每个误检样本按原因分类是反光、是震动、还是参数分类错误。对每一类单独调对应ROI的参数。永远要用之前保留的回归样本集复测不要改完参数只瞄一眼测试集就上线。源码里的debug_mode会输出所有正常件的差分图方便快速看出哪些区域总是出现伪差异。6. 进阶用法把传统视觉升级成混合检测低代码接入产线6.1 用OCR验证印刷内容差影法能发现形状差异但不知道印出来的内容到底对不对。厂家要求序列号不能串号时常见做法是加OCR识别字符区域然后把结果和配置的期望字符串比对。源码外挂了一个ocr_check.py核心逻辑很直接import pytesseract def ocr_check(plate_roi, expected): text pytesseract.image_to_string(plate_roi, config--psm 7) confidence 0.0 if text.strip().upper() expected.upper(): confidence 1.0 else: import difflib confidence difflib.SequenceMatcher( None, text.strip().upper(), expected.upper()).ratio() return text, confidence说明--psm 7告诉tesseract把ROI当成单行文本适合序列号这类单行内容如果铭牌区域是多行需要按行切分后逐行调用。difflib的ratio返回0到1的相似度可以用来做模糊判断。注意pytesseract只是Python封装你还需要单独安装tesseract-ocr本体并且把训练好的中文字库放进去否则中文铭牌会识别成乱码。6.2 接PLC信号把检测结果变成电信号产线上下一个工位通常需要知道当前工件是OK还是NG源码里在plc_client.py里做了一个socket客户端每秒把状态发出去import socket def send_result(sock, ok): sock.send(b1 if ok else b0)常见做法是PLC做TCP Server工控机做Client连接之后PLC每收到一个字节就翻一次气缸挡板。这个字节没有协议头所以要在PLC侧做超时判断如果连续2秒没收到心跳就认为视觉系统掉线强制停车。源码里每2秒发送一个0x7F心跳PLC通过心跳状态判断链路健康这一点非常关键不然视觉程序崩了产线还在放行NG件。6.3 让误检样本回流简单半监督闭环系统上线之后维护才是重头。我在几个项目里吃过亏一开始调参调到满意就扔在那里结果跑了半个月误检率慢慢上来又得从零开始查。后来养成了一个习惯每过一段时间把现场新增的误检样本按当天日期放进样本库重新跑一次全量仿真看哪些ROI的阈值需要调整。这个操作不花太多时间但能避免系统慢慢退化。从那以后我每次上线视觉系统都会强制走一遍留一批图、跑一周、再调一次的流程。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?