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

反量化

反量化其实很简单,将霍夫曼解码出来的数据乘上对应的量化表就好了

通过当前色度选择出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)

posted @ 2024-11-24 13:59  迷路的鹿1202  阅读(12)  评论(0编辑  收藏  举报