1.引言
1. 这套引擎解决什么问题?
把"能在电脑上跑的算法"变成"能在盒子上长期跑的应用",且换芯片不改业务代码。 一次接入、多平台复用,补上模型管理、编排、报警、事件这些"工程脏活"。
2. 它是怎么解决的?
上层一套流程编排,下层把解码、缩放、推理、后处理抽象成统一接口,按芯片各写一份实现;构建时只编入一个后端,模型随平台生成。
3. 跑起来是什么效果?
官方给了一份多平台 72 小时连续观测:前三款按 8 路 / 16 个任务绑定、RV1126B 按 4 路 / 8 个绑定配置,四款芯片平均 5.09~5.23 FPS/路 ,4320 个采样点保留 4316 个(99.91%),零丢弃、零严重事件;并发混合负载下算能 BM1688 跑到 16 路 / 32 个业务绑定,瑞芯微 RV1126B 为 4 路 / 8 个绑定。x86 上不需要任何 NPU,一条 docker compose 就能在浏览器里看到带框的实时检测结果:

图:x86 模式跑通的"未戴安全帽"检测任务,左侧实时画面带检测框与 OSD 叠加,右侧为算法结果列表。无需边缘硬件即可复现。
多平台 72 小时受控观测(每业务 5 FPS,本地回放输入):
| 平台 | 配置路数 | 任务绑定 | 采样点 | 最低 / 平均 / 最高 FPS | 最大丢弃 | 峰值 CPU / 内存 / 磁盘 | 结论 |
|---|---|---|---|---|---|---|---|
| 算能 BM1688 | 8 | 16 | 4316 / 4320 | 4.68 / 5.086 / 5.49 | 0 | 30% / 44% / 96% | PASS |
| 算能 CV186X | 8 | 16 | 4316 / 4320 | 4.54 / 5.085 / 5.29 | 0 | 43% / 44% / 96% | PASS |
| 瑞芯微 RK3576 | 8 | 16 | 4316 / 4320 | 5.00 / 5.098 / 5.17 | 0 | 46% / 30% / 15% | PASS |
| 瑞芯微 RV1126B | 4 | 8 | 4316 / 4320 | 4.85 / 5.230 / 5.37 | 0 | 41% / 41% / 47% | PASS |
单任务容量矩阵(最后一列是"通过的最大路数",≥ 表示配置上限内都过了):
| 平台 | 任务 | 24 FPS | 10 FPS | 7 FPS | 5 FPS |
|---|---|---|---|---|---|
| BM1688 | 人员检测 | ≥8 | ≥16 | ≥16 | ≥16 |
| BM1688 | 未戴安全帽(检测+分类) | ≥7* | ≥14* | ≥16 | ≥16 |
| CV186X | 人员检测 | ≥8* | ≥15* | ≥16 | ≥16 |
| CV186X | 未戴安全帽 | 6 | ≥13* | ≥16 | ≥16 |
| RK3576 | 人员检测 | 6 | 12 | ≥16 | ≥8† |
| RK3576 | 未戴安全帽 | 6 | 10 | 12 | ≥16 |
| RV1126B | 人员检测 | 2 | ≥4 | ≥4 | ≥4 |
| RV1126B | 未戴安全帽 | 2 | ≥4 | ≥4 | ≥4 |
压测总览卡片(49 个独立小模型用例 + 72 小时长跑 + VLM 验证):

2.项目拆解 & 分析
- 项目架构

图:从接入层到硬件层的六层结构。注意最底部那行字------一个二进制只含一个推理后端,这是理解整个项目的钥匙。
整个仓库 1500+ 个源文件(src/ 下),主体是 C++17,前端是 Vue 3。源码目录划分得很干净:
| 目录 | 干什么的 | 我的备注 |
|---|---|---|
src/nn |
推理后端抽象 (core / node / device / pipeline / guard) | 全项目最值钱的部分 |
src/media |
解复用、解码、编码、帧处理、OSD 叠加 | 按平台各写一份,工厂选择 |
src/mem |
设备上下文 + 定长块内存池 | 边缘端内存碎片是隐形杀手 |
src/infer |
检测/分类/跟踪/识别/OCR/VLM 的统一封装 | 业务侧的门面 |
src/flow |
通道、任务、动作链、报警、区域判断 | 编排层的执行体 |
src/service |
服务注册与业务服务域(任务/模型/事件/系统...) | 启动入口在这里 |
src/api |
HTTP/WebSocket 消息路由与处理器 | 40+ 个消息处理器 |
src/db |
SQLite DAO(人员、事件、审核、客流...) | 端侧轻量持久化 |
src/network |
HTTP Server、MQTT 客户端 | 事件外发通道 |
src/platform |
看门狗、网卡、重启、时间 | 7×24 运行的兜底 |
启动时序也很有工程味:ServiceRegistry 分四步------注册基础设施服务 → 注册业务服务 → 初始化 → 启动运行时服务(MQTT / HTTP / WebSocket / 设备发现 / 存储清理 / 看门狗)。看门狗和存储清理被当成一等公民,说明这玩意儿是冲着"装在现场无人值守"去的,不是 demo。
- 项目实现流程
把上面的架构落到"一帧视频从进来到报警出去",就是下面这条链路(右列是模型导入的离线链路):

