数据压缩实验五--JPEG
JPEG编解码原理
编码原理
基本流程:
1.亮度信号零偏置电平下移,对于灰度级是2n的像素,通过减去2n-1,将无符号的整数值变成是有符号数。这样做的目的是使像素的绝对值出现3位10进制的概率大大减少。
2.DCT变换
对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,并进行8×8DCT变换,目的是去除图像数据的相关性,便于量化过程去除图像数据的空间冗余。
3.量化
利用人眼视觉特性设计而成的矩阵量化DCT系数,减小视觉冗余。因为人眼对亮度信号比色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值;根据人眼对低频敏感,对高频不太敏感,对低频分量采取较细的量化,对高频分量采取较粗的量化。
4.熵编码
(1)对量化后的DC系数进行差分编码
8×8图像块经过DCT变换之后得到的DC直流系数有两个特点:系数的数值较大;相邻8×8图像块的DC系数值变化不大。根据这个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术对相邻图像块之间量化DC系数的差值进行编码。
(2)AC系数进行Z字扫描和游程编码后,再分别进行VLC编码
Z字扫描:由于DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会,可以使用游程编码。
游程编码:在JPEG和MPEG编码中规定为(run,level):表示连续run个0,后面跟值为level的系数。
解码原理
解码是编码的逆过程。
JPEG文件格式
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,则跳过此字节不予处理;
Segment Marker
SOI
SOI(start of image),表示图像的开始,标记代码FFD8。
APP0
标记代码:FFE0
APP0:Application,应用程序保留标记0。
给出如下信息:
量化表DQT
量化表标记代码:FFDB
精度及量化表 ID:
1byte, 低位为量化表的 ID 的索引值,其取值范围为 0~3,所以说最多可能有四张量化表(亮度量化表,色度量化表,可能会有 R、G、B 各一张量化表);而高位为量化精度,有两个可选值,0:8 位;1:16 位,这里是 00,代表量化精度为 8bit,即用一个字节来表示一个量化系数
SOF0
如:
DHT
SOS
EOI
EOI(start of image),表示图像的结束,标记代码FFD9。
JPEG解码
解码流程
1.读取文件
2.解析 Segment Marker
- 2.1 解析 SOI
- 2.2 解析 APP0
– 检查标识“JFIF”及版本
– 得到一些参数 - 2.3解析 DQT
– 得到量化表长度(可能包含多张量化表)
– 得到量化表的精度
– 得到及检查量化表的序号(只能是 0 —— 3)
– 得到量化表内容(64 个数据) - 2.4解析 SOF0
– 得到每个 sample 的比特数、长宽、颜色分量数
– 得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表
序号(与 DQT 中序号对应) - 2.5解析 DHT
– 得到 Huffman 表的类型(AC、DC)、序号
– 依据数据重建 Huffman 表 - 2.6解析 SOS
– 得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号(与 DHT中序号对应)
程序设计整体框架
main函数
接受输入输出文件,并可选择输出文件的类型
int main(int argc, char *argv[])
{
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;
#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;
}
tinyjpeg.c中的函数
parse_JFIF:解析marker标识
parse_DQT:解析DQT
parse_SOF:解析SOF
parse_DHT:解析DHT
parse_SOS:解析SOS
build_huffman_table:建立huffman表
build_quantization_table:建立量化表
三个结构体
struct huffman_table
huffman查找表有两种实现方法,分别为huffman树的遍历和loookup查找表。优先选用lookup查找表,code_size给出每个符号的编码比特位数。
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
包含struct huffman_table。
定义了采样因子,量化表,AC和DC huffman表,前一个块的直流DCT系数(亮度编码为DPCM,需要使用上一个块的直流系数来解码),当前块的DCT系数。
struct component
{
unsigned int Hfactor;//水平采样因子
unsigned int Vfactor;//垂直采样因子
float *Q_table;//量化表 /* Pointer to the quantisation table to use */
struct huffman_table *AC_table;//交流AC huffman表
struct huffman_table *DC_table;//直流DC huffman表
short int previous_DC; /* Previous DC coefficient */
short int DCT[64]; /* DCT coef */
#if SANITY_CHECK
unsigned int cid;
#endif
};
struct jdec_private
包含struct huffman_table,struct component 。
定义了图像宽高,数据流起始,数据流长度,量化表和huffman表。
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];
};
TRACE
#define TRACE 1//add by nxn //1为使用TRACE
#define TRACEFILE "trace_jpeg.txt"//add by nxn //TRACE文件名,程序运行过程中输出的数据会写入trace_jpeg.txt中
实验步骤
1.JPG文件输出YUV文件
修改loadjpg.c中的write_yuv函数:
static void write_yuv(const char *filename, int width, int height, unsigned char **components)
{
FILE *F;
char temp[1024];
snprintf(temp, 1024, "%s.Y", filename);
F = fopen(temp, "wb");
fwrite(components[0], width, height, F);
fclose(F);
snprintf(temp, 1024, "%s.U", filename);
F = fopen(temp, "wb");
fwrite(components[1], width*height/4, 1, F);
fclose(F);
snprintf(temp, 1024, "%s.V", filename);
F = fopen(temp, "wb");
fwrite(components[2], width*height/4, 1, F);
fclose(F);
//添加代码
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);
}
修改命令参数:
得到的YUV文件,用YUVviewer打开(图像宽和高均为1024)
2.以txt文件输出所有的量化矩阵和所有的HUFFMAN码表
量化矩阵
在build_quantization_table函数中增加代码:
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];
}
}
//增加代码
#if TRACE
const unsigned char* zz1 = zigzag;
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
fprintf(p_trace, "%d", ref_table[*zz1++]);
if (j == 7) {
fprintf(p_trace, "\n");
}
}
}
#endif
}
输出的量化矩阵保存在trace_jpeg.txt中
HUFFMAN码表
build_huffman_table函数可以输出HUFFMAN码表
static void build_huffman_table(const unsigned char *bits, const unsigned char *vals, struct huffman_table *table)
{
unsigned int i, j, code, code_size, val, nbits;
unsigned char huffsize[HUFFMAN_BITS_SIZE+1], *hz;
unsigned int huffcode[HUFFMAN_BITS_SIZE+1], *hc;
int next_free_entry;
/*
* Build a temp array
* huffsize[X] => numbers of bits to write vals[X]
*/
hz = huffsize;
for (i=1; i<=16; i++)
{
for (j=1; j<=bits[i]; j++)
*hz++ = i;
}
*hz = 0;
memset(table->lookup, 0xff, sizeof(table->lookup));
for (i=0; i<(16-HUFFMAN_HASH_NBITS); i++)
table->slowtable[i][0] = 0;
/* Build a temp array
* huffcode[X] => code used to write vals[X]
*/
code = 0;
hc = huffcode;
hz = huffsize;
nbits = *hz;
while (*hz)
{
while (*hz == nbits)
{
*hc++ = code++;
hz++;
}
code <<= 1;
nbits++;
}
/*
* Build the lookup table, and the slowtable if needed.
*/
next_free_entry = -1;
for (i=0; huffsize[i]; i++)
{
val = vals[i];
code = huffcode[i];
code_size = huffsize[i];
#if TRACE
fprintf(p_trace,"val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
fflush(p_trace);
#endif
table->code_size[val] = code_size;
if (code_size <= HUFFMAN_HASH_NBITS)
{
/*
* Good: val can be put in the lookup table, so fill all value of this
* column with value val
*/
int repeat = 1UL<<(HUFFMAN_HASH_NBITS - code_size);
code <<= HUFFMAN_HASH_NBITS - code_size;
while ( repeat-- )
table->lookup[code++] = val;
}
else
{
/* Perhaps sorting the array will be an optimization */
uint16_t *slowtable = table->slowtable[code_size-HUFFMAN_HASH_NBITS-1];
while(slowtable[0])
slowtable+=2;
slowtable[0] = code;
slowtable[1] = val;
slowtable[2] = 0;
/* TODO: NEED TO CHECK FOR AN OVERFLOW OF THE TABLE */
}
}
}
输出DC,AC图像并统计其概率分布
在tinyjpeg_decode中添加代码
int file_length = 0;
FILE* DC_file = fopen("DC.yuv", "wb");
FILE* AC_file = fopen("AC.yuv", "wb");
unsigned char dc;
unsigned char ac;
dc = (unsigned char)((priv->component_infos->DCT[0] + 512.0) / 4);//DC系数的取值范围[-512,511],转换为[0,255]的范围
ac = (unsigned char)(priv->component_infos->DCT[1] + 128);//AC系数的取值范围为[-128,127],将其转换为[0,255]的范围
fwrite(&dc, 1, 1, DC_file);
fwrite(&ac, 1, 1, AC_file);
file_length++;
unsigned char uv = 128;
for (int i = 0; i < file_length / 4 * 2; i++) {
fwrite(&uv, 1, 1, DC_file);
fwrite(&uv, 1, 1, AC_file);
}
fclose(DC_file);
fclose(AC_file);
得到DC,AC图像: