您的位置:首页 > 其它

HEVC函数入门(6)——帧内预测-详细概念和HM16.3实现(上)

2017-06-21 16:14 471 查看
前面已经讲过了帧内预测的原理,但是只是讲了一下方向DC以及平面预测的概念,对其中的细节没有细讲,这里,整理http://blog.csdn.net/shaqoneal/article/details/44749057这篇博文来看看具体概念。

(1)角度预测

HEVC中的角度预测方法用于高效地对图像和视频的不同方向性结构进行建模的方法。该方法所选择的预测方向经过设计,可以达到对典型视频内容的编码效率和编码复杂度的平衡。算法对不同的预测块大小和预测方向提出了严格的低复杂度要求,因为HEVC帧内预测所支持的预测模式远远超过H.264等前期标准。在HEVC中,HEVC定义了从4×4到32×32这四种不同大小的帧内预测块尺寸,每种尺寸都支持33种不同的预测方形,所以一个解码器必须支持总共132中组合。

(1.1)角度的定义

HEVC定义了33中预测角度的集合,其精度为1/32。根据经验,图像内容中水平和垂直一致通常出现的概率超过其他方向一致。相邻方向之间的角度差越接近这两个方向便越小,越靠近对角线方向便越大,其目的在于在接近水平和垂直模式时可以提供更加精准的预测结果,而在出现机会较低的对角方向减小预测的运算负荷。各个预测方向如下图所示:



图中角度差用参数V表示,在生成参考像素时会作为获取参考数据的依据。

(1.2)参考像素对负角度方向的扩展

为了简化预测过程,编码器将当前像素块上方的参考像素p[x][-1]和左方的参考像素p[-1][y]置于一个一维数组中。对于正角度(模式26~33和模式2~10),该数组直接拷贝预测方向上的像素值,公式如下:



对于负角度,左侧和上方的参考像素都会被使用到,此时参考数组中的非负索引的元素依旧如前文所述,负索引值所表示的像素通过映射获得,公式如下:



其中B与角度参数A(V)的对应关系如下表:



这部分该如何理解呢?以下两张图分别是正向角度的垂直模式/水平模式的方向示意图:



对于不同的预测方向,预测块所使用的预测数据时不同的。由于预测像素的组织形式是一个一维数组,以顶点像素为中心向两边扩展。在正向角度预测时,只需要或正方向或负方向的预测数据级就可以为预测块进行赋值。而对于负向角度,情况将有所不同。下图表示负向角度下的预测方向示意图:



上面的这些内容呢,用我自己的语言组织一下,先看第一张正向角度、垂直类图,从右上到左下的预测方向,把这个方向放到最上面的原理图,发现这个方向代表V+2-V+32 前面又讲过,26代表垂直预测,那么与垂直预测离得近的我们称为垂直类,角度又为正,他们可以直接直接拷贝预测方向上的像素值(即上方的像素值)。

再看第三张图,从H-2到V-2,他们不仅需要上面的像素,还需要左方的像素值。

那么在HM中,我们知道xPredIntraAng来决定预测方向,因此找到这个函数。

后面原博的分析是HM10,和16.3不一样,我就按我的方法分析一下吧:

首先在

