ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于YOLOv11的营养成分表检测与部署实战

基于YOLOv11的营养成分表检测与部署实战

1. 为什么盯着营养成分表不放:这个项目的起点和真实痛点

1.1 市面上的识别工具为什么不够用

我最初接到这个需求时,第一反应和大多数人一样:扫个码、拍个照,直接调现成的文字识别接口不就行了?后来在真实货架上跑了一批样本才发现,问题远比想象中复杂。市面上通用的OCR工具擅长处理“干净的文本”,比如扫描件、文档照片、白底黑字的表单,但食品包装不是文档,它是最不友好的识别场景:深色底纹、弧形瓶身、烫金反光、半透明包装袋、甚至标签和背景颜色几乎融为一体。就算文字识别模块再强,前提也是先拿到一张清晰、端正、只包含营养成分表的局部图。如果连“表在哪”都没定位到,后续一切无从谈起。

这个项目真正要解决的,不是“认出字”,而是“先找到字所在的那块区域”。换句话说,目标检测模型要起到一个前置过滤器的作用,从整张包装图中快速锁定营养成分表的位置边界,再裁剪出来交给OCR模块。听起来简单,但营养标签和普通目标有一个本质区别:它没有固定的外观特征。可口可乐的标签、薯片包装上的配料表、牛奶盒侧面的营养信息,版式完全不同,边框粗细、表格结构、字体大小、颜色深浅千差万别。有些标签是印刷在塑料软包装上,被褶皱拉扯后文字都变形了;有些标签表面覆了一层反光膜,正对着拍全是高光。想让模型在各种条件下都具备稳定的检出能力,就必须专门训练一个面向这个场景的检测器,而不是指望通用目标检测模型一上来就能干好这件细活。

1.2 目标检测在营养标签场景下的特殊性

营养标签检测和常规目标检测最大的区别,在于“检测质量”的定义方式不同。比如行人检测,框住一个人大概的范围就够了,下车道、数人头都不需要精确到像素级;车辆检测也类似,一个宽松的包围框完全可以满足后续逻辑。但营养标签不同,检测框的精度直接影响下游OCR的裁剪效果。框偏了,边缘的文字被切掉;框大了,背景里干扰性的图案和广告文字混进来,OCR就会出现大量误识别;框斜了,表格里的行列线全部错位,数字解析直接崩盘。

所以这个项目的检测精度要求是“贴着表格外围走”,而不是“大致圈起来”。这决定了我在数据标注阶段就必须定下严苛的边界规则。另外还有一个容易被忽视的点:营养成分表在不同食品上的物理尺寸差异极大。有的酸奶盖子上只有指甲盖大小的一块,有的礼盒侧面有半张A4纸那么大。同一张图片里可能会出现多个待检测对象,比如正反面包装同时入镜,或者货架上多个商品前后遮挡。这些情况都要求在模型训练时尽量多覆盖多尺度目标和遮挡目标的样本,不能只拿“单瓶饮料放在纯色背景上”的摆拍数据训练完就觉得完事。

2. 数据集是整个项目的命脉:采集、标注与增强全流程

2.1 真实场景数据采集的四个维度

一个检测模型的上限,往往在数据采集阶段就已经决定了。我在这个项目里前后采集了大概6000多张有效图像,覆盖了超市货架、便利店冷柜、家庭餐桌、户外光线、夜间室内灯光等不同场景。这里我把采集经验总结成四个维度,供参考。

第一是拍摄角度。不能只拍垂直正对包装的平视图,还需要包含一定比例的俯拍和斜拍。实际使用场景中,用户拿着手机随手一拍,角度基本没有标准正对的。我专门在30度到60度斜角范围内采集了约三成数据,这样模型才会对透视形变不那么敏感。

第二是光照条件。冷柜里的荧光灯、超市顶部的暖黄射灯、户外自然光、拍摄时产生的镜面反光,这些都要让模型见过。尤其是覆膜包装的高光区域,经常会把表格数字直接“吃掉”。我通过调整拍摄位置和遮挡角度,刻意制造了一部分反光样本,让模型学习在局部高光下仍然保持检测能力。