图:左列是运行时主链路,右列是模型导入与图装配。两条线在"推理"这一步汇合------这也是理解"换模型不改代码"的关键。
1 运行时主链路:一帧要走完的 11 步
① 视频接入。 支持本地文件、RTSP、USB 摄像头(V4L2),新版本还加了 ONVIF 发现与鉴权取流、GB28181 的信令与 TCP/UDP 取流。解复用层用策略模式拆成 FileDemuxStrategy / NetworkDemuxStrategy / RtspDemuxStrategy / UsbDemuxStrategy,统一交给 VideoDemuxer 调度。
② 解复用。 FFmpeg 拉流,产出 H.264/H.265 码流包。
③ 解码。 三家分叉最狠的一步:VideoDecoderCreateSophon.cc(走 VPU 硬解)、VideoDecoderCreateRockchip.cc(走 MPP)、VideoDecoderCreateCpu.cc(FFmpeg 软解)。工厂函数签名一致,编译期选一个实现编进去。瑞芯微那个解码器实现有 3.3 万字节------可见平台差异的脏活全在这层。
④ 帧分发 + 按任务抽帧。 前面讲过的 AlgDataQueueDistributor:一路帧按 channelId/taskId 复制到多个任务队列,每个队列用自己的 FpsCtrl 控帧率。比如相机是 24 FPS,但"人员检测"任务只要 5 FPS,那就 5 FPS 采样,剩下 19 帧直接不进推理。
⑤ 预处理。 缩放、裁剪、仿射变换、归一化。能上 NPU 就上 NPU(SophonCropResizeNode 等),不能就 CPU(HostNodeFactory)。
⑥ 神经网络推理。 真正的前向。权重格式 .bmodel / .rknn / .onnx,运行时 BMRT / RKNN / ONNX Runtime。
⑦ 后处理。 解码头 → 框/类别/分数 → NMS。可在 NPU(NODE_YOLO_NPU_DECODE)或 CPU(yolov8_decode_node)。输出统一成 ObjectInfoV1 这类结构:{box, classId, score, mask?, corners?(旋转框)}。
⑧ 跟踪 + 规则过滤。 跟踪这块源码很扎实:src/nn/utils/tracker/ 下有匈牙利算法匹配(hungarian.cc,1.4 万字节)+ 卡尔曼滤波(eigen_kalman_filter.cc、eigen_kalman_tracker.cc),也有轻量版 default_tracker。过滤则包括类别过滤、最小尺寸过滤、ROI 区域过滤。
⑨ 二级分类 / 大模型复核。 检测到人体后裁一块小图送去分类(比如"戴帽/没戴帽"),或者调用视觉语言模型做复核。后者见 TaskAlarmLlmReview.cc------先把疑似目标筛出来,再让大模型看一眼,而不是每帧都跑大模型,这个成本控制思路很务实(VLM 的实测能力只有 0.1 FPS/路量级,必须这么省着用)。
⑩ 报警判定 · 灵敏度 · 抑制。 这块代码量惊人(TaskAlarmEventBuilder.cc 3.1 万字节、AreaAlarm.cc 1.2 万字节、TaskAlarmSuppression.cc 6 千字节)。灵敏度有两种常见形态:计时型 (连续命中 N 秒才报)和计数型(命中次数达到阈值才报)。抑制则负责去重------同一个目标在短时间内不重复刷屏。
⑪ 事件上报。 结构化事件 + 最佳抓拍帧(AiDetectBestEl 里存了最优质量分和对应帧)+ 可选录像片段,落 SQLite,同时通过 REST / WebSocket / MQTT / HTTP Webhook 外发。
2 离线链路:一个模型是怎么被"翻译"成执行图的
右列那 8 步,是从"训练好的模型"到"能被引擎加载"的过程:
- 训练产出:自有数据训练或第三方开源模型(ONNX 等);
- 平台转换:算能转.bmodel、瑞芯微转.rknn、x86 直接用.onnx;
- 打包:config.json(模型类型、输入输出 shape、归一化参数、标签、阈值)+ 权重 + MD5;
- 上传与校验:这里有个我喜欢的安全细节------ArchiveListingValidator专门做解压清单校验(防 zip slip 这类路径穿越);
- 解析为元信息:CombinedModelInfo(shape、dtype、预处理参数、解码类型、max_batch);
- 装配计算图:预处理链 → Net 节点 → 后处理链,多模型可串联;
- 加载权重 + 预分配:LoadWeight()→InferTopDesc()推导每个节点输出 shape →AllocaAllBlobs()一次性分配;
- 实例池预热:按任务数准备 N 份推理实例。
3.运行结果
控制台:可视化编排长什么样
真正让我觉得"这不只是个推理库"的,是它的编排画布。任务不是代码写死的,而是在浏览器里把节点拖成一条链:

图:编排画布。内置模板的节点顺序是:视频解码 → 目标检测 → 追踪 → 类别过滤 → 目标分类 → 目标判断 → 灵敏度计算 → 事件上报。
编排时文档里给了四条很实在的原则,我 paraphrase 一下:
- 先产生数据,再应用规则------没解码就不能推理,没有目标就不能做区域判断;
- 尽早过滤------在昂贵的分类/大模型节点之前,先把无关类别和过小目标滤掉;
- 一个节点只干一件事------重复插同类过滤会让结果无法解释;
- 先跑通最小链路,再逐项加------先解解码 + 检测,再加跟踪、区域、事件。
社区里还有个 x86 上跑 YOLOv8 猫狗检测的实录,实时结果页是这样的:

另一张官方教程里的结果页截图,可以看到事件列表与抓拍图:

联系我们

有课设/论文指导需求或图像算法开发、商务合作,欢迎联系并注明来意。