原文地址:http://blog.csdn.net/feixiang_john/article/details/8258452
HEVC中SAO--自適應樣點補償:
本文分三個部分, 1.Sample Adaptive Offset原理, 2.SAO處理流程分析, 3.SAO意義何在!
a) SAO原理:
SAO是在DB之後進行, 輸入是重建幀和原始幀數據, 輸出是SAO數據和SAO後的重建幀. 自適應樣點補償是一個自適應選擇過程,在去塊濾波後進行。
下面是整個HEVC的編碼框圖, 可以看到SAO是在整個幀編碼完成後得到重建幀後進行的,屬於Slice級別(幀級).
首先把Frame劃分爲若干LCU, 然後對每個LCU中每個像素進行SAO操作.將根據其LCU像素特徵選擇一種像素補償方式,以減少源圖像與重構圖像之間的失真。自適應樣點補償方式分爲帶狀補償(Band Offset,BO)和邊緣補償(Edge Offset,EO)兩大類。
帶狀補償將像素值強度等級劃分爲若干個條帶,每個條帶內的像素擁有相同的補償值。進行補償時根據重構像素點所處的條帶,選擇相應的帶狀補償值進行補償。
邊緣補償主要用於對圖像的輪廓進行補償。它將當前像素點值與相鄰的2個像素值進行對比,用於比較的2個相鄰像素可以在下圖中所示的4種模板中選擇,從而得到該像素點的類型。解碼端根據碼流中標示的像素點的類型信息進行相應的補償校正。
對每個模板還要確定屬於那種類型,類型確定有下面表格來決定.
b) SAO處理流程分析:
SAO主函數代碼結構如下:主要有3個函數完成所有操作.
Void TEncSampleAdaptiveOffset::SAOProcess()
{ …
rdoSaoUnitAll(pcSaoParam, dLambdaLuma, dLambdaChroma, depth);
…
if (pcSaoParam->bSaoFlag[0])
processSaoUnitAll(saoLcuParam[0], oneUnitFlag[0], 0);
if (pcSaoParam->bSaoFlag[1])
{
processSaoUnitAll(saoLcuParam[1], oneUnitFlag[1], 1);
processSaoUnitAll(saoLcuParam[2], oneUnitFlag[2], 2);
}
}
其中TEncSampleAdaptiveOffset::rdoSaoUnitAll函數完成對整個Frame的所有LCU的YUV進行reset stats和calcSaoStatsCu,以及saoComponentParamDist得到最佳SAO_TYPE選擇.最後encodeSaoOffset.
Void TEncSampleAdaptiveOffset::rdoSaoUnitAll()
{ …
for (idxY = 0; idxY< frameHeightInCU; idxY++)
{
for (idxX = 0; idxX< frameWidthInCU; idxX++)
{ …
// reset stats Y, Cb, Cr
…
calcSaoStatsCu(addr, compIdx, compIdx);
saoComponentParamDist();
sao2ChromaParamDist();
…
for ( compIdx=0;compIdx<3;compIdx++)
encodeSaoOffset(&saoLcuParam[compIdx][addr], compIdx);
// Cost of Merge
}
}
}
下面是子函數的說明:
也就是TEncSampleAdaptiveOffset::calcSaoStatsCuOrg
主要是得到LCU中所有像素各種SAOType的所有信息和狀態統計,
分析記錄各種SAOType(SAO_EO_0,SAO_EO_1,SAO_EO_2,SAO_EO_3,SAO_BO)以及各種SAOTypeLen和YUV分量對應的m_iOffsetOrg和m_iCount.
iStats = m_iOffsetOrg[0.1.2][0.1.2.3.4]; //YUV and SA0_TYPE
iCount = m_iCount[0.1.2][ 0.1.2.3.4];
如果是BO,通過iClassIdx = m_lumaTableBo[pRec[x]];來確定屬於那個條帶,並記錄iStats[iClassIdx] += (pOrg[x] - pRec[x]);iCount[iClassIdx]++;這裏iClassIdx大小爲0~32.
如果是EO, 通過iClassIdx =m_auiEoTable[uiEdgeType]來確定模板類型,並記錄iStats[iClassIdx] += (pOrg[x] - pRec[x]);iCount[iClassIdx]++;
這裏iClassIdx大小爲0~4.
其中映射關係如下:就是查表映射.
const UInt TComSampleAdaptiveOffset::m_auiEoTable[9] =
{ 1, //0 2, //1 0, //2 3, //3 4, //4
0, //5 0, //6 0, //7 0};
隨後函數TEncSampleAdaptiveOffset::saoComponentParamDist完成最佳SAOTYPE的選擇. 得到最佳dCostPartBest和typeIdx等信息.
下面兩個語句得到每個BO帶或者EO種類的offset.
m_iOffset[compIdx][typeIdx][classIdx] = m_iOffsetOrg[compIdx][typeIdx][classIdx]/(m_iCount[compIdx][typeIdx][classIdx];
m_iOffset[compIdx][typeIdx][classIdx] = Clip3(-m_iOffsetTh+1, m_iOffsetTh-1, (Int)m_iOffset[compIdx][typeIdx][classIdx]);
m_iOffset[compIdx][typeIdx][classIdx] = estIterOffset();
最後通過比較得到最佳dCostPartBest和typeIdx.
if(m_dCost[yCbCr][typeIdx] < dCostPartBest)
{
dCostPartBest = m_dCost[yCbCr][typeIdx];
// saoLcuParam保存目前最好的.
copySaoUnit(saoLcuParam, &saoLcuParamRdo );
bestDist = estDist;
}
同樣TEncSampleAdaptiveOffset::sao2ChromaParamDist完成色度的選擇.
TEncSampleAdaptiveOffset::SAOProcess()中的函數processSaoUnitAll主要完成SAO解碼的操作,也就是對重建幀進行SAOoffset疊加.
另外一個同名函數SAOProcess屬於COM庫,主要是解碼使用,也就調用processSaoUnitAll爲主,負責恢復SAO偏移量.
Void TComSampleAdaptiveOffset::SAOProcess(TComPic* pcPic, SAOParam* pcSaoParam)
{ …
if (pcSaoParam->bSaoFlag[0] || pcSaoParam->bSaoFlag[1])
{ if (pcSaoParam->bSaoFlag[0])
{
processSaoUnitAll(saoLcuParam[iY], oneUnitFlag[iY], iY);
}
if(pcSaoParam->bSaoFlag[1])
{
processSaoUnitAll(saoLcuParam[1], oneUnitFlag[1], 1);//Cb
processSaoUnitAll(saoLcuParam[2], oneUnitFlag[2], 2);//Cr
}
}
}
a) SAO--自適應樣點補償意義何在:
SAO意義:大量模擬測試和資料顯示, SAO平均可以節約2%到6%的碼率, 而編解碼的複雜度只增加2%左右!SAO主要目的和操作原理減少源圖像與重構圖像之間的失真。如果只看這點,實際上每幀編碼後的碼率反而會增加,因爲多了SAO的相關語法和語義以及補償值的編碼!其實不然,雖然當前幀的碼率增加了幾個字節或者幾個bit, 但是這點增加碼字使得源圖像與重構圖像的失真減少,使接下來的預測殘差更小了,從而大大的降低碼率了!
我在想是否可以增加一個無損編碼的HEVC版本呢? 增加另外一些語法和語義,把源圖像與重構圖像之間的失真單獨拿出來再次採用其它無損編碼技術編碼,如果在需要超清晰的圖像時,就可以和這個無損編碼的碼流組合解碼,重建無損圖像.個人隨想!大家也可以發表下觀點啊!