VTM5中支持对于大于等于8x8的CU在进行帧间预测时进行三角划分( triangle partition mode) 。三角划分模式如下,将CU沿对角线或反对角线方向划分。
当CU使用三角划分模式时,CU被均匀的划分为两个部分,每个部分分别独立的进行帧间预测但是只能进行单向帧间预测,每个部分都有一个MV和一个参考图像。
如果当前CU使用三角划分模式,则需要传输一个标志位表示划分方向(对角线或反对角线)和两个merge索引(每部分各一个)。
当每部分都完成预测之后,对角线或反对角线上的预测值有两部分自适应加权得到。得到完整的预测CU后,在整个CU上进行变换和量化操作而不是分开处理。
三角划分模式不和SBT同时使用[2],当使用三角划分模式时不需要传输cu_sbt_flag 直接默认为0。
单向预测候选列表建立
对于三角划分的每个部分,其单向候选列表的建立由扩展的merge模式中候选列表得到。假设n是单向候选列表中第n个候选项的索引。单向候选列表中第n个候选项等于LX中第n个候选项,其中X等于n的奇偶位。如下图所示,第0个候选项等于L0的第0个候选项,第1个候选项等于L1的第1个候选项,第2个候选项等于L0的第2个候选项,第3个候选项等于L1的第3个候选项,第4个候选项等于L0的第4个候选项。如果LX的对应候选项不存在则用L(1-X)对应候选项代替。
对角线上预测值生成
当两个部分分别完成预测后,由于对角线上像素在每个部分都预测过一次,所以需要对对角线上的两个预测值进行加权得到最终预测值。
-
亮度分量权值:{7/8, 6/8, 5/8, 4/8, 3/8, 2/8, 1/8}
-
色度分量权值:{6/8, 4/8, 2/8}
其中亮度分量每行有7个像素需要加权,色度分量每行有3个像素需要加权,权值分别如上。注意:需要加权的像素可能不在块内,加权时需要去掉这部分像素。
亮度分量加权示意图如下:
相关代码如下:
void InterPrediction::xWeightedTriangleBlk( const PredictionUnit &pu, const uint32_t width, const uint32_t height, const ComponentID compIdx, const bool splitDir, PelUnitBuf& predDst, PelUnitBuf& predSrc0, PelUnitBuf& predSrc1 )
{
Pel* dst = predDst .get(compIdx).buf;
Pel* src0 = predSrc0.get(compIdx).buf;
Pel* src1 = predSrc1.get(compIdx).buf;
int32_t strideDst = predDst .get(compIdx).stride - width;
int32_t strideSrc0 = predSrc0.get(compIdx).stride - width;
int32_t strideSrc1 = predSrc1.get(compIdx).stride - width;
const char log2WeightBase = 3;
const ClpRng clipRng = pu.cu->slice->clpRngs().comp[compIdx];
const int32_t clipbd = clipRng.bd;
const int32_t shiftDefault = std::max<int>(2, (IF_INTERNAL_PREC - clipbd));
const int32_t offsetDefault = (1<<(shiftDefault-1)) + IF_INTERNAL_OFFS;
const int32_t shiftWeighted = std::max<int>(2, (IF_INTERNAL_PREC - clipbd)) + log2WeightBase;
const int32_t offsetWeighted = (1 << (shiftWeighted - 1)) + (IF_INTERNAL_OFFS << log2WeightBase);
const int32_t ratioWH = (width > height) ? (width / height) : 1;
const int32_t ratioHW = (width > height) ? 1 : (height / width);
#if JVET_N0671_INTRA_TPM_ALIGNWITH420
const bool longWeight = (compIdx == COMPONENT_Y);
#else
const bool longWeight = (compIdx == COMPONENT_Y) || ( predDst.chromaFormat == CHROMA_444 );
#endif
const int32_t weightedLength = longWeight ? 7 : 3; //!<每行要加权处理的长度
int32_t weightedStartPos = ( splitDir == 0 ) ? ( 0 - (weightedLength >> 1) * ratioWH ) : ( width - ((weightedLength + 1) >> 1) * ratioWH ); //!<可为负
int32_t weightedEndPos = weightedStartPos + weightedLength * ratioWH - 1;
int32_t weightedPosoffset =( splitDir == 0 ) ? ratioWH : -ratioWH;
Pel tmpPelWeighted;
int32_t weightIdx;
int32_t x, y, tmpX, tmpY, tmpWeightedStart, tmpWeightedEnd;
for( y = 0; y < height; y+= ratioHW )
{
for( tmpY = ratioHW; tmpY > 0; tmpY-- )
{
for( x = 0; x < weightedStartPos; x++ )
{//!<加权之前直接复制
*dst++ = ClipPel( rightShift( (splitDir == 0 ? *src1 : *src0) + offsetDefault, shiftDefault), clipRng );
src0++;
src1++;
}
tmpWeightedStart = std::max((int32_t)0, weightedStartPos);
tmpWeightedEnd = std::min(weightedEndPos, (int32_t)(width - 1));
weightIdx = 1;
if( weightedStartPos < 0 )
{//!<起始位置可能在块外部
weightIdx += abs(weightedStartPos) / ratioWH;
}
for( x = tmpWeightedStart; x <= tmpWeightedEnd; x+= ratioWH )
{
for( tmpX = ratioWH; tmpX > 0; tmpX-- )
{//!<加权处理
tmpPelWeighted = Clip3( 1, 7, longWeight ? weightIdx : (weightIdx * 2));
tmpPelWeighted = splitDir ? ( 8 - tmpPelWeighted ) : tmpPelWeighted;
*dst++ = ClipPel( rightShift( (tmpPelWeighted*(*src0++) + ((8 - tmpPelWeighted) * (*src1++)) + offsetWeighted), shiftWeighted ), clipRng );
}
weightIdx ++;
}
for( x = weightedEndPos + 1; x < width; x++ )
{//!<加权之后直接复制
*dst++ = ClipPel( rightShift( (splitDir == 0 ? *src0 : *src1) + offsetDefault, shiftDefault ), clipRng );
src0++;
src1++;
}
dst += strideDst;
src0 += strideSrc0;
src1 += strideSrc1;
}//!<下一行加权的起始位置和结束位置右移
weightedStartPos += weightedPosoffset;
weightedEndPos += weightedPosoffset;
}
}
参考
[1] JVET-N1002
[2] JVET-N0483
感兴趣的请关注微信公众号Video Coding