三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

基于 RK3588 的多输入视频 AI 推理流水线:YOLO 车牌检测、OCR、WebRTC 推流与边缘联动

基于 RK3588 的多输入视频 AI 推理流水线:YOLO 车牌检测、OCR、WebRTC 推流与边缘联动

基于 RK3588 的多输入视频 AI 推理流水线:YOLO 车牌检测、OCR、WebRTC 推流与边缘联动

本文介绍一个面向 Rockchip RK3588 的 C++ 视频智能分析项目。项目将 MP4、USB 摄像头和 RTSP 视频统一接入同一条边缘推理流水线,完成目标检测、车牌 OCR、跟踪、OSD、硬件编码和网络分发,并可选接入 MQTT 事件上报、OLED 显示和 Modbus 继电器。

采用 NPU Core Affinity 机制,各推理线程持有独立rknn_context,通过 core_mask 绑定不同 NPU 硬件核心,实现多路检测任务在多 NPU 上并行执行,降低单 NPU 资源争抢,提升多路 RTSP 流推理吞吐。
GitHub:https://github.com/shaddockpeel2/Plate_detection_recognition(点⭐哦)

一、项目主要做什么

这个项目解决的不是“加载一个 YOLO 模型并输出检测框”这么单一的问题,而是一个完整的边缘视频分析链路:

MP4 / USB 摄像头 / RTSP │ ▼ 解封装、采集与硬件解码 │ ▼ RGA 图像预处理 │ ▼ RKNN YOLO 推理 │ ▼ 后处理、ByteTrack、车牌 OCR │ ▼ OSD 叠加结果 │ ▼ MPP H.264/H.265 编码 │ ┌─────┴──────────────┐ ▼ ▼ 保存 MP4 RTMP 推送到 ZLMediaKit │ WebRTC / HTTP-FLV / HLS

项目的典型应用场景包括:

  • RK3588 边缘盒子上的车辆和车牌识别;
  • 停车场、园区或出入口的视频分析;
  • 需要低延迟预览的智能摄像头网关;
  • 识别结果需要上传到平台,或者需要联动继电器、门禁等设备的场景;
  • 在一块 RK3588 上运行三路独立推理任务的性能验证。

核心程序是rk_mp4_yolo_stage5,它负责单路视频从输入到输出的完整处理。rk_yolo_supervisor负责多路 worker 的配置校验、启动、健康检查和重启,不直接参与图像推理。

二、项目的核心架构

1. 多输入统一为DecodedFrame

项目将输入分支和后续推理链路解耦。

对于 MP4 和 RTSP 输入,程序使用 FFmpeg 完成解封装,再使用 Rockchip MPP 硬件解码 H.264/H.265。RTSP 采用 TCP 传输,并配置读流超时。

对于 USB 摄像头,程序使用 V4L2 的 MMAP 方式采集YUYV数据,在摄像头输入线程内转换为NV12,随后也进入DecodedFrameQueue。这样,摄像头不需要复制一套后处理、推理和编码逻辑。

统一后的DecodedFrame不只是一个图像指针,还包含:

  • frame_id:应用层帧编号;
  • pts:源视频时间戳;
  • source_fps:源帧率;
  • 宽高和 stride;
  • MPP 帧对象和 DMA-BUF 文件描述符。

后续模块通过 DMA fd 直接访问硬件缓冲区,减少不必要的 CPU 拷贝。

2. RGA 负责预处理,RKNN 负责推理

预处理线程从解码队列取帧,使用 RGA 完成:

  • NV12 到 RGB/BGR 的格式转换;
  • 缩放;
  • Letterbox 填充;
  • 写入 RKNN 输入张量;
  • 对量化模型执行必要的输入转换。

项目通过RknnInputRuntime在启动时查询模型输入输出属性,包括输入尺寸、布局、数据类型和量化信息,并创建输入内存池。默认输入池包含多个 DMA 内存槽位,使预处理和推理之间可以形成有界流水线,而不是每帧重复申请和释放内存。

RGA 预处理和 RKNN 输入内存之间通过 DMA fd 连接,适合 RK3588 这类需要充分利用硬件加速和共享缓冲区的环境。

3. RKNN 推理支持 NPU 核绑定

