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

文章介绍了在HEVC编码标准中实现的两种算法优化:快速内模式决策和RDOQ提前跳过,以提高编码效率。快速内模式决策通过减少搜索模式的数量降低计算时间,而RDOQ提前跳过则通过预估成本减少编码时间。实验数据显示,这些优化方法在大多数视频序列中能有效减少编码时间并保持较好的编码质量。
摘要由CSDN通过智能技术生成

在大致了解了 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 两个函数。这两个函数的讲解可以参考以下两篇博客:

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.0 TComRdCost.cpp 和 TComRdCost.h 中

(upd:2023-9-14:这是之前漏掉的部分,缺失了这一部分确实会使程序运行不起来,感谢读者指出。由于时间过去太久自己都忘记这部分是什么意思了,就把代码直接放出来吧,说明等什么时候拾起这块知识再回来补充吧^_^)

2.0.1 TComRdCost.h 中

在 TComRdCost 类中声明以下函数:

  //自定义的 calcHad
  Distortion calcHAD_subCost(Int bitDepth, Pel* pi0, Int iStride0, Pel* pi1, Int iStride1, Int iWidth, Int iHeight,Double *subCost);
2.0.2 TComRdCost.cpp 中

定义函数 calcHAD_subCost

Distortion TComRdCost::calcHAD_subCost(Int bitDepth, Pel* pi0, Int iStride0, Pel* pi1, Int iStride1, Int iWidth, Int iHeight,Double *subCost){
  Distortion uiSum = 0;
  Int x, y;
  UInt w=0,h=0;
  UInt had[8][8];
  Pel *pi00 = pi0;
  Pel *pi11 = pi1;
  // Bool bVarValid = false;
  subCost[0]=subCost[1]=subCost[2]=subCost[3]=0;

  if ( ( (iWidth % 8) == 0 ) && ( (iHeight % 8) == 0 ) )
  {
    // bVarValid = true;
    w = iWidth>>3;
    h = iHeight>>3;
    for ( y=0; y<h; y+= 8 )
    {
      for ( x=0; x<w; x+= 8 )
      {
        had[y][x] += xCalcHADs8x8( &pi0[x], &pi1[x], iStride0, iStride1, 1
#if VECTOR_CODING__DISTORTION_CALCULATIONS && (RExt__HIGH_BIT_DEPTH_SUPPORT==0)
          , bitDepth
#endif
          );
        
        uiSum += had[y][x];
      }
      pi0 += iStride0*8;
      pi1 += iStride1*8;
    }

    if(h>1){
      for(y=0;y<h;y++){
        for(x=0;x<w;x++){
          if(x<(w>>1) && y<(h>>1)){
            subCost[0] += had[y][x];
          }
          else if(x>=(w>>1) && y>=(h>>1)){
            subCost[1] += had[y][x];
          }
          else if(x<(w>>1) && y>=(h>>1)){
            subCost[2] += had[y][x];
          }
          else{
            subCost[3] += had[y][x];
          }
        }
      }
    }
    else{
      w = 2;
      h = 2;
      for(y=0;y<h;y++){
        for(x=0;x<w;x++){
          subCost[2*y+x] = had[y][x] = xCalcHADs4x4(&pi00[x*4],&pi11[x*4],iStride0,iStride1,1);
        }
        pi00 += iStride0*4;
        pi11 += iStride1*4;
      }
    }
  }
  else
  {
    assert ( ( (iWidth % 4) == 0 ) && ( (iHeight % 4) == 0 ) );
    for ( y=0; y<iHeight; y+= 4 )
    {
      for ( x=0; x<iWidth; x+= 4 )
      {
        uiSum += xCalcHADs4x4( &pi0[x], &pi1[x], iStride0, iStride1, 1 );
      }
      pi0 += iStride0*4;
      pi1 += iStride1*4;
    }
  }
  return ( uiSum >> DISTORTION_PRECISION_ADJUSTMENT(bitDepth-8) );
}

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:"<<ThirdNum<<std::endl;
        }
        if(uiRightMode<35 && !abModeIsTested[uiRightMode]){
          abModeIsTested[uiRightMode] = true;
          auiThirdModeToBeTest[ThirdNum++] = uiRightMode;
          // std::cout<<"Right:"<<ThirdNum<<std::endl;
        }
      }

      //第五步:用这些模式进行编码,计算 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<<uiSectionNum<<std::endl;
    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. 原始数据
视频序列使用的算法QP22QP27QP32QP37
耗时(s)比特率(kbps)PSNR(dB)耗时(s)比特率(kbps)PSNR(dB)耗时(s)比特率(kbps)PSNR(dB)耗时(s)比特率(kbps)PSNR(dB)
PeopleOnStreet(2560x1600)源编码器3632.925104260.72343.87723209.08660452.73140.70222892.29234366.12337.78142670.30120011.46935.1108
FIMD1766.414105580.96343.82771453.66861152.09440.64541179.62535489.74637.6938967.24220950.34434.9847
Traffic(2560x1600)源编码器3571.135102059.19843.47033148.05957487.14240.4552847.1132804.13637.71932636.23418551.05935.0546
FIMD1709.562103644.01443.42621344.11559292.68640.37661078.05834186.33937.5991856.15719453.84834.8723
BasketballDrive(1902x1080)源编码器1726.44149707.97642.18151439.00921573.13640.38391304.17311788.1638.87661232.9286723.637.0848
FIMD821.13850207.64842.1558600.60122128.52840.3498437.2812413.48838.8195372.4527187.63236.9962
Tennis(1920x1080)源编码器1640.02421701.91442.93431406.96211439.06841.20961289.1946054.65539.27711217.773155.2737.1992
FIMD766.84922050.4942.9085570.16211716.01341.1720438.7176309.36239.199348.5373344.1637.0863
Cactus(1920x1080)源编码器1975.155110417.49641.15681629.26251486.07238.54051431.17828058.63236.4391318.33215229.64834.1816
FIMD1039.355110800.72841.1184774.06952266.79238.5097607.09728713.13636.3832497.02615688.1234.1004
RaceHorses(832x480)源编码器389.52717265.53342.6478345.0410667.00539.1022311.1806189.05335.6469278.7713132.92632.2599
FIMD190.70417430.98442.5846162.55710815.61939.0425135.8966326.86635.5389111.5623216.69132.1165
Keiba(832x480)源编码器356.61510541.96643.2681315.4216119.06440.2650284.9943486.39837.3744261.8031910.47234.5593
FIMD178.15110690.64643.2243147.6616243.16340.2169117.2623568.64637.277493.6151969.95834.4218
BQMall(832x480)源编码器387.92228932.34642.2937341.5817767.95839.2672308.33310668.79736.177282.1856023.46233.0579
FIMD198.5529168.13142.2452162.63317982.9639.2094135.0110833.64836.0983108.1586149.92332.9175
SlideShow(1280x720)源编码器573.3253927.04650.411550.4912471.25447.1421532.5941619.25444.0857517.1991057.58140.8910
FIMD160.6384032.05450.3539140.2442543.14947.061126.1811672.52543.9124106.0431106.69840.5339
ChinaSpeed(1024x768)源编码器271.45821774.37245.75248.35114846.8241.9913230.1949930.55238.421214.7066557.54434.9552
FIMD124.822218.645.674107.33215244.59241.886993.75410231.48838.277877.7336808.17634.6793
vidyo1(1280x720)源编码器274.43421062.49645.3337249.43612285.43243.1012233.7687422.9640.6631221.0854461.7237.9657
FIMD117.64121607.22445.281191.81812904.46443.006676.2527899.28840.522264.1874759.6837.7682
vidyo3(1280x720)源编码器273.99921925.65645.2769249.99713141.3243.0019234.0398145.33640.4839220.9714923.64837.6668
FIMD120.11622425.55245.229196.87513573.03242.934882.3778448.4840.378269.4065161.34437.5642
Mobisode2(416x240)源编码器25.326642.82847.533423.6365.26845.186322.607226.27242.879621.907151.6240.8236
FIMD9.65662.11247.41357.896382.29645.04866.66238.6242.69475.586164.97640.4996
RaceHorses(416x240)源编码器40.335070.85242.619736.233169.18838.681232.1831833.80435.021128.427996.99631.8403
FIMD20.8635105.742.567218.2643202.10438.619715.5171854.0634.94312.5611013.26831.7339
BasketballPass(416x240)源编码器36.2545266.0643.272531.9263098.8639.798528.4821733.3236.555925.764948.9833.6288
FIMD18.7285332.8443.214415.0483151.6239.713811.931778.636.46079.865967.833.5072
Flowervase(416x240)源编码器27.1021161.648.382825.495738.68444.823824.198447.1841.397822.993251.83238.0865
FIMD9.5481177.54848.27927.851748.65644.69996.674452.84441.22015.366255.01237.8065
BQSquare(416x240)源编码器43.07612828.74442.199438.1118413.51238.11733.9825375.59234.549230.6863342.1231.0505
FIMD23.64112891.28842.124420.2288464.29638.042917.1325401.9234.473114.6323366.16830.9767
  1. 与源编码器对比:
视频序列使用的算法QP22QP27QP32QP37
时间减少(%)比特率上升(%)PSNR降低(dB)时间减少(%)比特率上升(%)PSNR降低(dB)时间减少(%)比特率上升(%)PSNR降低(dB)时间减少(%)比特率上升(%)PSNR降低(dB)
PeopleOnStreet(2560x1600)FIMD51.38%1.27%0.049554.70%1.16%0.056859.21%3.27%0.087663.78%4.69%0.1261
Traffic(2560x1600)52.13%1.55%0.044157.30%3.14%0.078462.14%4.21%0.120267.52%4.87%0.1823
BasketballDrive(1902x1080)52.44%1.01%0.025758.26%2.57%0.034166.47%5.30%0.057169.79%6.90%0.0886
Tennis(1920x1080)53.24%1.61%0.025859.48%2.42%0.037665.97%4.21%0.078171.38%5.99%0.1129
Cactus(1920x1080)47.38%0.35%0.038452.49%1.52%0.030857.58%2.33%0.055862.30%3.01%0.0812
RaceHorses(832x480)51.04%0.96%0.063252.89%1.39%0.059756.33%2.23%0.108059.98%2.67%0.1434
Keiba(832x480)50.04%1.41%0.043853.19%2.03%0.048158.85%2.36%0.097064.24%3.11%0.1375
BQMall(832x480)48.82%0.81%0.048552.39%1.21%0.057856.21%1.55%0.078761.67%2.10%0.1404
SlideShow(1280x720)71.98%2.67%0.057174.52%2.91%0.081176.31%3.29%0.173379.50%4.64%0.3571
ChinaSpeed(1024x768)54.03%2.04%0.076056.78%2.68%0.104459.27%3.03%0.143263.80%3.82%0.2759
vidyo1(1280x720)57.13%2.59%0.052663.19%5.04%0.094667.38%6.42%0.140970.97%6.68%0.1975
vidyo3(1280x720)56.16%2.28%0.047861.25%3.29%0.067164.80%3.72%0.105768.59%4.83%0.1026
Mobisode2(416x240)61.90%3.00%0.119966.54%4.66%0.137770.54%5.46%0.184974.50%8.81%0.3240
RaceHorses(416x240)48.27%0.69%0.052549.59%1.04%0.061551.79%1.10%0.078155.81%1.63%0.1064
BasketballPass(416x240)48.34%1.27%0.058152.87%1.70%0.084758.11%2.61%0.095261.71%1.98%0.1216
Flowervase(416x240)64.77%1.37%0.103669.21%1.35%0.123972.42%1.27%0.177776.66%1.26%0.2800
BQSquare(416x240)45.12%0.49%0.075046.92%0.60%0.074149.59%0.49%0.076152.32%0.72%0.0738

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

四、 总结

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

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

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值