[HEVC] HEVC学习(三) —— 帧内预测系列之一
今天开始进入实质性内容的讨论,主要是从代码实现的角度比较深入地研究帧内预测算法。由于帧内预测涉及到的函数的数量相对于编解码器复杂部分来说少,但事实上大大小小也牵涉到了十几二十个函数(没具体统计过,只是大概估算了下),想要一下子讨论完比较困难,所以打算在接下来的若干篇文章里逐步地尽可能详尽地分析每一个较为重要的函数。今天所要讨论的是fillReferenceSamples这个函数,它主要功能是在真正进行帧内预测之前,使用重建后的Yuv图像对当前PU的相邻样点进行赋值,为接下来进行的角度预测提供参考样点值。
这个函数实际上实现的是官方当前标准(JCTVC-J1003)draft 8.4.4.2.2(Reference sample substitution process for intra sample prediction),具体内容我这里就不重复了,有兴趣的朋友可以自己下下来去看看,我先简单把该过程复述一遍:(1)如果所有相邻点均不可用,则参考样点值均被赋值为DC值;(2)如果所有相邻点均可用,则参考样点值都会被赋值为重建Yuv图像中与其位置相同的样点值;(3)如果不满足上述两个条件,则按照从左下往左上,从左上往右上的扫描顺序进行遍历,(如下图所示),
如果第一个点不可用,则使用下一个可用点对应的重建Yuv样点值对其进行赋值;对于除第一个点外的其它邻点,如果该点不可用,则使用它的前一个样点值进行赋值(前一个步骤保证了前一个样点值一定是存在的),直到遍历完毕。
在了解了算法的大致流程后,我们就可以看看代码是怎么具体实现的了。我主要把我对代码的注释连同代码一起贴出来,以供大家参考,有些地方理解上肯定还是有所欠缺的,望大家不吝赐教。
Void TComPattern::fillReferenceSamples( TComDataCU* pcCU, Pel* piRoiOrigin, Int* piAdiTemp, Bool* bNeighborFlags, Int iNumIntraNeighbor, Int iUnitSize, Int iNumUnitsInCu, Int iTotalUnits, UInt uiCuWidth, UInt uiCuHeight, UInt uiWidth, UInt uiHeight, Int iPicStride, Bool bLMmode )
{
Pel* piRoiTemp; //!< piRoiOrgin指向重建Yuv图像对应于当前PU所在位置的首地址,piRoiTemp用于指向所感兴趣的重建Yuv的位置,piAdiTemp
Int i, j;
Int iDCValue = ( 1<<( g_uiBitDepth + g_uiBitIncrement - 1) );
if (iNumIntraNeighbor == 0) // all samples are not available
{
// Fill border with DC value
for (i=0; i<uiWidth; i++) //!< AboveLeft + Above + AboveRight
{
piAdiTemp = iDCValue;
}
for (i=1; i<uiHeight; i++) //!< Left + BelowLeft
{
piAdiTemp[i*uiWidth] = iDCValue;
}
}
else if (iNumIntraNeighbor == iTotalUnits) // all samples are available
{
// Fill top-left border with rec. samples
piRoiTemp = piRoiOrigin - iPicStride - 1; //!< 左上
piAdiTemp[0] = piRoiTemp[0];
// Fill left border with rec. samples
piRoiTemp = piRoiOrigin - 1; //!< 左
if (bLMmode) //!< bLMmode 默认值为false
{
piRoiTemp --; // move to the second left column
}
for (i=0; i<uiCuHeight; i++)
{
piAdiTemp[(1+i)*uiWidth] = piRoiTemp[0]; //!< 每个参考样点赋值为对应位置重建Yuv样点值
piRoiTemp += iPicStride; //!< 指向重建Yuv下一行
}
// Fill below left border with rec. samples
for (i=0; i<uiCuHeight; i++) //!< 左下
{
piAdiTemp[(1+uiCuHeight+i)*uiWidth] = piRoiTemp[0]; //!< 每个参考样点赋值为对应位置重建Yuv样点值
piRoiTemp += iPicStride;
}
// Fill top border with rec. samples
piRoiTemp = piRoiOrigin - iPicStride; //!< 重新指向重建Yuv的上方
for (i=0; i<uiCuWidth; i++)
{
piAdiTemp[1+i] = piRoiTemp; //!< 每个参考样点赋值为对应位置重建Yuv样点值
}
// Fill top right border with rec. samples
piRoiTemp = piRoiOrigin - iPicStride + uiCuWidth; //!< 指向右上
for (i=0; i<uiCuWidth; i++)
{
piAdiTemp[1+uiCuWidth+i] = piRoiTemp; //!< 每个参考样点赋值为对应位置重建Yuv样点值
}
}
else // reference samples are partially available
{
Int iNumUnits2 = iNumUnitsInCu<<1;
Int iTotalSamples = iTotalUnits*iUnitSize; //!< neighboring samples的总数,iTotalUnits以4x4块为单位,iUnitSize为块的大小
Pel piAdiLine[5 * MAX_CU_SIZE];
Pel *piAdiLineTemp; //!<临时存储用于填充neighboring samples的样点值
Bool *pbNeighborFlags;
Int iNext, iCurr;
Pel piRef = 0; //!< 存储临时样点值
// Initialize
for (i=0; i<iTotalSamples; i++) //!< 先将所有样点值赋值为DC值
{
piAdiLine = iDCValue;
}
// Fill top-left sample
piRoiTemp = piRoiOrigin - iPicStride - 1; //!< 指向重建Yuv左上角
piAdiLineTemp = piAdiLine + (iNumUnits2*iUnitSize); //!< piAdiLine的扫描顺序为左下到左上,再从左到右上
pbNeighborFlags = bNeighborFlags + iNumUnits2; //!< 标记neighbor可用性的数组同样移动至左上角
if (*pbNeighborFlags) //!< 如果左上角可用,则左上角4个像素点均赋值为重建Yuv左上角的样点值
{
piAdiLineTemp[0] = piRoiTemp[0];
for (i=1; i<iUnitSize; i++)
{
piAdiLineTemp = piAdiLineTemp[0];
}
}
// Fill left & below-left samples
piRoiTemp += iPicStride; //!< piRoiTemp指向重建Yuv的左边界
if (bLMmode)
{
piRoiTemp --; // move the second left column
}
piAdiLineTemp--; //!< 移动指针置左边界
pbNeighborFlags--; //!< 移动指针置左边界
for (j=0; j<iNumUnits2; j++) //!< 从左往左下扫描
{
if (*pbNeighborFlags) //!< 如果可用
{
for (i=0; i<iUnitSize; i++) //!< 每个4x4块里的4个样点分别被赋值为对应位置的重建Yuv的样点值
{
piAdiLineTemp[-i] = piRoiTemp[i*iPicStride];
}
}
piRoiTemp += iUnitSize*iPicStride; //!< 指针挪到下一个行(以4x4块为单位,即实际上下移了4行)
piAdiLineTemp -= iUnitSize; //!< 指针下移
pbNeighborFlags--; //!< 指针下移
}
// Fill above & above-right samples
piRoiTemp = piRoiOrigin - iPicStride; //!< piRoiTemp 指向重建Yuv的上边界
piAdiLineTemp = piAdiLine + ((iNumUnits2+1)*iUnitSize); //!< 指向上边界
pbNeighborFlags = bNeighborFlags + iNumUnits2 + 1; //!< 指向上边界
for (j=0; j<iNumUnits2; j++) //!< 从左扫描至右上
{
if (*pbNeighborFlags)
{
for (i=0; i<iUnitSize; i++)
{
piAdiLineTemp = piRoiTemp; //!< 每个4x4块里的4个样点分别被赋值为对应位置的重建Yuv的样点值
}
}
piRoiTemp += iUnitSize; //!< 指针右移
piAdiLineTemp += iUnitSize; //!< 指针右移
pbNeighborFlags++; //!< 指针右移
}
// Pad reference samples when necessary
iCurr = 0;
iNext = 1;
piAdiLineTemp = piAdiLine; //!< 指向左下角(纵坐标最大的那个位置,即扫描起点)
while (iCurr < iTotalUnits) //!< 遍历所有neighboring samples
{
if (!bNeighborFlags[iCurr]) //!< 该点不可用
{
if(iCurr == 0) //!< 第一个点就不可用
{
while (iNext < iTotalUnits && !bNeighborFlags[iNext]) //!< 找到第1个可用点
{
iNext++;
}
piRef = piAdiLine[iNext*iUnitSize]; //!< 保存该可用点的样点值
// Pad unavailable samples with new value
while (iCurr < iNext) //!< 使用保存下来的第一个可用点的样点值赋值给在其之前被标记为不可用的点
{
for (i=0; i<iUnitSize; i++)
{
piAdiLineTemp = piRef;
}
piAdiLineTemp += iUnitSize;
iCurr++;
}
}
else //!< 当前点不可用且其不是第一个点,则使用该点的前一个可用点的样点值进行赋值
{
piRef = piAdiLine[iCurr*iUnitSize-1];
for (i=0; i<iUnitSize; i++)
{
piAdiLineTemp = piRef;
}
piAdiLineTemp += iUnitSize;
iCurr++;
}
}
else //!< 当前点可用,继续检查下一点
{
piAdiLineTemp += iUnitSize;
iCurr++;
}
}
// Copy processed samples
piAdiLineTemp = piAdiLine + uiHeight + iUnitSize - 2; //!< ??? 这里的坐标计算不明白加上iUnitSize是什么意思,从下文来看,指针是想指向左上点
for (i=0; i<uiWidth; i++) //!< 将最终结果拷贝到左上、上、右上边界
{
piAdiTemp = piAdiLineTemp;
}
piAdiLineTemp = piAdiLine + uiHeight - 1; //!< uiHeight = uiCUHeight2 + 1
for (i=1; i<uiHeight; i++) //!< 将最终结果拷贝到左边界和左下边界
{
piAdiTemp[i*uiWidth] = piAdiLineTemp[-i]; //!< piAdiLineTemp下标为-i是因为赋值方向与实际存储方向是相反的
}
}
}
关于一个PU的相邻点,以及它的相邻点的可用性如何判断的问题,是一个细节问题,并不会影响我们对这个函数实现功能的理解,但是,为了更好地理解这一个过程,这些坐标为什么这么算还是一个值得讨论的问题,限于篇幅,本文不作讨论。我会在接下来的文章中陆续对留下来的问题作更为详尽的讨论。
(转载请注明出处。原文地址:http://blog.csdn.net/hevc_cjl/article/details/8175721)
[HEVC] HEVC学习(三) —— 帧内预测系列之一
今天开始进入实质性内容的讨论,主要是从代码实现的角度比较深入地研究帧内预测算法。由于帧内预测涉及到的函数的数量相对于编解码器复杂部分来说少,但事实上大大小小也牵涉到了十几二十个函数(没具体统计过,只是大概估算了下),想要一下子讨论完比较困难,所以打算在接下来的若干篇文章里逐步地尽可能详尽地分析每一个较为重要的函数。今天所要讨论的是fillReferenceSamples这个函数,它主要功能是在真正进行帧内预测之前,使用重建后的Yuv图像对当前PU的相邻样点进行赋值,为接下来进行的角度预测提供参考样点值。 这个函数实际上实现的是官方当前标准(JCTVC-J1003)draft 8.4.4.2.2(Reference sample substitution process for intra sample prediction),具体内容我这里就不重复了,有兴趣的朋友可以自己下下来去看看,我先简单把该过程复述一遍:(1)如果所有相邻点均不可用,则参考样点值均被赋值为DC值;(2)如果所有相邻点均可用,则参考样点值都会被赋值为重建Yuv图像中与其位置相同的样点值;(3)如果不满足上述两个条件,则按照从左下往左上,从左上往右上的扫描顺序进行遍历,(如下图所示), 如果第一个点不可用,则使用下一个可用点对应的重建Yuv样点值对其进行赋值;对于除第一个点外的其它邻点,如果该点不可用,则使用它的前一个样点值进行赋值(前一个步骤保证了前一个样点值一定是存在的),直到遍历完毕。 在了解了算法的大致流程后,我们就可以看看代码是怎么具体实现的了。我主要把我对代码的注释连同代码一起贴出来,以供大家参考,有些地方理解上肯定还是有所欠缺的,望大家不吝赐教。 Void TComPattern::fillReferenceSamples( TComDataCU* pcCU, Pel* piRoiOrigin, Int* piAdiTemp, Bool* bNeighborFlags, Int iNumIntraNeighbor, Int iUnitSize, Int iNumUnitsInCu, Int iTotalUnits, UInt uiCuWidth, UInt uiCuHeight, UInt uiWidth, UInt uiHeight, Int iPicStride, Bool bLMmode ) { Pel* piRoiTemp; //!< piRoiOrgin指向重建Yuv图像对应于当前PU所在位置的首地址,piRoiTemp用于指向所感兴趣的重建Yuv的位置,piAdiTemp Int i, j; Int iDCValue = ( 1<<( g_uiBitDepth + g_uiBitIncrement - 1) ); if (iNumIntraNeighbor == 0) // all samples are not available { // Fill border with DC value for (i=0; i<uiWidth; i++) //!< AboveLeft + Above + AboveRight { piAdiTemp = iDCValue; } for (i=1; i<uiHeight; i++) //!< Left + BelowLeft { piAdiTemp[i*uiWidth] = iDCValue; } } else if (iNumIntraNeighbor == iTotalUnits) // all samples are available { // Fill top-left border with rec. samples piRoiTemp = piRoiOrigin - iPicStride - 1; //!< 左上 piAdiTemp[0] = piRoiTemp[0]; // Fill left border with rec. samples piRoiTemp = piRoiOrigin - 1; //!< 左 if (bLMmode) //!< bLMmode 默认值为false { piRoiTemp --; // move to the second left column } for (i=0; i<uiCuHeight; i++) { piAdiTemp[(1+i)*uiWidth] = piRoiTemp[0]; //!< 每个参考样点赋值为对应位置重建Yuv样点值 piRoiTemp += iPicStride; //!< 指向重建Yuv下一行 } // Fill below left border with rec. samples for (i=0; i<uiCuHeight; i++) //!< 左下 { piAdiTemp[(1+uiCuHeight+i)*uiWidth] = piRoiTemp[0]; //!< 每个参考样点赋值为对应位置重建Yuv样点值 piRoiTemp += iPicStride; } // Fill top border with rec. samples piRoiTemp = piRoiOrigin - iPicStride; //!< 重新指向重建Yuv的上方 for (i=0; i<uiCuWidth; i++) { piAdiTemp[1+i] = piRoiTemp; //!< 每个参考样点赋值为对应位置重建Yuv样点值 } // Fill top right border with rec. samples piRoiTemp = piRoiOrigin - iPicStride + uiCuWidth; //!< 指向右上 for (i=0; i<uiCuWidth; i++) { piAdiTemp[1+uiCuWidth+i] = piRoiTemp; //!< 每个参考样点赋值为对应位置重建Yuv样点值 } } else // reference samples are partially available { Int iNumUnits2 = iNumUnitsInCu<<1; Int iTotalSamples = iTotalUnits*iUnitSize; //!< neighboring samples的总数,iTotalUnits以4x4块为单位,iUnitSize为块的大小 Pel piAdiLine[5 * MAX_CU_SIZE]; Pel *piAdiLineTemp; //!<临时存储用于填充neighboring samples的样点值 Bool *pbNeighborFlags; Int iNext, iCurr; Pel piRef = 0; //!< 存储临时样点值 // Initialize for (i=0; i<iTotalSamples; i++) //!< 先将所有样点值赋值为DC值 { piAdiLine = iDCValue; } // Fill top-left sample piRoiTemp = piRoiOrigin - iPicStride - 1; //!< 指向重建Yuv左上角 piAdiLineTemp = piAdiLine + (iNumUnits2*iUnitSize); //!< piAdiLine的扫描顺序为左下到左上,再从左到右上 pbNeighborFlags = bNeighborFlags + iNumUnits2; //!< 标记neighbor可用性的数组同样移动至左上角 if (*pbNeighborFlags) //!< 如果左上角可用,则左上角4个像素点均赋值为重建Yuv左上角的样点值 { piAdiLineTemp[0] = piRoiTemp[0]; for (i=1; i<iUnitSize; i++) { piAdiLineTemp = piAdiLineTemp[0]; } } // Fill left & below-left samples piRoiTemp += iPicStride; //!< piRoiTemp指向重建Yuv的左边界 if (bLMmode) { piRoiTemp --; // move the second left column } piAdiLineTemp--; //!< 移动指针置左边界 pbNeighborFlags--; //!< 移动指针置左边界 for (j=0; j<iNumUnits2; j++) //!< 从左往左下扫描 { if (*pbNeighborFlags) //!< 如果可用 { for (i=0; i<iUnitSize; i++) //!< 每个4x4块里的4个样点分别被赋值为对应位置的重建Yuv的样点值 { piAdiLineTemp[-i] = piRoiTemp[i*iPicStride]; } } piRoiTemp += iUnitSize*iPicStride; //!< 指针挪到下一个行(以4x4块为单位,即实际上下移了4行) piAdiLineTemp -= iUnitSize; //!< 指针下移 pbNeighborFlags--; //!< 指针下移 } // Fill above & above-right samples piRoiTemp = piRoiOrigin - iPicStride; //!< piRoiTemp 指向重建Yuv的上边界 piAdiLineTemp = piAdiLine + ((iNumUnits2+1)*iUnitSize); //!< 指向上边界 pbNeighborFlags = bNeighborFlags + iNumUnits2 + 1; //!< 指向上边界 for (j=0; j<iNumUnits2; j++) //!< 从左扫描至右上 { if (*pbNeighborFlags) { for (i=0; i<iUnitSize; i++) { piAdiLineTemp = piRoiTemp; //!< 每个4x4块里的4个样点分别被赋值为对应位置的重建Yuv的样点值 } } piRoiTemp += iUnitSize; //!< 指针右移 piAdiLineTemp += iUnitSize; //!< 指针右移 pbNeighborFlags++; //!< 指针右移 } // Pad reference samples when necessary iCurr = 0; iNext = 1; piAdiLineTemp = piAdiLine; //!< 指向左下角(纵坐标最大的那个位置,即扫描起点) while (iCurr < iTotalUnits) //!< 遍历所有neighboring samples { if (!bNeighborFlags[iCurr]) //!< 该点不可用 { if(iCurr == 0) //!< 第一个点就不可用 { while (iNext < iTotalUnits && !bNeighborFlags[iNext]) //!< 找到第1个可用点 { iNext++; } piRef = piAdiLine[iNext*iUnitSize]; //!< 保存该可用点的样点值 // Pad unavailable samples with new value while (iCurr < iNext) //!< 使用保存下来的第一个可用点的样点值赋值给在其之前被标记为不可用的点 { for (i=0; i<iUnitSize; i++) { piAdiLineTemp = piRef; } piAdiLineTemp += iUnitSize; iCurr++; } } else //!< 当前点不可用且其不是第一个点,则使用该点的前一个可用点的样点值进行赋值 { piRef = piAdiLine[iCurr*iUnitSize-1]; for (i=0; i<iUnitSize; i++) { piAdiLineTemp = piRef; } piAdiLineTemp += iUnitSize; iCurr++; } } else //!< 当前点可用,继续检查下一点 { piAdiLineTemp += iUnitSize; iCurr++; } } // Copy processed samples piAdiLineTemp = piAdiLine + uiHeight + iUnitSize - 2; //!< ??? 这里的坐标计算不明白加上iUnitSize是什么意思,从下文来看,指针是想指向左上点 for (i=0; i<uiWidth; i++) //!< 将最终结果拷贝到左上、上、右上边界 { piAdiTemp = piAdiLineTemp; } piAdiLineTemp = piAdiLine + uiHeight - 1; //!< uiHeight = uiCUHeight2 + 1 for (i=1; i<uiHeight; i++) //!< 将最终结果拷贝到左边界和左下边界 { piAdiTemp[i*uiWidth] = piAdiLineTemp[-i]; //!< piAdiLineTemp下标为-i是因为赋值方向与实际存储方向是相反的 } } } 关于一个PU的相邻点,以及它的相邻点的可用性如何判断的问题,是一个细节问题,并不会影响我们对这个函数实现功能的理解,但是,为了更好地理解这一个过程,这些坐标为什么这么算还是一个值得讨论的问题,限于篇幅,本文不作讨论。我会在接下来的文章中陆续对留下来的问题作更为详尽的讨论。 (转载请注明出处。原文地址:http://blog.csdn.net/hevc_cjl/article/details/8175721) |