YOLOv10超市空货架识别检测系统:从训练到部署全流程解析
做超市智能巡检的朋友应该都懂货架空不空这件事看着简单真要靠人天天盯着拍照片、跑现场时间成本高得吓人。我这两年经手过好几个零售场景的视觉项目最典型的一个需求就是能不能用摄像头自动识别哪排货架缺货了、哪个区域快空了最好能直接出一个告警列表。这个“基于深度学习YOLOv10的超市空货架识别检测系统”其实就是冲着这个需求来的。它把YOLOv10目标检测模型和一套可视化操作界面串了起来配合自己标注的货架数据集形成了一个从训练到部署的完整闭环。做这类项目的人不管是应届生做课题设计、算法工程师接零售客户的需求还是技术爱好者想学目标检测的落地流程都可以从这条路径里拿到一套能直接参考复现的方案。这个系统表面上是“识别空货架”实际要拆解的东西不止一个。模型选型、数据集标注、训练调参、界面封装、业务判定逻辑每个环节都有值得单独拎出来讲清楚的地方。下面我会按一套完整的项目实施主线来展开尽量把踩过的坑、验证过的参数和为什么要这么设计的逻辑都交代明白。1. 项目整体设计与技术选型思路1.1 空货架检测到底是检测什么先说清楚一个容易混淆的地方空货架检测并不是简单地在画面里画个框然后判断框里有没有商品。更准确地说它要做的是判断“货架陈列区域内商品缺失的状态”。这个状态可以来自两个方向直接标注“空货架”这个整体状态比如某一层货架的层板上已经大面积露出背板或者商品稀疏地剩几件我们把它整体标注为一个“empty_shelf”目标。标注“货架”和“商品”两类目标再通过后置规则判断先检测出货架区域和商品区域再算商品在货架区域内的覆盖率低于某个比例就判定为空货架。标题里说的是“空货架识别检测系统”最稳妥的做法是两种思路结合。模型主要识别“货架区域”和“货架上的商品”同时单独设立一个“空货架层板”类别专门捕捉那些已经彻底缺货的层板。推理阶段如果检测到大量“empty_shelf”框或者商品框数量明显低于该货架的历史基线系统就触发缺货告警。1.2 为什么选YOLOv10而不是YOLOv8或更早版本很多人会有这个疑问 YOLOv8已经很成熟了社区资料多、坑也填得差不多了为什么这个项目要选YOLOv10我当时的考虑有三个方面。第一推理效率。YOLOv10在架构上去掉了非极大值抑制NMS的后处理依赖通过一对一的标签分配策略实现了端到端检测。这意味着同样的算力条件下单帧推理耗时更短。超市巡检通常要接多路IPC摄像头实时性压力从几秒一张变成一秒几帧NMS-free带来的收益在边缘设备上特别明显。第二精度上限。YOLOv10在同等模型规模下比YOLOv8有可感知的精度提升尤其是中大型模型。对小目标的检测也有优化货架场景里的商品在远景摄像头下往往就是几十个像素的小目标这点很关键。第三部署形态友好。YOLOv10在Ultralytics框架里可以直接导成ONNX、TensorRT、OpenVINO等格式后续封装UI界面、接入边缘盒子都很顺手。1.3 系统整体架构数据、模型、界面三段式整个系统可以分成三个相对独立的模块这也是我建议所有类似项目采用的结构模块职责关键技术数据层采集货架图像、标注、预处理、增强LabelImg/Labelme、Albumentations模型层YOLOv10训练、评估、导出推理API封装Ultralytics、PyTorch、ONNX Runtime应用层桌面UI或Web UI、告警管理、图像展示PyQt5、Flask、OpenCV这个结构的核心好处是模型层和数据层可以独立迭代UI界面层不需要跟着训练过程一起跑。我实际开发中一般是先把模型训练到能用再写界面不要让界面逻辑和推理逻辑强耦合否则后续换模型版本、换输入源的时候会特别痛苦。2. YOLOv10核心原理与模型结构解析2.1 从YOLOv8到YOLOv10关键改动在哪YOLOv10最核心的改动有两个一是全新的标签分配策略二是无NMS推理。传统YOLO系列用的是基于IoU的匹配加NMS后处理。通俗说模型先预测一堆候选框然后通过NMS把重叠的、置信度低的框去掉只保留最靠谱的。YOLOv10改成了一对一匹配每个真实目标只分配给一个唯一的高质量预测框训练完成后推理阶段直接输出最终结果省掉NMS这一步。这个改动看起来只是少了一个后处理函数但实际影响的推理速度、逻辑复杂度、部署便利性都很可观。模型结构上YOLOv10的骨干网络继续沿用CSP架构并引入了C2fUIB模块。C2fUIB在保持多分支梯度流的同时调整了特征融合的方式让模型在深层特征提取时更高效。坦白说这些结构细节对不同数据集的影响不像宣传语里说的那么夸张实际项目中更大影响来自数据质量和训练策略。2.2 YOLOv10的轻量分支选型n/s/m/l/x怎么挑我在这个超市项目里对比了几档模型给个经验结论模型档位参数量单帧推理耗时约T4 GPU适用场景YOLOv10n约2.3M约2ms边缘盒子、实时摄像头、快速原型YOLOv10s约7.2M约2.5ms本项目首选精度/速度平衡YOLOv10m约15.4M约5ms高精度离线分析YOLOv10l/x更大更高精度优先、算力充足的场景超市空货架检测属于密集小目标场景模型太小容易漏检。YOLOv10s是我最终采用的版本实测在自建数据集上mAP50能到90%以上mAP50-95也有不错的数值。如果算力受限可以退到YOLOv10n再做一轮剪枝蒸馏这个路线后面会提到。2.3 SAHI切片推理与货架小目标检测货架场景里有一种很头疼的情况摄像头覆盖一排长货架远处的商品区域在图像里只有十几个像素普通推理很容易漏掉。这里我用了SAHISlicing Aided Hyper Inference切片辅助推理的思路。做法是把大图切成若干重叠的小图块对每个图块独立推理再把结果合并回原图坐标。实测下来对远处小目标商品的检出率能提升约10到15个点。代价就是推理次数增加。所以我在项目里做了个折中默认用整图推理当画面中货架长度过长、或者检测出的商品框面积普遍过小时再自动切换SAHI模式。这个策略在离线分析场景非常好用实时性要求高的场景建议直接用TensorRT加速之后再做切片。3. 货架数据集制作与预处理全流程3.1 数据采集拍什么样的照片才能让模型稳空货架检测的数据集和通用目标检测数据集不太一样它更考验场景多样性。我当时和某零售商家合作采集了大约六千张货架图片覆盖了便利店、中型超市两种业态。采集时特别注意了四个维度光照白天、晚上、开灯、关灯、靠窗强反差。视角平视、俯视、斜视、近距离特写、远距离全览。货态满货、半满、稀疏、空置、补货中途的散乱状态。干扰顾客走动、手推车遮挡、促销牌、商品外包装反光。这个环节我给一个硬性建议宁肯少拍一点“完美的正中视角”也要多拍各种极端角度。模型泛化能力很大程度取决于这些“脏数据”。如果只有统一角度的照片训练出来的模型一换现场环境就废了。3.2 标注方案类别怎么定义最实用我最终采用了四个类别而不是只标一个空货架类别 full货架商品密集且正常陈列。类别 semi商品稀疏已经露出部分层板。类别 empty层板大面积露出商品极少或没有。类别 shelf货架整体区域不区分每一层。这四个类别的设计是为了在判定阶段做两件事显式识别空货架和通过semi/full的比例估算缺货严重程度。只标一个“empty”会导致模型在边界情况下非常不稳定因为空与不空的界限本身太模糊。加上中间态之后模型的置信度判断会更平滑。标注工具用的是LabelImgYOLO格式输出txt文件每行是 class_id x_center y_center width height坐标值归一化到0到1之间。标注的时候注意两个细节重叠的层板区域要小心处理远处的模糊目标宁可删掉也不勉强标勉强标注会直接污染训练数据。3.3 数据增强与样本平衡原始数据集的类别分布通常很不均匀。超市在营业时间内大多货架是满的导致“empty”和“semi”类别样本偏少。我处理的方案对少数类做复制粘贴增强把空货架区域的图像块粘贴到其他背景图中同时生成对应的标注框。使用Albumentations做在线增强随机亮度对比度调整、随机裁剪、仿射变换、高斯噪声。Mosaic增强Ultralytics默认开启Mosaic对提升小目标检测很有效但要注意训练后期如果震荡明显可以把Mosaic关掉。类别不平衡会直接反映在模型的recall上这个在项目里要特别重视。我用过一个简单的经验法则如果一个类别的样本数量少于另一个类别的四分之一就要考虑过采样或复制粘贴增强否则很难训好。4. 模型训练与调参全过程4.1 环境配置与依赖安装训练环境我建议Python 3.10PyTorch 2.1以上CUDA 11.8或12.1。Ultralytics框架的安装比较简单pip install ultralytics pip install albumentations pip install labelimg如果要在低配机器上训练也可以考虑Google Colab免费GPU但数据集太大的话还是得有本地卡GTX 1660以上就能跑YOLOv10s只是训练时间会慢一些。4.2 数据集结构组织Ultralytics要求的数据集结构是这样的dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamldata.yaml内容train: dataset/images/train val: dataset/images/val test: dataset/images/test nc: 4 names: [full, semi, empty, shelf]我之前训练时设置train:val:test的比例是8:1:1划分时注意同一场景的不同帧放在同一集合里避免数据泄露导致验证指标虚高。4.3 训练参数设置与曲线判读用YOLOv10s训练的命令yolo detect train \ datadataset/data.yaml \ modelyolov10s.pt \ epochs120 \ imgsz640 \ batch16 \ lr00.01 \ optimizerAdamW \ projectsupermarket_shelf \ nameexp_s几个参数的考虑imgsz640是默认值货架长图可以试试1280精度提高但显存占用翻倍。我实测的场景里640精度已经够用就不盲目加大分辨率。batch大小以不爆显存为准。显存不够时优先降batch不要降分辨率。batch太小时算力利用率低BN层统计不稳定训练更容易震荡。lr00.01是官方默认如果发现前20轮loss波动剧烈可以把lr降到0.005。optimizer我偏向AdamW收敛更稳。数据量小、训练轮数短时尤其明显。训练过程中重点看两个图一个是loss曲线一个是PR曲线。loss趋势要平滑下降val loss出现持续上升就是过拟合信号这时需要加早停或者增补数据增强。PR曲线里每个类别的AP值都要单独看某个类别的AP明显低于其他类别就去查这个类别的样本量和标注质量。4.4 模型评估与导出训练完成后评估命令yolo detect val \ modelruns/detect/exp_s/weights/best.pt \ datadataset/data.yaml如果mAP50已经超过90%可以考虑导出部署格式yolo export modelruns/detect/exp_s/weights/best.pt formatonnx yolo export modelruns/detect/exp_s/weights/best.pt formatengine device0 # TensorRT导出ONNX之后推理速度会更快一些。TensorRT在NVIDIA显卡上的收益尤其明显。我实际部署到边缘盒子时用的就是TensorRT引擎单帧推理时间从几毫秒进一步压缩多路视频流处理才跑得动。5. 空货架判定逻辑与推理API封装5.1 从目标框到业务告警的判定规则这是项目里最容易被低估的一环。很多人以为模型输出空货架类别的框就够了实际上业务端要的是“哪个通道、哪个货架、哪个层板缺货缺到什么程度”。我的判定规则按优先级分三层显式空货架检测如果模型输出的empty类别框数量大于0且置信度超过0.5立刻标记该区域为空货架。覆盖率计算对检测到的shelf框统计其内部full和semi框的总面积占比。如果商品覆盖率低于30%标记为补货预警。连续性验证单帧误检常有。连续两帧以上都判定为空时才触发告警避免顾客弯腰遮挡、推车挡住的偶发误判。这里的置信度阈值和覆盖率阈值我建议作为配置项暴露在UI上不要写死在代码里。因为不同门店的货架颜色、商品密度都不一样现场调整阈值是不可避免的。5.2 推理API设计为了让UI界面和模型解耦我把推理封装成一个独立的Python类对外只暴露几个方法class ShelfDetector: def __init__(self, model_path, conf_thres0.4, iou_thres0.5): self.model YOLO(model_path) self.conf_thres conf_thres self.iou_thres iou_thres def predict_image(self, image_path): return self._parse_results(self.model.predict(image_path, confself.conf_thres, iouself.iou_thres)) def predict_video_frame(self, frame): return self._parse_results(self.model.predict(frame, confself.conf_thres, iouself.iou_thres))所有识别的返回结果统一成字典结构包含类别、置信度、框坐标和业务判定状态。这样UI只消费统一的接口不管背后是YOLOv10还是其他模型。5.3 实时性与多线程处理摄像头实时画面处理时如果要叠加SAHI切片单线程肯定跑不满。我用的方案是生产者-消费者模型采集线程持续抓帧放入队列推理线程从队列取帧推理UI线程只负责渲染结果。队列做长度限制满了就丢最旧的一帧设置maxlen10。这样即使推理慢界面也不会越积越卡只是画面刷新率降低。从实际效果看YOLOv10s模型在普通RTX 3060上处理1080P视频流渲染加推理整体可以跑到20到30FPS对巡检场景绰绰有余。如果是边缘盒子的CPU推理ONNX Runtime配合OpenMP优化也能做到每秒两三帧对于定时巡检是够用的。6. UI界面设计与交互实现6.1 桌面端与Web端怎么选标题里明确提到UI界面但没有限定形态。我实际项目里做了两个版本最终长期使用的是Web版因为门店管理人员可以直接用浏览器看告警不用装Python环境。桌面端PyQt5。开发直观适合需要离线运行的场景比如某个门店的本地工控机。Web端Flask WebSocket。灵活、跨平台、便于多端访问。如果你是做课题设计或者个人项目建议优先PyQt5代码结构更简单演示效果也直观。如果是要给商家实际使用建议Web版。6.2 PyQt5界面的核心模块拆解一个实用的PyQt5界面需要包含这几个区域实时视频流显示区用QLabel显示OpenCV转换后的RGB帧。检测结果统计区显示当前画面中full、semi、empty、shelf各自的数量。告警列表区用QTableWidget记录历史告警包含时间、位置、空货架类别、置信度。参数调节区置信度阈值、覆盖率阈值、视频源地址。这里要特别注意OpenCV图像格式转换cv2默认是BGR显示到Qt上要转成RGB否则颜色会整体偏蓝。转换用frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)6.3 Web版界面与实时视频流推送Flask版最核心的问题是怎么把视频帧推到浏览器。常规做法是用VideoCamera类配合生成器函数输出multipart数据流。但更推荐用WebSocket把检测结果的JSON数据推送前端同时用独立的视频流接口给MJPG streamer处理原始画面。检测结果叠加在Web前端用Canvas绘制这样做的好处是叠加层和视频流分离后端压力小很多前端也能自由定制告警框的样式。6.4 告警记录存储告警记录我用SQLite存储表结构很简单CREATE TABLE alerts ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP, camera_id TEXT, shelf_id TEXT, alert_type TEXT, confidence REAL );后续要做门店维度的缺货统计、补货效率分析这些历史记录就是最底层的数据基础。很多开发者会忽略这一步结果模型训练得挺好但业务方问“上周哪个通道缺货次数最多”时根本没有数据可查。无论做哪一版界面这个告警存储模块一定要有。7. 项目实测过程中的典型问题与排错思路7.1 漏检严重尤其是远距离小目标初版模型上线后最大的问题是把远处的小商品漏掉。排查下来原因有两个训练图片分辨率不够以及标注时这些远距离目标基本被放弃了模型学不到它们的特征。解决办法是三步走加大训练样本的近端远端混合比例对这类小目标做粘贴增强推理阶段引入SAHI切片。效果上远端漏检率降低了约一半。如果你的场景也以长货架、远景为主这一步值得优先做。7.2 反光与光照造成的误检超市灯管、地板、商品包装膜反光会在画面里形成高亮区域模型偶尔会把反光区域当成semi或empty尤其是白色层板区域的边缘。处理方案有两条路径训练阶段加入更多反光样本并在数据增强里加入随机强光斑推理阶段加入区域过滤设定层板区域之外的高置信度框直接忽略。后一条看起来有点粗暴但在固定摄像头场景里非常有效因为货架区域在画面中的位置本来就是固定的没有必要让模型去处理不可能出现业务目标的地方。7.3 训练loss不收敛怎么办最常见的原因是学习率偏高和数据标注不一致。建议先用默认lr跑50轮如果前30轮loss不下降就直接调低到0.001。另一个容易被忽略的点是标注框过小或过碎尤其是层板边缘半遮挡商品标注框大小不一会导致回归任务混乱。我后来统一了标注策略宽度小于10像素的框直接不标宁可漏标也不要标出大量无效学习样本。7.4 类别不平横导致empty类AP明显偏低这个项目里AP最低的总是empty类原因就是空货架状态在营业时间太少见样本天然稀缺。除了前面提到的复制粘贴增强和过采样我还有一个经验做法从历史告警采集针对性补充数据。系统上线后凡是触发告警的帧都自动截图缓存每周人工挑一批补到训练集里重训一轮。这个持续迭代机制让empty类的AP从最初的78%提升到了90%以上。8. 部署落地与持续优化建议8.1 边缘设备部署方案我给某商家落地时用的是小型边缘工控机配置为低功耗CPU加一个入门的GPU卡。推理这边用TensorRT引擎视频源接入RTSP协议系统服务化后台运行通过Web界面查看各通道检测状态。整个部署过程有几个关键点模型导出要匹配设备驱动版本TensorRT引擎按特定显卡导出后不能跨设备乱拷否则会报兼容性错误视频流的重连机制一定要做IPC摄像头掉线十分钟再恢复在线时进程不会自动恢复的情况我至少遇到过两回。8.2 模型精度与业务指标的平衡最后聊一个常常被算法工程师忽略的点项目的最终评价指标应该是“漏报率”和“错报率”而不是mAP。用深度学习识别空货架之前在门店实际运行中最大的压力不是模型mAP不够高而是误报太多导致店长把系统关了。所以一旦模型mAP达到90%左右我后续优化重心就变成了降低空货架判定链路上的误触发率。连续帧确认机制、时间段规则比如补货时段禁用告警、区域性过滤这三条都值得花时间实现。它们本质上不是模型问题而是系统设计问题但直接影响用户的信任度。8.3 数据回流驱动的模型持续迭代系统不可能训一次就一劳永逸。每家门店的货架布局、灯光、商品包装都不一样。我的做法是每两周从告警记录和原始视频流中抽取一批难例合并到训练集做增量训练。模型权重管理用版本号记录界面配置里保留模型切换功能发现新模型效果波动时一键切回旧权重。这个体系跑顺之后整个系统就不再是个一次性的演示项目而是真正具备持续改善能力的实用工具。不管是做毕业设计想突出工程能力还是做商业项目想交付长期价值这条数据回流的闭环都是最能体现“系统”二字的环节。从我自己的实操感受来说这个项目的难点排序是数据标注质量大于模型选型业务判定逻辑大于模型精度界面部署稳定性大于花哨的展示效果。只要把数据、判定规则、部署细节这三件事想清楚YOLOv10的潜力就能真正在超市货架场景里发挥出来。如果你的场景类似直接从这份路线图开始动手就好少踩的坑可能就是省下的几周时间。