第三是包装状态。食品包装在货架上通常是被挤压、折叠的。软包装的褶皱会导致标签扭曲,盒装产品的边角会卷起。这些样本不能只在网上找图,必须自己到线下超市去拍实物的自然状态。我在两个不同定位的商超拍了大约40%的数据,就是为了让模型真正适应货架环境。

第四是负样本。训练集里不能只有“包含营养成分表”的正样本,还必须混入大量不包含任何营养成分表、但看起来很像表格的包装图片,比如食品背面的配料表在排版上容易和营养表混淆,还有说明书的表格、包装上的生产日期喷码区域。这类负样本大约占了15%,用来压低误检率。

2.2 标注标准的制定:检测框到底该框到什么边界

标注是这一类项目里最不能含糊的环节,标准不统一,模型学出来的特征就混乱。我一开始团队内部讨论过一个问题:检测框应该包含整个“营养成分表”的外框,还是只框表格内部的文字区域?参考了标注工具的可视化效果和后续OCR的裁剪需求后,我最终定下的规则是:检测框必须包含表格的完整外框线,但不包含表头最上方可能存在的品牌Logo或产品名称区域。

举个例子,有些包装上会把“营养成分表”五个字放在表格外部,紧挨着表格上边缘,这五个字在项目里算作表头的一部分,检测框要包含;但它上方如果还有一行“净含量:250毫升”的印刷文字,这一行不要框进来。为什么这样定?因为在绝大多数食品包装上,“营养成分表”标题和表格主体在视觉上是一体的,属于用户要找的信息;而净含量、生产日期这些属于“其他印刷信息”,不属于营养标签。这个标准看着简单,但在标注过程中需要反复校准。我还设置了标注复核环节,每批次标注完成后,由另一个人对全部框体做二次检查,重点排查框体外扩和内缩超差的问题。

标注工具选择的是常见的矩形框标注工具,模式下只标一个类别:能够输出Pascal VOC格式的XML文件和YOLO格式的TXT文件,方便后续切换训练框架。数据量方面,单张图上可能同时出现两到三个营养标签的情况不少,所以最终标注出的目标实例总数大约在8200个左右。

2.3 数据增强策略:让模型见多识广

采集的数据再多,也不可能穷尽所有使用场景,数据增强是扩充覆盖面的核心手段。但增强不是越多越好,用错了反而会损伤模型。我参考了实际部署反馈,最后只选用了四类效果明显、风险较低的增强方式。

第一类是几何变换,包括小角度的旋转、水平翻转、轻微的缩放和裁剪平移。这一步能让模型对拍摄角度和构图差异更鲁棒。需要注意,旋转角度不要超过15度,超过之后营养成分表里的文字内容就会出现严重的透视变形,反而和真实场景不符。

第二类是光照扰动,包括亮度随机调整、对比度随机调整和Gamma校正。这类增强直接针对超市和户外光照差异大的问题。我把亮度扰动范围设置在正负25%,对比度扰动范围也控制在正负20%,并用随机HSV色偏模拟不同灯光下的色温差异。

第三类是模糊模拟,用高斯模糊和运动模糊模拟手持拍摄时的轻微抖动。这一步对检测器很重要,因为训练样本太“清晰”会让模型在真实场景中出现精度退化的现象。模糊核大小我控制在3到7像素之间,且只对30%的样本做模糊处理。

第四类是Mosaic拼接增强。把四张不同的训练图像随机拼接成一张,让模型被迫适应更复杂的背景。这种方法能显著提升模型在小目标检测上的表现。我在这类增强上叠加了随机位置抖动,避免模型学到拼接缝区域的不自然特征。

测试下来,单纯使用Mosaic让mAP提升了约2.1个百分点,而在此基础上叠加模糊增强,还能再提升约0.8个百分点。这个增幅在检测任务里已经很可观了。增强参数没有统一公式可套,最靠谱的做法是定期做A/B验证,拿增强前后两组模型在真实采集的验证集上对比效果,而不是凭感觉堆参数。

