RKNN YOLOv5 推理直接使用 NV12 视频帧可行性分析

RKNN YOLOv5 推理直接使用 NV12 视频帧可行性分析

问题

从摄像头拉取的原始视频帧格式为 NV12 (YUV420SP),当前流程中需要先通过 nv12_to_rgb() 将 NV12 转换为 RGB888 后再送入 inference_yolov5_model() 做推理,造成不必要的 CPU 资源消耗。


核心结论

不能直接将 NV12 原始数据送入 YOLOv5 模型推理。

YOLOv5 模型的第一层卷积层期望输入为 3 通道 RGB 数据 (shape [1, 640, 640, 3]),而 NV12 是 YUV 色彩空间的 1.5 通道平面格式(Y 平面 + UV 交错平面),两者的数据布局和语义完全不同。即使使用 rknn_input.pass_through = 1 将原始字节直接传递给 NPU,模型也会输出无意义的结果。

最优方案:使用 Rockchip RGA(2D 硬件加速器)替代 CPU 完成 NV12→RGB888 转换。

该代码库的 image_utils.c 已原生支持此方案------通过 RGA 的 improcess()一次硬件调用 内完成 NV12→RGB888 色彩空间转换 + 缩放 + letterbox 填充,彻底消除 CPU 逐像素转换的开销。


代码库已有支持

关键结构体和函数已就位:

组件 位置 说明
IMAGE_FORMAT_YUV420SP_NV12 utils/common.h:13 image_buffer_t 支持的 NV12 格式枚举
get_rga_fmt() returns RK_FORMAT_YCbCr_420_SP utils/image_utils.c:468-469 NV12 到 RGA 格式的映射
convert_image_rga() utils/image_utils.c:498 使用 RGA 硬件转换,支持不同 src/dst 格式
convert_image_with_letterbox() utils/image_utils.c:699 NV12→RGB888 + resize + letterbox 一次完成
librga.so install/.../lib/librga.so 已部署的 RGA 运行时库

代码修改方案

1. main.cc --- 替换 CPU 端 NV12→RGB 转换

删除以下代码:

cpp 复制代码
// main.cc:1113  --- 分配 RGB 缓冲区
stream->rgb_buffer = (unsigned char *)malloc(WIDTH * HEIGHT * 3);

// main.cc:1123-1125 --- 设置 RGB888 格式
img.width = WIDTH;
img.height = HEIGHT;
img.format = IMAGE_FORMAT_RGB888;

// main.cc:1146-1147 --- CPU 转换并赋值
nv12_to_rgb((unsigned char *)frame, stream->rgb_buffer, WIDTH, HEIGHT);
img.virt_addr = stream->rgb_buffer;

替换为以下逻辑:

cpp 复制代码
// 设置源图像:直接指向 NV12 帧
image_buffer_t src_img;
memset(&src_img, 0, sizeof(src_img));
src_img.virt_addr = (unsigned char *)frame;
src_img.width = WIDTH;
src_img.height = HEIGHT;
src_img.format = IMAGE_FORMAT_YUV420SP_NV12;
src_img.size = WIDTH * HEIGHT * 3 / 2;  // NV12 size = w*h*1.5

// 设置目标图像:模型输入尺寸的 RGB888
image_buffer_t dst_img;
memset(&dst_img, 0, sizeof(dst_img));
dst_img.width = stream->model_ctx.model_width;
dst_img.height = stream->model_ctx.model_height;
dst_img.format = IMAGE_FORMAT_RGB888;
dst_img.size = stream->model_ctx.model_width * stream->model_ctx.model_height * 3;
dst_img.virt_addr = (unsigned char *)malloc(dst_img.size);

// RGA 硬件完成 NV12→RGB888 + resize + letterbox(一次调用)
letterbox_t letterbox;
convert_image_with_letterbox(&src_img, &dst_img, &letterbox, 114);

// 传给推理函数(格式 RGB888,尺寸匹配模型输入)
img = dst_img;

注意dst_img.virt_addr 需要在整个流生命周期内可复用(只需分配一次),建议将其存储在 stream_runtime_t 中,替换原有的 rgb_buffer

2. inference_yolov5_model() (rknpu2/yolov5.cc)

无需修改。

当前函数第 182-184 行会检查:

cpp 复制代码
if (img->width == app_ctx->model_width &&
    img->height == app_ctx->model_height &&
    img->format == IMAGE_FORMAT_RGB888)
{
    input_buf = img->virt_addr;  // 走快路径,直接使用
}

由于 main.cc 已通过 RGA 将图像预处理为尺寸匹配的 RGB888,此快路径条件满足,函数会直接将缓冲区送入 NPU,无需任何额外的 CPU 转换。

3. convert.py --- 模型转换

无需修改。

RGA 硬件输出的 RGB888 数据与 ONNX/RKNN 模型期望的输入格式完全一致。


方案对比

方案 CPU 负载 硬件加速 延迟 代码改动量
当前:CPU nv12_to_rgb() + 软件 resize 高(逐像素)
RGA 硬件 NV12→RGB888 + resize 极低 RGA 硬件 极低(~1ms) main.cc 少量修改
pass_through=1 直接送 NV12 NPU 需修改 ONNX 图,侵入性强
修改模型首层接受 NV12 NPU 需重新训练/修改模型,复杂度高

推荐方案:RGA 硬件加速转换------这是 Rockchip 平台的标准做法,代码改动最小,性能收益最大。


RGA 转换关键代码路径

复制代码
main.cc
  |
  v
convert_image_with_letterbox(src=NV12, dst=RGB888)
  |
  v
convert_image()
  |-- try: convert_image_rga()        ← RGA 硬件 `improcess()`
  |      成功 → NV12→RGB888 + resize + letterbox 一次完成
  |
  |-- fail fallback: convert_image_cpu()
          `src->format != dst->format` → 返回 -1
          (CPU 路径不支持跨格式转换,RGA 不可用时 NV12 路线不可用)

当前平台的 RGA 硬件至关重要。如果 RGA 不可用(如 RGA 驱动未加载),建议保持原有的 CPU nv12_to_rgb() 路径作为回退。

相关推荐
minge000137 分钟前
【世界杯中的AI】(2026-07-05)当足球遇见AI:7月5日世界杯的“人机共舞”与冷门之夜
人工智能·科技·世界杯
IT_陈寒1 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
wumingxiaoyao1 小时前
从 0 开始学 AI:第 2 课,AI、机器学习、深度学习和大模型是什么关系?
人工智能·深度学习·机器学习·ai·大模型·llm
2601_962683891 小时前
治理遗留系统中的“生肉 SQL”:一次用多模型协作优化慢查询的实战复盘
数据库·人工智能·sql
踩着两条虫1 小时前
可视化 vs 终端 vs 云端:VTJ.PRO、Claude Code、Codex 三强横评
前端·vue.js·人工智能·低代码·架构
字节跳动视频云技术团队1 小时前
Agentic 范式下的视频画质优化:火山引擎的新路径
人工智能·音视频开发
欢子2 小时前
Android 端接入AI
人工智能
酱学编程2 小时前
【从零到一实现一个 AI Agent 框架 · 第四篇】04. 任务规划:拆解复杂目标 -
服务器·网络·数据库·人工智能
手写码匠2 小时前
手写 AI 上下文压缩系统:从零实现 Prompt 压缩与选择性上下文管理
人工智能·深度学习·算法·aigc