x264代码剖析(十三):核心算法之帧间预測函数x264_mb_analyse_inter_*()

帧间预測是指利用视频时间域相关性,使用临近已编码图像像素预測当前图像的像素,以达到有效去除视频时域冗余的目的。

因为视频序列通常包含较强的时域相关性,因此预測残差值接近于0。将残差信号作为兴许模块的输入进行变换、量化、扫描及熵编码。可实现对视频信号的高效压缩。

本文将重点讨论基本档次支持的P片帧间预測工具以及主要和扩展档次支持的B片和加权预測等帧间预測工具,最后分析了帧间预測函数的主要功能。

1、可变尺寸块运动补偿

在H.264中。每一个16*16大小的宏块能够有4种切割方式:一个16*16,两个16*8,两个8*16,四个8*8,其运动补偿也有对应的四种,例如以下图所看到的。

而8*8模式的每一个子宏块还能够进一步用4种方式再进行切割:一个8*8,两个8*4。两个4*8。四个4*4。其运动补偿也有对应的四种,例如以下图所看到的。

这些切割和宏块大大提高了各宏块之间的关联性,这样的切割下的运动补偿称为树状结构运动补偿。

每一个切割或子宏块都有一个独立的运动补偿,每一个运动矢量MV必须被编码和传输,切割的选择也需编码到压缩比特流中。

对于大的切割尺寸而言。MV选择和切割类型仅仅须要少量的比特来表征,可是运动补偿残差在多细节区域能量将很高。对于小的尺寸而言,切割运动补偿残差能量低,可是须要较多的比特来表征MV和切割选择,切割尺寸的选择影响了压缩性能。

总体而言,大的切割尺寸适合于平坦区域,而小的尺寸适合于多细节区域。

2、运动矢量

帧间编码宏块的每一个切割或者子宏块都是从參考图像的某一同样尺寸区域预測而得。两者之间的差异(MV),对于亮度成分採用1/4像素精度,而对于色度成分则採用1/8像素精度。亚像素位置的亮度和色度像素并不存在于參考图像中。需利用临近已编码点进行内插而得。假设MV的垂直和水平分量为整数,则參考块对应像素实际存在。假设当中一个或两个为分数,则预測像素要通过參考帧中对应像素内插获得。

3、MV预測

每一个切割MV的编码须要相当的比特数,特别是使用小的切割尺寸时。

为降低传输比特数,利用临近切割的MV较强的相关性,MV可由临近已编码切割的MV预測而得。预測矢量MV(p)基于已计算MV和MVD(预測与当前的差异)。并被编码和传送。MV(p)取决于运动补偿尺寸和临近MV的有无。

举例说明:例如以下图,E为当前宏块或宏块切割子宏块。A/B/C分别为E的左、上、右上方的三个相相应块。假设E的左边不止一个切割,取其最上的一个为A。上方不止一个切割时,取其最左边一个为B,如右下图所看到的,左下图显示了全部切割有同样尺寸时的临近切割选择,右下图给出了不同尺寸时的临近切割的选择。

当中:

(1)、传输切割不包含16*8和8*16时,MV(p)为A/B/C切割MV的中值;

(2)、16*8切割。上面部分MV(p)由B预測,以下部分由A预測;

(3)、8*16切割,左面部分MV(p)由A预測。右面部分由C预測;

(4)、Skipped宏块。MV(p)为A/B/C切割MV的中值。

当已传送块不存在时,MV(p)选择需又一次调整。在解码端,MV(p)以同样的方式形成并加到MVD上,对于跳跃宏块来说,不存在MVD,运动补偿宏块也由MV直接生成。

4、B片预測

B片中的帧间编码宏块的每一个子块都是由一个或两个參考图像预測而得。该參考图像在当前图像的前面或后面。

參考图像存储于编解码器中,其选择有多种方式。下图显示了三种方式:一个前向一个后向、两个前向、两个后向。

4.1、參考图像

B片用到了两个已编码图像列表:list0和list1。包括短期和长期两种。

这两个列表都可包括前向和后向的已编码图像,图像的显示顺序排列採用图像序号POC表示。当中:

List0:近期前向图像标为index0,接着是其余前向图像(POC递减顺序)以及后向图像(POC递增顺序)。

