一、实验目的
ROI视频编码即感兴趣区域视频编码,即针对感兴趣区域进行重点编码,提高编码质量,而对非感兴趣区域采用低质量编码。通过这种方法可以降低码率。本实验即让同学们能够在算能的FFMPEG接口下实现基于ROI的视频编码。
二、实验内容
在实验8的基础上,进一步实现ROI编码。即对图像不同区域设置不同的QP进行视频编码。
三、开发环境
开发主机:Ubuntu 20.04.6 LTS
硬件:算能SE5
四、实验器材
开发主机 + 云平台(或SE5硬件)
五、实验过程与结论
ROI视频编码原理
整帧视频图像的视觉质量很大程度上取决于感兴趣区域ROI部分的视觉质量,而非ROI的图像质量下降不易被察觉,对整帧图像的视觉质量影响较小, 感兴趣区域并不局限于人眼的感兴趣区域,在某些具体的工程应用中,可以将某些具体的场景或者事物定义为感兴趣区域。
感兴趣区域ROI的视频编码的基本方法如图所示,对于输入视频帧序列,通过特定的算法和视频帧本身的特性,提取出感兴趣区域和不感兴趣区域掩膜,并将其作为控制信息输入到视频编码器中,用其控制编码器编码当前帧相应的编码参数,包括量化参数,运动估计时的搜索区域大小和参考帧数目,以及预测模式范围等。感兴趣区域ROI提取模块起到预处理和控制器的作用,感兴趣区域提取涉及视觉显著性等,显著性是指视频帧序列中的能够引起 HVS 注意的物体所具有的特性,一般感兴趣区域的显著性可以表现为纹理、亮度、色度、对比度、形态、对比度等特征。
码率控制的目的是在给定的码率条件下,保证良好稳定的输出码流。按照粒度大小码率控制可分为 GOP 层、帧层以及宏块层(或者CTU层)。编码器通过对不同层级中编码单元分配不同的码率比计算相应的QP值。QP 越大,编码单元丢失的细节越多,图像的失真度增加,质量下降,同时码率也降低,随着 QP 的减小,编码单元的细节保留的增多,在提高质量的同时增大了码率。
在固定目标比特率的 CBR 编码模式中,编码器通过编码结果对每帧、每个宏块或者CTU分配不同的码率,动态计算调整 QP 大小,让最终编码的整体码率与给定码率接近。在基于ROI的编码方法中,在ROI 分析与提取的基础上,基于CBR 的码率控制模型,对宏块级的 QP 进行调整,即对 ROI 降低 QP 提高编码质量,对非 ROI 相应提高 QP。通过这种方式,视觉关注区域得到了更多的码率,而非视觉关注区域节省了非必要的码率分配,进而达到在整体码率不变的条件下,提升视频的主观视觉质量的目的。
关键核心代码讲解
设置ROI有效
ROI的设置是在编码过程中通过设置不同区域的QP来设置ROI,在设置之前需要在初始化编码器时设置ROI有效,因此,我们需要在实验8的openEnc函数中设置ROI有效,具体如下:
//av_dict_set_int(&dict, "qp", 25, 0);
av_dict_set_int(&dict, "roi_enable", 1, 0);
注意,上式中的av_dict_set_int(&dict, "qp", 25, 0)是实验8的代码,更换为ROI后需要删除。
设置QP
我们在实验8的基础上,在编码前设置图像不同区域的编码QP。本次实验对编码图像分成四个区域,如图所示,其中不同区域设置成不同的量化参数。在视频编码中,量化参数QP是非常重要的参数,它直接影响着视频的编码比特率。对于某些应用场合,尤其是当传输速率受限时,灵活地控制量化 参数使得编码速率尽量接近给定速率尤为重要。图中右下角区域每个编码单元(宏块或者CTB)QP设为较小的值,QP=10,该区域经过编码后,保留较多的细节信息。而其他三个区域QP=40,这几个区域可以获得较小的码率和较少的计算资源。
H264中ROI代码:
对于H.264编码器,首先计算每个宏块的索引位置(i,j),当判断当前宏块位于右下角区域时,QP设置为40,其他则设置为10。
#define BM_ALIGN16(_x) (((_x)+0x0f)&~0x0f)
#define BM_ALIGN32(_x) (((_x)+0x1f)&~0x1f)
#define BM_ALIGN64(_x) (((_x)+0x3f)&~0x3f)
AVFrameSideData *fside = av_frame_get_side_data(picture, AV_FRAME_DATA_BM_ROI_INFO);
if (fside) {
AVBMRoiInfo *roiinfo = (AVBMRoiInfo*)fside->data;
memset(roiinfo, 0, sizeof(AVBMRoiInfo));
if (enc_ctx->codec_id == AV_CODEC_ID_H264) {
roiinfo->customRoiMapEnable = 1;
roiinfo->customModeMapEnable = 0;
for (int i = 0;i <(BM_ALIGN16(height) >> 4);i++) {
for (int j=0;j < (BM_ALIGN16(width) >> 4);j++) {
int pos = i*(BM_ALIGN16(width) >> 4) + j;
// test_1
if ( (j >= (BM_ALIGN16(width) >> 4)/2) && (i >= (BM_ALIGN16(height) >> 4)/2) ) {
roiinfo->field[pos].H264.mb_qp = 10;
}else{
roiinfo->field[pos].H264.mb_qp = 40;
}
}
}
} else if (enc_ctx->codec_id == AV_CODEC_ID_H265) {
roiinfo->customRoiMapEnable = 1;
roiinfo->customModeMapEnable = 0;
roiinfo->customLambdaMapEnable = 0;
roiinfo->customCoefDropEnable = 0;
for (int i = 0;i <(BM_ALIGN64(height) >> 6);i++) {
for (int j=0;j < (BM_ALIGN64(width) >> 6);j++) {
int pos = i*(BM_ALIGN64(width) >> 6) + j;
// test_1
if ( (j > (BM_ALIGN64(width) >> 6)/2) && (i > (BM_ALIGN64(height) >> 6)/2) ) {
roiinfo->field[pos].HEVC.sub_ctu_qp_0 = 10;
roiinfo->field[pos].HEVC.sub_ctu_qp_1 = 10;
roiinfo->field[pos].HEVC.sub_ctu_qp_2 = 10;
roiinfo->field[pos].HEVC.sub_ctu_qp_3 = 10;
} else {
roiinfo->field[pos].HEVC.sub_ctu_qp_0 = 40;
roiinfo->field[pos].HEVC.sub_ctu_qp_1 = 40;
roiinfo->field[pos].HEVC.sub_ctu_qp_2 = 40;
roiinfo->field[pos].HEVC.sub_ctu_qp_3 = 40;
}
roiinfo->field[pos].HEVC.ctu_force_mode = 0;
roiinfo->field[pos].HEVC.ctu_coeff_drop = 0;
roiinfo->field[pos].HEVC.lambda_sad_0 = 0;
roiinfo->field[pos].HEVC.lambda_sad_1 = 0;
roiinfo->field[pos].HEVC.lambda_sad_2 = 0;
roiinfo->field[pos].HEVC.lambda_sad_3 = 0;
}
H265中ROI代码:
对于H.265/HEVC编码器,编码标准制定了一种非常灵活的QP控制机制,它引入了量化组(Quantization Group, QG)的概念,规定一个CTB可以包含一个或多个固定大小的QG,同一个QG内的所有含有非零系数的CU共享一个QP,不同的QG可以使用不同的QP。这样一来,编码器能够更灵活地进行速率控制。
QG是指将一幅图像分成的固定大小(NxN)的正方形像素块。其大小N由图像参数集(PPS)指定,且必须处于最大CU与最小CU之间(包含最大与最小CU)。图中给出了一个32x32 QG的示意图,其中粗实线为CTB边界,粗虚线表示CU划分方式,细线为QG分界线。CU与QG没有固定的大小关系。由于在一幅图像中,QG为固定大小,而CU是根据视频内容自适应划分出来的,因此可能出现一个QG包含一个或多个CU的情形,也可能存在一个CU包含多个QG的情形。
实验过程
生成可执行文件:
makefile的写法与前面的例程基本相同,按照前面实验1、实验2步骤,生成可执行文件并上传到算能盒子或者云平台中。如果是在云平台上测试,则可将编译好的执行文件通过云空间文件系统上传。
root@d11ae417e206:/tmp/test# ls
ffmpeg_encode_withRoi 1080p.yuv
给可执行文件赋权限并执行。
root@d11ae417e206:/tmp/test# chmod 777 ffmpeg_encode_withRoi
运行指令:
生成并上传编译文件后,根据如下指令在目标开发机终端运行。
./ffmpeg_encode_withRoi 1080.yuv output.h264
运行结果如下:
[h264_bm @ 0x429a90] width =1920
[h264_bm @ 0x429a90] height=1080 src/allocator.c:136 (ion_dmabufalloc_map)
DEBUG: retrieved virtual address for physical memory
src/allocator.c:144 (ion_dmabufalloc_map)
DEBUG: Invalidated physical memory
src/allocator.c:155 (ion_dmabufalloc_map)
DEBUG: virtual address: 0x7f820c8000 aligned: 0x7f820c8000
src/allocator.c:176 (ion_dmabufalloc_unmap)
DEBUG: shut down virtual address for 3133440 bytes of physical memory
[h264_bm @ 0x429a90] input frame: context=(nil), pts=100, dts=-9223372036854775808
[85fb6010] src/enc.c:2264 (Wave5BitIssueCommand)
instanceIndex=0: cmd=0x100
[85fb6010] src/enc.c:1647 (enc_start_one_frame)
instance Index: 0. instance Queue Count: 1, total Queue Count: 1
[85fb6010] src/enc.c:733 (VpuHandlingInterruptFlag)
INTR Done: enc pic. reason=0x100
[85fb6010] src/enc.c:2264 (Wave5BitIssueCommand)
instanceIndex=0: cmd=0x4000
[h264_bm @ 0x429a90] output frame: context=(nil), pts=91, dts=87
enc_pkt.pts=91, enc_pkt.dts=87
rescaled enc_pkt.pts=46592, enc_pkt.dts=44544
Muxing frame
The end of file!
Flushing video encoder
[85fb6010] src/enc.c:2264 (Wave5BitIssueCommand)
instanceIndex=0: cmd=0x100
[85fb6010] src/enc.c:1647 (enc_start_one_frame)
instance Index: 0. instance Queue Count: 1, total Queue Count: 1
[85fb6010] src/enc.c:733 (VpuHandlingInterruptFlag)
INTR Done: enc pic. reason=0x100
[85fb6010] src/enc.c:2264 (Wave5BitIssueCommand)
instanceIndex=0: cmd=0x4000 [h264_bm @ 0x429a90] encoding end!
src/encoder.c:804 (bmvpu_enc_close)
DEBUG: closing encoder src/allocator.c:112 (ion_dmabufalloc_deallocate)
DEBUG: deallocated 262144 bytes of physical memory
src/allocator.c:112 (ion_dmabufalloc_deallocate)
DEBUG: deallocated 262144 bytes of physical memory
src/allocator.c:112 (ion_dmabufalloc_deallocate)
DEBUG: deallocated 262144 bytes of physical memory
src/allocator.c:112 (ion_dmabufalloc_deallocate)
DEBUG: deallocated 262144 bytes of physical memory
src/encoder.c:295 (bmvpu_enc_unload)
DEBUG: unloaded VPU [AVIOContext @ 0x499190] Statistics: 2 seeks, 7 writeouts
encode finish!
#######VideoEnc_FFMPEG exit
注:上图展示为部分关键运行结果,详细运行结果请见运行界面。
Elecard StreamEye分析
对视频文件进行ROI编码后,用Elecard StreamEye分析码流。通过播放编码后的视频可以看出,右下角区域比其他区域的清晰度更高,细节保留的更多。左下角使用较大的QP,使得图像细节丢失,木板的间隙模糊,人工效应明显。而右下角区域使用较小的QP编码,则相对清晰。
同时可以调出MB信息查看每一个编码单元的QP值,可以看出,右下角的QP值更低,其他区域的QP值更高。