说到餐具识别,很多人第一反应是"不就是给图片打个标签嘛"。但真正在餐饮场景里跑过一遍就知道了,碗和盘子长得像、釉面反光、汤渍遮挡、堆叠遮挡,随便一个都能让模型翻车。我以前做过一个自助餐结算台的项目,顶部架一个摄像头,识别餐盘里的餐具种类来计费,踩了不少坑才把效果稳定到可落地。这篇就以"餐具种类识别系统"为线索,把从数据整理、模型训练到服务部署的完整链路拆开讲一遍,里面所有参数和步骤都是我实测后确认可行的。
1. 项目到底解决什么问题
1.1 核心需求拆解
餐饮行业里,"餐具种类识别"听起来简单,实际对应的是两个完全不同的业务方向。
第一个方向是结算场景。无论是自助餐厅按餐盘颜色计费、按餐具数量结算,还是食堂按"碗、碟、盘、杯"分别计价,都需要在结算台上装摄像头,实时判断顾客托盘中放了几样餐具、分别是什么种类。这个场景要求的是边缘端实时推理,单次判断延迟必须控制在200毫秒以内,否则顾客排队就会堵住。
第二个方向是后厨管理。洗碗间需要统计一天洗了多少个碗、多少个盘子,用于损耗管理和采购预测。这个场景不要求实时,可以用离线批量识别,但对分类精度要求更高,因为数据会用于财务统计。
我这次分享的项目重心放在结算场景,也就是实时、边缘部署、高精度这三个关键词上。
除了技术指标,还有两个容易被忽视的需求:
- 抗干扰能力:餐饮环境的灯光复杂,餐桌反光、头顶射灯、不锈钢台面的倒影都会影响成像质量。系统必须对这些干扰有足够的鲁棒性。
- 种类可扩展:餐厅的餐具不只有几个固定款式,老板随时会采购新样式。系统不能重新训练整个模型,要支持增量学习或快捷加类。
1.2 识别对象与评测指标
项目定义了6个核心餐具类别:米饭碗、汤碗、菜盘、骨碟、水杯、汤匙。这6类覆盖了中餐场景的绝大对数需求,也包含了形状上容易混淆的"碗和碟"这对经典难题。
评测指标不能只看准确率,还要关注:
| 指标 | 说明 | 本项目目标值 |
|---|---|---|
| Top-1 准确率 | 单张图片正确分类的比例 | ≥ 97% |
| 单次推理延迟 | 从输入图像到输出结果的时间 | ≤ 150ms(边缘设备) |
| 漏检率 | 托盘中有餐具但未被识别的比例 | ≤ 3% |
| 易混淆类准确率 | 碗和碟、杯和碗之间的区分能力 | ≥ 95% |
值得注意的是,漏检率在结算场景中比准确率更关键。漏检意味着顾客白拿了一个碗却没付钱,而误判最多只是类别错了,可以人工修正。所以后文讲模型设计时,我特意在决策层加了"置信度否决"机制,宁可输出"不确定",也不允许乱猜。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据集的搭建是成败的关键
2.1 数据采集的三条铁律
我在项目里反复向前端和运营强调一句话:模型的效果上限在采集阶段就决定了。很多开发者从公开数据集里下载一批餐具图片就开训,测试效果还行,一到现场准确率就掉到八成以下——问题几乎都出在"训练数据和真实数据的分布不一致"上。
数据采集必须遵守三条铁律:
-
铁律一:采集环境必须和部署环境一致。 如果实际部署是俯拍视角(摄像头装在结算台正上方约1.2米处),那么训练数据也必须以俯拍为主,辅以少量倾斜角度做增强。如果你拿网图训练,那些侧面平视的餐具图片,在俯拍场景中几乎没有迁移价值。
-
铁律二:必须覆盖餐厅的真实光照范围。 我实测下来,同一套餐具在不同光照下拍摄,模型的表现波动很大。所以采集时间要分早、中、晚三个时段,条件允许的话还要采集开灯、关灯(自然光)两个模式。
-
铁律三:实物和图片要统一标注口径。 最简单的做法是用唯一编号给每件实物餐具贴上标签,拍照后在标注平台里把标签号和餐具类别对应。避免出现"A员工把汤碗标成米饭碗,B员工把米饭碗标成汤碗"这种标注不一致的问题。
2.2 数据增强策略
公共数据集的图片质量太干净了,真实场景里的图像噪声远不止平移旋转这么简单。我在训练时用了以下几类增强策略,实测收益很明显:
code复制# 训练时应用的数据增强
- 随机亮度调整(±40%):模拟不同时段的光照变化
- 随机HSV扰动(色相±10,饱和度±30%,明度±40%):应对不同色温的灯光
- 随机旋转(0~30度):模拟餐具摆放角度随意的情况
- 随机缩放(0.8~1.2倍):模拟不同距离下的成像尺寸差异
- 随机遮挡(随机擦除5%~15%区域):模拟菜汤、食物遮挡餐具表面的情况
- 高斯噪声(σ=0.01~0.03):模拟传感器噪声
有些增强策略看上去很美,但实际会让任务变得更难。比如CutOut和RandomErasing,如果遮挡比例太高,会把碗沿和盘子边缘破坏掉,反而让模型产生错误的形状认知。我把随机擦除的上限控制在15%,而且只在训练的后期阶段启用,避免前期的特征学习被干扰。
2.3 标注规范与质检流程
标注这件事,外包给团队做的时候必须有明确的规范文档。我项目的标注规范里定义了几个关键点:
- 单张图片中所有可见餐具都要标注,不能漏标;
- 用任意四边形框标注餐具轮廓,不要用紧密贴合形状的复杂多边形,因为推理端要用矩形框做NMS;
- 餐具被大面积遮挡(超过60%面积不可见)时,该样本直接标记为背景,不参与训练;
- 标注完成后必须有二次抽检环节,抽检比例不低于10%,抽检不一致率超过2%就退回全部重标。
这里我踩过一个坑:第一次标注时没有做二次质检,训练出来的模型对"汤碗"这类边缘圆润的类别识别率只有72%,排查后发现是标注框普遍标注不齐,有的框只框住了碗口的一半,人为引入了大量噪声。后来重新规范标注流程,准确率直接提了10个百分点。
3. 模型选型与训练方案
3.1 为什么选择了YOLOv8s
餐具识别最主流的方案是目标检测,因为它能同时完成"定位"和"分类"两件事。选型时我在YOLOv8s和更轻量的YOLOv5n之间纠结过,最终选了YOLOv8s,三个理由:
- 精度和速度的平衡点在s这个档位。 在NVIDIA Jetson Orin Nano上,YOLOv8s的FP16推理延迟大约110ms,Top-1准确率能到97.8%;YOLOv5n的延迟虽然降到70ms,但准确率只有94.2%,无法满足目标。
- YOLOv8的C2f结构在特征提取上更强。 尤其是对"碗和碟"这类轮廓相似的类别,更深的C2f模块能学到更多细粒度特征,例如碗沿的圆弧曲率差异。
- 后续迭代方便。 YOLOv8的架构更利于蒸馏量化,在压缩后精度损失比v5小。
如果是做纯分类需求(不需要定位),也可以换成轻量级图像分类网络如MobileNetV4或EfficientNet-Lite,但实际项目中,结算场景必须定位,因为要判断"托盘里有没有这个餐具",而不是"整张图中是否包含餐具"。
3.2 训练流程与核心参数
训练流程分为三个阶段:预训练权重加载、冻结骨干微调、全量微调。
python复制# 数据集配置(data.yaml)
train: ./datasets/train/images
val: ./datasets/val/images
nc: 6
names:
0: rice_bowl
1: soup_bowl
2: dish
3: bone_plate
4: cup
5: spoon
以下是实测推荐的关键超参组合:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| image size | 640x640 | 兼顾细节和性能;低于480时小目标(汤匙)检测率明显下降 |
| batch size | 16 | 显存8GB左右的设备上限 |
| epochs | 200 | 第150个epoch后开始使用CosineAnnealingLR |
| optimizer | SGD(momentum=0.937) | 相比AdamW,SGD在检测头上泛化效果更稳 |
| lr0 | 0.01 | 冻结阶段建议设0.005 |
| weight_decay | 0.0005 | 防止对背景区域过拟合 |
| close_mosaic | 10 | 最后10个epoch关闭Mosaic增强,让模型适应真实分布 |
| fliplr | 0.5 | 水平翻转,注意不能开flipud,托盘里餐具没有上下翻转的场景 |
特别说明一下close_mosaic=10这个参数。YOLOv8默认全程开启Mosaic增强,即把四张图拼成一张训练,能有效提升泛化能力。但在训练末尾如果还开Mosaic,模型会学到"图片边缘总有拼接缝"的伪特征,我在实际项目中将最后10个epoch的Mosaic关闭,让模型回归真实图像的分布,mAP提升了约1.2%。
3.3 难例挖掘与类别均衡策略
餐具识别最大的难点是难例数量分布不均。采集数据中,米饭碗和菜盘的样本量远大于汤匙和水杯,若直接训练,模型会对高频类别过拟合。
我用了两个手段:
- Focal Loss系数调整:YOLOv8默认用BCEWithLogitsLoss,我将正样本的Focal系数γ从默认0调整为1.5,在难例(如半遮挡的碗)上明显更敏感,代价是训练收敛速度稍微变慢。
- Copy-Paste增强:将汤匙、水杯等低频类别的实例随机粘贴到其他训练图上,同时适当缩小粘贴比例(控制在0.3左右),避免伪造过多不合理场景。
从实验数据看,在加入Copy-Paste增强后,汤匙的AP从87.6%提高到93.4%。
3.4 蒸馏与量化部署
边缘设备上还需要进一步瘦身。直接部署FP32的YOLOv8s权重约21MB,在Jetson上跑FP16没问题,但如果目标是低成本设备(例如用树莓派或者RK3588),我建议分两步:
- FP16量化:在验证集上做PTQ(训练后量化),精度损失通常在0.3%以内。
- INT8量化:配合蒸馏。先让FP16模型作为teacher,训练一个FP32量化的student模型——或者直接拿原始的YOLOv8s当作teacher,用蒸馏损失加上原始检测损失,联合蒸馏两个epoch,INT8精度可以保持在96.5%以上,但是模型体积压缩到4MB,推理速度提升2.3倍。
整条链路的模型部署流程是:PyTorch → ONNX → TensorRT/OpenVINO → 边缘设备。这个流程虽然是常规路径,但在导出时需要留意检测头的输出结构,YOLOv8的ONNX导出要额外指定opset=12以上,并把dynamic的batch维度打开,否则后续TensorRT的batch推理会受限制。
4. 识别服务落地:从模型到可用的系统
4.1 推理服务架构设计
检测模型本身只是"发动机",完整的餐具识别系统还包括很多外围工程。我实际搭建的服务架构如下:
code复制[摄像头采集] → [图像预处理] → [目标检测模型推理] → [后处理逻辑] → [业务对接]
↑ |
└──────────── 帧缓存队列 ← 防抖逻辑 ←───────────────────────┘
关键点在于防抖和去重。结算场景中摄像头是连续运行的,顾客放一个碗到结算台,绘本上会产生多帧连续画面。如果每一帧都触发计费,同一个碗会被识别三次、扣三份钱。所以在业务层必须做:
- 目标稳定跟踪:对检测框做IoU匹配,如果同一个检测框在连续N帧(N=5)中都对应同一个类别且中心点位移小于阈值,才判定为“出现”;
- 计费状态机:每个托盘区域维护一个“空闲→识别中→已结算→已离开”的状态机,只有从“识别中”切换到“已结算”时才真正调用计费接口。
4.2 低延迟部署的六大优化
在Jetson Orin Nano上运行时,我实测默认配置的推理延迟约280ms,通过以下六项优化降到128ms:
| 优化项 | 收益 |
|---|---|
| TensorRT FP16引擎 | 降低约65ms |
| 输入分辨率保持640×640 | 不做任意缩放,用letterbox填充,避免额外resize耗时 |
| 使用CUDA加速预处理 | 将BGR通道转换、归一化合入GPU流水线 |
| 开启batch推理(batch=2) | 两个托盘同时结算时延迟提升不大,吞吐翻倍 |
| 关闭系统GUI和后台日志 | 降低CPU争抢 |
| 显存锁页(pinned memory) | 减少H2D拷贝时间约8ms |
其中"CUDA加速预处理"容易被忽略。如果使用Python绑定里的普通OpenCV做resize和归一化,每帧大约消耗25ms,这对边缘设备是很大开销。我用OpenCV的CUDA模块将预处理放到GPU执行,延迟直接从153ms降到128ms。
4.3 快速加新类别的增量策略
餐饮老板随时可能采购新餐具,重新训练整个模型周期长、成本高。我维护了一套"加类流程":
- 新餐具放到固定采集台,自动拍摄200张图片(覆盖四角度、三光照);
- 用现有模型对图片做初步标注,人工修正错误框;
- 只训练新增类别+所有原类别数据(防止灾难性遗忘,会让模型重新回放旧数据,比例约1:2);
- 微调40个epoch后,验证集上新增类别AP≥95%并且原类别AP下降不超过1%,才允许上线。
这个流程的代价是:每次加类约需额外训练30分钟(在单块消费级显卡上),但好处是老板不用等几天。
4.4 硬件选型与成本控制
| 部署场景 | 推荐硬件 | 说明 |
|---|---|---|
| 大型食堂 | Jetson Orin NX 16GB | 同时接4路摄像头,单路延迟<200ms |
| 中小餐厅 | Jetson Orin Nano 8GB | 单路或双路摄像头,性价比高 |
| 极低成本方案 | RK3588开发板 | INT8量化后延迟约180ms,功耗低但精度略降低 |
关于功率,Jetson Orin Nano在满载跑模型时约7W-15W,我建议配一个24W以上的DC电源,并且不要和电视屏幕共用同一路电,否则启动峰值电流不稳定会导致设备异常重启。这看起来是小事,但我在现场调试时遇到至少三次"系统莫名其妙重启"的问题,最终都查到了供电上。
5. 实战踩坑经验与排查技巧
5.1 高反光场景下的识别失效
第一次现场测试时,纯白瓷碗在食堂顶灯下反光严重,检测框一直跳来跳去,偶尔漏检。排查后发现问题出在灰度化预处理:我为了提升速度,在预处理阶段直接做了灰度转换,但白瓷碗在白光下高光区域的灰度值接近255,边缘信息完全丢失。
解决方法有两个,我实际用了第一个:
- 保留RGB通道,去除灰度统一化。模型在RGB模式下能利用颜色信息抑制高光干扰。
- 在采集端增加偏振片。成本低,但要和灯具厂商联合调试偏振角度,否则效果不稳定。
5.2 碗碟混淆的专项优化
即使整体准确率做到97.8%,"碗"和"碟"之间仍然存在约5%的互相错认。原因是俯拍视角下,碟子边缘的弧形和碗口几乎一致。
我做了两件事优化:
- 在数据集中增加一个"俯拍+侧拍"双视角关联逻辑,即在模型输出类别后,结合图像中检测框的长宽比做几何约束:若框的长宽比大于1.6倍,则大概率是碟子而不是碗。给碗和碟的类别分别设定不同的长宽比先验值。
- 后处理时加入类别感知的置信度修正:当模型输出
碗和碟两种候选且置信度差距小于0.1时,强行采用"更扁的形状优先为碟"的规则。
这两招加起来,碗碟混淆率从5.1%降低到1.8%。
5.3 推理进程被杀掉之内存泄漏
项目上线一周后,推理进程每6小时被杀一次。排查时先用dmesg看内核日志,发现是OOM Killer把进程干掉了。进一步查/proc/PID/status的VmRSS,发现内存占用随运行时间线性增长。
内存泄漏的根源是拼接帧队列没有做上限控制。当摄像头输入帧率高于推理速度时,队列不断堆积,每个元素里还包含了原始图像数组和检测结果副本。我在队列上加了两层保护:
- 队列最大长度设为4,超过时丢弃最旧帧;
- 每处理完一帧立即清理中间变量,显式触发
torch.cuda.empty_cache()的频度控制在每20帧调用一次(每次调用会带来约几毫秒的额外开销,太频繁反而影响性能)。
修复之后,连续运行72小时,内存占用稳定在1.6GB左右,不再增长。
5.4 摄像头画面偏色导致误判
某种特定灯光下,摄像头拍出来的白瓷碗带了淡淡的青色调,模型会把米饭碗识别成汤碗。排查后发现是摄像头自动白平衡(AWB)引起的。
解决方案是锁定摄像头的AWB和AE:
code复制# Camera settings
AE: off (固定曝光时间1/125s)
AWB: off (固定色温5200K)
Gamma: 1.0
Saturation: 55
这里有个细节:固定AWB后,早晨自然光和傍晚暖色灯光条件下画面会偏色,因此需要做一次"动态色彩校准"。我实现了一个简单的校准程序:在结算台固定位置放置标准灰卡,每30分钟捕获一次画面,计算灰卡的RGB均值,将偏差值作为增益系数补偿到图像上。这个方案成本低、效果比重新训模型靠谱得多。
5.5 常见问题速查表
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
| 检测框抖动 | 低置信度阈值设为40% | 阈值调到55%~65% |
| 汤匙完全漏检 | 目标太小(< 24×24像素) | 增大输入分辨率至768,或改进切片推理 |
| 晚上灯少时准确率骤降 | 光照分布不足 | 采集夜晚数据,或开启红外补光 |
| 同一种餐具检测出两个框 | NMS阈值过低 | IoU阈值设为0.5,置信度阈值调高 |
| 模型输出类别全为背景 | 加载权重时锚框配置错误 | 检查yaml文件的anchor设定 |
排查这类问题时,优先看可视化检测结果而不是只看指标曲线。我在项目的调试工具里加了一个"检测可视化面板",显示每帧的检测框、置信度、类别和重叠IoU,配合控制台日志一起看,定位问题效率能提升很多。
6. 从模型到产品化的一点心得
回看这个项目,最深的体会是:餐具识别系统的难点不在模型结构,而在工程化过程中对细节的把握。数据分布、光照一致性、部署硬件适配、后处理业务逻辑,每一个环节都直接决定最终效果。
另一个很现实的经验是,指望"开箱即用、一个模型解决所有餐厅"是不可能的。不同餐厅的桌布颜色、灯光布置、餐具品牌都会改变图像分布。所以我把服务端做成"多餐厅多模型"的架构,每个餐厅用同一套代码,但独立训练一个专用模型,通过接口中的"餐厅ID"路由到对应模型。这套架构上线后,新店接入的成本从一周缩短到两天。
最后再分享一个小技巧。在模型训练时,我通常在最后的embedding层输出后接一个额外的Class-Balanced Loss分支(用于弱势类别的重加权),和主检测头联合训练。这原本是我在另一个视觉项目中偶然发现的技巧,但在餐具识别里同样有效。它带来的提升不算夸张——弱势类别AP平均上涨3%左右,但不需要增加推理时的计算量,属于典型的 "免费午餐"。如果你的项目里有样本分布不均衡的问题,不妨也试试。
这套系统的搭建,我从采集设备选型到最终上线,前后大约用了六周,其中数据收集和标注就占了三周半。看完整篇文章,相信你对从零搭建一个餐具识别系统已经有了完整的思路。如果照着这套流程走一遍,多数坑都能提前避开。
