实验原理:
1.JPEG解码原理
(1)预处理
像块分割:把原图像分割成若干个8x8互相不重叠矩形块,每一个像块作为一个独立的单元进行变换和熵编码。
零偏置(Level Offset):对于灰度级是2n的像素,通过减去2n-1,将无符号的整数值变成有符号数。如对于n=8,即将0~255的值域,通过减去
128,转换为值域在-128~127之间的值。其目的是使像素的绝对值出现3位10进制的概率大大减少。
(2)8x8DCT
经过DCT变换后,前后图像能量不变,图像的低频分量会集中在左上角,故而左上角数值大,相关性小。
(3)量化
根据人眼对低频敏感,对高频不太敏感的视觉特性,对低频分量采取比较细的量化,对高频分量采取比较粗的量化。
因为人眼对亮度信号比对色差信号更敏感,所以使用了两种量化表:亮度量化值和色差量化值。
(4)编码
对DC系数的差分编码:
8x8图像快经过DCT变换后得到的DC直流系数有两个特点:系数的数值比较大、相邻8x8图像块的DC系数值变化不大–冗余。根据这个特点,JPEG算法使用了DPCM技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:
码字=类别ID+类内索引
类别ID同时也是解码类内索引时,还需读几位的码流,存在DC系数Huffman码表的全值中。
如:DC=8,上一DC=5,则DIFF=8-5=3,类别ID=2,类内索引=3,则码流=10011。
对交流系数AC采用之字形扫描、游程编码和VLC编码。
AC系数的Z字Sam扫描:
由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。在最后,如果都是0,给出EOB(End of Block)即可。
AC系数的游程编码:(run,level)
连续run个0,后面跟值为level的系数。
如:0,2,0,0,3,0,-4,0,0,0,-6,0,0,5,7表示为(1,2),(2,3),(1,-4),…
Run:最多15个,用4位表示RRRR
Level:类似DC。分成16个类别,用4位表示SSSS表示类别号与类内索引。
2.JPEG文件格式
(1)Segment的组织形式
JPEG在文件中以Segment的形式组织,具有以下特点:
均以0xFF开始,后跟 1 byte 的 Marker 和 2 byte 的 Segment length(包含表示 Length 本身所占用的 2 byte,不含“0xFF” + “Marker” 所占用的 2 byte);
采用 Motorola 序(相对于 Intel 序),即保存时高位在前,低位在后;
Data 部分中,0xFF 后若为 0x00,则跳过此字节不予处理
(2)JPEG的Segment Maker
3.JPEG的解码流程
1.读取文件
2.解析 Segment Marker
①解析 SOI
②解析 APP0
- 检查标识“JFIF”及版本
- 得到一些参数
③解析 DQT
- 得到量化表长度(可能包含多张量化表)
- 得到量化表的精度
- 得到及检查量化表的序号(只能是 0 —— 3)
- 得到量化表内容(64 个数据)
④解析 SOF0 - 得到每个 sample 的比特数、长宽、颜色分量数
- 得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表 序号(与 DQT 中序号对应)
⑤解析 DHT - 得到 Huffman 表的类型(AC、DC)、序号
- 依据数据重建 Huffman 表 3.2.6 解析 SOS
- 得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号(与 DHT 中序号对应)
3.依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个 MCU 中 8*8 宏块的个数
4.对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU 中每个分量宏块解 码)
①对每个宏块进行 Huffman 解码,得到 DCT 系数
②对每个宏块的 DCT 系数进行 IDCT,得到 Y、Cb、Cr
③遇到 Segment Marker RST 时,清空之前的 DC DCT 系数
5.解析到 EOI,解码结束
6.将 Y、Cb、Cr 转化为需要的色彩空间并保存。
实验内容:
1.逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。
在loadjpeg.c的write_yuv函数中添加如下代码,将单独的Y、U、V数据合并输出到.YUV文件中:
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图像:
2.调试程序:
理解程序设计框架
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;
#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函数:解码
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();
if (jdec == NULL)
exitmessage("Not enough memory to alloc the structure need for decompressing\n");
//解析Segment Maker
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;
}
/* Only called this if the buffers were allocated by tinyjpeg_decode() */
tinyjpeg_free(jdec);
/* else called just free(jdec); */
free(buf);
return 0;
}
tinyjpeg_parse_header函数:解析JPEG文件的文件头
int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size)
{
int ret;
/* Identify the file */
//判断JPEG文件头
if ((buf[0] != 0xFF) || (buf[1] != SOI))
snprintf(error_string, sizeof(error_string),"Not a JPG file ?\n");
priv->stream_begin = buf+2;
priv->stream_length = size-2;
priv->stream_end = priv->stream_begin + priv->stream_length;
ret = parse_JFIF(priv, priv->stream_begin);
return ret;
}
理解三个结构体的设计目的
- struct huffman_table
存放DC系数、AC系数的哈夫曼码表。
struct huffman_table
{
//快速查找表,使用HUFFMAN_HASH_NBITS的比特可以直接得到符号,如果符号小于0,那么需要在树表中查找
short int lookup[HUFFMAN_HASH_SIZE];
//给出编码符号的位数
unsigned char code_size[HUFFMAN_HASH_SIZE];
//存储未在查找表中编码的值的位置,通过计算判断256值是否足以存储所有值
uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];
};
- struct component
存放8x8数据块组成的最小数据单元的数据值,每访问下一个像块,该结构体就会更新一次值。
struct component
{
unsigned int Hfactor;//水平采样因子
unsigned int Vfactor;//垂直采样因子
float *Q_table; /* Pointer to the quantisation table to use */
struct huffman_table *AC_table;
struct huffman_table *DC_table;
short int previous_DC; /* Previous DC coefficient */
short int DCT[64]; /* DCT coef */
#if SANITY_CHECK
unsigned int cid;
#endif
};
- struct jdec_private
最上层结构体,支配整个图像
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];
//对YUV进行量化的量化表
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 */
//临时存储反DCT之后存三个分量的空间
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];
};
3.以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。
输出量化矩阵:
tinyjpeg.h
#define TABLES 1
FILE *f_tables;
loadjpeg.c中的main函数中添加(打开关闭文件):
#if TABLES
char temp[1024];
snprintf(temp, 1024, "%s_tables.txt", output_filename)
#endif
#if TABLES
fclose(f_tables);
#endif
tinyjpeg.c
parse_DQT函数:
while (stream < dqt_block_end)
{
qi = *stream++;
#if TRACE
PrintQtable(table, qi);
#endif;
#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
#if TABLES
fprintf(f_tables, "Quantization_table [%d]:\n", qi);
fflush(f_tables);
#endif
table = priv->Q_tables[qi];
build_quantization_table(table, stream);
stream += 64;
}
build_quantization_table函数:
while (stream < dqt_block_end)
{
qi = *stream++;
#if TRACE
PrintQtable(table, qi);
#endif;
#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
#if TABLES
fprintf(f_tables, "Quantization_table [%d]:\n", qi);
fflush(f_tables);
#endif
table = priv->Q_tables[qi];
build_quantization_table(table, stream);
stream += 64;
}
build_quantization_table函数:
for (i=0; i<8; i++)
{
for (j=0; j<8; j++)
{
//add by susu
#if TABLES
fprintf(f_tables, "%d\t", ref_table[*zz]);
fflush(f_tables);
if (j == 7)
{
fprintf(f_tables,"\n");
fflush(f_tables);
}
#endif
输出哈夫曼码表:
tinyjpeg.c
build_huffman_table函数:
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
#if TABLES
fprintf(f_tables, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
fflush(f_tables);
#endif
table->code_size[val] = code_size;
......
}
输出结果:
4.输出DC图像与某一个AC值并统计其概率分布。
在tinyjpeg.h文件中声明:
FILE *DC_FILE;
FILE *AC_FILE;
static void output_DC_AC_Image(struct jdec_private *priv, int temp);
static void write_DC_AC_Image(struct jdec_private *priv);
在main函数中添加文件打开关闭代码:
char temp[1024];
snprintf(temp, 1024, "%output_dc.yuv", output_filename);
DC_FILE = fopen(temp, "wb");
snprintf(temp, 1024, "%output_ac.yuv", output_filename);
AC_FILE = fopen(temp, "wb");
fclose(AC_FILE);
fclose(DC_FILE);
写出DC、AC图像write_DC_AC_Image:
static void write_DC_AC_Image(struct jdec_private *priv)
{
int i;
short int DC_min, DC_max, AC_min, AC_max;
unsigned char *temp;
DC_min = priv->DC_Image[0];
DC_max = priv->DC_Image[0];
AC_min = priv->AC_Image[0];
AC_max = priv->AC_Image[0];
temp = (unsigned char*)malloc(priv->height*priv->width / 64);
for (i = 0; i < (priv->height*priv->width/64); i++)
{
if (priv->DC_Image[i] > DC_max)
DC_max = priv->DC_Image[i];
if (priv->DC_Image[i] < DC_min)
DC_min = priv->DC_Image[i];
if (priv->AC_Image[i] > AC_max)
AC_max = priv->AC_Image[i];
if (priv->AC_Image[i] < AC_min)
AC_min = priv->AC_Image[i];
}
for (i = 0; i < (priv->height*priv->width/64); i++)
{
temp[i] = (unsigned char)255 * (priv->DC_Image[i] - DC_min) / (DC_max - DC_min);
}
fwrite(temp, 1, priv->width*priv->height / 64, DC_FILE);
for (i = 0; i < (priv->height*priv->width / 64); i++)
{
temp[i] = (unsigned char)255 * (priv->AC_Image[i] - AC_min) / (AC_max - AC_min);
}
fwrite(temp, 1, priv->width*priv->height / 64, AC_FILE);
if (temp) free(temp);
}
图像输出
DC图像:
AC图像:
5.概率分布: