简介:这份资源是面向机器视觉与工业质检方向的实战项目包,聚焦OCR喷码缺陷检测,适合初学者入门缺陷检测,也可供有经验的工程师参考。内容围绕喷码字符的识别与缺陷判定展开,覆盖数据收集、图像预处理、特征提取、模型训练到检测算法实现的完整链路,并讲解去噪、对比度增强等关键环节。压缩包共207个文件,约156.81MB,包含55张jpg与26张png图像样本、41个csv数据记录、12个json配置、12个pdparams与11个pdopt模型权重、11个py源码脚本及yml、txt、md等说明文档,结构清晰便于按模块查阅。项目源码注释丰富,可据此调整优化,实现个性化检测需求。目前已有109人学习,适合希望快速掌握OCR喷码缺陷检测方法、提升工业视觉技术实力的读者。
1. 喷码缺陷检测到底在检什么:从一条产线误判说起
喷码缺陷检测,本质上是在高速产线上判断字符“印得对不对、清不清楚、位置偏没偏”。它和通用 OCR 文字识别最大的区别是:OCR 只关心“读出来是什么”,而喷码缺陷检测要同时回答三个问题——字符内容是否正确、字符形态是否合格、喷印位置是否在允许范围内。很多刚接触这个方向的工程师,第一反应是拿一个开源 OCR 模型直接跑,结果发现漏检率居高不下,原因就在于把“识别”当成了“检测”。
这个方向适合三类人:一是做机器视觉缺陷检测、想从表面缺陷扩展到字符缺陷的工程师;二是做产线自动化、需要把人工目检替换成自动判定的开发者;三是手里有 OCR 基础、想找一个完整项目源码和原理流程教程来打通落地链路的同学。它解决的核心痛点是:喷码字符对比度低、背景复杂、字体固定但缺陷形态多样,传统模板匹配容易翻车,纯深度学习 OCR 又缺少缺陷判定逻辑。
我见过一条灌装线,喷码机墨量不稳,字符偶尔出现断笔画。人眼能看出来,但当时用的 OCR 置信度依然有 0.92,系统直接放行。后来加了字符结构比对和缺陷分类,才把这类“能读但印坏了”的样本拦住。这就是喷码缺陷检测和普通 OCR 的分水岭:前者要的是判定,后者要的是转录。
2. 喷码缺陷检测的原理链路:从图像到判定结果
2.1 为什么不能直接拿 OCR 结果当检测结果
OCR 的输出是一个字符串加置信度,它衡量的是“模型有多确信自己读对了”,而不是“这个字符印得有多完整”。一个断了一半的“8”,OCR 可能仍然输出“8”,置信度也不低,因为剩余笔画足以让它分类。但产线要的是“这个 8 有没有缺墨、有没有拖尾、有没有偏移”。
所以喷码缺陷检测的链路必须拆成两段:第一段做字符定位与识别,拿到每个字符的位置和内容;第二段做缺陷判定,基于字符区域做形态学分析或分类。常见做法是先用检测模型框出字符区域,再对每个字符区域做预处理和特征比对。也有方案直接用分割网络把字符前景提取出来,再算连通域、笔画宽度、面积占比等指标。
选型上,如果喷码字体固定、背景干净,传统视觉加模板匹配就能跑;如果字体有变形、背景有反光或纹理干扰,就需要引入深度学习检测。我一般会先跑一遍传统方案看误检分布,再决定要不要上模型,而不是一上来就堆网络。
2.2 字符定位与预处理的四个关键步骤
第一步是 ROI 提取。产线相机拍到的图往往包含传送带、瓶身、背景,喷码只占一小块。常见做法是用固定 ROI 加轻微偏移搜索,或者用目标检测模型定位喷码区域。ROI 不准,后面全白搭。
第二步是灰度化与增强。喷码通常是浅色背景上的深色字符,或者反过来。用 CLAHE 做局部对比度增强,比全局直方图均衡更稳。参数上,clipLimit 一般设 2.0 到 4.0,tileGridSize 用 8×8,太大容易放大噪声。
第三步是二值化。固定阈值在光照变化时会翻车,推荐用自适应阈值或 Otsu。如果字符和背景对比度本身够,Otsu 就够用;如果背景有渐变,自适应阈值更稳,blockSize 取 31 或 51,C 取 5 到 10。
第四步是形态学清理。开运算去噪点,闭运算连断笔。核大小根据字符笔画宽度来定,一般取笔画宽度的 1.5 到 2 倍。核太大,细笔画会被吃掉,反而制造缺陷。
import cv2 import numpy as np def preprocess_code_region(roi): # 转灰度 gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) # CLAHE 局部对比度增强 clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8)) enhanced = clahe.apply(gray) # 自适应阈值二值化 binary = cv2.adaptiveThreshold( enhanced, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, blockSize=31, C=8 ) # 形态学清理:先开运算去噪,再闭运算连断笔 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) closed = cv2.morphologyEx(opened, cv2.MORPH_CLOSE, kernel) return gray, binary, closed这段代码里,clipLimit=3.0控制对比度增强幅度,太大噪声会被放大;blockSize=31是自适应阈值的邻域大小,必须取奇数;C=8是从均值里减去的常数,越大二值化越保守。形态学核用 3×3 是起点,实际要根据相机分辨率和字符笔画宽度调。如果字符在图中占 40 像素高,笔画大概 3 到 5 像素宽,3×3 核合适;如果字符只有 15 像素高,核要缩到 2×2 甚至不做闭运算。
2.3 缺陷判定的三条路线:模板比对、特征规则、分类模型
模板比对适合字体固定、位置固定的场景。做法是拿一张标准样本做模板,对齐后算差分图,差分超过阈值的区域标为缺陷。难点在配准,喷码位置每次都有轻微偏移,直接减会满屏都是差异。常见做法是用相位相关或特征点做亚像素对齐,再做差分。
特征规则适合缺陷类型明确的场景。比如断笔对应连通域数量异常,拖尾对应字符外接矩形宽高比异常,缺墨对应前景像素面积低于阈值。这些规则可以单独用,也可以组合成打分。我一般会先统计一批正常样本的特征分布,取 3σ 或分位数作为阈值,而不是拍脑袋定。
分类模型适合缺陷形态多、规则难覆盖的场景。做法是把每个字符区域裁出来,缩放到固定尺寸,送进一个小 CNN 做二分类或多分类。数据量少的时候,用预训练模型加微调,或者用异常检测思路只学正常样本。Dinomaly 这类方法在 2D 缺陷检测里被讨论得多,思路是只拿正常样本训练,推理时算重建误差,误差大的判为异常。喷码缺陷检测也可以借用这个思路,尤其是缺陷样本难收集的时候。
三条路线不是互斥的。实际项目里,我常用模板比对做快速筛选,特征规则做可解释判定,分类模型兜底复杂缺陷。这样既有速度,又有可追溯性。
3. 用 Python 和 OpenCV 跑通喷码缺陷检测最小闭环
3.1 环境准备与依赖安装
这个最小闭环只需要 Python、OpenCV、NumPy。不依赖深度学习框架,方便先验证链路是否通。安装命令如下:
pip install opencv-python numpy如果要用到 OCR 做字符内容校验,可以再加 Tesseract 或 PaddleOCR。Tesseract 安装时注意语言包,喷码字符一般是英文数字,eng包就够。PaddleOCR 对中文和数字混合更友好,但依赖稍重。离线场景下,Tesseract 更轻量。
提示:OpenCV 版本建议 4.x,
adaptiveThreshold和形态学接口在不同版本间行为一致,不容易出玄学问题。
3.2 字符区域提取与对齐的完整代码
下面这段代码做三件事:提取字符轮廓、按位置排序、把每个字符裁出来并统一尺寸。排序很关键,喷码可能是多行或多段,不排序的话字符顺序会乱。
import cv2 import numpy as np def extract_char_boxes(binary_img, min_area=50): # 找外轮廓 contours, _ = cv2.findContours( binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) boxes = [] for cnt in contours: area = cv2.contourArea(cnt) if area < min_area: continue x, y, w, h = cv2.boundingRect(cnt) # 过滤明显不是字符的细长区域 aspect_ratio = w / float(h) if aspect_ratio > 5 or aspect_ratio < 0.1: continue boxes.append((x, y, w, h)) # 按从左到右、从上到下排序 boxes.sort(key=lambda b: (b[1] // 20, b[0])) return boxes def crop_and_normalize(roi, boxes, target_size=(32, 48)): chars = [] for (x, y, w, h) in boxes: # 适当外扩,避免切掉边缘笔画 pad = 2 x1 = max(0, x - pad) y1 = max(0, y - pad) x2 = min(roi.shape[1], x + w + pad) y2 = min(roi.shape[0], y + h + pad) char_img = roi[y1:y2, x1:x2] char_img = cv2.resize(char_img, target_size) chars.append(char_img) return charsmin_area=50是最小面积阈值,低于这个值的轮廓当噪声丢掉。如果相机分辨率高,字符面积大,这个值要往上调。aspect_ratio过滤掉传送带边缘、反光条这类细长干扰。排序时用b[1] // 20做行分组,20 是行高容差,实际要根据字符高度调。外扩pad=2是防止二值化把边缘笔画削掉,导致误判断笔。
3.3 缺陷判定:面积、连通域、宽高三条规则
拿到每个字符的裁剪图后,就可以算特征做判定。下面这段代码对每个字符算三个指标:前景像素占比、连通域数量、外接矩形宽高比。正常字符的这三个指标会在一个稳定范围内,偏离就判缺陷。
def check_char_defect(char_img, ref_stats): gray = cv2.cvtColor(char_img, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 指标一:前景像素占比 fg_ratio = np.count_nonzero(binary) / binary.size # 指标二:连通域数量 num_labels, _ = cv2.connectedComponents(binary) num_components = num_labels - 1 # 去掉背景 # 指标三:外接矩形宽高比 contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: x, y, w, h = cv2.boundingRect(max(contours, key=cv2.contourArea)) aspect = w / float(h) if h > 0 else 0 else: aspect = 0 defects = [] if fg_ratio < ref_stats['fg_ratio_mean'] - 3 * ref_stats['fg_ratio_std']: defects.append('缺墨') if num_components > ref_stats['components_max']: defects.append('断笔') if abs(aspect - ref_stats['aspect_mean']) > 3 * ref_stats['aspect_std']: defects.append('变形或拖尾') return defects, (fg_ratio, num_components, aspect)ref_stats是从一批正常样本统计出来的均值和标准差。fg_ratio低于均值减三倍标准差判缺墨;num_components超过正常最大值判断笔;aspect偏离均值三倍标准差判变形。这三个阈值不是固定的,换一批产品、换一种字体都要重新统计。我一般会先跑 200 到 500 张正常样本,把分布画出来,再定阈值。如果正常样本本身波动大,说明预处理或 ROI 不稳定,要先解决上游问题。
注意:连通域数量对噪声很敏感。如果二值化后有小噪点,连通域会虚高。所以形态学开运算不能省,或者用面积过滤把小于 5 像素的连通域去掉再计数。
4. 喷码缺陷检测避坑:五条血泪经验
4.1 现象:正常字符被大量判为断笔。原因:二值化阈值把浅色笔画切碎了。解决:改用自适应阈值,或者先做对比度增强再二值化,形态学闭运算核适当加大。
4.2 现象:缺陷漏检,尤其是轻微拖尾和边缘毛刺。原因:特征阈值太宽,或者预处理把毛刺平滑掉了。解决:降低形态学核尺寸,保留边缘细节;对拖尾类缺陷单独加方向梯度或投影特征。
4.3 现象:换一批产品后误检率飙升。原因:ROI 位置偏移、光照变化、喷码颜色变化导致预处理参数失效。解决:ROI 加自动搜索或检测模型定位;预处理参数按批次自适应,或者每班次用标准样本重新标定一次。
4.4 现象:OCR 读出来内容对,但系统判缺陷,产线不认。原因:缺陷判定规则和产线质量标准没对齐。解决:拉上质检人员一起看样本,把“可接受”和“不可接受”的边界样本标出来,用这些样本反推阈值,而不是只看统计分布。
4.5 现象:推理速度跟不上产线节拍。原因:每个字符都跑一遍分类模型,或者图像分辨率过高。解决:先做 ROI 裁剪再推理,字符区域统一缩放;分类模型用轻量网络或 ONNX 加速;规则能搞定的不要上模型。
5. 从最小闭环到产线可用:三个进阶技巧
第一个技巧是用正常样本做异常检测,解决缺陷样本难收集的问题。具体做法是只拿正常字符训练一个自编码器或 Dinomaly 类模型,推理时算重建误差,误差超过阈值的判异常。这样不需要标注缺陷类型,适合缺陷形态多变、标注成本高的场景。我一般会先用规则跑一版,把规则判不了的样本挑出来,再用异常检测兜底。
第二个技巧是把字符内容校验和形态判定解耦。OCR 负责读内容,形态判定负责看印得好不好。两者结果做与运算:内容错或形态错都判缺陷。这样避免 OCR 置信度高但印坏了被放行,也避免形态规则把正常但字体特殊的字符误杀。Tesseract 和 PaddleOCR 都可以输出每个字符的置信度,可以拿来辅助判定。
第三个技巧是建立样本回流机制。产线每天跑,总会遇到新缺陷。把系统判缺陷但人工复核为正常的样本、以及系统放行但人工发现缺陷的样本,定期回流到测试集,重新统计特征分布或微调模型。没有这个机制,系统上线三个月后误检率会慢慢爬升,这是很多项目翻车的地方。
| 技巧 | 适用场景 | 关键参数 | 注意事项 |
|---|---|---|---|
| 异常检测兜底 | 缺陷样本少、形态多变 | 重建误差阈值 | 正常样本要覆盖足够多的正常波动 |
| 内容与形态解耦 | 字符内容需校验 | OCR 置信度阈值 | 两个判定结果做与运算 |
| 样本回流 | 长期运行产线 | 回流周期、统计窗口 | 定期重新统计特征分布 |
验证方法上,我习惯用混淆矩阵加缺陷类型分布来看。不光看整体准确率,还要看每类缺陷的召回率和误报率。如果某类缺陷召回低,就单独补样本或加规则;如果误报集中在某个字符或某个位置,就查 ROI 和预处理。
最后说个习惯:我每次调完阈值,都会拿一批没参与调参的样本跑一遍,确认不是过拟合。喷码缺陷检测这行,阈值调得太贴合当前样本,换一批就翻车,留点余量比追求极致指标更实用。希望帮到你。
本文还有配套的精品资源,点击获取