做超市智能巡检的朋友应该都懂,货架空不空这件事看着简单,真要靠人天天盯着拍照片、跑现场,时间成本高得吓人。我这两年经手过好几个零售场景的视觉项目,最典型的一个需求就是:能不能用摄像头自动识别哪排货架缺货了、哪个区域快空了,最好能直接出一个告警列表。这个“基于深度学习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切片推理与货架小目标检测货架场景里有一种很头疼的情况:摄像头覆盖一排长货架,远处的商品区域在图像里只有十几个像素,普通推理很容易漏掉。这里我用了SAHI(Slicing Aided Hyper Inference)切片辅助推理的思路。做法是把大图切成若干重叠的小图块,对每个图块独立推理,再把结果合并回原图坐标。实测下来对远处小目标商品的检出率能提升约10到15个点。代价就是推理次数增加。所以我在项目里做了个折中:默认用整图推理,当画面中货架长度过长、或者检测出的商品框面积普遍过小时,再自动切换SAHI模式。这个策略在离线分析场景非常好用,实时性要求高的场景建议直接用TensorRT加速之后再做切片。3. 货架数据集制作与预处理全流程3.1 数据采集:拍什么样的照片才能让模型稳空货架检测的数据集和通用目标检测数据集不太一样,它更考验场景多样性。我当时和某零售商家合作采集了大约六千张货架图片,覆盖了便利店、中型超市两种业态。采集时特别注意了四个维度:光照:白天、晚上、开灯、关灯、靠窗强反差。视角:平视、俯视、斜视、近距离特写、远距离全览。货态:满货、半满、稀疏、空置、补货中途的散乱状态。干扰:顾客走动、手推车遮挡、促销牌、商品外包装反光。这个环节我给一个硬性建议:宁肯少拍一点“完美的正中视角”,也要多拍各种极端角度。模型泛化能力很大程度取决于这些“脏数据”。如果只有统一角度的照片,训练出来的模型一换现场环境就废了。3.2 标注方案:类别怎么定义最实用我最终采用了四个类别,而不是只标一个空货架:类别 full:货架商品密集且正常陈列。类别 semi:商品稀疏,已经露出部分层板。类别 empty:层板大面积露出,商品极少或没有。类别 shelf:货架整体区域,不区分每一层。这四个类别的设计是为了在判定阶段做两件事:显式识别空货架,和通过semi/full的比例估算缺货严重程度。只标一个“empty”会导致模型在边界情况下非常不稳定,因为空与不空的界限本身太模糊。加上中间态之后,模型的置信度判断会更平滑。标注工具用的是LabelImg,YOLO格式输出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.10,PyTorch 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 \ data=dataset/data.yaml \ model=yolov10s.pt \ epochs=120 \ imgsz=640 \ batch=16 \ lr0=0.01 \ optimizer=AdamW \ project=supermarket_shelf \ name=exp_s几个参数的考虑:imgsz=640是默认值,货架长图可以试试1280,精度提高但显存占用翻倍。我实测的场景里,640精度已经够用,就不盲目加大分辨率。batch大小以不爆显存为准。显存不够时优先降batch,不要降分辨率。batch太小时算力利用率低,BN层统计不稳定,训练更容易震荡。lr0=0.01是官方默认,如果发现前20轮loss波动剧烈,可以把lr降到0.005。optimizer我偏向AdamW,收敛更稳。数据量小、训练轮数短时尤其明显。训练过程中重点看两个图:一个是loss曲线,一个是PR曲线。loss趋势要平滑下降,val loss出现持续上升就是过拟合信号,这时需要加早停或者增补数据增强。PR曲线里每个类别的AP值都要单独看,某个类别的AP明显低于其他类别,就去查这个类别的样本量和标注质量。4.4 模型评估与导出训练完成后,评估命令:yolo detect val \ model=runs/detect/exp_s/weights/best.pt \ data=dataset/data.yaml如果mAP50已经超过90%,可以考虑导出部署格式:yolo export model=runs/detect/exp_s/weights/best.pt format=onnx yolo export model=runs/detect/exp_s/weights/best.pt format=engine device=0 # 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_thres=0.4, iou_thres=0.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, conf=self.conf_thres, iou=self.iou_thres)) def predict_video_frame(self, frame): return self._parse_results(self.model.predict(frame, conf=self.conf_thres, iou=self.iou_thres))所有识别的返回结果统一成字典结构:包含类别、置信度、框坐标和业务判定状态。这样UI只消费统一的接口,不管背后是YOLOv10还是其他模型。5.3 实时性与多线程处理摄像头实时画面处理时,如果要叠加SAHI切片,单线程肯定跑不满。我用的方案是生产者-消费者模型:采集线程持续抓帧放入队列,推理线程从队列取帧推理,UI线程只负责渲染结果。队列做长度限制,满了就丢最旧的一帧,设置maxlen=10。这样即使推理慢,界面也不会越积越卡,只是画面刷新率降低。从实际效果看,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的潜力就能真正在超市货架场景里发挥出来。如果你的场景类似,直接从这份路线图开始动手就好,少踩的坑可能就是省下的几周时间。
做超市智能巡检的朋友应该都懂,货架空不空这件事看着简单,真要靠人天天盯着拍照片、跑现场,时间成本高得吓人。我这两年经手过好几个零售场景的视觉项目,最典型的一个需求就是:能不能用摄像头自动识别哪排货架缺货了、哪个区域快空了,最好能直接出一个告警列表。这个“基于深度学习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切片推理与货架小目标检测货架场景里有一种很头疼的情况:摄像头覆盖一排长货架,远处的商品区域在图像里只有十几个像素,普通推理很容易漏掉。这里我用了SAHI(Slicing Aided Hyper Inference)切片辅助推理的思路。做法是把大图切成若干重叠的小图块,对每个图块独立推理,再把结果合并回原图坐标。实测下来对远处小目标商品的检出率能提升约10到15个点。代价就是推理次数增加。所以我在项目里做了个折中:默认用整图推理,当画面中货架长度过长、或者检测出的商品框面积普遍过小时,再自动切换SAHI模式。这个策略在离线分析场景非常好用,实时性要求高的场景建议直接用TensorRT加速之后再做切片。3. 货架数据集制作与预处理全流程3.1 数据采集:拍什么样的照片才能让模型稳空货架检测的数据集和通用目标检测数据集不太一样,它更考验场景多样性。我当时和某零售商家合作采集了大约六千张货架图片,覆盖了便利店、中型超市两种业态。采集时特别注意了四个维度:光照:白天、晚上、开灯、关灯、靠窗强反差。视角:平视、俯视、斜视、近距离特写、远距离全览。货态:满货、半满、稀疏、空置、补货中途的散乱状态。干扰:顾客走动、手推车遮挡、促销牌、商品外包装反光。这个环节我给一个硬性建议:宁肯少拍一点“完美的正中视角”,也要多拍各种极端角度。模型泛化能力很大程度取决于这些“脏数据”。如果只有统一角度的照片,训练出来的模型一换现场环境就废了。3.2 标注方案:类别怎么定义最实用我最终采用了四个类别,而不是只标一个空货架:类别 full:货架商品密集且正常陈列。类别 semi:商品稀疏,已经露出部分层板。类别 empty:层板大面积露出,商品极少或没有。类别 shelf:货架整体区域,不区分每一层。这四个类别的设计是为了在判定阶段做两件事:显式识别空货架,和通过semi/full的比例估算缺货严重程度。只标一个“empty”会导致模型在边界情况下非常不稳定,因为空与不空的界限本身太模糊。加上中间态之后,模型的置信度判断会更平滑。标注工具用的是LabelImg,YOLO格式输出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.10,PyTorch 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 \ data=dataset/data.yaml \ model=yolov10s.pt \ epochs=120 \ imgsz=640 \ batch=16 \ lr0=0.01 \ optimizer=AdamW \ project=supermarket_shelf \ name=exp_s几个参数的考虑:imgsz=640是默认值,货架长图可以试试1280,精度提高但显存占用翻倍。我实测的场景里,640精度已经够用,就不盲目加大分辨率。batch大小以不爆显存为准。显存不够时优先降batch,不要降分辨率。batch太小时算力利用率低,BN层统计不稳定,训练更容易震荡。lr0=0.01是官方默认,如果发现前20轮loss波动剧烈,可以把lr降到0.005。optimizer我偏向AdamW,收敛更稳。数据量小、训练轮数短时尤其明显。训练过程中重点看两个图:一个是loss曲线,一个是PR曲线。loss趋势要平滑下降,val loss出现持续上升就是过拟合信号,这时需要加早停或者增补数据增强。PR曲线里每个类别的AP值都要单独看,某个类别的AP明显低于其他类别,就去查这个类别的样本量和标注质量。4.4 模型评估与导出训练完成后,评估命令:yolo detect val \ model=runs/detect/exp_s/weights/best.pt \ data=dataset/data.yaml如果mAP50已经超过90%,可以考虑导出部署格式:yolo export model=runs/detect/exp_s/weights/best.pt format=onnx yolo export model=runs/detect/exp_s/weights/best.pt format=engine device=0 # 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_thres=0.4, iou_thres=0.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, conf=self.conf_thres, iou=self.iou_thres)) def predict_video_frame(self, frame): return self._parse_results(self.model.predict(frame, conf=self.conf_thres, iou=self.iou_thres))所有识别的返回结果统一成字典结构:包含类别、置信度、框坐标和业务判定状态。这样UI只消费统一的接口,不管背后是YOLOv10还是其他模型。5.3 实时性与多线程处理摄像头实时画面处理时,如果要叠加SAHI切片,单线程肯定跑不满。我用的方案是生产者-消费者模型:采集线程持续抓帧放入队列,推理线程从队列取帧推理,UI线程只负责渲染结果。队列做长度限制,满了就丢最旧的一帧,设置maxlen=10。这样即使推理慢,界面也不会越积越卡,只是画面刷新率降低。从实际效果看,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的潜力就能真正在超市货架场景里发挥出来。如果你的场景类似,直接从这份路线图开始动手就好,少踩的坑可能就是省下的几周时间。