这里主要是结合HEVC的解码端I帧进行讲解的,其中P,B帧基本上没有太大的出入,主要是PU还存在不规则的情况,因为我现在刚做完I帧,对P帧还没有把握
之后清楚解析后,再进行补充
在之前的博文中提到了编码树结构的相关概念,这里主要结合代码进行进一步的讲解
在帧内模式中:
35中预测模式是在PU的基础上进行定义的,但是在具体的帧内预测过程中是以TU为单位的,标准规定PU可以四叉树的形式划分为TU,并且同一个PU内的TU共享一种预测模式
在实际的预测中,每一个TU自己预测自己的,自己参考自己周围的像素点
所以说:PU只是定义预测的方式,而真正的和预测像素和重构的过程都是通过TU进行处理的
下面是熵解码读出一些数据的记录线索:
pCu 代表的是一个CTU对应的结构体
uiAbsPartIdx 表示当前CU在CTU中的位置(以配置文档中最小TU为单位)
最后一个参数表示当前CU或者PU中有多少个TU
//<从CTU中递归进入CU的过程中记录深度信息
memset( pCu->pPuhDepth + uiAbsPartIdx, uiDepth+splitFlag, uiCurNumParts );
//<记录当前CU预测部分PU是N*N,or2N*2N,同时记录当前CU的width,height
memset(pCu->pPePartSize+uiAbsPartIdx,partSize,uiCurNumParts);
memset(pCu->pPuhWidth+uiAbsPartIdx,pSps->uiMaxCUWidth>>uiDepth,uiCurNumParts);
memset(pCu->pPuhHeight+uiAbsPartIdx,pSps->uiMaxCUHeight>>uiDepth,uiCurNumParts);
//<写入预测模式,帧内or帧间
memset(pCu->pPePredMode+uiAbsPartIdx,MODE_INTRA,uiCurNumParts);(目前都是写入帧内)
//<针对PU的模式写入亮度,色度预测部分的方向
memset(pCu->pPuhIntraDir[CHANNEL_TYPE_LUMA]+uiAbsPartIdx+i*partOffset,intraPredMode,uiCurNumParts);
i表示的是如果进行劈分,分四次写入
memset(pCu->pPuhIntraDir[CHANNEL_TYPE_CHROMA]+uiAbsPartIdx,symbol,uiCurNumParts);
//<从CU为根节点进入TU,递归获得最后的最小TU进行解残差系数的解码.帧内部分和PU相关的是:如果为帧内模式,同时PU劈分,那么TU需要劈分
//<在每一次递归过程中,CBF的值
memset(pCu->pPuhCbf[compID]+uiAbsPartIdx,uiCbf,pImg->numPartitionsInCtu>>(uiDepthAdj<<1));
//<TU递归到最小的时候,写入当前TU相对于CU的深度
memset(pCu->pPuhTrIdx + uiAbsPartIdx,uiTrDepth,uiCurNumParts);
//<每一个TU中残差系数
存储在:
pCoeff = pCu->pTrCoeff[compID]+pTu->offsets[compID];
Offsets表示的是通过递归获取的当前TU在CTU中的偏移位置
注意:每一次劈分到最小解码TU,并不代表当前TU就一定是配置信息中的最小TU,这是在编码端决定的
得到对应的信息之后,进行解码CTU操作
首先是进入
m_pcCuDecoder->decompressCtu ( pCtu ); 函数
之后进入递归过程,和读数据时候的过程是相同的,得到最小的CU之后进行解码:
<span style="font-size:18px;"> case MODE_INTRA: xReconIntraQT( m_ppcCU[uiDepth], uiDepth ); //<开始解帧内预测</span>
之后根据partSize 将CU划分为对应的PU
TComTURecurse <span style="color:#ff0000;"><strong>tuRecurseCU</strong></span>(pcCU, 0); TComTURecurse tuRecurseWithPU(tuRecurseCU, false, (uiInitTrDepth==0)?TComTU::DONT_SPLIT : TComTU::QUAD_SPLIT); do { xIntraRecQT( m_ppcYuvReco[uiDepth], m_ppcYuvReco[uiDepth], m_ppcYuvResi[uiDepth], chanType, tuRecurseWithPU ); } while (tuRecurseWithPU.nextSection(tuRecurseCU));
因为虽然现在是PU获得预测模式,但是最后还是通过TU进行解码,所以这里还是用了TU的存储结构
while循环内就进入了PU开始解TU的过程
Void TDecCu::<strong><span style="color:#ff0000;">xIntraRecQT</span></strong>(TComYuv* pcRecoYuv, TComYuv* pcPredYuv, TComYuv* pcResiYuv, const ChannelType chType, TComTU &rTu) { UInt uiTrDepth = rTu.GetTransformDepthRel(); TComDataCU *pcCU = rTu.getCU(); UInt uiAbsPartIdx = rTu.GetAbsPartIdxTU(); UInt uiTrMode = pcCU->getTransformIdx( uiAbsPartIdx ); if( uiTrMode == uiTrDepth ) { if (isLuma(chType)) xIntraRecBlk( pcRecoYuv, pcPredYuv, pcResiYuv, COMPONENT_Y, rTu ); else { const UInt numValidComp=getNumberValidComponents(rTu.GetChromaFormat()); for(UInt compID=COMPONENT_Cb; compID<numValidComp; compID++) { xIntraRecBlk( pcRecoYuv, pcPredYuv, pcResiYuv, ComponentID(compID), rTu ); } } } else { TComTURecurse tuRecurseChild(rTu, false); do { xIntraRecQT( pcRecoYuv, pcPredYuv, pcResiYuv, chType, tuRecurseChild ); } while (tuRecurseChild.nextSection(rTu)); } }
从这个函数可以看出来,从PU进入TU之后还是进行一个和读参数对应的递归操作
从读数据和解数据的过程基本可以看出来了
CU,TU,PU可以说是相互独立的,因为他们各自负责一部分的模块
但是又有一定的联系,就帧内预测而言:
根节点都是CU,编码过程中是CU->PU,CU->TU,在帧内模式的情况下如果PU是2N*2N,那么CU->TU是一定会劈分一次
解码过程,先判断出帧内情况,所以根据PU先判断CU是否劈分了一次,之后再根据深度信息得到TU
对于帧间情况下,之后进行补充