SAO的全稱是Sample adaptiveoffset,中文意思是采樣自適應補償。SAO是H.265編碼標準中重要的壓縮技術,該技術的思想來自三星的提案JCTVC-A124。實驗結果表明,SAO比去塊和ALF能帶來更大的壓縮增益。
SAO模塊在編碼器結構圖中的位置如下(紅色部分):
圖1 SAO在編碼器中的位置
SAO在解碼器第二個結構圖中的位置如下(紅色部分):
圖2 SAO在解碼器中的位置
從流程圖可以看出,SAO和ALF是循環中的運算,後面是去塊,輸入包括原始YUV圖像和去塊的輸出,生成的參數需要進行熵編碼。參考圖3:
圖3 SAO算法概述
兩種SAO算法介紹
圖像經過壓縮解壓縮後,精度會有所損失。從psnr的計算公式可以看出,重構數據與原始數據之差的平方和YUV決定了psnr。SAO對原始數據和重構數據進行分析,對去塊後的數據進行偏移補償操作,使其盡可能接近原始像素值,達到提高psnr的目的。
均方差:
峰值信噪比:
如何通過具體操作提高PSNR值?壹個直接的思路就是把去塊的重構數據和原始YUV中相同位置的每個像素做差值,把這個差值傳遞給解碼器,這樣YUV就可以完全恢復。其實這樣會導致碼率非常高,達不到壓縮的效果。
H.265為了提高psnr,只增加壹點點碼率,在碼率和psnr之間做了壹個權衡。我們來看看是怎麽做到的。
H.265基於CTB做騷。通過分析原始數據和去塊後的重構數據,將像素分為三種SAO模式:
SaoTypeIdx有三個像素,像素值分別為32、35、35,這樣我們就知道這個波段的平均像素值為(32+35+35)/3 = 34;去塊後對應的帶包含三個像素,分別為30、32、34,平均值為(30+32+34)/3 = 32。可以看出,這個波段上原始像素值的平均值比重構波段的平均值大34-32=2,所以可以給這個波段賦值offset=+2,這就是解碼器端。32個都這樣做,最後選4個連續的。
對於頻帶偏移模式和邊緣偏移模式,如果當前CTB的SAO參數與左或上CTB的相同,則不需要傳輸當前CTB的SAO參數,而是直接使用左或上CTB的SAO參數。
三種SAO算法的優化
1.優化前的常用算法:
對於CTB的每個像素,需要計算:
1.首先計算原始像素值和重構像素值的差值,每個像素的差值用變量offset_value表示;
2.根據重建的像素值,需要計算每個像素的五種類型中每個子類型的值,即B0,EO0,EO1,EO2,EO3。這種類型命名為sao_class,所以64*64的CTB方陣需要遍歷五次,訪問次數大概是5*64*64。
3.然後對64*64方陣,4096像素的CTB,統計每種類型的offset_value之和,以及每種類型的像素cnt_of_class的個數。
2.優化的算法:
該算法的特點是每個像素的offset_value左移12位,為32位整數,offset_value高20位,像素數低12位。對於每個像素,低12位被初始化為1的CTB塊。64*64,和上壹張壹樣。
12 ~ 31位(偏移值)
0 ~ 11 (cnt_of_class)
圖7復合數據格式
這樣,通過將offset_value和cnt_of_class組合成壹個32位的整數,可以同時累加兩個數據,運算量減少壹半。
假設CTB塊為64*64,偏移值定義如下:
offset _ value[64][64]= {……};每種數據格式都符合該數據格式。
rec _ pixel _ value[64][64]= {……};重建像素值0 ~ 255
BO _ class[64][64]= {……};值的範圍從0到31
EO0 _ class[64][64]= {……};值的範圍從0到4
EO 1 _ class[64][64]= {……};值的範圍從0到4
EO2 _ class[64][64]= {……};值的範圍從0到4
EO3 _ class[64][64]= {……};值的範圍從0到4
定義數組:
int BO _ Class[32]= { 0 };
for(int I = 0;我& lt64;i++)
for(intj = 0;j & lt64;j++)
{
int Rec _ pixel _ value[I][j]& gt;& gt3;
BO _ Class[Class]+= Offset _ value[I][j];
}
完成上述操作後,您可以將每個子類從BO_Class中分離出來:
for(int I = 0;我& lt32;i++)
{
offset _ value = BO _ Class[I]& gt;& gt12;
BO Class[I]& amp;0xFFF
}
邊緣偏移模式:
1.將EO0,EO1組合成壹個數組:EO0和EO1都包含5個子類0 ~ 4,需要3位。為了將兩個子類結合起來,需要建立壹個二維數組,兩個下標分別代表兩類子類索引。假設左下標表示EO1的子類索引,右下標表示EO0的子類索引:
EO_01[8][8]
2.根據上述方法將EO2和EO3組合成壹個數組:
第二十三條第八款
3.完成以下操作:
{
int class _ 0 = EO0 _ class[I][j];
int class _ 1 = EO 1 _ class[I][j];
int Offset = Offset _ value[I][j];
EO _ 01[class _ 1][class _ 0]+=偏移量
EO _ 23[class _ 3][class _ 2]+= offset;
4.將EO0、EO1、EO2、EO3和EO3分開:
int EO0[5]= { 0 };
int EO 1[5]= { 0 };
int EO2[5]= { 0 };
int EO3[5]= { 0 };
for(int I = 0;我& lt5;i++)
for(int J = 0;J & lt5;J++)
EO0[j]+= EO _ 01[I][j];
EO 1[I]+= EO _ 01[I][j];
EO2[j]+= EO _ 23[I][j];
最後,分離每個類的偏移量和計數。
四個總結
優化後,SAO中炮檢距統計的計算量減少到25%左右。整個SAO模塊90%的運算時間都被統計部分消耗掉了,所以這種算法的優化在C級是很明顯的。在匯編級,有壹定效果,但不明顯,因為運算中間加了壹個8*8的數組,不利於多媒體指令集的並行實現。