Void TComPrediction::predIntraAng( const ComponentID compID, UInt uiDirMode, Pel* piOrg /* Will be null for decoding */, UInt uiOrgStride, Pel* piPred, UInt uiStride, TComTU &rTu, Bool bAbove, Bool bLeft, const Bool bUseFilteredPredSamples, const Bool bUseLosslessDPCM )
{
const ChromaFormat   format      = rTu.GetChromaFormat();
const ChannelType    channelType = toChannelType(compID);
const TComRectangle &rect        = rTu.getRect(isLuma(compID) ? COMPONENT_Y : COMPONENT_Cb);
const Int            iWidth      = rect.width;
const Int            iHeight     = rect.height;

assert( g_aucConvertToBit[ iWidth ] >= 0 ); //   4x  4
assert( g_aucConvertToBit[ iWidth ] <= 5 ); // 128x128
//assert( iWidth == iHeight  );

Pel *pDst = piPred;

// get starting pixel in block
const Int sw = (2 * iWidth + 1);

if ( bUseLosslessDPCM )
{
const Pel *ptrSrc = getPredictorPtr( compID, false ); //获取参考数据的指针 ,指向的是当前块的参考数据
// Sample Adaptive intra-Prediction (SAP)
if (uiDirMode==HOR_IDX)
{
// left column filled with reference samples 用左列的参考样值填充 水平方向
// remaining columns filled with piOrg data (if available).
for(Int y=0; y<iHeight; y++)
{
piPred[y*uiStride+0] = ptrSrc[(y+1)*sw];
}
if (piOrg!=0)
{
piPred+=1; // miss off first column
for(Int y=0; y<iHeight; y++, piPred+=uiStride, piOrg+=uiOrgStride)
{
memcpy(piPred, piOrg, (iWidth-1)*sizeof(Pel));
}
}
}
else // VER_IDX
{
// top row filled with reference samples
// remaining rows filled with piOrd data (if available) 垂直方向
for(Int x=0; x<iWidth; x++)
{
piPred[x] = ptrSrc[x+1];
}
if (piOrg!=0)
{
piPred+=uiStride; // miss off the first row
for(Int y=1; y<iHeight; y++, piPred+=uiStride, piOrg+=uiOrgStride)
{
memcpy(piPred, piOrg, iWidth*sizeof(Pel));
}
}
}
}
else
{
const Pel *ptrSrc = getPredictorPtr( compID, bUseFilteredPredSamples );

if ( uiDirMode == PLANAR_IDX )
{
xPredIntraPlanar( ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, channelType, format );   //以平面模式构建当前PU的帧内预测块
}
else
{
// Create the prediction
TComDataCU *const pcCU              = rTu.getCU();
const UInt              uiAbsPartIdx      = rTu.GetAbsPartIdxTU();
const Bool              enableEdgeFilters = !(pcCU->isRDPCMEnabled(uiAbsPartIdx) && pcCU->getCUTransquantBypass(uiAbsPartIdx));

#if O0043_BEST_EFFORT_DECODING
xPredIntraAng( g_bitDepthInStream[channelType], ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, channelType, format, uiDirMode, bAbove, bLeft, enableEdgeFilters );
#else
xPredIntraAng( g_bitDepth[channelType], ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, channelType, format, uiDirMode, bAbove, bLeft, enableEdgeFilters ); //角度预测模式(INTRA_ANGULAR2~INTRA_ANGULAR34)
#endif

if(( uiDirMode == DC_IDX ) && bAbove && bLeft )
{
xDCPredFiltering( ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, channelType ); //模式为DC模式时还会调用xDCPredFiltering函数,对Intra16×16模式的特殊处理
}
}
}

}


这里我对垂直和水平做了标记,如果不是水平、垂直、平面和DC模式,那么就调用xPredIntraAng,前面那个if O0043_BEST_EFFORT_DECODING我们跳转到定义:< 0 (default) = disable code related to best effort decoding, 1 = enable code relating to best effort decoding [ decode-side only ].是禁用或启用effort decoding ,这里不管这个。继续分析

那么我们来看看这个函数吧:

const Bool modeDC        = dirMode==DC_IDX;

