HM 复现论文 ——《Fast Intra Mode Decision for High Efficiency Video Coding》

在大致了解了 HEVC 编码标准后,这段时间我也开始尝试在 HM-16.22 中复现一些算法。这篇博客主要用于记录进一个月来的工作情况。

复现的论文标题为 Fast Intra Mode Decision for High Efficiency Video Coding,作者为 Hao Zhang 和 Zhan Ma。论文下载链接: 这个

一、论文解读

论文中提出的三个加速算法

1 逐步进行的模糊搜索

这是一种由大步长开始,渐渐缩小

首先需要引入一个定义:模式距离。除了 DC (模式 0)和 PLANAR (模式1)外,模式 i i i 和模式 j j j 的模式距离 d d d 定义如下:
∣ i − j ∣ = d 2 ≤ i , j ≤ 34 |i-j| = d \quad 2\le i,j\le 34 ij=d2i,j34

例如,与模式 18 距离为 3 的模式为 15 和 21;与 模式 34 相距为 1 的模式为 33;与模式 3 相距为 2 的模式为 5(模式 1 被排除在外)

在了解了这个定义之后,该算法的流程如下:

  1. 先对 0 , 1 , 2 + 4 δ , δ ∈ [ 0 , 8 ] 0,1,2+4\delta,\delta\in[0,8] 0,1,2+4δ,δ[0,8] 共计 11 种模式做 HT,并按照 HT 代价 (HCost) 升序排序。
  2. 从中寻找 HCost最小的 6 种模式,并将与其模式距离为 2 且还没测试过的模式加入到待测试集合中。如果当前 CU 的上方和左侧 CU 的最佳模式还没有测试过,那么也把他们加入到待测试集合中。
  3. 对待测试集合中的模式做 HT,从中(包括第 2 步中选择的 6 个模式)选择出 HCost 最小的两种模式。
  4. 找到与这两个模式距离为 1 的模式。如果还没有测试过,则对其做 HT。
  5. 如果当前 CU 的 MPM 还没有测试过,则将其也加入到列表中
  6. 从所有已测试过的模式中,按照 HCost 从低到高选择 M(编码器自己生成的) 个模式组成一个新的集合,用于进行后续的 RDCost 的计算。

以下是一个演示例子:

  1. 一开始,我们需要从 { 0 , 1 , 2 , 6 , 10 , 14 , 18 , 22 , 26 , 30 , 34 } \{0,1,2,6,10,14,18,22,26,30,34\} {0,1,2,6,10,14,18,22,26,30,34} 模式中选择出六种最优模式,设它们是 { 0 , 6 , 10 , 14 , 18 , 26 } \{0,6,10,14,18,26\} {0,6,10,14,18,26}
  2. 与它们距离为 2 的模式分别为(模式 0 除外) { 4 , 8 } { 8.12 } { 12 , 16 } { 16 , 20 } { 24 , 28 } \{4,8\} \{8.12\}\{12,16\}\{16,20\}\{24,28\} {4,8}{8.12}{12,16}{16,20}{24,28}。设左侧和上侧 CU 的最优模式分别是 4 4 4 8 8 8 ,那么需要测试的模式集合为 { 4 , 8 , 12 , 16 , 20 , 24 , 28 } \{4,8,12,16,20,24,28\} {4,8,12,16,20,24,28}
  3. 设最优的两个模式为 4 , 6 4,6 4,6,它们距离为 1 的模式分别为 { 3 , 5 } { 5 , 7 } \{3,5\}\{5,7\} {3,5}{5,7},因此需要测试的模式集合为 { 3 , 5 , 7 } \{3,5,7\} {3,5,7}
  4. 如果当前 CU 的 MPM 模式还没有测试过,则将其加入到候选列表中。
  5. 候选列表按照 HCost 升序排序,从中选取 M 个模式进行下一步的 RDCost 计算

算法分析:

该算法相较于挨个遍历而言,能减少遍历的模式数,从而降低时间时间开销。但是由于没有逐个遍历,则有可能陷入局部最优解,导致最终选择到的模式并不是全局的最优的模式,使得码率上升。

2 RDOQ 提前跳过算法

这里有一个模式“近邻”的概念,指的是模式距离小于等于 2 的两个模式。

我们从上一步得到了 M 种候选模式。默认的编码器是将这 M 种模式一一进行 RDCost 的计算。这个算法是在此之前,先对 M 种候选模式进行一个过滤,减少后续进行 RDCost 计算的模式数,从而降低编码时间开销。

该算法的流程如下:

  1. 先将候选模式中(按照 HCost 升序排序)的前两种模式加入到集合中,将该集合记为 S S S
  2. 之后遍历剩下的模式。如果该模式不是集合 S S S 中任意模式的近邻,则将其加入到集合 S S S
  3. 检查集合 S S S 中是否已经包含前两种模式以及模式 0 、模式 1 和 MPM。如果是,则结束步骤 2;否则继续进行步骤 2
  4. 将集合 S S S 作为新的候选模式集合进行 RDCost 计算

该算法可行的依据:

  1. 角度模式下,相距较近的两个模式所使用的角度相差不大,即编码差异不大
  2. 后续计算 RDCost 得到的最优模式有 90% 以上来自于候选列表的前 3 种模式

3 提前终止 CU 划分

传统 H.265 编码器在确定最终划分的 CU 大小时,会使用四叉树递归的方法遍历所有的划分模式,并从中选择整体 RDCost 最小的模式。对于一个 2Nx2N 大小的 CU N ∈ { 4 , 8 , 16 , 32 } N\in\{4,8,16,32\} N{4,8,16,32},编码器会先计算一次整体的 RDCost,随后将其划分为 4 个 NxN 大小的 CU,并重复上述步骤。最终,编码器会对比整体的 RDCost 和四个子 CU 的 RDCost 的和,选择 RDCost 较小的作为最终的划分方式。显然,这也是一个耗时的过程。

该算法是一种提前终止划分的方法。通过预测最终的划分结果,从而可以提前知道最终的划分情况,提前终止划分。这个表达式如下:

( m i n { 4 K , J d + 1 , K = 4 H J d + 1 , K H } ) ∑ i = 1 K J d + 1 R D ( i ) > β K J d R D (min\{\frac{4}{K},\frac{J^H_{d+1,K=4}}{J^H_{d+1,K}}\})\sum^K_{i=1}J^{RD}_{d+1}(i)>\beta_KJ^{RD}_d (min{K4,Jd+1,KHJd+1,K=4H})i=1KJd+1RD(i)>βKJdRD

