跳到主要内容

RKMPI

1 简介

利用 RKMPI 库实现摄像头图像捕获、预处理、硬件编码,结合 opencv-mobile 进行图像处理,可以将 RKNN 推理结果标注在图像上后作为作为流媒体服务器进行 rtsp 推流,在 局域网 下的 PC 可以使用 VLC 软件拉取并观察图像。

实例包括 RKMPI 库基本使用、opencv-mobile 对数据帧处理两种方式、RTSP 推流结合 RKNN 推理两种方式。主要实现效果:

  • 基于 retinaface 人脸检测网络摄像头

  • 基于 yolov5 物体检测网络摄像头

2 平台支持

DEMOCPU系统摄像头
luckfox_pico_rtsp_opencvRV1103(需修改分辨率)、RV1106buildrootsc3336
luckfox_pico_rtsp_opencv_captureRV1103、RV1106buildrootsc3336
luckfox_pico_rtsp_retinafaceRV1103、RV1106buildrootsc3336
luckfox_pico_rtsp_retinaface_osdRV1103、RV1106buildrootsc3336
luckfox_pico_rtsp_yolov5RV1106buildrootsc3336
  • RV1103Luckfox Pico Luckfox Pico Mini A Luckfpx Pico Miini B Luckfox Pico Plus
  • RV1106Luckfox Pico Pro Luckfox Pico Max Luckfox Pico Ultra Luckfox Pico Ultra W

3 环境搭建

  1. 确认 Luckfox-pico Sdk 的正确安装,确认交叉编译链的环境变量设置正确
arm-rockchip830-linux-uclibcgnueabihf-gdb --version
  • 显示交叉编译链的版本信息说明,说明环境变量设置正确。
  1. 使用编译好的 buildroot 镜像或从网盘中获取镜像。确保摄像头驱动、 rknpu 驱动和 rockit 驱动、 rkisp 驱动等成功被加载。 在 Luckfox-pico 板端上执行 lsmod 验证。
# lsmod
Module Size Used by Tainted: G
rockit 154528 0
rknpu 24552 0
mpp_vcodec 375759 1 rockit
rga3 86680 1 rockit
sc3336 9940 1
os04a10 15198 0
phy_rockchip_csi2_dphy 7674 0
phy_rockchip_csi2_dphy_hw 8161 0
video_rkisp 148435 1 rockit
video_rkcif 130130 0
rk_dvbm 5813 2 mpp_vcodec,video_rkisp
  • 注意: 要确保镜像中的根文件系统有足够的空闲空间,推荐使用运行在 SD 卡上的系统。

4 RKMPI 库使用

4.1 系统概览

Rockchip 提供的媒体处理接口( Rockchip Media Process Interface,简称 RKMPI ),可支持多媒体应用软件快速开发。该平台整合了 Rockchip 芯片的硬件资源,对应用软件屏蔽了芯片相关的复杂底层处理,使用户在软件层面调用相应接口就可以完成媒体处理开发,如输入视频捕获、H264 / H265 / JPEG 编码、视频图像处理等。

系统框架

  • 应用层:基于 RKMPI 及其他驱动,由用户开发的应用软件系统。
  • RKMPI 层:基于适配层( 已有的 Rockchip 芯片适配封装接口 ),完成媒体处理功能。
  • 操作系统适配层:基于 Rockchip 芯片已有的硬件模块对外提供的驱动接口。
  • 操作系统层:基于 Linux 系统。
  • 硬件层:由 Rockchip 芯片加上必要的外围器件构成。

IPC(网络摄像机)应用框架

  • VI 模块捕获视频图像,做剪切、缩放等处理,可以输出多路不同分辩率的图像数据。
  • VPSS 模块接收 VI 模块传输的图像,可对图像进行裁剪、缩放、旋转、像素格式转换等处理。
  • VENC 模块可以直接接收 VI 模块捕获的图像或 VPSS 处理后输出的图像数据,可叠加用户通过 RGN 模块设置的 OSD 图像,然后按不同协议进行编码并输出相应码流。
  • 各个模块通道之间进行绑定,处理后的数据流会直接传输到绑定的下一个多媒体模块。

4.2 内存管理

RKMPI 库的各个组件主要使用内存缓冲池来进行内存管理,内存缓冲池通过内存块进行内存分配和回收,使内存资源在各个媒体处理模块中合理使用。内存缓冲池与其他应用内存相隔离,保证了 RKMPI 库的稳定运行,但是应用数据无法直接对内存缓冲池内的内存进行操作。