YOLO 模型通过 RKNN Runtime 初始化,程序可以使用rknn_set_core_mask将推理上下文绑定到指定 NPU 核:

--npu-core 0 使用 NPU core 0 --npu-core 1 使用 NPU core 1 --npu-core 2 使用 NPU core 2 --npu-core auto 交给 RKNN 自动调度

绑定只作用于 RKNN 推理上下文,并不意味着 RGA、MPP、CPU、内存带宽和网络资源也被隔离。三路 worker 即使分别使用 core 0、1、2,仍然会共享其他系统资源,因此实际部署仍需要根据输入分辨率、帧率、OCR 和编码码率进行容量评估。

4. 后处理兼容不同 YOLO 输出布局

后处理模块会读取 RKNN 输出张量,并根据张量布局解析检测框和类别分数,当前代码中包含两类主要路径:

  • YOLO26 风格的检测输出;
  • YOLOv8 DFL 风格的 box、score 输出。

完成输出解码后,模块依次执行:

  1. 置信度过滤;
  2. 坐标还原,将模型坐标映射回原始图像;
  3. NMS 去除重复框;
  4. ByteTrack 关联连续帧中的同一目标;
  5. 为检测结果写入track_id

Detection数据结构同时保存检测框、检测分数、跟踪 ID、车牌文本和 OCR 分数,后面的 OCR、OSD、上传和继电器逻辑都基于这份结构继续处理。

三、车牌 OCR 是如何接入的

车牌 OCR 不是单独的离线工具,而是后处理阶段的一部分。

当检测到车辆目标并且 ByteTrack 已经分配了track_id后,PlateOcrStage会执行以下操作:

车辆检测框 │ ▼ 扩大并裁剪车牌区域 │ ▼ RGA 缩放到 OCR 模型输入尺寸 │ ├─ 量化输入:直接使用 RKNN DMA 输入内存 └─ 浮点输入:转为 OpenCV Mat 后归一化 │ ▼ PP-OCRv5 车牌识别模型 │ ▼ CTC 解码 + 字典映射 │ ▼ plate_text / plate_score

OCR 阶段包含几个适合实时视频的优化:

  • 同一帧限制最大 OCR 目标数量;
  • 同一跟踪目标按帧间隔重新识别;
  • 识别成功后按track_id缓存结果;
  • 识别失败时使用较短的重试间隔;
  • 缓存过期后自动清理,避免目标长期消失仍保留旧车牌。

需要注意,当前 YOLO 和 OCR 默认绑定到同一个 NPU core。启用 OCR 后,实际吞吐会下降,需要结合目标数量和 OCR 触发频率重新评估,而不能只看 YOLO 单模型的速度。

四、OSD、编码和播放

1. OSD 直接叠加到视频帧

后处理线程会在原始 NV12 帧上绘制:

  • 检测框;
  • 跟踪 ID;
  • 车牌识别文字;
  • 中文或 ASCII 文字。

中文文字使用 FreeType 加载系统字体并生成位图缓存,重复出现的车牌不会每帧重复生成字形。这样编码输出的视频本身就已经包含检测框和车牌文字,浏览器端不需要再次执行绘制。

2. MPP 编码支持本地文件和网络输出

编码线程使用 MPP 硬件编码器输出 H.264/H.265 码流,并根据输出模式选择不同 sink:

mp4 -> FFmpeg libavformat 封装为 MP4 annexb -> 保存裸 H.264/H.265 码流 rtmp -> H.264 Annex-B 写入 FFmpeg 子进程,再转封装为 FLV/RTMP

RTMP 推流使用FfmpegPushSink,主程序通过管道把 MPP 输出的 H.264 码流交给 FFmpeg,FFmpeg 使用视频复制方式转封装并推送到 ZLMediaKit。推流进程异常时支持有限次数重启。

ZLMediaKit 负责将 RTMP 转换为浏览器可消费的协议,页面可以使用:

  • WebRTC:低延迟预览;
  • HTTP-FLV:便于 VLC、ffplay 或接口排障;
  • HLS:兼容部分原生 HLS 播放器,但延迟通常更高。

3. 实时帧率和时间戳处理