其中, d d d 表示当前大的 CU 的深度; K K K 表示当前已完成编码的 d + 1 d+1 d+1 深度的 CU 的编号, K ∈ { 0 , 1 , 2 , 3 } K\in\{0,1,2,3\} K{0,1,2,3} J d + 1 , K H J^H_{d+1,K} Jd+1,KH 表示深度为 d + 1 d+1 d+1 的前 K K K 个 CU 的 HCost 之和; J d + 1 R D ( i ) J^{RD}_{d+1}(i) Jd+1RD(i) 表示深度为 d + 1 d+1 d+1 的第 i i i 个 CU 的 RDCost; J d R D J^{RD}_d JdRD 表示深度为 d d d 的 CU 整体的 RDCost; β K \beta_K βK 为一组系数, β K = { 1.5 , 1.2 , 1.1 , 1.0 } \beta_K=\{1.5,1.2,1.1,1.0\} βK={1.5,1.2,1.1,1.0}

如果上式在任何时候成立,那么 CU 的划分就可以被终止。

二、在源码中修改

注:修改内容会以 PAPER_INTRA_ZHANGHAO 宏来进行条件编译

这里主要是需要修改 xCompressCU 和 estInraLumaQT 两个函数。这两个函数的讲解可以参考以下两篇博客:

  • xCompressCU : 这篇博客
  • estIntraLumaQT:这篇博客

1. 数据结构声明

  1. 在 TComDataCU.h 文件中声明以下成员变量:
#if PAPER_INTRA_ZHANGHAO
private:  
  UChar m_uhAbovePUIntraMode; //保存上方 PU 的帧内预测模式
  UChar m_uhLeftPUIntraMode;  //保存左侧 PU 的帧内预测模式
public:
  UChar getAbovePUIntraMode() {return m_uhAbovePUIntraMode;} //get 函数,下同
  UChar getLeftPUIntraMode()  {return m_uhLeftPUIntraMode;}

  //用于检测并得到上方 PU 和左侧 PU 的模式
  Void setPUAboveAndLeftIntraMode(UInt uiAbsPartIdx, const ComponentID compID);

  Double subHadCost[4][4]; //用于保存 LCU 中的子 CU 的 HCost
  Double lumaBestCost[4];  //用于保存亮度通道上的最优 RDCost
  Double cost1Part[4];     //用于保存当前 CU 的四个子 CU 的 RDCost
  Bool b4x4SplitEarlyStop; //对 4x4 进行编码时,判断是否能提前终止
#endif



在 CU 划分中,最多只会划分到深度 3 ,此时 CU 的大小为 8x8.。但是在继续执行帧内最优模式的选择时,又会将其进一步划分成 4x4 的小块进行选择(这里说明一下,CU 大小的最小值是 8x8 ,但是在 HM 中,CU 是以 4x4 为单位进行保存的)。因此,在进行提前终止检测时,也需要对 4x4 的块进行检测。

其中,setPUAboveAndLeftIntraMode 函数在 TComDataCU.cpp 中定义,代码如下:

#if PAPER_INTRA_ZHANGHAO
Void TComDataCU::setPUAboveAndLeftIntraMode(UInt uiAbsPartIdx, const ComponentID compID){
  UInt        LeftPartIdx  = MAX_UINT;
  UInt        AbovePartIdx = MAX_UINT;
  Int         iLeftIntraDir, iAboveIntraDir;
  const TComSPS *sps=getSlice()->getSPS();
  const UInt partsPerMinCU = 1<<(2*(sps->getMaxTotalCUDepth() - sps->getLog2DiffMaxMinCodingBlockSize()));

  const ChannelType chType = toChannelType(compID);
  const ChromaFormat chForm = getPic()->getChromaFormat();
  // Get intra direction of left PU
  const TComDataCU *pcCULeft = getPULeft( LeftPartIdx, m_absZIdxInCtu + uiAbsPartIdx );

  if (isChroma(compID))
  {
    LeftPartIdx = getChromasCorrespondingPULumaIdx(LeftPartIdx, chForm, partsPerMinCU);
  }
  iLeftIntraDir  = pcCULeft ? ( pcCULeft->isIntra( LeftPartIdx ) ? pcCULeft->getIntraDir( chType, LeftPartIdx ) : DC_IDX ) : DC_IDX;

  // Get intra direction of above PU
  const TComDataCU *pcCUAbove = getPUAbove( AbovePartIdx, m_absZIdxInCtu + uiAbsPartIdx, true, true );

  if (isChroma(compID))
  {
    AbovePartIdx = getChromasCorrespondingPULumaIdx(AbovePartIdx, chForm, partsPerMinCU);
  }
  iAboveIntraDir = pcCUAbove ? ( pcCUAbove->isIntra( AbovePartIdx ) ? pcCUAbove->getIntraDir( chType, AbovePartIdx ) : DC_IDX ) : DC_IDX;

  if (isChroma(chType))
  {
    if (iLeftIntraDir  == DM_CHROMA_IDX)
    {
      iLeftIntraDir  = pcCULeft-> getIntraDir( CHANNEL_TYPE_LUMA, LeftPartIdx  );
    }
    if (iAboveIntraDir == DM_CHROMA_IDX)
    {
      iAboveIntraDir = pcCUAbove->getIntraDir( CHANNEL_TYPE_LUMA, AbovePartIdx );
    }
  }

  if(pcCUAbove){
    m_uhAbovePUIntraMode = (UChar)iAboveIntraDir;
  }
  else{
    m_uhAbovePUIntraMode = 255;
  }

  if(pcCULeft){
    m_uhLeftPUIntraMode = (UChar)iLeftIntraDir;
  }
  else{
    m_uhLeftPUIntraMode = 255;
  }
}
#endif

这一段是参考源码中获取周围 CU 的代码写的(也可以说直接抄的源码的,因为我当时还看不懂 TvT )。模式设为 255 表示该 PU 不存在。

2.算法实现

2.1 TComDataCU.cpp 中

  1. 在 xCompressCU 一开始的地方,写入以下语句:
#if (PAPER_INTER_SHENLIQUAN_2) || (PAPER_INTER_SHENLIQUAN) || (PAPER_INTRA_ZHANGHAO)
  TComDataCU *pcLCU = pcPic->getCtu(rpcTempCU->getCtuRsAddr());
#endif

这个是用来获取 LCU 的。

  1. TEncCu.cpp 中,在 further split 注释下的 iQP 循环中,加入以下代码段
 // further split
    Double splitTotalCost = 0;
