JPEG原理分析及其解码器的调试
目录
一、实验目的
掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
二、实验内容
(一)实验原理
1、JPEG文件介绍及格式
(1)JPEG简介
JPEG( Joint Photographic Experts Group),联合图像专家组,是用于连续色调静态图像压缩的一种标准。主要采用的是预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式。属于有损压缩格式,能够将图像压缩在很小的储存空间,一定程度上会造成图像数据的损伤。
(2)JPEG文件格式
-
SOI :Start of Image,图像开始;标记代码:2字节,固定值:0xFFD8
-
APP0:Application,应用程序,保留标记 0;标记代码 :2字节,固定值:0xFFE0
包含9个具体字段:- ① 数据长度 2字节 ①~⑨9个字段的总长度
- ② 标识符 5字节 固定值0x4A46494600,即字符串“JFIF0”
- ③ 版本号 2字节 一般是0x0102,表示JFIF的版本号1.2
- ④ X和Y的密度单位 1字节 只有三个值可选:
0:无单位;1:点数/英寸;2:点数/厘米 - ⑤ X方向像素密度 2字节 取值范围未知
- ⑥ Y方向像素密度 2字节 取值范围未知
- ⑦ 缩略图水平像素数目 1字节 取值范围未知
- ⑧ 缩略图垂直像素数目 1字节 取值范围未知
- ⑨ 缩略图RGB位图 长度可能是3的倍数 缩略图RGB位图数据
-
DQT:Define Quantization Table,定义量化表 ;标记代码:2字节,固定值:0xFFDB
包含9个具体字段:- ① 数据长度 2字节 字段①和多个字段②的总长度
- ② 量化表 数据长度-2字节
- a) 精度及量化表ID 1字节
高4位:精度,只有两个可选值 0:8位;1:16位
低4位:量化表ID,取值范围为0~3 - b) 表项 (64×(精度+1))字节
例如8位精度的量化表,其表项长度为64×(0+1)=64字节
本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次
- a) 精度及量化表ID 1字节
-
SOF0:Start of Frame,帧图像开始;标记代码:2字节;固定值:0xFFC0
包含9个具体字段:- ① 数据长度 2字节 ①~⑥六个字段的总长度
- ② 精度 1字节 每个数据样本的位数 通常是8位,一般软件都不支持 12位和16位
- ③ 图像高度 2字节 图像高度(单位:像素)
- ④ 图像宽度 2字节 图像宽度(单位:像素)
- ⑤ 颜色分量数 1字节 只有3个数值可选 1:灰度图;3:YCrCb或YIQ;4:CMYK 而JFIF中使用YCrCb,故这里颜色分量数恒为3
- ⑥颜色分量信息 颜色分量数×3字节(通常为9字节)
- a)颜色分量ID 1字节
- b)水平/垂直采样因子 1字节
高4位:水平采样因子
低4位:垂直采样因子 - c) 量化表 1字节 当前分量使用的量化表的ID
-
DHT:Define Huffman Table,定义哈夫曼表;标记代码:2字节;固定值:0xFFC4
包含2个具体字段:- ① 数据长度 2字节
- ② huffman表 数据长度-2字节
表ID和表类型 1字节
高4位:类型,只有两个值可选
0:DC直流;1:AC交流 低4位:哈夫曼表ID,
注意,DC表和AC表分开编码
不同位数的码字数量 16字节
编码内容:16个不同位数的码字数量之和(字节)本标记段中,字段②可以重复出现(一般4次),也可以只出现1次。
-
SOS:Start of Scan,扫描开始:12字节;标记代码:2字节;固定值:0xFFDA
包含2个具体字段:- ①数据长度 2字节 ①~④两个字段的总长度
- ②颜色分量数 1字节 应该和SOF中的字段⑤的值相同,即: 1:灰度图是;3: YCrCb或YIQ;4:CMYK。
- ③颜色分量信息
- a) 颜色分量ID 1字节
- b) 直流/交流系数表号 1字节
高4位:直流分量使用的哈夫曼树编号
低4位:交流分量使用的哈夫曼树编号
- ④ 压缩图像数据
- a)谱选择开始 1字节 固定值0x00
- b)谱选择结束 1字节 固定值0x3F
- c)谱选择 1字节 在基本JPEG中总为00
-
EOI:End of Image,图像结束:2字节;标记代码:2字节,固定值:0xFFD9
2、JPEG编码过程
JPEG编码的过程如下图所示,解码是编码的逆过程。
(二)程序实现
1、读取文件
enum std_markers {
DQT = 0xDB, /* Define Quantization Table */
SOF = 0xC0, /* Start of Frame (size information) */
DHT = 0xC4, /* Huffman Table */
SOI = 0xD8, /* Start of Image */
SOS = 0xDA, /* Start of Scan */
RST = 0xD0, /* Reset Marker d0 -> .. */
RST7 = 0xD7, /* Reset Marker .. -> d7 */
EOI = 0xD9, /* End of Image */
DRI = 0xDD, /* Define Restart Interval */
APP0 = 0xE0,
};
解析 Segment Marker
static int parse_SOS(struct jdec_private *priv, const unsigned char *stream)
{
unsigned int i, cid, table;
unsigned int nr_components = stream[2];
#if TRACE
fprintf(p_trace,"> SOS marker\n");
fflush(p_trace);
#endif
#if SANITY_CHECK
if (nr_components != 3)
snprintf(error_string, sizeof(error_string),"We only support YCbCr image\n");
#endif
stream += 3;
for (i=0;i<nr_components;i++) {
cid = *stream++;
table = *stream++;
#if SANITY_CHECK
if ((table&0xf)>=4)
snprintf(error_string, sizeof(error_string),"We do not support more than 2 AC Huffman table\n");
if ((table>>4)>=4)
snprintf(error_string, sizeof(error_string),"We do not support more than 2 DC Huffman table\n");
if (cid != priv->component_infos[i].cid)
snprintf(error_string, sizeof(error_string),"SOS cid order (%d:%d) isn't compatible with the SOF marker (%d:%d)\n",
i, cid, i, priv->component_infos[i].cid);
#if TRACE
fprintf(p_trace,"ComponentId:%d tableAC:%d tableDC:%d\n", cid, table&0xf, table>>4);
fflush(p_trace);
#endif
#endif
priv->component_infos[i].AC_table = &priv->HTAC[table&0xf];
priv->component_infos[i].DC_table = &priv->HTDC[table>>4];
}
priv->stream = stream+3;
#if TRACE
fprintf(p_trace,"< SOS marker\n");
fflush(p_trace);
#endif
return 0;
}
2、解析DQT
- 得到量化表长度(可能包含多张量化表)
- 得到量化表的精度
- 得到及检查量化表的序号(只能是 0 —— 3)
- 得到量化表内容(64 个数据)
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
int qi;
float *table;
const unsigned char *dqt_block_end;
FILE* DQTfile;
#if TRACE
fprintf(p_trace,"> DQT marker\n");
fflush(p_trace);
#endif
dqt_block_end = stream + be16_to_cpu(stream);
stream += 2; /* Skip length */
while (stream < dqt_block_end)
{
qi = *stream++;
#if SANITY_CHECK
if (qi>>4)
snprintf(error_string, sizeof(error_string),"16 bits quantization table is not supported\n");
if (qi>4)
snprintf(error_string, sizeof(error_string),"No more 4 quantization table is supported (got %d)\n", qi);
#endif
table = priv->Q_tables[qi];
build_quantization_table(table, stream);
stream += 64;
}
#if TRACE
fprintf(p_trace,"< DQT marker\n");
fflush(p_trace);
//************************************************
DQTfile = fopen("DQTfile.txt", "a");
fputs("量化表\n", DQTfile);
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
fprintf(DQTfile, "%f ", *table);
table++;
}
fputs("\n", DQTfile);
}
//************************************************
#endif
return 0;
}
zig-zag 排序
static const unsigned char zigzag[64] =
{
0, 1, 5, 6, 14, 15, 27, 28,
2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43,
9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63
};
static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
/* Taken from libjpeg. Copyright Independent JPEG Group's LLM idct.
* For float AA&N IDCT method, divisors are equal to quantization
* coefficients scaled by scalefactor[row]*scalefactor[col], where
* scalefactor[0] = 1
* scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7
* We apply a further scale factor of 8.
* What's actually stored is 1/divisor so that the inner loop can
* use a multiplication rather than a division.
*/
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];
}
}
}
3、解析SOF0
- 得到每个 sample 的比特数、长宽、颜色分量数
- 得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表
序号(与 DQT 中序号对应)
static int parse_SOF(struct jdec_private *priv, const unsigned char *stream)
{
int i, width, height, nr_components, cid, sampling_factor;
int Q_table;
struct component *c;
#if TRACE
fprintf(p_trace,"> SOF marker\n");
fflush(p_trace);
#endif
print_SOF(stream);
height = be16_to_cpu(stream+3);
width = be16_to_cpu(stream+5);
nr_components = stream[7];
#if SANITY_CHECK
if (stream[2] != 8)
snprintf(error_string, sizeof(error_string),"Precision other than 8 is not supported\n");
if (width>JPEG_MAX_WIDTH || height>JPEG_MAX_HEIGHT)
snprintf(error_string, sizeof(error_string),"Width and Height (%dx%d) seems suspicious\n", width, height);
if (nr_components != 3)
snprintf(error_string, sizeof(error_string),"We only support YUV images\n");
if (height%16)
snprintf(error_string, sizeof(error_string),"Height need to be a multiple of 16 (current height is %d)\n", height);
if (width%16)
snprintf(error_string, sizeof(error_string),"Width need to be a multiple of 16 (current Width is %d)\n", width);
#endif
stream += 8;
for (i=0; i<nr_components; i++) {
cid = *stream++;
sampling_factor = *stream++;
Q_table = *stream++;
c = &priv->component_infos[i];
#if SANITY_CHECK
c->cid = cid;
if (Q_table >= COMPONENTS)
snprintf(error_string, sizeof(error_string),"Bad Quantization table index (got %d, max allowed %d)\n", Q_table, COMPONENTS-1);
#endif
c->Vfactor = sampling_factor&0xf;
c->Hfactor = sampling_factor>>4;
c->Q_table = priv->Q_tables[Q_table];
#if TRACE
fprintf(p_trace,"Component:%d factor:%dx%d Quantization table:%d\n",
cid, c->Hfactor, c->Hfactor, Q_table );
fflush(p_trace);
#endif
}
priv->width = width;
priv->height = height;
#if TRACE
fprintf(p_trace,"< SOF marker\n");
fflush(p_trace);
#endif
return 0;
}
static void print_SOF(const unsigned char *stream)
{
int width, height, nr_components, precision;
#if TRACE
const char *nr_components_to_string[] = {
"????",
"Grayscale",
"????",
"YCbCr",
"CYMK"
};
#endif
precision = stream[2];
height = be16_to_cpu(stream+3);
width = be16_to_cpu(stream+5);
nr_components = stream[7];
#if TRACE
fprintf(p_trace,"> SOF marker\n");
fprintf(p_trace,"Size:%dx%d nr_components:%d (%s) precision:%d\n",
width, height,
nr_components, nr_components_to_string[nr_components],
precision);
fflush(p_trace);
#endif
}
4、解析DHT
- 得到 Huffman 表的类型(AC、DC)、序号
- 依据数据重建 Huffman 表
static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)
{
unsigned int count, i;
unsigned char huff_bits[17];
int length, index;
FILE* HuffFile;
HuffFile = fopen("huffmanFile.txt", "a");
length = be16_to_cpu(stream) - 2;
stream += 2; /* Skip length */
#if TRACE
fprintf(p_trace,"> DHT marker (length=%d)\n", length);
fflush(p_trace);
#endif
while (length>0) {
index = *stream++;
/* We need to calculate the number of bytes 'vals' will takes */
huff_bits[0] = 0;
count = 0;
for (i=1; i<17; i++) {
huff_bits[i] = *stream++;
count += huff_bits[i];
}
#if SANITY_CHECK
if (count >= HUFFMAN_BITS_SIZE)
snprintf(error_string, sizeof(error_string),"No more than %d bytes is allowed to describe a huffman table", HUFFMAN_BITS_SIZE);
if ( (index &0xf) >= HUFFMAN_TABLES)
snprintf(error_string, sizeof(error_string),"No more than %d Huffman tables is supported (got %d)\n", HUFFMAN_TABLES, index&0xf);
#if TRACE
fprintf(p_trace,"Huffman table %s[%d] length=%d\n", (index&0xf0)? "AC" : "DC", index & 0xf, count);//index低位代表是几号表
fflush(p_trace);
//*****************************************************************************************************
fprintf(HuffFile, "Huffman table %s[%d] length=%d\r\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);
fflush(HuffFile);
//*****************************************************************************************************
#endif
#endif
if (index & 0xf0 )
build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);
else
build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);
length -= 1;
length -= 16;
length -= count;
stream += count;
}
#if TRACE
fprintf(p_trace,"< DHT marker\n");
fflush(p_trace);
#endif
return 0;
}
5、解析SOS
- 得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号(与 DHT中序号对应)
static int parse_SOS(struct jdec_private *priv, const unsigned char *stream)
{
unsigned int i, cid, table;
unsigned int nr_components = stream[2];
#if TRACE
fprintf(p_trace,"> SOS marker\n");
fflush(p_trace);
#endif
#if SANITY_CHECK
if (nr_components != 3)
snprintf(error_string, sizeof(error_string),"We only support YCbCr image\n");
#endif
stream += 3;
for (i=0;i<nr_components;i++) {
cid = *stream++;
table = *stream++;
#if SANITY_CHECK
if ((table&0xf)>=4)
snprintf(error_string, sizeof(error_string),"We do not support more than 2 AC Huffman table\n");
if ((table>>4)>=4)
snprintf(error_string, sizeof(error_string),"We do not support more than 2 DC Huffman table\n");
if (cid != priv->component_infos[i].cid)
snprintf(error_string, sizeof(error_string),"SOS cid order (%d:%d) isn't compatible with the SOF marker (%d:%d)\n",
i, cid, i, priv->component_infos[i].cid);
#if TRACE
fprintf(p_trace,"ComponentId:%d tableAC:%d tableDC:%d\n", cid, table&0xf, table>>4);
fflush(p_trace);
#endif
#endif
priv->component_infos[i].AC_table = &priv->HTAC[table&0xf];
priv->component_infos[i].DC_table = &priv->HTDC[table>>4];
}
priv->stream = stream+3;
#if TRACE
fprintf(p_trace,"< SOS marker\n");
fflush(p_trace);
#endif
return 0;
}
6、MCU
- 依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个 MCU 中 8*8宏块的个数
- 对每个宏块进行 Huffman 解码,得到 DCT 系数
- 对每个宏块的 DCT 系数进行 IDCT,得到 Y、Cb、Cr
- 遇到 Segment Marker RST 时,清空之前的 DC DCT 系数
- 解完所有 MCU,解码结束
- 将 Y、Cb、Cr 转化为需要的色彩空间并保存。
static void decode_MCU_1x1_3planes(struct jdec_private *priv)
{
// Y
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y, 8);
// Cb
process_Huffman_data_unit(priv, cCb);
IDCT(&priv->component_infos[cCb], priv->Cb, 8);
// Cr
process_Huffman_data_unit(priv, cCr);
IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}
/*
* Decode a 1x1 directly in 1 color
*/
static void decode_MCU_1x1_1plane(struct jdec_private *priv)
{
// Y
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y, 8);
// Cb
process_Huffman_data_unit(priv, cCb);
IDCT(&priv->component_infos[cCb], priv->Cb, 8);
// Cr
process_Huffman_data_unit(priv, cCr);
IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}
/*
* Decode a 2x1
* .-------.
* | 1 | 2 |
* `-------'
*/
static void decode_MCU_2x1_3planes(struct jdec_private *priv)
{
// Y
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y, 16);
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y+8, 16);
// Cb
process_Huffman_data_unit(priv, cCb);
IDCT(&priv->component_infos[cCb], priv->Cb, 8);
// Cr
process_Huffman_data_unit(priv, cCr);
IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}
/*
* Decode a 2x1
* .-------.
* | 1 | 2 |
* `-------'
*/
static void decode_MCU_2x1_1plane(struct jdec_private *priv)
{
// Y
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y, 16);
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y+8, 16);
// Cb
process_Huffman_data_unit(priv, cCb);
// Cr
process_Huffman_data_unit(priv, cCr);
}
/*
* Decode a 2x2
* .-------.
* | 1 | 2 |
* |---+---|
* | 3 | 4 |
* `-------'
*/
static void decode_MCU_2x2_3planes(struct jdec_private *priv)
{
// Y
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y, 16);
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y+8, 16);
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y+64*2, 16);
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y+64*2+8, 16);
// Cb
process_Huffman_data_unit(priv, cCb);
IDCT(&priv->component_infos[cCb], priv->Cb, 8);
// Cr
process_Huffman_data_unit(priv, cCr);
IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}
/*
* Decode a 2x2 directly in GREY format (8bits)
* .-------.
* | 1 | 2 |
* |---+---|
* | 3 | 4 |
* `-------'
*/
static void decode_MCU_2x2_1plane(struct jdec_private *priv)
{
// Y
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y, 16);
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y+8, 16);
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y+64*2, 16);
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y+64*2+8, 16);
// Cb
process_Huffman_data_unit(priv, cCb);
// Cr
process_Huffman_data_unit(priv, cCr);
}
/*
* Decode a 1x2 mcu
* .---.
* | 1 |
* |---|
* | 2 |
* `---'
*/
static void decode_MCU_1x2_3planes(struct jdec_private *priv)
{
// Y
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y, 8);
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y+64, 8);
// Cb
process_Huffman_data_unit(priv, cCb);
IDCT(&priv->component_infos[cCb], priv->Cb, 8);
// Cr
process_Huffman_data_unit(priv, cCr);
IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}
/*
* Decode a 1x2 mcu
* .---.
* | 1 |
* |---|
* | 2 |
* `---'
*/
static void decode_MCU_1x2_1plane(struct jdec_private *priv)
{
// Y
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y, 8);
process_Huffman_data_unit(priv, cY);
IDCT(&priv->component_infos[cY], priv->Y+64, 8);
// Cb
process_Huffman_data_unit(priv, cCb);
// Cr
process_Huffman_data_unit(priv, cCr);
}
7、tinyjpeg.c完整程序
/*
* Small jpeg decoder library
*
* Copyright (c) 2006, Luc Saillard <luc@saillard.org>
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of the author nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "stdint.h"
#include <errno.h>
#include "tinyjpeg.h"
#include "tinyjpeg-internal.h"
#define snprintf _snprintf
enum std_markers {
DQT = 0xDB, /* Define Quantization Table */
SOF = 0xC0, /* Start of Frame (size information) */
DHT =