List1:近期后向图像标为index0。接着是其余后向图像(POC递增顺序)以及前向图像(POC递减顺序)。

举例说明:一个H.264解码器存储了6幅短期參考图像。其POC分别为:123,125。126,128。129和130。

当前图像为127。全部6幅短期參考图像在list0和list1中都标为“用作參考”。例如以下表所看到的。

4.2、预測模式选择

B片的预測方式包含:宏块切割方式、双向选择方式、參考列表选择方式等等。

详细说,
B片中宏块切割可由多种预測方式中的一种实现,如直接模式、利用list0的运动补偿模式、利用list1的运动补偿模式或者利用list0和list1的双向运动补偿模式。每一个切割可选择各自的不同的预測模式例如以下表。假设8×8切割被使用,每一个8×8切割所选择的模式适用于切割中的全部亚切割。

下图给出一个样例,左边的两个16×8切割分别使用List0和双向预測模式,而右边的 4 个8×8切割分别採用直接、list0、list1和双向预測四种模式。

4.3、双向预測

所谓双向预測就是參考块是由list0和list1的參考图像得出的,从list0和list1分别得出两个运动补偿參考区域(须要两个MV)。而预測块的像素取list0和list1对应像素的平均值。

4.4、直接预測

直接预測模式编码的B片宏块或宏块切割不传送MV。而是由解码器计算list0和 list1 中已编码MV,得出解码残差像素的双向预測运动补偿。

B片中的skipped宏块便由解码器用直接模式重建而得。

直接预測模式详细可分为空域和时域两种模式,片头会指明将用时间还是空间方式计算直接模式或其切割的矢量。

在空间模式中。 list0 和 list1 预測矢量计算例如以下:假设第一幅 list1 參考图像的 co-located MB 或切割有一个 MV 幅度上小于±1/2 亮度像素,其一个或两个预測矢量置为 0。否则预測 list0 和 list1 矢量用以计算双向运动补偿。

在时间模式中,计算过程例如以下:

1)、找出 list1 图像 co-located MB 或切割对应的 list0 參考图像。该 list0 參考作为当前 MB 或切割的 list0 參考;

2)、找出 list1 图像 co-located MB 或切割对应的 list0MV。

3)、计算当前图像和 list1 图像的 POC 的 MV,作为新的 list1 MV1;

4)、计算当前图像和 list0 图像的 POC 的 MV,作为新的 list0 MV0。

这些模式在预測參考宏块或切割不提供或帧内编码等情况下需作出调整。

举例:当前宏块 list1 參考在当前帧两幅图像后出现,例如以下图所看到的。

List1參考 co-located MB有一MV(+2.5,+5),指向list0參考图像(出现于当前图像3幅图像前)。

解码器分别计算指向
list1和 list0 的MV1(-1。-2)和MV0(+1.5,+3)。

5、加权预測

加权预測是一种用来修正 P 或
B 片中运动补偿预測像素方法。

H.264 中由 3 中加权预測类型:

1)、P 片宏块“explicit”加权预測;

2)、B 片宏块“explicit”加权预測;

3)、B 片宏块“implicit”加权预測;

每一个预測像素 pred0(i,j)和 pred1(i,j)在运动补偿之前通过加权系数 ω0 和 ω1 修正。在“explicit”类型中,加权系数由编码器决定并在片头中传输。在“implicit”类型中。系数 ω0 和 ω1 由对应 list0 和 list1 參考图像的时间位置推出。

大的系数用于时间上接近当前图像的情况,小的则用于时间上远离当前图像的情况。

可见, H.264 採用树状结构的运动补偿技术,提高了预測能力。特别是。小块预測提高了模型处理更好的运动可以描写叙述的能力。产生更好的图像质量。 H.264 运动向量的精度提高到 1/4 像素(亮度),运动补偿算法的预測能力得到进一步提高。 H.264 还提供多參考帧可选模式。这将产生更好的视频质量和效率更高的视频编码。相对于 1 帧參考, 5 个參考帧可以节约 5%~10%的比特率。且有助于比特流的恢复。当然,并非说參考帧越多越好,经实验。考虑到缓冲区的能力和编码器的效率,眼下一般都选取3~5个參考帧。

6、帧间预測函数x264_mb_analyse_inter_*()

