4. 视频流格式转换

在RKNN视频流部署中,摄像头采集到的原始数据通常不是我们熟悉的RGB格式,而是YUV家族格式(如NV12、YUYV、NV21等)。NPU推理前,图像必须经过格式转换、尺寸缩放、ROI裁剪等预处理操作,而这些操作在RK平台上通常由RGA(Rockchip Graphics Acceleration)硬件完成。如果对像素格式的理解不到位,轻则出现颜色偏绿、偏紫,重则内存越界、花屏、推理精度大幅下降。本章将系统梳理YUV、RGB、NV12等格式的存储原理、转换方法,并结合RGA的输入输出约束,给出可直接落地的工程实践方案。

视频流像素格式转换与RGA预处理链路 摄像头采集 输出: NV12/YUYV 8-bit / 10-bit 格式解析 YUV420SP / NV12 Y平面 + UV交错 RGA硬件预处理 格式转换: NV12→RGB888 缩放/裁剪/旋转 零拷贝, 无需CPU参与 NPU推理 输入: RGB888 归一化/量化 常见像素格式存储布局对比 RGB888 (连续) R G B R G B ... 每像素3字节 W×H×3 字节 适合NPU直接输入 NV12 (YUV420SP) Y平面 W×H UV交错 W×H/2 总大小 = W×H×1.5 摄像头/视频解码默认输出 YUYV (YUV422) Y0 U0 Y1 V0 Y2 U1 Y3 V1 每2像素共享UV 总大小 = W×H×2 UVC摄像头常见输出

上图展示了从摄像头采集到NPU推理的完整数据链路。核心要点在于:摄像头输出的是YUV格式,而RKNN模型输入通常要求RGB或BGR格式,中间的转换与预处理必须由RGA硬件完成,避免CPU参与像素级操作导致性能瓶颈。下面我们深入解析各格式的底层原理与工程转换方法。

4.1 核心原理:YUV与RGB的数学本质与存储差异

YUV色彩空间将亮度信息(Y)与色度信息(U/V)分离,其设计初衷是兼容黑白电视与彩色电视信号。人眼对亮度变化敏感,对色度变化相对迟钝,因此可以通过降低色度采样率来压缩数据量,这就是YUV 4:2:0、4:2:2等采样格式的由来。

在RKNN部署场景中,最常遇到的YUV格式是NV12(YUV420SP)和YUYV(YUV422)。NV12的存储布局为:先存储完整的Y平面(W×H字节),随后存储UV交错平面(W×H/2字节),其中U和V交替排列。YUYV则采用Y0 U0 Y1 V0的打包方式,每两个像素共享一对UV分量。

RGB与YUV之间的转换公式(BT.601标准)如下:

Y  = 0.299R + 0.587G + 0.114B
U  = -0.169R - 0.331G + 0.500B + 128
V  = 0.500R - 0.419G - 0.081B + 128

R = Y + 1.402(V - 128)
G = Y - 0.344(U - 128) - 0.714(V - 128)
B = Y + 1.772(U - 128)

在RK3588/RK3568平台上,RGA硬件内置了BT.601、BT.709等多种转换矩阵,开发者无需手动实现浮点运算,只需正确配置RGA的输入输出格式即可。

格式名称采样方式存储布局每像素字节数典型来源
NV12YUV420SPY平面 + UV交错1.5RK ISP、H.264/H.265解码
NV21YUV420SPY平面 + VU交错1.5部分Android摄像头
YUYVYUV422Y0 U0 Y1 V0打包2.0UVC免驱摄像头
RGB888RGBR G B连续排列3.0模型输入标准格式
BGR888BGRB G R连续排列3.0OpenCV默认格式

4.2 判断方法:如何识别当前视频流的像素格式

在实际开发中,摄像头输出的格式往往由驱动或V4L2配置决定。开发者需要掌握以下判断方法:

  1. 查看V4L2格式描述符:通过v4l2-ctl --list-formats-ext命令查看摄像头支持的像素格式,输出中会明确标注NV12、YUYV等标识。
  2. 检查解码器输出:如果视频流来自H.264/H.265解码,RKMPP解码器默认输出NV12格式,可通过解码器回调的frame_info结构体确认。
  3. 分析内存布局:通过打印首行像素的十六进制值,结合已知颜色(如纯红色)判断格式。例如纯红色在NV12中Y=76, U=84, V=255,在RGB中则为(255,0,0)。
  4. 使用RGA格式探测:调用RGA的query_format接口,传入待检测的内存地址和大小,RGA会返回格式是否受支持。
经验提示:在RK3588平台上,ISP输出的默认格式通常是NV12,且对齐要求为16像素宽。如果摄像头输出YUYV,建议在驱动层或ISP配置中直接改为NV12输出,避免在应用层做额外的格式转换,减少CPU开销。

4.3 操作流程:基于RGA的NV12到RGB888转换实战

RGA(Rockchip Graphics Accelerator)是RK平台上的2D硬件加速器,支持格式转换、缩放、旋转、裁剪等操作。以下是通过librga库完成NV12到RGB888转换的标准流程:

#include <rga/RgaApi.h>

// 1. 初始化RGA
RgaInit();

