news 2026/10/21 8:04:25

无人售货柜视觉结算实战:YOLO在RK平台的商品比对与状态机设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
无人售货柜视觉结算实战:YOLO在RK平台的商品比对与状态机设计

这个系列写到第7篇,前面几篇把YOLO在RK平台上的模型转换、推理加速、摄像头接入都过了一遍。但说句实在话,检测跑通了并不等于业务能落地,能框出商品和能算出顾客该付多少钱之间,还隔着整整一个“业务闭环”的距离。这一篇我们就来解决这个核心问题:顾客开门、拿商品、关门,这三个动作发生之后,系统怎么自动算出一笔正确的订单。内容会聚焦在商品比对算法、结算状态机、RK平台的算力分配,以及大量从真实设备调试里踩出来的坑上,正在做无人货柜边缘视觉方案的开发者可以直接照着这个思路搭。

1. 业务闭环设计:一次购物要拆成多少个状态

1.1 一次完整购物的时间线

无人售货柜的结算逻辑,本质上是个状态机问题。我之前见过不少新手直接写“循环检测,检测完就对比”,这样在演示Demo里能跑通,但一放到真实场景就各种漏单、错扣。原因是真实购物过程有太多中间态,比如顾客开门了但不买、拿起商品又放下、换了另一瓶、甚至把商品拿到手里转了一圈再放回去。

我建议把一次购物拆成这样的状态流:

  1. 待机状态:设备低频运行,摄像头处于休眠或低帧率模式,只靠人体感应模块(红外、毫米波雷达或微波雷达)判断是否有人接近。
  2. 唤醒预热:人体感应触发后,摄像头唤醒并开始预采集画面,这个过程一般要提前于开门动作1到2秒,保证开门瞬间摄像头已经处于稳定曝光状态。
  3. 开门前基准帧采集:门磁或光电开关检测到门打开的瞬间,立刻抓取当前画面作为“基准帧”。这是整个结算逻辑的地基。
  4. 营业中持续检测:门保持打开期间,YOLO以3到5帧每秒的频率持续检测货架区域,这期间只需要维护检测结果,不需要做结算判断。
  5. 关门收尾帧采集:门磁检测到关门动作后,再抓取一帧或多帧作为“收尾帧”。
  6. 差异比对与订单生成:把基准帧和收尾帧做商品差异分析,得到“减少了什么、多出了什么”,生成购物清单。
  7. 支付扣款与状态复位:调起支付流程,完成后回到待机状态。

这套流程里最容易被忽视的就是第2步。很多团队直接把摄像头设为常开模式,觉得省事,但带来的问题有两个:一是设备长期高负载运行,发热和功耗都在增加,尤其户外柜夏天很容易过热降频;二是常开会录下大量无意义画面,调试时想找一次有效购物记录要翻几个小时录像。所以我强烈建议把人体感应模块做进主流程,用GPIO中断唤醒,而不是靠轮询视频流。

1.2 为什么要“开门前基准帧”

结算逻辑里存在一个先验信息:货架上摆了什么,在开门之前是“已知集合”。无人售货柜不像开放货架,它的SKU是固定的、理货位置也是固定的。既然如此,就不需要每次结算都从头识别整个货架,而应该把“开门前状态”当作标准答案,只关注“变化”的部分。

这就是基准帧最大的价值:它帮我们把问题从“识别所有商品”降维成“识别商品变化”。前者需要解决各种光照、遮挡、视角问题,后者只需要做好前后差异比对。举个生活中的例子,你出门前手机放在桌上,回来发现手机不见了,你不需要重新扫描整个房间才知道桌面少了什么,你只需要对比“之前有什么”和“现在有什么”。售货柜结算也是同样的逻辑。

实际操作中,基准帧的采集时机很讲究。如果抓到的是“门已经打开一半”的画面,顾客手可能已经伸进去了,会把拿取动作的一部分信息破坏掉。所以门磁信号要够快,或者直接用光电开关挡在门缝位置,在门缝露出一条缝的时候就触发采集。另外,基准帧不要只抓一帧,我会连续抓3到5帧,取检测结果最稳定的那一帧作为基准,因为单帧画面可能恰好有反光或者运动模糊。