RTSP 实时场景最容易出现的问题不是模型本身,而是输入速度、编码声明和媒体时间戳不一致。项目对此做了专门处理:

  • --output-fps 25时,在 RGA 和 RKNN 之前均匀抽帧,避免只修改编码器声明而仍然处理全部输入帧;
  • --output-fps auto时,跟随 RTSP 建连时探测到的源帧率,不主动抽帧;
  • 输出队列有界,系统过载时丢弃最旧帧,优先保持实时性;
  • RTMP 路径不使用墙钟时间戳,也不使用 FFmpeg-re强制限速;
  • FFmpeg 根据输入帧率生成连续 PTS,避免推理耗时抖动直接变成媒体时间轴抖动;
  • MPP 编码输出按 partition 收齐,只有完整帧结束后才交给下游,避免半帧 H.264 造成推流断开。

项目现有技术报告记录了三路1024×576 @ 30 FPS测试流的验证结果:每路约 5 秒输出 150 个包,PTS 间隔约为0.033~0.034秒,三个 worker 的重启次数为 0。这个结果说明帧率选择和 RTMP 时间戳链路已经能够稳定工作,但不代表任意分辨率、码率和 OCR 配置下都能获得相同吞吐。

五、三路独立推理与 supervisor

项目提供了一个轻量级的rk_yolo_supervisor。它将三路推理拆成三个独立进程:

worker.core0 -> --npu-core 0 -> rtmp://127.0.0.1:1936/live/rk3588-core0 worker.core1 -> --npu-core 1 -> rtmp://127.0.0.1:1936/live/rk3588-core1 worker.core2 -> --npu-core 2 -> rtmp://127.0.0.1:1936/live/rk3588-core2

supervisor 的职责包括:

  • 读取config/inference_fleet.ini
  • 检查 NPU core、流名、摄像头设备、截图目录、MQTT 标识和外设资源是否冲突;
  • 为每个 worker 构造命令行并脱敏打印;
  • 创建独立日志和 health 文件;
  • 监测 worker 进程和健康心跳;
  • 对 RTSP、摄像头 worker 进行有限次数的指数退避重启;
  • 原子生成网页读取的rk-yolo-status.json

浏览器页面web/rk_yolo.html根据状态文件显示三个视频卡片,每一路可以独立重试、停止或切换到排障播放地址。因此一路摄像头或 RTSP 断开时,不会直接阻塞另外两路。

三路模式适合以下两类任务:

  1. 三路独立摄像头分别占用一个 NPU core;
  2. 使用同一个输入源对三份 worker 做性能压测。

生产环境不建议让三个 worker 同时打开同一个 USB 摄像头,应使用三路独立 RTSP、不同的/dev/video*,或混合 MP4 测试源。

六、远程事件上报与硬件联动

1. HTTP 图片 + MQTT 事件

远程上报采用“图片和事件分离”的设计:

检测 / 跟踪 / OCR │ ▼ 阈值判断与去重 │ ▼ 有界 UploadEventQueue │ ▼ 独立上传线程 ├─ 裁剪车辆或车牌截图并编码 JPEG ├─ HTTP multipart 上传图片 └─ MQTT 发布事件 JSON 和 image_url

MQTT 只传事件元数据,不传图片二进制。事件通常包含设备 ID、帧号、跟踪 ID、车牌文本、检测分数、OCR 分数、检测框和图片 URL。

网络请求全部发生在上传线程中,主链路不会因为 HTTP 或 MQTT 服务器不可达而阻塞。上传队列满时丢弃新事件,不让网络反压传递到检测、OCR、OSD 和编码线程。当前版本不做失败事件的本地持久化缓存,断网期间失败的事件会丢失,但视频主链路仍可继续运行。

2. 白名单继电器和 OLED

继电器功能默认关闭,打开后需要同时满足:

  • 车牌位于白名单文件;
  • 检测分数达到阈值;
  • OCR 分数达到阈值;
  • 车牌不在冷却时间内。

控制器使用 Modbus RTU 通过串口发送线圈开关命令,默认脉冲结束后强制关闭。OLED 则通过 I2C 显示近期识别到的车牌。

这两个功能都通过独立队列和线程接入,不改变主视频处理链路。部署时必须确认串口、I2C 设备和继电器通道的实际硬件参数,不能直接照搬示例路径。

七、性能监控和背压设计

