JPEG格式研究——(4)反量化、逆ZigZag變化和IDCT變換

迷路的鹿1202發表於2024-11-24

反量化

反量化其實很簡單,將霍夫曼解碼出來的資料乘上對應的量化表就好了

透過當前色度選擇出SOF中的Component,其中的Tqi指出了這一色度所需的量化表id

image.png

Component的結構如下:

名稱 長度(bit) 備註
Ci 8 Compoenent的id
Hi 4 水平縮放因子
Vi 4 垂直縮放因子
Tqi 8 對應的量化表id

然後就可以根據量化表id找到量化表,將其中每一個元素與霍夫曼解碼的結果相乘就OK了

// 就是簡單粗暴的直接相乘
for (int i = 0; i < 8; ++i) {
    for (int j = 0; j < 8; ++j) {
        result[i][j] = input[i][j] * dqt[i][j];
    }
}

逆ZigZag變換

逆ZigZag變換實際上和ZigZag變換做的是同樣的操作,直接按Z字形重新排序就好

參考程式碼

// 直接查表大法
const int zigzag_table[8*8][2] = {
    {0, 0}, {0, 1}, {1, 0}, {2, 0}, {1, 1}, {0, 2}, {0, 3}, {1, 2},
    {2, 1}, {3, 0}, {4, 0}, {3, 1}, {2, 2}, {1, 3}, {0, 4}, {0, 5},
    {1, 4}, {2, 3}, {3, 2}, {4, 1}, {5, 0}, {6, 0}, {5, 1}, {4, 2},
    {3, 3}, {2, 4}, {1, 5}, {0, 6}, {0, 7}, {1, 6}, {2, 5}, {3, 4},
    {4, 3}, {5, 2}, {6, 1}, {7, 0}, {7, 1}, {6, 2}, {5, 3}, {4, 4},
    {3, 5}, {2, 6}, {1, 7}, {2, 7}, {3, 6}, {4, 5}, {5, 4}, {6, 3},
    {7, 2}, {7, 3}, {6, 4}, {5, 5}, {4, 6}, {3, 7}, {4, 7}, {5, 6},
    {6, 5}, {7, 4}, {7, 5}, {6, 6}, {5, 7}, {6, 7}, {7, 6}, {7, 7}
};
void zigzag_transform(int input[8][8], int output[8][8]) {
    for (int i = 0; i < 8*8; ++i) {
        int row = zigzag_table[i][0];
        int col = zigzag_table[i][1];
        output[row][col] = input[i/8][i%8];
    }
}

IDCT變換

最後再進行IDCT2D,IDCT變換就是DCT變換的逆變換,IDCT2D就是橫著變換一次再豎著變換一次。這個更多是數學上的東西我就不多說了,怕誤人子弟

話不多說,上程式碼:

pub struct DCT {
    pub idct2d_data: [[[[f32; 8]; 8]; 8]; 8],
}

fn cc(i: usize, j: usize) -> f32 {
    if i == 0 && j == 0 {
        return 1.0 / 2.0;
    } else if i == 0 || j == 0 {
        return 1.0 / (2.0 as f32).sqrt();
    } else {
        return 1.0;
    }
}

impl DCT {
    // 先提前算好一部分
    pub fn new() -> DCT {
        let mut tmp: [[[[f32; 8]; 8]; 8]; 8] = Default::default();
        for i in 0..8 {
            for j in 0..8 {
                for x in 0..8 {
                    let i_cos = ((2 * i + 1) as f32 * PI / 16.0 * x as f32).cos();
                    for y in 0..8 {
                        let j_cos = ((2 * j + 1) as f32 * PI / 16.0 * y as f32).cos();
                        tmp[i][j][x][y] = cc(x, y) * i_cos * j_cos / 4.0;
                    }
                }
            }
        }
        DCT { idct2d_data: tmp }
    }

    pub fn idct2d(&self, data: [[f32; 8]; 8]) -> [[f32; 8]; 8] {
        let mut tmp: [[f32; 8]; 8] = Default::default();
        for i in 0..8 {
            for j in 0..8 {
                tmp[i][j] = {
                    let mut tmp = 0.0;
                    for x in 0..8 {
                        for y in 0..8 {
                            tmp += self.idct2d_data[i][j][x][y] * data[x][y];
                        }
                    }
                    tmp
                };
            }
        }
        tmp
    }
    // 後面還有SSE和AVX加速的程式碼就不放了,直接看原始碼就好了
}

尾聲

到這裡JPEG解碼的部分就全部結束了。需要注意的是,這裡解碼出來的資料顏色格式並不是可以直接輸出到螢幕的RGB888(其實允許這種格式,但很少見,比較常見的是YCbCr格式),還要根據需要處理。

提醒一下,解碼出來的是分割出來的8x8的塊,還要再拼起來

參考資料

部落格園部落格:JPEG解碼——(4)霍夫曼解碼 - 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)

相關文章