内存缓冲池运行方式

  • 内部分配 私有模式下,内存缓冲池由媒体处理模块的通道自行申请,所申请的大小、个数由通道内部根据所设参数申请。

  • 外部分配 共有模式下,内存缓冲池可以由用户自己创建,设置内存缓冲块的大小、属性与数据,传递给指定模块使用。

4.3 视频输入 VI

视频输入组件( VI )实现了将 MIPI Rx( 包含 MIPI 接口、LVDS 接口 )等接口接收视频数据的功能。 VI 组件将接收到的数据存入到指定的内存区域,实现视频数据的采集。

VI 内部数据处理

VI 从软件上划分未输入设备( dev )、输入管道( pipe )、输入通道( channel )三个层级。

  • VI 设备( dev ):支持若干种时序输入,负责对时序进行解析。
  • VI 管道( pipe ):绑定在设备后端,负责设备解析后的数据再处理,主要用于载入 ISP 算法。
  • VI 通道( channel ):视频输入最后一级获取通道。

VI 基础使用

  1. 在进行 VI 模块初始化前请先运行 ISP 算法实现自动曝光控制、自动增益控制、自动白平衡、色彩校正等操作,保证捕获图像的质量。
// CamID: 摄像头 ID
// hdr_mode: HDR 模式
// multi_sensor:多路摄像头
// iq_dir: 摄像头 ISP 算法配置文件 iqfile 所在目录
SAMPLE_COMM_ISP_Init(CamId, hdr_mode, multi_sensor, iq_dir);
  1. 运行 ISP 算法
SAMPLE_COMM_ISP_Run(CamId);
  1. 启动 VI 设备
RK_MPI_VI_EnableDev(devID);
  1. 绑定 VI 设备与 VI 管道
RK_MPI_VI_SetDevBindPipe(devID, &stBindPipe);
  1. 设置 VI 通道属性
RK_MPI_VI_SetChnAttr(PipeID, ChnID, &Chn_attr);
  • 注意: VI 通道的输出分辨率参考摄像头模组支持分辨率比率,避免出现图像畸变的情况。
  1. 启动 VI 通道
RK_MPI_VI_EnableChn(PipeID, ChnID);
  1. 绑定其他多媒体模块
MPP_CHN_S viChn;
viChn.enModId = RK_ID_VI;
viChn.s32DevID = DevID;
viChn.s32ChnID = ChnID;
RK_MPI_SYS_Bind(&viChn, &otherChn);
  1. 与其他多媒体取消绑定
RK_MPI_SYS_UnBind(&viChn,&otherChn);
  1. 关闭 VI 通道与设备
RK_MPI_VI_DisableChn(PipeID,ChnID);
RK_MPI_VI_DisableDev(DevID);

4.4 视频处理子系统 VPSS

视频处理子系统( VPSS )支持对输入图像镜像缩放,固定角度旋转,像素格式转换、裁剪等处理。

VPSS 上下文关系

VPSS 可与 VI 和 VENC 等模块进行绑定,前者为 VPSS 的输入源,后者为 VPSS 的接收者。用户可通过 VPSS 接口对组进行管理,每个组仅可与一个输入源绑定。

  • VPSS 组( group ):控制输入,各个组分时复用硬件设备,硬件依次处理各个组提交的任务。
  • VPSS 通道( channel ):控制输出,每个通道具有缩放、裁剪等功能,通道处理后的图像发送给 VPSS 的接收者。

VPSS 基础使用

  1. 创建 VPSS 组
RK_MPI_VPSS_CreateGrp(GrpID, &VpssGrpAttr);
  1. 设置 VPSS 通道属性
RK_MPI_VPSS_SetChnAttr(GrpID, ChnID, &VpssChnAttr);
  • 注意: VPSS 组负责管理输入,设置属性时注意设置 enPixelFormat 属性与绑定的输入源一致,u32MaxWu32MaxH 大于输入源的捕获图像大小。
  1. 启动 VPSS 通道
RK_MPI_VPSS_EnableChn(GrpID, ChnID);
  1. 启动 VPSS 组
RK_MPI_VPSS_StartGrp(GrpID);
  1. 输出 VPSS 数据
  • 绑定其它多媒体模块
MPP_CHN_S vpssChn;
vpssChn.enModId = RK_ID_VI;
vpssChn.s32DevID = DevID;
vpssChn.s32ChnID = ChnID;
RK_MPI_SYS_Bind(&vpssChn, &otherChn);
  • 直接获取帧数据
RK_MPI_VPSS_GetChnFrame(GrpID, ChnID, &VpssFrame,-1);
void *data = RK_MPI_MB_Handle2VirAddr(VpssFrame.stVFrame.pMbBlk);
  1. 停止并关闭 VPSS 组
