多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

OCR喷码缺陷检测实战:从原理到部署的全流程方案

OCR喷码缺陷检测实战:从原理到部署的全流程方案 简介这套OCR喷码缺陷检测实战资源面向工业自动化、质量控制与机器视觉入门人群系统解决喷码不清晰、缺失、错误等常见缺陷的自动识别问题。压缩包共207个文件约156.81MB包含55个jpg与26个png图像样本、41个csv训练日志、12个json配置、11个py脚本、11个pdparams模型权重及12个pdopt优化器等各类型文件用途明确可支撑完整项目复现。目前已有114人学习下载。资源同时提供原理流程教程与完整项目源码从图像采集、去噪增强、对比度提升到特征提取、模型训练与缺陷判断逐个环节拆解实现要点项目重点展示了OCR技术对不同字体、光照条件和背景干扰的适应性并通过算法优化减少误报漏报、提升检测效率。源码包含丰富注释便于阅读修改与二次开发能够帮助初学者快速掌握喷码缺陷检测的落地路径也适合工业质检工程师作为工程参考。1. OCR喷码缺陷检测在检什么不只读懂字符更要判断这行字合格不合格一条食品包装产线上喷码机每天要给几万个包装印生产日期和批号。读码器能读懂字符却经常把“打花了”“缺了一列”的喷码判成合格直到下游客户投诉才发现问题。这就是 OCR 喷码缺陷检测要解决的真正难点不仅要让机器“认字”还要让它判断这行字打得合不合格。标题里的项目本质上是一套从图像采集、喷码区域定位、OCR 识别到缺陷判别的完整流程源码包加原理流程教程解决的就是从业者从零搭这条流程的学习路径。适合刚接触工业视觉、想在真实产线场景里练手的人也适合已经在做视觉检测、想补齐喷码这一块方案的人。2. 先把方案立住喷码检测的三层任务与两步架构选型2.1 喷码检测到底在检什么内容、质量、姿态三类缺陷喷码是用喷墨方式印在瓶盖、药盒、食品袋上的点阵字符常见内容是生产日期、批号、有效期和追溯码。这类字符和印刷体最大的区别是它由一颗颗墨滴组成墨滴的均匀性、密度、落点位置都会波动。所以喷码缺陷检测不能简单理解成“ OCR 识别”它至少包含三层任务。第一层是内容识别也就是 OCR 本身读出来的字符串必须和预期一致比如批号不能打错、日期不能印反。第二层是字符质量这是喷码特有的喷头某个喷嘴堵了会导致字符整列缺失这叫断针墨滴飞溅到字符周围形成杂点这叫飞墨墨量过大导致笔画糊在一起这叫糊字。第三层是姿态与位置喷码有没有歪斜、有没有超出允许区域、字符间距是否异常。缺陷类型表现常见成因检测思路断针缺墨字符某列整列变浅或消失喷头喷嘴堵塞列投影统计、墨量占比飞墨字符周围出现零星墨点墨滴带静电偏移形态学开运算后统计杂点糊字笔画粘连、字符边界不清墨量过大、底材不吸墨二值化后笔画宽度统计字符模糊整体发虚、边缘不锐利喷头距离过远、速度不匹配梯度幅值、清晰度评价位置偏移字符区域偏离指定位置产线速度波动、喷码机触发延迟检测框中心坐标比对内容错误字符与预期不符喷码机数据配置错误OCR 文本与期望值比对这里有一个新手容易忽略的结论OCR 识别正确并不代表字符质量合格。神经网络对轻微缺列的字符往往还能正确读出来因为上下文信息足够它“猜”对但产线要的是墨迹本身完整。所以缺陷判定必须独立于 OCR 识别这也是整个项目架构设计的前提。2.2 传统视觉方案的边界为什么阈值分割在喷码场景挡不住很多刚接触喷码检测的人第一反应是用传统图像处理固定阈值二值化把墨迹从背景里抠出来再做连通域分析统计字符个数和位置最后和标准模板比对。这套方案在一两个固定样品上往往效果不错演示时也很能说服人但一上产线就翻车。原因有三条。第一光照不稳定。产线现场常有环境光变化、光源老化、包装膜反光喷码区域有时亮成一片白板固定阈值直接失效动态阈值比如 Otsu能缓解一部分但遇到低对比度图仍然会崩。第二点阵字符本身有随机性。同一台喷码机打出来的同一个字墨滴落点每次都有轻微差异模板匹配稍微严格一点就误杀稍微宽松一点就漏检。第三字符位置不固定。包装袋在传送带上不可能完全静止字符区域随速度波动而左右移动固定 ROI 经常切半个字后续识别和判定全部失真。传统方案不是完全没用它可以作为缺陷判别模块里的一个辅助工具比如用二值化和列投影去统计墨量、找断针列。但把它作为整个检测方案的主干遇到真实产线数据基本守不住。这一点在原理流程教程里通常会放在最前面讲目的就是让学习者先建立“要用学习型方案”的判断。2.3 深度学习检测加 OCR 识别的两步架构怎么选目前做喷码缺陷检测主流且最好落地的是两步架构第一步用目标检测模型定位喷码区域把整图里那一小块字符裁出来第二步把裁剪图送给 OCR 模型识别文本第三步在 OCR 结果之外加一个独立的缺陷判别模块用图像统计和规则判断字符质量。三步串成一条流水线。为什么不用一个端到端网络同时输出文本和缺陷端到端方案听起来更先进但实际维护成本高喷码缺陷类型多样有些需要像素级统计才能判断比如断针列的数量、墨量的占比这些信息很难直接从分类网络的特征里可靠地提取出来。两步架构的好处是每个环节可以单独调参、单独换模型、单独排错。OCR 识别不准就换 OCR 的字典和后处理缺陷漏检就调缺陷判别模块的阈值不需要动整个网络。目标检测部分一般用 YOLO 系的小模型就够了因为喷码区域在整图里特征明显不是小目标检测难题。OCR 部分用带 CTC 的轻量识别网络即可不需要上大规模语言模型喷码字符集通常只有数字、字母和少量符号字典很短。整个项目的复杂度其实不在模型选型而在数据准备和缺陷判别规则的设计上。3. 跑通最小流程从数据准备到训练检测与 OCR 识别模型3.1 数据准备与项目目录结构标注格式转换是第一个门槛拿到源码包之后先别急着训练第一件事是把数据整理成框架需要的格式。常见做法是检测部分用 YOLO 格式的 txt 标注每行一个目标内容是“类别ID 中心点x 中心点y 宽 高”坐标都归一化到 0 到 1OCR 部分需要单独维护一个“裁剪图 文本标签”的配对关系。dataset/ images/train/ # 原始产线图像 images/val/ labels/train/ # YOLO 格式检测标注 labels/val/ crops/train/ # 从原图裁剪出的喷码区域供 OCR 训练 crops/val/ char_dict.txt # 字符集字典每行一个字符 train_list.txt # OCR 训练列表图片路径 标签文本如果手里的标注是 VOC 格式的 XML需要先转换成 YOLO 格式。转换脚本不复杂但坐标归一化容易写错尤其是除以宽高时用成整数除法。下面这个函数是常用的转换写法import os import xml.etree.ElementTree as ET def voc2yolo(xml_path, out_dir, class_id0): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name ! spray_code: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) x_center (x1 x2) / 2 / img_w y_center (y1 y2) / 2 / img_h box_w (x2 - x1) / img_w box_h (y2 - y1) / img_h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) base os.path.splitext(os.path.basename(xml_path))[0] out_path os.path.join(out_dir, base .txt) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines))这个脚本里的关键参数是class_id。喷码检测通常只有一个类别写 0 就行如果后续要同时检测多个区域比如喷码区域和印刷区域按类别顺序递增。坐标归一化时全部除以图像宽高x 和 y 不要搞混这是最常见的低级错误。转换完成后用脚本随机抽几张图把标注框画回去看一眼确认框的位置和字符区域吻合再进入训练环节。3.2 训练喷码区域检测模型定位是 OCR 和缺陷判定的地基检测模型的训练命令在各个 YOLO 系框架里大同小异。以常见的 YOLOv5 为例配置好数据文件后训练命令如下python train.py \ --data spray.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0对应的spray.yaml内容很简单train: dataset/images/train val: dataset/images/val nc: 1 names: [spray_code]这里几个参数值得展开。--img 640是训练输入尺寸喷码区域在一张 1280x1024 的产线图里通常占宽度的一半以下640 的输入尺寸足够让检测模型看清字符区域如果喷码在整图里占比很小可以提高到 960但训练速度会明显下降。--batch 16受显存约束在 8GB 显存的卡上 16 可能偏大报 OOM 就降到 8。--epochs 100对于单类别检测任务偏多通常跑 60 到 80 轮就能收敛多跑也不会差只是浪费时间。检测模型训练好之后输出是喷码区域的外接矩形框。保存 best.pt 权重后面 OCR 训练要用它来批量裁剪样本。裁剪时注意一个细节检测框要比实际字符区域稍微外扩几个像素因为 OCR 识别时字符不能贴边太紧了会影响识别精度。一般外扩 5 到 10 个像素就够。3.3 训练喷码 OCR 识别模型点阵字符转文本OCR 模型训练有两种常见做法。一种是直接在自己裁剪好的喷码图上微调一个开源 OCR 框架另一种是用源码包自带的 OCR 训练脚本。以常见的 PaddleOCR 系框架为例数据准备好后训练命令通常是python tools/train.py \ -c configs/rec/rec_mv3_none_bilstm_ctc.yml \ -o Global.dataset.train.label_file_listtrain_list.txt \ Global.dataset.val.label_file_listval_list.txt \ Global.epoch_num50 \ Global.save_model_diroutput/ocr_spray如果源码包里不是这套框架把命令换成你下载的源码包里的训练入口即可核心流程不变。OCR 训练前必须确认两件事字典文件和训练列表格式。字典文件char_dict.txt每行一个字符。喷码字符集一般包含数字 0-9、大写字母去掉易混淆的 I 和 O、以及横线、冒号、斜杠等符号加起来通常不超过 40 个字符。字典越小模型越容易收敛。训练列表train_list.txt每行是“图片路径 制表符 标签文本”dataset/crops/train/0001.jpg\t20240516 dataset/crops/train/0002.jpg\tA1B2C3OCR 模型的输入尺寸一般把高度固定为 32 或 48宽度按比例缩放但限制最大宽度超过的做 padding。喷码字符数量通常很少生产日期加批号一般在 10 到 20 个字符之间所以序列长度不用设很大。这里的核心参数是“序列长度”和“字典大小”它们决定了模型输出的维度。序列长度设太长会引入多余 blank 字符影响 CTC 解码设太短会把长文本截断。按项目字符量16 到 24 是合理区间。3.4 缺陷判别与结果输出OCR 之外的独立关卡整个项目的关键在最后这一步。OCR 模型输出文本和置信度但不能直接拿文本判合格需要单独写一个缺陷判别函数。下面是一个常见的判别逻辑示例import cv2 import numpy as np def judge_defect(crop_img, ocr_text, ocr_conf, expect_textNone, conf_thr0.85, ink_min0.03, ink_max0.35): crop_img: 检测模型裁剪出的喷码区域 BGR 图 ocr_text: OCR 识别出的文本 ocr_conf: OCR 文本置信度 expect_text: 期望文本可为空 reasons [] # 1. OCR 置信度检查 if ocr_conf conf_thr: reasons.append(OCR置信度不足) # 2. 内容比对 if expect_text and ocr_text ! expect_text: reasons.append(内容与期望不符) # 3. 墨量占比统计 gray cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY) _, bw cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) ink_ratio cv2.countNonZero(bw) / (bw.shape[0] * bw.shape[1]) if ink_ratio ink_min: reasons.append(墨量过低,疑似缺墨) elif ink_ratio ink_max: reasons.append(墨量过高,疑似糊字) # 4. 列投影统计断针 col_proj np.sum(bw 0, axis0) zero_cols np.sum(col_proj 1) if zero_cols 3: reasons.append(f检测到{zero_cols}列连通断裂) return reasons if reasons else [OK]这个函数里的参数值得逐个解释。conf_thr是 OCR 置信度阈值产线上建议设 0.85 以上宁可多复核也不要让低置信度结果直接通过。ink_min和ink_max是墨量占比的上下限这两个值的确定方法是先统计一批正常样本的墨量占比取均值的上下浮动区间不要拍脑袋定。zero_cols统计的是二值图里墨迹为空的列数断针的本质就是某一列完全没有墨滴这个统计比 OCR 识别更可靠。做完这四个判断把结果写进 CSV 或数据库输出 NG 图片到指定目录供产线人员复看。整个流水线就是读图 → 检测模型定位 → 裁剪 → OCR 识别 → 缺陷判别 → 输出结果。4. 参数和阈值怎么定影响喷码检测效果的几个关键旋钮4.1 检测模型的三个关键参数输入尺寸、置信度阈值、NMS 阈值检测模型训练完只是第一步推理时的参数设置直接影响产线表现。输入尺寸和训练时保持一致是基本要求但实际部署时可以试不同尺寸看精度和速度的平衡点。--img 640训练出来的模型部署时用 640 推理最稳如果产线要求更快降到 480 也能跑但喷码太小时检测框可能抖动。置信度阈值conf_thres是检测模型最重要的旋钮。训练时默认的 0.25 只适合验证集调优产线上建议设到 0.5 以上否则背景里的印刷文字、反光斑会被误检成喷码区域。反过来如果现场喷码本身对比度低0.5 会漏检这时候要先解决图像质量而不是一味降阈值。NMS 的 IoU 阈值一般保持默认的 0.45喷码区域是单目标这个参数影响很小不需要特别调。4.2 OCR 模型的度量衡字典、序列长度和置信度分布OCR 模型的调参重点不在学习率而在字典和序列长度。前面说过字符集通常 30 到 40 个字符字典文件里多一个不需要的字符模型就要多学一个分类头收敛变慢。序列长度设 16 还是 24取决于最长样本的字符数。一个实用技巧是训练完成后统计验证集上所有样本的预测序列长度分布如果大多集中在最大值附近说明序列长度设短了如果大多数远小于最大值可以适当缩短减少 CTC 的 blank 干扰。OCR 输出的置信度不是均匀分布的。正常样本的置信度通常在 0.99 以上缺陷样本往往在 0.6 到 0.9 之间波动。如果发现正常样本置信度只有 0.8不要急着调阈值先检查裁剪质量检测框是否贴字符太近、图像是否模糊、字符是否被裁掉半个。这属于典型的“模型没问题前处理背锅”。4.3 缺陷判定的阈值表哪些参数值得存成配置文件缺陷判别模块的阈值是所有参数里最需要文档化的。换一条产线、换一种底材墨水的附着效果就不同阈值几乎必然需要重新校准。把阈值写死在代码里是新手常犯的错误我一般会把这些值单独放在一个 YAML 或 JSON 配置文件中每次换线体只改配置不动代码。参数名用途推荐初始值调整方向conf_thrOCR 置信度下限0.85漏检多则降低误杀多则升高ink_min墨量占比下限0.03深色底材调高浅色底材调低ink_max墨量占比上限0.35墨量不稳定时放宽zero_cols允许断列数上限3点阵稀疏时适当放宽char_min文本最短长度8按实际字符数设置max_angle允许最大倾斜角15度按产线喷码姿态设置这些参数的校准方法是收集 500 到 1000 张经过人工标注的产线图分别跑一遍检测和判别统计误报率和漏检率。漏检比误杀更可怕因为漏检意味着不合格品流出工厂误杀最多是停机复检损失可控。所以调整阈值时我通常会先保证漏检率为零再逐步收窄阈值降低误报。这一原则值得写进项目文档首页。5. 喷码检测最常见的五个坑从漏检到误杀的实际排查5.1 反光导致检测模型集体漏检现象白天产线环境光变化时喷码区域经常拍成白花花一片检测模型在验证集上 mAP 很高现场却一张都检不出来。原因包装膜或瓶盖表面反光把点阵字符的对比度压没了墨迹和背景几乎融为一体。解决先治光源用低角度环形光源加偏振片让光线从侧面斜射进字符凹坑代码层面可以对输入图像先做 CLAHE 局部对比度增强再送进检测模型。注意不要用全局直方图均衡那会把背景噪点一起放大。5.2 断针缺陷被 OCR 的容错机制“洗白”现象喷码缺了一整列OCR 依然识别出正确文本缺陷判别也判了 OK。原因字符的上下文太强了比如日期“20240516”里缺了左边一竖模型照样能推出“1”OCR 对微小缺陷天然有鲁棒性这个鲁棒性恰好掩盖了缺陷。解决不依赖 OCR 文本做质量判断用列投影统计墨迹空列数甚至可以直接统计二值化后每列的像素数方差。方差过大说明墨量分布不均这种统计方法对断针非常敏感且不受 OCR 容错影响。5.3 喷码字符间距随机检测框忽大忽小现象同一台喷码机传送带速度快时字符被拉长速度慢时字符被压缩检测框的宽高比在 2:1 到 5:1 之间波动。原因喷码字符是逐行扫描喷出的传送带速度直接影响字符的纵向间距。解决训练检测模型时不要固定 anchor 比例把 anchor 的宽高比范围放宽到包含极端情况后处理里拿到检测框后按字符区域的“高度”做归一化不要用固定宽度裁剪。这个坑在原理流程教程里通常会放在“数据增强”部分讲建议训练时对标注框做随机拉伸增强模拟速度波动。5.4 换产线后准确率骤降现象在 A 产线的测试集上准确率 99%换到 B 产线后跌到 80%。原因两条产线的底材颜色不同A 产线是白底纸盒B 产线是透明薄膜喷码的对比度分布完全变了另外墨水的干燥时间不同字符的锐度也不同。解决每个产线 ID 单独存一份配置文件和校准阈值检测和 OCR 模型在预训练权重基础上用新产线的小样本数据做几十轮微调通常几百张图就够不需要重新标注几千张。这个做法在工业项目里叫产线适配比训练一个通用模型更可控。5.5 正负样本失衡模型只会说“合格”现象缺陷检测的准确率看着很高但这是因为正常样本占 90% 以上模型什么都不检测也能拿到高准确率。原因现场缺陷样本本来就少断针、飞墨这些缺陷出现概率很低数据集里正负样本比例悬殊。解决主动合成缺陷样本对正常喷码图做形态学腐蚀模拟断针随机撒点模拟飞墨加入训练集。合成样本不需要太逼真能让模型学会“这类异常要警惕”即可。import cv2 import numpy as np def make_synthetic_defect(img, drop_cols2, noise_prob0.005): aug img.copy() h, w aug.shape[:2] # 模拟断针随机抹掉几列 for col in np.random.choice(w, drop_cols, replaceFalse): aug[:, max(0, col-1):min(w, col2)] 255 # 模拟飞墨随机撒黑色噪点 mask np.random.random((h, w)) noise_prob aug[mask] 0 return aug这里的drop_cols控制断针的列数noise_prob控制飞墨密度。合成样本要控制比例一般让缺陷样本占训练集的 20% 到 30%太多会让模型对正常样本也产生误报。6. 部署到产线前再做的三件事导出、校正与节拍优化6.1 把模型导出为 ONNX 再做推理训练好的检测模型和 OCR 模型不要直接用 PyTorch 推理产线机器上不一定有完整的训练环境。统一导出 ONNX 格式用 ONNX Runtime 跑推理部署简单且推理速度接近原生框架。检测模型导出一般一条命令搞定。OCR 模型导出后用 OnnxRuntime 加载输入输出维度保持不变。导出的模型记得用测试集图片跑一遍确认导出前后结果一致。6.2 裁剪图先做倾斜校正再送 OCR喷码经常有轻微倾斜瓶身弧度、传送带抖动都会造成角度偏移。OCR 模型虽然对小幅倾斜有容忍度但倾斜超过 15 度识别率会明显下降。在裁剪图送入 OCR 之前加一步倾斜校正能稳定提升两三个百分点的识别准确率。def deskew(crop_img): gray cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY) _, bw cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) coords cv2.findNonZero(bw) angle cv2.minAreaRect(coords)[-1] if angle -45: angle 90 angle h, w crop_img.shape[:2] center (w // 2, h // 2) M cv2.getRotationMatrix2D(center, angle, 1.0) return cv2.warpAffine(crop_img, M, (w, h), borderValue255)注意cv2.minAreaRect返回的角度范围是 -90 到 0 度需要做一次角度换算否则校正方向是反的。这一步看似简单实际是喷码 OCR 项目里最常见的“玄学”点效果提升不明显时先检查倾斜校正的角度换算有没有写对往往问题就出在符号上。6.3 用批处理和帧缓存压推理耗时产线相机通常是连续采集不是一张图单独触发。常见做法是把连续若干帧放进缓存队列检测模型按 batch 推理OCR 模型的输入依赖检测结果无法直接批量但对同一检测框做多次采集时可以缓存上一次的结果连续几帧相同就不重复识别。实测在普通工控机上检测加 OCR 串行每张大约 60 到 80 毫秒加上批处理和缓存后能压到 30 毫秒以内满足大多数产线 30 帧每秒的节拍要求。我现在的习惯是每接一个新产线先跑一遍整个流程把缺陷判别的阈值全部写进产线配置文件再在产线连续运行一整天统计误报和漏检用真实数据校准。不要相信验证集上的准确率数字产线数据永远比测试集更“不讲理”。希望帮到你。本文还有配套的精品资源点击获取
返回列表