智能售货柜视觉流水线:IPC拉流、视频抽帧与YOLO部署实战
前一阵一直在捣鼓智能售货柜的视觉识别方案从最开始的 IPC 拉流到中间的视频抽帧再到最后的 YOLO 识别完整跑通了一条“摄像头 → 算法 → 业务结果”的流水线。这套流程看起来不复杂真正落地的时候坑基本都藏在细节里拉流怎么拉才稳定、抽帧怎么抽才不会影响画面、YOLO 推理怎么在边缘设备上跑起来不掉帧……每一项单独拎出来都是折腾人的活。这篇文章就来完整复盘一下这套售货柜实战流水线把每个环节的选型思路、关键参数、踩坑记录都摊开写清楚。不管你是刚接触 IPC 和 YOLO 的新手还是已经在做边缘部署的老手这套从零到一的经验应该都能帮你省下不少时间。项目基于常见的嵌入式平台比如 RK3588 或 x86 工控机加普通网络摄像头整套逻辑可以平移到绝大多数售货柜、自助设备场景。1. 整体设计与思路拆解为什么是“拉流 → 抽帧 → YOLO”三步走1.1 售货柜视觉识别到底是什么场景售货柜和普通监控最大的区别在于监控是“一直录、有空再查”售货柜是“实时看、立刻判断”。顾客打开柜门拿走商品系统需要在几秒内给出“拿了什么、拿了几件”的结论才能联动扣款。这意味着视觉链路不是离线的视频分析而是实时性要求很高的在线识别。实时识别最简单的做法是直接调摄像头 SDK 拿单帧但问题是市面上售货柜用的 IPC网络摄像机品牌太多海康、大华、宇视甚至一些杂牌设备SDK 各不相同。与其逐个适配 SDK不如统一走 RTSP 拉流把摄像头当成一个“网络视频源”来对待。这样无论底层是什么牌子只要支持标准 RTSP 协议就能用同一套代码处理。整体链条设计成三步IPC 通过 RTSP 协议把视频流传出来中间层按需抽帧把 H.264/H.265 的编码流解码成 YOLO 能用的 RGB 图像最后送入 YOLO 模型推理得到商品类别和位置。每一步之间用队列解耦拉流线程、抽帧线程、推理线程各干各的互不阻塞这样即使某一帧推理超时也不会导致拉流缓存被堵死。1.2 为什么不在摄像头端直接跑识别有人可能会问现在很多 IPC 自带智能分析功能为什么还要在售货柜的工控板上跑 YOLO这里有三个现实原因。第一售货柜的识别目标不是“人”或者“车”而是具体的商品比如可乐、薯片、方便面。摄像头自带的智能分析基本都是人形、车辆、绊线这类通用场景不可能内置售货柜的商品模型。第二售货柜需要识别的是柜内商品的增减变化摄像头视角通常是从上往下或者侧面斜拍这种角度也不是 IPC 自带算法擅长的。第三商品会不断上新、下架识别模型要跟随商品库更新摄像头端是封闭系统根本无法灵活迭代。所以核心思路是IPC 只负责“看得见”后端算力负责“看得懂”。推荐的做法是把 IPC 安装在柜内顶部视角覆盖全部层板通过 RTSP 流把画面实时传到售货柜的主控板主控板上的 YOLO 模型专门负责商品识别。1.3 软硬件选型和整体架构图不用花哨实用为主先交代一下我手上这套跑通的配置读者可以参考摄像头海康威视 DS-2CD3T86FWDV2-I3S800 万像素支持 H.265RTSP 地址格式标准主控平台瑞芯微 RK3588 开发板8GB 内存版本带 6 TOPS NPU推理框架RKNN-Toolkit2 RKNN RuntimeYOLOv8s 模型导出为 RKNN 格式拉流/解码库FFmpeg 5.1自己编译开启 rkmpp 硬件解码业务逻辑层Python 3.8 OpenCV串接各环节为什么要选 RK3588 而不是直接上 x86 工控机主要考虑三点功耗、成本和稳定性。售货柜通常放在室外或者半室外环境散热条件有限一台满载 65W 的 x86 工控机对散热是很大的考验而 RK3588 整板功耗一般控制在 10W~15W发热小很多。6 TOPS 的 NPU 跑 YOLOv8s 的 INT8 量化模型单帧推理时间大约 35ms~50ms完全够用。另外强调一点售货柜识别场景的“延迟”不是指单帧推理时间而是指“顾客从拿走商品到系统确认商品”的整体耗时这个链路里拉流延迟、抽帧排队、推理耗时、结果上传都要算进去。用队列解耦之后每个环节都在流水线上并行工作整体延迟能做到 800ms 到 1.2s顾客体验基本无感。2. IPC 拉流实操RTSP 协议细节与稳定连接的几个关键点2.1 RTSP 拉流基础主码流还是子码流RTSPReal Time Streaming Protocol是 IPC 最常见的实时流媒体协议本质上是一个“带外控制 带内传输”的组合控制命令走 RTSP音视频数据走 RTP。拉流的时候客户端向摄像头发起 RTSP 请求摄像头返回 SDP 描述包含编码格式、分辨率、码率等信息然后双方协商传输方式TCP 或 UDP之后 RTP 包就源源不断送过来。选择主码流还是子码流是第一个要做的决定。主码流分辨率高、清晰度好但码率也高子码流分辨率低适合预览。售货柜场景要识别商品细节比如瓶装饮料的标签、包装袋上的文字分辨率太低根本认不出来。我这里实测下来主码流 2560×1440 或 1920×1080 都行低于 720P 识别准确率就明显下降。但直接用主码流有一个问题带宽和算力开销都上去了。H.265 的 1080P 主码流一般控制在 2Mbps~4Mbps本地用网线直连问题不大如果是无线传输就要谨慎测试。建议第一次接摄像头的时候先用 VLC 或 ffplay 拉一下流看看画面的实际分辨率和清晰度再决定主码流参数。2.2 FFmpeg 拉流代码实现与参数调优拉流的核心代码用 FFmpeg 的 C API 或者 Python 的 OpenCV 都能实现。OpenCV 的VideoCapture底层也是 FFmpeg但封装之后很多参数暴露不出来调试不方便。生产环境我更推荐直接用 FFmpeg 命令行或 FFmpeg C API把 RTSP 流转成原始帧再喂给推理模块。先看 Python 侧最常见的一段拉流代码import cv2 cap cv2.VideoCapture( rtsp://admin:password192.168.1.64:554/Streaming/Channels/101, cv2.CAP_FFMPEG ) # 关键参数关闭 OpenCV 自动优化打开 TCP 传输 cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) cap.set(cv2.CAP_PROP_FPS, 30) while True: ret, frame cap.read() if not ret: print(拉流失败尝试重连...) cap.release() cap.open(rtsp://admin:password192.168.1.64:554/Streaming/Channels/101) continue # 这里是抽帧和推理的入口 process_frame(frame)这段代码能跑通但有几个隐患cap.read()默认是阻塞的如果网络不好可能卡住几秒OpenCV 内部会有一个缓冲队列拉流速度大于处理速度时队列里堆满旧帧就会造成“画面拖影”和“处理延迟越来越大”。解决办法是设置CAP_PROP_BUFFERSIZE为 1只缓存最新一帧处理完一帧再拉下一帧。再看一个用 FFmpeg C API 的简化流程适合嵌入到 C 或者通过 ctypes 调用的场景AVFormatContext* fmt_ctx NULL; AVPacket packet; avformat_open_input(fmt_ctx, url, NULL, NULL); avformat_find_stream_info(fmt_ctx, NULL); // 关键设置 RTSP 传输方式为 TCP AVDictionary* opts NULL; av_dict_set(opts, rtsp_transport, tcp, 0); avformat_open_input(fmt_ctx, url, NULL, opts);用 TCP 还是 UDP 传输是我踩过的一个很大的坑。UDP 延迟低但丢包严重尤其是无线环境画面容易出现马赛克、花屏TCP 虽然延迟略高但可靠基本不会丢包。售货柜场景帧率要求不高5~10FPS 足够但对画面完整性要求高——一帧如果丢了商品区域那这一帧等于白算。所以强烈建议rtsp_transport设置成tcp。2.3 断线重连与心跳机制IPC 拉流稳定性的核心IPC 不像 USB 摄像头一直在线断网、IPC 重启、供电波动都会导致 RTSP 连接断开。如果代码不做断线重连整个识别流水线就瘫痪了。但重连逻辑不是简单while循环重新 open 就行要考虑几个问题第一IPC 可能在重启过程中暂时不响应 RTSP 请求重连频率太密会加重 IPC 负担甚至导致 IPC 死机。第二重连之后要重置解码器状态否则可能出现花屏。第三要在业务层做“拉流中断”的告警通知运维人员处理。一个实用的重连策略是“指数退避 最大重试次数”import time max_retry 5 retry_interval 1 for attempt in range(max_retry): cap cv2.VideoCapture(rtsp_url, cv2.CAP_FFMPEG) if cap.isOpened(): break time.sleep(retry_interval) retry_interval min(retry_interval * 2, 30) else: raise RuntimeError(拉流重试失败检查摄像头网络)还有一个容易被忽略的设置RTSP 的超时时间。默认情况下 FFmpeg 的 socket 超时时间可能长达 60 秒也就是说摄像头已经断了但 FFmpeg 还卡在那里不知道。需要在avformat_open_input前设置timeout参数单位为微秒比如设置为 5 秒AVDictionary* opts NULL; av_dict_set(opts, rtsp_transport, tcp, 0); av_dict_set(opts, stimeout, 5000000, 0); // 5秒超时这样一旦网络断开5 秒内就能感知并触发重连逻辑而不是傻等一分钟。3. 抽帧策略视频怎么抽帧才能不影响画面和识别效果3.1 抽帧不是“随便丢帧”关键帧概念要搞清楚摄像头输出的是一连串视频帧如果每一帧都送去推理RK3588 的 NPU 会吃不消。售货柜场景下商品被拿走的那一瞬间动作很快但识别并不需要抓取每一帧通常每秒抽 2~5 帧就足够。这里的关键不是“抽多少帧”而是“什么时候抽”。H.264/H.265 视频流有 I 帧、P 帧、B 帧的概念。I 帧是完整的关键帧P 帧和 B 帧是预测帧依赖前面的帧才能还原出完整画面。如果直接在最底层丢包把 P 帧全丢了只留 I 帧那解码器会报错画面根本出不来。所以抽帧操作一定要在“解码完成之后、用完丢弃”的层面去做也就是先解码出完整的 YUV/RGB 帧再决定这一帧要不要送推理。我这里实现了一个简单的“帧调度器”拉流线程每拿到一帧解码之后放入一个无锁环形队列抽帧线程每隔固定间隔比如 200ms从队列里取最新一帧丢弃中间的旧帧送入推理。这样既保证了解码连续性又控制了推理的频率。3.2 抽帧频率怎么定动态抽帧比固定抽帧更优固定每隔 200ms 抽一帧5FPS看似简单实际场景下并不理想。顾客拿商品的动作慢的时候5FPS 足够但快速拿取时可能一个动作只持续 300ms固定 5FPS 只能抽到 1~2 帧容易漏检。更聪明的做法是“动态抽帧”先以较低频率比如 2FPS抽帧当检测到画面有变化用帧差法判断像素变化比例时自动提升到 10FPS 抽帧等画面恢复静止后再降回来。帧差法开销很小对 CPU 基本无感def has_motion(prev_gray, cur_gray, threshold10): diff cv2.absdiff(prev_gray, cur_gray) mean_val diff.mean() return mean_val threshold, mean_val不过动态抽帧也有副作用推理频率不稳定导致结果输出节奏不可控。如果你要考虑稳定性优先那就固定 5FPS保证每个周期内都能有稳定的识别结果输出。我个人的建议是MVP 阶段先固定 5FPS跑通之后再优化成动态抽帧。3.3 抽帧与推理的并行队列缓冲与丢帧策略抽帧和推理如果串行执行一帧推理耗时 40ms加上解码、预处理耗时整体流水线一秒最多处理十几帧抽帧频率稍微高一点就互相卡住。正确的姿势是用队列解耦。抽帧线程把 RGB 帧放入队列推理线程从队列里取帧推理。队列长度要限制比如 5队满时直接丢弃新帧而不是阻塞抽帧线程。为什么要丢新帧而不是丢旧帧因为抽帧的目标是“尽量抓取最新画面”如果队列里堆了 5 帧旧画面顾客早就把商品拿走了推理结果就过时了。队满丢新帧能保证队列里的帧始终是最新的。有一个 Python 的queue.Queue(maxsize5)就能实现这个逻辑但put_nowait在队满时会抛异常要捕获后丢弃。生产环境建议用collections.deque(maxlen5)也可以或者直接用 C 的无锁队列能减少 GIL 和加锁开销。我实测 Python 的queue在多线程下性能也够用瓶颈不在这里。3.4 原理解读为什么抽帧不会“影响画面”很多人担心抽帧之后画面变卡、变模糊这里要澄清一下如果抽帧发生在“解码之前”也就是把部分帧丢弃不送给解码器那确实会导致画面跳帧看起来一卡一卡但抽帧发生在“解码之后”丢弃的只是“已经解码但还没被使用的帧”整个画面播放是流畅的只是算法侧少看了几帧。这就好比你站在路边数经过的车眼睛看到每辆车解码但只在每分钟的第 10 秒记录一次抽帧这并不会影响你看到车流只是记录密度变低了。在售货柜场景里我们真正关注的是“商品数量变化”不需要每一帧都分析抽帧降低的是计算量不影响画面本身的输出。如果还需要把画面实时传到后台做人工监看那监看通道走解码后的完整帧识别通道走抽帧后的低帧率帧两路互不干扰。4. YOLO 识别核心环节模型选型、数据准备与推理加速4.1 YOLOv8s 还是 YOLOv5s售货柜场景的模型选择逻辑售货柜识别属于典型的目标检测任务输入一张柜内画面输出画面中所有商品的类别和位置。YOLO 系列是这类任务的首选但 YOLO 版本太多了YOLOv5、YOLOv8、YOLOv9、YOLO11……到底选哪个这里的核心考量是“算力平台”。RK3588 的 NPU 支持 INT8 量化的 RKNN 模型但对模型的算子支持有兼容性要求。YOLOv8 的 C2f 模块在 RKNN 上支持良好YOLOv5 的 C3 模块也很成熟两者都能顺利转换。但 YOLOv9 用到了 GELAN 和 PG 结构YOLOv10 的 NMS-free 结构在 RKNN 转模型时可能会遇到算子不支持的问题。为了稳定我最终选了 YOLOv8s。YOLOv8s 的参数量和计算量在“精度-速度”曲线上比较均衡COCO 精度约 44.9% mAP单帧 INT8 推理 35ms完全满足售货柜场景。如果追求更高精度可以换 YOLOv8m但推理时间会翻倍如果追求更快的速度可以换 YOLOv8n但小目标商品比如口香糖的召回率会下降。4.2 数据准备标注格式与训练技巧售货柜识别模型的训练数据收集比很多人想象中麻烦。最大的问题是商品类别会不断变化——今天卖可乐明天可能上架一款新出的功能饮料。所以模型要设计成“可增量更新”的最好的做法是给每个 SKU 一个固定类别 ID新增商品时只增加类别 ID不需要重新训练整个模型。为了减少标注工作量可以先用半自动标注用现成模型做预标注人工修正之后再入训练集。标注格式建议直接用 COCO 格式JSONYOLO 训练时再转成 YOLO 格式txt。网上有大量 kitti 标注转 YOLO、COCO 转 YOLO 的工具脚本不用重复造轮子。关键点是一张图里同一个商品的不同实例都要标注且框要尽量贴合商品边缘不能太大也不能太小。标注框的大小直接影响 YOLO 的 anchor 匹配效果。训练参数方面我提供一个基础模板# dataset.yaml train: ./datasets/train/images val: ./datasets/val/images nc: 10 # 商品类别数 names: [coca_cola, pepsi, sprite, fanta, water, ...]基础训练命令yolo detect train \ datadataset.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ device0epochs 不用太多100 轮足够关键是要做数据增强。售货柜场景里光照变化很常见增强策略里建议重点加入hsv_h、hsv_s、hsv_v和随机亮度、对比度调整模拟柜内 LED 灯和日光交替的情况。我还加了一点mosaic1.0和mixup0.2对小目标检测有提升。4.3 RKNN 模型转换与 INT8 量化模型在 PC 上训练好之后不能直接在 RK3588 上跑需要转成 RKNN 格式。转换流程大体是PyTorch 模型 → ONNX → RKNN中间有几处容易踩坑。先装 RKNN-Toolkit2在 x86 PC 上跑不是跑在板子上然后写转换脚本from rknn.api import RKNN rknn RKNN() # 加载 ONNX 模型 rknn.load_onnx(model./yolov8s.onnx) # 量化指定量化数据集 rknn.config(mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588) rknn.build(do_quantizationTrue, dataset./dataset.txt) rknn.export_rknn(./yolov8s.rknn)dataset.txt是量化校准图片的路径列表一般放 100~200 张有代表性的柜内图片覆盖不同光照条件。如果 calibration 图片太少少于 50 张量化后精度可能掉 2~3 个点这会直接影响商品识别准确率。所以量化图片一定要“多而全”宁可多收集一些。转 RKNN 之后在板子上推理的流程是读取 RKNN 模型 → 推理输入预处理letterbox → 输入 NPU → 获取输出 → 后处理解码框、NMS。注意 RKNN 的输出是三维或四维的原始特征图YOLOv8 的输出头是解耦头后处理需要自己写。网上有很多 RKNN 版 YOLOv8 后处理代码可以直接参考核心是理解输出的[batch, 4 num_classes, grid_h, grid_w]的格式。4.4 推理后处理与置信度阈值影响识别效果的关键参数YOLO 模型输出的原始数据不能直接用需要经过后处理才能得到商品框。后处理包括解码预测框中心点宽高、过滤低置信度框、NMS非极大值抑制去重、过滤不合理的框比如太小、太靠边缘。直接给参数参考conf_thres置信度阈值建议 0.35~0.5。设太低误检多比如把空瓶认成商品设太高漏检多特别是小目标。售货柜场景我设为 0.4平衡了误检和漏检。iou_thresNMS 阈值建议 0.45~0.5。商品之间有遮挡时NMS 阈值太低会合并两个不同商品太高会保留大量重复框。最小框过滤宽或高小于整图尺寸 2% 的框直接丢弃。柜内画面中正常商品最小也有 30×40 像素左右小于这个尺寸的框大概率是误检。还有一个容易忽略的 trick柜面反光会导致同一商品在不同位置呈现完全不同的颜色和纹理。处理办法是训练数据里加入柜面反光样本并且在推理时对同一帧做两次检测原图和水平翻转图取置信度高的结果但这样推理时间翻倍。MVP 阶段不建议上先把基础后处理做好。5. 完整流水线实操从摄像头到业务结果的全链路打通5.1 流水线的整体代码框架把三个阶段串起来之后代码框架大概是这样的Python 伪码import threading import queue import cv2 frame_queue queue.Queue(maxsize5) def capture_loop(rtsp_url): 拉流线程不断读帧把最新帧放入队列 cap cv2.VideoCapture(rtsp_url, cv2.CAP_FFMPEG) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) while True: ret, frame cap.read() if not ret: reconnect() continue if frame_queue.full(): try: frame_queue.get_nowait() # 丢弃旧帧 except queue.Empty: pass frame_queue.put(frame) def infer_loop(): 推理线程取帧预处理推理后处理输出结果 rknn load_rknn_model(yolov8s.rknn) while True: frame frame_queue.get() if frame is None: continue results rknn_inference(rknn, frame) business_logic(results) # 启动线程 t1 threading.Thread(targetcapture_loop, args(rtsp_url,)) t2 threading.Thread(targetinfer_loop) t1.start() t2.start()两个线程之间通过队列解耦各跑各的逻辑清晰也方便扩展。比如要增加一路摄像头只需要再加一个capture_loop线程和对应的frame_queue推理线程改成从多个队列读取或者为每路摄像头分配一个推理实例。5.2 业务逻辑层怎么判断“商品被拿走”YOLO 识别出来的是“当前画面中有哪些商品”但业务层要回答的是“顾客拿走了什么”。这一步需要做帧间对比核心是“库内更新机制”。常见的做法是“检测结果快照”对比法系统启动时对空柜或满柜拍一张基线图识别出每个商品的位置和类别存为“初始库存”。每次识别到新结果和上一次识别结果做对比某个位置的商品类别从 A 变成空说明 A 被拿走了从空变成 B说明放回了 B。为了保证不误判同一变化要连续出现 2~3 次才确认防抖。这里还涉及一个重要概念置信度投票。单个帧的识别结果可能有误差比如反光导致某帧把可乐误检为空瓶。解决办法是滑动窗口投票每 5 次识别结果中同一位置同一商品出现 ≥3 次才确认库存状态。这和监控领域的目标跟踪思路类似但比跟踪要简单因为售货柜场景里商品基本是静止的只有“变化瞬间”需要关注。5.3 联动开门信号与扣款逻辑售货柜识别完成后业务层要做的是把“识别结果”转成“扣款指令”。完整流程是收到“柜门打开”信号通常由门磁或电磁锁控制器上报。锁定当前库存快照。持续识别直到“柜门关闭”信号到来。柜门关闭后对这段时间内所有识别结果做汇总计算最终库存变化。变化量 → 生成订单 → 调用支付接口完成扣款。这里有个细节柜门关闭后的 1~2 秒内识别系统还在处理最后几帧画面所以扣款动作要延迟触发等识别结果稳定后再执行。实现上可以在关门信号到来后增加一个 1.5 秒的“缓冲期”期间继续采集识别结果然后统一汇总。5.4 交付、部署与监控流水线跑起来还不够要看得住流水线开发完成后部署到实际售货柜上还要做好监控。我的经验是至少监控四个指标拉流帧率实时统计每秒实际拿到的帧数低于阈值说明网络或摄像头有问题。抽帧帧率抽帧线程实际的抽帧频率判断是否按预期工作。推理耗时模型单帧推理的平均耗时和 P95 耗时超过阈值说明 CPU/NPU 过载。识别结果稳定性单位时间内“无法识别任何商品”的帧占比占比过高说明摄像头角度或光照出了问题。这些指标可以定期上报到云端在运营平台上做可视化。没有监控的视觉系统上线后只会变成黑盒出了问题根本无从排查。强烈建议 MVP 阶段就把监控加上哪怕只是简单的日志也比完全没有好。6. 踩坑实录IPC 拉流和 YOLO 落地中最常见的几个问题6.1 拉流端问题排查速查表整理一下我在 IPC 拉流阶段遇到过的典型问题方便读者对照排查现象可能原因排查思路画面花屏、马赛克UDP 丢包严重切换 TCP 传输 (rtsp_transporttcp)拉流卡死几秒后恢复RTSP socket 超时设置太长设置stimeout5000000偶发断流日志无报错网络抖动或 IPC 主动断开增加心跳检测断线自动重连画面延迟越来越大OpenCV 缓冲队列积压旧帧设置CAP_PROP_BUFFERSIZE1重新连接后花屏解码器状态未重置重连时销毁并重建解码器6.2 推理侧问题与精度调优实战推理端最容易遇到的问题是“模型训练时精度很高部署到售货柜上精度骤降”。这一步先别怀疑模型优先检查三件事第一预处理是否一致。训练时用letterbox缩放推理时如果直接resize图片变形会导致检测框偏移和精度下降。第二色域转换是否一致。OpenCV 读进来是 BGR模型训练时如果用的是 RGB推理时必须在预处理里cvtColor转换否则颜色错乱会影响浅色商品识别。第三量化校准数据是否覆盖实际场景。很多 RKNN 量化精度掉点是 calibration 图片太单一导致的多加一些不同光照下的柜内图就能改善。量化精度问题还有一个隐蔽原因模型输出的 nms 后处理在 RKNN 里是用 CPU 实现的NPU 只做前向推理如果 CPU 后处理代码写得不对也会导致结果异常。建议先在 PC 上用 ONNX Runtime 跑一遍同一张输入图对比 RKNN 的后处理输出两者应该高度一致不一致就是后处理实现有 bug。6.3 热词“yolo 后处理流程”和“yolo 损失函数”再补两句很多读者看 YOLO 教程时会被“损失函数”“后处理流程”这些概念劝退。其实从落地视角看损失函数是训练阶段的事推理阶段只需要关心“输出格式”和“后处理流程”。但理解损失函数有一点帮助YOLO 的损失包含分类损失、定位损失和目标性损失过了训练阶段部署时这些损失和模型无关。真正要烂熟于心的是“输出张量怎么解析”。YOLOv8 的输出后处理流程可以概括为将输出的特征图按照 stride 映射回原图坐标解码得到框的中心点和宽高然后进行 sigmoid 得到类别概率过滤低置信度框最后 NMS 去重。把这一步写成工具函数换型号、换数据集都能复用。6.4 关于“yolo部署”的一些经验补充最后补充一点部署层面的经验很多人在 Jetson Nano、RK3588、树莓派这些平台上部署 YOLO第一反应是“模型越大越好”其实边缘设备上“能跑得动”比“精度高”重要得多。售货柜场景里商品类别通常只有 10~20 类YOLOv8n 或 YOLOv8s 完全够用强行用 YOLOv8l 只会让推理耗时飙升、功耗增加得不偿失。另外如果后面想进一步提升帧率可以考虑“检测 跟踪”联动检测不需要每个抽帧周期都做可以用 ByteTrack 之类的轻量跟踪器对检测到的商品框做跨帧关联检测每 5 帧做一次跟踪每帧做一次这样整体算力利用率更高。这也是很多工业级售货柜方案在用的优化路径。7. 后续优化方向从能用到好用这套流水线跑通之后还有三个方向可以继续推进。第一个方向是“误检消解”。售货柜场景最大的敌人是反光、柜门玻璃指纹、光线变化这些都会带来偶发误检。除了调阈值还可以训练一个小的分类器纠偏对检测出的每个框做二次分类商品 vs 非商品能过滤掉很大一部分反光导致的误检。这个分类器可以是 MobileNet 级别的轻量模型跑在 CPU 上都不吃力。第二个方向是“多柜协同”。一个店铺通常有多个售货柜可以在边缘侧部署一个管理进程统一调度多个柜子的推理任务共享一个模型实例降低单柜硬件成本。实践中 RK3588 的 NPU 可以同时加载两个模型实例一个给柜 A一个给柜 B资源利用率高很多。第三个方向是“云边协同”。把边缘侧识别结果和云端商品销售数据打通可以做到“自动学习新商品”当系统在某个柜子上连续 n 次检测到低置信度但位置稳定的未知物体时自动截取图片上传云端人工确认后加入训练集实现模型在线更新。这个闭环跑通之后售货柜的运营成本会大幅下降。从 IPC 拉流到抽帧再到 YOLO 识别这条流水线的每一步都有值得深耕的细节。我在这套项目里踩过的坑基本都在上面这几章里了。如果你也正在做类似的智能柜项目建议先照着这套流程把最小闭环跑通再逐步优化模型和业务逻辑。毕竟真正上线之后你会发现自己 80% 的时间都花在了处理那 20% 的异常场景上而提前把拉流稳定性、抽帧策略、后处理参数这些基本功打牢能让你在遇到异常时从容很多。最后再分享一个小技巧在整个流水线里给每一帧都打上时间戳从拉流到识别结果输出的全链路耗时就能精确计算。你会发现延迟最大的环节往往不是模型推理而是图像解码和队列传输。把时间戳日志加上优化就有了方向。