RK_MPI_VPSS_StopGrp(GrpID);
RK_MPI_VPSS_DestroyGrp(GrpID);

4.5 视频编码 VENC

视频编码组件( VENC )支持多路实时编码且每路编码独立,支持在编码的同时调度 Region 模块对编码图像内容进行叠加和遮挡。主要支持的编码格式有 H264 、 H265 、 JPEG 、 MJPEG 。

VENC 内部数据处理

VENC 从数据帧中获取图像,进行图像处理、 RGN 模块处理后传入编码通道,结合数据帧的时间戳等信息进行编码。

  • VENC 通道( channel ):管理图像的输入与编码属性,使用 RK_MPI_VENC_StartRecvFrame 接口来启动视频编码接收输入图像数据。

VENC 基础使用

  1. 创建并设置 VENC 通道
RK_MPI_VENC_CreateChn(chnId, &stAttr);
  • VENC 通道需要设置 enTypeu32Profile 参数来设置编码类型
// 设置编码类型为 H264
stAttr.stVencAttr.enType = RK_VIDEO_ID_AVC;
stAttr.stVencAttr.u32Profile = H264E_PROFILE_MAIN;
  • VENC 需要根据设置的编码类型设置相应的结构体参数
// 设置 H264 编码属性
stAttr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
stAttr.stRcAttr.stH264Cbr.u32BitRate = BitRate;
stAttr.stRcAttr.stH264Cbr.u32Gop = Gop;
  1. 开始接收帧数据
RK_MPI_VENC_StartRecvFrame(ChnId, &stRecvParam);
  1. 从 VENC 输出中获取帧数据
RK_MPI_VENC_GetStream(ChnId, &stFrame, -1);
  1. 获取 VENC 帧内存块虚拟地址
void *Data = RK_MPI_MB_Handle2VirAddr(stFrame.pstPack->pMbBlk);
  1. 停止接收帧数据
RK_MPI_VENC_StopRecvFrame(ChnId);
  1. 关闭 VENC 通道
RK_MPI_VENC_DestroyChn(ChnId);

5 RKRTSP 库使用

RKRTSP 是 RockChip 提供的一套仅供测试使用的 rtsp 服务器软件接口,仅作为 RKMPI 库的功能验证使用,可以通过软件接口快速实现 rtsp 推流功能。

5.1 RKRTSP 基础使用

  1. 创建 rtsp 实例
g_rtsplive = create_rtsp_demo(port);
  1. 创建 rtsp 接口
g_rtsp_session = rtsp_new_session(g_rtsplive, "/live/0");
  1. 设置 rtsp 传输属性
rtsp_set_video(g_rtsp_session, RTSP_CODEC_ID_VIDEO_H264, NULL, 0);
  • 注意: 目前 RKRTSP 库软件接口暂不支持 MJPEG 推流。
  1. 同步 rtsp 时间戳
rtsp_sync_video_ts(g_rtsp_session, rtsp_get_reltime(), rtsp_get_ntptime());
  1. 更新视频流设置并驱动事件
rtsp_tx_video(g_rtsp_session, (uint8_t *)pData, PackLen, PTS);
rtsp_do_event(g_rtsplive);
  1. 删除 rtsp 实例
rtsp_del_demo(g_rtsplive);

6 opencv-mobile 标注推流帧数实例

6.1 图像帧获取

RKMPI 库内部内存使用内存缓冲进行管理,在应用层想要实现对捕获图像进行处理再进行 rtsp 推流有两种方式。

  • 使用 VI 组件进行捕获

将捕获的图像经过 VPSS 转换像素格式后获取帧数据,转换为 cv::Mat 类型后使用 opencv-mobile 进行处理,最后拷贝回帧数据内存缓冲块虚拟地址,使用 RK_MPI_VENC_SendFrame 将 VPSS 帧数据传输到 VENC 组件进行编码。

  • 使用 opencv-mobile 进行捕获

创建内存缓冲池,从内存缓冲池中获取内存缓冲块存放图像数据,使用 opencv-mobile 捕获图像进行处理,转换像素格式拷贝入内存缓冲块,最后调用 RK_MPI_VENC_SendFrame 将存放内存缓冲块地址、时间戳、图像大小等信息的 VIDEO_FRAME_INFO_S 结构体数据传输到 VENC 组件进行编码。

6.2 基于 VI 图像捕获 rtsp 推流实例

运行效果

使用 Luckfox-pico pro 的 VI 组件捕获 2304 × 1296 图像,使用 opencv-mobile 标注帧率后编码为 H264 进行 rtsp 推流,可以达到 23 帧左右。