#if PAPER_INTRA_ZHANGHAO
    Bool bSplitEarlyStop = false;
#endif
    for (Int iQP=iMinQP; iQP<=iMaxQP; iQP++)
    {
      const Bool bIsLosslessMode = false; // False at this level. Next level down may set it to true.

      rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );

      UChar       uhNextDepth         = uiDepth+1;
      TComDataCU* pcSubBestPartCU     = m_ppcBestCU[uhNextDepth];
      TComDataCU* pcSubTempPartCU     = m_ppcTempCU[uhNextDepth];
      DEBUG_STRING_NEW(sTempDebug)

//以上是源码,定位使用
#if PAPER_INTRA_ZHANGHAO
/****************************************自定义开始****************************************************/
      Double my_splitTotalCost = 0.0;
      
      const Double betaK[4] = {0.666666667,0.8333333333,0.909091,1.0};
      Double cost1=0.0,cost2=0.0;
        for(Int i = 0;i < 4;i++){
          cost2 += pcLCU->subHadCost[uiDepth][i];
        }
/****************************************自定义结束****************************************************/
#endif

这段代码主要是给一些变量赋值,方便后续计算。

这里我做了一个小变化,将原来公式中的系数写成了倒数的形式。这样做的目的是将除法运算转换成乘法运算,能够稍微提升执行的速度。

  1. 在进入遍历四个子 CU 的循环时,加入以下代码:
      for ( UInt uiPartUnitIdx = 0; uiPartUnitIdx < 4; uiPartUnitIdx++ )
      {
      //以上是源码,定位使用
#if PAPER_INTRA_ZHANGHAO
        cost1 += pcLCU->subHadCost[uiDepth][uiPartUnitIdx];
#endif
  1. 在每次深度递归完成后,加入以下代码:
#if PAPER_INTRA_ZHANGHAO
          my_splitTotalCost += m_pcRdCost->calcRdCost(pcSubBestPartCU->getTotalBits(),pcSubBestPartCU->getTotalDistortion());
#endif
//以下是源码,用于定位的
  rpcTempCU->copyPartFrom( pcSubBestPartCU, uiPartUnitIdx, uhNextDepth );         // Keep best part data to current temporary data.
          xCopyYuv2Tmp( pcSubBestPartCU->getTotalNumPart()*uiPartUnitIdx, uhNextDepth );

3, 4 都是用来保存和更新跟提前终止划分相关的变量

  1. 在循环的最后快结束的地方,加入以下代码:
        else
        {
          pcSubBestPartCU->copyToPic( uhNextDepth );
          rpcTempCU->copyPartFrom( pcSubBestPartCU, uiPartUnitIdx, uhNextDepth );
        }

        //以上是源码,用于定位的

        //自定义 earlyStop
#if PAPER_INTRA_ZHANGHAO
        if(std::min(4.0/(uiPartUnitIdx+1),cost2/cost1)*my_splitTotalCost*betaK[uiPartUnitIdx]>rpcBestCU->getTotalCost()){
          bSplitEarlyStop = true;//early stop
          break;
        }
#endif
  1. 在 xCheckBestMode 调用前面,加入以下代码:
#if PAPER_INTRA_ZHANGHAO
          if(!bSplitEarlyStop)
#endif
//以下是源码
            xCheckBestMode( rpcBestCU, rpcTempCU, uiDepth DEBUG_STRING_PASS_INTO(sDebug) DEBUG_STRING_PASS_INTO(sTempDebug) DEBUG_STRING_PASS_INTO(false) );
  

因为在提前终止后,表示当前的 rpcTempCU 最终是不会优于 rpcBestCU 的。但是因为没有跑完,会导致 TempCU 中的 RDCost 更小,同时很多参数也会缺失。因此此处应避免二者进行比较。

  1. 在 xCheckRDCostIntra 函数中,在调用 estIntraPredLumaQT 前后加上以下代码:
#if PAPER_INTRA_ZHANGHAO
  rpcTempCU->b4x4SplitEarlyStop = false;
#endif
//以下这句是源码,我们接下来也会修改这个函数
  m_pcPredSearch->estIntraPredLumaQT( rpcTempCU, m_ppcOrigYuv[uiDepth], m_ppcPredYuvTemp[uiDepth], m_ppcResiYuvTemp[uiDepth], m_ppcRecoYuvTemp[uiDepth], resiLuma DEBUG_STRING_PASS_INTO(sTest) );
#if PAPER_INTRA_ZHANGHAO
  if(rpcTempCU->b4x4SplitEarlyStop){
    return;
  }

这里的原因类似。接下来我们要修改 estIntraPredLumaQT 这个函数,其中也会涉及到提前终止的问题。因此,一旦该问题成立,则需要跳过后续步骤(因为这里的输入参数是 TempCU,如果不是最优的话那么以 BestCU 返回就好了)。

2.2 TEncSearch.cpp 中

  1. 在 estIntraPredLumaQT 函数中,加入以下代码,声明一些变量:
  const TComSPS     &sps                   = *(pcCU->getSlice()->getSPS());
  const TComPPS     &pps                   = *(pcCU->getSlice()->getPPS());
	//上面为源码,定位使用
#if PAPER_INTRA_ZHANGHAO
  /********************************自定义初始化*************************************/
  TComDataCU *pcLCU = pcCU->getPic()->getCtu(pcCU->getCtuRsAddr());
  UInt uiWidth = pcCU->getWidth(0) >> uiInitTrDepth;
  UInt uiHeight= pcCU->getHeight(0)>> uiInitTrDepth; 
  Bool earlyStop = false;
#endif
  1. 在进入 PU 循环时,加入以下代码:
    initIntraPatternChType( tuRecurseWithPU, COMPONENT_Y, true DEBUG_STRING_PASS_INTO(sTemp2) );

#if PAPER_INTRA_ZHANGHAO
/**********************自定义初始化*************************/
    UInt uiSectionNum = tuRecurseWithPU.GetSectionNumber();
    Double subCost[35][4];

      for(int i=0;i<35;i++){
        for(int j=0;j<4;j++){
          subCost[i][j] = 0;
        }
      }
#endif

这段代码同样是初始化使用的

  1. 然后就是比较长的一段了,主要是实现算法1和算法2。代码如下:
#if PAPER_INTRA_ZHANGHAO

      Int bitDepth = sps.getBitDepth(CHANNEL_TYPE_LUMA);
      //*****************************************自定义模式决策算法********************************************************
      for( Int i=0; i < numModesAvailable; i++ )
      {
        CandCostList[ i ] = MAX_DOUBLE;
      }
      Bool abModeIsTested[35];
      memset(abModeIsTested,0,sizeof(abModeIsTested));

      //第一步:选择11种模式(0、1、2、6、10、14、18、22、26、30、34)进行编码,选择 cost 最小的 6 种
      UInt auiFirstModeList[11]={0,1,2,6,10,14,18,22,26,30,34};
      for(int i=0;i<11;i++){
        UInt       uiMode = auiFirstModeList[i];
        Distortion uiSad  = 0;        

        //将当前模式标记为已测试
        abModeIsTested[uiMode] = true;

        //以下几行代码跟原来一样
        const Bool bUseFilter=TComPrediction::filteringIntraReferenceSamples(COMPONENT_Y, uiMode, puRect.width, puRect.height, chFmt, sps.getSpsRangeExtension().getIntraSmoothingDisabledFlag());
        predIntraAng( COMPONENT_Y, uiMode, piOrg, uiStride, piPred, uiStride, tuRecurseWithPU, bUseFilter, TComPrediction::UseDPCMForFirstPassIntraEstimation(tuRecurseWithPU, uiMode) );
        uiSad+=m_pcRdCost->calcHAD_subCost(bitDepth,piOrg,uiStride,piPred,uiStride,uiWidth,uiHeight,&subCost[uiMode][0]);
        UInt   iModeBits = 0;

        iModeBits+=xModeBitsIntra( pcCU, uiMode, uiPartOffset, uiDepth, CHANNEL_TYPE_LUMA );
        Double cost      = (Double)uiSad + (Double)iModeBits * sqrtLambdaForFirstPass;

        //更新列表,保留 cost 最少的六种模式
        CandNum+=xUpdateCandList(uiMode,cost,6,uiRdModeList,CandCostList);      
      }

      //第二步:找到左PU 和 上PU 的模式,以及六个最小 cost 距离为 2 的模式 (0,1 除外)
      pcCU->setPUAboveAndLeftIntraMode(uiPartOffset,COMPONENT_Y);
      UInt auiSecondModeToBeTest[14];  /*最多可能有 6x2+2=14 种*/
      UInt SecondNum=0;

      //将左边和上面的 CU 的模式加入到待测试列表中(如果存在的话)
      UInt AboveIntraMode = (UInt)pcCU->getAbovePUIntraMode();
      UInt LeftIntraMode  =  (UInt)pcCU->getLeftPUIntraMode();

      if(AboveIntraMode<35 && !abModeIsTested[AboveIntraMode]){
        abModeIsTested[AboveIntraMode] = true;
        auiSecondModeToBeTest[SecondNum++] = AboveIntraMode;
      }
      if(LeftIntraMode<35 && !abModeIsTested[LeftIntraMode]){
        abModeIsTested[LeftIntraMode] = true;
        auiSecondModeToBeTest[SecondNum++] = LeftIntraMode;
      }

      //找距离为2的模式
      for(int i=0;i<6;i++){
        UInt uiMode = uiRdModeList[i];
        
        //如果模式是 0 或 1,则直接跳过
        if(uiMode==0 || uiMode==1){
          continue;
        }

        //如果这个模式没有左邻居或右邻居(减完小于2或加完大于34),则设置为 35。之后遇到 35 的模式则不将其放入列表 
        UInt uiLeftMode  = uiMode-2<2 ? 35 : uiMode-2;
        UInt uiRightMode = uiMode+2>34 ?35 : uiMode+2;

        //判断其左右的距离为2的邻居是否判断过。若没有则加入到 auiSecondModeList 中,并设置为已测试
        if(uiLeftMode<35 && !abModeIsTested[uiLeftMode]){
          abModeIsTested[uiLeftMode] = true;
          auiSecondModeToBeTest[SecondNum++] = uiLeftMode;
        }
        if(uiRightMode<35 && !abModeIsTested[uiRightMode]){
          abModeIsTested[uiRightMode] = true;
          auiSecondModeToBeTest[SecondNum++] = uiRightMode;
        }
      }

      //第三步:用新加入的模式进行编码,计算 cost 并更新 uiRdModeList
      for(int i=0;i<SecondNum;i++){
        UInt       uiMode = auiSecondModeToBeTest[i];
        Distortion uiSad  = 0;        

        //将当前模式标记为已测试 (已经在加入列表的时候就标记过了)
        // abModeIsTested[uiMode] = true;

        //以下几行代码跟原来一样
        const Bool bUseFilter=TComPrediction::filteringIntraReferenceSamples(COMPONENT_Y, uiMode, puRect.width, puRect.height, chFmt, sps.getSpsRangeExtension().getIntraSmoothingDisabledFlag());
        predIntraAng( COMPONENT_Y, uiMode, piOrg, uiStride, piPred, uiStride, tuRecurseWithPU, bUseFilter, TComPrediction::UseDPCMForFirstPassIntraEstimation(tuRecurseWithPU, uiMode) );
        uiSad+=m_pcRdCost->calcHAD_subCost(bitDepth,piOrg,uiStride,piPred,uiStride,uiWidth,uiHeight,&subCost[uiMode][0]);
        // distParam.DistFunc(&distParam);
        UInt   iModeBits = 0;

        iModeBits+=xModeBitsIntra( pcCU, uiMode, uiPartOffset, uiDepth, CHANNEL_TYPE_LUMA );
        Double cost      = (Double)uiSad + (Double)iModeBits * sqrtLambdaForFirstPass;

        //选择 cost 最低的两个模式(至少要两个)
        CandNum+=xUpdateCandList(uiMode,cost,std::max(2,numModesForFullRD),uiRdModeList,CandCostList);
      }

      //第四步:寻找与最小 cost 的两个模式距离为1的模式加入到待测试列表中
      
      UInt auiThirdModeToBeTest[4]; /*最多有 2x2=4 种*/
      Int ThirdNum = 0;

      for(int i=0;i<2;i++){
        UInt uiMode = uiRdModeList[i];

        //如果模式是0或1,直接跳过
        if(uiMode==0 || uiMode==1){
          continue;
        }

        //如果这个模式没有左邻居或右邻居(减完小于2或加完大于34),则设置为 35。之后遇到 35 的模式则不将其放入列表 
        UInt uiLeftMode  = uiMode-1<2 ? 35 : uiMode-1;
        UInt uiRightMode = uiMode+1>34 ?35 : uiMode+1;

        //判断其左右的距离为1的邻居是否判断过。若没有则加入到 auiSecondModeList 中,并设置为已测试
        if(uiLeftMode<35 && !abModeIsTested[uiLeftMode]){
          abModeIsTested[uiLeftMode] = true;
          auiThirdModeToBeTest[ThirdNum++] = uiLeftMode;
          // std::cout<<"Left:"<
        }
        if(uiRightMode<35 && !abModeIsTested[uiRightMode]){
          abModeIsTested[uiRightMode] = true;
          auiThirdModeToBeTest[ThirdNum++] = uiRightMode;
          // std::cout<<"Right:"<
        }
      }

      //第五步:用这些模式进行编码,计算 cost 并更新列表
      for(int i=0;i<ThirdNum;i++){
        UInt       uiMode = auiThirdModeToBeTest[i];
        Distortion uiSad  = 0;      

        //以下这些跟原来一样
        const Bool bUseFilter=TComPrediction::filteringIntraReferenceSamples(COMPONENT_Y, uiMode, puRect.width, puRect.height, chFmt, sps.getSpsRangeExtension().getIntraSmoothingDisabledFlag());
        predIntraAng( COMPONENT_Y, uiMode, piOrg, uiStride, piPred, uiStride, tuRecurseWithPU, bUseFilter, TComPrediction::UseDPCMForFirstPassIntraEstimation(tuRecurseWithPU, uiMode) );
        uiSad+=m_pcRdCost->calcHAD_subCost(bitDepth,piOrg,uiStride,piPred,uiStride,uiWidth,uiHeight,&subCost[uiMode][0]);
        UInt   iModeBits = 0;

        iModeBits+=xModeBitsIntra( pcCU, uiMode, uiPartOffset, uiDepth, CHANNEL_TYPE_LUMA );
        Double cost      = (Double)uiSad + (Double)iModeBits * sqrtLambdaForFirstPass;

        //更新 uiRdModeList
        CandNum+=xUpdateCandList(uiMode,cost,std::max(2,numModesForFullRD),uiRdModeList,CandCostList);
      }

      
      //第六步:如果当前 CU 的 MPM 没有检测过,则将其加在 RdModeList 的后面
      Int uiPreds[NUM_MOST_PROBABLE_MODES] = {-1, -1, -1};
      Int iMode = -1;
      pcCU->getIntraDirPredictor( uiPartOffset, uiPreds, COMPONENT_Y, &iMode );
      const Int numCand = ( iMode >= 0 ) ? iMode : Int(NUM_MOST_PROBABLE_MODES);
      for(Int i=0;i < numCand;i++){
        Int mostProbableMode = uiPreds[i];
        if(!abModeIsTested[mostProbableMode]){
          abModeIsTested[mostProbableMode] = true;
          uiRdModeList[numModesForFullRD++] = mostProbableMode;
        }
      }
      
      
      //DEBUG 部分
#if DEBUG_INTRA_SEARCH_COST
      // std::cout<<"==========================================================================\n";

      // std::cout<<"==========================================================================\n"
#endif

      /*early skip of the RDOQ 算法*/
      
      Int numModeFinalFullRDList=0; //集合中的元素数量
      UInt uiModeFinalFullRDList[35]; //集合中的元素
      UInt uiMode;
      UInt MostProbableMode = uiRdModeList[numModesForFullRD-1];
      
      memset(abModeIsTested,0,sizeof(abModeIsTested)); //使用这个数组来判断一个模式是否加入了几何(懒得开个新的了)

      // 第一步:把前两种模式直接加入到集合中
      // 注:由于 uiRdModeList 中的模式是按照 cost 升序排好序的
              //因此在后续的步骤中,只要是顺序访问 uiRdModeList
              //就能保证 FinalFullRDList 中的模式也是按照 cost 升序排序的,不需要额外进行排序

      uiMode = uiRdModeList[0];
      abModeIsTested[uiMode] = true;
      uiModeFinalFullRDList[numModeFinalFullRDList++] = uiMode;

      uiMode = uiRdModeList[1];
      abModeIsTested[uiMode] = true;
      uiModeFinalFullRDList[numModeFinalFullRDList++] = uiMode;

      //第二步:检查后续的模式,如果不与已选择的模式重复或相邻,则加入到集合中
      for(Int i=2;i<numModesForFullRD;i++){
        uiMode = uiRdModeList[i];
        
        if(abModeIsTested[uiMode]){
          continue;
        }
        if(uiMode>3 && abModeIsTested[uiMode-1]){
          continue;
        }
        if(uiMode<34 && uiMode>2 && abModeIsTested[uiMode+1]){
          continue;
        }

        abModeIsTested[uiMode] = true;
        uiModeFinalFullRDList[numModeFinalFullRDList++] = uiMode;

        //第三步:如果集合中已有 m1, m2, PLANAR(0), DC(1), MPM 五种模式,则直接结束循环       
        //由于 m1, m2 一开始就在集合里了,因此可以不需要判断
        if(abModeIsTested[0] && abModeIsTested[1] && abModeIsTested[MostProbableMode]){
          break;
        } 
      }

      numModesForFullRD = numModeFinalFullRDList;
      memcpy(uiRdModeList,uiModeFinalFullRDList,sizeof(UInt)*numModesForFullRD);

      ;

      // *****************************************自定义结束********************************************************
#else //这里之后是源码。因为这个相当于是整个算法都换掉了,所以原来的代码全都用不了
      DistParam distParam;
      const Bool bUseHadamard=pcCU->getCUTransquantBypass(0) == 0;
      m_pcRdCost->setDistParam(distParam, sps.getBitDepth(CHANNEL_TYPE_LUMA), piOrg, uiStride, piPred, uiStride, puRect.width, puRect.height, bUseHadamard);
      distParam.bApplyWeight = false;
      for( Int i=0; i < numModesForFullRD; i++ )
      {
        CandCostList[ i ] = MAX_DOUBLE;
      }
      for( Int modeIdx = 0; modeIdx < numModesAvailable; modeIdx++ )
      {
        UInt       uiMode = modeIdx;
        Distortion uiSad  = 0;

        const Bool bUseFilter=TComPrediction::filteringIntraReferenceSamples(COMPONENT_Y, uiMode, puRect.width, puRect.height, chFmt, sps.getSpsRangeExtension().getIntraSmoothingDisabledFlag());

        predIntraAng( COMPONENT_Y, uiMode, piOrg, uiStride, piPred, uiStride, tuRecurseWithPU, bUseFilter, TComPrediction::UseDPCMForFirstPassIntraEstimation(tuRecurseWithPU, uiMode) );

        // use hadamard transform here
        uiSad+=distParam.DistFunc(&distParam);

        UInt   iModeBits = 0;

        // NB xModeBitsIntra will not affect the mode for chroma that may have already been pre-estimated.
        iModeBits+=xModeBitsIntra( pcCU, uiMode, uiPartOffset, uiDepth, CHANNEL_TYPE_LUMA );

        Double cost      = (Double)uiSad + (Double)iModeBits * sqrtLambdaForFirstPass;

#if DEBUG_INTRA_SEARCH_COSTS
        std::cout << "1st pass mode " << uiMode << " SAD = " << uiSad << ", mode bits = " << iModeBits << ", cost = " << cost << "\n";
#endif

        CandNum += xUpdateCandList( uiMode, cost, numModesForFullRD, uiRdModeList, CandCostList );
      }

      if (m_pcEncCfg->getFastUDIUseMPMEnabled())
      {
        Int uiPreds[NUM_MOST_PROBABLE_MODES] = {-1, -1, -1};

        Int iMode = -1;
        pcCU->getIntraDirPredictor( uiPartOffset, uiPreds, COMPONENT_Y, &iMode );

        const Int numCand = ( iMode >= 0 ) ? iMode : Int(NUM_MOST_PROBABLE_MODES);

        for( Int j=0; j < numCand; j++)
        {
          Bool mostProbableModeIncluded = false;
          Int mostProbableMode = uiPreds[j];

          for( Int i=0; i < numModesForFullRD; i++)
          {
            mostProbableModeIncluded |= (mostProbableMode == uiRdModeList[i]);
          }
          if (!mostProbableModeIncluded)
          {
            uiRdModeList[numModesForFullRD++] = mostProbableMode;
          }
        }
      }
#endif

这是就是算法1和算法2实现的地方了。注意需要把原来的代码全部注释掉,因为算法已经变了。

  1. 在计算 RDCost 时,加入以下代码:
    //===== check modes (using r-d costs) =====
#if HHI_RQT_INTRA_SPEEDUP_MOD
    UInt   uiSecondBestMode  = MAX_UINT;
    Double dSecondBestPUCost = MAX_DOUBLE;
#endif
    DEBUG_STRING_NEW(sPU)
    UInt       uiBestPUMode  = 0;
    Distortion uiBestPUDistY = 0;
    Double     dBestPUCost   = MAX_DOUBLE;
#if PAPER_INTRA_ZHANGHAO
/*********************************自定义开始********************************************/
    Double dSumHadCost = 0.0;
    if(uiNumPU == 1){
      for(Int j = 0;j < 4;j++){
        pcLCU->subHadCost[uiDepth][j] = subCost[uiRdModeList[0]][j];
        dSumHadCost += subCost[uiRdModeList[0]][j];
      }
      dSumHadCost /= (uiWidth*uiHeight);
    }
/*********************************自定义结束********************************************/
#endif

用于给一些变量赋值,以及初始化

  1. 在比较完 RDCost 后,加入以下代码。主要是算法 3 的:
#if HHI_RQT_INTRA_SPEEDUP_MOD
      else if( dPUCost < dSecondBestPUCost )
      {
        uiSecondBestMode  = uiOrgMode;
        dSecondBestPUCost = dPUCost;
      }
#endif

//以上代码为定位使用
#if PAPER_INTRA_ZHANGHAO
/*********************************自定义开始****************************************/
      
      if(uiNumPU == 1){
        pcLCU->lumaBestCost[uiDepth] = dBestPUCost;
      }

      pcCU->b4x4SplitEarlyStop = false;
      if(uiDepth>0&&pcLCU->lumaBestCost[uiDepth]<MAX_DOUBLE/2){
        UInt uiIdx = 0,uiLastDepth = 0;

        if(uiNumPU == 1){
          // uiIdx = 
          // uiLastDepth = uiDepth - 1;
        }

        else if(uiNumPU == 4){
          uiIdx = uiSectionNum;
          uiLastDepth = uiDepth;

        pcCU->cost1Part[uiIdx] = dBestPUCost;
        Double dThresh[] = {1.5, 1.2, 1.1, 1};
        Double dModeCoef[35];
        Double cost1 = 0, cost2 = pcLCU->lumaBestCost[uiLastDepth];
        Double hadTotalCost = 0, hadCost = 0;
        for(int k = 0; k < 35; k++)
          dModeCoef[k] = 1;
        dModeCoef[0] = 1.5;
        dModeCoef[1] = 1.2;

        for(int k = 0; k < 4; k++)
          hadTotalCost += pcLCU->subHadCost[uiLastDepth][k];
        for(int k = 0; k <= uiIdx; k++)
        {
          cost1   +=       pcCU->cost1Part[k];
          hadCost += pcLCU->subHadCost[uiLastDepth][k];
        }

        // cost1: d+1 深度的前 K 个子 CU 的 RDCost 和; cost2: d 深度的 CU 的 RDCost 
        if( cost1 >= cost2 * max(1.0*(uiIdx + 1)/4, hadCost/hadTotalCost) * dThresh[uiIdx] * dModeCoef[uiMode])
        {
          // if(uiNumPU == 4)
            // earlyStop = true;
          // pcCU->b4x4SplitEarlyStop = true;
          earlyStop = true;
          break; // break mode loop
        }
        }
      }
/*********************************自定义结束****************************************/
#endif
    } // Mode loop
