精华内容
下载资源
问答
  • 在H.264的解码过程中,每一的数据按照相应的NAL Unit在码流中的顺序传入解码器进行解码。需注意的是,首先传入解码器的视频的NAL unit,解码完成后其对应的图像不一定会首先显示。其原因是由于B的存在,视频...

    《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.264的标准进行解析和实现,欢迎观看!

    “纸上得来终觉浅,绝知此事要躬行”,只有自己按照标准文档以代码的形式操作一遍,才能对视频压缩编码标准的思想和方法有足够深刻的理解和体会!

    链接地址:H.264/AVC视频编解码技术详解

    GitHub代码地址:点击这里


    一、基本概念

    在H.264的解码过程中,每一帧的数据按照相应的NAL Unit在码流中的顺序传入解码器进行解码。需注意的是,首先传入解码器的视频帧的NAL unit,解码完成后其对应的图像不一定会首先显示。其原因是由于B帧的存在,视频帧在输出时会进行顺序重排。视频帧的NAL Unit数据传入解码器的顺序称之为解码顺序,而解码完成后图像显示或输出的顺序称之为显示顺序。解码顺序和显示顺序的概念如下图所示:


    在H.264的码流中,表示解码顺序和显示顺序分别有相应的语法元素表示。其中解码顺序由frame_num表示,而显示顺序由picture_order_count表示。这两个值都会在码流中保存,并在读取slice信息时解析。这两个值在SliceHeader中按下列形式保存:


    某一帧图像在解码完成后,可能会被保存于解码图像缓存中,用于后续图像帧间预测的参考帧。在解码图像缓存中,用于P帧或B帧帧间编码的参考帧图像保存为一个或两个参考帧列表,列表中参考帧的顺序可以通过某个专门的过程进行重排列。

    二、解码顺序计数值frame_num

    在每一个slice的slice_header结构中都可以解析出frame_num这一语法元素,该值表示了当前slice在一个GOP中的解码顺序值。

    在一个GOP中,第一个slice即作为随机接入点的IDR slice,其frame_num值为0,表示当前slice是一个GOP的起点。GOP中的其他slice按照相应距离IDR的顺序按1递增。当另一个语法元素gaps_in_frame_num_value_allowed存在时,slice可以以大于1的值递增,此时缺失的frame_num值需要解码器用空slice数据进行填充。

    三、显示顺序标志值picture_order_count

    POC即picture order count,是用于表示视频帧显示顺序的值。视频中IDR的第一个field作为POC的开始,其值为0。在H.264的标准中,POC的计算方法在标准文档中的8.2.1节中定义。

    对于H.264的码流,有三种结构会被赋予POC的值:coded frame(编码帧), coded field(编码场)和complementary field pair(互补参考场对),每种类型的POC都由TopFieldOrderCnt和BottomFieldOrderCnt这两个值的一个或两个组成:

    1. 对于每一个编码帧,poc包含两个值TopFieldOrderCnt和BottomFieldOrderCnt;
    2. 对于每一个编码场,poc包含一个值,如果该field为顶场则为TopFieldOrderCnt,如果是底场为BottomFieldOrderCnt;
    3. 对于每一个互补参考场对,POC包含两个值,对顶场为TopFieldOrderCnt,对底场为BottomFieldOrderCnt;

    在H.264中,TopFieldOrderCnt和BottomFieldOrderCnt共定义了3种解析方法,由sps中的值pic_order_cnt_type决定。

    3.1 Slice Header中直传模式

    当pic_order_cnt_type为0时,POC的值通过slice_header中的数据计算得到。计算方式如下:

    3.1.1 计算中间变量prevPicOrderCntMsb和prevPicOrderCntLsb

    中间变量prevPicOrderCntMsb和prevPicOrderCntLsb可以认为是当前帧前面一帧的POC数据,其计算方式为:

    • 如果当前帧为IDR帧,则prevPicOrderCntMsb和prevPicOrderCntLsb都设为0;
    • 如果当前帧为非IDR帧,则prevPicOrderCntMsb和prevPicOrderCntLsb都取自按照解码顺序的前一个参考帧的数据,prevPicOrderCntMsb的值为该帧的PicOrderCntMsb,prevPicOrderCntLsb的值为该帧的pic_order_cnt_lsb。

    3.1.2 计算当前帧的PicOrderCntMsb

    该值可以认为是当前帧的POC的高位值。在该过程中需要两个从码流中解析出的语法元素值:

    • pic_order_cnt_lsb:从slice_header结构中读取;
    • MaxPicOrderCntLsb:从sps结构中的log2_max_pic_order_cnt_lsb_minus4计算得到;

    获取到了上述数据之后,可依据标准文档中的公式8-3计算PicOrderCntMsb:

    if((pic_order_cnt_lsb < prevPicOrderCntLsb)&&((prevPicOrderCntLsb − pic_order_cnt_lsb) >= (MaxPicOrderCntLsb / 2)))
        PicOrderCntMsb = prevPicOrderCntMsb + MaxPicOrderCntLsb 
    else if((pic_order_cnt_lsb > prevPicOrderCntLsb) && ((pic_order_cnt_lsb − prevPicOrderCntLsb) > (MaxPicOrderCntLsb / 2)))
        PicOrderCntMsb = prevPicOrderCntMsb − MaxPicOrderCntLsb
    else
        PicOrderCntMsb = prevPicOrderCntMsb
    

    3.1.3 计算TopFieldOrderCnt和BottomFieldOrderCnt

    对于一个非底场的slice,TopFieldOrderCnt的计算方式非常简单,即将PicOrderCntMsb与pic_order_cnt_lsb求和即可:

    TopFieldOrderCnt = PicOrderCntMsb + pic_order_cnt_lsb
    

    对于一个非顶场的slice,BottomFieldOrderCnt的计算方式根据field_pic_flag值判断。若field_pic_flag为0,即当前slice按帧编码,则BottomFieldOrderCnt的计算方法为:

    BottomFieldOrderCnt = TopFieldOrderCnt + delta_pic_order_cnt_bottom
    

    否则,当field_pic_flag为1,即当前slice为一帧的底场时,BottomFieldOrderCnt的计算方法为:

    BottomFieldOrderCnt = PicOrderCntMsb + pic_order_cnt_lsb
    

    3.2 预测与差分方式

    当pic_order_cnt_type为1时,采用这种模式计算POC的值。计算步骤如下:

    3.2.1 计算中间变量prevFrameNum、FrameNumOffset和prevFrameNumOffset

    变量prevFrameNum被赋值为按照解码顺序在当前帧之前一帧的frame_num;类似地,变量prevFrameNumOffset被赋值为按照解码顺序在当前帧之前一帧的FrameNumOffset;
    FrameNumOffset计算方法根据当前帧是否是IDR,以及prevFrameNum与frame_num的比较关系计算得到:

    • 若当前帧为IDR,则FrameNumOffset为0;
    • 若当前帧非IDR,且prevFrameNum大于frame_num,则计算方式为:
      	FrameNumOffset = prevFrameNumOffset + MaxFrameNum
      
    • 若当前帧非IDR,且prevFrameNum小于frame_num,则FrameNumOffset的值即为prevFrameNumOffset;

    3.2.2 计算中间变量absFrameNum、picOrderCntCycleCnt、frameNumInPicOrderCntCycle和expectedPicOrderCnt

    判断从sps中读取的值num_ref_frames_in_pic_order_cnt_cycle,若该值为非0,则absFrameNum的计算方法为:

    absFrameNum = FrameNumOffset + frame_num
    

    否则,absFrameNum的值为0。另外,若nal_ref_idc值为0且absFrameNum非0,absFrameNum需再减去1:

    absFrameNum = absFrameNum − 1
    

    当absFrameNum大于0时,picOrderCntCycleCnt和frameNumInPicOrderCntCycle分别为absFrameNum - 1除以num_ref_frames_in_pic_order_cnt_cycle的商和余数:

    picOrderCntCycleCnt = ( absFrameNum − 1 ) / num_ref_frames_in_pic_order_cnt_cycle
    frameNumInPicOrderCntCycle = ( absFrameNum − 1 ) % num_ref_frames_in_pic_order_cnt_cycle
    

    下一步根据absFrameNum的计算expectedPicOrderCnt的值。若absFrameNum的值为0,则expectedPicOrderCnt的值亦为0;否则该值由picOrderCntCycleCnt、ExpectedDeltaPerPicOrderCntCycle和offset_for_ref_frame共同计算得到:

    if( absFrameNum > 0 ){
    	expectedPicOrderCnt = picOrderCntCycleCnt * ExpectedDeltaPerPicOrderCntCycle;
    	for( i = 0; i <= frameNumInPicOrderCntCycle; i++ )
    		expectedPicOrderCnt = expectedPicOrderCnt + offset_for_ref_frame[ i ];
    } else
    	expectedPicOrderCnt = 0
    

    如果nal_ref_idc的值为0,expectedPicOrderCnt再增加offset_for_non_ref_pic:

    expectedPicOrderCnt = expectedPicOrderCnt + offset_for_non_ref_pic;
    

    3.2.3 计算TopFieldOrderCnt和BottomFieldOrderCnt

    对于帧编码的slice,TopFieldOrderCnt和BottomFieldOrderCnt通过上面计算得到的expectedPicOrderCnt,以及sps中读取的语法元素delta_pic_order_cnt和offset_for_top_to_bottom_field计算得到:

    TopFieldOrderCnt = expectedPicOrderCnt + delta_pic_order_cnt[0];
    BottomFieldOrderCnt = TopFieldOrderCnt +offset_for_top_to_bottom_field + delta_pic_order_cnt[1];
    

    3.3 显示顺序与解码顺序一致

    当pic_order_cnt_type为2时,采用这种模式计算POC的值。计算步骤如下:

    FrameNumOffset和prevFrameNumOffset同模式2中的计算方法类似,FrameNumOffset计算方法根据当前帧是否是IDR,以及prevFrameNum与frame_num的比较关系计算得到:

    • 若当前帧为IDR,则FrameNumOffset为0;
    • 若当前帧非IDR,且prevFrameNum大于frame_num,则计算方式为:
      FrameNumOffset = prevFrameNumOffset + MaxFrameNum
      
    • 若当前帧非IDR,且prevFrameNum小于frame_num,则FrameNumOffset的值即为prevFrameNumOffset;

    计算tempPicOrderCnt:

    • 若当前帧为IDR,则tempPicOrderCnt值为0;
    • 若当前帧为非IDR,且nal_ref_idc为0,则tempPicOrderCnt的计算方法为
      tempPicOrderCnt = 2 * ( FrameNumOffset + frame_num ) − 1
      
    • 否则,tempPicOrderCnt的计算方法为:
      tempPicOrderCnt = 2 * ( FrameNumOffset + frame_num )
      

    最后,在帧编码的条件下,TopFieldOrderCnt和BottomFieldOrderCnt的值都与tempPicOrderCnt相等:

    TopFieldOrderCnt = tempPicOrderCnt
    BottomFieldOrderCnt = tempPicOrderCnt
    

    四、图像管理

    当H.264作为参考帧的某一帧解码完成后,该帧的数据将会保存在解码图像缓存区中,并且按照相应的规则标记为短期或长期参考帧。其中,短期参考帧由上文中提到的frame_num标记,长期参考帧由另一个值LongTermPicNum标记。

    每一个P帧的解码对应一个参考帧列表RefPicList0,每一个B帧对应两个独立的参考帧列表RefPicList0RefPicList1

    4.1 计算图像序号

    参考帧的索引值用于从参考帧列表中获取数据。在参考帧列表的初始化、更新,参考帧的标记,以及处理非连续的frame_num时,需要计算参考帧的图像序号,其中主要有FrameNum, FrameNumWrap, PicNum, LongTermFrameIdx 和 LongTermPicNum等。

    对于一个短期参考帧,计算FrameNum和FrameNumWrap。当前帧的FrameNum和FrameNumWrap计算方法为:

    • 首先设FrameNum的值为对应的短期参考帧的frame_num的值;
    • 如果FrameNum的值大于当前帧slice_header中解析出的frame_num值,则FrameNumWrap的计算方式为:
      FrameNumWrap = FrameNum - MaxFrameNum
      
      否则,FrameNumWrap的计算方式为:
      FrameNumWrap = FrameNum
      

    对于一个长期参考帧,计算其LongTermFrameIdx的值。该过程在下节中详细讨论。

    最后,对于每一个短期参考帧图像,计算PicNum值,对于一个长期参考帧图像,计算LongTermPicNum。如果当前帧为帧编码,即field_pic_flag为0,则二者的值分别与FrameNumWrap和LongTermPicNum相等:

    PicNum = FrameNumWrap
    LongTermPicNum = LongTermFrameIdx
    

    在JM8.6代码中的体现如下:

    if (currPicStructure == FRAME)  
      {
        for (i=0; i<dpb.ref_frames_in_buffer; i++)
        {
          if (dpb.fs_ref[i]->is_used==3)
          {
            if ((dpb.fs_ref[i]->frame->used_for_reference)&&(!dpb.fs_ref[i]->frame->is_long_term))
            {
              if( dpb.fs_ref[i]->frame_num > img->frame_num )
              {
                dpb.fs_ref[i]->frame_num_wrap = dpb.fs_ref[i]->frame_num - MaxFrameNum;
              }
              else
              {
                dpb.fs_ref[i]->frame_num_wrap = dpb.fs_ref[i]->frame_num;
              }
              dpb.fs_ref[i]->frame->pic_num = dpb.fs_ref[i]->frame_num_wrap;
              dpb.fs_ref[i]->frame->order_num=list0idx;
            }
          }
        }
      }
    

    4.2 解码参考帧的标记

    当NALU中的nal_ref_idc值非0,即当前NALU所代表的图像会被作为参考帧的时候,会执行参考帧的标记过程。执行该过程的主要原因可以理解为,由于当前帧会作为参考帧数据放入DPB中,当前DPB中已有的参考帧的性质可能会发生变化,即短期参考帧可能会变为长期参考帧,或者某个参考帧可能会被标记为不再用做参考,因此需要对DPB中的参考帧数据进行重新标记。

    如同其命名所表示的含义一样,一个被标记为“用于短期参考”或“用于长期参考”的视频帧在解码过程中可以作为后续帧的参考数据,直到该参考帧被标记为“不再用作参考”为止。将一个参考帧标记为“不再用作参考”的方法通常有两种:

    • 滑动窗口法:通过一种“先进先出”机制进行的方法;
    • 自适应内存控制法;

    在标准文档8.2.5.1节中,解码参考帧的标记过程按如下步骤执行:

    1. 首先,确保当前帧的所有slice解码完成;
    2. 随后,判断当前帧的帧类型。如果当前帧为一个IDR帧,则进行以下操作:
      • 将所有的参考帧标记为“不作为参考”;
      • 如果long_term_reference_flag为0,则该IDR帧被标记为“作为短期参考”,且MaxLongTermFrameIdx设为“无长期参考帧索引”;
      • 如果long_term_reference_flag为1,则该IDR帧被标记为“作为长期参考”,其LongTermFrameIdx设为0,并且MaxLongTermFrameIdx设为0;
    3. 如果当前帧为非IDR帧,则执行以下操作:
      • 如果adaptive_ref_pic_marking_mode_flag为1,则进行自适应内存控制法标记参考帧;
      • 如果adaptive_ref_pic_marking_mode_flag为0,则进行滑动窗口法标记参考帧;
    4. 如果当前帧为非IDR帧,且没有因为memory_management_control_operation的值等于6而被标记为“用于长期参考”,则该帧被标记为“用于短期参考”。

    4.2.1 滑动窗口法

    下面首先介绍滑动窗口法:

    1. 如果当前图像是一个 complementary reference field pair 中按照解码顺序的第二个场,且第一场被标记为“用作短期参考”,那么当前图像和该complementary reference field pair都被标记为“作为短期参考”。
    2. 否则的话,根据如下步骤执行:
      1. 设numShortTerm为作为短期参考的编码帧、编码场或互补场对的总数,numLongTerm为长期参考帧/场/场对的总数;
      2. 当numShortTerm与numLongTerm之和达到Max(max_num_ref_frames, 1)时,在满足numShortTerm大于0的前提下,FrameNumWrap值最小的那个作为短期参考的编码帧、编码场或互补场对将被标记为“不作为参考”。

    4.2.2 自适应内存控制法

    从上一小节中可以看出,滑动窗口法的效果主要在于将过期的短期参考帧从DPB中移除出去,并不涉及到对长期参考帧的操作(除非遇到IDR时将DPB全部清空)。而自适应内存控制法的操作流程比滑动窗口法要复杂得多。

    执行自适应内存控制法标记参考帧的条件是adaptive_ref_pic_marking_mode_flag为1,此时slice_header中会包含一些附加的语法元素信息,如下表所示:

    从上图的dec_ref_pic_marking结构中可以看出,如果adaptive_ref_pic_marking_mode_flag的值为1,那么其中将会多出若干个值:

    • memory_management_control_operation;
    • difference_of_pic_nums_minus1;
    • long_term_pic_num;
    • long_term_frame_idx;
    • max_long_term_frame_idx_plus1

    其中,memory_management_control_operation可以取的范围为1~6,分别代表了不同的操作。

    4.2.2.1 将短期参考帧标记为“不作为参考”

    当memory_management_control_operation为1时,自适应内存控制过程会将某一个短期参考帧标记为“不作为参考”。具体的执行过程为:

    1. 首先计算picNumX。计算方法为:
      picNumX = CurrPicNum − ( difference_of_pic_nums_minus1 + 1 )
      
      其中,CurrPicNum为当前帧的frame_number,difference_of_pic_nums_minus1从dec_ref_pic_marking中解析得到。
    2. 对于帧编码的图像,PicNum等于picNumX的短期参考帧会被标记为“不作为参考”;

    4.2.2.2 将长期参考帧标记为“不作为参考”

    当memory_management_control_operation为2时,自适应内存控制过程会将某一个长期参考帧标记为“不作为参考”。具体的执行过程很简单,索引为LongTermPicNum等同于long_term_pic_num的长期参考帧将被标记为不作为参考。

    4.2.2.3 将短期参考帧标记为“长期参考帧”

    当memory_management_control_operation为3时,自适应内存控制过程会将某一个短期参考帧标记为“作为长期参考帧”。在这种情况下,码流中会同时包含difference_of_pic_nums_minus1以及long_term_frame_idx这两个值。执行过程如下:

    1. 按照4.2.2.1中的方法计算picNumX;
    2. 如果long_term_frame_idx对应的长期参考帧存在,则该长期参考帧标记为“不作为参考”;
    3. 对于帧编码图像,由picNumX所代表的短期参考帧,将被标记为长期参考帧,并将对应的LongTermFrameIdx设为long_term_frame_idx。

    4.2.2.4 计算MaxLongTermFrameIdx

    当memory_management_control_operation为4时,执行计算MaxLongTermFrameIdx的操作。计算过程如下:

    • 如果码流中解析出的max_long_term_frame_idx_plus1的值为0,则MaxLongTermFrameIdx被设置为“无长期参考帧索引”;
    • 否则,MaxLongTermFrameIdx的值设置为max_long_term_frame_idx_plus1-1。

    所有被标记为“用作长期参考”且LongTermFrameIdx大于了MaxLongTermFrameIdx的图像都会被标记为“不作为参考”。

    4.2.2.5 清空参考帧列表

    当memory_management_control_operation为5时,执行清空参考帧列表操作。该过程会将所有参考帧标记为“不作为参考”并将MaxLongTermFrameIdx设置为“无长期参考帧索引”。

    4.2.2.6 将当前帧标记为长期参考帧

    当memory_management_control_operation为6时,将当前帧标记为长期参考帧。在这种情况下,需从码流中解析出long_term_frame_idx。执行过程如下:

    • 如果long_term_frame_idx对应的长期参考帧存在,则该长期参考帧标记为“不作为参考”;
    • 将当前帧标记为“作为长期参考”,并将其LongTermFrameIdx设置为long_term_frame_idx;

    在当前帧标记完成后,所有被标记为“作为参考帧”的帧、场和互补场对的数量综合不能超过Max( max_num_ref_frames, 1 )规定的值。

    展开全文
  • 一、预测编码 预测编码最常用的形式是差分脉冲编码调制(DPCM) 预测器设计:广泛采用的最简单的是 固定线性预测器,它很适合满足 静态随机... 采样帧间预测编码可以减少时间域上的冗余度,提高压缩比。 ...

    一、预测编码

          预测编码最常用的形式是差分脉冲编码调制(DPCM)

          2011070616504238.png

          预测器设计:广泛采用的最简单的是 固定线性预测器,它很适合满足 静态随机过程模型 的图像。

                           自适应线性预测和非线性预测有更好的效果。

    二、运动估计与运动补偿

          采样帧间预测编码可以减少时间域上的冗余度,提高压缩比。

          采用运动补偿帧间预测可以使预测差的方差大大减少,从而减低码率,提高压缩比。

          块匹配法(BMA)是目前最常用的一种运动估计算法。

         

         

         

    转载于:https://www.cnblogs.com/jiangjh/archive/2011/06/15/2081747.html

    展开全文
  • 作为一个视频编码小白,最近开始着手啃HEVC帧间预测的代码,想用博客记录一下自己的学习过程,也想与大家分享、交流一下。 HEVC代码的学习主要是参考两位大神岳麓吹雪、NB_vol_1的博客以及HM参考软件。 两位大神的...

    作为一个视频编码小白,最近开始着手啃HEVC帧间预测的代码,想用博客记录一下自己的学习过程,也想与大家分享、交流一下。

    HEVC代码的学习主要是参考两位大神岳麓吹雪NB_vol_1的博客以及HM参考软件。
    两位大神的关于HEVC帧间部分的博客如下:
    HEVC代码学习
    HM编码器代码阅读(33)——帧间预测的总结
    而HM软件的安装配置可参考HEVC代码:HM使用+码流分析教程

    应接下来研究的需要(想将自己用其他方式获取的MV直接喂入HEVC),先对HEVC帧间预测部分MV的获取、传递和存储方式进行总结,摸清楚在HEVC中MV的传输路径。

    帧间预测基本架构

    下面是我暂时总结的帧间部分跟MV有关的基本结构,忽略了很多细节的部分,主要是帧间预测部分中关键函数的调用关系。
    在这里插入图片描述

    涉及的主要Class/Struct

    在学习代码的过程中,我发现如果想更好的理清楚HM的代码结构,那么对其中各种Class的定义及调用关系的学习至关重要。
    关于Class的学习可以参考HM参考网站HEVC Test Model (HM) HM-16.18——Class List

    下面我总结出了帧间预测中与MV相关的几个重要Class,有助于更好的理解MV信息在帧间预测过程中如何获取、传递以及存储。

    TComDataCU

    官方定义:CU data structure class.
    是HM中CU的数据类型,其中定义了非常多CU内涉及到的变量及函数。
    该Class内与MV有关的变量及函数总结如下(根据自己的进度持续更新):

    // 将pcCU的MV及其参考信息赋给rcMvField
    static Void   getMvField( const TComDataCU* pcCU, UInt uiAbsPartIdx, RefPicList eRefPicList, TComMvField& rcMvField );
    
    // 获取AMVP候选列表
    Void          fillMvpCand( const UInt uiPartIdx, const UInt uiPartAddr, const RefPicList eRefPicList, const Int iRefIdx, AMVPInfo* pInfo ) const;
    
    // 获取Merge候选列表
    Void          getInterMergeCandidates( UInt uiAbsPartIdx, UInt uiPUIdx, TComMvField* pcMFieldNeighbours, UChar* puhInterDirNeighbours, Int& numValidMergeCand, UInt& numSpatialMergeCandidates , Int mrgCandIdx = -1) const;
    
    // MV信息
    TComCUMvField m_acCUMvField[NUM_REF_PIC_LIST_01];     ///< array of motion vectors.
    // 获取MV信息
    TComCUMvField* getCUMvField( RefPicList e )  { return &m_acCUMvField[e];                  }
    
    // 帧间预测方向
    UChar*        m_puhInterDir;                          ///< array of inter directions
    // 设置帧间预测方向
    Void          setInterDirSubParts( UInt uiDir,  UInt uiAbsPartIdx, UInt uiPartIdx, UInt uiDepth );
    
    // 最优MVP索引
    SChar*        m_apiMVPIdx[NUM_REF_PIC_LIST_01];       ///< array of motion vector predictor candidates
    // 获取最优MVP索引
    SChar*        getMVPIdx( RefPicList eRefPicList ) { return m_apiMVPIdx[eRefPicList];           }
    // 设置/存储最优MVP索引
    Void          setMVPIdxSubParts( Int iMVPIdx, RefPicList eRefPicList, UInt uiAbsPartIdx, UInt uiPartIdx, UInt uiDepth );
     
    // 有效MVP候选数量
    SChar*        m_apiMVPNum[NUM_REF_PIC_LIST_01];       ///< array of number of possible motion vectors predictors
    // 获取有效MVP候选数量
    SChar*        getMVPNum( RefPicList eRefPicList ) { return m_apiMVPNum[eRefPicList];           }
    // 设置/存储有效MVP候选数量
     Void         setMVPNumSubParts( Int iMVPNum, RefPicList eRefPicList, UInt uiAbsPartIdx, UInt uiPartIdx, UInt uiDepth );
    

    TComCUMvField

    官方定义:class for motion information in one CU
    该Class用来表示一个CU内的MV信息。主要包括MV、MVD、参考帧索引、AMVP候选列表
    协作图:
    在这里插入图片描述
    该Class定义如下:

    class TComCUMvField
    {
    private:
      TComMv*   m_pcMv;  // MV
      TComMv*   m_pcMvd; // MVD
      SChar*    m_piRefIdx; // 参考帧索引
      UInt      m_uiNumPartition;
      AMVPInfo  m_cAMVPInfo; // AMVP候选列表
    
      template <typename T>
      Void setAll( T *p, T const & val, PartSize eCUMode, Int iPartAddr, UInt uiDepth, Int iPartIdx );
    
    public:
      TComCUMvField() : m_pcMv(NULL), m_pcMvd(NULL), m_piRefIdx(NULL), m_uiNumPartition(0) {}
      ~TComCUMvField() {}
    
      // ------------------------------------------------------------------------------------------------------------------
      // create / destroy
      // ------------------------------------------------------------------------------------------------------------------
    
      Void    create( UInt uiNumPartition );
      Void    destroy();
    
      // ------------------------------------------------------------------------------------------------------------------
      // clear / copy
      // ------------------------------------------------------------------------------------------------------------------
    
      Void    clearMvField();
    
      Void    copyFrom( TComCUMvField const * pcCUMvFieldSrc, Int iNumPartSrc, Int iPartAddrDst );
      Void    copyTo  ( TComCUMvField* pcCUMvFieldDst, Int iPartAddrDst ) const;
      Void    copyTo  ( TComCUMvField* pcCUMvFieldDst, Int iPartAddrDst, UInt uiOffset, UInt uiNumPart ) const;
    
      // ------------------------------------------------------------------------------------------------------------------
      // get
      // ------------------------------------------------------------------------------------------------------------------
    
      TComMv const & getMv    ( Int iIdx ) const { return  m_pcMv    [iIdx]; }   // 获取MV
      TComMv const & getMvd   ( Int iIdx ) const { return  m_pcMvd   [iIdx]; }   // 获取MVD
      Int            getRefIdx( Int iIdx ) const { return  m_piRefIdx[iIdx]; }   // 获取参考帧索引
    
      AMVPInfo* getAMVPInfo () { return &m_cAMVPInfo; }  // 和获取AMVP候选列表
    
      // ------------------------------------------------------------------------------------------------------------------
      // set
      // ------------------------------------------------------------------------------------------------------------------
    
      // 以下四个函数的设置方式都是将第一个值(如rcMv)赋给当前CU对应值(如m_pcMv)
      Void    setAllMv     ( TComMv const & rcMv,         PartSize eCUMode, Int iPartAddr, UInt uiDepth, Int iPartIdx=0 );  // 设置/存储MV
      Void    setAllMvd    ( TComMv const & rcMvd,        PartSize eCUMode, Int iPartAddr, UInt uiDepth, Int iPartIdx=0 );  // 设置/存储MVD
      Void    setAllRefIdx ( Int iRefIdx,                 PartSize eMbMode, Int iPartAddr, UInt uiDepth, Int iPartIdx=0 );  // 设置/存储参考帧索引
      Void    setAllMvField( TComMvField const & mvField, PartSize eMbMode, Int iPartAddr, UInt uiDepth, Int iPartIdx=0 );  // 同时设置/存储MV及参考帧索引
    
      Void setNumPartition( Int iNumPart )
      {
        m_uiNumPartition = iNumPart;
      }
    
      Void linkToWithOffset( TComCUMvField const * src, Int offset )
      {
        m_pcMv     = src->m_pcMv     + offset;
        m_pcMvd    = src->m_pcMvd    + offset;
        m_piRefIdx = src->m_piRefIdx + offset;
      }
    
    #if REDUCED_ENCODER_MEMORY
      Void compress(SChar *pePredMode, const SChar* pePredModeSource, const Int scale, const TComCUMvField &source);
    #else
      Void compress(SChar* pePredMode, Int scale);
    #endif
    };
    

    TComMvField

    官方定义:class for motion vector with reference index
    该Class表示带有参考帧索引信息的MV
    (疑问:为什么不直接用TComCUMvField的MV和参考帧索引呢?而是额外定义了这个Class?)

    协作图:
    在这里插入图片描述
    该Class定义如下:

    /// class for motion vector with reference index
    class TComMvField
    {
    private:
      TComMv    m_acMv;  // MV
      Int       m_iRefIdx;  // 参考帧索引
    
    public:
      TComMvField() : m_iRefIdx( NOT_VALID ) {}
    
      // 设置MV及其参考帧索引信息
      Void setMvField( TComMv const & cMv, Int iRefIdx )
      {
        m_acMv    = cMv;
        m_iRefIdx = iRefIdx;
      }
      // 单独设置参考帧索引
      Void setRefIdx( Int refIdx ) { m_iRefIdx = refIdx; }
      
      // 获取MV
      TComMv const & getMv() const { return  m_acMv; }
      TComMv       & getMv()       { return  m_acMv; }
    
      Int getRefIdx() const { return  m_iRefIdx;       }
      Int getHor   () const { return  m_acMv.getHor(); }
      Int getVer   () const { return  m_acMv.getVer(); }
    };
    

    TComMv

    官方定义:basic motion vector class
    即MV的Class,内部主要包括其水平和垂直分量

    主要内容:

    private:
      Short m_iHor;     ///< horizontal component of motion vector
      Short m_iVer;     ///< vertical component of motion vector
      
    public:
      // 设置水平/垂直变量
      Void  set       ( Short iHor, Short iVer)     { m_iHor = iHor;  m_iVer = iVer;            }
      Void  setHor    ( Short i )                   { m_iHor = i;                               }
      Void  setVer    ( Short i )                   { m_iVer = i;                               }
      
      // 获取水平/垂直变量
      Int   getHor    () const { return m_iHor;          }
      Int   getVer    () const { return m_iVer;          }
      Int   getAbsHor () const { return abs( m_iHor );   }
      Int   getAbsVer () const { return abs( m_iVer );   }
    

    AMVPInfo

    官方定义:parameters for AMVP
    该结构体表示AMVP候选列表,其内容包括三个:AMVP候选列表m_acMvCand、列表内有效候选数量iN、列表内空域候选数量numSpatialMVPCandidates

    typedef struct _AMVPInfo
    {
      TComMv m_acMvCand[ AMVP_MAX_NUM_CANDS ];  ///< array of motion vector predictor candidates
      Int    iN;                                ///< number of motion vector predictor candidates
    #if MCTS_ENC_CHECK
      UInt   numSpatialMVPCandidates;
    #endif
    } AMVPInfo;
    

    帧间预测入口函数

    帧间预测部分的入口函数是xCompressCU,其主要作用是完成块划分,确定最优预测模式。主要可以分为:
    1.帧间预测xCheckRDCostInter——Inter模式、xCheckRDCostMerge2Nx2N——Merge模式
    2.帧内预测xCheckRDCostIntra
    3.PCM模式xCheckIntraPCM

    xCompressCU的学习可参考博客:
    HEVC代码学习11:xCompressCU函数
    HM编码器代码阅读(12)——CU编码

    Inter模式(AMVP)

    入口函数——xCheckRDCostInter

    Inter模式(AMVP模式)的入口函数为xCheckRDCostInter,被xCompressCU调用。
    该函数主要流程如下:
    (1)调用predInterSearch,进行ME(运动估计)和MC(运动补偿)。
    (2)调用encodeResAndCalcRdInterCU,根据预测值计算残差,然后进行TU的划分、变换、量化等操作,并计算RD cost。
    (3)调用xCheckBestMode选择最好的模式。

    xCheckRDCostInter的学习可参考博客:
    HEVC代码学习12:xCheckRDCostInter函数
    HM编码器代码阅读(13)——帧间预测之AMVP模式(一)总体流程

    predInterSearch

    predInterSearch的作用是进行ME(运动估计)和MC(运动补偿)。
    predInterSearch的学习可参考博客:
    HEVC代码学习13:predInterSearch函数
    HM编码器代码阅读(14)——帧间预测之AMVP模式(二)predInterSearch函数

    该函数的主要流程如下:
    对当前CU下所有PU逐一进行以下操作(PU地址索引为ipartIdx):
    (1)遍历参考列表(iRefList)以及参考列表中所有参考帧(参考帧索引iRefIdxTemp),进行以下操作:

    • 调用xEstimateMvPredAMVP获取最优MVP,以及最优MVP在候选列表中的索引、MVP候选列表中候选数量。调用代码如下:
     // 获取最优MVP
     xEstimateMvPredAMVP( pcCU, pcOrgYuv, iPartIdx, eRefPicList, iRefIdxTemp, cMvPred[iRefList][iRefIdxTemp], false, &biPDistTemp);
     // 获取最优MVP索引
    aaiMvpIdx[iRefList][iRefIdxTemp] = pcCU->getMVPIdx(eRefPicList, uiPartAddr);
    // 获取MVP候选列表中候选数量
    aaiMvpNum[iRefList][iRefIdxTemp] = pcCU->getMVPNum(eRefPicList, uiPartAddr);
    

    xEstimateMvPredAMVP函数的详细情况将单独写一篇文章进行总结,文章链接如下:
    HEVC代码学习——帧间预测:预测MV获取(xEstimateMvPredAMVP、fillMVPCand)

    总之对于predInterSearch来说,上述三个语句执行后的返回结果为:

     //最优MV存入cMvPred[iRefList][iRefIdxTemp]
     cMvPred[iRefList][iRefIdxTemp] = cBestMv
     // 最优MVP索引存入CU中(执行xEstimateMvPredAMVP的结果),然后赋值给aaiMvpIdx[iRefList][iRefIdxTemp]
     aaiMvpIdx[iRefList][iRefIdxTemp] = pcCU->m_apiMVPIdx[eRefPicList][uiPartAddr]  
     // MVP候选数量存入CU中(执行xEstimateMvPredAMVP的结果),然后赋值给aaiMvpNum[iRefList][iRefIdxTemp] 
     aaiMvpNum[iRefList][iRefIdxTemp] = pcCU->m_apiMVPNum[eRefPicList][uiPartAddr]
    
    • 调用xMotionEstimation进行运动估计,以上面得到的最优MVP为起点进行搜索,最终得到最优MV以及其bits、cost。
      调用语句如下:
     xMotionEstimation ( pcCU, pcOrgYuv, iPartIdx, eRefPicList, &cMvPred[iRefList] [iRefIdxTemp], iRefIdxTemp, cMvTemp[iRefList][iRefIdxTemp], uiBitsTemp,  uiCostTemp );
    

    xMotionEstimation的详细情况将单独写一篇文章进行总结。文章链接如下:
    (待更)

    总之调用后的返回结果为:

      cMvTemp[iRefList][iRefIdxTemp] = 最优MV
      uiBitsTemp = 最优MV的bits
      uiCostTemp = 最优MV的cost
    
    • 调用xCheckBestMVP更新最优MVP。在得到最优MV后,重新寻找最优MVP,并于之前的最优MVP比较bits,从而更新最优MVP。
      (这里有个疑问,更新的时候为什么比较bit而不是cost?是为接下来编码做准备吗?而且更新MVP的目的是什么?更新之后的MVP还有什么用?)

    • 更新每一参考列表下最优(cost最小)MV,以及其对应的参考帧索引。
      代码如下:

     if ( iRefList == 0 )
     {
          uiCostTempL0[iRefIdxTemp] = uiCostTemp;
          uiBitsTempL0[iRefIdxTemp] = uiBitsTemp;
      }
      
      if ( uiCostTemp < uiCost[iRefList] )
      {
          uiCost[iRefList] = uiCostTemp;
          uiBits[iRefList] = uiBitsTemp; // storing for bi-prediction
    
          // set motion
          cMv[iRefList]     = cMvTemp[iRefList][iRefIdxTemp];
          iRefIdx[iRefList] = iRefIdxTemp;
       }
    
    if ( iRefList == 1 && uiCostTemp < costValidList1 && pcCU->getSlice()->getList1IdxToList0Idx( iRefIdxTemp ) < 0 )
     {
          costValidList1 = uiCostTemp;
          bitsValidList1 = uiBitsTemp;
    
          // set motion
          mvValidList1     = cMvTemp[iRefList][iRefIdxTemp];
          refIdxValidList1 = iRefIdxTemp;
     }
    

    因此在遍历完所有参考列表下的所有参考帧后,将得到该PU在所有参考列表[iRefList]下的最优MVcMv[iRefList]以及其对应的参考帧索引iRefIdx[iRefList]、bits、cost。

    (2)B帧处理

    (3)存储MV、MVD及其信息。

    // B帧
    if ( uiCostBi <= uiCost[0] && uiCostBi <= uiCost[1])
        {
          uiLastMode = 2;
          pcCU->getCUMvField(REF_PIC_LIST_0)->setAllMv( cMvBi[0], ePartSize, uiPartAddr, 0, iPartIdx );
          pcCU->getCUMvField(REF_PIC_LIST_0)->setAllRefIdx( iRefIdxBi[0], ePartSize, uiPartAddr, 0, iPartIdx );
          pcCU->getCUMvField(REF_PIC_LIST_1)->setAllMv( cMvBi[1], ePartSize, uiPartAddr, 0, iPartIdx );
          pcCU->getCUMvField(REF_PIC_LIST_1)->setAllRefIdx( iRefIdxBi[1], ePartSize, uiPartAddr, 0, iPartIdx );
    
          TempMv = cMvBi[0] - cMvPredBi[0][iRefIdxBi[0]];
          pcCU->getCUMvField(REF_PIC_LIST_0)->setAllMvd    ( TempMv,                 ePartSize, uiPartAddr, 0, iPartIdx );
    
          TempMv = cMvBi[1] - cMvPredBi[1][iRefIdxBi[1]];
          pcCU->getCUMvField(REF_PIC_LIST_1)->setAllMvd    ( TempMv,                 ePartSize, uiPartAddr, 0, iPartIdx );
    
          pcCU->setInterDirSubParts( 3, uiPartAddr, iPartIdx, pcCU->getDepth(0) );
    
          pcCU->setMVPIdxSubParts( aaiMvpIdxBi[0][iRefIdxBi[0]], REF_PIC_LIST_0, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
          pcCU->setMVPNumSubParts( aaiMvpNum[0][iRefIdxBi[0]], REF_PIC_LIST_0, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
          pcCU->setMVPIdxSubParts( aaiMvpIdxBi[1][iRefIdxBi[1]], REF_PIC_LIST_1, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
          pcCU->setMVPNumSubParts( aaiMvpNum[1][iRefIdxBi[1]], REF_PIC_LIST_1, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
    
          uiMEBits = uiBits[2];
        }
        //P帧(List0)
        else if ( uiCost[0] <= uiCost[1] )
        {
          uiLastMode = 0;
          pcCU->getCUMvField(REF_PIC_LIST_0)->setAllMv( cMv[0], ePartSize, uiPartAddr, 0, iPartIdx );
          pcCU->getCUMvField(REF_PIC_LIST_0)->setAllRefIdx( iRefIdx[0], ePartSize, uiPartAddr, 0, iPartIdx );
    
          TempMv = cMv[0] - cMvPred[0][iRefIdx[0]];
          pcCU->getCUMvField(REF_PIC_LIST_0)->setAllMvd    ( TempMv,                 ePartSize, uiPartAddr, 0, iPartIdx );
    
          pcCU->setInterDirSubParts( 1, uiPartAddr, iPartIdx, pcCU->getDepth(0) );
    
          pcCU->setMVPIdxSubParts( aaiMvpIdx[0][iRefIdx[0]], REF_PIC_LIST_0, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
          pcCU->setMVPNumSubParts( aaiMvpNum[0][iRefIdx[0]], REF_PIC_LIST_0, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
    
          uiMEBits = uiBits[0];
        }
        // P帧(List1)
        else
        {
          uiLastMode = 1;
          pcCU->getCUMvField(REF_PIC_LIST_1)->setAllMv( cMv[1], ePartSize, uiPartAddr, 0, iPartIdx );
          pcCU->getCUMvField(REF_PIC_LIST_1)->setAllRefIdx( iRefIdx[1], ePartSize, uiPartAddr, 0, iPartIdx );
    
          TempMv = cMv[1] - cMvPred[1][iRefIdx[1]];
          pcCU->getCUMvField(REF_PIC_LIST_1)->setAllMvd    ( TempMv,                 ePartSize, uiPartAddr, 0, iPartIdx );
    
          pcCU->setInterDirSubParts( 2, uiPartAddr, iPartIdx, pcCU->getDepth(0) );
    
          pcCU->setMVPIdxSubParts( aaiMvpIdx[1][iRefIdx[1]], REF_PIC_LIST_1, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
          pcCU->setMVPNumSubParts( aaiMvpNum[1][iRefIdx[1]], REF_PIC_LIST_1, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
    
          uiMEBits = uiBits[1];
        }
    

    以上代码简单来说即:

    MV存入:            pcCU->m_acCUMVField[eRefPicList]->m_pcMv 
    MV参考帧索引存入:    pcCU->m_acCUMVField[eRefPicList]->m_piRefIdx 
    MVD信息存入:        pcCU->m_acCUMVField[eRefPicList]->m_pcMvd 
    帧间预测方向信息存入: pcCU->m_puhInterDir 
    最优MVP索引信息存入: pcCU->m_apiMVPIdx[eRefPicList] 
    MVP候选数量信息存入: pcCU->m_apiMVPNum[eRefPicList]
    

    (4)对于非2Nx2N的分块,需要计算并合并他们的运动估计代价。

    (5)调用motionCompensation进行运动补偿

    Merge模式

    入口函数—xCheckRDCostMerge2Nx2N

    Merge模式与AMVP模式不同,其得到的MVP将直接作为MV(没有MVD),因此不需要再进行运动估计,直接进行运动补偿。

    执行流程如下:
    (1)调用getInterMergeCandidates获取MVP候选列表;
    (2)调用motionCompensation进行运动补偿;
    (3)调用encodeResAndCalcRdInterCU计算残差,变换量化,选出最优的QP参数;
    (4)调用xCheckBestMode,比较选出最优MV和最优模式信息

    xCheckRDCostMerge2Nx2N的学习可以参考博客:
    HM编码器代码阅读(17)——帧间预测之merge模式(一)Merge模式的介绍以及相关函数
    HEVC代码学习31:xCheckRDCostMerge2Nx2N函数

    getInterMergeCandidates

    该函数的作用是建立Merge模式下的MVP候选列表。
    在这里插入图片描述
    其处理流程如下:
    (1)先建立空域候选列表。空域最多只能提供4个候选MV,候选的遍历顺序是A1->B1->B0->A0->B2,优先处理前面四个,如果前面四个有不满足条件的时,才处理B2。在遍历每一个相邻PU时,都进行以下操作:

    • 检测是否有效
    • 若有效则写入候选列表记录其MV
    • 检测列表是否已满

    代码如下(以A1为例):

    if ( isAvailableA1 )  // 检测是否有效,后面的PU在检测时会考虑之前PU的情况,所以检测flag会更复杂
      {
        abCandIsInter[iCount] = true;
        // get Inter Dir 获取帧间预测方向
        puhInterDirNeighbours[iCount] = pcCULeft->getInterDir( uiLeftPartIdx );
        // get Mv from Left 获取List0的MV信息,该函数的原理详见下面
        TComDataCU::getMvField( pcCULeft, uiLeftPartIdx, REF_PIC_LIST_0, pcMvFieldNeighbours[iCount<<1] );
        if ( getSlice()->isInterB() ) // 如果是B帧,再获取List1的MV信息
        {
          TComDataCU::getMvField( pcCULeft, uiLeftPartIdx, REF_PIC_LIST_1, pcMvFieldNeighbours[(iCount<<1)+1] );
        }
        // 空间候选数量标志 numSpatialMergeCandidates + 1
        if ( mrgCandIdx == iCount )
        {
    #if MCTS_ENC_CHECK
          numSpatialMergeCandidates = iCount + 1;
    #endif
          return;
        }
        // 候选数量+1
        iCount ++;
      }
    
      // early termination 检测列表是否已满,如果已满则直接结束列表的建立
      if (iCount == getSlice()->getMaxNumMergeCand())
      {
    #if MCTS_ENC_CHECK
        numSpatialMergeCandidates = iCount;
    #endif
        return;
      }
    

    其中获取相邻PU的MV的函数是TComDataCU::getMvField,在上面关于TComDataCU的总结中已知,getMvFieldTComDataCU中定义的一个用于获取MV信息的函数,下面具体学习一下这个函数:

    Void TComDataCU::getMvField ( const TComDataCU* pcCU, UInt uiAbsPartIdx, RefPicList eRefPicList, TComMvField& rcMvField )
    {
      if ( pcCU == NULL )  // OUT OF BOUNDARY
      {
        TComMv  cZeroMv;
        rcMvField.setMvField( cZeroMv, NOT_VALID );
        return;
      }
    
      const TComCUMvField*  pcCUMvField = pcCU->getCUMvField( eRefPicList );
      rcMvField.setMvField( pcCUMvField->getMv( uiAbsPartIdx ), pcCUMvField->getRefIdx( uiAbsPartIdx ) );
    }
    

    setMvField的定义为:

      Void setMvField( TComMv const & cMv, Int iRefIdx )
      {
        m_acMv    = cMv;
        m_iRefIdx = iRefIdx;
      }
    

    所以getMvField的返回结果为:

    rcMvField.m_acMv = pcCU->m_acCUMvField[eRefPicList]->m_pcMv[uiAbsPartIdx];
    rcMvField.m_iRefIdx = pcCU->m_acCUMvField[eRefPicList]->m_piRefIdx[uiAbsPartIdx];
    

    即,将pcCU的MV和参考帧索引信息赋给rcMvField。具体到调用getMvField语句(以A1为例),就是将pcCULeft的List0的MV和参考帧索引信息赋值给pcMvFieldNeighbours[iCount<<1](偶数位),将pcCULeft的List1的MV和参考帧索引信息赋值给pcMvFieldNeighbours[(iCount<<1)+1](奇数位)。iCount表示当前选中的MVP在MVP候选列表中的位置。

        TComDataCU::getMvField( pcCULeft, uiLeftPartIdx, REF_PIC_LIST_0, pcMvFieldNeighbours[iCount<<1] );
        if ( getSlice()->isInterB() ) 
        {
          TComDataCU::getMvField( pcCULeft, uiLeftPartIdx, REF_PIC_LIST_1, pcMvFieldNeighbours[(iCount<<1)+1] );
        }
    

    (2)建立时域候选列表
    (3)为B Slice建立组合列表
    (4)候选列表未满时用0填充

    xCheckRDCostMerge2Nx2N调用getInterMergeCandidates的代码吗如下:

    #if MCTS_ENC_CHECK
      UInt numSpatialMergeCandidates = 0;
      rpcTempCU->getInterMergeCandidates( 0, 0, cMvFieldNeighbours, uhInterDirNeighbours, numValidMergeCand, numSpatialMergeCandidates );
    #else
      rpcTempCU->getInterMergeCandidates( 0, 0, cMvFieldNeighbours,uhInterDirNeighbours, numValidMergeCand );
    #endif
    

    因此从getInterMergeCandidates返回的结果有:

    cMvFieldNeighbours[] = MVP候选列表
    uhInterDirNeighbours[] = 列表中对应的帧间预测方向
    numValidMergeCand = 候选列表中有效候选数量
    numSpatialMergeCandidates = 候选列表中空域候选数量
    
    展开全文
  • 帧间预测Merge模式

    2020-11-14 17:05:10
    HEVC提出了一种名为“Merge”的帧间预测技术,在VVC中沿用了这一种帧间预测技术,Merge技术运用的是空域以及时域运动矢量预测的思想,利用当前编码块空时域上相邻已编码块的运动信息(运动矢量和参考帧信息)直接推导...

    HEVC提出了一种名为“Merge”的帧间预测技术,在VVC中沿用了这一种帧间预测技术,Merge技术运用的是空域以及时域运动矢量预测的思想,利用当前编码块空时域上相邻已编码块的运动信息(运动矢量和参考帧信息)直接推导得到当前块运动信息。在Merge模式下,会为当前块建立一个候选表,由周围已编码块运动信息组成,在解码端也使用同样的方法建立该候选列表,如此以来,编码器只需要传输最佳候选的索引到编码端即可,大大减少了编码过程的比特消耗。
    Merge模式建立的候选列表由空域候选和时域候选组成,建立候选列表包括了空域候选推导过程,空域候选冗余校验过程和时域候选候选推导过程,对于B帧还采用了组合列表的方式,具体介绍如下:
    (1)空域候选列表的建立。
    空域列表候选的位置如图,Ao, A1分别表示位于当前块左下方距离最近的以及左侧最下方的已编码块,Bo, B1分别表示位于当前块右上方距离最近的以及上方最右侧的编码块,B2则表示当前块左上方距离最近的编码块。空域最多为当前块提供四个候选运动矢量,也就是说需要在上述的五个候选块中选出四个添加进Merge候选列表中,添加进列表的顺序A1—B1—Bo—Ao—(B2),当前四个候选均不可用时,才采用B2的运动信息。
    在这里插入图片描述
    如果当前编码块采用矩形划分方式进行划分,其候选列表的建立需要做特殊处理,如图。
    在这里插入图片描述
    当CU划分方式为NXN, NX2N, nLX2N或nR X 2N的时候 ,A1的运动信息不能出现在PU:的候选列表中。这是因为一旦PU2使用了A1的运动信息(比如PUI的运动矢量MV),那么PU2与PUI的MV将会相同,与2N X 2N的划分方式没有区别。
    同样的道理,如下图,当划分方式为2NXN, 2NXnU或2NXnD的时候,A1的运动信息不能出现在PU2的候选列表中。
    在这里插入图片描述
    (2)时域候选列表的建立。
    时域候选来自于当前PU在时域上邻近已编码图像中对应位置的PU(同位PU),如下图所示。与空域候选列表不同的是,时域候选列表不能直接利用对应的候选块的运动信息,需要根据相对位置和距离做一定的比例伸缩调整。此外,在时域上,只提供一个候选,由图中位置H的同位PU进行MV伸缩而得,如果H位置的候选存在不可用的情况(比如帧内编码或者是在当前CTU行之外),则采用位置C的同位PU。
    在这里插入图片描述
    根据同位PU 推导时域候选MV 的具体步骤如图3-5 所示,cur_PU 表示当前PU,col_PU 表示他的同位PU, col_pic、cur_pic、 col_ref、cur_ref 分别表示同位图像、当前图像、同位图像参考图像、当前图像参考图像, td 和tb 分别表示当前图像与它的参考图像、同位图像与它的参考图像之间的距离,colMV 表示同位PU的MV,curMV 表示当前PU 的时域候选MV,则有如下等式成立:
    在这里插入图片描述
    此外,Slice 头中定义了候选列表的长度L,当处理完空域和时域的候选MV之后,如果列表中候选MV 的数量不足L 个,则使用零向量(0,0)进行填充直到达到规定的数量。
    (3)建立组合列表
    B 帧的帧间预测比较特殊,因为会进行双向预测,所以存在两个MV,候选列表也需要提供两个预测MV,HEVC 标准将MV 候选列表(由步骤1 和步骤2 建立的)中的前4 个候选MV 两两组合,产生B 帧的组合列表。组合顺序依次为(0,1)、
    (1,0)、(0,2)、(2,0)、(1,2)、(2,1)、(0,3)、(3,0)、(1,3)、(3,1)、(2,3)、(3,2),0、1、2、3 分别表示候选列表中候选MV 的序号,这12 个组合分别对应候选对序号0~11。
    在这里插入图片描述

    展开全文
  • HEVC中的帧间预测

    2020-04-22 19:47:49
    帧间预测的基本原理:利用相邻的已经编码的图像为当前编码块寻找最佳匹配块,把这个最佳匹配块作为当前块的预测值,然后将预测值和当前块的原始像素值相减,得到当前块的残差值,后续的变换、量化等操作都是基于残差...
  • 【H2645】帧间预测

    2020-07-30 23:33:50
    1、帧间预测原理 先看下图,对比前后两帧图像,只有圆的位置发生变化,因此我们可以根据前一...在H.264中将这一过程叫做运动估计,它是将16x16的亮度宏块,分成16x16、16x8、8x16和8x8的子块进行帧间预测。对于8x8的块
  • HEVC帧间预测原理

    2020-05-11 20:19:58
    一、帧间预测基本原理 主要原理是为当前图像的每个像素块在之前已编码图像中寻找一个最佳匹配块,该过程称为运动估计( Motion Estimation,ME)。其中用于预测的图像称为参考图(Reference Frame),参考块到当前像素块...
  • H.264/AVC 帧间预测

    2019-08-20 14:28:20
    在早前介绍MPEG-2的时候,就介绍过帧间预测,它是从过去编码后重构的相邻帧(参考帧)的样本,预测当前帧(待编码帧)样本的过程。这一过程分为三个步骤: (1)在参考帧中,找出与待编码帧图块的最佳匹配块,所谓...
  • 声明:本文大部分内容摘取至《基于H.264的视频编码处理技术与应用》(科学出版社,贾克斌、刘鹏宇、吕卓逸、邓智玭编著)一书。如有侵犯版权问题,请通知,本人立即删除。 ...帧间预测过程中,最
  • 针对监控视频多噪点以及存在大量静止背景的特点,以AVS视频编码标准为基础,提出了一种结合降采样视频预处理技术和及早停止准则的快速帧间预测模式选择算法。实验结果表明,该算法在保持视频主观质量相当的条件下,...
  • 一、帧间预测 利用相邻图像中已经编码的像素生成预测值,生成预测值之后,和原始的值相减,得到残差,后续的变换量化等操作就是基于残差进行处理的。 主要原理:为当前的PU在参考帧上寻找一个最佳的匹配块,这个找到...
  • 函数中有一个bTestNormalMC变量,它表示是否进行正常的MC过程,正常的MC过程就是进行ME再进行MC。 正常的MC流程是,遍历所有的参考,进行ME(运动估计:xEstimateMvPredAMVP),然后记录AVP或者MV的信息,进行MC...
  • 帧间预测编码模式下,每个分块都有一个或两个mv需要编码。当采用小尺寸编码时,一个宏块内需要编码的mv也越多,所需比特额越多。同时,mv有两个值并且以1/4像素为单位,这也就意味着mv的数值还不小,因此有必要对...
  • HEVC/H.265理论知识(4)——帧间预测

    千次阅读 2016-11-22 17:18:23
    一、帧间预测,利用相邻图像中已经编码的像素生成预测值 二、生成预测值之后,和原始的值相减,得到残差,后续的变换量化等操作就是基于残差进行处理的 三、主要原理是,为当前的PU在参考帧上寻找一个最佳...
  • 一、帧间预测,利用相邻图像中已经编码的像素生成预测值 二、生成预测值之后,和原始的值相减,得到残差,后续的变换量化等操作就是基于残差进行处理的 三、主要原理是,为当前的PU在参考帧上寻找一个最佳的匹配块...
  • 在滤波过程中,边界强度(Bs)的计算量是最大的,几乎达到整个滤波过程的90%,为了简化Bs的计算复杂度,在分析了H.264的去块滤波原理之后,提出了一种基于片类型和帧间预测的H.264去块滤波优化算法。通过实验表明,...
  • H.264/AVC标准编码过程中多步搜索(multi-step search method, MSSM)搜索点数多、占用的搜索资源大,而钻石搜索法(DSM, diamond search method)虽然占用的搜索资源少,其在预测准确性却有些欠缺. 在此提出了改进...
  • 然后根据帧类型采用帧内预测或者帧间预测获得当前X的预测块Xp。差分编码的到预测误差图像△X。误差图像进行8x8或者4x4块级DCT变换,量化得到量化系数,经游程编码,可变长 编码,熵编码产生量化系数对应的的编码比特...
  • 由于用于HEVC的编码过程非常复杂,因此用于SHVC的编码过程甚至更加复杂,因此提高其编码速度非常重要。 在本文中,我们提出了一种用于质量SHVC预测的快速模式和深度决策算法。 最初,仅检查部分模式以根据模式...
  • ...我需要宏观的关于视频编码的指导,最好...如帧间预测编码过程,帧内预测编码过程,熵编码过程。细节可不详细,但求有宏观的流程概念,不胜感激! 关注者 707 被浏览 14507
  • 一、H.265(HEVC)编码过程 和H.264一样,H.265编码由帧内预测、帧间预测、量化、...其它帧中的块大多数使用帧间预测编码,过程包括选择预测模式、参考图像的运动数据和生成每个块的运动矢量(MV)。 编码器通过旁路传
  • 视频压缩原理之 预测编码

    千次阅读 2015-12-25 09:59:32
    预测法是最简单和实用的视频压缩方法,压缩...预测法大致可分为帧内预测和帧间预测。下面将依次介绍两种预测法,还会提到重叠块运动补偿,最后再细说一下运动估计。     帧内预测:  帧内预测的具体方法是根据
  • 视频压缩原理之-预测编码

    千次阅读 2018-05-17 14:54:55
    预测法的流程图如下:(在量化过程中,会产生量化误差) 预测法大致可分为帧内预测和帧间预测。下面将依次介绍两种预测法,还会提到重叠块运动补偿,最后再细说一下运动估计。 帧内预测: 帧内预测的具体方法是...
  • 编码过程中,信号的线性依赖可以通过线性预测和线性变换来消除。但是由于预测往往不够完美,导致一些依赖信息不能完全消除,结果在预测后的残差中依然存在方向信息,传统的线性变换很难去除这些方向信息...
  • 然后,依据当前CU与其时空域相邻CU及上一深度CU对应的预测单元(Prediction Unit,PU)在空间划分上的相似性,减少PU模式的遍历范围,加速帧间预测过程。实验结果表明,相比于HM16.9,在不同编码接入方式下该算法可...
  • 一种基于内容的快速帧间模式选择算法,王原丽,江根,在图像编码过程中,H.264中定义的7种不同大小的帧间宏块模式,采用多模式运动估计,可以有效减少块匹配预测误差,但随着模式的�
  • 针对高效视频编码(HEVC)帧间预测过程所引入较高的复杂度,分别提出提前决策skip模式,编码单元(CU)提前终止分割以及变换单元(TU)提前终止分割。首先,根据自然视频序列多采用skip模式,利用当前块和空间相邻块...
  • IBP是什么?

    千次阅读 2016-09-19 10:05:09
    在了解I帧B帧P帧之前,先聊聊什么是编码过程中的帧内预测和帧间预测1. 帧内预测编码帧内编码用来缩减图像的空间冗余。为了提高H.264帧内编码的效率,在给定帧中充分利用相邻宏块的空间相关性,相邻的宏块通常含有...

空空如也

空空如也

1 2 3 4
收藏数 69
精华内容 27
关键字:

帧间预测编码过程