注意: 使用 Luckfox-pico mini / plus 进行测试时可以因为内存限制无法正常运行,请尝试降低捕获分辨率。

实现流程

  1. RKRTSP、 RKMPI、 RKAIQ 初始化
// RKAIQ Init
SAMPLE_COMM_ISP_Init(CamID, hdr_mode, multi_sensor, iq_dir);
SAMPLE_COMM_ISP_Run(CamID);

// RTSP Init
g_rtsplive = create_rtsp_demo(554);
g_rtsp_session = rtsp_new_session(g_rtsplive, "/live/0");
rtsp_set_video(g_rtsp_session, RTSP_CODEC_ID_VIDEO_H264, NULL, 0);
rtsp_sync_video_ts(g_rtsp_session, rtsp_get_reltime(), rtsp_get_ntptime());

// RKMPI Init
RK_MPI_SYS_Init();
  1. VI、 VPSS、 VENC 组件初始化
// VI Init
vi_dev_init();
vi_chn_init(0, width, height);

// VPSS Init
vpss_init(0, width, height);

// VENC Init
RK_CODEC_ID_E enCodecType = RK_VIDEO_ID_AVC;
venc_init(0, width, height, enCodecType);
  • VI 通道的像素格式要与 VPSS 组的像素格式一致
vi_chn_attr.enPixelFormat = RK_FMT_YUV420SP; // VI 通道属性

stGrpVpssAttr.enPixelFormat = RK_FMT_YUV420SP; // VPSS 组属性
  • 为了方便将数据传入 RKNN 模型中进行推理,使用 VPSS 组件将像素格式转换为 RGB888
stVpssChnAttr.enPixelFormat = RK_FMT_RGB888;
  • VENC 设置进行 H264 编码的相关属性
stAttr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
stAttr.stRcAttr.stH264Cbr.u32BitRate = BitRate;
stAttr.stRcAttr.stH264Cbr.u32Gop = GOP;
  • 注意: 例程中使用的 GOP 为 1 , 设置所有数据帧都为 I 帧,在 PC 端使用 VLC 软件设置缓存时间不能低于 500 ms,可以尝试修改进行优化。
  1. 绑定 VI 通道到 VPSS 上
stSrcChn.enModId = RK_ID_VI;
stSrcChn.s32DevId = 0;
stSrcChn.s32ChnId = 0;

stvpssChn.enModId = RK_ID_VPSS;
stvpssChn.s32DevId = 0;
stvpssChn.s32ChnId = 0;
s32Ret = RK_MPI_SYS_Bind(&stSrcChn, &stvpssChn);
  1. 获取 VPSS 数据帧和对应内存缓冲块虚拟地址
RK_MPI_VPSS_GetChnFrame(DevId, ChnId, &stVpssFrame, -1);
void *data = RK_MPI_MB_Handle2VirAddr(stVpssFrame.stVFrame.pMbBlk);
  1. 使用 opencv-mobile 标注帧率
cv::Mat frame(height,width,CV_8UC3,data);
cv::putText(frame,fps_text,
cv::Point(40, 40),
cv::FONT_HERSHEY_SIMPLEX,1,
cv::Scalar(0,255,0),2);
  1. 发送数据到 VENC 中编码为 H264 格式
//为了保证数据的完整性使用阻塞发送。
RK_MPI_VENC_SendFrame(vpssChn, &stVpssFrame,-1);
  1. 进行 rtsp 推流
RK_MPI_VENC_GetStream(vencChn, &stFrame, -1);
RK_MPI_MB_Handle2VirAddr(stFrame.pstPack->pMbBlk);
rtsp_tx_video(g_rtsp_session, (uint8_t *)pData, stFrame.pstPack->u32Len,
stFrame.pstPack->u64PTS);
rtsp_do_event(g_rtsplive);
  1. 资源释放
RK_MPI_SYS_UnBind(&stSrcChn, &stvpssChn);

RK_MPI_VI_DisableChn(DevId, ChnId);
RK_MPI_VI_DisableDev(DevId);

RK_MPI_VPSS_StopGrp(GrpId);
RK_MPI_VPSS_DestroyGrp(Grpid);

RK_MPI_VENC_StopRecvFrame(ChnId);
RK_MPI_VENC_DestroyChn(ChnId);
// RKRTSP
rtsp_del_demo(g_rtsplive);
// RKIRQ
SAMPLE_COMM_ISP_Stop(CamId);
// RKMPI
RK_MPI_SYS_Exit();

6.3 基于 opencv-mobile 图像捕获 rtsp 推流例程

运行效果

