【作业】JPEG解码实现

  • Post author:
  • Post category:其他


根据JPEG编码的流程,将一个JPEG编码的图像解码为YUV的原始像素图像。

实现了1×1宏块格式的解码,并输出为YUV444格式。

以Luc Saillard的jpeg_minidec作为范例。

由于没有时间仔细阅读JPEG标准,因此编写过程中借助范例调试对照了各个解码环节,并移植了范例中的部分代码到自己的项目中:

程序按照三个层次:

  1. BITIO提供文件/内存的比特流输入输出,比特流支持是为了适配Huffman解码的操作,方便逐比特读入数据。
  2. JPEG头解析和图像数据解码,包括文件头解析、Huffman解码 、IDCT等。
  3. 用户函数,包括加载JPEG文件,打印JPEG基本信息,解码,转码,保存几个主要函数。

另外还编写了一些用于调试的函数,如TRACE日志功能,用于跟踪打印程序调试输出。



JPEG格式解析

以下是JPEG编码的基本框图:

在这里插入图片描述

下面对编码过程做简要描述,重点是为了逆推解码过程。

  1. 零偏置
  2. 切分为若干的8×8宏块,并进行DCT变换得到8×8的DCT系数。
  3. 使用量化表对变换结果进行标量量化,DCT系数经量化后,取直流系数(直流系数为DCT系数的第一个),不同宏块之间的直流系数再经差分后再做Huffman编码。
  4. 对于交流系数,先经Zig-zag扫描(将有值的的数据集中到一起,提高游程编码和Huffman编码的效果),在进行游程编码和Huffman编码,得到最终的数据。

上面是编码过程的简要描述,没有涉及数据的具体存储方式,下面说一下解码的几个关键环节:



JPEG标记提取

JPEG标记有以下几种类型(摘自Wikipedia):

在这里插入图片描述

标记的起始格式是固定,第一个字节为0xff,第二个字节表明标记类型。



量化

图片自己会携带定义量化表(DQT)(两个,分别用于亮度分量和色度分量)作为参考量化表,将参考量化表再与JPEG规定的量化矩阵逐项相乘,得到用于转换的量化表。

DQT的结构为

byte[64]

JPEG规定的量化表考虑了人眼视觉特性对不同频率分量的敏感特性:对低频敏感,对高频不敏感,因此对低频数据采用了细量化,对高频数据采用了粗量化。

static const double aanscalefactor[8] = {
   
   1.0, 1.387039845, 1.306562965, 1.175875602, 1.0, 0.785694958, 0.541196100, 0.275899379};

这是用到的量化表的参数值,从左到右分别对应8x8DCT变换后的直流、低频到高频系数,可以看到越到高频越倾向于将数值缩小(这样量化得到的系数越接近0),对于低频处的系数,倾向于放大数值,以实现比较精细的量化(在后续计算中降低舍入误差)。

下面是从定义量化表(DQT)构建最终可用的量化表的方式(取自jpeg_minidec)

void jpeg_build_quantization_table(float *qtable, byte * ref_table) {
   
    int i, j;
    static const double aanscalefactor[8] = {
   
        1.0, 1.387039845, 1.306562965, 1.175875602,
        1.0, 0.785694958, 0.541196100, 0.275899379};
    const unsigned char *zz = zigzag;

    for (i = 0; i < 8; i++)
    {
   
        for (j = 0; j < 8; j++)
        {
   
            *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
        }
    }
}



Huffman编码

图片会携带定义Huffman表(DHT),共4个,分别用于亮度信号的直流、交流编码和色度信号的直流、交流编码。每个DHT会有一个自己编号,表明自己用于哪个信号的编码。

每个DHT有两个数组

  1. BitTable,指明了不同长度的码字的个数。BitTable所有值的总和即为用到的码字的个数(亦即下面ValueTable的长度),根据不同长度的码字个数,可以将码字逐个生成出来。
  2. ValueTable,每一个码字都对应一个权重(weight),这些权重值存储在ValueTable。权重值在直流、交流解码时有不同的意义。

由BitTable生成Huffman码字:

  1. 第一个码字必定为0

    1. 如果第一个码字位数为1,则码字为0
    2. 如果第二个码字位数为2,则码字为00,以此类推
  2. 从第二码字开始,如果它和它前面的码字位数相同,则当前码字为它前面的码字加1;如果它的位数比它前面的码字位数大,则当前码字的前面的码字加1后再在后面若干个零,直至满足位数长度为止。

下面给出了上述方法的具体实现(各种结构体的定义见源码):

void jpeg_build_huff_table(DHTInfo *dht_info) {
   
    HuffLookupTable * table = &dht_info->huff_table;
    int sum = 0;
    for(int i=0;i<16;i++){
   
        byte t = dht_info->bit_table[i];
        sum += t;
    };

    table->code = (word *) malloc(sizeof(word) * sum);
    table->len = (byte *) malloc(sizeof(byte) * sum);
    table->weight = (byte *) malloc(sizeof(byte) * sum)



版权声明:本文为weixin_41177606原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。