啃cpp感觉还是太痛苦了,啃得甚至感觉有些动摇了,我最近一直在看Rust,但可惜秋招市场还是cpp为主,咬咬牙还是接着学吧。
我打算把目前CPU做的一些YUUV -> BGR 这一部分迁移到使用瑞芯微提供的RGA进行硬件加速,熟悉这个Demo。
编译 首先需要把RGA的库加入编译。
1 2 3 4 5 6 7 8 9 10 11 12 add_library (rga_runtime SHARED IMPORTED)set_target_properties (rga_runtime PROPERTIES IMPORTED_LOCATION "${THIRD_PARTY}/librga/lib/librga.so" )target_include_directories (yolo_camera_demo PRIVATE "${THIRD_PARTY}/librga/include" )target_link_libraries (yolo_camera_demo PRIVATE rga_runtime)set_target_properties (yolo_camera_demo PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "$ORIGIN/../3rdparty/librga/lib" )
声明导入库(add_library): SHARED表示动态库.so,IMPORTED表示它不是由项目编译生成,而是外部库;
指定导入库的实际路径(set_target_properties):把刚刚导入的库和实际路径连接;
给项目添加头文件目录(target_include_directories):让项目在编译阶段能搜索到库的头文件,PRIVATE表示只对项目yolo_camera_demo生效;
链接静态库(target_link_libraries):在链接阶段让库参与链接;
设置安装后的rpath(set_target_properties):在运行阶段让库参与运行。
NV12 -> BGR 这一部分说起来没有那么难,主要参考官方文档 和官方例程 。不过在啃代码的地方看到一些AI的雷霆写法:
1 2 3 4 5 6 7 8 9 bt709_ = enc == V4L2_YCBCR_ENC_709; bt709_ = (enc == V4L2_YCBCR_ENC_709);if (enc == V4L2_YCBCR_ENC_709) { bt709_ = true ; } else { bt709_ = false ; }
像素格式 扯远了,接下来回到正题。 原程序中的图像格式转换是在core.hpp中,做了一些固定权重的转换。在主程序中:
1 2 3 4 5 6 7 8 for (unsigned row = 0 ; row < height_; ++row) { auto * dst = frame.bgr.ptr <cv::Vec3b>(row); const auto * chroma = uv + size_t (row / 2 ) * stride_; for (unsigned col = 0 ; col < width_; ++col) { const auto c = yuvToBgr (y[size_t (row) * stride_ + col], chroma[col & ~1u ], chroma[(col & ~1u ) + 1 ], full_, bt709_); dst[col] = {c.b, c.g, c.r}; } }
首先要理解NV12的像素格式,是一种半压缩的视频流格式,Y表示亮度,U表示蓝色差,V表示红色差。形如:
1 2 3 4 5 6 7 8 9 Y: Y00 Y01 Y02 Y03 Y10 Y11 Y12 Y13 Y20 Y21 Y22 Y23 Y30 Y31 Y32 Y33 UV: U00 V00 U01 V01 U10 V10 U11 V11
其次还有一个色域的标准BT.709,他定义了一套色度转换系数,如下所示: $$ \begin{bmatrix} R \ G \ B \end{bmatrix} = \begin{bmatrix} 1.16438 & 0.00000 & 1.79274 \ 1.16438 & -0.21325 & -0.53291 \ 1.16438 & 2.11240 & 0.00000 \end{bmatrix} \begin{bmatrix} Y-16 \ U-128 \ V-128 \end{bmatrix} $$ 除此之外,还有其他的BT.601,色域更窄一些,如果使用错误的标准,会导致颜色被压缩,画面暗淡。
wrapbuffer_virtualaddr_t 知道标准之后,那么最核心的一句就可以确定下来了:
1 2 3 4 5 6 7 const IM_STATUS status = imcvtcolor ( src, dst, RK_FORMAT_YCbCr_420_SP, RK_FORMAT_BGR_888, IM_YUV_TO_RGB_BT601_FULL );
但其中,src和dst要提前定义内存空间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 auto * nv12 = static_cast <uint8_t *>(mem.ptr) + latest_plane.data_offset; const auto src = wrapbuffer_virtualaddr_t ( nv12, width_, height_, stride_,height_, RK_FORMAT_YCbCr_420_SP ); const auto dst = wrapbuffer_virtualaddr_t ( frame.bgr.data, frame.bgr.cols, frame.bgr.rows, static_cast <int >(frame.bgr.step / frame.bgr.elemSize ()), frame.bgr.rows, RK_FORMAT_BGR_888 );
这里填充的参数就不过多解释了,主要是wrapbuffer_virtualaddr_t,他的主要作用是申请图像的缓冲区,我们这里是从V4L2拿的数据,目前还需要拷贝一下。
图像内存 MMAP 我有点讨厌cpp这种事无巨细的管理,不过这也是高性能的来源。相机的数据先被搬到内存中后,再由内核建立联系,这个桥梁就是MMAP。组合使用 ioctl 和 mmap,才能访问到V4L2的视频流。
bytesused/offset/stride
bytesused 可以理解为申请视频帧缓存的有效内容。
offset 表示有效数据的起点。
stride表示一行数据这内存中占用的字节数。
1 有效数据 = buffer[offset, offset + bytesused)
参考资料