在 Luckfox-pico pro 上使用 opencv-mobile 捕获图像标注帧率后编码为 H264 进行 rtsp 推流,可以达到 9 帧左右。

注意: 使用 opencv-mobile 捕获摄像头图像的方式较为简单,但是失去了 VI 组件和 VPSS 组件的硬件加速,在性能上有明显损失,推荐使用 VI 来实现图像捕获。

实现流程

  1. 创建内存缓存池,分配内存缓存块
MB_POOL src_Pool = RK_MPI_MB_CreatePool(&PoolCfg);
MB_BLK src_Blk = RK_MPI_MB_GetMB(src_Pool, width * height * 3, RK_TRUE);
  • 创建内存缓冲池的设置的内存块数量不能小于捕获图像像素的数量
PoolCfg.u64MBSize = width * height * channel ;
PoolCfg.u32MBCnt = num;
PoolCfg.enAllocType = MB_ALLOC_TYPE_DMA;
  1. RKRTSP、 RKMPI初始化
// RTSP Init
g_rtsplive = create_rtsp_demo(554);
g_rtsp_session = rtsp_new_session(g_rtsplive, "/live/0");
rtsp_set_video(g_rtsp_session, RTSP_CODEC_ID_VIDEO_H264, NULL, 0);
rtsp_sync_video_ts(g_rtsp_session, rtsp_get_reltime(), rtsp_get_ntptime());

// RKMPI Init
RK_MPI_SYS_Init();
  1. VENC 组件初始化
// VENC Init
RK_CODEC_ID_E enCodecType = RK_VIDEO_ID_AVC;
venc_init(0, width, height, enCodecType);
  • VENC 设置进行 H264 编码的相关属性
stAttr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
stAttr.stRcAttr.stH264Cbr.u32BitRate = BitRate;
stAttr.stRcAttr.stH264Cbr.u32Gop = GOP;
  • 注意: 例程中使用的 GOP 为 1 , 设置所有数据帧都为 I 帧,在 PC 端使用 VLC 软件设置缓存时间不能低于 500 ms,可以尝试修改进行优化。
  1. 构建 VIDEO_FRAME_INFO_S 数据帧结构体
VIDEO_FRAME_INFO_S h264_frame;
h264_frame.stVFrame.u32Width = width;
h264_frame.stVFrame.u32Height = height;
h264_frame.stVFrame.u32VirWidth = width;
h264_frame.stVFrame.u32VirHeight = height;
h264_frame.stVFrame.enPixelFormat = RK_FMT_RGB888;
h264_frame.stVFrame.u32FrameFlag = 160;
h264_frame.stVFrame.pMbBlk = src_Blk;
unsigned char *data = (unsigned char *)RK_MPI_MB_Handle2VirAddr(src_Blk);
  • 数据帧像素格式要与 VENC 通道像素格式一致。
  1. 使用 opencv-mobile 捕获图像并标注帧率,将图像数据以 RGB888 的格式拷贝到数据帧的内存缓冲块中
cap >> bgr;
cv::putText(bgr,fps_text,
cv::Point(40, 40),
cv::FONT_HERSHEY_SIMPLEX,1,
cv::Scalar(0,255,0),2);

for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
cv::Vec3b pixel = bgr.at<cv::Vec3b>(y, x);
data[(y * width + x) * 3 + 0] = pixel[2]; // Red
data[(y * width + x) * 3 + 1] = pixel[1]; // Green
data[(y * width + x) * 3 + 2] = pixel[0]; // Blue
}
}
  1. 发送数据到 VENC 中编码为 H264 格式
//为了保证数据的完整性使用阻塞发送。
RK_MPI_VENC_SendFrame(vpssChn, &h264_Frame,-1);
  1. 进行 rtsp 推流
RK_MPI_VENC_GetStream(vencChn, &stFrame, -1);
RK_MPI_MB_Handle2VirAddr(stFrame.pstPack->pMbBlk);
rtsp_tx_video(g_rtsp_session, (uint8_t *)pData, stFrame.pstPack->u32Len,
stFrame.pstPack->u64PTS);
rtsp_do_event(g_rtsplive);
  1. 资源释放
// Destory MB
RK_MPI_MB_ReleaseMB(src_Blk);
// Destory Pool
RK_MPI_MB_DestroyPool(src_Pool);
// VENC
RK_MPI_VENC_StopRecvFrame(ChnId);
RK_MPI_VENC_DestroyChn(ChnId);
// RKRTSP
rtsp_del_demo(g_rtsplive);
// RKMPI
RK_MPI_SYS_Exit();

7 RKNN 推理图像 rtsp 推流实例