// Do the DC prediction
if (modeDC)
{
const Pel dcval = predIntraGetPredValDC(pSrc, srcStride, width, height, channelType, format, blkAboveAvailable, blkLeftAvailable);

for (Int y=height;y>0;y--, pTrueDst+=dstStrideTrue)
{
for (Int x=0; x<width;) // width is always a multiple of 4.
{
pTrueDst[x++] = dcval;
}
}
}
else // Do angular predictions
{
const Bool       bIsModeVer         = (dirMode >= 18);
const Int        intraPredAngleMode = (bIsModeVer) ? (Int)dirMode - VER_IDX :  -((Int)dirMode - HOR_IDX);
const Int        absAngMode         = abs(intraPredAngleMode);
const Int        signAng            = intraPredAngleMode < 0 ? -1 : 1;
const Bool       edgeFilter         = bEnableEdgeFilters && isLuma(channelType) && (width <= MAXIMUM_INTRA_FILTERED_WIDTH) && (height <= MAXIMUM_INTRA_FILTERED_HEIGHT);

// Set bitshifts and scale the angle parameter to block size
static const Int angTable[9]    = {0,    2,    5,   9,  13,  17,  21,  26,  32};
static const Int invAngTable[9] = {0, 4096, 1638, 910, 630, 482, 390, 315, 256}; // (256 * 32) / Angle
Int invAngle                    = invAngTable[absAngMode];
Int absAng                      = angTable[absAngMode];//将模式索引差值转换为角度偏移差
Int intraPredAngle              = signAng * absAng; //计算当前模式同水平/垂直模式之间的角度差

Pel* refMain;
Pel* refSide;

Pel  refAbove[2*MAX_CU_SIZE+1];
Pel  refLeft[2*MAX_CU_SIZE+1];

// Initialise the Main and Left reference array.
if (intraPredAngle < 0)//预测角度小于零
{
const Int refMainOffsetPreScale = (bIsModeVer ? height : width ) - 1;
const Int refMainOffset         = height - 1;
for (Int x=0;x<width+1;x++)
{
refAbove[x+refMainOffset] = pSrc[x-srcStride-1];//赋值上方的参考像素到refAbove[blkSize-1]~refAbove[2*blkSize-1],只复制前半部分
}
for (Int y=0;y<height+1;y++)
{
refLeft[y+refMainOffset] = pSrc[(y-1)*srcStride-1];//赋值左方的参考像素到refLeft[blkSize-1]~refLeft[2*blkSize-1],只复制前半部分
}
refMain = (bIsModeVer ? refAbove : refLeft)  + refMainOffset;//refMain和refSide指向拷贝的参考点的起始位置
refSide = (bIsModeVer ? refLeft  : refAbove) + refMainOffset;

// Extend the Main reference to the left.
Int invAngleSum    = 128;       // rounding for (shift by 8)用于四舍五入
for (Int k=-1; k>(refMainOffsetPreScale+1)*intraPredAngle>>5; k--)
{
invAngleSum += invAngle;
refMain[k] = refSide[invAngleSum>>8];//挑选某几个参考像素进行拷贝,
}
}
else
{ // 角度差为正
for (Int x=0;x<2*width+1;x++)
{
refAbove[x] = pSrc[x-srcStride-1];
}
for (Int y=0;y<2*height+1;y++)
{
refLeft[y] = pSrc[(y-1)*srcStride-1];
}
refMain = bIsModeVer ? refAbove : refLeft ;
refSide = bIsModeVer ? refLeft  : refAbove;
}

// swap width/height if we are doing a horizontal mode:转换宽度和高度如果使用了水平模式
Pel tempArray[MAX_CU_SIZE*MAX_CU_SIZE];
const Int dstStride = bIsModeVer ? dstStrideTrue : MAX_CU_SIZE;
Pel *pDst = bIsModeVer ? pTrueDst : tempArray;
if (!bIsModeVer)
{
std::swap(width, height);
}

if (intraPredAngle == 0)  // pure vertical or pure horizontal 如果正好是水平、垂直
{
for (Int y=0;y<height;y++)
{
for (Int x=0;x<width;x++)
{
pDst[y*dstStride+x] = refMain[x+1];//直接复制参考数据中的值
}
}

if (edgeFilter)
{
for (Int y=0;y<height;y++)
{
pDst[y*dstStride] = Clip3 (0, ((1 << bitDepth) - 1), pDst[y*dstStride] + (( refSide[y+1] - refSide[0] ) >> 1) );
}
}
}
else
{//预测模式存在角度差
Pel *pDsty=pDst;

for (Int y=0, deltaPos=intraPredAngle; y<height; y++, deltaPos+=intraPredAngle, pDsty+=dstStride)
{
const Int deltaInt   = deltaPos >> 5;
const Int deltaFract = deltaPos & (32 - 1);

if (deltaFract)
{
// Do linear filtering
const Pel *pRM=refMain+deltaInt+1;
Int lastRefMainPel=*pRM++;
for (Int x=0;x<width;pRM++,x++)
{
Int thisRefMainPel=*pRM;
pDsty[x+0] = (Pel) ( ((32-deltaFract)*lastRefMainPel + deltaFract*thisRefMainPel +16) >> 5 );//亚像素预测,从两个相邻像素中获取加权均值
lastRefMainPel=thisRefMainPel;
}
}
else
{
// Just copy the integer samples
for (Int x=0;x<width; x++)
{
pDsty[x] = refMain[x+deltaInt+1];
}
}
}
}

// Flip the block if this is the horizontal mode 如果这是水平模式,则翻转块
if (!bIsModeVer)
{
for (Int y=0; y<height; y++)
{
for (Int x=0; x<width; x++)
{
pTrueDst[x*dstStrideTrue] = pDst[x];
}
pTrueDst++;
pDst+=dstStride;
}
}
}
}


这里附上原博的一些内容,稍微参考一下吧:

intraPredAngle这个变量中就保存了当前模式同水平/垂直模式映射到边界上的偏移值,精度为1/32像素。如果该参数为正,那么将当前预测块的上方和左方预测像素复制到两个数组中,并依据当前模式的方向分类确定哪一个作为主要参考哪一个作为辅助参考:mode11的预测方向为水平向右,并略带向右下方倾斜,其参考的像素大部分为左侧像素,同时也会用到几个上方的像素。具体所需的像素个数为块尺寸×角度差参数A的绝对值÷32,对于64×64的mode11就是64×2÷32=4。这几个值从refSide中每隔M个像素取一个,M的取值为invAngle的取值除以256。

(1.3)角度预测模式的像素值预测

