在大致了解了 HEVC 编码标准后,这段时间我也开始尝试在 HM-16.22 中复现一些算法。这篇博客主要用于记录进一个月来的工作情况。
复现的论文标题为 Fast Intra Mode Decision for High Efficiency Video Coding,作者为 Hao Zhang 和 Zhan Ma。论文下载链接: 这个
这是一种由大步长开始,渐渐缩小
首先需要引入一个定义:模式距离。除了 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 ∣i−j∣=d2≤i,j≤34
例如,与模式 18 距离为 3 的模式为 15 和 21;与 模式 34 相距为 1 的模式为 33;与模式 3 相距为 2 的模式为 5(模式 1 被排除在外)
在了解了这个定义之后,该算法的流程如下:
以下是一个演示例子:
算法分析:
该算法相较于挨个遍历而言,能减少遍历的模式数,从而降低时间时间开销。但是由于没有逐个遍历,则有可能陷入局部最优解,导致最终选择到的模式并不是全局的最优的模式,使得码率上升。
这里有一个模式“近邻”的概念,指的是模式距离小于等于 2 的两个模式。
我们从上一步得到了 M 种候选模式。默认的编码器是将这 M 种模式一一进行 RDCost 的计算。这个算法是在此之前,先对 M 种候选模式进行一个过滤,减少后续进行 RDCost 计算的模式数,从而降低编码时间开销。
该算法的流程如下:
该算法可行的依据:
传统 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=1∑KJd+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 两个函数。这两个函数的讲解可以参考以下两篇博客:
#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 不存在。
#if (PAPER_INTER_SHENLIQUAN_2) || (PAPER_INTER_SHENLIQUAN) || (PAPER_INTRA_ZHANGHAO)
TComDataCU *pcLCU = pcPic->getCtu(rpcTempCU->getCtuRsAddr());
#endif
这个是用来获取 LCU 的。
// 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
这段代码主要是给一些变量赋值,方便后续计算。
这里我做了一个小变化,将原来公式中的系数写成了倒数的形式。这样做的目的是将除法运算转换成乘法运算,能够稍微提升执行的速度。
for ( UInt uiPartUnitIdx = 0; uiPartUnitIdx < 4; uiPartUnitIdx++ )
{
//以上是源码,定位使用
#if PAPER_INTRA_ZHANGHAO
cost1 += pcLCU->subHadCost[uiDepth][uiPartUnitIdx];
#endif
#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 都是用来保存和更新跟提前终止划分相关的变量
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
#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 更小,同时很多参数也会缺失。因此此处应避免二者进行比较。
#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 返回就好了)。
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
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
这段代码同样是初始化使用的
#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实现的地方了。注意需要把原来的代码全部注释掉,因为算法已经变了。
//===== 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
用于给一些变量赋值,以及初始化
#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 循环。
#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
编译,即可运动。
视频序列 | 使用的算法 | 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 |
视频序列 | 使用的算法 | 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% 然后提不上去了,就搁置了一段时间看另外一篇去了。现在另一篇复现出来,再回过头来看,也就有了一定的思路,然后就顺利地实现了文献中的算法。
回顾这两三个月的时间吧,感觉在学习和调试过程中虽然十分痛苦,但最后做出了自己想要的成果,还是挺开心的。接下去继续努力吧 ^__^