为了最大化提高 rtsp 推流的帧率,实例使用 VI 组件来实现摄像头图像捕获。在 RKNN 推理结果的标注上,采用两种方式来实现:

  • 将标注好的图像上传到 VENC 组件进行编码传输
  • 在图像上传到 VENC 组件后再使用 RGN 模块以打 OSD 的形式标注结果

7.1 基于 opencv-mobile 绘制 RKNN 推理结果

运行效果

使用 Luckfox-pico pro 的 VI 组件捕获 720 × 480 分辩率图像,经过 VPSS 组件像素格式转换,opencv-mobile 缩放后进行 RKNN 模型推理,将获取到的结果标注在原数据帧上传输到 VENC 组件进行编码传输。

注意: 受内存限制,Luckfox-pico mini / plus 无法运行 yolov5 物体检测例程。

  • 使用 retinaface 人脸检测模型推流帧率可达到 11 帧左右。

  • 使用 yolov5 物体检测模型推流帧率可达到 7 帧左右。

实现流程

  1. RKRTSP、 RKMPI、 RKAIQ 和 RKNN 初始化
// RKNN Init
init_rknn_model(model_path, &rknn_app_ctx);

// RKAIQ Init
SAMPLE_COMM_ISP_Init(CamID, hdr_mode, multi_sensor, iq_dir);
SAMPLE_COMM_ISP_Run(CamID);

// RTSP Init
g_rtsplive = create_rtsp_demo(554);
g_rtsp_session = rtsp_new_session(g_rtsplive, "/live/0");
rtsp_set_video(g_rtsp_session, RTSP_CODEC_ID_VIDEO_H264, NULL, 0);
rtsp_sync_video_ts(g_rtsp_session, rtsp_get_reltime(), rtsp_get_ntptime());

// RKMPI Init
RK_MPI_SYS_Init();
  1. VI、 VPSS、 VENC 组件初始化
// VI Init
vi_dev_init();
vi_chn_init(0, width, height);

// VPSS Init
vpss_init(0, width, height);

// VENC Init
RK_CODEC_ID_E enCodecType = RK_VIDEO_ID_AVC;
venc_init(0, width, height, enCodecType);
  • VI 通道的像素格式要与 VPSS 组的像素格式一致
vi_chn_attr.enPixelFormat = RK_FMT_YUV420SP; // VI 通道属性

stGrpVpssAttr.enPixelFormat = RK_FMT_YUV420SP; // VPSS 组属性
  • 为了方便将数据传入 RKNN 模型中进行推理,使用 VPSS 组件将像素格式转换为 RGB888。
stVpssChnAttr.enPixelFormat = RK_FMT_RGB888;
  • VENC 设置进行 H264 编码的相关属性
stAttr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
stAttr.stRcAttr.stH264Cbr.u32BitRate = BitRate;
stAttr.stRcAttr.stH264Cbr.u32Gop = GOP;
  • 注意: 例程中使用的 GOP 为 1 , 设置所有数据帧都为 I 帧,在 PC 端使用 VLC 软件设置缓存时间不能低于 500 ms,可以尝试修改进行优化。
  1. 绑定 VI 通道到 VPSS 上
stSrcChn.enModId = RK_ID_VI;
stSrcChn.s32DevId = 0;
stSrcChn.s32ChnId = 0;

stvpssChn.enModId = RK_ID_VPSS;
stvpssChn.s32DevId = 0;
stvpssChn.s32ChnId = 0;
s32Ret = RK_MPI_SYS_Bind(&stSrcChn, &stvpssChn);
  1. 获取 VPSS 数据帧和对应内存缓冲块虚拟地址
RK_MPI_VPSS_GetChnFrame(DevId, ChnId, &stVpssFrame, -1);
void *data = RK_MPI_MB_Handle2VirAddr(stVpssFrame.stVFrame.pMbBlk);
  1. opencv-mobile 读取 VPSS 数据帧后,将图像处理为模型推理的需求大小
cv::Mat frame(height,width,CV_8UC3, data);
cv::Mat model_input;
cv::resize(frame, model_input, cv::Size(model_width,model_height), 0, 0, cv::INTER_LINEAR);
  • 注意: 如果使用对原图像比例要求较高的特征值获取模型,不能简单对图像进行缩放,需要使用缩放并填充黑边的操作来处理图像。
  1. 获取推理结果标注在 VPSS 数据帧图像上。
memcpy(rknn_app_ctx.input_mems[0]->virt_addr, model_input.data, 640*640*3);
inference_rknn_model(&rknn_app_ctx, &od_results);
  1. 发送数据到 VENC 中编码为 H264 格式