帧间预測的帧类型大多是P帧或B帧,也有可能有I帧,对于P或B帧,则进行例如以下的流程:

a)、调用x264_macroblock_probe_pskip()分析是否为Skip宏块,假设是的话则不再进行以下分析。

b)、调用x264_mb_analyse_inter_p16x16()分析P16x16帧间预測的代价。

c)、调用x264_mb_analyse_inter_p8x8()分析P8x8帧间预測的代价。

d)、假设P8x8代价值小于P16x16,则依次对4个8x8的子宏块切割进行推断:

i、调用x264_mb_analyse_inter_p4x4()分析P4x4帧间预測的代价。

ii、假设P4x4代价值小于P8x8。则调用
x264_mb_analyse_inter_p8x4()和x264_mb_analyse_inter_p4x8()分析P8x4和P4x8帧间预測的代价。

e)、假设P8x8代价值小于P16x16,调用x264_mb_analyse_inter_p16x8()和x264_mb_analyse_inter_p8x16()分析P16x8和P8x16帧间预測的代价。

f)、此外还要调用x264_mb_analyse_intra(),检查当前宏块作为Intra宏块编码的代价是否小于作为P宏块编码的代价(P
Slice中也同意有Intra宏块)。

以x264_mb_analyse_inter_p16x16()函数为例,其函数关系图例如以下所看到的:

以下相同以x264_mb_analyse_inter_p16x16()为例分析P16x16宏块的帧间预測方式。该函数的定义位于encoder\analyse.c,代码例如以下:

/******************************************************************/
/******************************************************************/
/*
======Analysed by RuiDong Fang
======Csdn Blog:http://blog.csdn.net/frd2009041510
======Date:2016.03.17
*/
/******************************************************************/
/******************************************************************/ /************====== x264_mb_analyse_inter_p16x16()函数 ======************/
/*
功能:帧间预測:16*16大小的P Slice
*/
static void x264_mb_analyse_inter_p16x16( x264_t *h, x264_mb_analysis_t *a )
{
//运动预计相关的信息
//后面的初始化工作主要是对该结构体赋值
x264_me_t m;
int i_mvc;
ALIGNED_4( int16_t mvc[8][2] );
int i_halfpel_thresh = INT_MAX;
int *p_halfpel_thresh = (a->b_early_terminate && h->mb.pic.i_fref[0]>1) ? &i_halfpel_thresh : NULL; /* 16x16 Search on all ref frame */
m.i_pixel = PIXEL_16x16;//设定像素分块大小
LOAD_FENC( &m, h->mb.pic.p_fenc, 0, 0 ); a->l0.me16x16.cost = INT_MAX; //循环搜索全部的參考帧
//i_ref
//mb.pic.i_fref[0]存储了參考帧的个数
for( int i_ref = 0; i_ref < h->mb.pic.i_fref[0]; i_ref++ )
{
m.i_ref_cost = REF_COST( 0, i_ref );
i_halfpel_thresh -= m.i_ref_cost; /* search with ref */
//载入半像素点的列表
//參考列表的4个分量列表,包含yN(整点像素),yH(1/2水平内插),yV(1/2垂直内插), yHV(1/2斜对角内插)
LOAD_HPELS( &m, h->mb.pic.p_fref[0][i_ref], 0, i_ref, 0, 0 );
LOAD_WPELS( &m, h->mb.pic.p_fref_w[i_ref], 0, i_ref, 0, 0 ); x264_mb_predict_mv_16x16( h, 0, i_ref, m.mvp ); //////////////////获得预測的运动矢量MV(通过取中值) if( h->mb.ref_blind_dupe == i_ref )
{
CP32( m.mv, a->l0.mvc[0][0] );
x264_me_refine_qpel_refdupe( h, &m, p_halfpel_thresh );
}
else
{
x264_mb_predict_mv_ref16x16( h, 0, i_ref, mvc, &i_mvc );
x264_me_search_ref( h, &m, mvc, i_mvc, p_halfpel_thresh ); ////////////////关键:运动预计(搜索參考帧)
} /* save mv for predicting neighbors */
CP32( h->mb.mvr[0][i_ref][h->mb.i_mb_xy], m.mv );
CP32( a->l0.mvc[i_ref][0], m.mv ); /* early termination
* SSD threshold would probably be better than SATD */
if( i_ref == 0
&& a->b_try_skip
&& m.cost-m.cost_mv < 300*a->i_lambda
&& abs(m.mv[0]-h->mb.cache.pskip_mv[0])
+ abs(m.mv[1]-h->mb.cache.pskip_mv[1]) <= 1
&& x264_macroblock_probe_pskip( h ) )
{
h->mb.i_type = P_SKIP;
x264_analyse_update_cache( h, a );
assert( h->mb.cache.pskip_mv[1] <= h->mb.mv_max_spel[1] || h->i_thread_frames == 1 );
return;
} m.cost += m.i_ref_cost;
i_halfpel_thresh += m.i_ref_cost; if( m.cost < a->l0.me16x16.cost )
h->mc.memcpy_aligned( &a->l0.me16x16, &m, sizeof(x264_me_t) );
} x264_macroblock_cache_ref( h, 0, 0, 4, 4, 0, a->l0.me16x16.i_ref );
assert( a->l0.me16x16.mv[1] <= h->mb.mv_max_spel[1] || h->i_thread_frames == 1 ); h->mb.i_type = P_L0;
if( a->i_mbrd )
{
x264_mb_init_fenc_cache( h, a->i_mbrd >= 2 || h->param.analyse.inter & X264_ANALYSE_PSUB8x8 );
if( a->l0.me16x16.i_ref == 0 && M32( a->l0.me16x16.mv ) == M32( h->mb.cache.pskip_mv ) && !a->b_force_intra )
{
h->mb.i_partition = D_16x16;
x264_macroblock_cache_mv_ptr( h, 0, 0, 4, 4, 0, a->l0.me16x16.mv );
a->l0.i_rd16x16 = x264_rd_cost_mb( h, a->i_lambda2 );
if( !(h->mb.i_cbp_luma|h->mb.i_cbp_chroma) )
h->mb.i_type = P_SKIP;
}
}
}