#if PAPER_INTRA_ZHANGHAO
  if(earlyStop) {
    pcCU->b4x4SplitEarlyStop = true;
    // std::cout<
    break;
  }
#endif

在这里就是判断是否需要提前终止。第一个 break 是跳出模式循环,第二个break是跳出 PU 循环。

  1. 之后是一个小 trick,代码如下:
#if !PAPER_INTRA_ZHANGHAO
// #if 0
      UInt uiOrgMode = uiBestPUMode;
#endif
#endif

#if ENVIRONMENT_VARIABLE_DEBUG_AND_TEST
      if (DebugOptionList::ForceLumaMode.isSet())
      {
        uiOrgMode = DebugOptionList::ForceLumaMode.getInt();
      }
#endif

#if !(PAPER_INTRA_ZHANGHAO)
// #if 0
      pcCU->setIntraDirSubParts ( CHANNEL_TYPE_LUMA, uiOrgMode, uiPartOffset, uiDepth + uiInitTrDepth );
      DEBUG_STRING_NEW(sModeTree)

      // set context models
      m_pcRDGoOnSbacCoder->load( m_pppcRDSbacCoder[uiDepth][CI_CURR_BEST] );

      // determine residual for partition
      Distortion uiPUDistY = 0;
      Double     dPUCost   = 0.0;

      
      xRecurIntraCodingLumaQT( pcOrgYuv, pcPredYuv, pcResiYuv, resiLumaPU, uiPUDistY, false, dPUCost, tuRecurseWithPU DEBUG_STRING_PASS_INTO(sModeTree));

      // check r-d cost
      if( dPUCost < dBestPUCost )
      {
        DEBUG_STRING_SWAP(sPU, sModeTree)
        uiBestPUMode  = uiOrgMode;
        uiBestPUDistY = uiPUDistY;
        dBestPUCost   = dPUCost;

        xSetIntraResultLumaQT( pcRecoYuv, tuRecurseWithPU );

        if (pps.getPpsRangeExtension().getCrossComponentPredictionEnabledFlag())
        {
          const Int xOffset = tuRecurseWithPU.getRect( COMPONENT_Y ).x0;
          const Int yOffset = tuRecurseWithPU.getRect( COMPONENT_Y ).y0;
          for (UInt storedResidualIndex = 0; storedResidualIndex < NUMBER_OF_STORED_RESIDUAL_TYPES; storedResidualIndex++)
          {
            if (bMaintainResidual[storedResidualIndex])
            {
              xStoreCrossComponentPredictionResult(resiLuma[storedResidualIndex], resiLumaPU[storedResidualIndex], tuRecurseWithPU, xOffset, yOffset, MAX_CU_SIZE, MAX_CU_SIZE );
            }
          }
        }

        const UInt uiQPartNum = tuRecurseWithPU.GetAbsPartIdxNumParts();
        ::memcpy( m_puhQTTempTrIdx,  pcCU->getTransformIdx()       + uiPartOffset, uiQPartNum * sizeof( UChar ) );

        for (UInt component = 0; component < numberValidComponents; component++)
        {
          const ComponentID compID = ComponentID(component);
          ::memcpy( m_puhQTTempCbf[compID], pcCU->getCbf( compID  ) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
          ::memcpy( m_puhQTTempTransformSkipFlag[compID],  pcCU->getTransformSkip(compID)  + uiPartOffset, uiQPartNum * sizeof( UChar ) );
        }
      }
#endif

这里是将原本需要再次执行的不划分的 CU 遍历给终止了。这是因为之前已经执行过划分的 CU 遍历了,理论上来说划分得更细致,则大概率其失真会更低,因此可以不需要再次划分,能够节省更多的时间。

之后,在 TypeDef,h 里面定义:

#define PAPER_INTRA_ZHANGHAO 1

编译,即可运动。

三、实验数据

  1. 原始数据
视频序列 使用的算法 QP22 QP27 QP32 QP37
耗时(s) 比特率(kbps) PSNR(dB) 耗时(s) 比特率(kbps) PSNR(dB) 耗时(s) 比特率(kbps) PSNR(dB) 耗时(s) 比特率(kbps) PSNR(dB)
PeopleOnStreet(2560x1600) 源编码器 3632.925 104260.723 43.8772 3209.086 60452.731 40.7022 2892.292 34366.123 37.7814 2670.301 20011.469 35.1108
FIMD 1766.414 105580.963 43.8277 1453.668 61152.094 40.6454 1179.625 35489.746 37.6938 967.242 20950.344 34.9847
Traffic(2560x1600) 源编码器 3571.135 102059.198 43.4703 3148.059 57487.142 40.455 2847.11 32804.136 37.7193 2636.234 18551.059 35.0546
FIMD 1709.562 103644.014 43.4262 1344.115 59292.686 40.3766 1078.058 34186.339 37.5991 856.157 19453.848 34.8723
BasketballDrive(1902x1080) 源编码器 1726.441 49707.976 42.1815 1439.009 21573.136 40.3839 1304.173 11788.16 38.8766 1232.928 6723.6 37.0848
FIMD 821.138 50207.648 42.1558 600.601 22128.528 40.3498 437.28 12413.488 38.8195 372.452 7187.632 36.9962
Tennis(1920x1080) 源编码器 1640.024 21701.914 42.9343 1406.962 11439.068 41.2096 1289.194 6054.655 39.2771 1217.77 3155.27 37.1992
FIMD 766.849 22050.49 42.9085 570.162 11716.013 41.1720 438.717 6309.362 39.199 348.537 3344.16 37.0863
Cactus(1920x1080) 源编码器 1975.155 110417.496 41.1568 1629.262 51486.072 38.5405 1431.178 28058.632 36.439 1318.332 15229.648 34.1816
FIMD 1039.355 110800.728 41.1184 774.069 52266.792 38.5097 607.097 28713.136 36.3832 497.026 15688.12 34.1004
RaceHorses(832x480) 源编码器 389.527 17265.533 42.6478 345.04 10667.005 39.1022 311.180 6189.053 35.6469 278.771 3132.926 32.2599
FIMD 190.704 17430.984 42.5846 162.557 10815.619 39.0425 135.896 6326.866 35.5389 111.562 3216.691 32.1165
Keiba(832x480) 源编码器 356.615 10541.966 43.2681 315.421 6119.064 40.2650 284.994 3486.398 37.3744 261.803 1910.472 34.5593
FIMD 178.151 10690.646 43.2243 147.661 6243.163 40.2169 117.262 3568.646 37.2774 93.615 1969.958 34.4218
BQMall(832x480) 源编码器 387.922 28932.346 42.2937 341.58 17767.958 39.2672 308.333 10668.797 36.177 282.185 6023.462 33.0579
FIMD 198.55 29168.131 42.2452 162.633 17982.96 39.2094 135.01 10833.648 36.0983 108.158 6149.923 32.9175
SlideShow(1280x720) 源编码器 573.325 3927.046 50.411 550.491 2471.254 47.1421 532.594 1619.254 44.0857 517.199 1057.581 40.8910
FIMD 160.638 4032.054 50.3539 140.244 2543.149 47.061 126.181 1672.525 43.9124 106.043 1106.698 40.5339
ChinaSpeed(1024x768) 源编码器 271.458 21774.372 45.75 248.351 14846.82 41.9913 230.194 9930.552 38.421 214.706 6557.544 34.9552
FIMD 124.8 22218.6 45.674 107.332 15244.592 41.8869 93.754 10231.488 38.2778 77.733 6808.176 34.6793
vidyo1(1280x720) 源编码器 274.434 21062.496 45.3337 249.436 12285.432 43.1012 233.768 7422.96 40.6631 221.085 4461.72 37.9657
FIMD 117.641 21607.224 45.2811 91.818 12904.464 43.0066 76.252 7899.288 40.5222 64.187 4759.68 37.7682
vidyo3(1280x720) 源编码器 273.999 21925.656 45.2769 249.997 13141.32 43.0019 234.039 8145.336 40.4839 220.971 4923.648 37.6668
FIMD 120.116 22425.552 45.2291 96.875 13573.032 42.9348 82.377 8448.48 40.3782 69.406 5161.344 37.5642
Mobisode2(416x240) 源编码器 25.326 642.828 47.5334 23.6 365.268 45.1863 22.607 226.272 42.8796 21.907 151.62 40.8236
FIMD 9.65 662.112 47.4135 7.896 382.296 45.0486 6.66 238.62 42.6947 5.586 164.976 40.4996
RaceHorses(416x240) 源编码器 40.33 5070.852 42.6197 36.23 3169.188 38.6812 32.183 1833.804 35.0211 28.427 996.996 31.8403
FIMD 20.863 5105.7 42.5672 18.264 3202.104 38.6197 15.517 1854.06 34.943 12.561 1013.268 31.7339
BasketballPass(416x240) 源编码器 36.254 5266.06 43.2725 31.926 3098.86 39.7985 28.482 1733.32 36.5559 25.764 948.98 33.6288
FIMD 18.728 5332.84 43.2144 15.048 3151.62 39.7138 11.93 1778.6 36.4607 9.865 967.8 33.5072
Flowervase(416x240) 源编码器 27.102 1161.6 48.3828 25.495 738.684 44.8238 24.198 447.18 41.3978 22.993 251.832 38.0865
FIMD 9.548 1177.548 48.2792 7.851 748.656 44.6999 6.674 452.844 41.2201 5.366 255.012 37.8065
BQSquare(416x240) 源编码器 43.076 12828.744 42.1994 38.111 8413.512 38.117 33.982 5375.592 34.5492 30.686 3342.12 31.0505
FIMD 23.641 12891.288 42.1244 20.228 8464.296 38.0429 17.132 5401.92 34.4731 14.632 3366.168 30.9767
  1. 与源编码器对比:
视频序列 使用的算法 QP22 QP27 QP32 QP37
时间减少(%) 比特率上升(%) PSNR降低(dB) 时间减少(%) 比特率上升(%) PSNR降低(dB) 时间减少(%) 比特率上升(%) PSNR降低(dB) 时间减少(%) 比特率上升(%) PSNR降低(dB)
PeopleOnStreet(2560x1600) FIMD 51.38% 1.27% 0.0495 54.70% 1.16% 0.0568 59.21% 3.27% 0.0876 63.78% 4.69% 0.1261
Traffic(2560x1600) 52.13% 1.55% 0.0441 57.30% 3.14% 0.0784 62.14% 4.21% 0.1202 67.52% 4.87% 0.1823
BasketballDrive(1902x1080) 52.44% 1.01% 0.0257 58.26% 2.57% 0.0341 66.47% 5.30% 0.0571 69.79% 6.90% 0.0886
Tennis(1920x1080) 53.24% 1.61% 0.0258 59.48% 2.42% 0.0376 65.97% 4.21% 0.0781 71.38% 5.99% 0.1129
Cactus(1920x1080) 47.38% 0.35% 0.0384 52.49% 1.52% 0.0308 57.58% 2.33% 0.0558 62.30% 3.01% 0.0812
RaceHorses(832x480) 51.04% 0.96% 0.0632 52.89% 1.39% 0.0597 56.33% 2.23% 0.1080 59.98% 2.67% 0.1434
Keiba(832x480) 50.04% 1.41% 0.0438 53.19% 2.03% 0.0481 58.85% 2.36% 0.0970 64.24% 3.11% 0.1375
BQMall(832x480) 48.82% 0.81% 0.0485 52.39% 1.21% 0.0578 56.21% 1.55% 0.0787 61.67% 2.10% 0.1404
SlideShow(1280x720) 71.98% 2.67% 0.0571 74.52% 2.91% 0.0811 76.31% 3.29% 0.1733 79.50% 4.64% 0.3571
ChinaSpeed(1024x768) 54.03% 2.04% 0.0760 56.78% 2.68% 0.1044 59.27% 3.03% 0.1432 63.80% 3.82% 0.2759
vidyo1(1280x720) 57.13% 2.59% 0.0526 63.19% 5.04% 0.0946 67.38% 6.42% 0.1409 70.97% 6.68% 0.1975
vidyo3(1280x720) 56.16% 2.28% 0.0478 61.25% 3.29% 0.0671 64.80% 3.72% 0.1057 68.59% 4.83% 0.1026
Mobisode2(416x240) 61.90% 3.00% 0.1199 66.54% 4.66% 0.1377 70.54% 5.46% 0.1849 74.50% 8.81% 0.3240
RaceHorses(416x240) 48.27% 0.69% 0.0525 49.59% 1.04% 0.0615 51.79% 1.10% 0.0781 55.81% 1.63% 0.1064
BasketballPass(416x240) 48.34% 1.27% 0.0581 52.87% 1.70% 0.0847 58.11% 2.61% 0.0952 61.71% 1.98% 0.1216
Flowervase(416x240) 64.77% 1.37% 0.1036 69.21% 1.35% 0.1239 72.42% 1.27% 0.1777 76.66% 1.26% 0.2800
BQSquare(416x240) 45.12% 0.49% 0.0750 46.92% 0.60% 0.0741 49.59% 0.49% 0.0761 52.32% 0.72% 0.0738

其中大部分测试序列的表现都是不错的,跟论文中得到的数据吻合

四、 总结

这篇文献是我最早开始阅读的(大概从春节开始吧),但由于一开始对编码器了解不深,进行起来十分困难。之前提速到了 40% 然后提不上去了,就搁置了一段时间看另外一篇去了。现在另一篇复现出来,再回过头来看,也就有了一定的思路,然后就顺利地实现了文献中的算法。

回顾这两三个月的时间吧,感觉在学习和调试过程中虽然十分痛苦,但最后做出了自己想要的成果,还是挺开心的。接下去继续努力吧 ^__^

你可能感兴趣的:(视频编码学习,算法,机器学习,视频编解码)