1.3 摄像头布局与货架区域划分

在讲比对算法之前,必须先说清楚摄像头怎么装。市面上主流的视觉货柜有两种方案:一种是每层一个摄像头垂直向下拍,另一种是柜子对角装两个广角摄像头交叉覆盖。我做的项目用的是每层一个摄像头的方案,原因是这样生成的画面天然是“俯视矩形”,非常容易做货架槽位划分。

每一层货架在画面里可以预先标定出若干个固定区域。比如一层货架宽80厘米,放5个槽位,每个槽位在图像上有固定的多边形坐标。这个坐标在设备安装后做一次标定即可,后续不需要改动。检测框的中心点落在哪个槽位里,就认为该槽位上有一个商品。

区域划分的好处是:比对逻辑不需要做跨帧的目标追踪,只要按槽位对比“基准状态”和“收尾状态”就行。这比跟踪ID要简单得多,也稳定得多。因为售货柜里的商品基本是静态放置的,顾客拿取会让商品短暂移位,但最终要么被拿走、要么被放回某个位置,网格映射能把绝大多数误判挡在门外。

2. 商品比对逻辑:用差异检测还原用户动作

2.1 检测结果的数据模型

YOLO模型输出的是一组检测框,每个框包含4个坐标、一个类别ID、一个置信度。在结算场景里,我们要给这组数据额外加一个“槽位编号”字段。这样一来,每一帧画面都被抽象成一个数组,数组长度等于槽位总数,每个元素记录对应槽位上检测到的商品类别编码。

举个例子,某一层货架有5个槽位,基准帧的槽位状态是:

槽位编号基准状态类别ID置信度
1有商品可乐0.92
2有商品可乐0.88
3有商品雪碧0.90
4有商品雪碧0.85
5空无-

收尾帧则拿到另一组槽位状态。比对就是逐槽位对比状态差异。需要特别说明的是,这里不要直接用“检测到的类别是否等于基准类别”来判断,因为检测置信度会有波动,同一瓶可乐在不同光照下,模型输出概率可能从0.92掉到0.6,这时候类别ID不变,但置信度变了。对比逻辑应该以“槽位上有没有商品”为第一层判断,而不是以“类别置信度高不高”为第一层判断。

2.2 三层确认:帧内检测、区域映射、消失确认

我在项目里落地了一套“三层确认”机制,专门解决商品被遮挡、手部干扰、短暂移位这些常见问题。

第一层是帧内检测,YOLO在营业状态持续输出每帧的检测结果,这一步不需要做额外处理。第二层是区域映射,把检测框的中心点投影到预先标定的槽位编号。第三层是消失确认,某一槽位连续多帧都检测不到商品,才认为该商品可能被拿走了。

这个三层确认很关键。如果只靠单帧判断,会出现“顾客的手遮住商品一瞬间,漏检一帧,系统就误判为取走”的情况。营业状态用3到5帧每秒的速度采集,连续5帧确认大概需要1到1.7秒,这段时间足够滤掉绝大多数瞬时遮挡。实际测试下来,取消失确认的窗口为5到8帧比较合适,太短误判多,太长会拖慢关门后的结算响应速度。

还有一个细节:关门后的收尾帧不要只拍一帧。因为顾客关门瞬间手可能还在柜门附近,货架上商品被身体或手臂挡住的情况非常常见。我的做法是关门后连续抓5帧,每一帧都做一次槽位状态提取,最后把5帧的状态做投票合并。比如5帧里有4帧认为1号槽位空了,就接受这个结果。

2.3 拿取和放回怎么判定

差异比对的结果会分成三类:

  1. 商品减少:基准帧有商品,收尾帧没有,判定为“顾客取走”。
  2. 商品增多:基准帧为空,收尾帧有商品,判定为“顾客放回”,需要在最终的购物车里扣减。
  3. 类别变化:基准帧是可乐,收尾帧变成了雪碧,这属于“先取走A,又放回B”的组合动作。

