目录
一、实验目的
掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
二、实验原理
1.JPEG简介
JPEG是Joint Photographic Experts Group的缩写,即ISO和IEC联合图像专家组,负责静态图像压缩标准的规定。这个专家组开发的算法称为JPEG算法,并已成为当前的通用标准,即JPEG标准。遵照JPEG标准建立的图像文件使用的格式称为JFIF格式,文件名称的后缀为“.jpg”或者“.jpeg”。
JPEG主要是采用预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式,以去除冗余的图像和彩色数据,属于有损压缩格式,它能够将图像压缩在很小的储存空间,一定程度上会造成图像数据的损伤。尤其是使用过高的压缩比例,将使最终解压缩后恢复的图像质量降低,如果追求高品质图像,则不宜采用过高的压缩比例。
2.JPEG文件格式
1.Segment 的组织形式
JPEG 在文件中以 Segment 的形式组织,它具有以下特点:
(1)均以 0xFF 开始,后跟 1 byte 的 Marker 和 2 byte 的 Segment length(包含表示Length 本身所占用的 2 byte,不含“0xFF” + “Marker” 所占用的 2 byte);
(2)采用 Motorola 序(相对于 Intel 序),即保存时高位在前,低位在后;
(3)Data 部分中,0xFF 后若为 0x00,则跳过此字节不予处理;
2.常见marker如下:
(1)SOI&EOI:
SOI ,Start of Image, 图像开始
标记代码 2字节 固定值0xFFD8
EOI,End of Image, 图像结束 2字节
标记代码 2字节 固定值0xFFD9
(2)APP0 应用程序保留标记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位图数据
(3)DQT 定义量化表 :
标记代码 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次
(4)SOF0 帧图像开始 :
标记代码 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
(5)DHT,定义哈夫曼表:
标记代码 2字节 固定值0xFFC4
包含2个具体字段:
① 数据长度 2字节
② huffman表 数据长度-2字节
表ID和表类型 1字节
高4位:类型,只有两个值可选 0:DC直流;1:AC交流
低4位:哈夫曼表ID,注意,DC表和AC表分开编码
不同位数的码字数量 16字节
编码内容 16个不同位数的码字数量之和(字节)
本标记段中,字段②可以重复出现(一般4次),也可以只出现1次。
(6)SOS 扫描开始:
标记代码 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
3.JPEG编解码原理
JPEG编码的过程如上图所示,解码是编码的逆过程。
图像进行JPEG编码前需注意输入信号的数据空间:
(1)JPEG编码实现中通常将高度相关的RGB颜色空间转换到相关性较小的YUV颜色空间。图像的主要信息包括在Y通道。U、V更平滑且人眼对色度分量不敏感,所以色度分量更易压缩以及可对色度分量进行下采样,如:4:2:2。
(2)对图像从左到右,从上到下进行8*8分块,宽高不足的部分可复制与其最近像素的值。
编码器流程如下:
(1)零偏置(Level Offset)
对于灰度级是2^n 的像素,通过减去2^n-1,将无符号的整数值变成有符号数,目的是使像素的绝对值出现3位10进制的概率大大减少。零偏置后,YUV取值同一空间了。
(2)DCT变换
对每个单独的彩色图像分量,把整个分量图像分成8*8的图像块,并作为二维离散余弦变换DCT的输入。
(3)量化
因为人眼对亮度信号比对色度信号更敏感,所以使用了两种量化表:亮度量化表和色度量化表。
根据人眼的视觉特性(对低频敏感)对低频分量细量化,对高频分量粗量化。所以如果原始图像细节丰富,则压缩时去掉的数据较多。
(4)编码分为两路,一路对DC系数进行编码,另一路对AC系数进行编码:
·DC 系数的差分编码:
由于直流系数 F(0,0)反映了该子图像中包含的直流成分,通常较大,又由于两个相邻的子图像的直流系数通常具有较大的相关性,所以对 DC 系数采用差值脉冲编码(DPCM),即对本像素块直流系数与前一像素块直流系数的差值进行无损编码。
·AC 系数编码:
首先,进行游程编码(RLC),并在最后加上块结束码(EOB);然后,系数序列分组,将非零系数和它前面的相邻的全部零系数分在一组内;每组用两个符号表示[(Run,Size),(Amplitude)]
Amplitude:表示非零系数的幅度值;
Run:表示零的游程即零的个数;
Size:表示非零系数的幅度值的编码位数;
AC系数的Z字扫描:
经DCT变换以后,系数大多数集中在左上角,即低频分量区,所以采用Z字形按频率的高低顺序读出,可出现很多连零的机会。
JPEG 的解码流程
1.读取文件
2. 解析 Segment Marker
(1)解析 SOI
(2)解析 APP0
·检查标识“JFIF”及版本
·得到一些参数
(3)解析 DQT
·得到量化表长度(可能包含多张量化表)
·得到量化表的精度
·得到及检查量化表的序号(只能是 0 —— 3)
·得到量化表内容(64 个数据)
(4) 解析 SOF0
·得到每个 sample 的比特数、长宽、颜色分量数
·得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表
序号(与 DQT 中序号对应)
(5) 解析 DHT
·得到 Huffman 表的类型(AC、DC)、序号
·依据数据重建 Huffman 表
(6)解析 SOS
·得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号(与 DHT中序号对应)
3. 依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个 MCU 中 8*8宏块的个数
4. 对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU 中每个分量宏块解码)
(1) 对每个宏块进行 Huffman 解码,得到 DCT 系数
(2) 对每个宏块的 DCT 系数进行 IDCT,得到 Y、Cb、Cr
(3) 遇到 Segment Marker RST 时,清空之前的 DC DCT 系数
5. 解析到 EOI,解码结束
6. 将 Y、Cb、Cr 转化为需要的色彩空间并保存。
三、程序调试过程中,理解程序设计的整体框架
1.调试JPEG解码器程序,分析程序设计:
(1)三个结构体设计目的:
- struct huffman_table:存储huffman码表
struct huffman_table
{
/* Fast look up table, using HUFFMAN_HASH_NBITS bits we can have directly the symbol,
* if the symbol is <0, then we need to look into the tree table */
short int lookup[HUFFMAN_HASH_SIZE];
/* code size: give the number of bits of a symbol is encoded */
unsigned char code_size[HUFFMAN_HASH_SIZE];
/* some place to store value that is not encoded in the lookup table
* FIXME: Calculate if 256 value is enough to store all values
*/
uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];
};
- struct component:存储当前8*8像块中有关解码的信息
struct component
{
unsigned int Hfactor; //水平采样因子
unsigned int Vfactor; //垂直采样因子
float *Q_table; //该8*8块使用的量化表
struct huffman_table *AC_table;//该8*8块使用的AC huffman码表
struct huffman_table *DC_table;//该8*8块使用的DC huffman码表
short int previous_DC; //前一个块的直流DCT系数
short int DCT[64]; //DCT系数数组
#if SANITY_CHECK
unsigned int cid;
#endif
};
- struct jdec_private:JPEG数据流的结构体,用于存储JPEG图像的宽高、压缩比特流及huffman码表等信息。包括struct huffman_table和struct component。
struct jdec_private
{
/* Public variables */
uint8_t *components[COMPONENTS];
unsigned int width, height; /* Size of the image */
unsigned int flags;
/* Private variables */
const unsigned char *stream_begin, *stream_end;//存储压缩比特流
unsigned int stream_length;
const unsigned char *stream; /* Pointer to the current stream */
unsigned int reservoir, nbits_in_reservoir;
struct component component_infos[COMPONENTS];
float Q_tables[COMPONENTS][64]; /* quantization tables */
struct huffman_table HTDC[HUFFMAN_TABLES]; /* DC huffman tables */
struct huffman_table HTAC[HUFFMAN_TABLES]; /* AC huffman tables */
int default_huffman_table_initialized;
int restart_interval;
int restarts_to_go; /* MCUs left in this restart interval */
int last_rst_marker_seen; /* Rst marker is incremented each time */
/* Temp space used after the IDCT to store each components */
uint8_t Y[64*4], Cr[64], Cb[64];
jmp_buf jump_state;
/* Internal Pointer use for colorspace conversion, do not modify it !!! */
uint8_t *plane[COMPONENTS];
};
(2)解码器代码整体流程分析:
//输入JPEG文件,进行解码并存储结果
int convert_one_image(const char *infilename, const char *outfilename, int output_format)
{
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 */
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(); //初始化jdec结构体
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)//解析JPEG文件头
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</