HEVC代码学习——帧间预测:预测MV获取(xEstimateMvPredAMVP、fillMVPCand)_hevc amvp-程序员宅基地

技术标签: hevc  HEVC  

HEVC帧间预测在AMVP模式下是依靠xEstimateMvPredAMVP函数获取预测MV(MVP)的。

这部分内容的学习还可以参考这两篇博客:
HEVC代码学习15:AMVP相关函数
HM编码器代码阅读(16)——帧间预测之AMVP模式(四)预测MV的获取

xEstimateMvPredAMVP

xEstimateMvPredAMVP的作用是建立MVP列表并获取最优MVP,最终将最优MVP以及其索引等信息返回给上层函数——preInterSearch

xEstimateMvPredAMVP的基本流程如下:
(1)判断bFilled标识,该标识表示MVP候选列表是否已经建好;
(2)如果bFilled是false,通过fillMvpCand获取MVP候选列表;否则,不需要重新建立MVP候选列表
(3)先把MVP候选列表中的第一个MVP作为最优的MVP
(4)如果候选列表中MVP的数量小于等于1,那么直接把步骤3选出的MVP返回
(5)如果bFilled是true,表示MVP候选列表是原来已经建立好的,那么直接根据PU的相关信息得到最优的MVP,然后返回
(6)遍历MVP候选列表,选出代价最小的MVP作为当前PU的MVP,并设置相关信息,然后返回

xEstimateMvPredAMVP代码如下:

Void TEncSearch::xEstimateMvPredAMVP( TComDataCU* pcCU, TComYuv* pcOrgYuv, UInt uiPartIdx, RefPicList eRefPicList, Int iRefIdx, TComMv& rcMvPred, Bool bFilled, Distortion* puiDistBiP )
{
    
  AMVPInfo*  pcAMVPInfo = pcCU->getCUMvField(eRefPicList)->getAMVPInfo();

  TComMv     cBestMv;  //最优MV
  Int        iBestIdx   = 0; //最优MV在候选中的索引
  TComMv     cZeroMv;
  TComMv     cMvPred;  
  Distortion uiBestCost = std::numeric_limits<Distortion>::max();
  UInt       uiPartAddr = 0; // PU地址
  Int        iRoiWidth, iRoiHeight; // PU长宽
  Int        i;
  Int        minMVPCand;
  Int        maxMVPCand;

  // 获取当前PU的索引和大小
  pcCU->getPartIndexAndSize( uiPartIdx, uiPartAddr, iRoiWidth, iRoiHeight );
  // Fill the MV Candidates
  if (!bFilled)
  {
    
    pcCU->fillMvpCand( uiPartIdx, uiPartAddr, eRefPicList, iRefIdx, pcAMVPInfo ); // 建立MVP候选列表
  }
  // initialize Mvp index & Mvp
#if MCTS_ENC_CHECK
  if (m_pcEncCfg->getTMCTSSEITileConstraint() && pcCU->isLastColumnCTUInTile() && (pcAMVPInfo->numSpatialMVPCandidates < pcAMVPInfo->iN))
  {
    
    iBestIdx    = (pcAMVPInfo->numSpatialMVPCandidates == 0) ? 1 : 0;
    cBestMv     = pcAMVPInfo->m_acMvCand[(pcAMVPInfo->numSpatialMVPCandidates == 0) ? 1 : 0];
    minMVPCand  = (pcAMVPInfo->numSpatialMVPCandidates == 0) ? 1 : 0;
    maxMVPCand  = (pcAMVPInfo->numSpatialMVPCandidates == 0) ? pcAMVPInfo->iN : 1;
  }
  else
  {
    
    iBestIdx = 0;
    cBestMv  = pcAMVPInfo->m_acMvCand[0];
    minMVPCand  = 0;
    maxMVPCand  = pcAMVPInfo->iN;
  }
#else
  iBestIdx = 0;
  cBestMv  = pcAMVPInfo->m_acMvCand[0];
  minMVPCand  = 0;
  maxMVPCand  = pcAMVPInfo->iN;
#endif
  if (pcAMVPInfo->iN <= 1)
  {
    
    rcMvPred = cBestMv;

    pcCU->setMVPIdxSubParts( iBestIdx, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));
    pcCU->setMVPNumSubParts( pcAMVPInfo->iN, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));

    if(pcCU->getSlice()->getMvdL1ZeroFlag() && eRefPicList==REF_PIC_LIST_1)
    {
    
      (*puiDistBiP) = xGetTemplateCost( pcCU, uiPartAddr, pcOrgYuv, &m_cYuvPredTemp, rcMvPred, 0, AMVP_MAX_NUM_CANDS, eRefPicList, iRefIdx, iRoiWidth, iRoiHeight);
    }
    return;
  }

  // 上述步骤(5)
  if (bFilled)
  {
    
    assert(pcCU->getMVPIdx(eRefPicList,uiPartAddr) >= 0);
    rcMvPred = pcAMVPInfo->m_acMvCand[pcCU->getMVPIdx(eRefPicList,uiPartAddr)];
    return;
  }

  m_cYuvPredTemp.clear();
  //-- Check Minimum Cost.
  // 比较得出最优MV
  for ( i = minMVPCand ; i < maxMVPCand; i++)
  {
    
    Distortion uiTmpCost;
    uiTmpCost = xGetTemplateCost( pcCU, uiPartAddr, pcOrgYuv, &m_cYuvPredTemp, pcAMVPInfo->m_acMvCand[i], i, AMVP_MAX_NUM_CANDS, eRefPicList, iRefIdx, iRoiWidth, iRoiHeight);
    if ( uiBestCost > uiTmpCost )
    {
    
      uiBestCost = uiTmpCost;
      cBestMv   = pcAMVPInfo->m_acMvCand[i];
      iBestIdx  = i;
      (*puiDistBiP) = uiTmpCost;
    }
  }

  m_cYuvPredTemp.clear();

  // Setting Best MVP
  // 设置最优MVP的信息,将获取的最优MVP信息返回给preInterSearch
  rcMvPred = cBestMv;
  pcCU->setMVPIdxSubParts( iBestIdx, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));
  pcCU->setMVPNumSubParts( pcAMVPInfo->iN, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));
  return;
}

