做零售柜商品检测这个项目,表面上看是“调一个YOLO模型跑起来”那么简单,真正动手之后才发现,从数据采集、标注策略、模型选型到桌面端界面集成,每一环都是坑。这篇就把我实际搭建这套基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的零售柜商品检测识别系统的完整过程拆开讲,包括为什么同时支持四个版本、数据集怎么做才不容易翻车、训练参数怎么调、PySide6界面和推理线程怎么整合,以及调试阶段那些让人头疼的问题和对应的排查思路。无论你是要做毕业设计、竞赛方案,还是真想落地一个智能货柜Demo,这份经验应该都能帮你少走不少弯路。1. 整体方案设计与模型选型思路1.1 为什么选择多版本YOLO框架接到零售柜商品检测这个需求时,我第一反应不是直接锁定某一个模型版本,而是先想清楚“这套系统要跑在什么环境里”。零售柜的应用场景很杂,有的柜子用的是嵌入式工控机,显卡老、显存小;有的柜子直接接PC,显卡稍微好一点;还有的只是先做原型验证,后面才考虑换硬件。如果一开始就把代码死死绑在YOLOv8上,后面换环境时就只能重新改代码。所以我最后决定做一套兼容YOLOv5、YOLOv6、YOLOv7、YOLOv8四个版本的系统。这四个版本虽然网络结构有差异,但核心思路同源,数据接口和训练流程的共性也很多,统一封装之后切换成本很低。多版本兼容还有一个实际好处:不同版本在相同硬件上的表现侧重点不同。YOLOv5s在低显存设备上跑得快,适合老工控机;YOLOv7-tiny的精度和速度平衡得不错;YOLOv8的默认配置在精度上通常更好,适合硬件条件允许的场景。实际部署时,我会先跑一遍四个版本的验证集指标,再结合目标硬件的推理速度,选出最合适的一个。1.2 YOLOv5到YOLOv8的核心差异对比在项目初期,我花了些时间把四个版本的关键结构差异整理出来,方便后面做版本迁移时心里有底。这里直接列一个精简对照表:版本Backbone核心模块Head设计Anchor策略主要改进点YOLOv5CSPDarknet + C3模块耦合检测头Anchor-Based(基于锚框)工程成熟、生态完善、部署案例最多YOLOv6CSPDarknet改进 + RepVGG解耦头Anchor-Based重参数化结构利于推理加速YOLOv7CSPDarknet + E-ELAN耦合检测头Anchor-Based训练技巧丰富,实时性优秀YOLOv8CSPDarknet + C2f模块解耦头Anchor-Free(无锚框)自适应样本分配,精度和泛化更好这个对比表不是让新手背参数的,而是告诉你换版本之后要改哪些东西。最直观的一个差异是YOLOv5和YOLOv7还用Anchor-Based策略,而YOLOv8改成了Anchor-Free。这两种方式在训练代码里的差异非常大,YOLOv8不再需要预先计算锚框尺寸,超参里少了一组anchor相关的配置,所以在做多版本适配时,模型配置文件不能直接混用,我每个版本都单独维护了一份参数文件。1.3 几个版本如何做到无缝切换在代码层面,我做了一个统一的模型加载接口,对外暴露的推理方法固定为detect(frame),不同版本的底层实现通过配置项切换。比如模型权重文件路径、类别名称列表、输入尺寸、置信度阈值和NMS阈值作为统一参数传入,内部再根据版本类型走不同的预处理和后处理流程。用一句话概括这个设计思路:把变化的部分(网络结构、预处理细节、输出格式)封装在插件层,让上层界面代码完全不需要关心当前跑的是哪个版本。这样做最大的好处是,界面部分只写一遍,后面换模型版本时不需要动UI代码。2. 零售柜商品数据集的生产与预处理2.1 数据采集场景分析与标注工具选择模型能识别的上限,其实在数据阶段就被决定了。零售柜的拍摄场景有几个典型特点:摄像头固定在柜内、货架层板会遮挡商品、瓶子罐子的反光严重、商品密集排列且外观相似。我在采集数据时,没有只拍正对着货架的“标准照”,而是刻意模拟了真实用户的开柜拿取动作,比如斜着取货、局部遮挡、手部入镜、不同光线强度下的画面。标注工具我选用Labelme,它生成的JSON格式信息完整,方便后期二次处理。为了兼顾标注效率,我另外写了一个批量辅助标注脚本,对同一类商品历史帧做比对,只标注新增或位置变化的目标,而不是每次都从头框一遍。标注时最需要注意的一点是“用一整套固定规则约束所有人”。我见过的项目翻车,很大一部分原因是不同标注人员的框选习惯差异太大,有的喜欢贴边,有的习惯多留白,导致同一个商品在不同样本上的标注框偏移明显。后来我规定了一套标准:矩形框贴合商品外轮廓,最大误差不超过一个像素;遮挡超过一半的目标不标;反光导致轮廓模糊的目标不标。2.2 数据格式转换与目录组织Labelme标出来的JSON不能直接喂给YOLO训练,需要转成YOLO格式的txt文件。YOLO格式每行是“类别ID 中心点x 中心点y 宽度w 高度h”,全部用相对于图片宽高的归一化坐标表示。转换脚本的逻辑很简单,核心是解析JSON中每个多边形的外接矩形,再除以图片宽高。数据集的目录结构我是按YOLO规范组织的,分为images/train、images/val、labels/train、labels/val四个目录。这里有一个经常被忽略的细节:图片和标签的名称必须完全一致,后缀可以不同,但主文件名不能有差异。我在脚本里加了自动校验,训练前先扫描一遍,找出没有对应标签的图片,防止训练时出现“退学警告”。2.3 数据增强策略不能生搬硬套YOLO官方训练默认会开启Mosaic数据增强,把四张图拼成一张,对小目标检测确实很有帮助。但我发现一个坑:零售柜场景中,多个目标本来就密集排列,Mosaic增强后目标变得更小、更密集,训练初期的loss下降速度会变慢,甚至出现不收敛的假象。我的调整方法是分阶段增强:前30个epoch开启Mosaic、MixUp等强增强,让模型见足够多的“复杂组合”;后面切到轻量增强,只用HSV扰动、随机翻转和轻微尺度变化,让模型在接近真实分布的样本上做精细拟合。同时关闭了随机90度旋转,因为零售柜里商品的方向基本是固定的,瓶子不会倒着放,过头的数据增强反而制造了真实场景中不存在的样本。另外,负样本不能少。零售柜里经常有空货位、空白层板、员工手部等背景,我在数据集中专门加入了一定比例的不含任何商品的图片,帮助模型降低误检率。这一点很多人会漏掉,但落地时它对误报的影响非常明显。3. 模型训练配置与调参实战3.1 从YOLOv8s开始的基线配置我以YOLOv8s作为基线模型,原因很直接:它的精度和显存占用比较平衡,适合作为对比基准。后续再对比YOLOv5s、YOLOv6s、YOLOv7-tiny时,用同一份数据集和相同的训练轮次,才有横向可比性。基线训练参数可以参考如下配置:# 以ultralytics YOLOv8为示例 yolo detect train \ data=retail_cabinet.yaml \ model=yolov8s.pt \ epochs=200 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ momentum=0.937 \ weight_decay=0.0005 \ optimizer=AdamW这个配置里,imgsz选640是经过权衡的。零售柜里很多商品宽度只占图片的百分之几,太小了特征不明显;太大又拖慢训练速度,显存占用直线上升。如果目标是嵌入式设备,建议直接用512或者416训练,部署时保持一致的分辨率,不要训练时用640、部署时用416,那种“训练与部署输入不一致”会让精度明显掉一截。3.2 训练过程中的指标观察方法我训练时不是只盯着最后的mAP看,而是同时记录三个维度的变化:训练集loss、验证集mAP50和mAP50-95。loss持续下降且验证集mAP同步上升,说明模型在正常学习;如果训练loss还在降,但验证集mAP已经徘徊不动甚至下降,基本可以判断是过拟合了,这时要加大数据增强强度,或者提前用早停。mAP50和mAP50-95的区别值得新手重视。mAP50只要求预测框和真实框的IoU大于0.5就算命中,相对宽松;mAP50-95则是对0.5到0.95的多个IoU阈值做平均,要求更严格。零售柜场景中,商品定位太粗糙会造成计费错误,所以我在汇报效果时既看mAP50,更看重mAP50-95,两个指标一高一低就说明“框的位置还不够精准”。每轮训练完后,我会单独生成一张“Bad Case”图,把验证集中漏检、误检、定位偏差大的图片拼接展示。看bad case比看指标更能暴露问题,比如某个SKU总是漏检,那大概率是训练样本太少或者标注框不统一;反光区域总是多出一个框,那就在数据增强里加入更强的亮度扰动。3.3 超参数调整的几条实践经验调参这块我采用的是“先粗后细”的思路,避免一上来就陷入局部搜索。首轮只调学习率策略,下一步调输入尺寸和训练轮次,最后才动损失函数相关的参数。YOLOv8里有一个分类损失权重和框回归损失权重的比例,这个比例影响“框得准”和“分得对”两者的平衡,零售柜场景我倾向于稍微提高框回归权重,因为算错数量比分类稍微模糊一点更致命。标签平滑我建议保留,默认的0.0005就够了,不要为了追求训练集指标好看把它关掉。Anchor-Based版本里有一个anchor自动匹配机制,开启之后模型会根据数据分布自动调整锚框尺寸,这组参数对YOLOv5和YOLOv7的收敛速度影响很大,建议始终开启。还有一个很多教程不会提的细节:如果精心挑选了同一型号的饮料瓶,但在训练时发现“可乐”和“雪碧”容易被混淆,不要急着加数据,先把这两类样本的标注检查一遍。很多时候是标注人员自己都分不清,或者某几张图的反光让罐体颜色发生了偏移。把这类样本单独挑出来做色彩归一化处理,往往比盲目增加样本量更有效。4. 基于PySide6的桌面系统界面实现4.1 界面功能布局与交互设计这套系统的界面不是简单放一个视频画面加一个按钮就完事,我参考了实际零售柜管理后台的操作习惯,把界面分成四个核心区域:左侧是实时视频预览区,显示摄像头画面和检测框叠加结果;右侧上方是识别结果列表,展示当前画面中每个商品的类别、数量和置信度;右侧下方是数据统计面板,汇总整柜库存变化;底部工具栏则放置摄像头选择、模型版本切换、置信度阈值调节滑条和日志输出窗口。界面框架用PySide6实现,它是Qt官方Python绑定的现代版本,API设计比PyQt5更清晰。窗口布局用QVBoxLayout和QHBoxLayout组合,视频显示控件用QLabel更新QImage实现。置信度阈值滑条是整个界面里最“救命”的设计。调试阶段不同场景对置信度的要求完全不一样,反光严重时阈值要拉高到0.45以上,光线均匀时可以放到0.25。如果把这个参数写死在代码里,每次调参都要重启程序,效率太低了。4.2 推理线程与界面线程的分离PySide6界面最忌讳在网络推理这类耗时操作上阻塞主线程。如果直接在按钮事件里跑模型推理,界面会立刻卡死,拖动窗口都成问题。我在这里使用了QThread加信号槽机制,把推理循环放在独立线程中,界面主线程只负责接收信号并更新画面。核心步骤如下:自定义DetectThread继承自QThread,在run()方法里循环读取视频帧。每推理一帧后,把检测结果打包成自定义数据结构,通过pyqtSignal发送给主界面。主界面只负责绘制检测框和更新表格,不做任何计算。线程停止使用标志位控制,保证退出时不卡界面。这里有一个容易忽略的内存问题:视频帧的传递如果每次都复制一份完整图像,在高分辨率下内存消耗会非常大。我的做法是在线程内部完成必要的检测框绘制,只把绘制好的图像和检测列表发送给界面。这样主线程不需要再访问原图,也避免了两线程同时读写同一份图像数据造成竞态。4.3 模型加载与后处理细节模型推理前的预处理我使用的是letterbox方式,把原始图像按比例缩放并填充灰色边条,统一到训练时的输入尺寸,避免直接拉伸导致目标形变。做完一次推理后,从模型的输出中解析出检测框坐标、类别ID和置信度,先按置信度阈值过滤低质量结果,再做NMS(非极大值抑制)消除同一目标上的重复框。不同版本的输出结构格式不完全相同,通常包含(batch, num_anchors, 4+1+num_classes)的信息维度。这块必须在自己电脑上跑一行打印确认,别凭经验直接索引切片,否则最容易出“尺寸不匹配”的运行时错误。每个商品类别我给配置了固定的显示颜色,同类商品的检测框颜色统一,方便肉眼快速区分。数量统计直接基于检测框计数,但如果同一帧画面里同时出现前后两排同款商品,模型可能需要重复计数,我给检测结果增加了一个简单的去重逻辑:通过IoU判断,同类别且距离过近的检测框视为同一商品实例。4.4 模型切换与推理速度平衡在界面上实现YOLOv5到YOLOv8的切换,我用的是一个下拉选择框,选择后重新加载对应版本的模型文件。模型加载是个耗时操作,如果放在主线程里同样会卡界面,所以我单独做了一个ModelManager类,内部用字典缓存多个已加载模型,切换时尽可能复用已有实例。实际部署时,模型切换一次大约需要1到3秒,对于不频繁切换的零售柜场景完全可接受。但如果要做实时切换,最好还是先加载好模型再切换,或者做异步加载加进度提示。考虑到零售柜的检测精度要求,帧率能稳定在20FPS以上就够用,因为用户拿取动作本身是慢速的,一秒能刷新10次画面已经能清晰观察整个识别过程了。5. 训练与部署过程中的高频问题排查5.1 环境安装与CUDA兼容问题YOLO系列对Python版本和PyTorch版本比较敏感,最容易出现的问题是torch.cuda.is_available()返回False。遇到这种问题,不要急着重装CUDA,先把PyTorch、CUDA Toolkit、显卡驱动的版本对齐。我的经验是直接用PyTorch官方提供的匹配版本组合,比如PyTorch 2.x配CUDA 11.8,通常能减少很多不必要的麻烦。如果在CPU和GPU混合环境下调试,还有一个容易踩的坑:模型加载后第一次推理特别慢,后面才恢复正常。这是因为CUDA在做运行时初始化,缓存了kernel,属于正常现象,不代表代码有问题。显存不足是另一类高频报错。零售柜数据集图片普遍较大,如果训练时batch设置过大,很容易直接OOM。解决思路有两个:先用batch=8跑通训练流程,再逐步增大batch;或者开启梯度累积模拟更大batch的效果。部署阶段如果显存不足,最简单的办法是降低输入分辨率,或者换用轻量模型如YOLOv5s和YOLOv8n。5.2 检测效果不好的排查顺序检测效果不好时,先别急着调参,我建议按固定顺序排查:数据集质量、模型容量、输入分辨率、后处理参数。数据集的样本均衡性问题往往比模型结构更致命,零售柜场景里如果某个SKU只有几十张图,另一个SKU有上千张,模型自然会偏向多数据的类别。误检多的时候,先查置信度阈值是不是设得太低。置信度阈值一般不低于0.25,如果调到0.1,背景里稍微像瓶盖的东西都会被框出来。漏检多的时候,先查输入分辨率是不是太小,再用bad case图对比训练图片和实时画面的光照差异,若差异大,优先考虑在实时侧做图像增强而不是盲目加数据。5.3 PySide6界面运行时的典型异常界面上最常见的异常是QThread: Destroyed while thread is still running,这通常是因为程序退出时线程没有正确停止。我的处理办法是在界面关闭事件里设置停止标志并调用wait()等待线程安全退出,再销毁窗口组件。另一个常见问题是视频画面显示偏绿或偏色,原因是OpenCV读入的是BGR彩色图片,而Qt显示需要RGB格式。转换时务必用cv2.cvtColor(frame, cv2.COLOR_BGR2RGB),别漏这一步。虽然是小问题,但它能让很多人在排查上浪费半天功夫。5.4 模型训练不收敛的快速定位方法训练不收敛有几个常见诱因:学习率过大、数据集标注错误比例过高、类别间样本极度不均衡。我的排查方法是先做一个“过拟合单批数据”的快速实验:只用一小批数据训练几十个epoch,如果loss能压到很低,说明模型和数据链路没有大问题,问题出在训练策略;如果一小批数据都无法过拟合,那很可能代码逻辑或者数据管线有问题。这个经验帮我省了很多时间,建议你在大规模训练之前花半小时做一次这个验证。另外,权重初始化方式在YOLO的不同版本里也有差异,从官方预训练权重开始训练,而不是随机初始化,通常能更快收敛且最终精度更高。为此我特意在文档里强调了“预训练权重下载地址”这一项,项目源码中也包含了下载指引,避免新手因为权重缺失而无法复现。6. 系统集成与后续扩展建议6.1 与硬件摄像头对接的几种方式零售柜摄像头大致分两类:USB摄像头和网络摄像头。USB摄像头用OpenCV的VideoCapture读取即可,但要注意在长时间运行下可能出现掉帧或者设备丢失,需要加一个摄像头重连机制。网络摄像头一般走RTSP流,OpenCV也支持,但延迟比较高,需要调低缓冲或者使用专门的低延迟拉流方案。在系统源码中,我预留了CameraManager模块,统一封装以上两种读取方式,上层界面只调用read_frame()方法。后续如果要换成SDK相机,只需要在这个模块里增加对应实现。6.2 模型导出与嵌入式部署如果要把训练好的模型部署到嵌入式设备,比如瑞芯微或地平线之类的边缘芯片,需要先把PyTorch模型导出为ONNX再转换。导出时有一个关键细节:固定输入尺寸,避免动态尺寸导致转换失败或推理性能下降。导出ONNX的简单命令如下:yolo export model=runs/train/exp/weights/best.pt format=onnx imgsz=640 opset=12导出之后一定要用自己的测试图片在ONNX Runtime上跑一遍推理,确认输出和PyTorch一致。很多模型训练精度很高,但导出后掉点严重,就是因为预处理或输出后处理没有对齐。6.3 从检测到计费的系统闭环思考商品检测只是零售柜系统中的一环,真正的业务闭环还包括:识别结果入库、库存变化记录、异常事件上报、用户订单生成。我在本项目中把检测结果统一封装成标准JSON消息,定时写入本地SQLite数据库,为后续对接计费系统打好基础。这套设计的好处是,检测部分可以独立迭代,不用频繁改动界面和数据库逻辑。如果你后续要做多柜管理,只需要把检测结果通过MQTT或HTTP上报到服务端,本机的SQLite作为离线兜底,不影响基础功能。我个人在实际项目里最深的一点体会是:零售柜商品检测的难点从来不只是模型精度,而是数据、界面和业务逻辑的整体配合。很多人在模型上死磕,最后发现实际落地时数据才是最贵的成本。所以如果你时间有限,优先把数据采集和标注规范做扎实,再跑训练,这样的性价比远高于反复调参。最后再分享一个小技巧:在界面里放一个“保存当前帧”按钮,调试阶段随手截图并自动带上原始时间戳。看起来不起眼,但它能帮你积累大量真实场景样本,后续做模型迭代时,这些图就是最有说服力的训练素材。
做零售柜商品检测这个项目,表面上看是“调一个YOLO模型跑起来”那么简单,真正动手之后才发现,从数据采集、标注策略、模型选型到桌面端界面集成,每一环都是坑。这篇就把我实际搭建这套基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的零售柜商品检测识别系统的完整过程拆开讲,包括为什么同时支持四个版本、数据集怎么做才不容易翻车、训练参数怎么调、PySide6界面和推理线程怎么整合,以及调试阶段那些让人头疼的问题和对应的排查思路。无论你是要做毕业设计、竞赛方案,还是真想落地一个智能货柜Demo,这份经验应该都能帮你少走不少弯路。1. 整体方案设计与模型选型思路1.1 为什么选择多版本YOLO框架接到零售柜商品检测这个需求时,我第一反应不是直接锁定某一个模型版本,而是先想清楚“这套系统要跑在什么环境里”。零售柜的应用场景很杂,有的柜子用的是嵌入式工控机,显卡老、显存小;有的柜子直接接PC,显卡稍微好一点;还有的只是先做原型验证,后面才考虑换硬件。如果一开始就把代码死死绑在YOLOv8上,后面换环境时就只能重新改代码。所以我最后决定做一套兼容YOLOv5、YOLOv6、YOLOv7、YOLOv8四个版本的系统。这四个版本虽然网络结构有差异,但核心思路同源,数据接口和训练流程的共性也很多,统一封装之后切换成本很低。多版本兼容还有一个实际好处:不同版本在相同硬件上的表现侧重点不同。YOLOv5s在低显存设备上跑得快,适合老工控机;YOLOv7-tiny的精度和速度平衡得不错;YOLOv8的默认配置在精度上通常更好,适合硬件条件允许的场景。实际部署时,我会先跑一遍四个版本的验证集指标,再结合目标硬件的推理速度,选出最合适的一个。1.2 YOLOv5到YOLOv8的核心差异对比在项目初期,我花了些时间把四个版本的关键结构差异整理出来,方便后面做版本迁移时心里有底。这里直接列一个精简对照表:版本Backbone核心模块Head设计Anchor策略主要改进点YOLOv5CSPDarknet + C3模块耦合检测头Anchor-Based(基于锚框)工程成熟、生态完善、部署案例最多YOLOv6CSPDarknet改进 + RepVGG解耦头Anchor-Based重参数化结构利于推理加速YOLOv7CSPDarknet + E-ELAN耦合检测头Anchor-Based训练技巧丰富,实时性优秀YOLOv8CSPDarknet + C2f模块解耦头Anchor-Free(无锚框)自适应样本分配,精度和泛化更好这个对比表不是让新手背参数的,而是告诉你换版本之后要改哪些东西。最直观的一个差异是YOLOv5和YOLOv7还用Anchor-Based策略,而YOLOv8改成了Anchor-Free。这两种方式在训练代码里的差异非常大,YOLOv8不再需要预先计算锚框尺寸,超参里少了一组anchor相关的配置,所以在做多版本适配时,模型配置文件不能直接混用,我每个版本都单独维护了一份参数文件。1.3 几个版本如何做到无缝切换在代码层面,我做了一个统一的模型加载接口,对外暴露的推理方法固定为detect(frame),不同版本的底层实现通过配置项切换。比如模型权重文件路径、类别名称列表、输入尺寸、置信度阈值和NMS阈值作为统一参数传入,内部再根据版本类型走不同的预处理和后处理流程。用一句话概括这个设计思路:把变化的部分(网络结构、预处理细节、输出格式)封装在插件层,让上层界面代码完全不需要关心当前跑的是哪个版本。这样做最大的好处是,界面部分只写一遍,后面换模型版本时不需要动UI代码。2. 零售柜商品数据集的生产与预处理2.1 数据采集场景分析与标注工具选择模型能识别的上限,其实在数据阶段就被决定了。零售柜的拍摄场景有几个典型特点:摄像头固定在柜内、货架层板会遮挡商品、瓶子罐子的反光严重、商品密集排列且外观相似。我在采集数据时,没有只拍正对着货架的“标准照”,而是刻意模拟了真实用户的开柜拿取动作,比如斜着取货、局部遮挡、手部入镜、不同光线强度下的画面。标注工具我选用Labelme,它生成的JSON格式信息完整,方便后期二次处理。为了兼顾标注效率,我另外写了一个批量辅助标注脚本,对同一类商品历史帧做比对,只标注新增或位置变化的目标,而不是每次都从头框一遍。标注时最需要注意的一点是“用一整套固定规则约束所有人”。我见过的项目翻车,很大一部分原因是不同标注人员的框选习惯差异太大,有的喜欢贴边,有的习惯多留白,导致同一个商品在不同样本上的标注框偏移明显。后来我规定了一套标准:矩形框贴合商品外轮廓,最大误差不超过一个像素;遮挡超过一半的目标不标;反光导致轮廓模糊的目标不标。2.2 数据格式转换与目录组织Labelme标出来的JSON不能直接喂给YOLO训练,需要转成YOLO格式的txt文件。YOLO格式每行是“类别ID 中心点x 中心点y 宽度w 高度h”,全部用相对于图片宽高的归一化坐标表示。转换脚本的逻辑很简单,核心是解析JSON中每个多边形的外接矩形,再除以图片宽高。数据集的目录结构我是按YOLO规范组织的,分为images/train、images/val、labels/train、labels/val四个目录。这里有一个经常被忽略的细节:图片和标签的名称必须完全一致,后缀可以不同,但主文件名不能有差异。我在脚本里加了自动校验,训练前先扫描一遍,找出没有对应标签的图片,防止训练时出现“退学警告”。2.3 数据增强策略不能生搬硬套YOLO官方训练默认会开启Mosaic数据增强,把四张图拼成一张,对小目标检测确实很有帮助。但我发现一个坑:零售柜场景中,多个目标本来就密集排列,Mosaic增强后目标变得更小、更密集,训练初期的loss下降速度会变慢,甚至出现不收敛的假象。我的调整方法是分阶段增强:前30个epoch开启Mosaic、MixUp等强增强,让模型见足够多的“复杂组合”;后面切到轻量增强,只用HSV扰动、随机翻转和轻微尺度变化,让模型在接近真实分布的样本上做精细拟合。同时关闭了随机90度旋转,因为零售柜里商品的方向基本是固定的,瓶子不会倒着放,过头的数据增强反而制造了真实场景中不存在的样本。另外,负样本不能少。零售柜里经常有空货位、空白层板、员工手部等背景,我在数据集中专门加入了一定比例的不含任何商品的图片,帮助模型降低误检率。这一点很多人会漏掉,但落地时它对误报的影响非常明显。3. 模型训练配置与调参实战3.1 从YOLOv8s开始的基线配置我以YOLOv8s作为基线模型,原因很直接:它的精度和显存占用比较平衡,适合作为对比基准。后续再对比YOLOv5s、YOLOv6s、YOLOv7-tiny时,用同一份数据集和相同的训练轮次,才有横向可比性。基线训练参数可以参考如下配置:# 以ultralytics YOLOv8为示例 yolo detect train \ data=retail_cabinet.yaml \ model=yolov8s.pt \ epochs=200 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ momentum=0.937 \ weight_decay=0.0005 \ optimizer=AdamW这个配置里,imgsz选640是经过权衡的。零售柜里很多商品宽度只占图片的百分之几,太小了特征不明显;太大又拖慢训练速度,显存占用直线上升。如果目标是嵌入式设备,建议直接用512或者416训练,部署时保持一致的分辨率,不要训练时用640、部署时用416,那种“训练与部署输入不一致”会让精度明显掉一截。3.2 训练过程中的指标观察方法我训练时不是只盯着最后的mAP看,而是同时记录三个维度的变化:训练集loss、验证集mAP50和mAP50-95。loss持续下降且验证集mAP同步上升,说明模型在正常学习;如果训练loss还在降,但验证集mAP已经徘徊不动甚至下降,基本可以判断是过拟合了,这时要加大数据增强强度,或者提前用早停。mAP50和mAP50-95的区别值得新手重视。mAP50只要求预测框和真实框的IoU大于0.5就算命中,相对宽松;mAP50-95则是对0.5到0.95的多个IoU阈值做平均,要求更严格。零售柜场景中,商品定位太粗糙会造成计费错误,所以我在汇报效果时既看mAP50,更看重mAP50-95,两个指标一高一低就说明“框的位置还不够精准”。每轮训练完后,我会单独生成一张“Bad Case”图,把验证集中漏检、误检、定位偏差大的图片拼接展示。看bad case比看指标更能暴露问题,比如某个SKU总是漏检,那大概率是训练样本太少或者标注框不统一;反光区域总是多出一个框,那就在数据增强里加入更强的亮度扰动。3.3 超参数调整的几条实践经验调参这块我采用的是“先粗后细”的思路,避免一上来就陷入局部搜索。首轮只调学习率策略,下一步调输入尺寸和训练轮次,最后才动损失函数相关的参数。YOLOv8里有一个分类损失权重和框回归损失权重的比例,这个比例影响“框得准”和“分得对”两者的平衡,零售柜场景我倾向于稍微提高框回归权重,因为算错数量比分类稍微模糊一点更致命。标签平滑我建议保留,默认的0.0005就够了,不要为了追求训练集指标好看把它关掉。Anchor-Based版本里有一个anchor自动匹配机制,开启之后模型会根据数据分布自动调整锚框尺寸,这组参数对YOLOv5和YOLOv7的收敛速度影响很大,建议始终开启。还有一个很多教程不会提的细节:如果精心挑选了同一型号的饮料瓶,但在训练时发现“可乐”和“雪碧”容易被混淆,不要急着加数据,先把这两类样本的标注检查一遍。很多时候是标注人员自己都分不清,或者某几张图的反光让罐体颜色发生了偏移。把这类样本单独挑出来做色彩归一化处理,往往比盲目增加样本量更有效。4. 基于PySide6的桌面系统界面实现4.1 界面功能布局与交互设计这套系统的界面不是简单放一个视频画面加一个按钮就完事,我参考了实际零售柜管理后台的操作习惯,把界面分成四个核心区域:左侧是实时视频预览区,显示摄像头画面和检测框叠加结果;右侧上方是识别结果列表,展示当前画面中每个商品的类别、数量和置信度;右侧下方是数据统计面板,汇总整柜库存变化;底部工具栏则放置摄像头选择、模型版本切换、置信度阈值调节滑条和日志输出窗口。界面框架用PySide6实现,它是Qt官方Python绑定的现代版本,API设计比PyQt5更清晰。窗口布局用QVBoxLayout和QHBoxLayout组合,视频显示控件用QLabel更新QImage实现。置信度阈值滑条是整个界面里最“救命”的设计。调试阶段不同场景对置信度的要求完全不一样,反光严重时阈值要拉高到0.45以上,光线均匀时可以放到0.25。如果把这个参数写死在代码里,每次调参都要重启程序,效率太低了。4.2 推理线程与界面线程的分离PySide6界面最忌讳在网络推理这类耗时操作上阻塞主线程。如果直接在按钮事件里跑模型推理,界面会立刻卡死,拖动窗口都成问题。我在这里使用了QThread加信号槽机制,把推理循环放在独立线程中,界面主线程只负责接收信号并更新画面。核心步骤如下:自定义DetectThread继承自QThread,在run()方法里循环读取视频帧。每推理一帧后,把检测结果打包成自定义数据结构,通过pyqtSignal发送给主界面。主界面只负责绘制检测框和更新表格,不做任何计算。线程停止使用标志位控制,保证退出时不卡界面。这里有一个容易忽略的内存问题:视频帧的传递如果每次都复制一份完整图像,在高分辨率下内存消耗会非常大。我的做法是在线程内部完成必要的检测框绘制,只把绘制好的图像和检测列表发送给界面。这样主线程不需要再访问原图,也避免了两线程同时读写同一份图像数据造成竞态。4.3 模型加载与后处理细节模型推理前的预处理我使用的是letterbox方式,把原始图像按比例缩放并填充灰色边条,统一到训练时的输入尺寸,避免直接拉伸导致目标形变。做完一次推理后,从模型的输出中解析出检测框坐标、类别ID和置信度,先按置信度阈值过滤低质量结果,再做NMS(非极大值抑制)消除同一目标上的重复框。不同版本的输出结构格式不完全相同,通常包含(batch, num_anchors, 4+1+num_classes)的信息维度。这块必须在自己电脑上跑一行打印确认,别凭经验直接索引切片,否则最容易出“尺寸不匹配”的运行时错误。每个商品类别我给配置了固定的显示颜色,同类商品的检测框颜色统一,方便肉眼快速区分。数量统计直接基于检测框计数,但如果同一帧画面里同时出现前后两排同款商品,模型可能需要重复计数,我给检测结果增加了一个简单的去重逻辑:通过IoU判断,同类别且距离过近的检测框视为同一商品实例。4.4 模型切换与推理速度平衡在界面上实现YOLOv5到YOLOv8的切换,我用的是一个下拉选择框,选择后重新加载对应版本的模型文件。模型加载是个耗时操作,如果放在主线程里同样会卡界面,所以我单独做了一个ModelManager类,内部用字典缓存多个已加载模型,切换时尽可能复用已有实例。实际部署时,模型切换一次大约需要1到3秒,对于不频繁切换的零售柜场景完全可接受。但如果要做实时切换,最好还是先加载好模型再切换,或者做异步加载加进度提示。考虑到零售柜的检测精度要求,帧率能稳定在20FPS以上就够用,因为用户拿取动作本身是慢速的,一秒能刷新10次画面已经能清晰观察整个识别过程了。5. 训练与部署过程中的高频问题排查5.1 环境安装与CUDA兼容问题YOLO系列对Python版本和PyTorch版本比较敏感,最容易出现的问题是torch.cuda.is_available()返回False。遇到这种问题,不要急着重装CUDA,先把PyTorch、CUDA Toolkit、显卡驱动的版本对齐。我的经验是直接用PyTorch官方提供的匹配版本组合,比如PyTorch 2.x配CUDA 11.8,通常能减少很多不必要的麻烦。如果在CPU和GPU混合环境下调试,还有一个容易踩的坑:模型加载后第一次推理特别慢,后面才恢复正常。这是因为CUDA在做运行时初始化,缓存了kernel,属于正常现象,不代表代码有问题。显存不足是另一类高频报错。零售柜数据集图片普遍较大,如果训练时batch设置过大,很容易直接OOM。解决思路有两个:先用batch=8跑通训练流程,再逐步增大batch;或者开启梯度累积模拟更大batch的效果。部署阶段如果显存不足,最简单的办法是降低输入分辨率,或者换用轻量模型如YOLOv5s和YOLOv8n。5.2 检测效果不好的排查顺序检测效果不好时,先别急着调参,我建议按固定顺序排查:数据集质量、模型容量、输入分辨率、后处理参数。数据集的样本均衡性问题往往比模型结构更致命,零售柜场景里如果某个SKU只有几十张图,另一个SKU有上千张,模型自然会偏向多数据的类别。误检多的时候,先查置信度阈值是不是设得太低。置信度阈值一般不低于0.25,如果调到0.1,背景里稍微像瓶盖的东西都会被框出来。漏检多的时候,先查输入分辨率是不是太小,再用bad case图对比训练图片和实时画面的光照差异,若差异大,优先考虑在实时侧做图像增强而不是盲目加数据。5.3 PySide6界面运行时的典型异常界面上最常见的异常是QThread: Destroyed while thread is still running,这通常是因为程序退出时线程没有正确停止。我的处理办法是在界面关闭事件里设置停止标志并调用wait()等待线程安全退出,再销毁窗口组件。另一个常见问题是视频画面显示偏绿或偏色,原因是OpenCV读入的是BGR彩色图片,而Qt显示需要RGB格式。转换时务必用cv2.cvtColor(frame, cv2.COLOR_BGR2RGB),别漏这一步。虽然是小问题,但它能让很多人在排查上浪费半天功夫。5.4 模型训练不收敛的快速定位方法训练不收敛有几个常见诱因:学习率过大、数据集标注错误比例过高、类别间样本极度不均衡。我的排查方法是先做一个“过拟合单批数据”的快速实验:只用一小批数据训练几十个epoch,如果loss能压到很低,说明模型和数据链路没有大问题,问题出在训练策略;如果一小批数据都无法过拟合,那很可能代码逻辑或者数据管线有问题。这个经验帮我省了很多时间,建议你在大规模训练之前花半小时做一次这个验证。另外,权重初始化方式在YOLO的不同版本里也有差异,从官方预训练权重开始训练,而不是随机初始化,通常能更快收敛且最终精度更高。为此我特意在文档里强调了“预训练权重下载地址”这一项,项目源码中也包含了下载指引,避免新手因为权重缺失而无法复现。6. 系统集成与后续扩展建议6.1 与硬件摄像头对接的几种方式零售柜摄像头大致分两类:USB摄像头和网络摄像头。USB摄像头用OpenCV的VideoCapture读取即可,但要注意在长时间运行下可能出现掉帧或者设备丢失,需要加一个摄像头重连机制。网络摄像头一般走RTSP流,OpenCV也支持,但延迟比较高,需要调低缓冲或者使用专门的低延迟拉流方案。在系统源码中,我预留了CameraManager模块,统一封装以上两种读取方式,上层界面只调用read_frame()方法。后续如果要换成SDK相机,只需要在这个模块里增加对应实现。6.2 模型导出与嵌入式部署如果要把训练好的模型部署到嵌入式设备,比如瑞芯微或地平线之类的边缘芯片,需要先把PyTorch模型导出为ONNX再转换。导出时有一个关键细节:固定输入尺寸,避免动态尺寸导致转换失败或推理性能下降。导出ONNX的简单命令如下:yolo export model=runs/train/exp/weights/best.pt format=onnx imgsz=640 opset=12导出之后一定要用自己的测试图片在ONNX Runtime上跑一遍推理,确认输出和PyTorch一致。很多模型训练精度很高,但导出后掉点严重,就是因为预处理或输出后处理没有对齐。6.3 从检测到计费的系统闭环思考商品检测只是零售柜系统中的一环,真正的业务闭环还包括:识别结果入库、库存变化记录、异常事件上报、用户订单生成。我在本项目中把检测结果统一封装成标准JSON消息,定时写入本地SQLite数据库,为后续对接计费系统打好基础。这套设计的好处是,检测部分可以独立迭代,不用频繁改动界面和数据库逻辑。如果你后续要做多柜管理,只需要把检测结果通过MQTT或HTTP上报到服务端,本机的SQLite作为离线兜底,不影响基础功能。我个人在实际项目里最深的一点体会是:零售柜商品检测的难点从来不只是模型精度,而是数据、界面和业务逻辑的整体配合。很多人在模型上死磕,最后发现实际落地时数据才是最贵的成本。所以如果你时间有限,优先把数据采集和标注规范做扎实,再跑训练,这样的性价比远高于反复调参。最后再分享一个小技巧:在界面里放一个“保存当前帧”按钮,调试阶段随手截图并自动带上原始时间戳。看起来不起眼,但它能帮你积累大量真实场景样本,后续做模型迭代时,这些图就是最有说服力的训练素材。