3. 模型选型、训练配置与第一轮效果

3.1 为什么选择YOLOv11而不是继续用老版本

目标检测领域可选模型非常多,两阶段检测器精度高但速度慢,不适合移动端实时的场景;单阶段检测器里,YOLO系列一直保持不错的精度和推理速度均衡。为什么选YOLOv11而不是继续用以前的版本?主要有三个考虑。

第一是C3k2模块的改进。YOLOv11的主干网络使用了C3k2结构,相比老版本中的C2f,它在同样计算量下拥有更强的梯度流和特征复用能力,对小目标检测更友好。营养成分表在整张包装图片里通常占比不到20%,属于中小目标,这种特征融合层面的改进直接影响检测精度。

第二是M-C2f模块引入的跨尺度特征融合。这个设计同时聚合不同层的特征信息,并带有注意力机制,能够更好地区分“表格”和“背景文字”之间的细微差别。实测下来,在包含大量装饰字体和广告文案的包装图上,模型的误检数量比老版本少了不少。

第三是训练和部署生态更成熟。YOLOv11的训练代码结构清晰,从准备数据集到输出权重文件,全程命令行操作,而且自带多种预训练权重。对于项目周期有限、不需要从零设计网络的工程来说,这是最稳的选择。

3.2 训练参数的选择依据与实际训练过程

训练环境方面,我用了一台配备24GB显存级别显卡的深度学习工作站,操作系统是Ubuntu。代码层面直接使用了YOLO系列的开源训练框架,安装了依赖库后,只需把标注好的数据集按照官方目录结构整理好,就能开始训练。

训练配置里,我选择YOLOv11n作为初始模型,原因是推理速度优先,后续根据效果再考虑是否升级到更大模型。具体参数如下:

  • 输入图像尺寸:640x640像素。这是速度与精度的折中,再加大到1280像素虽然小目标检出率更高,但推理耗时几乎会翻倍,不利于端侧实时应用。
  • 训练轮数:150轮。训练过程中开启了早停机制,当验证集损失连续20轮不下降就停止,避免过拟合。
  • 批次大小:16。在24GB显存条件下比较宽松,梯度更新也更稳定。
  • 优化器与初始学习率:使用SGD优化器,初始学习率0.01,配合余弦退火调度。动量0.937,权重衰减0.0005。
  • 数据划分:训练集70%、验证集15%、测试集15%,划分时按“图片不共享增强版本”的方式保证数据独立性,避免增强后的同源图片同时落入训练集和验证集,否则验证集的指标会虚高。

训练过程耗时大约12小时。前30轮损失下降非常快,从初始的8左右降到3.4,之后下降变缓,到第80轮附近趋于平稳。最终验证集上的检测精度达到97.2%的mAP50,mAP50-95为84.6%,推理速度在GPU上约3毫秒一帧。从数值上看,效果似乎很不错。

3.3 第一轮结果分析:mAP很高但实际却不灵

数值漂亮不代表项目就能交付。我把训练好的模型接进一个简单的测试演示,拿新拍的包装图片做推理,很快暴露了一个问题:模型在“摆拍”条件下表现出色,但一旦遇到真实货架环境,漏检率一下子升高。尤其是冷柜里的乳制品,瓶身覆膜后标签反光严重,部分样本直接检测不到。

我复盘后发现,问题出在两个环节。首先是训练数据的分布有偏差——训练集里有大量正对拍摄、光线均匀的照片,形状上和货架实拍的差异不小;其次是部分正样本标注框本身就很“理想化”,框体紧密贴合表格,但在真实图片中,表格的边界就是残缺、模糊的,模型没见过“残缺的表格长什么样”,自然难以给出置信度较高的检测结果。

