2009年10月31日 星期六

JPEG編碼流程

 
若不牽扯到 Jpeg2000 或 Progressive JPEG,只針對 JFIF 基礎標準,
或是只採用 YCbCr + Baseline 模式 + Huffman 編碼的 TIFF 標準,
其作法可簡述如下:

將圖的長寬 zero padding 成 8 的倍數,
以 8x8 區塊為處理單位,理由是:
1. 做成晶片時省材料費 (開玩笑的?),
2. 程式師偏好 2 的冪次方以利記憶體對齊並加速運算 (半開玩笑的..),
3. 一般圖像的連續色調常侷限於 17x17 的區塊裡...

將 RGB 轉到均勻色彩空間 YCbCr (最常用的 YUV 一族),
由於綠光在可見光譜中所佔範圍較大,導致人視網膜上紅藍細胞較少,
加上柱狀細胞占多數,因此我們可保留 Y,而捨棄一些 Cb Cr 訊息,
Jpeg 可施行 4:4:4、4:2:2、4:2:0 3 種 downsampling (三選一)。

之後,將 Y、Cb、Cr 值正規化至 -128~127 (一般,Y 要扣掉 128),
再分別進行 Forward DCT 變換該矩陣的底基向量
(這部份有許多加速法,例如將 8x8 DCT 分解成 x, y 方向的兩個 1D DCT,
或是用 AA&N 並搭配他們的量化表),然後將各值除以quantization table
(如 CCIR 601)中對應的值,再取 round。
採用 DCT 而不用 FFT,是因為他能以較少的係數來逼近連續性資料。
(圖釋: http://www.cs.cf.ac.uk/Dave/Multimedia/node231.html)

為了將盡可能多的 0 靠在一起,以利壓縮資料,
需將量化後表中的 AC 值以 Zig-Zag 順序重新編排,
(相當於從低頻排到高頻) 以 EOB 標籤 (1010) 取代尾端那些 0。
至於中間的 0,可用 RLE 壓縮掉,
RLE 中每組值分成 high 4-bits 與 low n-bits 兩部分,
high 4-bits 表示 0 的連續個數,(這和 Jpeg 標籤採用 4-bits 有關)
low bits 表示接在連續 0 後的值,以 Huffman 編碼表之。
low bits 表示的值有可能是 0,因為 high 4-bits 最多只能紀錄
15 個連續的 0,若有 15 個以上的連續 0,low bits 便是 0 了。

因為相鄰兩方塊的平均色彩 (也就是 DC 值) 一般很接近,
也就是說他們的差值很小,可用少量 bits 表示,
便以 DPCM 對區塊間的 DC 值進行 Predictive coding。

最後在各資料前頭貼標籤,存檔。
處理 jpg 檔中的眾多標籤也是件麻煩工作。

如何正確做 Jpeg Canonical Huffman Code 對應的說法很雜,
你可以用 ISO/IEC 10918-1 中給出的 DC, AC 預設編碼表試試看。
Jpeg codec 要寫的完善是件苦工喔...


簡單說來,對 8x8 區塊裡的 63 個 AC 值,以及區塊間 DC 的差值,
分別使用不同的 Huffman 表去編碼。而且 Y 與 CbCr 各有其編碼表,
所以總共有 4 份碼表。

以 Y 成分的 DC 差值為例,將每組差值表示成如下形式:
(Huffman識別碼, 差值的 binary code)

底下用「HI」簡稱「Huffman識別碼」,
用「DL」簡稱「差值 binary code 的長度」。
其碼表為:
DL HI長度 HI ----------------------- 0 2 00 1 3 010 2 3 011 3 3 100 4 3 101 5 3 110 6 4 1110 7 5 11110
若現在 Y 成分有 3 個區塊,其 DC 值分別是 58, 50, 53
安插 0 到最前端,便利差值運算,形成 0, 58, 50, 53。
將串列中元素,後者減前者,得差值為:

58 -8 3

差值為負時,先取絕對值,再將位元反相,故得:

111010 0111 11

111010 長度是 7,對照碼表得 HI 為 11110,故編碼成 11110 111010
0111 長度是 4,對照碼表得 HI 為 101,故編碼成 101 0111
11 長度是 2,對照碼表得 HI 為 00,故編碼成 00 11
最後寫入:

11110 111010 + 63個AC編碼
101 0111 + 63個AC編碼
00 11 + 63個AC編碼

為何要安插1個0到最前端做運算?
因為解碼時需要知道第一個 DC 值,才能將之後的 DC 值還原回來,
否則只憑 -8, 3,並無法還原回 58, 50, 53,之後的 IDCT 亦會出錯。

2 則留言:

  1. 請問58值為111010,長度為何是7(應為6)? 是因為安插的0放置在111010前端嗎?
    不好意思我只是想釐清意思,謝謝你!

    回覆刪除