【十四】 H.266/VVC | 帧间预测技术 | 三角形划分模式(TPM)
基本原理
在当VVC中,三角预测模式是一种Merge模式,当使用TMP技术时,一个CU被均匀地分割成为两个三角形区域,使用对角分割或者反对角分割,如下图所示,每个三角分区都只允许单向预测,因此每个分区都必须分配一个单向的预测MV以及对应的参考帧索引,两个分区根据各自分区的单向预测MV,运动补偿出各自分区的预测值,利用CU中各个三角形分区的预测值,在三角形分割线(对角线或者反对角线)的周围通过自适应的权值加权融合出新的预测值,同时保持各自分区的预测值不变,最终得到整个CU的三角预测值;这里需要注意的是,三角预测模式的运动场是以4 * 4的单元为基础存储的,也就是说将整个CU划分为一个接着一个的4 * 4,取每个小块的中心位置作为整个小块的运动向量
- 如果当前CU使用三角划分模式,则需要传输一个标志位表示划分的方向(对角线或者反对角线方向)和两个merge索引(每个部分各一个)
- 每个部分都完成预测之后,对角线或者反对角线上的预测值由两部分的自适应加权得到,得到完整的预测CU之后,在整个CU上进行变换和量化操作而不是分开进行处理的
- 三角预测模式是支持三角形划分模式的帧间预测技术,TMP只应用于大于8 * 8编码块
使用了三角形预测模式的CU的预测值可以分为两个部分
- 非加权区:分割线周围以外的预测区,包括分区一的单向预测值和分区二的单向预测值
- 加权区:分割线周围一定区域内的预测值是根据两个分区的单向预测值根据不同的权重加权得到的(可以把分割线周围的区域看成是双向MV预测区域)
单向预测候选列表的构建
对于三角划分的每一个区域,其单向候选列表的建立是由扩展的merge模式中得到。假设n是单向候选列表中的第n个候选项的索引值。利用第n个扩展Merge候选项的LX运动矢量作为三角形划分模式的第n个单向预测运动矢量,其中X=n的奇偶性用来判断选择前向MV还是后向MV,若为偶数,则选择前向候选MV;若为奇数,则选择后向候选MV。这些选中的MV在下表中用“x”标记。如果扩展Merge候选项n对应的LX运动矢量不存在则用L(1-X)MV代替三角形划分模式的单向预测运动向量
注意点:
- 得到候选列表后,两个三角形分区的单向MV以及两种划分方式总共有6 * 5 * 2= 60中组合方式
- 使用三个参数【分割方式、分区一预测块的MV、分区二预测块的MV】和权重来对边界以及这个整个CU进行运动补偿
对角线上预测值的生成
在得到各自三角分区的单向预测值之后,需要沿着三角形分区的边缘进行加权预测
加权预测过程中使用到的权重如下:
- 对于亮度分量的预测权重为:{7/8,6/8,5/8,4/8,3/8,2/8,1/8}
- 对于色度分量的预测权重为:{6/8,4/8,2/8}
如下图所示,中间灰色区域代表加权区域,两边黄色区域以及粉色区域代表非加权区域:
- 其中亮度分量每行有7个像素需要加权,色度分量每行有3个像素需要加权
- 需要加权的像素可能不在块内,加权时需要去掉这部分像素
预测区MV的存储方式
以三角形划分模式编码的CU的运动向量存储在4 * 4单元中,即每个4 * 4的单元共用中心位置的MV。根据每个4 * 4单元的位置,存储单向预测或者双向预测MV。假设MV1和MV2分别表示分区1和分区2的单向预测MV。如果4 * 4区域位于非加权区域,则为该4 * 4区域存储MV1或者MV2,如果位于加权区域,则存储一个双向预测MV。
代码实现算法的流程:
- 首先进行一些初始化的操作,定义一些变量、存放预测值以及残差的buffer、RD代价列表等
- 定义三角形Merge单向预测列表,它是从常规的Merge列表中派生出来的,然后调用getTriangleMergeCandidates()函数,获取三角形预测的单向Merge候选列表
- 遍历6个三角Merge候选,运动补偿出每个候选的单向预测值
- 遍历60种组合模式,进行亮度分量的SAD,进行粗选操作,通过调用weightedTriangleBlk()函数计算亮度分量下的每种三角组合下的加权预测值,每遍历一个组合模式,需要通过调用updateCandList()函数更新三角模式的RD候选模式列表TriangleRdModeList以及三角代价列表triglecandCostList.遍历更新完之后,triangleRdModeList列表中的候选的Cost值是从小到大排序的,RD列表的最大长度为3,也就是从60中组合中RDCost选出3个较优的的候选
- 使用SATD值限制候选列表的数量,在上一步SAD粗选完之后对RD候选列表的容量进一步进行限制,减少RD候选列表的范围,在3个候选中再做缩减,可以缩减为0,1,2,3中的任何值
- 对那些经过SAD以及进一步数量限制筛选出的较优的候选进行遍历,逐个计算出相应色度分量点的加权预测值。需要注意的是,在使用亮度分量进行筛选,筛选结束之后再去对相应的色的进行加权
- 开始正式的HAD_Cost的细选过程,遍历需要进行SATD细选的列表中的每一个候选,进行SATD值的比较,得到Cost值最小的那个候选作为最优的三角模式。然后再去与其余的Merge模式去进行率失真比较,选出最优的一种三角组合模式,再在外层与其他的Merge模式一起去竞争。这里会调用xEncodeInterResidual()函数进行帧间残差编码
详细代码实现:
#if !JVET_Q0806
void EncCu::xCheckRDCostMergeTriangle2Nx2N( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner, const EncTestMode& encTestMode )
{
const Slice &slice = *tempCS->slice;
const SPS &sps = *tempCS->sps;
if (slice.getPicHeader()->getMaxNumTriangleCand() < 2)
return;
CHECK( slice.getSliceType() != B_SLICE, "Triangle mode is only applied to B-slices" );
tempCS->initStructData( encTestMode.qp ); //初始化数据结构
bool trianglecandHasNoResidual[TRIANGLE_MAX_NUM_CANDS];//60个三角预测候选是否有残差
for( int mergeCand = 0; mergeCand < TRIANGLE_MAX_NUM_CANDS; mergeCand++ )
{
trianglecandHasNoResidual[mergeCand] = false; //都初始化为false
}
bool bestIsSkip = false;
uint8_t numTriangleCandidate = TRIANGLE_MAX_NUM_CANDS; //三角候选数量有60种
uint8_t triangleNumMrgSATDCand = TRIANGLE_MAX_NUM_SATD_CANDS; //三角预测用于SATD比较的最大候选数量为3
PelUnitBuf triangleBuffer[TRIANGLE_MAX_NUM_UNI_CANDS];//三角形的缓存单元最大数量为6,三角预测的单向Merge列表的长度为6
PelUnitBuf triangleWeightedBuffer[TRIANGLE_MAX_NUM_CANDS]; //三角加权预测值的缓存为60个
static_vector<uint8_t, TRIANGLE_MAX_NUM_CANDS> triangleRdModeList; //三角形预测的RD的模式列表,首选粗选出的组合候选放入到最终的RDCost候选列表中
static_vector<double, TRIANGLE_MAX_NUM_CANDS> tianglecandCostList; //三角形预测的候选代价列表,首选粗选选出的组合候选的代价放入到最终的代价列表中
uint8_t numTriangleCandComb = slice.getPicHeader()->getMaxNumTriangleCand() * (slice.getPicHeader()->getMaxNumTriangleCand() - 1) * 2; //三角预测模式的组合数量:6*5*2=60
DistParam distParam;
const bool useHadamard = !tempCS->slice->getDisableSATDForRD();
m_pcRdCost->setDistParam( distParam, tempCS->getOrgBuf().Y(), m_acMergeBuffer[0].Y(), sps.getBitDepth( CHANNEL_TYPE_LUMA ), COMPONENT_Y, useHadamard );
const UnitArea localUnitArea( tempCS->area.chromaFormat, Area( 0, 0, tempCS->area.Y().width, tempCS->area.Y().height) ); //开辟编码CU的空间
const double sqrtLambdaForFirstPass = m_pcRdCost->getMotionLambda( );
MergeCtx triangleMrgCtx; //定义三角Merge单向预测列表,是从常规的Merge列表中派生出来
{
CodingUnit cu( tempCS->area );
cu.cs = tempCS;
cu.predMode = MODE_INTER; //预测模式为帧间模式
cu.slice = tempCS->slice; //指向当前的Slice
cu.tileIdx = tempCS->pps->getTileIdx( tempCS->area.lumaPos() );
cu.triangle = true; //是三角预测模式
cu.mmvdSkip = false; //不是MMVDSkip模式
cu.BcwIdx = BCW_DEFAULT; //双向预测权重索引
PredictionUnit pu( tempCS->area ); //为当前CU开辟临时缓存空间
pu.cu = &cu;
pu.cs = tempCS;
pu.regularMergeFlag = false; //不是regular模式
//获取三角形预测的单向Merge候选列表
PU::getTriangleMergeCandidates( pu, triangleMrgCtx );
const uint8_t maxNumTriangleCand = pu.cs->picHeader->getMaxNumTriangleCand(); //三角预测的最大候选数
//遍历6个三角Merge候选
for (uint8_t mergeCand = 0; mergeCand < maxNumTriangleCand; mergeCand++)
{
triangleBuffer[mergeCand] = m_acMergeBuffer[mergeCand].getBuf(localUnitArea);
triangleMrgCtx.setMergeInfo( pu, mergeCand ); //设置三角Merge候选的初始信息
PU::spanMotionInfo( pu, triangleMrgCtx );
if( m_pcEncCfg->getMCTSEncConstraint() && ( !( MCTSHelper::checkMvBufferForMCTSConstraint( pu ) ) ) )
{
// Do not use this mode
tempCS->initStructData( encTestMode.qp ); //初始化数据结构
return;
}
m_pcInterSearch->motionCompensation( pu, triangleBuffer[mergeCand] ); //运动补偿获得每个候选的单向预测值
}
}
//需要进行SATDCost比较的三角Merge候选的数量
triangleNumMrgSATDCand = min(triangleNumMrgSATDCand, numTriangleCandComb);
{
CodingUnit &cu = tempCS->addCU( tempCS->area, partitioner.chType ); //定义CU
partitioner.setCUData( cu ); //初始化CU
cu.slice = tempCS->slice; //定义片的临时缓存
cu.tileIdx = tempCS->pps->getTileIdx( tempCS->area.lumaPos() );
cu.skip = false;
cu.predMode = MODE_INTER; //帧间模式
cu.chromaQpAdj = m_cuChromaQpOffsetIdxPlus1;
cu.qp = encTestMode.qp;
cu.triangle = true; //三角模式为真
cu.mmvdSkip = false; //不是Skip模式
cu.BcwIdx = BCW_DEFAULT; //双向预测权重索引为默认值
PredictionUnit &pu = tempCS->addPU( cu, partitioner.chType );
if( abs(floorLog2(cu.lwidth()) - floorLog2(cu.lheight())) >= 2 ) //如果当前CU的宽高比大于或等于2,则只有一种划分方向,共30种
{
numTriangleCandidate = 30;
}
else
{
//否则最大候选的数量为60
numTriangleCandidate = TRIANGLE_MAX_NUM_CANDS;
}
//从三角候选数量和三角的结合候选数量中选择最小的那一个
numTriangleCandidate = min(numTriangleCandidate, numTriangleCandComb);
//遍历60中候选,进行SAD-Cost,进行粗选操作
for( uint8_t mergeCand = 0; mergeCand < numTriangleCandidate; mergeCand++ )
{
bool splitDir = m_triangleModeTest[mergeCand].m_splitDir; //获取三角的划分方向
uint8_t candIdx0 = m_triangleModeTest[mergeCand].m_candIdx0; //获取第一个三角分区的候选索引,6个中选一个
uint8_t candIdx1 = m_triangleModeTest[mergeCand].m_candIdx1;//获取第二个三角形分区的候选索引,剩下的5个中选一个
//将上面三个变量传递个当前pu
pu.triangleSplitDir = splitDir;
pu.triangleMergeIdx0 = candIdx0;
pu.triangleMergeIdx1 = candIdx1;
pu.mergeFlag = true;//Merge模式可用
pu.regularMergeFlag = false;//常规Merge模式不可用
triangleWeightedBuffer[mergeCand] = m_acTriangleWeightedBuffer[mergeCand].getBuf( localUnitArea );//三角权重的临时缓存
//第一个分区的预测值的临时缓存
triangleBuffer[candIdx0] = m_acMergeBuffer[candIdx0].getBuf( localUnitArea );
//第二个分区的预测值的临时缓存
triangleBuffer[candIdx1] = m_acMergeBuffer[candIdx1].getBuf( localUnitArea );
//亮度分量的加权过程
m_pcInterSearch->weightedTriangleBlk( pu, splitDir, CHANNEL_TYPE_LUMA, triangleWeightedBuffer[mergeCand], triangleBuffer[candIdx0], triangleBuffer[candIdx1] );
//当前组合模式的三角亮度权重
distParam.cur = triangleWeightedBuffer[mergeCand].Y();
//计算当前组合模式失真的SAD
Distortion uiSad = distParam.distFunc( distParam );
//当前组合模式的总比特数(残差+头信息+语法元素)
uint32_t uiBitsCand = m_triangleIdxBins[splitDir][candIdx0][candIdx1];
//计算SAD代价
double cost = (double)uiSad + (double)uiBitsCand * sqrtLambdaForFirstPass;
//更新RD候选列表,将Cost较小的放入列表当中,遍历更新完之后,triangleRdModeList列表中的候选的Cost是从大到小进行排序的,RD列表的最大长度为3,也就是从60种组合方式中的RDSost中选出3个较优的候选
updateCandList( mergeCand, cost, triangleRdModeList, tianglecandCostList
, triangleNumMrgSATDCand );
}
// limit number of candidates using SATD-costs
//使用SATD-Cost限制候选列表的数量,在上一步SAD粗选完之后,对RD候选列表的容量做进一步的限制,可以缩减为0,1,2,3中的任何值
for( uint8_t i = 0; i < triangleNumMrgSATDCand; i++ )
{
//如果当前RD候选的Cost大于一定的阈值,则就跳出循环,将前面那些在Cost阈值范围之内的候选保存在列表当中,后面那些超出阈值的部分被排除在外
if( tianglecandCostList[i] > MRG_FAST_RATIO * tianglecandCostList[0] || tianglecandCostList[i] > getMergeBestSATDCost() )
{
triangleNumMrgSATDCand = i; //STAD候选的数量保留为那些在阈值之内的候选数量
break;
}
}
// perform chroma weighting process
//执行色度加权过程
//对那些经过SAD以及进一步数量筛选出的较优的候选进行遍历,用亮度分量进行筛选,筛选完之后再去对相应的色度进行加权
#if JVET_Q0438_MONOCHROME_BUGFIXES
if (isChromaEnabled(pu.chromaFormat))
{
#endif
for( uint8_t i = 0; i < triangleNumMrgSATDCand; i++ )
{
uint8_t mergeCand = triangleRdModeList[i]; //RDCost列表中的第几个候选
bool splitDir = m_triangleModeTest[mergeCand].m_splitDir; //划分方向
uint8_t candIdx0 = m_triangleModeTest[mergeCand].m_candIdx0; //分区一的候选
uint8_t candIdx1 = m_triangleModeTest[mergeCand].m_candIdx1; //分区2的候选
pu.triangleSplitDir = splitDir;
pu.triangleMergeIdx0 = candIdx0;
pu.triangleMergeIdx1 = candIdx1;
pu.mergeFlag = true; //标志Merge模式可用
pu.regularMergeFlag = false; //标志常规Merge模式不可用
//色度分量加权过程
m_pcInterSearch->weightedTriangleBlk( pu, splitDir, CHANNEL_TYPE_CHROMA, triangleWeightedBuffer[mergeCand], triangleBuffer[candIdx0], triangleBuffer[candIdx1] );
}
#if JVET_Q0438_MONOCHROME_BUGFIXES
}
#endif
//再初始化数据结果
tempCS->initStructData( encTestMode.qp );
}
//重新获取三角预测模式的数量,缩减之后的triangleNumMrgSATDCand列表的尺寸中取最小
triangleNumMrgSATDCand = min(triangleNumMrgSATDCand, (uint8_t)triangleRdModeList.size());
//是否是最优的Cost
m_bestModeUpdated = tempCS->useDbCost = bestCS->useDbCost = false;
//此处开始正式的HAD_Cost的细选过程
{
uint8_t iteration;
uint8_t iterationBegin = 0;//迭代从0开始
iteration = 2;//迭代两次
//外层的迭代次数
for (uint8_t noResidualPass = iterationBegin; noResidualPass < iteration; ++noResidualPass)
{
//遍历需要进行SATD细选的列表中的每一个候选,进行SATD_Cost的比较,得到Cost最小的那个候选作为最优的三角模式,然后再去与其余的Merge模式去进行率失真代价的比较
for( uint8_t mrgHADIdx = 0; mrgHADIdx < triangleNumMrgSATDCand; mrgHADIdx++ )
{
uint8_t mergeCand = triangleRdModeList[mrgHADIdx];
if ( ( (noResidualPass != 0) && trianglecandHasNoResidual[mergeCand] )
|| ( (noResidualPass == 0) && bestIsSkip ) )
{
continue;
}
bool splitDir = m_triangleModeTest[mergeCand].m_splitDir;
uint8_t candIdx0 = m_triangleModeTest[mergeCand].m_candIdx0;
uint8_t candIdx1 = m_triangleModeTest[mergeCand].m_candIdx1;
CodingUnit &cu = tempCS->addCU(tempCS->area, partitioner.chType);
partitioner.setCUData(cu); //设置CU的数据
cu.slice = tempCS->slice;
cu.tileIdx = tempCS->pps->getTileIdx( tempCS->area.lumaPos() );
cu.skip = false;
cu.predMode = MODE_INTER;
cu.chromaQpAdj = m_cuChromaQpOffsetIdxPlus1;
cu.qp = encTestMode.qp;
cu.triangle = true;
cu.mmvdSkip = false;
cu.BcwIdx = BCW_DEFAULT;
PredictionUnit &pu = tempCS->addPU(cu, partitioner.chType);
pu.triangleSplitDir = splitDir;
pu.triangleMergeIdx0 = candIdx0;
pu.triangleMergeIdx1 = candIdx1;
pu.mergeFlag = true;
pu.regularMergeFlag = false;
//重新设置三角预测的运动信息
PU::spanTriangleMotionInfo(pu, triangleMrgCtx, splitDir, candIdx0, candIdx1 );
if( m_pcEncCfg->getMCTSEncConstraint() && ( !( MCTSHelper::checkMvBufferForMCTSConstraint( *cu.firstPU ) ) ) )
{
// Do not use this mode
tempCS->initStructData( encTestMode.qp );
return;
}
//将之前得到的加权后的预测值直接拷贝过来,用于下面的残差编码(其中就进行了HAD的Cost的计算以及比较)
tempCS->getPredBuf().copyFrom( triangleWeightedBuffer[mergeCand] ); //得到三角预测的最终值
//编码帧间残差
xEncodeInterResidual( tempCS, bestCS, partitioner, encTestMode, noResidualPass, ( noResidualPass == 0 ? &trianglecandHasNoResidual[mergeCand] : NULL ) );
if (m_pcEncCfg->getUseFastDecisionForMerge() && !bestIsSkip)
{
bestIsSkip = bestCS->getCU(partitioner.chType)->rootCbf == 0;
}
tempCS->initStructData(encTestMode.qp);
}// end loop mrgHADIdx
}
}
if ( m_bestModeUpdated && bestCS->cost != MAX_DOUBLE )
{
//选出最优的一种三角组合模式,再与外层其他的Merge模式一起去竞争
xCalDebCost( *bestCS, partitioner );
}
}
更多关于视频编码知识和资源的分享,更精致的文章排版,欢迎关注博主微信公众号,一起交流、学习、进步!!!