这说明评估模型不能只看mAP,还要在反映真实使用的验证集上做测试。我重新整理了一份“难度验证集”,加入大量反光、倾斜、遮挡和低光照样本,结果mAP掉到了80%以下。有了这个对照,我后来每一步优化都有了明确的验收基准,而不是继续被虚假的高分数据误导。

4. 部署落地:模型转换、推理链路与工程化细节

4.1 模型导出与推理框架选择

训练完成后,模型需要部署到实际业务链路里。这里要考虑目标平台:服务端后台、手机端App、还是边缘设备。我在该项目中先以服务端为主,后续扩展手机端。

从PyTorch训练权重导出为ONNX格式是最常见的做法。导出时需要固定输入尺寸并在端侧指定动态轴。如果目标是发布到手机端,还需要再转成对应移动端推理框架格式。整个转换过程中最容易踩坑的是自定义算子的兼容性——YOLO系列模型中的某些模块在转换时可能出现算子不支持的报错,解决办法是先用官方提供的导出脚本生成标准ONNX,再用自带的Python样例脚本验证输出结果和原始PyTorch模型是否一致。

4.2 检测到区域之后还要做什么:后续处理链路

检测模型只负责输出“营养成分表在哪”,真正交付给用户的识别结果还需要后续步骤配合。我搭了一条完整的推理链路:

第一步,目标检测。模型输出所有营养成分表的检测框、置信度和类别编号。我会根据置信度阈值过滤掉低质量框,常见阈值为0.5。 第二步,倾斜校正。因为用户拍摄的照片经常是歪的,检测框本身虽然是矩形,但表内部结构可能是斜的。我使用检测框四角坐标和OpenCV的透视变换来做校正,把营养成分表拉正,再送入OCR。这里补充一下,检测框外侧要想裁剪到完整的表格内容,需要在四个方向各向外扩展约5到8个像素,防止边框线被裁掉。第三,超分处理。如果检测区域分辨率很低,先通过轻量级超分模型把图像放大两倍,再送OCR。实测这一步对小字号的营养成分表效果明显,解析数字的错误率降低了将近一半。于是我在低分辨率场景下保留了它的开关,在分辨率足够高的图片中跳过该步骤,以节省耗时。

4.3 端侧推理调优:从3秒到0.2秒的压缩

部署初期,整条链路的耗时大约3秒一帧,完全无法满足实时体验。经过逐步优化,最终压到0.2秒左右。我做了几件关键的事:

第一,把检测模型从YOLOv11n进一步蒸馏为更轻量的版本。蒸馏听起来复杂,实际操作就是把大模型在训练集上的输出结果作为“软标签”,指导小模型学习,让压缩后的小模型精度尽量贴近大模型。第二,将输入分辨率从640x640降低到416x416。营养标签本身占整图比例不算太小,分辨率降到416后检测精度下降幅度只有不到2%,但推理速度提升非常明显。第三,写入帧间追踪逻辑。在视频流的连续帧中,利用上一帧的检测结果预测当前帧的标签位置,只在预测位置附近做短距离搜索,大幅减少全图推理次数。若连续三帧都检测不到,再触发全图重新检测。这套逻辑把平均推理时间进一步压缩了约四成。

5. 训练和部署路上的六个经典坑,以及对应的修复方案

5.1 数据泄漏:增强样本导致的虚高指标

我一开始把Mosaic增强后的样本直接随机划分,训练到后期发现验证集mAP高得出奇,甚至比测试集高出好几个百分点。后来定位到问题根源:Mosaic增强后的图片中包含原始训练图片的内容,而验证集中也存在同一批原始图片的增强版本,出现过拟合和虚高性能。

解决方案很简单:先做数据划分,再对训练集单独做增强,确保验证集使用的都是未经增强的原始图像。重新评估之后,所有指标都回归了正常范围。这是个经典坑,但也很容易被忽视。

5.2 类别定义过细反而拉低精度