而在上层函数preInterSearch中,则通过以下语句调用xEstimateMvPredAMVP并获取从其中返回的上述三个值:cBestMv(最优MV)、iBestIdx(最优MV索引)、pcAMVPInfo->iN(AMVP候选列表中有效候选数量)

xEstimateMvPredAMVP( pcCU, pcOrgYuv, iPartIdx, eRefPicList, iRefIdxTemp, cMvPred[iRefList][iRefIdxTemp], false, &biPDistTemp);
aaiMvpIdx[iRefList][iRefIdxTemp] = pcCU->getMVPIdx(eRefPicList, uiPartAddr);
aaiMvpNum[iRefList][iRefIdxTemp] = pcCU->getMVPNum(eRefPicList, uiPartAddr);

通过调用上述语句,实则将上述三个值分别赋给以下三个值,以供后续使用:

 //最优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]

AMVPInfo

另需要特别注意的是,在HM中,AMVP候选列表是用一个AMVPInfo类的结构体进行存储和传递的

typedef struct _AMVPInfo
{
    
  // MVP候选列表
  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;

这个存储MVP候选列表信息的结构体,在xEstimateMvPredAMVP中名为pcAMVPInfo,在fillMVPCand中名为pInfo

fillMVPCand

下面学习fillMVPCand函数,该函数用于建立MVP候选列表。
该函数学习可参考博客:
HEVC代码学习30:fillMvpCand函数

在这里插入图片描述
由于在视频中经常出现一大片区域(比如同一物体)运动方向一样的情况,因此运动向量MV不论在空域上还是时域上都有着很强的相关性,因此可以用当前PU在空域、时域上相邻的块的MV近似代替当前PU的MV以实现运动矢量预测(MVP)。

AMVP技术及将理论上与当前PU的MV最可能相近的空域相邻块、时域相邻块的若干MV收录进MVP候选列表中,以供之后进行选择。而这个建立MVP候选列表并收录MVP的过程就是在fillMVPCand函数中实现的。

建立顺序如下:
一、建立空域候选列表:
1、按顺序搜索左侧块A0-A1-scaled A0-scaled A1,只要有一个MV存在,写入候选列表,跳出进行下一步。
2、按顺序搜索上方块B0-B1-B2(-scaled B0-scaled B1-scaled B2),只要有一个MV存在,写入候选列表,跳出进行下一步。而其中scaled B0-scaled B1-scaled B2只有当A0和A1的MV都不存在时,才进行搜索。
3、空域候选列表长度为2时,即左侧和上方各选出了一个候选,则对两者进行比较,相同时进行合并。
4、空域候选列表建立完成。
二、当空域候选数量少于2个,且启用时域候选时,建立时域候选列表。
三、如果空域+时域候选个数少于2,则使用(0,0)补足。

fillMVPCand代码如下:

Void TComDataCU::fillMvpCand ( const UInt partIdx, const UInt partAddr, const RefPicList eRefPicList, const Int refIdx, AMVPInfo* pInfo ) const
{
    
  pInfo->iN = 0; // 候选数量先置零
  if (refIdx < 0)
  {
    
#if MCTS_ENC_CHECK
    pInfo->numSpatialMVPCandidates = 0; // 空域候选数量先置零
#endif
    return;
  }

  //-- Get Spatial MV
  // 获取相邻块地址
  UInt partIdxLT, partIdxRT, partIdxLB;
  deriveLeftRightTopIdx( partIdx, partIdxLT, partIdxRT );
  deriveLeftBottomIdx( partIdx, partIdxLB );

  Bool isScaledFlagLX = false; /// variable name from specification; true when the PUs below left or left are available (availableA0 || availableA1).
  {
    
    UInt idx;
    const TComDataCU* tmpCU = getPUBelowLeft(idx, partIdxLB); // 获取左下角CU
    isScaledFlagLX = (tmpCU != NULL) && (tmpCU->isInter(idx));
    if (!isScaledFlagLX)
    {
    
      tmpCU = getPULeft(idx, partIdxLB);
      isScaledFlagLX = (tmpCU != NULL) && (tmpCU->isInter(idx));
    }
  }

  // Left predictor search
  // 获取左侧块MV
  if (isScaledFlagLX)
  {
    
    Bool bAdded = xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxLB, MD_BELOW_LEFT); // 获取A0的MV
    if (!bAdded)
    {
    
      bAdded = xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxLB, MD_LEFT ); // 获取A1的MV
      if(!bAdded)
      {
    
        bAdded = xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxLB, MD_BELOW_LEFT); // 获取scaled A0的MV
        if (!bAdded)
        {
    
          xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxLB, MD_LEFT ); // 获取scaled A1的MV
        }
      }
    }
  }

  // Above predictor search
  {
    
    Bool bAdded = xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxRT, MD_ABOVE_RIGHT);
    if (!bAdded)
    {
    
      bAdded = xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxRT, MD_ABOVE);
      if(!bAdded)
      {
    
        xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxLT, MD_ABOVE_LEFT);
      }
    }
  }

  if(!isScaledFlagLX)
  {
    
    Bool bAdded = xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxRT, MD_ABOVE_RIGHT);
    if (!bAdded)
    {
    
      bAdded = xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxRT, MD_ABOVE);
      if(!bAdded)
      {
    
        xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxLT, MD_ABOVE_LEFT);
      }
    }
  }

  if ( pInfo->iN == 2 )
  {
    
    if ( pInfo->m_acMvCand[ 0 ] == pInfo->m_acMvCand[ 1 ] )
    {
    
      pInfo->iN = 1;
    }
  }