项目没有使用无限队列。解码、预处理、推理和 OSD 之间都使用固定容量队列:

DecodedFrameQueue 容纳解码帧 PreprocessedFrameQueue 容纳 RGA 处理后的输入 InferenceResultQueue 容纳 RKNN 输出 OsdFrameQueue 容纳待编码帧 UploadEventQueue 容纳待上传事件

这样做的取舍很明确:

  • MP4 文件处理可以通过阻塞队列保持完整性;
  • RTSP 实时处理优先降低延迟,队列满时丢弃旧帧;
  • 上传事件队列满时丢弃新事件,不影响视频输出;
  • 所有队列都支持关闭和唤醒,便于程序退出时按顺序回收线程。

打开--metrics-interval-ms 1000后,程序会输出:

  • 解封装、MPP 提交、RGA、预处理、RKNN、后处理、编码和 RTMP 写入耗时;
  • 队列深度、峰值、入队等待和出队等待;
  • MPP buffer full、FFmpeg pipe 阻塞、推流重启等事件;
  • 实际输出 FPS 与声明输出 FPS。

这比只查看“平均 FPS”更适合定位实时视频卡顿:平均值正常并不代表不存在 P99 延迟或队列积压。

八、如何构建和运行

1. 环境要求

项目主要面向:

硬件:Rockchip RK3588 系统:aarch64 Linux 语言:C++11 构建:CMake + g++ 推理:RKNN Runtime 图像处理:RGA、OpenCV 视频:FFmpeg、Rockchip MPP 字体:FreeType

仓库提供了项目内的 Rockchip 依赖,也支持通过RKNPU2_DIR指向外部 SDK。x86 Linux 可以阅读和修改代码,但没有对应的 MPP、RGA、RKNN 驱动和库时不能直接完成板端运行。

2. 编译

cmake-S.-Bbuild cmake--buildbuild\--targetrk_mp4_yolo_stage5 rk_yolo_supervisor\-j$(nproc)

如果 CMake 找不到 FFmpeg、OpenCV 或 FreeType 开发库,应先检查pkg-config和系统开发包;如果运行时找不到 Rockchip 动态库,应检查构建出的 RUNPATH 是否指向third_party/rockchip

3. MP4 测试

关闭 OCR 进行基础链路验证:

./build/rk_mp4_yolo_stage5\--inputmp4\--video./video/test-video/5s.mp4\--model./models/car-v8/v8-car-relu-3588.rknn\--ocr-model off\--output./video/output-video/result.mp4

启用车牌 OCR 时,需要同时指定识别模型和字典:

./build/rk_mp4_yolo_stage5\--inputmp4\--video./video/test-video/9s.mp4\--model./models/car-v8/v8-car-relu-3588.rknn\--ocr-model ./ppocrv5/PP-OCRv5_mobile_rec_license_plate.rknn\--ocr-vocab ./ppocrv5/model/license_plate_dict.txt\--output./video/output-video/plate-result.mp4

4. 摄像头测试

第一版摄像头输入主要验证YUYV采集链路:

./build/rk_mp4_yolo_stage5\--inputcamera\--device/dev/video0\--width640\--height480\--fps25\--frame-limit300\--model./models/car-v8/v8-car-relu-3588.rknn\--ocr-model off\--output./video/output-video/camera-result.mp4

运行前可以使用v4l2-ctl --list-formats-ext查看摄像头是否支持目标分辨率和像素格式。

5. RTSP 推流测试

先启动 ZLMediaKit,再启动推理程序:

./build/rk_mp4_yolo_stage5\--inputrtsp\--urlrtsp://<camera-ip>:8554/live\--model./models/car-v8/v8-car-relu-3588.rknn\--ocr-model off\--output-mode rtmp\--push-url rtmp://127.0.0.1:1936/live/rk3588\--output-fps auto

ZLMediaKit 的具体端口和页面路径以部署配置为准。通常可以用 WebRTC 查看低延迟画面,用 HTTP-FLV 或 HLS 排查推流是否真正进入媒体服务器。

6. 三路 supervisor

cpconfig/inference_fleet.ini.example config/inference_fleet.ini ./build/rk_yolo_supervisor--configconfig/inference_fleet.ini--check./build/rk_yolo_supervisor--configconfig/inference_fleet.ini --dry-run ./build/rk_yolo_supervisor--configconfig/inference_fleet.ini