这个项目里,一开始只把“营养成分表”作为一个类别,但尝试过把“配料表”也单独设一个类别,让模型同时检测两种区域。结果发现精确率上升的同时召回率下降得厉害。原因是配料表和营养成分表在视觉上极其相似,而且不同包装上的两种表格排版风格几乎没有规律。对模型来说,在这个特征空间里强行区分它们反而制造了混淆。最后把两个类别合并回一种,只在后续处理中通过OCR结果判断该区域属于配料表还是营养成分表。

5.3 软包装褶皱导致检测框回归不稳

软包装食品,例如薯片袋、面包袋,标签区域经常沿着包装袋的褶皱产生扭曲。检测框是对齐坐标轴的矩形,面对这种扭曲表体时只能框住一个相对宽松的外包范围,导致杂质背景混入。我在褶皱明显的数据上加入了局部抖动差异化增强,模拟不同褶皱走向的变化;同时在推理阶段对检测框进行二次修正,减少裁切误差。改进后,褶皱样本的检测精度回升了5个百分点左右。

5.4 小标签对象被周围高对比度区域干扰

有些营养标签存在出生理面积小的区域,比如口香糖包装、酸奶杯盖。这些表面的印刷图案又花又杂,模型经常把小标签跟包装上的装饰图案混淆。

针对这个情况,一方面,我在增强阶段把这类小目标图像做复制粘贴增强:把小标签从中裁剪出来,随机贴到其他包装图的空白处,迫使模型更多关注小目标的细节特征;另一方面,对这部分样本单独进行过采样,提高它们在训练数据中的比例。

5.5 高光和反光问题比想象中严重

反光带来的信息缺失,本质上是图像成像质量的物理问题,模型能学到的最多只是“反光边缘的语义特征”。要彻底解决,更可靠的路径是改变成像条件,比如在多角度补光的采集箱里重新拍照。在无法改变用户环境的条件下,我用了两个互补策略:一是训练阶段加入模拟高光的随机亮度斑块增强;二是在推理阶段,如果有连续帧可用,就选取高光面积最小的一帧做检测,避免把高光最严重的那一帧作为唯一输入。

5.6 背景文字干扰引发的误检

营养标签周边通常还有大量文字内容,如配料表、净含量、厂名厂址宣传语。模型学到的“表格特征”如果不够强,很容易把这些包含多条短文本的块状区域误判成营养成分表。我在训练中刻意增加了负样本数量,并且专门搜集了一批“看起来像表格但实际不是营养标签”的包装图。此外,在推理时加上一个后置过滤规则:检测区域的宽高比是否处于合理范围、内部是否具有多行文本分布的特征,不符合规则的直接丢弃。经过这两个手段的叠加,误检率从最初的9.6%降到了1.4%。

6. 项目完结后的一些经验复盘

整个项目做下来,我最深的一个体会是:目标检测项目能不能落地,模型的网络结构只占三成,另外七成都在数据和工程细节上。YOLOv11本身确实好用,训练流程、封装程度都很顺手,但真正决定交付质量的,是我在最花时间的数据采集、标注规范、评估基准上下功夫的部分。

如果让我再从头做一次,我会把“难度验证集”的构建放在数据采集阶段同步推进,而不是等到第一轮模型训练完才发现问题。这样做的好处是,每一个数据采集批次都能立刻用统一标准衡量其对模型效果的实际贡献,增强方案的效果也能被及时评估。如果各位也在做类似的表格定位、区域提取、文档检测项目,我的建议是先别急着定模型结构,把半天时间花在“把真实场景拍清楚”上,这个动作的回报远大于换任何更大更新的网络。

最后分享一个小技巧:营养标签检测框的回归质量难以直观评估时,可以在验证阶段把模型输出的检测框直接叠加在可视化调试界面上,放大到像素级别去看框线和标签边缘的贴合误差。这个观察方法没什么成本,却远比盯着mAP数字能发现问题。项目结束后,我又把这套“先定位再识别”的思路复用到其他包装信息结构化任务里。事实证明,只要检测这一层做得足够干净,下游任何解析模块的存活率都会翻倍。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进