#if MCTS_ENC_CHECK
  pInfo->numSpatialMVPCandidates = pInfo->iN;
#endif
  // 获取时域MVP
  if (pInfo->iN < AMVP_MAX_NUM_CANDS && getSlice()->getEnableTMVPFlag() )
  {
    
    // Get Temporal Motion Predictor
    const UInt numPartInCtuWidth  = m_pcPic->getNumPartInCtuWidth();
    const UInt numPartInCtuHeight = m_pcPic->getNumPartInCtuHeight();
    const Int refIdx_Col = refIdx;
    TComMv cColMv;
    UInt partIdxRB;
    UInt absPartIdx;

    deriveRightBottomIdx( partIdx, partIdxRB );
    UInt absPartAddr = m_absZIdxInCtu + partAddr;

    //----  co-located RightBottom Temporal Predictor (H) ---//
    absPartIdx = g_auiZscanToRaster[partIdxRB];
    Int ctuRsAddr = -1;
    if (  ( ( m_pcPic->getCtu(m_ctuRsAddr)->getCUPelX() + g_auiRasterToPelX[absPartIdx] + m_pcPic->getMinCUWidth () ) < m_pcSlice->getSPS()->getPicWidthInLumaSamples () )  // image boundary check
       && ( ( m_pcPic->getCtu(m_ctuRsAddr)->getCUPelY() + g_auiRasterToPelY[absPartIdx] + m_pcPic->getMinCUHeight() ) < m_pcSlice->getSPS()->getPicHeightInLumaSamples() ) )
    {
    
      if ( ( absPartIdx % numPartInCtuWidth < numPartInCtuWidth - 1 ) &&  // is not at the last column of CTU
           ( absPartIdx / numPartInCtuWidth < numPartInCtuHeight - 1 ) )  // is not at the last row    of CTU
      {
    
        absPartAddr = g_auiRasterToZscan[ absPartIdx + numPartInCtuWidth + 1 ];
        ctuRsAddr = getCtuRsAddr();
      }
      else if ( absPartIdx % numPartInCtuWidth < numPartInCtuWidth - 1 )  // is not at the last column of CTU But is last row of CTU
      {
    
        absPartAddr = g_auiRasterToZscan[ (absPartIdx + numPartInCtuWidth + 1) % m_pcPic->getNumPartitionsInCtu() ];
      }
      else if ( absPartIdx / numPartInCtuWidth < numPartInCtuHeight - 1 ) // is not at the last row of CTU But is last column of CTU
      {
    
        absPartAddr = g_auiRasterToZscan[ absPartIdx + 1 ];
        ctuRsAddr = getCtuRsAddr() + 1;
      }
      else //is the right bottom corner of CTU
      {
    
        absPartAddr = 0;
      }
    }
    if ( ctuRsAddr >= 0 && xGetColMVP( eRefPicList, ctuRsAddr, absPartAddr, cColMv, refIdx_Col ) )
    {
    
      pInfo->m_acMvCand[pInfo->iN++] = cColMv;
    }
    else
    {
    
      UInt uiPartIdxCenter;
      xDeriveCenterIdx( partIdx, uiPartIdxCenter );
      if (xGetColMVP( eRefPicList, getCtuRsAddr(), uiPartIdxCenter,  cColMv, refIdx_Col ))
      {
    
        pInfo->m_acMvCand[pInfo->iN++] = cColMv;
      }
    }
    //----  co-located RightBottom Temporal Predictor  ---//
  }
  // 若候选列表不满,补零
  while (pInfo->iN < AMVP_MAX_NUM_CANDS)
  {
    
    pInfo->m_acMvCand[pInfo->iN].set(0,0);
    pInfo->iN++;
  }
  return ;
}