//为了保证数据的完整性使用阻塞发送
RK_MPI_VENC_SendFrame(vpssChn, &stVpssFrame,-1);
  1. 进行 rtsp 推流
RK_MPI_VENC_GetStream(vencChn, &stFrame, -1);
RK_MPI_MB_Handle2VirAddr(stFrame.pstPack->pMbBlk);
rtsp_tx_video(g_rtsp_session, (uint8_t *)pData, stFrame.pstPack->u32Len,
stFrame.pstPack->u64PTS);
rtsp_do_event(g_rtsplive);
  1. 资源释放
RK_MPI_SYS_UnBind(&stSrcChn, &stvpssChn);

RK_MPI_VI_DisableChn(DevId, ChnId);
RK_MPI_VI_DisableDev(DevId);

RK_MPI_VPSS_StopGrp(GrpId);
RK_MPI_VPSS_DestroyGrp(Grpid);

RK_MPI_VENC_StopRecvFrame(ChnId);
RK_MPI_VENC_DestroyChn(ChnId);
// RKRTSP
rtsp_del_demo(g_rtsplive);
// RKIRQ
SAMPLE_COMM_ISP_Stop(CamId);
// RKMPI
RK_MPI_SYS_Exit();
// RKNN
release_rknn_model(&rknn_app_ctx);

7.2 基于 OSD 绘制 RKNN 推理结果

运行效果

使用 Luckfox-pico pro 的 VI 组件捕获 720 × 480 分辩率图像,设置输出两个通道,一个通道与 VPSS 组绑定,一个通道与 VENC 通道绑定,开启两个线程,一个线程负责 rtsp 推流,一个线程运行 RKNN 模型推理,将推理结果以 OSD 的形式显示 VENC 码流上。

实现流程

  1. RKRTSP、 RKMPI、 RKAIQ 和 RKNN 初始化
// RKNN Init
init_rknn_model(model_path, &rknn_app_ctx);

// RKAIQ Init
SAMPLE_COMM_ISP_Init(CamID, hdr_mode, multi_sensor, iq_dir);
SAMPLE_COMM_ISP_Run(CamID);

// RTSP Init
g_rtsplive = create_rtsp_demo(554);
g_rtsp_session = rtsp_new_session(g_rtsplive, "/live/0");
rtsp_set_video(g_rtsp_session, RTSP_CODEC_ID_VIDEO_H264, NULL, 0);
rtsp_sync_video_ts(g_rtsp_session, rtsp_get_reltime(), rtsp_get_ntptime());

// RKMPI Init
RK_MPI_SYS_Init();
  1. VI、 VPSS、 VENC 组件初始化
// VI Init
vi_dev_init();
vi_chn_init(0, width, height);

// VPSS Init
vpss_init(0, width, height);

// VENC Init
RK_CODEC_ID_E enCodecType = RK_VIDEO_ID_AVC;
venc_init(0, width, height, enCodecType);
  • VI 通道的像素格式要与绑定的接收者一致
vi_chn_attr.enPixelFormat = RK_FMT_YUV420SP; // VI 通道属性

stGrpVpssAttr.enPixelFormat = RK_FMT_YUV420SP; // VPSS 组属性

stChnVencAttr.stVencAttr.enPixelFormat = RK_FMT_YUV420SP; // VENC 通道属性
  • 为了方便将数据传入 RKNN 模型中进行推理,使用 VPSS 组件将像素格式转换为 RGB888
stVpssChnAttr.enPixelFormat = RK_FMT_RGB888;
  • VENC 设置进行 H264 编码的相关属性
stAttr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
stAttr.stRcAttr.stH264Cbr.u32BitRate = BitRate;
stAttr.stRcAttr.stH264Cbr.u32Gop = GOP;
  • 注意: 例程中使用的 GOP 为 1 , 设置所有数据帧都为 I 帧,在 PC 端使用 VLC 软件设置缓存时间不能低于 500 ms,可以尝试修改进行优化。
  1. 通道绑定
  • VI 通道 0 与 VENC 通道绑定
stSrcChn.enModId = RK_ID_VI;
stSrcChn.s32DevId = 0;
stSrcChn.s32ChnId = 0;

stvencChn.enModId = RK_ID_VENC;
stvencChn.s32DevId = 0;
stvencChn.s32ChnId = 0;
s32Ret = RK_MPI_SYS_Bind(&stSrcChn, &stvencChn);
  • VI 通道 1 与 VPSS 组绑定
stSrcChn.enModId = RK_ID_VI;
stSrcChn.s32DevId = 0;
stSrcChn.s32ChnId = 1;