预测像素的值p[x][y]由pel[x][y]的当前位置按照模式规定的方向向参考像素数组上进行映射获取,并按照1/32像素的精度进行差值。差值由最接近的两个像素点按照线性关系生成。对于水平和垂直模式生成预测数据的公式如下:

水平模式:



垂直模式:



公式中的i表示对于垂直模式在y列和水平模式在x行的偏移值的整数部分,按照如下方式计算:



公式中的f表示偏移的小数部分,计算方式如下:



i和f这两个常量适用于预测一组数据(垂直模式的一行和水平模式的一列),只有线性插值操作需要对每个像素值进行操作。如果f值为0,那么不进行线性插值,直接将参考数据用作预测数据。

具体实现如以下代码所示:

if (intraPredAngle == 0)  // pure vertical or pure horizontal 如果正好是水平、垂直
{
for (Int y=0;y<height;y++)
{
for (Int x=0;x<width;x++)
{
pDst[y*dstStride+x] = refMain[x+1];//直接复制参考数据中的值
}
}

if (edgeFilter)
{
for (Int y=0;y<height;y++)
{
pDst[y*dstStride] = Clip3 (0, ((1 << bitDepth) - 1), pDst[y*dstStride] + (( refSide[y+1] - refSide[0] ) >> 1) );
}
}
}
else
{//预测模式存在角度差
Pel *pDsty=pDst;

for (Int y=0, deltaPos=intraPredAngle; y<height; y++, deltaPos+=intraPredAngle, pDsty+=dstStride)
{
const Int deltaInt   = deltaPos >> 5;
const Int deltaFract = deltaPos & (32 - 1);

if (deltaFract)
{
// Do linear filtering
const Pel *pRM=refMain+deltaInt+1;
Int lastRefMainPel=*pRM++;
for (Int x=0;x<width;pRM++,x++)
{
Int thisRefMainPel=*pRM;
pDsty[x+0] = (Pel) ( ((32-deltaFract)*lastRefMainPel + deltaFract*thisRefMainPel +16) >> 5 );//亚像素预测,从两个相邻像素中获取加权均值
lastRefMainPel=thisRefMainPel;
}
}
else
{
// Just copy the integer samples
for (Int x=0;x<width; x++)
{
pDsty[x] = refMain[x+deltaInt+1];
}
}
}
}

// Flip the block if this is the horizontal mode 如果这是水平模式,则翻转块
if (!bIsModeVer)
{
for (Int y=0; y<height; y++)
{
for (Int x=0; x<width; x++)
{
pTrueDst[x*dstStrideTrue] = pDst[x];
}
pTrueDst++;
pDst+=dstStride;
}
}
}


(2)、DC预测模式

对于DC预测模式,所有的预测数据采用同一数值,该数值由左方和上方参考数据的平均值生成。对于16×16或更小的DC预测块还需要一个滤波过程来优化左边和上方的边界效果,具体方法第四节详述。

(3)、平面预测模式

角度预测可以对方向性结构的像素块进行较为精确的预测,但在光滑的图像区也会产生一些肉眼可见的边界轮廓。类似的是,DC预测模式在中低码率下也可能会产生一些块效应。HEVC定义了平面模式用于处理类似的问题,可以生成一个在边界上没有不连续断层的预测平面。其方法为依据水平和垂直的线性预测方法,公式如下:



水平方向的预测结果Ph[x][y]和垂直方向上的预测结果Pv[x][y]按照以下方法生成:



(4)、像素预测值的后处理

有些预测模式在预测像素块的边界处可能产生不连续的像素值断层,对DC模式和角度预测中的水平和垂直模式尤为明显。在DC模式下,顶部和左侧边界都会产生不连续效应,因为整个预测像素值都由同一个平均值替换。对于垂直模式,左侧边界可能产生不连续边界,因为最左边一列的预测像素值复制了块上方最左侧的参考像素。对于水平模式的最顶行也存在类似的问题。

为了降低块边界的这种不连续性,预测块内部的边界像素需要考虑块边界的斜率做一次滤波并用结果进行替换。这一步仅仅针对DC、水平和垂直模式,而且预测块的尺寸小于32×32时进行。事实证明这种设置可以在编码效率和运算复杂度上取得一个较好的平衡。另外,由于亮度分量有更为均衡的特性,预测块边界滤波操作仅限于亮度分量。

如果预测模式为垂直模式,预测像素p[0]y由以下公式的结果进行替换:



对于水平模式,操作类似。

对于DC模式,需要根据原预测像素的位置分为三种情况:



这里内容比较多了,也按照原博一样,再分出一个章节吧,今天的内容我个人感觉总体理解的差不多了,如果不理解欢迎交流:)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  预测 hevc 编码
相关文章推荐