数据压缩原理实验5 JPEG原理分析及JPEG解码器的调试
班级:17广电工方向三
姓名:利满雯
学号:201710413068
一、实验原理
1. JPEG编码原理(解码是编码的逆过程)
(1)图像预处理
将输入图像分成若干个8×8的小块。在此过程中需对图像的宽和高进行剪裁剪,使其都为8的倍数,不足的部分复制与其最近的像素值。目的是方便进行8×8 DCT变换。
(2)零偏置(Level Offset)
对于灰度级是2n的像素,通过减去2n-1,将无符号的整数值变成有符号数。例如对于n=8,即0255的值域,通过减去128,转换为值域在-128127之间的值。这样做的目的是使像素的绝对值出现3位10进制的概率大大减少。
(3)8×8 DCT变换
对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,再以8×8的图像块为一个单位进行量化和编码处理。由于DCT变换是能量守恒变换,即DCT变换前后图像的能量不变,因此通过DCT变换可以实现能量集中和去相关,有利于提高编码的效率。同时由于人眼对低频敏感,高频不敏感,经DCT变化后,多数低频信息集中在左上角,且数值较大,相关性较小,只需对左上角的值进行细量化,能在保证图像质量的同时提高压缩比。
(4)量化(中平型均匀量化器)
量化步距按照系数所在的位置和颜色分量来确定。Y分量和左上角位置的系数步距小,U、V分量和右下角位置的分量步距大。因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值。根据人眼的对低频敏感而对高频不太敏感视觉特性对低频分量采取较细的量化,对高频分量采取较粗的量化。
(5)编码
a. DC系数的差分编码
8x8图像块经过DCT变换后,得到的DC直流系数有两个特点:数值大、相邻8×8图像块的DC系数值变化不大。根据这个特点,JPEG对DC系数进行差分脉冲调制编码(DPCM)(其中,相邻DC系数之差不在进行量化),对相邻图像块之间的量化DC系数之差DIFF进行Huffman编码。对DIFF用Huffman编码:码字由两部分组成:类别ID和类内索引。通过下表判断DIFF在哪一个范围内,记下类别ID,作为码字的第一部分,DIFF的真值作为码字的第二部分。例:DC=8,上一DC=5,则DIFF=8-5=3 类别ID=2,类内索引=3,则码流=10011。
b.AC系数的游程编码
由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z 字形扫描按频率的高低顺序读出,可以出现很多连零的机会。尤其在最后,如果都是零,给出EOB (End of Block)即可。
之字形扫描扫描后二维数据变成成一维数据,定义(run,level)结构来对此一维数据编码。其中,run表示非0数据前0的个数,level表示非0数据的值。run的最大值为15个,用RRRR表示;level的编码类似DC系数,先分为16个类别,用4位SSSS表示类别号,再查类内索引。对(RRRR, SSSS)联合用Huffman编码,对类内索引采用定长码编码。即交流huffman码字的前四位表示0的个数,后四位表示该交流分量数值的二进制位数,也即接下来需要读入的位数。
2. JPEG文件格式
(1)Segment的组织形式
JPEG 在文件中以 Segment 的形式组织,它具有以下特点:
a. 均以 0xFF 开始,后跟1byte的Marker和2byte的Segment length(包含表示 Length 本身所占用的 2 byte,不含“0xFF”+“Marker”所占用的2byte);
b. 采用 Motorola 序(相对于Intel 序),即保存时高位在前,低位在后;
c. Data 部分中,0xFF 后若为 0x00,则跳过此字节不予处理。
(2)JPEG 的 Segment Marker
(3)文件格式示例
①FFD8:SOI, Start of Image,图像开始,所有的JPEG文件都必须以SOI开始。
②FFE0:Application0,应用程序保留标记0
③FFDB:DQT,Define Quantization Table,定义量化表
④FFC0: SOF0 , Start of Frame, 基线离散余弦变换
⑤FFC4: DHT, Define Huffman Table,定义 Huffman 树表
⑥FFDA: SOS, Start of Scan,扫描开始
⑦熵编码数据
⑧FFD9:EOI,End of Image,图像结束,JPEG 文件必须以 EOI 结束
二、JPEG的解码流程
(1)读取文件
(2)解析 Segment Marker
解析 SOI
解析 APP0
检查标识“JFIF”及版本
得到一些参数
解析 DQT
得到量化表长度(可能包含多张量化表)
得到量化表的精度
得到及检查量化表的序号(只能是 0-3)
得到量化表内容(64 个数据)
解析 SOF0
得到每个 sample 的比特数、长宽、颜色分量数
得到每个颜色分量的ID、水平采样因子、垂直采样因子、使用的量化表序号(与DQT中序号对应)
解析 DHT
得到 Huffman 表的类型(AC、DC)、序号
依据数据重建 Huffman 表
解析 SOS
得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号(与 DHT 中序号对应)
(3)依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个MCU 中8x8宏块的个数
对每个宏块进行 Huffman 解码,得到 DCT 系数
对每个宏块的 DCT 系数进行 IDCT,得到 Y、Cb、Cr
遇到 Segment Marker RST 时,清空之前的 DC DCT 系数
(4)对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU 中每个分量宏块解 码)
(5)解析到 EOI,解码结束
(6)将 Y、Cb、Cr 转化为需要的色彩空间并保存。
三、关键代码分析及实验任务
tinyjpeg.h
#include <stdio.h>
#ifndef __JPEGDEC_H__
#define __JPEGDEC_H__
#ifdef __cplusplus
extern "C" {
#endif
// 添加
static const unsigned char zigzag[64];
struct jdec_private;
/* Flags that can be set by any applications */
#define TINYJPEG_FLAGS_MJPEG_TABLE (1<<1)
/* Format accepted in outout */
enum tinyjpeg_fmt {
TINYJPEG_FMT_GREY = 1,
TINYJPEG_FMT_BGR24,
TINYJPEG_FMT_RGB24,
TINYJPEG_FMT_YUV420P,
};
FILE *p_trace;//add by nxn
static char error_string[256];//add by nxn
struct jdec_private *tinyjpeg_init(void);
void tinyjpeg_free(struct jdec_private *priv);
int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size);
int tinyjpeg_decode(struct jdec_private *priv, int pixel_format);
const char *tinyjpeg_get_errorstring(struct jdec_private *priv);
void tinyjpeg_get_size(struct jdec_private *priv, unsigned int *width, unsigned int *height);
int tinyjpeg_get_components(struct jdec_private *priv, unsigned char **components);
int tinyjpeg_set_components(struct jdec_private *priv, unsigned char **components, unsigned int ncomponents);
int tinyjpeg_set_flags(struct jdec_private *priv, int flags);
static void outdcac(struct jdec_private *priv, FILE *DC_image, FILE *AC_image);
static void getdcac(struct jdec_private *priv);
#ifdef __cplusplus
}
#endif
#endif
#define snprintf _snprintf//add by nxn
// 通过定义TRACE的值为0或1来打开和关闭TRACE
#define TRACE 1//add by nxn
#define TRACEFILE "trace_jpeg.txt"//add by nxn
loadjpeg.c
main函数:打开输入输出文件,解析输出格式
int main(int argc, char *argv[])
{
// 初始化输出文件格式为YUV420P
int output_format = TINYJPEG_FMT_YUV420P;
char *output_filename, *input_filename;
clock_t start_time, finish_time;
unsigned int duration;
int current_argument;
int benchmark_mode = 0;
// 通过在tinyjpeg.h文件中设置TRACE的值为1以及#if TRACE … #endif打开打开TRACE文件,TRACE是用来跟踪和记录解码过程,存放解码过程中得到的数据
#if TRACE
p_trace=fopen(TRACEFILE,"w");
if (p_trace==NULL)
{
printf("trace file open error!");
}
#endif
if (argc < 3)
usage();
current_argument = 1;
while (1)
{
if (strcmp(argv[current_argument], "--benchmark")==0)
benchmark_mode = 1;
else
break;
current_argument++;
}
if (argc < current_argument+2)
usage();
input_filename = argv[current_argument];
if (strcmp(argv[current_argument+1],"yuv420p")==0)
output_format = TINYJPEG_FMT_YUV420P;
else if (strcmp(argv[current_argument+1],"rgb24")==0)
output_format = TINYJPEG_FMT_RGB24;
else if (strcmp(argv[current_argument+1],"bgr24")==0)
output_format = TINYJPEG_FMT_BGR24;
else if (strcmp(argv[current_argument+1],"grey")==0)
output_format = TINYJPEG_FMT_GREY;
else
exitmessage("Bad format: need to be one of yuv420p, rgb24, bgr24, grey\n");
output_filename = argv[current_argument+2];
start_time = clock();
if (benchmark_mode)
load_multiple_times(input_filename, output_filename, output_format);
else
convert_one_image(input_filename, output_filename, output_format);
finish_time = clock();
duration = finish_time - start_time;
snprintf(error_string, sizeof(error_string),"Decoding finished in %u ticks\n", duration);
#if TRACE
fclose(p_trace);
#endif
return 0;
}
convert_one_image:加载一幅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;
// 用来存储JPEG文件中的数据的缓冲区
unsigned char *buf;
struct jdec_private *jdec;
unsigned char *components[3];
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);
// 解压缩
// 首先初始化结构体变量
jdec = tinyjpeg_init();
if (jdec == NULL)
exitmessage("Not enough memory to alloc the structure need for decompressing\n");
// 解析 Segment Marker
if (tinyjpeg_parse_header(jdec, buf, length_of_file)<0)
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;
}
tinyjpeg_free(jdec);
/* else called just free(jdec); */
free(buf);
return 0;
}
任务一:将输出文件保存为可供YUVViewer观看的YUV文件
将write_yuv函数体内y、u、v三个文件单独输出改成合并输出到一个yuv文件
static void write_yuv(const char *filename, int width, int height, unsigned char **components)
{
FILE *F;
char temp[1024];
snprintf(temp, 1024, "%s.yuv", filename);
F = fopen(temp, "wb");
fwrite(components[0], width, height, F);
fwrite(components[1], width*height / 4, 1, F);
fwrite(components[2], width*height / 4, 1, F);
fclose(F);
}
tinyjpeg-internal.h
任务二:理解三个结构体的设计目的
// 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];
};
// 分量结构体:代表当前正在处理的某个分量的8x8块
struct component
{
unsigned int Hfactor;//水平采样因子
unsigned int Vfactor;//垂直采样因子
float *Q_table; /* Pointer to the quantisation table to use */
struct huffman_table *AC_table;//指向该宏块交流系数的Huffman码表
struct huffman_table *DC_table;//指向该宏块直交流系数的Huffman码表
short int previous_DC; /* Previous DC coefficient *///前一个块的DC系数
short int DCT[64]; /* DCT coef *///该块的DCT系数,其中DCT[0]为该块直流,其他为交流
#if SANITY_CHECK
unsigned int cid;
#endif
};
// 文件解码信息结构体
struct jdec_private
{