类别变化的处理要格外谨慎。真实场景里顾客可能拿起一瓶可乐看了看,又放下,但因为是斜着放的,模型可能会把倾斜的可乐识别成雪碧,导致误判。所以我的策略是:只有当类别变化的置信度同时满足“新类别置信度大于0.6”和“新类别连续3帧稳定出现”两个条件,才接受变化,否则保持基准状态。

放回商品的判定还有个更稳妥的替代方案:如果货柜装了重力感应层板或者压电传感器,可以用重量变化来辅助确认。视觉判定“槽位增加一瓶雪碧”,重力数据同时显示重量净变化在雪碧单瓶重量范围内,两条信息交叉验证,基本就能确定这是放回。视觉与称重双模态融合,是当前无人货柜防错扣最靠谱的方案。

3. YOLO模型在RK平台上的部署与算力规划

3.1 模型选型与算力预算

提到RK平台,市面上目前用得最多的两款芯片是RK3568和RK3588。它们的NPU算力差距很大:RK3568约1 TOPS,RK3588约6 TOPS。这个差距直接决定了你能跑多大的模型、同时跑几路视频流。

我的经验是:

平台适合模型单路640x640推理延迟建议推理路数
RK3568YOLOv5s / YOLOv8n40-80ms1至2路
RK3588YOLOv8s / YOLOv8n10-20ms3至4路

如果是RK3568,必须用YOLOv8n这种轻量模型,并且输入分辨率尽量控制在640以内。如果硬跑YOLOv8s,单帧延迟会到150ms以上,营业状态3fps的帧率都很难稳定维持,而且NPU长期满载会导致芯片表面温度升到80度以上,这对户外柜是不可接受的。

如果是RK3588,就可以从容一些。YOLOv8s配合1280的分辨率也能跑到实时,但我仍然建议用YOLOv8n,原因不是算力不够,而是“不需要”。售货柜商品是静态摆放的,目标尺寸相对固定,640分辨率足够识别一瓶罐装饮料;更高的分辨率只会增加预处理和后处理耗时,带来收益却很有限。把节省下来的算力留给多路视频流,让每个货架层都能获得独立的推理通道,远比单层跑超分辨率更有价值。

3.2 RKNN转换与量化

YOLO模型跑在RK平台上,必须经过RKNN-Toolkit2转换成.rknn格式。转换这一步有几个坑值得提前说。

第一,导出ONNX时要把模型输出改成RK平台友好的格式。YOLOv8的原生输出包含3个不同尺度的检测头,转ONNX后建议把输出节点统一concat成一个tensor,这样RKNN在解析时不会出现输出数量对不上的情况。第二,量化数据集一定要用真实货柜图片,不能用COCO的通用图片。这一点非常关键。RKNN默认的量化校准需要几百张图片,如果你喂给它的是COCO数据集里的自然图片,它统计出来的激活值范围跟真实货柜场景差异会很大,量化后的模型在货柜上的精度损失会明显增加。我自己习惯在现场采集100到300张不同光照、不同角度、包含商品和空槽位的图片,做成量化校准集再喂给工具。

下面是一个简化的RKNN转换流程示例:

from rknn.api import RKNN rknn = RKNN() rknn.config( mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rk3588' ) ret = rknn.load_onnx(model='yolov8n.onnx') ret = rknn.build(do_quantization=True, dataset='real_cabinet.txt') rknn.export_rknn('yolov8n.rknn')

转换完成后,一定不要在PC上直接测精度就完事,必须烧到板子上配合真实摄像头跑一遍。很多时候量化的精度损失只在特定光照下才暴露,PC端用静态图片测试根本发现不了。

3.3 多路并行与系统架构

如果货柜有3层,每层一个摄像头,那么在RK3588上最合理的架构是:把3路摄像头全部接入,每路一个独立的RKNN推理上下文,分别跑同一个模型。RK平台的NPU调度器支持多路并发,3路YOLOv8n同时推理,总延迟会比单路增加约50%,但依然能稳定工作在10到25帧每秒的总吞吐。

线程模型上,我建议拆成三个线程:

  • 采集线程:负责从摄像头拉帧,统一缩放到模型输入尺寸,推到推理队列。
  • 推理线程:不断从队列取帧,调用RKNN推理,完成NMS和后处理,输出检测结果。
  • 业务线程:消费检测结果,维护每个槽位的状态机,触发基准帧/收尾帧的保存,生成订单。

注意,不要让业务逻辑阻塞推理线程。如果NMS写在Python里,而推理是在C接口层跑的,那么在Python侧做NMS会严重影响吞吐。建议把后处理也下沉到C或C++层,或者至少用numpy向量化操作,避免逐框循环。

4. 自动结算与异常兜底:既不错扣也不漏扣

4.1 从检测类别到商品价格的映射

检测模型输出的类别ID,必须经过一张SKU映射表转换成具体的商品名和单价。这张表是部署时由商家配置的,包括类别ID、商品名称、单价、条码、重量这些字段。比如类别ID 0对应的可能是“330ml罐装可乐”,单价3元。

结算的核心逻辑就是维护一个购物车字典:

cart = {} # key为SKUID,value为数量 # 遍历槽位差异 for slot_id in diff_result: if diff_result[slot_id] == "商品减少": sku = sku_table[slot_id] cart[sku.id] = cart.get(sku.id, 0) + 1 elif diff_result[slot_id] == "商品增多": sku = sku_table[slot_id] cart[sku.id] = cart.get(sku.id, 0) - 1

这里要注意,某些SKU包装尺寸较大,一个商品可能同时跨两个相邻槽位。这时候区域映射会产生“一个商品占两个槽位编号”的情况。我的解决方案是:在槽位标定时,把这类大件商品的槽位合并,标记为“占用跨区”,比对时按合并区域处理。否则结算会出现同一瓶饮料被算两次的情况。

4.2 防错扣的时间稳定性校验

前面提到用“连续N帧消失确认”来过滤瞬时遮挡,但还有一类更难处理的情况:“商品短暂被拿起并放回,但放回位置偏了,模型在收尾帧里没检测到”。比如顾客拿起一瓶矿泉水,看了看,又放回,但瓶身是倒着放的,模型对这种非直立姿态的检出率会下降。

如果系统只做“基准帧 vs 收尾帧”的对比,这类情况会被误判为“取走”,造成错扣。所以我增加了“最终态复核”机制:在收尾帧判定某个槽位减少后,再翻看营业状态最后10帧的检测记录,如果该槽位在这10帧里曾经被重新检测到同类别商品,则取消这次“减少”判定。这个机制有点类似电商系统里的“先冻结后确认”,能显著减少临时起意放回导致的误扣。

时间稳定性校验的完整规则我整理成了这样:

场景判定规则说明
正常取走槽位连续8帧为空,且收尾复核帧仍为空确认扣款
拿起又放回槽位短暂为空后重新出现同类别商品取消减少判定
换货基准类别A消失,收尾出现类别B且B稳定3帧按减少A、增加B处理
疑似误检基准和收尾类别一致,但中间有跳变保持基准状态不动

4.3 云端复核与多模态兜底

无论边缘逻辑写得多严谨,总会有意想不到的情况。我的项目里保留了云边协同的兜底通路:当边缘侧出现无法确认的差异(比如槽位状态冲突、置信度低于阈值、或者同一个槽位在基准和收尾都检测到商品但类别ID不同)时,设备会把基准帧和收尾帧的裁剪图、检测结果、时间戳打包上传到云端,由云端更强大的模型或者运营人员远程复核。

云端复核不是实时路径,会有几秒到十几秒的延迟,所以用户看到的是“订单生成中”的状态。这里需要设计一个合理的超时机制,如果30秒内云端没有返回结果,先按边缘侧结果扣款,后续有争议再走退款流程。这样既不被云端延迟卡脖子,又能在大部分异常场景保住准确率。

如果设备装了称重传感器,我强烈建议把重量变化作为交叉验证通道接入结算逻辑。视觉识别“少了1瓶可乐”,重量显示净变化只有50克,明显不对,这时候就应该触发云端复核,而不是直接扣款。多模态融合的价值在这种边缘Case里体现得最明显。

4.4 多人同时购物的处理

多人同时在柜前购物,对视觉结算来说是灾难级的挑战。单目视觉很难区分“两个人分别拿了什么”,因为画面里可能出现交叉遮挡、商品被传递、甚至一个人开门另一个人伸手进来等复杂情况。

我目前的处理方案是分等级应对:

  • 基础场景:限定同一时间只允许一个顾客操作,判断依据是柜门前的人体感应是否持续被单目标占用。如果检测到多个独立人体目标同时存在,系统会进入“保守模式”。
  • 保守模式:每一笔差异都额外叠加云端复核,扣款前需要额外确认,甚至引导顾客分次扫码开门。
  • 长期方向:把摄像头从单目升级为双目立体视觉,结合深度信息做人体姿态估计和手部轨迹追踪,这样才能在多人场景下更可靠地关联“谁拿了什么”。这个方向我这边的项目还在迭代,后续有阶段性成果再单独写一篇。

5. 实战踩过的坑与排查思路

5.1 玻璃反光与虚检

无人售货柜几乎都带玻璃门,而玻璃反光会让YOLO凭空“看到”不存在的商品。我调试时遇到过最离谱的情况:柜内明明什么东西都没有,模型在空货架上检测出3瓶饮料,仔细看才发现是玻璃门反射了旁边路人衣服上的图案。

应对反光,我试过几种手段。物理层面最简单的是给摄像头加偏振镜,能大幅削弱玻璃反射的偏振光。软件层面,可以在采集线程里做一次简单的区域掩膜,把玻璃边缘区域置黑,让检测器忽略这些区域。还有人用“检测结果必须在基准帧和收尾帧之间保持一致才采信”这个规则来过滤虚检,但这只能过滤开门前后的虚检,营业状态里的虚检依然可能造成误确认,所以最好的办法还是从源头削弱。

5.2 相似包装SKU的区分困难

当货柜卖的是同一品牌不同口味的饮料时,模型分类很容易混淆。可乐和零度可乐、雪碧和柠檬味雪碧,外观差异极小,YOLO这种通用目标检测网络在低分辨率输入下很难区分它们。

我的建议是不要指望在检测阶段就精准分类。可以把检测模型的任务简化为“只判断槽位上有商品还是没商品”,把“具体是哪个SKU”交给另一个环节来解决。具体做法是:检测到槽位商品后,中心点附近的区域再裁剪成小图,用一个小型分类网络或者图像检索模块做精细识别。这个二次分类网络的输入可以更大一些,比如96x96或128x128,识别精度远高于把检测网络的类别数强行加多。此外,我还在商品外包装上加了二维码/条形码作为终极兜底,让用户在结算确认页自主选择口味,双保险。

5.3 光照变化导致检测抖动

室内售货柜还好,灯光基本恒定。户外柜就不一样了,正午阳光直射和夜间补光灯下的画面亮度差异非常大。YOLO在量化之后对光照变化更敏感,可能白天检测正常,到了傍晚就开始丢检测框。

我解决这个问题的思路有三个层面。第一,在摄像头SENSOR的AE算法上下功夫,把曝光目标区锁定在货架区域,而不是全画面平均亮度,避免天空或地面过曝影响货架区域。第二,做输入归一化时,不要把亮度信息全丢给模型自适应,建议在采集链路增加一个简单的直方图均衡或CLAHE预处理,把亮度抖动压平。第三,量化校准集里刻意加入不同光照的图片,让量化过程保留对亮度变化的鲁棒性。这三招叠加之后,夜间和白天切换时候的检测稳定性有明显改善。

5.4 调试神器:订单事件回放系统

做无人货柜开发,最痛苦的不是模型训练,而是出了错单、漏单之后,没法快速定位原因。所以我做了一个“事件回放”工具,把每次购物的关键信息完整保存下来,包括:开门前基准帧图片、关门后收尾帧图片、营业状态的所有检测结果、槽位状态变化记录、最终生成的购物清单、支付结果。

每次发生用户投诉或者账单争议,我只需要调出这条订单记录,按照时间线把状态变化逐帧播放出来,就能很快定位是“检测漏框”“分类错误”“还是逻辑Bug”。这个工具也可以用来持续迭代模型:攒够一批误检样本后,把它们加入下一轮训练集,模型越跑越准。建议所有做货柜视觉结算的团队,一定要在开发初期就把事件回放能力做进去,不然后期排查问题会崩溃。

5.5 从Demo到可部署,差的不只是准确率

最后说一个心态上的体会。很多开发者在实验室里把YOLO跑通了,觉得“精度还行”,就直接拉到现场部署,结果上线第一天就错单不断。我踩过同样的坑,事后复盘发现,问题往往不在模型本身,而在工程链路:视频采集帧率不稳定、门磁信号抖动导致基准帧抓早或抓晚、网络协议栈阻塞导致结算上报超时、设备掉电后状态没有恢复完整。

我的建议是,无论你的模型精度是多少,先假设它会出错,然后把所有能兜底的工程机制都做上:状态级联、事件回放、云端复核、人工介入通道。视觉只是这个系统里的一环,真正让用户信任这台风柜的,是它出错之后能有多快被纠正过来。

回到这个系列的主题,YOLO在RK平台上的部署只是手段,自动结算才是真正创造商业价值的一公里。我个人做下来的经验是:先把一个SKU、一层货架、一个柜子的闭环跑通,不要一开始就追求全品类、全场景覆盖。把基准帧、区域映射、消失确认、收尾复核这几条核心逻辑吃透,再往后加功能就会顺利很多。后续还可以在这个框架上继续扩展:多人购物识别、手势轨迹追踪、甚至根据顾客拿取动作做营销推送,这些都是很有价值的演进方向,我们后面找机会再聊。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 8:04:25

AI网关字段命名决策:为什么选responses而非chat

1. 项目概述:一个被标题掩盖的真实技术决策现场“Codex 接中转站,wire_api 填 chat 还是 responses?”——这行字乍看像某次深夜调试时随手记下的笔记,实则浓缩了一个典型工程落地场景中的关键接口选型困境。它不属于教科书里的标…

作者头像 李华
网站建设 2026/10/21 8:03:58

WinAppDriver元素定位实战:用Accessibility Insights拆解UIA识别全流程

在Windows桌面应用自动化这条路上,走到“元素识别”这一步,基本算是进入深水区了。第一篇文章我们聊了环境搭建和Appium/WinAppDriver的基础跑通,很多同学反馈说脚本能跑起来,但一到写定位就头大——为什么我照着Chrome的那套F12大…

作者头像 李华
网站建设 2026/10/21 8:03:55

AgentRouter config.toml 配置实战:Codex 中转站的稳定接入指南

1. 项目概述:为什么一个 config.toml 文件值得单独写一篇实战笔记?“Codex 接中转站”这个说法,最近在某类技术社群里高频出现,但很多人第一次听到时会愣一下——Codex 是什么?中转站又指什么?它和 config.…

作者头像 李华
网站建设 2026/10/21 8:03:37

Notepad免安装版:零依赖UTF-8文本编辑器实战指南

简介:本资源为Notepad 7.5.1免安装版绿色软件包,面向程序员、运维人员及日常文本处理需求者,专为无管理员权限、需快速部署或跨设备轻量编辑的场景设计,有效替代系统记事本,解决编码效率低、大文件卡顿、编码兼容性差等…

作者头像 李华
网站建设 2026/10/21 8:03:36

从GitHub热门榜到技术选型:开源项目筛选与评估实战指南

2026年02月27日那天,我和过去几百个普通工作日一样,在晚上十点打开了GitHub热门榜单,准备花十来分钟看看当天冒出来的开源项目。这个习惯我坚持了好几年,算下来帮我省过不少调研时间,也避免过几次踩坑。很多人觉得热门…

作者头像 李华
网站建设 2026/10/21 8:01:51

Unity与Ionic混合开发:Android/iOS双向桥接与Vuforia AR集成实战

简介:这份资源面向需要在 Ionic 混合应用中嵌入 Unity 3D 内容的移动开发者,重点解决 Android 与 iOS 双端集成及两者间消息通信的问题,并已在带 Vuforia 插件的中等规模 Unity 商业项目中验证,适合具备 Cordova、Objective-C 与 …

作者头像 李华