可见,最终构建的MVP候选列表存入了上述的名为pInfoAMVPInfo结构体中,并返回给xEstimateMvPredAMVPpcAMVPInfo

另外,从相邻块获取MV的函数分别为xAddMVPCandUnscaled以及xAddMVPCandWithScaling。前者为获取A0~B2 MV的函数,后者为获取scaled A0~B2的函数。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zzz_zzz12138/article/details/107347096

智能推荐

STM32开发项目:使用单个定时器驱动多路模拟PWM输出_一个定时器输出多路pwm-程序员宅基地

文章浏览阅读4k次,点赞11次,收藏35次。目录背景虚拟PWM库特性源码介绍头文件 virtual_pwm.h源文件 vir_pwm.c使用说明背景现在的主流MCU都支持硬件PWM输出,以STM32F103为例,通用定时器可以支持4路占空比可调的PWM输出,高级定时器可以支持4路带互补输出的PWM输出。硬件产生PWM,具有稳定可靠、执行效率高的特点。但是,硬件产生的PWM也有一些限制,例如:1.输出引脚位置固定,PCB连线可能会不方便;2.输出引脚的数量有限,在一些需要多通道输出的应用中(如多路控温)会占用过多定时器。虚拟PWM库特性由于项_一个定时器输出多路pwm

springboot内置tomcat和外部tomcat部署总结_内嵌tomcat和外部tomcat-程序员宅基地

文章浏览阅读4.9k次。目录一.使用内置tomcat启动二.使用外置tomcat启动三.Tomcat顶层结构图一.使用内置tomcat启动我们知道 springboot项目内置了 tomcat 服务器,表现在pom.xml中<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></depend_内嵌tomcat和外部tomcat

python高手之路 pdf百度云,python高手到什么水平-程序员宅基地

文章浏览阅读567次,点赞9次,收藏18次。authors : '【美】丹尼尔·戈尔曼\\n【美】理查德·博亚特兹\\n【美】安妮·麦基'authors : '【美】乔治·阿克洛夫\\n【美】罗伯特·席勒'authors : '【英】蒂姆·维卡利,萨菲娜·德福奇 等'title : '大谋小计五十年:诸葛亮传(全五册)',title : '周浩晖推理悬疑经典集(共10册)',authors : '【英】吉尔伯特·基思·切斯特顿'title : '春秋战国真有趣(全六册)',authors : '【美】马里奥·普佐'

