[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) |