根据JPEG编码的流程,将一个JPEG编码的图像解码为YUV的原始像素图像。
实现了1×1宏块格式的解码,并输出为YUV444格式。
以Luc Saillard的jpeg_minidec作为范例。
由于没有时间仔细阅读JPEG标准,因此编写过程中借助范例调试对照了各个解码环节,并移植了范例中的部分代码到自己的项目中:
程序按照三个层次:
- BITIO提供文件/内存的比特流输入输出,比特流支持是为了适配Huffman解码的操作,方便逐比特读入数据。
- JPEG头解析和图像数据解码,包括文件头解析、Huffman解码 、IDCT等。
- 用户函数,包括加载JPEG文件,打印JPEG基本信息,解码,转码,保存几个主要函数。
另外还编写了一些用于调试的函数,如TRACE日志功能,用于跟踪打印程序调试输出。
JPEG格式解析
以下是JPEG编码的基本框图:
下面对编码过程做简要描述,重点是为了逆推解码过程。
- 零偏置
- 切分为若干的8×8宏块,并进行DCT变换得到8×8的DCT系数。
- 使用量化表对变换结果进行标量量化,DCT系数经量化后,取直流系数(直流系数为DCT系数的第一个),不同宏块之间的直流系数再经差分后再做Huffman编码。
- 对于交流系数,先经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有两个数组
- BitTable,指明了不同长度的码字的个数。BitTable所有值的总和即为用到的码字的个数(亦即下面ValueTable的长度),根据不同长度的码字个数,可以将码字逐个生成出来。
- ValueTable,每一个码字都对应一个权重(weight),这些权重值存储在ValueTable。权重值在直流、交流解码时有不同的意义。
由BitTable生成Huffman码字:
-
第一个码字必定为0
- 如果第一个码字位数为1,则码字为0
- 如果第二个码字位数为2,则码字为00,以此类推
- 从第二码字开始,如果它和它前面的码字位数相同,则当前码字为它前面的码字加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)