VVC帧内预测参考像素获取及滤波--帧内编码学习(三)
在进行角度预测之前,需要先得到参考像素以及参考像素滤波,完成这个功能的函数就是:
initIntraPatternChType( cu, pu.Y(), IntraPrediction::useFilteredIntraRefSamples( COMPONENT_Y, pu, false, pu ) );//useFilteredIntraRefSamples用于判断是否需要滤波
其中
IntraPrediction::useFilteredIntraRefSamples( COMPONENT_Y, pu, false, pu )
用于判断是否满足参考像素滤波的条件。
initIntraPatternChType中参考像素的获取由
xFillReferenceSamples( cs.picture->getRecoBuf( area ), refBufUnfiltered, area, cu );//得到未滤波的参考像素
完成。 参考像素滤波由
xFilterReferenceSamples( refBufUnfiltered, refBufFiltered, area, *cs.sps
, cu.firstPU->multiRefIdx
);//参考像素滤波
完成。
1、参考像素获取,如下图是JVET-M1002中MRL的示意图。
1)首先按照单元的大小(通常为4,ISP下可以小于4),判断Segment D、E和B中各个部分的参考单元是否可用。
2)均不可用,填充DC值。
3)可用,按照顺序填充即可。
4)部分可用。先将可用的单元进行填充,再以近邻像素填充不可用参考像素。
5)SegmentA和F的参考像素直接由B和E最近像素填充
xFillReferenceSamples函数具体注释如下
void IntraPrediction::xFillReferenceSamples( const CPelBuf &recoBuf, Pel* refBufUnfiltered, const CompArea &area, const CodingUnit &cu )
{
const ChannelType chType = toChannelType( area.compID );
const CodingStructure &cs = *cu.cs;
const SPS &sps = *cs.sps;
const PreCalcValues &pcv = *cs.pcv;
const int multiRefIdx = (area.compID == COMPONENT_Y) ? cu.firstPU->multiRefIdx : 0;//MRL索引
const int tuWidth = area.width;//
const int tuHeight = area.height;
const int predSize = m_topRefLength;
const int predHSize = m_leftRefLength;
#if JVET_M0102_INTRA_SUBPARTITIONS
const int cuWidth = cu.blocks[area.compID].width;
const int cuHeight = cu.blocks[area.compID].height;
const int whRatio = cu.ispMode && isLuma(area.compID) ? std::max(1, cuWidth / cuHeight) : std::max(1, tuWidth / tuHeight);
const int hwRatio = cu.ispMode && isLuma(area.compID) ? std::max(1, cuHeight / cuWidth) : std::max(1, tuHeight / tuWidth);
#else
int whRatio = std::max(1, tuWidth / tuHeight);
int hwRatio = std::max(1, tuHeight / tuWidth);
#endif
const int predStride = predSize + 1 + (whRatio + 1) * multiRefIdx;
const bool noShift = pcv.noChroma2x2 && area.width == 4; // don't shift on the lowest level (chroma not-split)
#if JVET_M0102_INTRA_SUBPARTITIONS
const int unitWidth = tuWidth <= 2 && cu.ispMode && isLuma(area.compID) ? tuWidth : pcv.minCUWidth >> (noShift ? 0 : getComponentScaleX(area.compID, sps.getChromaFormatIdc()));
const int unitHeight = tuHeight <= 2 && cu.ispMode && isLuma(area.compID) ? tuHeight : pcv.minCUHeight >> (noShift ? 0 : getComponentScaleY(area.compID, sps.getChromaFormatIdc()));
#else
const int unitWidth = pcv.minCUWidth >> (noShift ? 0 : getComponentScaleX( area.compID, sps.getChromaFormatIdc() ));
const int unitHeight = pcv.minCUHeight >> (noShift ? 0 : getComponentScaleY( area.compID, sps.getChromaFormatIdc() ));
#endif
const int totalAboveUnits = (predSize + (unitWidth - 1)) / unitWidth;//上边所需参考单元数
const int totalLeftUnits = (predHSize + (unitHeight - 1)) / unitHeight;//左边所需参考单元
const int totalUnits = totalAboveUnits + totalLeftUnits + 1; //+1 for top-left
const int numAboveUnits = std::max<int>( tuWidth / unitWidth, 1 );//tu上边单元数
const int numLeftUnits = std::max<int>( tuHeight / unitHeight, 1 );//tu左边单元数
const int numAboveRightUnits = totalAboveUnits - numAboveUnits;//tu右上单元数
const int numLeftBelowUnits = totalLeftUnits - numLeftUnits;//tu左下单元数
CHECK( numAboveUnits <= 0 || numLeftUnits <= 0 || numAboveRightUnits <= 0 || numLeftBelowUnits <= 0, "Size not supported" );
// ----- Step 1: analyze neighborhood -----
//相邻块是否可用
const Position posLT = area;//TU左上坐标
const Position posRT = area.topRight();//TU右上坐标
const Position posLB = area.bottomLeft();//TU左下坐标
bool neighborFlags[4 * MAX_NUM_PART_IDXS_IN_CTU_WIDTH + 1];//相邻单元可用标记,通常大小为4*4,isp模式下可能为1或2
int numIntraNeighbor = 0;//相邻单元可用数量
memset( neighborFlags, 0, totalUnits );//初始化为均不可用
neighborFlags[totalLeftUnits] = isAboveLeftAvailable( cu, chType, posLT );//左上是否可用
numIntraNeighbor += neighborFlags[totalLeftUnits] ? 1 : 0;
numIntraNeighbor += isAboveAvailable ( cu, chType, posLT, numAboveUnits, unitWidth, (neighborFlags + totalLeftUnits + 1) );//上
numIntraNeighbor += isAboveRightAvailable( cu, chType, posRT, numAboveRightUnits, unitWidth, (neighborFlags + totalLeftUnits + 1 + numAboveUnits) );//右上
numIntraNeighbor += isLeftAvailable ( cu, chType, posLT, numLeftUnits, unitHeight, (neighborFlags + totalLeftUnits - 1) );//左
numIntraNeighbor += isBelowLeftAvailable ( cu, chType, posLB, numLeftBelowUnits, unitHeight, (neighborFlags + totalLeftUnits - 1 - numLeftUnits) );//左下
// ----- Step 2: fill reference samples (depending on neighborhood) -----
//参考样本填充
CHECK((predHSize + 1) * predStride > m_iYuvExtSize, "Reference sample area not supported");
const Pel* srcBuf = recoBuf.buf;
const int srcStride = recoBuf.stride;
Pel* ptrDst = refBufUnfiltered;
const Pel* ptrSrc;
const Pel valueDC = 1 << (sps.getBitDepth( chType ) - 1);
if( numIntraNeighbor == 0 )//无可用参考
{
// Fill border with DC value
for (int j = 0; j <= predSize + multiRefIdx; j++) { ptrDst[j] = valueDC; }//用DC值填充上参考
for (int i = 1; i <= predHSize + multiRefIdx; i++) { ptrDst[i*predStride] = valueDC; }//用DC值填充左参考
}
else if( numIntraNeighbor == totalUnits )//所以参考均可
{
// Fill top-left border and top and top right with rec. samples
ptrSrc = srcBuf - (1 + multiRefIdx) * srcStride - (1 + multiRefIdx);//指向左上角
for (int j = 0; j <= predSize + multiRefIdx; j++) { ptrDst[j] = ptrSrc[j]; }//填充上参考
ptrSrc = srcBuf - multiRefIdx * srcStride - (1 + multiRefIdx);//指向左
for (int i = 1; i <= predHSize + multiRefIdx; i++) { ptrDst[i*predStride] = *(ptrSrc); ptrSrc += srcStride; }//填充左参考
}
else // reference samples are partially available//部分可用
{
// Fill top-left sample(s) if available
ptrSrc = srcBuf - (1 + multiRefIdx) * srcStride - (1 + multiRefIdx);
ptrDst = refBufUnfiltered;
if (neighborFlags[totalLeftUnits])//左上可用
{
ptrDst[0] = ptrSrc[0];//最左上
for (int i = 1; i <= multiRefIdx; i++)
{
ptrDst[i] = ptrSrc[i];//MRL左上-行
ptrDst[i*predStride] = ptrSrc[i*srcStride];//MRL左上-列
}
}
// Fill left & below-left samples if available (downwards)//左和左下
ptrSrc += (1 + multiRefIdx) * srcStride;//指向原始左
ptrDst += (1 + multiRefIdx) * predStride;//指向参考左
for (int unitIdx = totalLeftUnits - 1; unitIdx > 0; unitIdx--)//逐个扫描左边参考单元--从上到下
{
if (neighborFlags[unitIdx])//该参考可用
{
for (int i = 0; i < unitHeight; i++)
{
ptrDst[i*predStride] = ptrSrc[i*srcStride];//填充对应参考
}
}
ptrSrc += unitHeight * srcStride;//指向下一单元
ptrDst += unitHeight * predStride;
}
// Fill last below-left sample(s)
if (neighborFlags[0])//最左下单元
{
int lastSample = (predHSize % unitHeight == 0) ? unitHeight : predHSize % unitHeight;
for (int i = 0; i < lastSample; i++)
{
ptrDst[i*predStride] = ptrSrc[i*srcStride];
}
}
// Fill above & above-right samples if available (left-to-right)//上和右上
ptrSrc = srcBuf - srcStride * (1 + multiRefIdx);//指向原始上
ptrDst = refBufUnfiltered + 1 + multiRefIdx;//指向目的上
for (int unitIdx = totalLeftUnits + 1; unitIdx < totalUnits - 1; unitIdx++)//从左往右扫描可用单元
{
if (neighborFlags[unitIdx])//
{
for (int j = 0; j < unitWidth; j++)
{
ptrDst[j] = ptrSrc[j];
}
}
ptrSrc += unitWidth;
ptrDst += unitWidth;
}
// Fill last above-right sample(s)
if (neighborFlags[totalUnits - 1])//最右上
{
int lastSample = (predSize % unitWidth == 0) ? unitWidth : predSize % unitWidth;
for (int j = 0; j < lastSample; j++)
{
ptrDst[j] = ptrSrc[j];
}
}
// pad from first available down to the last below-left
ptrDst = refBufUnfiltered;
int lastAvailUnit = 0;//上一个可用单元
if (!neighborFlags[0])//最左下不可用
{
int firstAvailUnit = 1;
while (firstAvailUnit < totalUnits && !neighborFlags[firstAvailUnit])//寻找第一个可用单元
{
firstAvailUnit++;
}
// first available sample
int firstAvailRow = 0;
int firstAvailCol = 0;
if (firstAvailUnit < totalLeftUnits)//第一个可用单元在左边
{
firstAvailRow = (totalLeftUnits - firstAvailUnit) * unitHeight + multiRefIdx;//算出第一个可用单元行
}
else if (firstAvailUnit == totalLeftUnits)//第一个可用单元为左上
{
firstAvailRow = multiRefIdx;
}
else//否则在上边
{
firstAvailCol = (firstAvailUnit - totalLeftUnits - 1) * unitWidth + 1 + multiRefIdx;//算出第一个可用单元列
}
const Pel firstAvailSample = ptrDst[firstAvailCol + firstAvailRow * predStride];//第一个可用下或左样本
// last sample below-left (n.a.)
int lastRow = predHSize + multiRefIdx;//最后一行
// fill left column
for (int i = lastRow; i > firstAvailRow; i--)//
{
ptrDst[i*predStride] = firstAvailSample;//填充不存在的左参考列
}
// fill top row
if (firstAvailCol > 0)//列大于0说明前面均没有
{
for (int j = 0; j < firstAvailCol; j++)
{
ptrDst[j] = firstAvailSample;//填充上参考行
}
}
lastAvailUnit = firstAvailUnit;
}
// pad all other reference samples.
int currUnit = lastAvailUnit + 1;
while (currUnit < totalUnits)//遍历每一个单元
{
if (!neighborFlags[currUnit]) // samples not available//当前单元不可用
{
// last available sample//上一次可使用单元右或上样本
int lastAvailRow = 0;
int lastAvailCol = 0;
if (lastAvailUnit < totalLeftUnits)//左边填充未结束
{
lastAvailRow = (totalLeftUnits - lastAvailUnit - 1) * unitHeight + multiRefIdx + 1;
}
else if (lastAvailUnit == totalLeftUnits)
{
lastAvailCol = multiRefIdx;
}
else
{
lastAvailCol = (lastAvailUnit - totalLeftUnits) * unitWidth + multiRefIdx;
}
const Pel lastAvailSample = ptrDst[lastAvailCol + lastAvailRow * predStride];
// fill current unit with last available sample
if (currUnit < totalLeftUnits)//填充当前单元--左侧
{
for (int i = lastAvailRow - 1; i >= lastAvailRow - unitHeight; i--)
{
ptrDst[i*predStride] = lastAvailSample;
}
}
else if (currUnit == totalLeftUnits)//填充当前单元--左上角
{
for (int i = 1; i < multiRefIdx + 1; i++)
{
ptrDst[i*predStride] = lastAvailSample;//列
}
for (int j = 0; j < multiRefIdx + 1; j++)
{
ptrDst[j] = lastAvailSample;//行
}
}
else//填充当前单元--右侧
{
int numSamplesInUnit = (currUnit == totalUnits - 1) ? ((predSize % unitWidth == 0) ? unitWidth : predSize % unitWidth) : unitWidth;//最后一个单元防止溢出
for (int j = lastAvailCol + 1; j <= lastAvailCol + numSamplesInUnit; j++)
{
ptrDst[j] = lastAvailSample;
}
}
}
lastAvailUnit = currUnit;
currUnit++;
}
}
// padding of extended samples above right with the last sample
int lastSample = multiRefIdx + predSize;
for (int j = 1; j <= whRatio * multiRefIdx; j++) { ptrDst[lastSample + j] = ptrDst[lastSample]; }//MRL上参考扩展
// padding of extended samples below left with the last sample
lastSample = multiRefIdx + predHSize;
for (int i = 1; i <= hwRatio * multiRefIdx; i++) { ptrDst[(lastSample + i)*predStride] = ptrDst[lastSample*predStride]; }//MRL左参考扩展
}
2、参考像素滤波
相当于参考像素从左下到右上排成一列,采用[1 2 1]的平滑滤波系数对参考像素进行滤波,最边缘不进行处理。
void IntraPrediction::xFilterReferenceSamples( const Pel* refBufUnfiltered, Pel* refBufFiltered, const CompArea &area, const SPS &sps
, int multiRefIdx
)
{
if (area.compID != COMPONENT_Y)
{
multiRefIdx = 0;
}
int whRatio = std::max(1, int(area.width / area.height));
int hwRatio = std::max(1, int(area.height / area.width));
const int predSize = m_topRefLength + (whRatio + 1) * multiRefIdx;
const int predHSize = m_leftRefLength + (hwRatio + 1) * multiRefIdx;
const int predStride = predSize + 1;
#if HEVC_USE_INTRA_SMOOTHING_T32 || HEVC_USE_INTRA_SMOOTHING_T64
// Strong intra smoothing
ChannelType chType = toChannelType( area.compID );
if( sps.getUseStrongIntraSmoothing() && isLuma( chType ) )
{
const Pel bottomLeft = refBufUnfiltered[predStride * predHSize];
const Pel topLeft = refBufUnfiltered[0];
const Pel topRight = refBufUnfiltered[predSize];
const int threshold = 1 << (sps.getBitDepth( chType ) - 5);
const bool bilinearLeft = abs( (bottomLeft + topLeft) - (2 * refBufUnfiltered[predStride * tuHeight]) ) < threshold; //difference between the
const bool bilinearAbove = abs( (topLeft + topRight) - (2 * refBufUnfiltered[ tuWidth ]) ) < threshold; //ends and the middle
if( tuWidth >= 32 && tuHeight >= 32 && bilinearLeft && bilinearAbove )
#if !HEVC_USE_INTRA_SMOOTHING_T32
if( tuWidth > 32 && tuHeight > 32 )
#endif
#if !HEVC_USE_INTRA_SMOOTHING_T64
if( tuWidth < 64 && tuHeight < 64 )
#endif
{
Pel *piDestPtr = refBufFiltered + (predStride * predHSize); // bottom left
// apply strong intra smoothing
for (int i = 0; i < predHSize; i++, piDestPtr -= predStride) //left column (bottom to top)
{
*piDestPtr = (((predHSize - i) * bottomLeft) + (i * topLeft) + predHSize / 2) / predHSize;
}
for( uint32_t i = 0; i <= predSize; i++, piDestPtr++ ) //full top row (left-to-right)
{
*piDestPtr = (((predSize - i) * topLeft) + (i * topRight) + predSize / 2) / predSize;
}
return;
}
}
#endif
// Regular reference sample filter
const Pel *piSrcPtr = refBufUnfiltered + (predStride * predHSize); // bottom left
Pel *piDestPtr = refBufFiltered + (predStride * predHSize); // bottom left
// bottom left (not filtered)
*piDestPtr = *piSrcPtr;//左下不滤波
piDestPtr -= predStride;//目标指针上移
piSrcPtr -= predStride;//原始指针上移
//left column (bottom to top)
for( int i = 1; i < predHSize; i++, piDestPtr -= predStride, piSrcPtr -= predStride)
{
*piDestPtr = (piSrcPtr[predStride] + 2 * piSrcPtr[0] + piSrcPtr[-predStride] + 2) >> 2;//[1 2 1]平滑滤波
}
//top-left
*piDestPtr = (piSrcPtr[predStride] + 2 * piSrcPtr[0] + piSrcPtr[1] + 2) >> 2;//左上角参考像素滤波
piDestPtr++;//指向上参考,从左到右第一个像素
piSrcPtr++;
//top row (left-to-right)
for( uint32_t i=1; i < predSize; i++, piDestPtr++, piSrcPtr++ )
{
*piDestPtr = (piSrcPtr[1] + 2 * piSrcPtr[0] + piSrcPtr[-1] + 2) >> 2;//上边参考像素滤波
}
// top right (not filtered)
*piDestPtr=*piSrcPtr;
}