為什么要有幀內預測?因為一般來說,對於一幅圖像,相鄰的兩個像素的亮度和色度值之間經常是比較接近的,也就是顏色是逐漸變化的,不會一下子突變成完全不一樣的顏色。而進行視頻編碼,目的就是利用這個相關性,來進行壓縮。
很好理解,存儲一個像素的亮度值可能需要8個bit,但是如果相鄰的兩個像素變化不大,我存儲一個像素的原始值,以及第二個像素相對第一個像素的變化值,那么第二個值我可能用2個bit就夠了,這就節約了很多的空間。而節約存儲消耗的bit數,也就是節約碼率,貫穿了H.264編碼器的所有過程,不管是幀內預測、幀間預測、變換、量化、熵編碼,一切的一切都是為這個目的服務的,明白了這一點,我們就能輕易的理解H.264編碼器,所有看上去復雜難懂的地方,都是為了一個目的——節約碼率。
說回到幀內預測,幀內預測的流程見下圖
首先,是上圖中藍色的部分,假設現在我需要對一個像素X進行編碼,在編碼這個像素之前,先假設我已經有一個參考像素X'了,這個參考像素與同一幀的臨近像素有關,根據參考像素X'的值,我得到了一個預測值Xp。
然后,是上圖中紅色的部分,我用編碼的像素X減去預測值Xp,得到了殘差d,這個殘差d代替原始值X被編碼進最終的圖像,起到了節省碼率的作用
最后,是上圖中黑色的部分,殘差d和預測值Xp相加,得到了X',用於下一個像素的預測。
總結起來就是三步:
1、以同一幀圖像內的臨近像素作為參考,計算預測值Xp
2、原始值X和預測值Xp的差值d,被傳遞到解碼端
3、解碼端接收到差值d,將其與預測值Xp相加,就得到了“原始值”X',X'=Xp+d
步驟很簡單,但是里面有幾個問題要明確,首先要知道的是,我們固然可以按像素來進行預測,但是這樣太費事了,要計算很多次,而且由於幀內預測的特性,你要預測當前的像素,能參考的像素只能是它的臨近像素,也就是前面已經編碼完成的,它后面的像素還沒有編到,所以自然是不能用來編碼當前像素的,因此我們只能一個像素一個像素進行編碼,編完了一個才能編下一個,這樣顯然會很慢。於是H.264標准中提出按塊進行計算,一個宏塊是16x16像素,然后它可以分成子塊,最小是4x4的(這個大小是對於亮度編碼而言,至於色度編碼,4:2:0格式的色度宏塊的長和寬都是亮度宏塊的一半),這樣也能大大提高計算速度。因此下面提到的“值”可以代表“像素”也可以代表“塊”,從原理上來說是一樣的,而實際采用的是“塊”,因此我也就統一用塊這個詞了。
我們從上面三個步驟從頭開始一點點找問題
1、這個參考值X'是怎么來的?答案是在第三步里,使用Xp和傳遞過來的殘差d相加得到的,這個X'用於后面的塊編碼時的參考。
2、預測值Xp是怎么從X'獲得的?答案是根據X',通過某個公式計算得到的。而這個X'並不是只有一個塊,而是有左側、左上、正上、右上一共四個塊作為參考。
3、上面提到的這個某個公式是什么?根據白皮書,Intra(幀內預測)有兩種,一種是4x4大小的亮度塊,一種是16x16大小的亮度塊。
對於4x4大小的亮度塊,我們有9種預測模式,如下圖所示
對於預測模式0(vertical),當前塊的十六個像素值,完全由其上方塊最后一行的那四個像素值決定,第一列所有的Xp值都等於A,第二排都等於B,以此類推。預測模式1(horizontal)也是一樣,完全由圖中IJKL四個像素值決定。
對於預測模式2(DC),則十六個像素值完全相等,等於ABCDIJKL這八個像素的平均值。
對於預測模式3-8,傾斜方向的,各個像素是由A到L像素通過權重不等的公式加權計算的
比如對於模式3(diagnal down-left)來說,a=(A+2B+C+2)/4,這里+2代表四舍五入,b和e=(B+2C+D)/4,cfi=(C+2D+E+2)/4,dgjm=(D+2E+F+2)/4,hkn=(E+2F+G+2)/4,lo=(F+2G+H+2)/4,p=(G+2H+H+2)/4=(G+3H+2)/4
對於模式4(diag down-right),加權系數也是(1,2,1)/4,afkp用IMA三個像素計算,以此類推
對於模式5(vertical right),aj=(M+A+1)/2,同理bk是AB均值,cl是BC均值,d是CD均值(因為這幾個像素延長線都不在預測用的13個像素里面)。en在M的延長線上,所以等於(I+2M+A+2)/4,同理fo\gp\h\i\m都可以計算出來
同理模式6、7、8也基本跟5一樣,注意對於模式8來說,klmnop四個像素都等於L,因為其延長線在L的下面和前面,沒有可以平均的像素,於是只好用L一個值代替了。
還有一點要注意的是,這里面A到L的像素有的沒有怎么辦?對於模式2(DC),有什么用什么,反正是求均值就對了。其余的模式就必須用到的那幾個像素都存在才行(EFGH四個像素可以不存在,此時認為都等於D)
最終,我們要選擇哪種模式進行預測?為了在后面節約碼率考慮,當然是預測的越准越好,也就是選擇Xp和X'差距最小為好。而評判這個差距其實也有好幾種算法,比如SAD、SATD等,等到了變換那里再詳細說。總之我們用一個公式把上面9種模式的預測都評價了一番,選出里面最好的一種,作為4x4幀內預測的選擇。
我們注意到這里有9種模式,之后要進行編碼的話,我們除了把殘差編進去,總得知道我預測的時候用了哪種模式吧,9這個數就尷尬了,因為剛好三個比特可以表示8種,四個比特可以表示16種,所以3bit不夠4bit又浪費了。怎么辦呢?有個方法就比較巧妙,我有1bit用來表示我當前用的模式和前面的是不是一樣的,因為經常有這樣的情況,我前面塊用的預測方向和現在這個塊用的預測方向一樣(比如物體邊緣是一條直線,那么對應的那幾個塊用的預測方向很可能都是一樣的),如果一樣,我只用1bit就足夠存儲了,如果不一樣,我再用用4個bit存儲,也就達到了節約bit的目的。
說完了4x4的亮度塊,我們看看16x16大小的亮度塊。16x16亮度塊有四種模式,如下圖
前面三種很好理解了,重點是第四種plane的計算方式,從圖上看大概能理解,不過具體算法我還不是很理解。
我們假設左上角起,上方那一行是17個像素是a1 b2 c3 d4 e5 f6 g7 h8 i9 j8 k7 l6 m5 n4 o3 p2 q1,用這17個像素計算一個H值。
我在上面標了1~9~1的數字,有數字相同的8對像素,后面計算的時候,都是一對對的計算的。
i9;j8 - h8;k7 - g7;l6 - f6;m5 - e5;n4 - d4;o3 - c3;p2 - b2;q1 - a1,這九對分別乘以權重0到8(也就是i9這個像素沒有用到),而最左邊和最右邊兩個像素權重最大。
同理V值,也是一樣的算法,從上到下像素記作帶'的(當然a1' = a1)。
然后計算一個A值,它等於右上角(q1)和左下角(V值計算時候對應的那個q1')的和乘以16
計算一個B值,它等於(5*H+32)/64,計算一個C值,C=(5*V+32)/64,這種后面加了32又除以64的,其實都是用來四舍五入的。
然后就能計算我們的預測值了。
i00 = A - 7*B -7*C + 16
pix[0][0] = i00 / 32(超出0到255范圍的要截斷成0或者255)
然后計算第一行十六個像素分別是i00 + B到i00 + 15*B,然后除以32(然后截斷到0~255)
第二行是在第一行基礎上加了一個C,一直到第16行,加了15個C,於是這256個像素都算出來了。
這就是plane方式的算法。算出來結果就跟下圖一樣。
對於色度塊是亮度塊的四分之一,也就是8x8的,那就只有一種了,預測模式也跟亮度16x16塊的類似,有四種,只不過具體的序號不一樣而已。是0代表DC,1代表horizontal,2代表vertical,3代表plane。
plane的算法和上面16x16的類似,只不過系數變了,而且兩個色度塊用的方式一定都是一樣的。
好了,我們回到前面的幀內預測的步驟,還有幾個問題沒有解決
4、重建值X'和原始值是不是一樣的?答案是不一樣,因為d在傳到解碼端的過程中經過了量化、變換、反變換和反量化,有了精度的損失,因此Xp+d得到的X'跟原始的X是不一樣的。這也是為什么要用重建值X'來得到Xp而不是用原始值X。因為解碼器那邊在解碼圖像的時候,用到的是有損的X'獲得Xp,如果編碼器用無損的X來得到Xp的話,那么得到的殘差d在編碼側和解碼側就不一致了,這個誤差擴散到了下一個塊里。
好了,這樣上面的步驟就組成了一個循環,可以一直編碼下去了,當然最開始沒有參考的那個塊,其預測值Xp又是怎么來的呢?可以有一種方式叫做DC128,也就是認為這個塊的每個像素都是0x80,然后依據此來計算。
實際上對於一個16x16的宏塊,上面這個16x16的四種模式和它16個子塊(大小4x4)的9種模式都會算一遍,然后用16個子塊的SATD最小值和16x16的四種模式的SATD最小值比較,選擇更小的那個,所以這里面計算量還是很大的。x264源代碼里有很多節約計算量的設計,不過這些就得看代碼了,白皮書里面是沒有的,以后再做研究。
另外x264里面還有Intra8x8亮度塊的模式,這種我認為應該也是為了找到一個預測最准的模式而加進去的,而且也不常用,所以也就不多介紹了。
回過頭來看最開始的那個步驟圖,跟白皮書里給的編碼器流程圖,是不是正是編碼器的一部分呢?