这几轮调试跑下来,我发现很多做无人售货柜的朋友都有一个共同误区:YOLO模型能识别出“框”了,就觉得闭环差不多了。其实画框和收钱之间,隔着一整套业务逻辑。这篇我专门把开门前后商品比对、自动结算的核心思路完整写出来,包括状态机怎么设计、比对算法怎么写、结算引擎怎么处理异常,以及在RK平台上的工程落地细节,希望给正在做同类项目的同学省掉几个月的弯路。
先交代一下背景,我这套方案用的是瑞芯微RK平台(型号是RK3588)跑YOLO,摄像头装在柜体顶部,俯拍货架。系列前面的文章已经讲了模型的训练、转换成RKNN格式、NPU部署这些事。这一篇是整个系统里最有“生意味道”的部分——识别只是手段,算对钱才是目的。
1. 画完检测框离“能收钱”还差一柜子逻辑
1.1 检测框数量差,不是订单金额差
我遇到过这样一个场景:设备装好后,我拿着手机扫码开柜,拿了一瓶饮料,关门。后台把开门前的帧和关门后的帧都跑完YOLO,结果出来了:前后差了1个框,类别也对,检测框的置信度0.83,一切看起来都很“对”。但系统当时并没有生成订单,因为压根没有结算模块,我手里拿着饮料,后台只是显示出“检测到商品减少”。
这件事让我彻底意识到,YOLO输出的“少了1个框”和业务上“用户需要支付5块钱”之间,至少还隔着三个问题:
第一,少的这个框是什么商品?需要把类别ID映射到SKU,再映射到单价。第二,这个商品是不是真的被用户拿走了?比如检测漏了一帧,或者商品被手挡住了没检出,这会导致“假减少”。第三,用户可能拿了两瓶,又放回了一瓶,前后数量差可能是净变化1瓶,但用户手上的动作实际上是拿2放1,结算系统不能被“净变化”误导——应该扣几瓶?这些都不能靠目标检测本身回答,而是要靠业务逻辑去翻译和兜底。
1.2 弃用重力方案背后的零售逻辑
在做视觉方案之前,我也认真调研过重力感应售货柜。重力方案在行业里非常成熟:每个货道下方装压力传感器,商品被拿走时重量变化,后台通过重量差来判SKU。听起来很直接,但实际用起来有几个让我难以接受的痛点。
一是SKU一多,重量标定就非常痛苦。比如可乐500ml和可乐330ml,空罐重量不一样,饮料重量不一样,如果货道里放了好几种尺寸相似的饮料,重力传感器很难区分到底是拿走了哪个。二是商品不能悬空挂着卖,像挂钩上的袋装零食、盒装巧克力,重力方案基本抓瞎。三是补货的时候,补货员的手压一下货道,重量变化会导致误判,需要复杂的滤波逻辑。
视觉方案的好处是“直接看到商品本身”,SKU更新只需要更新模型,货道布局改动不需要硬件跟随。而且现在嵌入式NPU算力够用了,RK3588上跑一个YOLOv5s的INT8模型,单帧推理可以压到几十毫秒,完全扛得住。代价就是业务逻辑复杂度转移到了软件层,于是就有了这篇要讲的整套“比对+结算”框架。
2. 柜门不是开关,而是一套状态机
2.1 开锁信号到摄像头调度的链路
无人售货柜的整个识别流程,不能靠“录一段视频再慢慢分析”,而要有一个清晰的事件触发链路。我这边的硬件连接是:柜锁控制器通过串口与RK主板通信,用户扫码后云端下发开锁指令,柜锁打开的同时,串口会发出一个“OPEN事件”。
RK主板收到这个事件后,立即做两件事:第一,把当前状态从IDLE切到BASELINE状态;第二,通知摄像头采集线程开始连续抓帧。这里有个细节:事件信号到摄像头实际出帧之间会有几十毫秒延迟,而用户的手可能已经伸进柜子开始拿东西了,所以绝对不能等收到事件后再抓“第一帧”作为基准。正确的做法是摄像头采集线程常驻运行,内存里始终保留最近N帧的环形缓冲,事件触发后立刻从缓冲中取出事件发生前的几帧作为基准帧。
2.2 五个状态,把一桩交易拆得明明白白
整个购买过程,我把它拆成五个状态,状态机跑在RK主控的独立线程里:
| 状态 | 触发条件 | 核心动作 | 退出条件 |
|---|---|---|---|
| IDLE | 设备上电 / 结算完成 | 等待事件,低功耗巡检 | 收到开锁事件 |
| BASELINE | 收到开锁事件 | 取出前置缓冲帧,执行多帧基准检测,生成基准库存快照 | 基准快照生成完成 |
| SHOPPING | 基准快照完成 | 持续低频率检测,记录中间帧供追溯 | 收到关锁事件 |
| FINAL_SNAP | 收到关锁事件 | 延时等待柜体稳定,连续抓帧并检测,生成当前库存快照 | 当前快照生成完成 |
| SETTLING | 当前快照完成 | 比对基准与当前,计算差额,生成订单 | 订单写入本地库且状态置为IDLE |
这个状态机的设计初衷,是把“拿商品的模糊过程”变成“两个清晰时间点的确定性对比”。我没有把精力放在实时追踪用户的手部动作上,因为那需要多目标跟踪和动作识别,复杂度高且容易误判。业务上真正重要的是:开门前货架上有什么,关门后货架上有什么,这两个快照之间的差,就是用户要付钱的变化。
2.3 基准帧为什么必须抢在开门前
一开始我没有做环形缓冲,第一次实测就翻了车。当时的设计是:收到开锁事件后,摄像头立刻抓一帧当作基准。结果那一帧里,用户的手已经伸进柜子,正好抓着一瓶饮料,YOLO把“手+饮料”整体漏检了,导致基准库存里压根没有这瓶饮料。关门后库存自然也没有它,比对结果就变成“没有商品变化”,这笔订单直接丢失。
从那以后,我都坚持用事件前的缓冲帧做基准。摄像头采集线程以10fps的帧率持续压入环形缓冲,缓冲大小保持最近30帧。开锁事件到来后,选取事件时间戳前1秒到前0.3秒之间的帧,经过筛选后取3帧做多帧投票,得到基准库存。这3帧里,用户的手大概率还没到货架区域,画面干净,检测结果可信度会高非常多。
# 伪代码:环形缓冲取基准帧 class FrameBuffer: def __init__(self, maxlen=30): self.buf = deque(maxlen=maxlen) def push(self, frame, ts): self.buf.append((frame, ts)) def fetch_baseline(self, open_ts, before=(1.0, 0.3)): # 取开锁事件前 before[0]~before[1] 秒之间的帧 frames = [] for frame, ts in self.buf: if open_ts - before[0] <= ts <= open_ts - before[1]: frames.append((frame, ts)) return frames[:3] # 取满足条件的前3帧3. 商品比对:用“库存差集”算清楚拿了什么
3.1 别做像素差,要做检测结果差集
有不少人第一次接触“商品比对”时会想:把开门前和关门后的图像做像素级差分,有差异的区域就是被变化的商品。实际操作下来,这个方法在售货柜场景里根本不可用。柜门开关带来的震动会导致摄像头轻微位移,环境光照变化会导致像素值全局漂移,货架上的倒影、反光更是让差分图变成一团噪声。
正确做法是把问题抽象成“检测结果列表的差集”。每次快照不是一张图,而是一个商品实例列表:
[sku_id, x_center, y_center, width, height, confidence, frame_ts]基准快照 A 是开门前多帧检测聚合后的商品集合,当前快照 B 是关门后多帧检测聚合后的商品集合。业务要算的是:B 比 A 少了什么、多了什么。少了的是被拿走的,多了的是被放回的。整个过程完全不需要看像素,只看语义检测结果,光照变化的影响就被天然隔离掉了。
3.2 同款多件场景下的按类计数
最开始我天真地用“逐框匹配”来算差:把基准的每个框和当前的每个框按位置和类别的IOU做匹配,匹配不上的就是变化。这个逻辑在“每种商品只放一个”的测试环境里跑得很顺,一放到真实货架上就出问题:货架上放着3瓶一模一样的可乐,用户拿走一瓶后,剩下的两瓶因为补货、震动,位置有轻微挪动,我的逐框匹配算法完全找不到“哪一瓶消失了”。
后来我把策略改成了“按类计数求差”。具体来说,先聚合出基准快照中每个SKU的数量,再聚合出当前快照中每个SKU的数量,两个数量的差值就是变化量。对于3瓶可乐拿走1瓶的场景,基准数量是3,当前数量是2,差值-1,干净利落。
但按类计数有一个前提:模型分类必须细到SKU级别。如果你的模型只识别“可乐”,不管大瓶小瓶,那基准计数和当前计数就混在一起,结算时没法区分价格。所以我在训练阶段就把每个SKU单独设为一个类别:可乐500ml是一个类,可乐330ml是另一个类,罐装可乐又是一个类。这样虽然训练类别数增多了,但结算逻辑变得非常简单可靠。
3.3 让多帧投票干掉单帧抖动
YOLO在真实场景的单帧漏检率并不会像公开测试集那么漂亮。我的实测中,反光、遮挡、手部干扰、商品紧贴,都会导致某一帧漏检。如果只靠单帧检测结果去生成快照,库存数量会一帧一个样。因此我引入了多帧投票机制。
基准阶段取3帧,结算阶段取5帧,每帧独立跑YOLO检测,然后把多帧的检测结果按SKU做计数聚合,取每类商品出现次数的中位数或众数作为该快照的库存数量。比如某个SKU在5帧里分别检出了3、3、2、3、3个,那这个SKU的库存数量就记为3,因为众数是3。少数帧的漏检会被多数帧拉平。
多帧投票还有一个变体,就是按检测框的跨帧IOU做实例关联,把同一件商品在不同帧的出现合并成一条轨迹,然后统计“稳定存在”的轨迹数。前者简单粗暴,适合快速迭代;后者更稳,但计算量稍大。我目前采用的是先把检测框按IOU关联,再按类统计轨迹数的方案,这样可以顺便干掉“同一个商品被两个相邻框重复计数”的问题。
3.4 比对核心代码片段
下面这段代码是我在RK平台上的业务层核心,用Python写的,方便阅读理解,实际部署时细分成C++模块。
def aggregate_detections(det_frames, iou_threshold=0.5): """多帧检测结果聚合成一个稳定库存表""" sku_counts = defaultdict(int) sku_boxes = defaultdict(list) for frame_dets in det_frames: for det in frame_dets: sku = det["sku_id"] box = det["box"] # [x, y, w, h] if any(compute_iou(box, exist_box) > iou_threshold for exist_box in sku_boxes[sku]): continue # 和已有轨迹重叠,视为同一个商品实例 sku_boxes[sku].append(box) sku_counts[sku] += 1 return dict(sku_counts) def calc_diff(baseline_counts, final_counts): """计算库存变化,返回 (taken, put_back)""" all_skus = set(baseline_counts.keys()) | set(final_counts.keys()) taken = {} put_back = {} for sku in all_skus: delta = final_counts.get(sku, 0) - baseline_counts.get(sku, 0) if delta < 0: taken[sku] = -delta put_back[sku] = 0 elif delta > 0: taken[sku] = 0 put_back[sku] = delta else: taken[sku] = 0 put_back[sku] = 0 return taken, put_back这段代码的本质就是把“商品比对”简化为“聚合计数”和“差值计算”,完全绕开了像素层面的干扰。很多人看到这会觉得:“就这么简单?”对,就这么简单。核心逻辑本身不复杂,复杂的是它周围的边界条件和异常分支,接下来我要展开讲结算引擎。
4. 结算引擎:拿走的、放回的、换位置的钱怎么算
4.1 变化量到金额的映射规则
拿到 taken 和 put_back 之后,还不能直接算钱。幅度很有限:首先要排除“用户拿错又放回”的情况,其次要处理视觉误检带来的“幽灵商品”。
我设计的结算规则如下:
- 对于每个SKU,最终计入订单的扣款数量 = max(0, taken - put_back)。也就是“净拿走的数量”。
- 计算金额 = 净拿数量 × SKU单价。
- 如果 put_back 的数量大于 taken,说明用户拿进来的商品比拿出去的多,系统只记录“放回超量”,不产生扣款,但要触发库存异常告警(防止用户拿自家东西塞进柜子再关门)。
这里有一个容易忽略的点:taken 和 put_back 不能跨SKU对冲。用户可能拿了一瓶可乐(5元)又放回一瓶矿泉水(2元),这两个SKU的单价不同,必须分开算。可乐的taken是1,put_back是0,扣5元;矿泉水的taken是0,put_back是1,不扣钱但库存增加1。如果硬要跨SKU对冲,就会变成“拿贵的水,放回便宜的水”,少扣3元,这种漏洞不能留。
4.2 先拿后放的冲抵策略
用户的一个高频操作是:拿起来看看,觉得不对,放回原位。对于同一个SKU,视觉系统看到的是“基准+1,最终+1”,也就是没有净变化,那结算引擎就会认为什么都没发生,金额为0。这是完全正确的,不需要额外处理。
但有一种情况要小心:用户拿了一瓶A,又放回了一瓶A,但放回的位置不是原位置,甚至放到了另一个货道。这时候基准计数和最终计数如果都是“1瓶A”,结果也是0,依然正确,因为货架上的A数量没变。但如果用户拿了一瓶A,放回的是一瓶B,那么基准A=1、最终A=0,基准B=0、最终B=1,系统会扣掉A的钱,同时库存增加B。后台需要提示运营人员去处理这瓶“外来B”,比如从货架上取走,或者标记为异常资产。
我的建议是:对“放回超量”的商品单独维护一张异常资产表。这样运营人员打开后台就能看到“某号柜有1瓶非本柜资产的矿泉水”,而不是让它稀里糊涂地混进库存。
4.3 低置信度决策与人工复核通道
不管YOLO模型训练得多好,总会出现检测置信度不高的情况。如果所有低置信度结果都直接丢弃,漏检就会算错账;如果都算进去,误检也会算错账。我的做法是引入一个“审核队列”:
- 每帧检测结果会带上该SKU的平均置信度。
- 当结算引擎算出的净扣款数量 >= 1 时,把所有参与结算的检测结果写入一条待审核流水。
- 如果该笔订单所有SKU的平均置信度高于阈值(我这边设置0.7),且变化数量与预期匹配,则订单直接自动通过。
- 如果置信度低于阈值,或者出现了“库存增加超量”、“变化数量异常”(比如用户不拿反而多出3瓶),订单进入人工审核队列。
人工审核不是要人去看视频,而是展示证据链:一张基准快照图、一张当前快照图、检测框叠加、置信度数字、变化差值。运营人员几秒就能判断这笔订单到底有没有问题。这套机制上线后,误扣率降到了可接受范围,而且用户投诉时我们能拿出带证据的完整记录。
这里要特别说一下:千万不要试图通过提高置信度阈值来“消灭”审核需求。你把阈值调到0.9,确实误检少了,但会导致大量真实商品被漏检,漏检在业务里比误检更可怕——误检顶多多扣几块钱,漏检直接让用户白拿。阈值宁可低一些多走人工审核,也不要把漏检风险放大。
4.4 补货模式必须单独开一条路
补货员打开柜门补货的时候,状态机如果还按“顾客购买”的逻辑跑,那后果就是:货架从半空变成全满,比对结果会算出“用户往柜子里放了几十件商品”,这个“外来资产”异常会炸掉整个后台。所以我给补货员单独开了一个权限位。
补货员通过APP或者小程序发起补货申请,云端验证权限后下发“补货模式指令”,RK主板收到指令后把状态机从“购买”切到“PACKING”。PACKING模式下,开门、关门动作不会再触发结算比对,只做两件事:一是记录补货过程视频留痕,二是等关门后重新生成一次基准快照,把新货架状态更新为最新基准库存。
补货模式下有一件麻烦事:如果补货员把商品放歪了、叠压了,导致新基准快照检测数量不准确,那后续订单就会带着错误基准一路错下去。所以我还会在PACKING模式关闭后加一步“基准校验”:系统快速检测一遍货架,如果某个SKU数量和补货单数量对不上,就在后台标记“该货道需要人工复核”。宁可让运营人员多跑一步,也不能让错误基准沉淀到系统里。
5. RK 平台上的落地细节:帧率预算与三线程架构
5.1 事件驱动推理 vs 实时视频流推理
很多同学在设计嵌入式AI推理时,习惯按“视频流”的思维去做:摄像头每帧都送进NPU,实时跑检测。这在售货柜场景里非常浪费。售货柜大部分时间是闲置的,货架静止,商品不动,每帧检测结果几乎一样,纯粹消耗功耗。
我的方案是事件驱动推理:
- 待机状态下,NPU不处理图像,只有摄像头以较低的帧率(比如5fps)维持环形缓冲。
- 开锁事件触发后,RK主控通知推理线程“我要基准帧”,这时才把缓存的3帧送进NPU跑一次推理。
- 关门事件触发后,推理线程再跑5帧,同时把开锁和关锁之间的低频率巡检帧保留在内存里作为过程留痕。
- 整个交易过程中,NPU的推理次数大约只有8到10次,单次推理按30-50ms算,总耗时不到1秒,功耗也控制住了。
这样做还有一个额外好处:因为推理次数少,设备完全不需要昂贵的散热方案,外壳可以做得更紧凑。
5.2 三线程相互解耦的分工
RK主控上的软件架构我分成三个线程,彼此之间用队列通信,核心目的是保证“摄像头抓帧”和“NPU推理”互不拖累。
| 线程 | 职责 | 输出 |
|---|---|---|
| 采集线程 | 维护环形缓冲,响应状态机指令,按需抓帧 | 原始帧队列 |
| 推理线程 | 从队列取帧,做letterbox预处理,送NPU,NMS后处理 | 检测结果队列 |
| 业务线程 | 订阅检测结果,维护状态机,执行比对和结算,写本地库 | 订单、状态迁移 |
为什么必须解耦?我遇到过一次实际的教训:第一版图省事把所有逻辑写在一个线程里,结果某个时间点摄像头驱动偶尔阻塞了几百毫秒,NPU推理跟着排队,导致关门后结算延后了整整2秒。用户体验极其糟糕。后来改成三线程,摄像头只管抓图,推理只消费抓到的图,业务线程只看推理结果,哪怕推理偶尔慢一点,也只是结果晚到,不会反过来影响抓帧节奏。
5.3 letterbox、NMS 与逐SKU置信度调参
RK3588跑YOLO的工程细节上,最容易踩坑的有三个点。
第一个是letterbox。摄像头画面是16:9,YOLO输入固定640x640,很多人图省事直接用resize拉伸,结果检测精度掉得很明显。正确做法是等比缩放后填充灰边,也就是letterbox。而且填充的灰度值最好和训练时保持一致,否则会影响小目标的置信度。
第二个是NMS的阈值设置。我一开始用的是默认的IoU 0.45、Conf 0.25,但真实货架场景里,商品密集贴在一起,NMS会把两个紧贴的同类商品合并成一个框。这时候需要把IoU阈值适当调小,我这边调到了0.35左右,同时配合前面说的跨帧IOU关联,误合并问题基本解决。
第三个是逐SKU置信度阈值。不同SKU的检测难度天差地别:易拉罐表面反光强,饮料瓶透明,薯片袋子褶皱多。如果所有SKU用同一个Conf阈值,就会出现“好识别的SKU置信度0.9,难识别的SKU只有0.6但确实是正确结果”。我的做法是给每个SKU单独配一个检测阈值,透明包装类SKU的阈值放低到0.3,包装色彩鲜明的零食可以放到0.6。这些阈值存在一个JSON配置里,运营人员可以在后台按SKU调整,不需要重新训练模型。
5.4 掉电恢复和本地流水表
无人售货柜最怕的就是结算到一半断电,或者网络断连导致订单传不上去。所以我在设计时强制要求“订单落库先于订单上传”。
每次结算完成后,业务线程先把订单写入本地SQLite,状态置为“待上传”,然后再尝试往云端同步。同步成功才把状态改成“已上传”。如果设备中途断电,重启后业务线程会扫描本地库,把待上传的订单补传到云端。整个过程不需要人工介入。
本地流水表的核心字段如下:
| 字段 | 说明 |
|---|---|
| order_no | 本地生成的订单号 |
| device_id | 设备编号 |
| open_ts / close_ts | 开锁、闭锁时间戳 |
| baseline_json | 基准快照的检测结果 |
| final_json | 当前快照的检测结果 |
| diff_json | 比对后的订单明细 |
| amount | 交易金额 |
| status | 0待上传 / 1已上传 / 2审核中 |
| review_flag | 是否需要人工审核 |
这张表是我的“后悔药”。顾客投诉说“我根本没拿东西却扣了钱”的时候,我能一键把当时的基准图、结算图、检测框、置信度全部调出来,用证据说话。
6. 实测最容易翻车的四个瞬间
6.1 开门瞬间的光照突变
售货柜内照明和外部环境光的切换,会对YOLO结果产生肉眼可见的影响。尤其是放在户外或者商场入口处的柜子,白天阳光直射柜体,开门瞬间室内照明自动开启,商品表面的亮度、色温会在几百毫秒内发生剧烈变化。
我最初实测时,同一瓶饮料在基准帧的置信度是0.82,在结算帧只有0.65,导致等于是“同一商品被识别成了两个不同状态”,幸好我的多帧投票和跨帧IOU关联能兜住,不然后果就是“基准有3瓶,当前只检出2瓶”,白扣用户一瓶。
应对办法有两个层面:一是训练阶段加入亮度抖动、色彩抖动等数据增强,让模型对亮度变化不那么敏感;二是推理前对帧做一次简单预处理,比如直方图均衡化。但要注意,预处理只做在推理线程的输入侧,不要影响原始帧留痕,否则证据链就不真实了。
6.2 手伸进去带来的短时遮挡
用户的手伸进柜体拿商品的时候,遮挡是不可避免的。最要命的干扰是:手正好在那个瞬间压住了旁边另一个商品,导致那个商品在那一帧里没被检出,但下一帧手移开后商品又出现了。
如果结算时机不对,就可能出现“为什么这瓶饮料消失了?算用户拿走!”的假订单。解决这个问题的关键,是让结算帧的采集窗口避开“手臂运动期”。
我的经验是:关门信号到来后,先让状态机等待2秒,再开始抓结算帧。这2秒内用户的手基本已经从柜体内离开,商品不再被遮挡。同时,红外对射传感器可以辅助判断“柜门区域是否还有异物存在”。如果2秒后仍有遮挡信号,就继续等待,最多等待5秒,再不行就让订单进入人工审核,绝不硬算。
6.3 相似包装的细粒度分类
我踩过最难受的坑是两瓶包装几乎一样的饮料:同一个品牌的柠檬味和青柠味,瓶身颜色都是浅绿色,只是标签上的水果图案不同。YOLO模型在检测时经常把它们互相混淆,置信度还都不低。
后来我做了两件事:第一,把这两个SKU的照片大量收集起来,单独训练了一组细粒度分类层,在YOLO检测出“绿瓶饮料”之后,再用分类分支区分具体口味;第二,在货道设计上做了区域ROI划分,每个货道在配置时明确“这个位置只能放A或B”,把全柜多分类问题降级成局部二分类问题。
这个方法非常值得推荐:视觉识别不一定要靠一个模型解决所有问题,把问题拆小,用“检测模型+区域约束+细粒度分类”的组合策略,比单模型硬刚所有SKU要靠谱得多。
6.4 两件商品贴太近变成一个大框
货架上两罐饮料并排放置,间隙小到YOLO把它们圈进同一个检测框,这是另一个高频翻车点。我用YOLOv5s的时候尤其明显,小目标紧贴场景下NMS容易把一个框落在两个目标中间。
应对手段有三个层次:一是从模型侧解决,训练数据里加入大量的“两件紧贴”正样本,让模型学会区分;二是从后处理解决,当你发现某个类别的检测框宽高比明显异常偏大时,可以按面积比例估算里面大约有几件商品,但不直接作为准确数量;三是从业务侧兜底,如果估算结果落在一个区间且无法确定,直接标记“该SKU数量存疑”,进入人工审核。
我的原则是:宁可让运营人员偶尔看一眼,也不要因为一个框算错钱导致用户产生不信任感。无人零售本来拼的就是信任,一笔糊涂账会抵消掉一百笔准确交易。
7. 项目做完之后的几句大实话
站在整个系统的角度回头看,我发现真正决定这套无人售货柜能不能商业落地的,不是YOLO模型的mAP,而是业务层的容错设计。模型识别率再高,只要没有状态机、多帧投票、审核通道这些机制,一次异常就会让用户觉得“这东西不靠谱”。
如果问我最想给后来者什么建议,我会说三件事。
第一,别试图用识别模型解决所有问题。把问题拆成“状态机负责时机”、“比对引擎负责数量”、“置信度负责过滤”、“人工审核负责最后兜底”,每一层都做自己擅长的事。
第二,一定要留证据。每个订单都要能回溯到当时的基准帧、结算帧、检测框和置信度。这不仅是为了处理投诉,更是为后续算法迭代提供真实样本。
第三,阈值设置是个长期工程,不要迷信默认值。我在这个项目里迭代了一个多月,反复调整每个SKU的置信度阈值和各类后处理参数,才让全自动通过的订单比例稳定下来。这个过程不能急,慢慢磨出来的数据才扎实。
再往下走,我打算把视觉方案和重力传感器做一次双模态融合,用重量粗筛加上视觉精识别,在结算置信度上再上一道保险。那部分内容等跑出更完整的测试数据,我再单独写一篇来分享。