从代码中能够看出,函数x264_mb_analyse_inter_p16x16()首先初始化了x264_me_t结构体相关的信息,接着x264_mb_predict_mv_16x16()函数获得预測的运动矢量MV,然后调用x264_me_search_ref()进行运动预计,最后统计运动预计的开销。当中x264_me_search_ref()完毕了运动搜索的流程。相对照较复杂,是帧间预測的重点。

总而言之,在H.264中。对于P帧,每一个16*16大小的宏块能够有4种切割方式:一个16*16(相应于x264_mb_analyse_inter_p16x16函数),两个16*8(相应于x264_mb_analyse_inter_p16x8函数)。两个8*16(相应于x264_mb_analyse_inter_p8x16函数)。四个8*8(相应于x264_mb_analyse_inter_p8x8函数);而8*8模式的每一个子宏块还能够进一步用4种方式再进行切割:一个8*8(相应于x264_mb_analyse_inter_p8x8函数)。两个8*4(相应于x264_mb_analyse_inter_p8x4函数),两个4*8(相应于x264_mb_analyse_inter_p4x8函数)。四个4*4(相应于x264_mb_analyse_inter_p4x4函数)。对于B帧,每一个16*16大小的宏块也能够有4种切割方式:一个16*16(相应于x264_mb_analyse_inter_b16x16函数)。两个16*8(相应于x264_mb_analyse_inter_b16x8函数)。两个8*16(相应于x264_mb_analyse_inter_b8x16函数),四个8*8(相应于x264_mb_analyse_inter_b8x8函数)。