// 2. 填充源图像信息
rga_info_t src_info;
memset(&src_info, 0, sizeof(src_info));
src_info.fd = -1;  // 使用虚拟地址
src_info.virAddr = (void*)nv12_buffer;
src_info.mmuFlag = 1;
src_info.rect.xoffset = 0;
src_info.rect.yoffset = 0;
src_info.rect.width = width;
src_info.rect.height = height;
src_info.rect.wstride = width;   // 注意:需要16字节对齐
src_info.rect.hstride = height;
src_info.format = RK_FORMAT_YCbCr_420_SP;  // NV12

// 3. 填充目标图像信息
rga_info_t dst_info;
memset(&dst_info, 0, sizeof(dst_info));
dst_info.fd = -1;
dst_info.virAddr = (void*)rgb_buffer;
dst_info.mmuFlag = 1;
dst_info.rect.xoffset = 0;
dst_info.rect.yoffset = 0;
dst_info.rect.width = width;
dst_info.rect.height = height;
dst_info.rect.wstride = width;
dst_info.rect.hstride = height;
dst_info.format = RK_FORMAT_RGB_888;

// 4. 执行转换
int ret = RgaBlit(&src_info, &dst_info, NULL);
if (ret != 0) {
    printf("RGA转换失败: %s\n", strerror(errno));
}

// 5. 释放资源
RgaDeInit();

关键参数说明:wstride和hstride表示内存中实际的行宽和行高,必须与分配内存时的对齐值一致。例如,如果使用dma_buffer分配内存,通常需要按64字节对齐,此时wstride应设置为对齐后的值,而不是图像的逻辑宽度。

4.4 案例拆解:UVC摄像头实时流接入RKNN推理

背景与约束:某安防项目需要将USB摄像头采集的视频流接入RK3588平台进行实时人形检测。摄像头输出YUYV格式,分辨率1920×1080@30fps,模型输入要求RGB888格式640×640分辨率。系统CPU负载需控制在30%以内,内存带宽有限。

初始现象:直接使用OpenCV的cvtColor函数进行YUYV→BGR转换,再使用resize缩放,CPU占用率飙升至85%,帧率跌至12fps,无法满足实时性要求。

分析过程:通过性能剖析发现,CPU主要耗时在色彩转换(约18ms/帧)和缩放(约12ms/帧)。YUYV到BGR的转换涉及大量浮点运算,且OpenCV的resize在非整数倍缩放时效率较低。

优化方案:改用RGA硬件完成全部预处理,具体步骤如下:

  1. 使用V4L2采集YUYV数据,通过mmap映射到用户空间。
  2. 调用RGA将YUYV转换为NV12(减少一次CPU拷贝,且NV12是后续处理的中间格式)。
  3. 再次调用RGA将NV12缩放至640×640,并同时转换为RGB888。
  4. 将RGB888数据直接传递给RKNN输入接口。

结果对比:优化后CPU占用率降至8%,帧率稳定在30fps,且RGA处理单帧耗时仅2.3ms(YUYV→NV12)和1.8ms(NV12→RGB888+缩放),完全满足实时性要求。

风险与代价:RGA转换的精度略低于CPU浮点转换(色差ΔE约1.5),但对人形检测任务影响可忽略。此外,RGA对输入内存有对齐要求,需要额外分配对齐缓冲区,增加了约5%的内存开销。

复盘经验:在RK平台做视频流处理时,应遵循"能硬件不软件"的原则。RGA不仅支持格式转换,还能同时完成缩放、裁剪、旋转等操作,一次调用即可完成多个预处理步骤。建议将RGA操作封装为独立模块,便于复用和性能调优。

4.5 常见误区与总结复盘

误区一:忽视stride对齐。许多开发者直接使用图像宽度作为stride,导致RGA处理时出现花屏或内存越界。正确做法是使用dma_alloc或rockchip_mpp_alloc分配对齐内存,并获取实际stride值。

误区二:混淆NV12与NV21。两者UV排列顺序相反,如果配置错误,图像会呈现紫色/绿色色调。在调试时可通过打印U/V分量值快速判断。

误区三:在CPU上做不必要的格式转换。例如先转成BGR再转成RGB,或使用OpenCV的Mat操作处理YUV数据。这些操作应全部交给RGA完成。

误区四:忽略色彩空间标准。摄像头输出的YUV可能基于BT.601或BT.709标准,而模型训练时可能使用不同的标准。RGA默认使用BT.601,如果模型基于BT.709训练,需要额外配置RGA的色彩空间参数。

检查项验证方法预期结果
格式识别打印首行像素值纯红色在NV12中Y≈76, U≈84, V≈255
stride对齐检查RGA返回的wstridewstride ≥ width,且为16的倍数
色彩正确性显示转换后的RGB图像肤色自然,无偏色
性能验证使用perf工具统计RGA单帧处理 < 5ms

总结而言,掌握YUV/RGB格式的本质差异和RGA的硬件特性,是构建高效RKNN视频流应用的基础。建议开发者在项目初期就明确数据链路中的每一步格式和内存布局,避免后期返工。在实际调试中,准备一张标准色卡(如Macbeth色卡)作为参照,可以快速定位色彩转换问题。