JPEG檔案除了影像資料之外,還儲存了與圖片相關的各種資訊,這些資訊透過不同型別的TAG儲存在檔案中。
TAG
JPEG透過TAG標記壓縮書記之外的資訊。所有的TAG都包含一個TAG型別,TAG型別大小為兩個位元組,位於一個TAG的最前面。TAG型別的第一個位元組一定為0xFF
以下是部分常見的TAG型別
TAG型別 | 數值 | 備註 |
---|---|---|
SOF0 ~ SOF3 SOF5 ~ SOF7 SOF8 ~ SOF11 SOF13 ~ SOF15 |
FF C0 ~ FF C3 FF C5 ~ FF C7 FF C8 ~ FF CB FF CD ~ FF CF |
不同的編碼模式對應不同的SOF,詳見JPEG標準Table B.1。本文以常見的SOF0為研究物件 |
DHT | FF C4 | Define Huffman table(s),定義瞭解碼所需的哈夫曼表 |
RSTm | FF D0 ~ FF D7 | 遇到時重置DC係數,數字會遞增,具體含義不清楚 |
SOI | FF D8 | Start of Image,表示影像檔案的開始 |
EOI | FF D9 | End of Image,表示影像檔案結束 |
SOS | FF DA | Start of scan,表示這一個TAG後就是壓縮的影像資料,記錄了DHT、DQT與影像不同部分的對應關係 |
DQT | FF DB | Define quantization table(s),定義瞭解碼所需的量化表 |
APP0 ~ APP15 | FF E0 ~ FF EF | 應用儲存的圖片相關資訊(如相機資訊等) |
壓縮資料中也是存在TAG的,雖然大部分TAG都在檔案開頭,但是也有少部分是例外。如EOI就在檔案的末尾,RSTm會出現在壓縮資料當中。
那麼問題來了,如果壓縮資料中有一個位元組本身就是0xFF怎麼辦?JPEG的做法是在0xFF後面再加一個位元組0x00,用於表示這不是一個TAG。
因為不知道解碼需要哪些TAG,我在嘗試寫JPEG解碼器的時候耗費了大量的時間在研究TAG上。總結出對於常見的JPEG圖片解碼需要的TAG:
1.SOI和EOI:用於確定檔案的開頭和結尾
2.DQT和DHT:儲存瞭解碼時需要用到的哈夫曼表和量化表
3.SOS:儲存了圖片不同部分的需要用哪個哈夫曼表
4.SOF:圖片的長和寬、取樣精度等、使用哪個量化表都儲存在SOF中
5.RST:部分圖片存在RST,遇到RST時要重置DC係數才能得到正確的影像
哈夫曼表與量化表
這裡有個小坑,我原先一直以為DQT和DHT都是一個TAG對應一個表,後來發現一個TAG可以不只一個表
哈夫曼表
哈夫曼表的儲存格式如下:
名稱 | 長度(bit) | 備註 |
---|---|---|
$Lh$ | 16 | 表示這一個TAG的長度(包括TAG型別的兩個位元組) |
$Tc$ | 4 | Table class,0=DC表,1=AC表 |
$Th$ | 4 | Huffman table destination identifier,表示該哈夫曼表的id |
$L_i$ | 8 | 表示這一長度的編碼個數 |
$V_{i,j}$ | 8 | 表示編碼前的原始資料 |
一個DHT中的哈夫曼表個數可以透過長度Lh算出:
$$
Lh = 2 + \sum_{t=1}^{n} (17+m_t)
$$
其中
$$
m_t=\sum_{1}^{16} L_i
$$
除了TAG型別和Lh一個TAG只有一個外,其餘的都是每個哈夫曼表都有的。
Th之後是一個長度為16位元組的陣列,分別對應長度從1bit到16bit的編碼個數。
再之後存的是各個編碼對應的原始資料(以位元組為單位)。JPEG採用的正規化哈夫曼編碼,可以這些資訊推匯出資料編碼前後的對應關係。
量化表
DQT的結構與DHT結構相似,比DHT還稍微簡單一些
名稱 | 長度(bit) | 備註 |
---|---|---|
Lq | 16 | 與Lh意義相同,表示這一TAG的長度 |
Pq | 4 | 量化表的精度,0=8bit,1=16bit |
Pq | 4 | 量化表的id |
Qk | 8 | 量化表中的資料 |
量化表大小固定為8x8,也就是一個表有64個數,DQT長度與量化表個數也有類似的關係:
$$
Pq = 2 + \sum_{t=1}^n (65 + 64 \times Pq(t))
$$
SOF
SOF(Start of Frame) TAG的結構如下:
名稱 | 長度(bit) | 備註 |
---|---|---|
Lf | 16 | 這一TAG的長度 |
P | 8 | 取樣精度 |
Y | 16 | 圖片的高度 |
X | 16 | 圖片的寬度 |
Nf | 4 | Component的個數 |
Component的結構如下:
名稱 | 長度(bit) | 備註 |
---|---|---|
Ci | 8 | Compoenent的id |
Hi | 4 | 水平縮放因子 |
Vi | 4 | 垂直縮放因子 |
Tqi | 8 | 對應的量化表id |
根據我的理解,這裡的Component個數相當於色度分量的個數,比如RGB和YUV都是3,灰度影像則是1.
SOS
名稱 | 長度(bit) | 備註 |
---|---|---|
Ls | 16 | 這一TAG的長度 |
Ns | 8 | 一個scan內的component數量 |
Ss | 8 | 沒用 |
Se | 8 | 沒用 |
Ah | 4 | 沒用 |
Al | 4 | 沒用 |
Scan中還描述了這一Scan內不同Component中哈夫曼表和量化表的對應關係:
名稱 | 長度(bit) | 備註 |
---|---|---|
Csi | 8 | 透過id選擇Component |
Tdi | 4 | 透過id選擇DC哈夫曼表 |
Tai | 4 | 透過id選擇AC哈夫曼表 |
到這裡JPEG解碼所需要的幾個重要TAG的結構就介紹完了,接下來就做好準備工作就可以開始解碼了。
參考資料
JPEG解碼系列部落格:多媒體-編解碼 - 隨筆分類 - OnlyTime_唯有時光 - 部落格園 (cnblogs.com)
JPEG標準:Microsoft Word - T081E.DOC (w3.org)
一個Rust寫的JPEG解碼器:MROS/jpeg_tutorial: 跟我寫 JPEG 解碼器 (Write a JPEG decoder with me) (github.com)
友情連結
我學習過程中寫的JPEG圖片檢視器:Ryan1202/my-tiny-jpeg-viewer: A Tiny Jpeg Viewer (github.com)