x264代码剖析(十三):核心算法之帧间预測函数x264_mb_analyse_inter_*()的更多相关文章

  1. HM编码器代码阅读(14)——帧间预測之AMVP模式(二)predInterSearch函数

    简单介绍     predInterSearch基本的工作是ME(运动预计)和MC(运动补偿).     函数中有一个bTestNormalMC变量.它表示是否进行正常的MC过程,正常的MC过程就是进 ...

  2. x264代码剖析(十五):核心算法之宏块编码中的变换编码

    x264代码剖析(十五):核心算法之宏块编码中的变换编码 为了进一步节省图像的传输码率.须要对图像进行压缩,通常採用变换编码及量化来消除图像中的相关性以降低图像编码的动态范围.本文主要介绍变换编码的相 ...

  3. x264代码剖析(八):encode()函数之x264_encoder_close()函数

    x264代码剖析(八):encode()函数之x264_encoder_close()函数 encode()函数是x264的主干函数.主要包含x264_encoder_open()函数.x264_en ...

  4. x264代码剖析(三):主函数main()、解析函数parse()与编码函数encode()

    x264代码剖析(三):主函数main().解析函数parse()与编码函数encode() x264的入口函数为main().main()函数首先调用parse()解析输入的參数,然后调用encod ...

  5. HM16.0之帧间预测——xCheckRDCostInter()函数

    参考:https://blog.csdn.net/nb_vol_1/article/category/6179825/1? 1.源代码: #if AMP_MRG Void TEncCu::xCheck ...

  6. H.264学习笔记3——帧间预测

    帧间预测主要包括运动估计(运动搜索方法.运动估计准则.亚像素插值和运动矢量估计)和运动补偿. 对于H.264,是对16x16的亮度块和8x8的色度块进行帧间预测编码. A.树状结构分块 H.264的宏 ...

  7. 用Java实现MVPtree——MVPtree核心算法代码的搭建

    项目需要,需要把MVPtree这种冷门的数据结构写入Java,然网上没有成形的Java实现,虽说C++看惯了不过对C++实现复杂结构也是看得蒙蔽,幸好客户给了个github上job什么的人用Java写 ...

  8. Java基础-使用JAVA代码剖析MD5算法实现过程

    Java基础-使用JAVA代码剖析MD5算法实现过程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.

  9. x264源代码简单分析:宏块分析(Analysis)部分-帧间宏块(Inter)

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

随机推荐

  1. 19年的桌面KDE的风雨和陪伴,没有什么能够割舍

    概述 KDE是史上功能最强大的桌面环境之一:开源且可自由使用.19年前,1996年10月14日,德国程序员 Matthias Ettrich 开始了这个美观的桌面环境的开发.KDE 提供了用户界面以及 ...

  2. 什麼是 usb upstream port

    主機USB埠是定義為USB纜線的上行端(Upstream)或「A」接頭,即PC端.而裝置USB埠是定義為USB纜線的下行端(Downstream)或「B」接頭,即行動產品端. Reference ht ...

  3. sublime 中设置pylint

    http://www.360doc.com/content/14/1110/11/15077656_424004081.shtml 安装 pylinter 插件   详见 sublime 插件安装 配 ...

  4. Goodbye 2017 B

    [题意]: 鲍勃编程一个机器人在2d迷宫中导航.迷宫有一些障碍.空单元格用'.'表示,其中障碍物用'#'表示.迷宫中有一个机器人.它的起始位置用字符“S”表示.这个位置没有任何障碍.迷宫中也有一个出口 ...

  5. Codeforces 743D Chloe and pleasant prizes(树型DP)

                                                                D. Chloe and pleasant prizes             ...

  6. ABP开发框架前后端开发系列---(1)框架的总体介绍

    ABP是ASP.NET Boilerplate的简称,ABP是一个开源且文档友好的应用程序框架.ABP不仅仅是一个框架,它还提供了一个最徍实践的基于领域驱动设计(DDD)的体系结构模型.学习使用ABP ...

  7. JSOI 2009 BZOJ 1444 有趣的游戏

    题面 题目描述 小阳阳发明了一个有趣的游戏:有n个玩家,每一个玩家均有一个长度为 l 的字母序列,任何两个玩家的字母序列不同.共有m种不同的字母,所有的字母序列都由这m种字母构成,为了方便,我们取大写 ...

  8. ssh免秘钥登陆实现

    1.用处 搭建集群或者工作中登陆跳板机经常需要做免秘钥互相登陆彼此服务器. 2. 准备工作   假设A主机10.20.0.1想通过ssh登录到B主机10.20.0.2上.   那么客户端(A主机)需要 ...

  9. Swagger2接口注释参数使用数组

    allowMultiple = true, paramType = "query", dataType = "string" 输出的就是这样的:Array[st ...

  10. GetModuleFileNameA()与GetCurrentDirectoryA()

    头文件: #include <windows.h> GetModuleFileNameA() char moduleFileName[MAX_PATH]; GetModuleFileNam ...