作者:姜生剃斧,PP云高級技術經(jīng)理鉴象,10余年視頻編解碼算法設計優(yōu)化慕的,流媒體應用等領域開發(fā)經(jīng)驗叠殷。
一 SAO 技術介紹
SAO 的全稱是 Sample adaptiveoffset堕伪,對應的中文意思是采樣自適應補償揖庄。SAO 是H.265編碼規(guī)范中一項重要的壓縮技術,該技術的思想源于Samsung提案JCTVC-A124欠雌。實驗測試結果顯示 SAO 能夠帶來的壓縮增益遠超過Deblock和ALF蹄梢。
SAO 模塊在編碼器的結構圖一中所處的位置如下(紅色的部分):
圖一 SAO在編碼器中的位置
SAO 在解碼器的結構圖二中所處的位置如下(紅色的部分):
圖二 SAO在解碼器中的位置
從流程圖中可以看出,SAO和ALF是loop內(nèi)的操作富俄,接在Deblock的后面禁炒,輸入包括原始的YUV圖像和Deblock的輸出,生成的參數(shù)需要進行entropy編碼霍比。參考圖三:
圖三 SAO 的算法概圖
二 SAO 算法介紹
圖像經(jīng)過壓縮和解壓后幕袱,精度會損失。通過psnr計算公式可以看出悠瞬,重構數(shù)據(jù)和原始數(shù)據(jù)YUV之間差值的平方和決定了psnr们豌。SAO 通過分析原始數(shù)據(jù)和重構后的數(shù)據(jù)涯捻,對deblock之后的進行offset補償操作,使得盡量接近原始的像素值望迎,達到提高psnr 的目的障癌。
均方誤差:
峰值信噪比:
如何具體運算來提高PSNR值呢,一個直接的想法是把deblock的重構數(shù)據(jù)和原始的YUV 中每一個相同位置的pixel做差值擂煞,把這個差值傳給decoder混弥,這樣可以完全恢復YUV。實際上对省,這樣做會導致碼率非常高蝗拿,達不到壓縮的效果。
為了能夠提高psnr蒿涎,同時只會增加極少量的碼率哀托,H.265 在碼率和psnr之間做了一個tradeoff。下面看一下是怎么做的劳秋。
H.265是基于CTB來做SAO的仓手,通過分析原始數(shù)據(jù)和deblock后的重構數(shù)據(jù),將pixel 分成三種SAO模式:
SaoTypeIdx[cIdx][rx][ry]
SAO type
Not applied
1
Band Offset(BO)
2
Edge Offset(EO)
由上表可以看出玻淑,SAO 有三種模式:不做嗽冒,BandOffset, Edge Offset, 后面兩種模式分別介紹如下:
EdgeOffset Mode:(邊界補償模式)
在這種模式下补履,SAO 需要為CTB 選擇一種梯度模式添坊,水平/垂直/45度角/135度角。這四個類別用sao_eo_class 語法元素表示箫锤,如圖四:
圖四 edgeoffset 梯度四種模式
為當前的CTB選擇好一種梯度模式后贬蛙,開始計算該CTB中每一個像素和相鄰兩個像素的大小關系,這個大小關系分成5類:
EdgeIdx
Condition
Meaning
P = n0 and p = n1
Flat area
1
P < n0 and p < n1
Local min
2
P < n0 and p = n1 or P = n0 and p < n1
Edge
3
P > n0 and p = n1 or P = n0 and p > n1
Edge
4
P > n0 and p > n1
Local max
圖五 EdgeIdx 四種類型
對CTB而言谚攒,EO(Edge Offset)的梯度模式在碼流里面被包含了阳准,但是對于每一個像素而言,EdgeIdx 是通過計算得來的馏臭,編碼器和解碼器所使用的計算方法一樣野蝇,所以得到的結果一樣,碼流里面不需要編碼EdgeIdx信息括儒,這樣節(jié)省了碼率浪耘,付出的代價是增加了CPU 的運算量。
對于EdgeIdx 為0的flat area塑崖,可以不需要做任何操作。對于其余四類痛倚,SAO為每一類分配了一個Offset 整數(shù)補償值规婆,這個Offset會add到被重構的每一對應類像素中。同時H.265規(guī)定,EdgeIdx=1抒蚜,2 這兩類掘鄙,offset 值必須為正數(shù),EdgeIdx=3嗡髓,4必須為負數(shù)操漠,這樣符號位不需要編碼,節(jié)省碼率饿这。
Band Offset Mode:
YUV 像素值的取值范圍通常是 0~255浊伙,平均分成32個band,每一個band包含的橫跨的范圍是8.通過一定的算法來選擇連續(xù)的4個band進行補償长捧,當CTB的YUV 像素值處于選定的4個band中時嚣鄙,需要對這個sample補償。
圖六 BandOffset 補償模式
Band Offset 的原理是:在編碼器端串结,對32個band分別做像素值的直方圖統(tǒng)計哑子,求每一個band像素值的平均值。下面是一個例子:
假設對于原始的CTB肌割,其中有一個band卧蜓,位于[28,35], 有三個pixel,像素值分別是:32把敞,35弥奸, 35,這樣可以知道該band 的像素平均值是(32 + 35+35)/3 = 34; 而對應的deblock之后的band先巴,包含三個像素其爵,分別是30,32伸蚯,34摩渺,平均值是(30 + 32 + 34) / 3 = 32, 可見,在該band上剂邮,原始的像素值平均值比重構的大 34-32=2摇幻,因此,可以分配offset=+2給這個band挥萌,在decoder 端為這個band 的每一個像素值加2.這樣保證在該band上出現(xiàn)的重構pixel和原始的平均值相等绰姻。對32個都做這種處理,最后選擇連續(xù)的4個引瀑。
對于Band OffsetMode和Edge Offset Mode而言狂芋,如果當前的CTB的SAO 參數(shù)與左邊或上邊CTB的SAO 參數(shù)相同,這時不需要為當前的CTB傳輸SAO參數(shù)憨栽,而是直接使用左邊或上邊CTB的SAO 參數(shù)帜矾。
三 SAO 算法的優(yōu)化
1. 優(yōu)化之前的常用算法:
對于CTB 內(nèi)的每一個像素而言翼虫,需要計算:
1. 先要計算出原始像素值和重構像素值的差值,每個像素的差值用變量offset_value 表示屡萤;
2. 需要根據(jù)重構的像素值珍剑,分別計算每個像素BO,EO0, EO1, EO2, EO3 這五種類型內(nèi)的每一種子類型值,這個類型之命名為 sao_class死陆,這樣 64*64的CTB 方陣招拙,要遍歷5次,訪問次數(shù)大約為:5*64*64
3. 然后對CTB的64*64的方陣措译,一共4096個像素統(tǒng)計每一個類型的offset_value之和别凤,以及每個類型像素個數(shù) cnt_of_class。
2. 優(yōu)化后的算法:
該算法的特點是瞳遍,把每個像素的 offset_value 向左偏移 12位闻妓,一個32位的整數(shù),高20位放offset_value 值掠械,低12位放像素個數(shù). 對于每一個像素而言由缆,低12位初始化為1. 64*64 的CTB 塊,同一個SAO 的子類型猾蒂,最多只有 2^12 個像素均唉,所以用低12位保存子類型個數(shù)剛剛好不會溢出,如下圖:
12 ~ 31 bit (offset_value)
0 ~ 11 (cnt_of_class)
圖七 復合數(shù)據(jù)格式
這樣把offset_value 和 cnt_of_class 合并到一個 32 位整型數(shù)內(nèi)肚菠,可以讓兩個數(shù)據(jù)同時累加運行舔箭,運算量減少一半。
假設 64*64的CTB 塊蚊逢,offset值定義為下面的數(shù)組:
Offset_value[64][64] = { … …} ; 其中的每個數(shù)據(jù)格式都是符合數(shù)據(jù)格式
Rec_pixel_value[64][64] = { … …} ; 重構像素值 0 ~ 255
BO_class[64][64] = { … …} ; 取值范圍 0 ~ 31
EO0_class[64][64] = { … …} ; 取值范圍 0 ~ 4
EO1_class[64][64] = { … …} ; 取值范圍 0 ~ 4
EO2_class[64][64] = { … …} ; 取值范圍 0 ~ 4
EO3_class[64][64] = { … …} ; 取值范圍 0 ~ 4
定義一個數(shù)組:
Int BO_Class[32] = {0} ;
For(int i = 0; i < 64; i++)
For(intj = 0; j < 64; j++)
{
Int Rec_pixel_value[i][j] >> 3;
BO_Class[class]+= Offset_value[i][j];
}
上面運算完成后层扶,可以從BO_Class中分離出每一個子類的:
For(int i = 0; i < 32; i++)
{
offset_value = BO_Class[i] >> 12;
cnt_of_ BO_Class[i] & 0xFFF;
}
Edge Offset Mode:
1. 把 EO0, EO1 和并到一個數(shù)組中:EO0, EO1 都包含了 5個子類 0 ~ 4, 需要3bit,為了把兩個子類合并起來烙荷,需要建立一個二維數(shù)組镜会,兩個下標分別代表兩個類型的子類索引。假設左邊的下標代表 EO1 的子類索引终抽,右邊的下標代表 EO0 的子類索引:
EO_01[8][8]
2. 按照上面的方法把 EO2, EO3合并到一個數(shù)組中:
EO_23[8][8]
3. 完成下面的運算:
{
Intclass_0 = EO0_class[i][j];
Int class_1 = EO1_class[i][j];
Int offset = Offset_value[i][j];
EO_01[class_1][class_0] += offset
EO_23[class_3][class_2] += offset;
4. 分離出 EO0, EO1, EO2, EO3:
Int EO0[5] = {0};
Int EO1[5] = {0};
Int EO2[5] = {0};
Int EO3[5] = {0};
For(int i = 0; i < 5; i++)
For(int J =0; J < 5; J++)
EO0[j] += EO_01[i][j];
EO1[i]+= EO_01[i][j];
EO2[j]+= EO_23[i][j];
最后把每一類的 offset 和 count 分離出來戳表。
四 總結
優(yōu)化后,SAO 中 offset 統(tǒng)計部分的計算量減少到原來的 25%左右昼伴。整個 SAO 模塊 90%的運算時間被統(tǒng)計部分消耗掉匾旭,所以這個算法的優(yōu)化在C 層面比較明顯。在匯編層面圃郊,有一定效果价涝,但不太明顯,因為在運算的中間加了一個 8*8的數(shù)組持舆,這個數(shù)組不利于用多媒體指令集并行方式來實現(xiàn)飒泻。