stvpssChn.enModId = RK_ID_VPSS;
stvpssChn.s32DevId = 0;
stvpssChn.s32ChnId = 0;
s32Ret = RK_MPI_SYS_Bind(&stSrcChn, &stvpssChn);
  1. RKNN 推理线程
  • 获取 VPSS 数据帧和对应内存缓冲块虚拟地址
RK_MPI_VPSS_GetChnFrame(DevId, ChnId, &stVpssFrame, -1);
void *data = RK_MPI_MB_Handle2VirAddr(stVpssFrame.stVFrame.pMbBlk);
  • opencv-mobile 读取 VPSS 数据帧图像后,将图像处理为模型推理的需求大小
cv::Mat frame(height,width,CV_8UC3, data);
cv::Mat model_input;
cv::resize(frame, model_input, cv::Size(model_width,model_height), 0, 0, cv::INTER_LINEAR);
  • 注意: 如果使用对原图像比例要求较高的特征值获取模型,不能简单对图像进行缩放,需要使用缩放并填充黑边的操作来处理图像。
  • 获取推理结果并将结果以 OSD 的形式显示在 VENC 码流上
memcpy(rknn_app_ctx.input_mems[0]->virt_addr, model_input.data, 640*640*3);
inference_rknn_model(&rknn_app_ctx, &od_results);
  • 注意: RGN 模块将 OSD 显示在码流上使用的是覆盖 ( overlay ) 的形式,对内存占用较高,例程中只实现了对一张人脸进行标注。
  1. 推流线程进行 rtsp 推流
RK_MPI_VENC_GetStream(vencChn, &stFrame, -1);
RK_MPI_MB_Handle2VirAddr(stFrame.pstPack->pMbBlk);
rtsp_tx_video(g_rtsp_session, (uint8_t *)pData, stFrame.pstPack->u32Len,
stFrame.pstPack->u64PTS);
rtsp_do_event(g_rtsplive);
  1. 资源释放
RK_MPI_SYS_UnBind(&stSrcChn0, &stvencChn);
RK_MPI_SYS_UnBind(&stSrcChn1, &stvpssChn);

RK_MPI_VI_DisableChn(DevId, ChnId0);
RK_MPI_VI_DisableChn(DevId, ChnId1);
RK_MPI_VI_DisableDev(DevId);

RK_MPI_VPSS_StopGrp(GrpId);
RK_MPI_VPSS_DestroyGrp(Grpid);

RK_MPI_VENC_StopRecvFrame(ChnId);
RK_MPI_VENC_DestroyChn(ChnId);
// RKRTSP
rtsp_del_demo(g_rtsplive);
// RKIRQ
SAMPLE_COMM_ISP_Stop(CamId);
// RKMPI
RK_MPI_SYS_Exit();
// RKNN
release_rknn_model(&rknn_app_ctx);

8 编译运行

8.1 编译(在PC上执行)

  1. 获取 git 仓库源码
git clone https://github.com/LuckfoxTECH/luckfox_pico_rkmpi_example.git
  1. 设置环境变量
export LUCKFOX_SDK_PATH=< luckfox-pico Sdk 地址>

注意:使用绝对地址。 3. 执行 ./build.sh 后选择编译的例程

1) luckfox_pico_rtsp_opencv
2) luckfox_pico_rtsp_opencv_capture
3) luckfox_pico_rtsp_retinaface
4) luckfox_pico_rtsp_retinaface_osd
5) luckfox_pico_rtsp_yolov5
Enter your choice [1-5]:

8.2 运行(在 Luckfox Pico 板端执行 )

  1. 编译完成后会在luckfox_pico_rkmpi_example/install文件夹下生成对应的部署文件夹
luckfox_pico_rtsp_opencv_demo
luckfox_pico_rtsp_opencv_capture_demo
luckfox_pico_rtsp_retinaface_demo
luckfox_pico_rtsp_retinaface_osd_demo
luckfox_pico_rtsp_yolov5_demo
  1. 将生成的部署文件夹完整上传到 Luckfox Pico 上 (可使用adb ssh等方式) ,板端进入文件夹运行
# 在 Luckfox Pico 板端运行,<Demo Target> 是部署文件夹中的可执行程序
chmod a+x <Demo Target>
./<Demo Target>
  1. 使用 VLC 打开网络串流 rtsp://172.32.0.93/live/0(按实际情况修改 IP 地址拉取图像)

注意

  • 在运行demo前请执行 RkLunch-stop.sh 关闭 Luckofox Pico 开机默认开启的后台程序 rkicp ,解除对摄像头的占用。
  • RV1103 的系统资源较少,无法正常运行时请降低视频捕获的分辨率。