正式部署前应修改:

  • 三路输入地址或摄像头设备;
  • npu_core,确保正好覆盖 0、1、2;
  • RTMP 应用名和流名;
  • 模型和 OCR 资源路径;
  • 日志、健康文件和状态页面路径;
  • MQTT、继电器和 OLED 配置。

公开配置或发布文章时,不要提交真实 RTSP 凭据、MQTT 密码、服务器地址和设备私钥。

九、项目目录说明

include/ 模块接口和数据结构 src/main.cpp 单路流水线编排 src/decoder_thread.cpp FFmpeg 解封装与 MPP 解码 src/camera_source_thread.cpp V4L2 摄像头采集与 YUYV/NV12 输入转换 src/preprocess_thread.cpp RGA 预处理和 Letterbox src/inference_thread.cpp RKNN 推理与输出张量复制 src/postprocess_osd_thread.cpp YOLO 后处理、NMS、OSD 和功能分发 src/plate_ocr_stage.cpp 车牌裁剪、OCR 调度和缓存 src/encoder_writer_thread.cpp MPP 编码和 MP4/Annex-B 输出 src/ffmpeg_push_sink.cpp RTMP 推流子进程管理 src/worker_supervisor_main.cpp 多 worker 健康管理和重启 src/worker_fleet_config.cpp INI 配置解析、校验和参数展开 models/ YOLO 模型和标签 ppocrv5/ OCR 模型、字典和测试资源 config/ 多路推理配置模板 web/ ZLMediaKit 播放页面 third_party/rockchip/ MPP、RGA、RKNN 项目依赖 docs/ RTSP、ZLMediaKit、MQTT 技术记录 doc/ 面向公开发布的项目介绍文章

十、当前边界和后续方向

项目已经形成可运行的端到端框架,但仍有明确边界:

  1. 摄像头第一版主要支持 V4L2YUYV,如果设备直接输出 NV12、MJPEG 或 H.264,需要在摄像头输入模块增加格式分支。
  2. NPU core 绑定只隔离 RKNN 上下文,三路并发仍需关注 RGA、MPP、CPU 和内存带宽竞争。
  3. OCR、上传和外设功能会增加处理开销,建议先关闭附加功能完成基础链路验证,再逐项打开。
  4. 单独运行推理程序时,RTSP 断流不会自动恢复;使用 supervisor 时可以通过 worker 退出或 health 过期触发重启。
  5. 上传失败事件当前不做本地失败缓存,断网期间应由平台侧接受事件丢失,或者后续增加持久化队列。
  6. WebRTC 依赖 ZLMediaKit 的 WebRTC 构建和端口配置,浏览器播放问题应先用 HTTP-FLV 或ffprobe验证上游媒体流。

后续可以继续完善:

  • 增加更多 V4L2 像素格式和硬件零拷贝路径;
  • 将多路配置、健康状态和性能指标接入统一运维平台;
  • 对 RGA、RKNN 和编码阶段采集 P95/P99 延迟;
  • 增加上传事件的本地持久化和断点补传;
  • 根据不同模型输出定义独立的后处理插件;
  • 3路扩大至6路;
  • 引入更完整的测试视频、模型和设备能力探测。

总结

这个项目的价值在于把 RK3588 上分散的硬件能力组织成了一条可部署的视频 AI 产品链路:输入侧兼容文件、摄像头和 RTSP,计算侧使用 MPP、RGA 和 RKNN,识别侧组合 YOLO、ByteTrack 和车牌 OCR,输出侧支持 MP4、RTMP 和 Web 播放,业务侧还可以继续连接 MQTT、OLED 和继电器。

从代码结构看,项目采用“输入分支统一、阶段线程解耦、队列有界、网络异步、worker 进程隔离”的设计。它更适合作为 RK3588 车牌识别、边缘视频网关和多路实时推理系统的工程基础,而不仅是一个模型推理样例。


关键词:RK3588、Rockchip MPP、RGA、RKNN、YOLO、车牌识别、OCR、ByteTrack、RTSP、ZLMediaKit、WebRTC、MQTT、边缘 AI、C++ 视频处理

← 返回列表