当前顶点状态和顶点数组——OpenGL ES Common/Common-Lite 规范(版本 1.1.12)_查看当前顶点在哪个顶点组-程序员宅基地

文章浏览阅读1.4k次。当前顶点状态当一个顶点数组被定义,但是数据还不可用时,当前值常用于联合顶点辅助的数据。当前值可能在任何时候被独占命令改变。使用以下的命令可设置当前的RGBA颜色值。void Color4{xf}(T red, T green, T blue, T alpha);void Color4ub(ubyte red, ubyte green, ubyte blue, uby_查看当前顶点在哪个顶点组

用JAVAFX做一个简单的桌面宠物(四)-程序员宅基地

文章浏览阅读603次。(完结篇)实现自动行走的功能(Move类)类成员与构造函数private long time;private ImageView imageView;private int direID; double x; double maxx; double width; Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds(); Stage stage;private EventListener lis

Anaconda详细安装及环境变量配置(图文)_anaconda环境变量-程序员宅基地

文章浏览阅读3.9w次,点赞39次,收藏182次。AnacondaAnaconda指的是一个开源的Python发行版本,其包含了conda、Python等180多个科学包及其依赖项。因为包含了大量的科学包,Anaconda的下载文件比较大(约531 MB),如果只需要某些包,或者需要节省带宽或存储空间,也可以使用Miniconda这个较小的发行版(仅包含conda和Python)。Conda是一个开源的包、环境管理器,可以用于在同一个机器上安装不同版本的软件包及其依赖,并能够在不同的环境之间切换。Anaconda包括Conda、Pyth..._anaconda环境变量

随便推点

学习笔记(1.1)区块链_metis 区块链-程序员宅基地

文章浏览阅读408次。 区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式,可以用于登记与发行数字化资产、产权凭证等,如比特币。实质上,区块链作为比特币的底层技术和基础架构,可以理解为为了保证虚拟交易时数据的全用户可知并认同、不可篡改性、记录的合理修改性等等问题而存在的一个储存加密货币的交易记录的公共帐本。 其中分布式数据存储即为,将数据分散存储到多个可进行数据..._metis 区块链

pandas 时间戳转时间保留北京时间日期(to_datetime )_pandas to_datetime 北京时间-程序员宅基地

文章浏览阅读8.8k次,点赞4次,收藏8次。``` user_id create_time0 38441 15410016021 38442 15410016642 38443 15410017443 38444 15410019264 38445 15410020125 38446 15410024136 38447 15410..._pandas to_datetime 北京时间

mysql 8.0 创建新的数据库、用户并授权,以及相关查看并删除操作_mysql8.0 添加创建数据库权限-程序员宅基地

文章浏览阅读1.6k次。一、创建数据库mysql> create database news character set utf8;Query OK, 0 rows affected (0.09 sec)二、创建用户mysql> create user ‘news’@‘39.15.16.14’ identified by ‘123news’;Query OK, 0 rows affected (0.09 sec)三、授权用户mysql> grant all privileges on news.* _mysql8.0 添加创建数据库权限

SonarQube学习笔记二:Sonar插件安装和API调用示例_sonar-pdf-plugin-程序员宅基地

文章浏览阅读1.9k次。本文主要内容是sonarqube安装插件实现功能扩展,也对sonarqube的API功能进行了可用性确认。_sonar-pdf-plugin

vue使用fullCalendar插件(类似日程表)_vue 行程插件-程序员宅基地

文章浏览阅读6.4w次,点赞6次,收藏44次。基本操作传送门显示效果图添加效果图代码:<template> <div style="width:90%;margin: 20px auto;"> <!-- ref 用来拿到这个日历对象 defaultView 默认以月份方式显示 locale 语言 header 头部的按钮 buttonText 头部按钮的文字 plugins 插件 wee......_vue 行程插件

关于layout_centerHorizontal、layout_gravity、gravity的区别-程序员宅基地

文章浏览阅读1.2w次,点赞5次,收藏8次。layout_centerHorizontal是相对于RelativeLayout的布局属性如果设置为true,就将该控价设置在相对于父控件水平居中的位置layout_gravity针对LinearLayout的一种控件对齐方式,可以把值设置成下列值:center_vertical、center_horizontal、center等等gravity控制控件内文字的对齐方式举个栗子:在写一个简单的