一、实验原理
1、JPEG(Joint Picture Expert Group),是联合图像专家组的英文缩写。
其编码使用的是标称Huffman编码。编码原理图如下:
解码是编码的逆过程。
2、JPEG文件格式
JPEG的每个标记都是由2个字节组成,其前一个字节是固定值0xFF。JFIF文件格式直接使用JPEG标准为应用程序定义的许多标记,因此JFIF格式成为事实上JPEG文件交换格式标准。将一张JPEG图片以二进制形式打开(部分数据),分析文件格式:
0xFFD8:SOI,Start of Image,图像开始
0xFFE0:APP0,Application,应用程序保留标记0,在这里指JFIF应用数据块
0xFFDB:DQT,Define Quantization Table,定义量化表
0xFFC0:SOF0,Start of Frame,帧图像开始
0xFFC4:DHT,Define Huffman Table,定义哈夫曼表
0xFFDA:SOS,Start of Scan,扫描开始12字节
0xFFD9:EOI,End of Image,图像结束2字节
3、DCT变换
变换编码是将输入的信号进行变换,得到一些系数。它可以从三个角度分析,一个是因子分析,一个是对主程序分析(与K-L变换等价),第三个是信号的合成与分解,是变化域系数与对应向量的线性组合,这也是DCT变换的本质。一维DCT变换:Y=AX ;二维DCT变换:Y=AXB(B为A的转置)。
DCT变换的三个特点:①能量守恒;②能量集中;③去相关性,将有记忆信源变成无记忆,使数据易于处理。
4、量化
JPEG中的量化表是按之字形扫描的格式保存的,需要反之字形扫描才能得到真正的量化表。文件存有两张量化表,分别对亮度信号和色差信号进行量化。根据人眼的视觉特性对低频分量采取细量化,对高频分量采取粗量化。
5、DC系数的差分编码
8X8图像块经过DCT变换后得到的DC直流系数有两个特点:①系数的数值较大;②相邻图像块的DC变化不大。根据这一特点,JPEG使用了差分脉冲调制编码技术,即DPCM技术,对相邻块之间的量化DC系数的差值DIFF进行Huffman编码。
6、AC系数的Z字扫描
由于DCT变换后,系数大多集中在左上角,即低频分量区,所以采用Z字形扫描按照频率从高到低的顺序读出,可以得到很多连零的结果。
7、Huffman码表存储方式
JPEG图片存储需要四张Huffman码表,分别是亮度DC,亮度AC,色度DC和色度AC。以一个Huffman码表为例进行分析:
0xFFC4是Huffman码表的开始标志。
黄色的“00”为Huffman表ID和表类型,0x00表示此部分为DC交流0号表。
绿色部分(16个字节)为不同位数的码字数量。没有1位的Huffman码字,2位的有1个,3位的有5个,4、5、6、7、8、9位各有一个,一共12个码字。
红色部分(12个字节,由绿色部分决定)为编码内容。这段数据表示12个叶子节点按从小到大排列,其权值用十六进制表示,依次为00,01,02,03,04,05,06,07,08,09,0A,0B.
二、实验步骤
分析JPEG编解码过程,要注意模块的划分和数据的流向。用三个结构体实现层次的嵌套:
struct jdec_private是最上层的结构体,包含 struct huffman_table和struct component。 struct huffman_table用来存放DC系数和AC系数的Huffman表;struct component用来存放8x8数据块组成的MCU(最小数据单元)的数值。
JPEG文件解码过程:读取文件->分析数据流结构->颜色分量单元的内部解码->直流系数的差分编码->反量化和反z字编码->反DCT变换
三、相关代码
1、解析三个结构体
/*************存放DC系数和AC系数的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数据块组成的MCU(最小数据单元)的数值*******************/
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
{
/* Public variables */
uint8_t *components[COMPONENTS];//分别指向YUV分量结构体
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];
short int *DC_Image;//指向输出DC图像的指针
short int *AC_Image;//指向输出AC图像的指针
jmp_buf jump_state;
/* Internal Pointer use for colorspace conversion, do not modify it !!! */
uint8_t *plane[COMPONENTS];
};
2、将输出文件保存为可供YUVViewer观看的YUV文件
①在命令行中将输出格式设为yuv。
②修改相关代码:
/********tinyjpeg.h***********/
enum tinyjpeg_fmt {
TINYJPEG_FMT_GREY = 1,
TINYJPEG_FMT_BGR24,
TINYJPEG_FMT_RGB24,
TINYJPEG_FMT_YUV420P,
TINYJPEG_FMT_YUV,//合成YUV分量并输出
};
/**********tinyjpeg.c***************/
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
unsigned int x, y, xstride_by_mcu, ystride_by_mcu;
unsigned int bytes_per_blocklines[3], bytes_per_mcu[3];
decode_MCU_fct decode_MCU;
const decode_MCU_fct *decode_mcu_table;
const convert_colorspace_fct *colorspace_array_conv;
convert_colorspace_fct convert_to_pixfmt;
if (setjmp(priv->jump_state))
return -1;
/* To keep gcc happy initialize some array */
bytes_per_mcu[1] = 0;
bytes_per_mcu[2] = 0;
bytes_per_blocklines[1] = 0;
bytes_per_blocklines[2] = 0;
decode_mcu_table = decode_mcu_3comp_table;
switch (pixfmt) {
case TINYJPEG_FMT_YUV:
......//add yuv format condition
case TINYJPEG_FMT_YUV420P:
colorspace_array_conv = convert_colorspace_yuv420p;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t *)malloc(priv->width * priv->height);
if (priv->components[1] == NULL)
priv->components[1] = (uint8_t *)malloc(priv->width * priv->height/4);
if (priv->components[2] == NULL)
priv->components[2] = (uint8_t *)malloc(priv->width * priv->height/4);
bytes_per_blocklines[0] = priv->width;
bytes_per_blocklines[1] = priv->width/4;
bytes_per_blocklines[2] = priv->width/4;
bytes_per_mcu[0] = 8;
bytes_per_mcu[1] = 4;
bytes_per_mcu[2] = 4;
break;
......
}
/**************loadjpeg.c******************/
int convert_one_image(const char *infilename, const char *outfilename, int output_format)
{
......
/* 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;
/******add yuv output**********/
case TINYJPEG_FMT_YUV:
write_add_yuv(outfilename, width, height, components);
break;
}
}
/**************YUV文件写出****************/
static void write_add_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);
snprintf(temp, 1024, "%s.yuv", filename);
fwrite(components[1], width*height/4, 1, F);
snprintf(temp, 1024, "%s.yuv", filename);
fwrite(components[2], width*height/4, 1, F);
fclose(F);
}
/*************主函数*************/
int main(int argc, char *argv[])
{
......
if (strcmp(argv[current_argument + 1], "yuv420p") == 0)
output_format = TINYJPEG_FMT_YUV420P;
......
else if (strcmp(argv[current_argument + 1], "yuv") == 0)//add
output_format = TINYJPEG_FMT_YUV;
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
......
return 0;
}
3、以txt文件输出量化矩阵和Huffman码表
/************tinyjpeg.c 输出量化表*************/
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
......
while (stream < dqt_block_end)
{
qi = *stream++;
#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
table = priv->Q_tables[qi];
build_quantization_table(table, stream);
stream += 64;
#if TABLES
fprintf(f_tables,"Quantization_table[%d]:\n",qi);//add
fflush(f_tables);
#endif
}
......
}
/***********tinyjpeg.c 以矩阵形式输出量化表*******************/
static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
......
for (i=0; i<8; i++) {
for (j=0; j<8; j++) {
#if TABLES
fprintf(f_tables,"%d\t",ref_table[*zz]);//add
fflush(f_tables);
if(j==7)
{
fprintf(f_tables,"%\n");
fflush(f_tables);
}
#endif
*qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
}
}
}
/*******parse_DHT函数,输出DC和AC***************/
if{
#if TABLES
fprintf(f_tables, "Huffman table AC[%d] length=%d\n", index & 0xf, count);
fflush(f_tables);
#endif
...
}
else
{
...
#if TABLES
fprintf(f_tables, "Huffman table DC[%d] length=%d\n", index & 0xf, count);
fflush(f_tables);
#endif
}
/******build_huffman_table,输出Huffman码表**********/
#if TABLES
fprintf(f_tables, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
fflush(f_tables);
#endif
4、输出DC和某一AC图像及概率分布
/**************将DC AC图像写入Image中*******************/
static void output_DC_AC_Image(struct jdec_private *priv)
{
static long int i=0;
if(i<priv->height*priv->width/64)
priv->DC_Image[i]=priv->component_infos[0].DCT[0];
priv->AC_Image[i]=priv->component_infos[0].DCT[1];
i++;
}
/**************输出DC AC图像***************/
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);
}
四、实验结果
1、输出YUV文件
2、输出量化矩阵
3、输出DC[0],AC[1]的Huffman码表
4、DC(左)、AC(右)的图像及概率分布图
五、实验结论
1、在有需要的地方添加trace,通过初始化,在解码过程中会输出一些与图像相关的参数及信息,将tinyjpeg.h中的trace变量设为1,则关闭trace文件。
2、 JPEG有三种失真:
①蚊子噪声->锐利的高频截止。
②马赛克、块效应->高压缩比图像中量化噪声相邻块的直流分量相差太大。
③轮廓线不光滑->轮廓线跨越几个块,在不同的块取值不同。