说实话,第一次接到“商品标签识别”这个需求时,我心里是有点打鼓的。这不是那种常见的“猫狗分类”Demo,而是要把货架上的商品标签——那种贴在饮料瓶、零食包装、纸箱侧面,经常被反光、褶皱、透明胶带覆盖的小纸片——从真实场景照片中准确地找出来,再交给下游去读取品名、规格、条码信息。当时甲方最直接的诉求是:仓库盘点时人工核对标签太慢,一天几千件货,光扫码和录入就要耗掉两三个小时,他们想要一套能自动识别并归档的方案。
我花了大约三周时间,用YOLO系列做了一条完整的“检测→裁剪→文字识别”流水线。今天这篇就把整个思考过程、踩坑记录和最终落地效果原原本本写下来。无论你是刚接触目标检测的学生,还是被公司派去做类似“XX识别系统”的开发者,这套从理论到实践的路径应该都能帮你少走不少弯路。
1. 从一次盘点需求说起:商品标签识别的真实痛点
1.1 需求远不止“找到标签”那么简单
先泼一盆冷水:很多人听到“商品标签识别”,第一反应是“用YOLO框出来不就行了”。但框出来之后呢?如果下游还要人工去读标签上的字,那这个系统就只是半个系统。我当时和需求方反复确认,最终敲定的完整链路是:
- 第一级:用目标检测模型在整图中定位标签位置,输出带倾斜角的四边形坐标;
- 第二级:把标签区域裁剪下来,做透视矫正,再丢给OCR组件提取文本字段;
- 第三级:按预设规则解析出“品名、规格、生产日期、条形码”,形成结构化记录。
所以,“YOLO识别商品标签”只是整条流水线的前半段。但恰恰是这个前半段,决定了后面OCR能不能稳定工作。标签框得歪了、缺角了、把旁边另一个标签的边也包进来了,OCR结果就会直接报废。
1.2 商品标签场景里的几个“魔鬼细节”
在做数据调研时,我把真实货架照片翻了几百张,发现商品标签的问题集中在四个方面:
- 小目标占比极高。一瓶550ml的矿泉水放在货架中层,拍摄距离一到两米时,整张图片里标签像素可能只有40×60,占整图不到1%。
- 标签密度大、相互粘连。同排商品紧密摆放,两个相邻瓶子的标签几乎贴在一起,检测框稍微大一点就会把两个标签框进同一个框。
- 光照和反光干扰。透明塑料瓶上的标签被灯光照出高光,热敏纸标签在暖光下偏黄,金属罐装标签则可能整体反白。
- 类别维度模糊。同样是某品牌的乌龙茶,有“无糖”和“低糖”两个版本,标签设计几乎一样,只有一行小字不同。如果类别定义不清楚,标注和训练都会乱套。
1.3 一个关键决定:把标签当作“目标”而不是“纹理”
第一次设计模型时,我差点走偏——想用一个模型同时完成“定位标签”和“识别标签上的文字”,也就是端到端的检测识别。查了一圈资料和开源方案后发现,这类端到端方案对数据量和算力要求极高,而且文字长度不固定,直接套用目标检测头并不灵活。
最终我采用了“检测+OCR”两级方案。这个决定的影响很大:
- YOLO只需要负责“标签在哪里、大致是什么姿态”,类别简单、正负样本分明,模型压力小;
- OCR可以单独训练、单独换模型,后续想提升识别准确率不需要重新标注检测数据;
- 检测环节对算力的要求可以压得很低,边缘设备也能跑。
如果你也在做类似的“证件识别”“单据识别”“铭牌识别”,我建议你先想清楚:识别的主体是“区域”还是“文字序列”。绝大多数情况下,拆成两段做,比一个端到端模型省心得多。
2. YOLO选型:yolov5、yolov8还是更新的版本?
2.1 先别追新,先看你的约束条件
“基于YOLO系列”,具体用哪个版本?我当时的约束条件是:推理设备是一块中端显卡(支持半精度),也有可能在Jetson类边缘盒子上部署;标注数据量不多,大约只有3000张图;训练时间预算在一天以内。
在这三个约束下,我的选择逻辑是这样的:
- 大型模型(如YOLO-X、YOLO-L)参数多、精度上限高,但小数据量下容易过拟合,且边缘端推理帧率不理想,优先排除;
- nano模型最轻,但标签这类小目标对特征表达能力要求高,nano的浅层特征图语义信息不足,实测漏检率偏高;
- 折中采用m或s规格,配合输入分辨率提升,性价比最高。
我最后选了YOLOv5m版本(开源社区成熟、文档多、ONNX导出方便)作为主力实验,同时也对比过YOLOv8s。YOLOv8的anchor-free检测头在密集目标上的召回率稍好一点,但同等训练条件下,反而因为标注框大小差异大,收敛速度不如v5稳定。这倒不是说新版不好,而是在“数据量不大、目标小且密集”的场景里,v5的anchor机制配合手动聚类锚框,反而更容易调出效果。
2.2 小目标检测:不是简单把图放大就行
商品标签在整张图里占比很小,这是模型精度最大的瓶颈。解决小目标问题,一般有三条路:
- 提升输入分辨率。把训练和推理的输入尺寸从640×640提升到1280×1280,小目标的AP收益非常明显,但显存占用和推理耗时也会跟着涨。我在训练时用1280,推理时根据设备性能在960~1280之间浮动。
- 增加高分辨率检测头。YOLOv8的P2检测头、YOLOv5的P2层,都是把特征图分辨率进一步提升,专门增强小目标感受野。实际测试中,加P2头之后瓶身小标签的召回率能提升4~6个百分点,但训练显存需求增加约30%。
- 图像切片推理。把大图切块分别检测,再拼接结果。这个方法在极端小目标场景有效,但标签识别属于“目标不算特别小”的范畴,切片带来的后处理复杂度远大于收益,我没有采用。
如果你做过类似项目就会发现:对小目标而言,最大的提升往往不是换更强的骨干网络,而是“把输入分辨率拉高”和“让标注框更准”。先把这两点做好,比纠结换哪个版本收益大得多。
2.3 锚框再聚类:不要用官方默认参数
YOLOv5自带的锚框是针对COCO数据集(80类日常物体)聚出来的,直接用来检测长宽比悬殊的商品标签效果一般。标签的常见长宽比集中在2:1到5:1之间,和COCO里“人、车、猫”那种接近1:1的目标差异很大。
我在训练前用k-means对自建数据集的标注框重新聚类,得到9组锚框,重点增加了细长形锚框的数量。这一步不麻烦,但对收敛速度的提升立竿见影:同样的epoch数,聚类锚框版本在验证集上的mAP比默认锚框高出了近2个点。
提示:重新聚类锚框时,聚类的尺度要和实际训练尺寸保持一致。如果你训练时输入是1280×1280,就用1280下的框尺寸聚类,而不是用640下的。这个细节很多人会忽略,导致聚类结果对不上实际训练分布。
3. 数据准备:3000张图是怎么攒出来、怎么标准的
3.1 采集策略:别只在“理想环境”下拍
数据采集是最耗时、也最影响最终效果的环节。我当时的采集原则可以用一句话概括:故意制造脏乱差,再从中挑出可用的。
具体做了三类采集:
- 货架正拍:用手机在距离货架1~1.5米的位置水平拍摄,覆盖不同层高、不同商品密度;
- 仓库手持拍:模拟盘点员边走边拍,画面有轻微的倾斜和抖动模糊;
- 补光/逆光拍:用射灯或窗外自然光制造局部高光,专门收集标签反光的失败样本。
三步采集下来一共收集了约5000张原始图片,经过人工筛选,剔除了严重失焦、标签占比过小(小于24×24像素)、以及遮挡面积超过40%的样本,最终保留3800张作为漏检训练集。
3.2 标注规则:一个框“该框什么”比想象中影响大
我旁边有同事负责标注,最开始标注质量参差,有人把标签上方的促销贴纸也圈进去了,有人只框了文字区域,有人框了标签带背纸的完整区域。这些不一致直接导致模型训练时回归目标混乱。
后来我把标注规则固定为:
- 框选区域是“标签的完整可视区域”,包括底色、边框、文字,不包含瓶身曲面明暗过渡;
- 如果标签被胶带覆盖,框选到胶带边缘,不扩展到胶带外;
- 如果同一商品有两个标签(正面主标签+侧面小标签),作为两个独立目标框;
- 类别只设一个“商品标签”(因为检测阶段不需要区分品名,区分品名是OCR之后的事)。
这里强烈建议:标注规范必须在项目启动第一天就写成文档,并且前一百张图要由建模的人亲自标,而不是直接甩给标注团队。标注问题造成的精度损失,很多时候无法靠调参弥补回来。
3.3 合成数据补强:解决“标签透明”和“光照变化”
真实标签中,透明塑料瓶上的标签往往和瓶内液体颜色混在一起,肉眼看着很清楚,但模型容易把标签和背景当成一个整体。针对这类问题,我做了两类合成增强:
- 背景替换合成:把标签图片从原始背景中抠出,贴到不同的货架、纸箱、手持场景背景图上,同时随机叠加高斯模糊、泊松噪声、亮度扰动;
- 字体扰动合成:对一些空标签模板,用脚本随机生成“品名+规格+条码”组合的模拟标签,再经过透视变换贴到商品图上,这样模型见到的文本布局更多样。
合成数据单看精度提升有限,但配合真实数据混合训练后,模型对“透明标签+复杂背景”的鲁棒性确实变好了。需要注意合成比例不要超过30%,否则模型会对合成纹理过拟合,真实场景表现反而下降。
3.4 增强策略里的一个“反向操作”
大家都用Mosaic增强(把四张图拼成一张训练),它确实能提升模型泛化能力。但我在做小目标标签检测时发现:Mosaic把图片缩小了,标签目标也跟着变小,如果叠加太多层随机缩放,大量标签会退化成十几个像素的小点,反而干扰训练。
我的做法是:
- 训练集按照“原始图增强”和“Mosaic增强”1:1混合;
- 在Mosaic分支里,限制最小缩放不低于0.6,避免目标缩得太小;
- 关闭Mosaic的概率在前30个epoch设为0,先让模型稳定拟合清晰的标签特征,后期再打开增强。
这个调整在验证集上直接带来了1.8%的mAP提升。如果你也做小目标检测,建议优先检查Mosaic之后的标签像素分布,而不是盲目堆增强。
4. 模型训练:配置、参数与调优记录
4.1 训练环境的版本选择与坑
训练机的显卡是GTX 3090(24GB显存),系统为Ubuntu 20.04,Python环境用的是3.8。YOLOv5官方代码依赖的PyTorch版本,开发环境与生产环境之间有一个容易被忽视的坑:训练时用的图像预处理库版本和推理时不一致,会导致同样的图片推理结果不稳。
举例来说,我训练时用的OpenCV读取BGR图像,而某个部署后端的图像加载组件默认读取RGB,导致输入图像通道顺序错误,模型准确率断崖式下跌。排查了很久才发现,是推理脚本里少了一句cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。
所以,建议在项目第一天就把“图像读取→预处理→模型推理→后处理”的每个环节的格式写清楚,尤其是通道顺序和归一化方式。这个问题不解决,模型训练得再好也白搭。
4.2 超参配置与训练命令
最终采用的训练配置如下(关键部分):
python train.py --data tag.yaml --weights yolov5m.pt \ --img 1280 --batch 16 --epochs 120 \ --hyp data/hyps/hyp.scratch-low.yaml \ --device 0 --workers 8几个关键参数的选择理由:
--img 1280:输入分辨率直接决定小目标的特征像素数,这点前面已经说过。代价是显存占用翻倍,所以batch从32减到16;--hyp hyp.scratch-low.yaml:低数据增强的配置,避免烂样本干扰早期训练。实际train时,我额外把mosaic的开关按前面说的方式做了自定义;--batch 16:batch过大在小数据集上会加速过拟合,batch过小则BN统计不稳定,16是折中。
学习率策略我使用cosine退火,从0.01开始,前5个epoch做warmup,并在最后20个epoch设置--cos-lr让学习率平滑降到接近0。这个组合在多种检测任务上都很稳定,省去很多手动调参时间。
4.3 类别不均衡与验证集划分
虽然检测阶段只有一个类别,但“商品标签”本身大小差异极大:大的纸箱标签可能有300×200像素,小的瓶身标签只有30×60像素。模型天然会偏向学习大目标,因为大目标的损失占大头。
我的处理方式是:给标注框面积小于32×32像素的样本提高采样权重。具体做法是拷贝多份小目标样本到训练列表中,让同一epoch内小目标出现的次数翻倍。这个技巧比调损失函数权重更简单直观,实测小目标AP提升了3个百分点。
验证集划分也要特别注意:同一商品的不同图片不能既出现在训练集又出现在验证集,否则验证结果虚高。我当时是按“商品款式+批次”分组的,同一SKU的所有图片进同一侧,避免数据泄漏。
4.4 训练过程的监控与诊断
训练不是跑完看个最终mAP就完了,过程监控能提前暴露问题。我每天会固定看五个东西:
- 训练损失曲线和验证损失曲线是否同步下降,验证损失在第80个epoch后开始回升就说明过拟合开始,需要提前保存快照;
- 在验证集上抽样可视化检测框,重点看漏检的小标签和误检的高光区域;
- 查看各类别(按标注框面积区间划分)的PR曲线,确认小目标是否拖后腿;
- 记录F1曲线在不同置信度阈值下的变化,为后续推理阈值提供参考;
- 输出confusion matrix,看标签是否和“促销贴纸”“瓶盖”等相似物体混淆。
这里多说一句:mAP是整体指标,但小目标漏检率才是生产环境真正关心的。我最终评估的时候没有死盯mAP,而是把“标签召回率(IoU=0.5)”作为主要考核标准,因为漏检造成的成本损失远高于误检。
5. 推理部署:从权重文件到可用的服务
5.1 导出与预处理细节
训练好的模型要落地,第一步是导出为ONNX格式,再转成TensorRT引擎(半精度FP16)。这一步本身很常规,但有两个经验值得分享:
- 导出时固定输入尺寸为1280×1280,不要导出动态尺寸。动态尺寸虽然灵活,但TensorRT在动态shape下会多次重新选kernel,推理延迟抖动明显;
- letterbox的填充颜色要和训练时保持一致。YOLOv5默认填充灰色(114,114,114),但你如果自定义了填充色,导出和推理脚本都要改,否则目标位置会偏移。
推理时我统一用以下预处理逻辑:
import cv2 import numpy as np def preprocess(img, target_size=1280): h, w = img.shape[:2] scale = target_size / max(h, w) new_w, new_h = int(w * scale), int(h * scale) resized = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LINEAR) canvas = np.full((target_size, target_size, 3), 114, dtype=np.uint8) offset_x = (target_size - new_w) // 2 offset_y = (target_size - new_h) // 2 canvas[offset_y:offset_y + new_h, offset_x:offset_x + new_w] = resized return canvas, scale, offset_x, offset_y坐标映射回原图时,别忘了减去offset再除以scale,这个小错误会让所有框都偏到一边。
5.2 NMS参数对密集标签的影响
商品标签密集排列时,NMS的IoU阈值很敏感。我最初用默认的iou_thr=0.45,结果一排十瓶饮料的标签,被合并删除到只剩五六个框,因为相邻两个标签的框交叠很大。后来把iou_thr降低到0.35,并关闭了类别间NMS合并,密集标签的保留情况改善明显。
另一个问题是置信度阈值。商品标签的分类难度不高,置信度普遍集中在0.85~0.95之间,但也存在被遮挡、模糊导致置信度降到0.4的极端情况。我最终设了0.25的检测置信度,并把低置信度框交给下游OCR二次确认。这样“漏检”和“误检”能得到平衡。
5.3 小标签区域的透视矫正与OCR联动
检测框输出的是带角度的四边形(YOLOv5的旋转框需要额外接入一个角度回归头,或者用最小外接矩形近似)。我们做了个简化的方式:用检测框的四个角点坐标,配合固定的目标尺寸(比如300×120像素),计算透视变换矩阵,把标签拉正。
这一步非常关键。倾斜的标签直接送OCR精度很差,矫正后识别准确率能从78%提升到96%。透视矫正的核心代码如下:
def rectify_label(src_pts, img): dst_pts = np.array([[0, 0], [299, 0], [299, 119], [0, 119]], dtype=np.float32) M = cv2.getPerspectiveTransform(src_pts.astype(np.float32), dst_pts) rectified = cv2.warpPerspective(img, M, (300, 120)) return rectified这里的目标宽高比是根据聚类统计的“标签长宽比中位数”确定的。如果标签长宽比变化很大,可以多设几个固定尺寸,分别矫正后把多个OCR结果做投票融合,能进一步压住个别错误识别。
OCR部分我选用了一个对中文支持较好的开源OCR组件,在矫正图上跑文本检测+识别,最后按关键词规则(包括“净含量”“保质期”“生产日期”等字段)解析出结构化字段。实际效果:正常光照环境下,单张图的检测+矫正+识别总耗时大约80ms;OCR识别准确率稳定在95%以上,加上规则后处理,最终输出记录的可用率约93%。
5.4 边缘部署与加速的实测结论
后来项目要求搬到Jetson边缘设备上跑,我重新做了性能测试。以1280×1280输入、YOLOv5m模型、FP16精度为例:
| 设备 | 推理耗时 | 总流程耗时 | 显存占用 |
|---|---|---|---|
| 服务器 GPU | 22ms | 80ms | 3.1GB |
| 边缘设备 GPU | 85ms | 160ms | 2.2GB |
边缘设备上检测阶段占用仍然偏高,主要瓶颈是小目标检测需要高分辨率输入。如果硬把输入降到640,推理时间能缩短到35ms,但小标签漏检率会暴涨将近一倍。所以部署这类小目标检测时,“分辨率”这个参数必须格外谨慎,不能为了帧率随意降。
6. 实测效果与走弯路总结
6.1 最终指标与验收口径
最终系统在验证集上的指标如下:
- 标签检测mAP@0.5:0.942;
- 标签召回率(IoU=0.5,置信度阈值0.25):0.913;
- 平均检测误差(框中心偏移占标签宽度的比例):约4.2%;
- OCR字段级准确率:93.4%;
- 单张完整流程平均耗时(服务端):80ms。
按甲方要求的验收口径:每1000个标签中,漏检数不超过25个,误检数不超过40个。实测调优后达到漏检约22个、误检约31个,算是在预算范围内通过了验收。
6.2 走弯路后总结出的“三个最值”
第一个最值:“最容易被低估的环节是数据标注”。我最初对标注质量不以为意,导致训练一天后发现验证集mAP只有0.88,排查后发现是标注框边界不齐、部分标签包含背景过多。重新规范标注并回炉训练后,mAP直接提升到0.924。这件事教会我一个道理:检测任务的精度瓶颈,很多时候不在模型结构,而在“目标定义的一致性”。
第二个最值:“最值得做的一次改动是输入分辨率从640改到1280”。当时曾担心算力不够,但实测发现1280下的mAP比640高6.3个百分点,而推理耗时只增加了不到30%。对小型检测目标来说,输入分辨率提升是投入产出比最高的操作。
第三个最值:“最后一公里往往是后处理”。检测模型输出一堆框,看起来挺像样,但如果不解决NMS误合并、透视矫正不到位、OCR文本解析规则不完善这三个问题,系统在生产上根本无法用。检测性能只占整体体验的六成,后处理才是决定上线体验的最后四成。
6.3 现场适配时遇到的意外工况
真实使用中有一类情况是测试集里没有覆盖的:热敏标签在仓库高温环境下放久了会整体变成深褐色,文字对比度极低。第一次现场测试时这类标签大量漏检,后来在采集环节专门补拍了“陈旧标签”样本,并在数据增强中加入了“整体亮度压低+褐色偏色”的扰动分支,才算把这一类补上。
还有一类是高反光标签上叠加了透明胶带,胶带边在图像里形成一道亮线,模型偶尔会把这条亮线当成标签边缘,导致检测框偏移。针对这个问题,我在矫正后处理里加了一个“边缘平滑校验”:检测框内边缘如果出现较长直线且与框边界平行,会触发一次局部微调,把框内收几个像素。虽然代码量不大,但显著减少了后续OCR裁错区域的现象。
6.4 这套方案后续还能怎么扩展
做完这个项目之后,我又琢磨了几条扩展路径,分享给大家参考:
- 检测阶段输出标签的四个角点坐标,进一步训练一个轻量的角点回归分支,能直接替代“最小外接矩形”方案,矫正精度更高;
- 把OCR解析出的品名、规格字段和已有商品库做相似度匹配,可以实现“自动判断这瓶饮料是否与货架计划相符”,这就从“识别”升级到了“盘点复核”;
- 如果场景变成监控视频流,可以隔帧检测、结合跟踪算法减少重复识别,降低算力消耗。
我个人的体会是,做这类“识别系统”的通用方法论是:先用小数据集快速把链路跑通,再回头补数据、调参数。一开始不要急着优化精度,先确保每一环(检测→矫正→OCR→解析)都能动起来,后面再逐环打磨,整个项目的节奏会健康很多。希望这篇记录能帮到正在做类似项目的你。