一、实验目的
- 掌握JPEG编解码系统的基本原理。
- 初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
二、实验内容
1.JPEG文件格式
Segment的组织形式
JPEG在文件中以Segment的形式组织 ,它具有以下特点:
- 均以0xFF开始,后跟1byte的Marker和2byte的Segment length(包含表示Length本身所占用的2byte,不含0xFF+Marker所占用的2byte);
- 采用 Motorola序(相对于Intel序),即保存时高位在前,低位在后;
- Data部分中,0xFF后若为0x00,则跳过此字节不予处理。
JPEG的Segment Market
2.JPEG的编码
1)零偏置:对于灰度级为2^n 的像素,通过减去2^(n-1),将无符号整数变为有符号数,即值域变为正负对称。将绝对值大的数出现的概率大大减小,提高编码效率。
2)DCT变换:先将图像分为8×8的像块,如果图像的宽(高)不是8的整数倍,使用图像边缘像素填充,以不改变频谱分布。实现能量集中和去相关,降低空间冗余度。
3)量化:利用人眼视觉特性设计而成的矩阵量化DCT系数,减小视觉冗余。因为人眼对亮度信号比色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值;根据人眼对低频敏感,对高频不太敏感,对低频分量采取较细的量化,对高频分量采取较粗的量化。JPEG标准中采用中平型均匀量化,输入DCT系数,输出量化系数。
4)DC系数差分编码:8×8像块经过DCT后得到的DC系数有两个特点:
一是系数的值较大;二是相邻像块的DC系数存在相关性(即存在冗余)。
5)AC系数之字形扫描:由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。尤其在最后,如果都是零,给出 EOB (End of Block)即可。
6)AC系数游程编码:当遇到很多连续的0时,为缩短数据长度,编码非零系数level和它之前0的个数run,(Run,level)。如:0,0,3,0,2,0,0,0,1–>游程编码:(2,3),(2,2),(3,1)
7)Huffman编码:对DC系数DPCM的结果和AC系数RLE的结果进行Huffman编码。
3.JPEG的解码
- 解码Huffman数据
- 解码DC差值
- 重构量化后的系数
- DCT逆变换
- 丢弃填充的行/列
- 反0偏置
- 对丢失的CbCr分量插值
- 将YCbCr通道数据变为RGB通道数据
三.程序设计
main函数:从命令行参数中接受输入输出文件名称,打开TRACEFILE,选择输出的文件格式
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;
}
struct huffman_table:快速查找表
huffman查找表有两种实现方法,分别为huffman树的遍历和loookup查找表。优先选用lookup查找表
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 compenent
定义水平采样因子和垂直采样因子以及DCT系数,直流哈夫曼表,交流哈夫曼表,前直流系数。其中包含struct huffman_table
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:定义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];
};
TRACE:
#define TRACE 1//add by nxn //1为使用TRACE
#define TRACEFILE "trace_jpeg.txt"//add by nxn //TRACE文件名,程序运行过程中输出的数据会写入trace_jpeg.txt中
JPG文件输出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);
}
再通过在build_quantization_table函数中增加代码以txt文件输出所有的量化矩阵和所有的huffman码表
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
}
在tinyjpeg_decode中添加代码用于输出DC AC图像
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);