一、实验目的
掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
二、实验原理
JPEG编码原理
JPEG编码包含以下几个主要内容:
- 8*8分块。
- 正向离散余弦变换(FDCT)
- 量化(quantization)
- Z字形编码(zigzag scan)
- 使用差分脉冲编码调制(DPCM)对直流系数(DC)进行编码
- 使用行程长度编码(RLE)对交流系数(AC)进行编码。
- 熵编码。
编码框图如图所示,分别对应着上述过程。

JPEG格式文件解析
图像开始SOI标记(0xFFD8)和图像结束EOI标记(0xFFD9)
FFD8: SOI,Start of Image,图像开始,所有的 JPEG 文件必须以 SOI 开始。
FFD9: End of Image,图像结束,JPEG 文件必须以 EOI 结束。

FFDB: DQT,Define Quantization Table,定义量化表
第一个FFDB定义第一张量化表
00 43 00 + qt_table(64字节)
第二个字节43表示这一部分长67字节,第三个字节表示索引号0,qt_table长64字节对应量化表的8*8个值
第二个FFDB定义第二章量化表,不同的是上张表的索引号为 00,这张表的索引号为 01,在后面的SOF0 的部分中我们将会知道上张表对应亮度量化表,这张表对应色度量化表,对这张图来说就这两张量化表。

FFC4: DHT,Define Huffman Table,定义 Huffman 树表
FFC4:标记代码,2 字节,代表定义 Huffman 表。
数据长度: 2 字节,这里是1F, 31 字节的长度(包括长度自身)
Huffman 表 ID 号和类型:1 字节,高 4 位为表的类型,0:DC 直流;1:AC 交流 可以看出这里是直流表;低四位为 Huffman 表 ID。 这里是00,可以看出这张表是直流 DC 的第 0 张表,在后面的扫描开始的部分中我们可以获右为亮度的直流系数表。
不同长度 Huffman 的码字数量:固定为 16 个字节,每个字节代表从长度为 1到长度为 16 的 码 字 的个 数 , 以 表 中 的 分析, 这 16 个字节之后的1+5+1+1+1+1+1+1=12 个字节对应的就是每个符字对应的权值,这些权值的含义即为 DC 系数经 DPCM 编码后幅度值的位长。
通过上面的码长与码字个数的关系来生成相应码长的码字,再对应上之后的权值即位长根据解码得到的位长来读取之后相应长度的码字,再查上面这张可变长二进制编码表,就可以得到直流系数的幅度值,注意这个幅度值是经过 DPCM 差分编码得到的。

** FFDA: 标记代码 SOS,Start of Scan**
数据长度:2 字节,这里是00 0C,长度为 12 字节。
颜色分量数:1 字节 应该和 SOF 中的颜色分量数相同
颜色分量信息:每个分量对应 3 个字节,第一个字节是颜色分量 ID,1,2,3
对应 YUV,第二个字节高位为直流分量使用的哈夫曼树编号,这里 Y 的直流分量用的是 DC 的第 0 张表,低四位代表交流分量使用的哈夫曼树编号,这里 Y的交流分量用的是 AC 的第 0 张表,而两个色度信号的直流分量都用的是 DC
的第 1 张表,交流分量用的是 AC 的第 1 张表.压缩图像数据
a)谱选择开始 1 字节 固定值 0x00
b)谱选择结束 1 字节 固定值 0x3F
c)谱选择 1 字节 在基本 JPEG 中总为 00

三、代码调试与分析
阅读JPEG解码相关代码,分析主要解码部分如下:
tinyjpeg_parse_header 文件头解析
int convert_one_image(const char *infilename, const char *outfilename, int output_format) //读取JPEG文件,开始解码,存储结果
{
FILE *fp;
unsigned int length_of_file; //文件大小
unsigned int width, height; //图像宽高
unsigned char *buf;
struct jdec_private *jdec;
unsigned char *components[3];
/* Load the Jpeg into memory 将JPEG文件读入缓冲区*/
fp = fopen(infilename, "rb");
if (fp == NULL)
exitmessage("Cannot open filename\n");
length_of_file = filesize(fp);
buf = (unsigned char *)malloc(length_of_file + 4);
if (buf == NULL)
exitmessage("Not enough memory for loading file\n");
fread(buf, length_of_file, 1, fp);
fclose(fp);
/* Decompress it */
jdec = tinyjpeg_init(); //初始化
if (jdec == NULL)
exitmessage("Not enough memory to alloc the structure need for decompressing\n");
if (tinyjpeg_parse_header(jdec, buf, length_of_file)<0) //解析JEPG文件头信息
exitmessage(tinyjpeg_get_errorstring(jdec));
/* Get the size of the image 获得图像宽高信息*/
tinyjpeg_get_size(jdec, &width, &height);
snprintf(error_string, sizeof(error_string),"Decoding JPEG image...\n");
if (tinyjpeg_decode(jdec, output_format) < 0) //解码实际数据
exitmessage(tinyjpeg_get_errorstring(jdec));
/*
* Get address for each plane (not only max 3 planes is supported), and
* depending of the output mode, only some components will be filled
* RGB: 1 plane, YUV420P: 3 planes, GREY: 1 plane
*/
tinyjpeg_get_components(jdec, components);
/* Save it 根据所需格式保存输出文件*/
switch (output_format)
{
case TINYJPEG_FMT_RGB24:
case TINYJPEG_FMT_BGR24:
write_tga(outfilename, output_format, width, height, components);
break;
case TINYJPEG_FMT_YUV420P:
write_yuv(outfilename, width, height, components);
break;
case TINYJPEG_FMT_GREY:
write_pgm(outfilename, width, height, components);
break;
}
/* Only called this if the buffers were allocated by tinyjpeg_decode() */
tinyjpeg_free(jdec);
/* else called just free(jdec); */
free(buf);
return 0;
}
parse_JFIF 解析marker标识
int convert_one_image(const char *infilename, const char *outfilename, int output_format) //读取JPEG文件,开始解码,存储结果
{
FILE *fp;
unsigned int length_of_file; //文件大小
unsigned int width, height; //图像宽高
unsigned char *buf;
struct jdec_private *jdec;
unsigned char *components[3];
/* Load the Jpeg into memory 将JPEG文件读入缓冲区*/
fp = fopen(infilename, "rb");
if (fp == NULL)
exitmessage("Cannot open filename\n");
length_of_file = filesize(fp);
buf = (unsigned char *)malloc(length_of_file + 4);
if (buf == NULL)
exitmessage("Not enough memory for loading file\n");
fread(buf, length_of_file, 1, fp);
fclose(fp);
/* Decompress it */
jdec = tinyjpeg_init(); //初始化
if (jdec == NULL)
exitmessage("Not enough memory to alloc the structure need for decompressing\n");
if (tinyjpeg_parse_header(jdec, buf, length_of_file)<0) //解析JEPG文件头信息
exitmessage(tinyjpeg_get_errorstring(jdec));
/* Get the size of the image 获得图像宽高信息*/
tinyjpeg_get_size(jdec, &width, &height);
snprintf(error_string, sizeof(error_string),"Decoding JPEG image...\n");
if (tinyjpeg_decode(jdec, output_format) < 0) //解码实际数据
exitmessage(tinyjpeg_get_errorstring(jdec));
/*
* Get address for each plane (not only max 3 planes is supported), and
* depending of the output mode, only some components will be filled
* RGB: 1 plane, YUV420P: 3 planes, GREY: 1 plane
*/
tinyjpeg_get_components(jdec, components);
/* Save it 根据所需格式保存输出文件*/
switch (output_format)
{
case TINYJPEG_FMT_RGB24:
case TINYJPEG_FMT_BGR24:
write_tga(outfilename, output_format, width, height, components);
break;
case TINYJPEG_FMT_YUV420P:
write_yuv(outfilename, width, height, components);
break;
case TINYJPEG_FMT_GREY:
write_pgm(outfilename, width, height, components);
break;
}
/* Only called this if the buffers were allocated by tinyjpeg_decode() */
tinyjpeg_free(jdec);
/* else called just free(jdec); */
free(buf);
return 0;
}

本文介绍了一个JPEG解码实验,详细解析了JPEG文件的结构及解码流程,包括文件头解析、量化表、Huffman码表的建立等关键步骤,并展示了实验结果。

1万+

被折叠的 条评论
为什么被折叠?



