文章目录
一、实验目的
掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
二、实验内容
1.JPEG编解码原理
(1)编码原理
- 将输入从RGB彩色空间转到YUV。
- Level offset:零偏置,对于灰度级是 2 n 2^n 2n的像素,通过减去 2 n − 1 2^n-1 2n−1,将无符号的整数值变成有符号数。对于n=8,即将0~ 255的灰度值,通过减去128,转换为值域在-128~127之间的值,使像素的绝对值出现3位10进制的概率大大减少。
- 将输入图像分成88像素的块来处理,不足88的,取边缘像素补齐,对每个块做DCT变换,直流系数在每个块的左上角,右下角的分量频率高。
- DCT变换后DC系数值较大,且相邻块变化不大,利用这个特性对其进行DPCM,对相邻块的DC差值huffman编码。
- 对AC系数做之字形扫描,即按频率高低排列,再游程编码。
(2) 解码原理
解码是编码的逆过程。
- 解码Huffman数据
- 解码DC差值
- 重构量化后的系数
- DCT逆变换
- 丢弃填充的行/列
- 反0偏置
- 对丢失的CbCr分量差值(下采样的逆过程)
- YCbCr转RGB
2.JPEG文件格式
JPEG在文件中以Segment的形式组织,它具有以下特点:
- 均以 0xFF 开始,后跟 1 byte 的 Marker 和 2 byte 的 Segment length(包含表示Length 本身所占用的 2 byte,不含“0xFF” + “Marker” 所占用的 2 byte);
- 采用 Motorola 序(相对于 Intel 序),即保存时高位在前,低位在后;
- Data 部分中,0xFF 后若为 0x00,则跳过此字节不予处理;
JPEG 的 Segment Marker
三、实验步骤与结果
实验输入图像素材为 test.jpg (1024*1024)
1.调试JPEG解码器程序
(1)将输入的JPG文件进行解码,将输出文件保存为YUV文件。
源代码中没有输出为YUV文件的代码,只有输出为Y,U,V分量三个文件的代码。
- 对write_yuv( )进行如下修改
static void write_yuv(const char *filename, int width, int height, unsigned char **components)
{
FILE *F;
char temp[1024];
//输出为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);
/**保存为 .Y .U .V 3个文件
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);
*/
}
- 在命令行参数中进行设置
从左到右以此为输入图像名称 输出格式 输出图像名称
- 运行结果
使用YUVviewer 查看图像
(2)理解程序设计的整体框架
程序设定了两个解码模式,benchmark_mode和convert_one_image,选择哪个由变量benchmark确定。
只对convert_one_image进行分析。
大致流程如下
-
读取文件
-
解析 Segment Marker
调用tinyjpeg_parse_header()解析JPEG流中的头部信息(marker),获取解码需要的信息。
(1)解析 SOI(FFD8)
(2)解析 APP0,检查APP0中的JFIF标识
(3)解析 DQT,得到量化表长度、精度、序号等内容
调用parse_DQT()找到了对应的需要创建的是哪张量化表,然后调用build_quantization_table()创建。
(4) 解析 SOF0
调用parse_SOF(),得到图像的宽度高度,颜色分量的比特数、每个颜色分量的 ID 、水平垂直采样因子及使用的量化表序号。
(5) 解析 DHT
调用parse_DHT()得到 Huffman 表的类型(AC/DC)、序号,并根据数据调用build_huffman_table()重建Huffman 表。
(6)解析 SOS
调用parse_SOS(),得到每个颜色分量的 DC 、AC 值所使用的Huffman 表序号。 -
解码
调用tinyjpeg_decode()
(1)依据每个分量的水平、垂直采样因子计算MCU(最小编码单元)的大小和输入图像的格式,并得到每个MCU中 8*8宏块的数量数量
(2)对每个 MCU解码
调用decode_MCU()进行解码,不同的MCU(不同的采样频率)有不同的算法。
比如:decode_MCU_2x2_3planes对应的是4:2:0的采样格式;decode_MCU_2x1_3planes对应的是4:2:2的采样格式;decode_MCU_1x1_3planes对应的是4:4:4的采样格式。
依照各分量水平、垂直采样因子对 MCU中每个分量宏块解码
对每个宏块进行Huffman 解码,得到 DCT系数
对每个宏块的DCT系数进行 IDCT,得到 Y、Cb 、Cr
遇到 Segment Marker RST 时,清空之前的DCT系数
(3)解析到 EOI,解码结束 -
将 Y、Cb 、Cr 转化为需要的色彩空间并保存
(3)理解三个结构体的设计目的
- struct huffman_table
存储Huffman码表,分为快速查找表和慢速查找表。
struct huffman_table
{
/* 快速查找表 */
short int lookup[HUFFMAN_HASH_SIZE];
/* 编码大小:给出符号被编码的比特数*/
unsigned char code_size[HUFFMAN_HASH_SIZE];
/* 慢速查找表*/
uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];
};
- struct component
存储当前有关解码的信息、DCT变换后的值;并指向量化表和DC、AC的Huffman表。
struct component
{
unsigned int Hfactor; //水平采样
unsigned int Vfactor; //垂直采样
float *Q_table; /* 指向要使用的量化表的指针 */
struct huffman_table *AC_table;
struct huffman_table *DC_table;
short int previous_DC; /* 前一个块的DC系数,用于后续的DPCM编码*/
short int DCT[64]; /* DCT系数矩阵 */
#if SANITY_CHECK
unsigned int cid;
#endif
};
- struct jdec_private
JPEG码流结构体,用于存储JPEG图像的宽高、码流长度、数据流指针、量化表、Huffman码表等内容,且包含了struct huffman_table和struct component。
struct jdec_private
{
/* Public variables */
uint8_t *components[COMPONENTS];
unsigned int width, height; /*图像宽、高*/
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]; /*量化表 */
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 */
/* 在IDCT之后用于存储每个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];
};
(4)理解在视音频编解码调试中TRACE的目的和含义
在程序的很多部分中都有如下的预处理命令块:
#if TRACE
...
#endif
TRACE在程序运行过程中可以记录下重要的信息,如解析DHT,解析DQT,采用的采样频率等等。
- 在tinyjpeg.h定义了TRACE相关内容
#define TRACE 1 //开启TRACE
#define TRACEFILE "trace_jpeg.txt"//定义存储文件
- 在main()中使用了TRACE
#if TRACE
p_trace=fopen(TRACEFILE,"w");
if (p_trace==NULL)
{
printf("trace file open error!");
}
#endif
......
#if TRACE
fclose(p_trace);
#endif
- 运行结果
可以看到文件中记录了对marker解析,Huffman表等内容。
- 关闭TRACE
要关闭TRACE功能,在tinyjpeg.h中进行修改:
#define TRACE 0
2. 以txt文件输出所有的量化矩阵和所有的HUFFMAN码表
代码中已经有存储变量,添加输出Huffman表和量化表的函数即可。
- 在tinyjpeg.h中定义check_table相关内容
#define check_table 1
FILE* Q_table;//add by nxn
FILE* H_table;//add by nxn
#define QTABLEFILE "qtable_jpeg.txt"//量化表的文件名
#define HTABLEFILE "htable_jpeg.txt"//Huffman表的文件名
- 在build_quantization_table()中添加输出量化表内容
static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
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++) {
//修改部分如下============================================================
#if check_table
fprintf(Q_table, "%d\t", ref_table[*zz]);//输出表的内容
fflush(Q_table);
if (j == 7)
{
fprintf(Q_table,"\n");
fflush(Q_table);
}
#endif
//=======================================================================
*qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
}
}
}
- 在parse_DQT()的循环中添加输出量化表id内容
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
int qi;
float *table;
const unsigned char *dqt_block_end;
#if TRACE
fprintf(p_trace,"> DQT marker\n");
fflush(p_trace);
#endif
dqt_block_end = stream + be16_to_cpu(stream);
stream += 2; /* Skip length */
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
//修改代码如下===========================================================
fprintf(Q_table, "Quantization_table [%d]:\n", qi);// 输出量化表id
fflush(Q_table);
#endif
//=======================================================================
table = priv->Q_tables[qi];
build_quantization_table(table, stream);
stream += 64;
}
#if TRACE
fprintf(p_trace,"< DQT marker\n");
fflush(p_trace);
#endif
return 0;
}
- parse_DHT()中添加输出Huffman表的类型和id
static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)
{
unsigned int count, i;
unsigned char huff_bits[17];
int length, index;
length = be16_to_cpu(stream) - 2;
stream += 2; /* Skip length */
#if TRACE
fprintf(p_trace,"> DHT marker (length=%d)\n", length);
fflush(p_trace);
#endif
while (length>0) {
index = *stream++;
/* We need to calculate the number of bytes 'vals' will takes */
huff_bits[0] = 0;
count = 0;
for (i=1; i<17; i++) {
huff_bits[i] = *stream++;
count += huff_bits[i];
}
#if SANITY_CHECK
if (count >= HUFFMAN_BITS_SIZE)
snprintf(error_string, sizeof(error_string),"No more than %d bytes is allowed to describe a huffman table", HUFFMAN_BITS_SIZE);
if ( (index &0xf) >= HUFFMAN_TABLES)
snprintf(error_string, sizeof(error_string),"No more than %d Huffman tables is supported (got %d)\n", HUFFMAN_TABLES, index&0xf);
#if TRACE
fprintf(p_trace,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);
fflush(p_trace);
#endif
//修改代码如下========================================================================================
#if check_table
fprintf(H_table, "Huffman table %s[%d] length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);//added 输出huffman表类型和id
fflush(H_table);
#endif
//===================================================================================
#endif
if (index & 0xf0 )
build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);
else
build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);
length -= 1;
length -= 16;
length -= count;
stream += count;
}
#if TRACE
fprintf(p_trace,"< DHT marker\n");
fflush(p_trace);
#endif
return 0;
}
- 在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
//修改代码如下=================================================================
#if check_table
fprintf(H_table, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);//added 输出Huffman表内容
fflush(H_table);
#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 */
}
}
}
- 在main()中使用check_table
//在源代码中添加如下内容
#if check_table
Q_table=fopen(QTABLEFILE,"w");
if (Q_table==NULL)
{
printf("Q_table file open error!");
}
H_table=fopen(HTABLEFILE,"w");
if (H_table==NULL)
{
printf("H_table file open error!");
}
#endif
...
...
#if check_table
fclose(Q_table);
fclose(H_table);
#endif
- 运行结果
输出Huffman表
输出量化矩阵
3.输出DC/AC图像并统计其概率分布
- 在tinyjpeg中定义ACDC_output相关内容
FILE* DCFILE;
FILE* ACFILE;
#define ACDC_output 1
#define DCoutputFILE "output_dc.yuv"
#define ACoutputFILE "output_ac.yuv"
- 在main()中使用ACDC_output
#if ACDC_output
DCFILE=fopen(DCoutputFILE,"w");
if (DCFILE==NULL)
{
printf("DC output file open error!");
}
ACFILE=fopen(ACoutputFILE,"w");
if (ACFILE==NULL)
{
printf("AC output file open error!");
}
#endif
...
...
#if ACDC_output
fclose(DCFILE);
fclose(ACFILE);
#endif
- 在tinyjpeg_decode()中添加输出DC、AC图像代码
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 ACDC_output
unsigned char* DCbuf;
unsigned char* ACbuf;
unsigned char* uvbuf = 128;
int count;
#endif
//-------------------------------------------------------------
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_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;
case TINYJPEG_FMT_RGB24:
colorspace_array_conv = convert_colorspace_rgb24;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);
bytes_per_blocklines[0] = priv->width * 3;
bytes_per_mcu[0] = 3*8;
break;
case TINYJPEG_FMT_BGR24:
colorspace_array_conv = convert_colorspace_bgr24;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);
bytes_per_blocklines[0] = priv->width * 3;
bytes_per_mcu[0] = 3*8;
break;
case TINYJPEG_FMT_GREY:
decode_mcu_table = decode_mcu_1comp_table;
colorspace_array_conv = convert_colorspace_grey;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t *)malloc(priv->width * priv->height);
bytes_per_blocklines[0] = priv->width;
bytes_per_mcu[0] = 8;
break;
default:
#if TRACE
fprintf(p_trace,"Bad pixel format\n");
fflush(p_trace);
#endif
return -1;
}
xstride_by_mcu = ystride_by_mcu = 8;
if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {
decode_MCU = decode_mcu_table[0];
convert_to_pixfmt = colorspace_array_conv[0];
#if TRACE
fprintf(p_trace,"Use decode 1x1 sampling\n");
fflush(p_trace);
#endif
} else if (priv->component_infos[cY].Hfactor == 1) {
decode_MCU = decode_mcu_table[1];
convert_to_pixfmt = colorspace_array_conv[1];
ystride_by_mcu = 16;
#if TRACE
fprintf(p_trace,"Use decode 1x2 sampling (not supported)\n");
fflush(p_trace);
#endif
} else if (priv->component_infos[cY].Vfactor == 2) {
decode_MCU = decode_mcu_table[3];
convert_to_pixfmt = colorspace_array_conv[3];
xstride_by_mcu = 16;
ystride_by_mcu = 16;
#if TRACE
fprintf(p_trace,"Use decode 2x2 sampling\n");
fflush(p_trace);
#endif
} else {
decode_MCU = decode_mcu_table[2];
convert_to_pixfmt = colorspace_array_conv[2];
xstride_by_mcu = 16;
#if TRACE
fprintf(p_trace,"Use decode 2x1 sampling\n");
fflush(p_trace);
#endif
}
resync(priv);
/* Don't forget to that block can be either 8 or 16 lines */
bytes_per_blocklines[0] *= ystride_by_mcu;
bytes_per_blocklines[1] *= ystride_by_mcu;
bytes_per_blocklines[2] *= ystride_by_mcu;
bytes_per_mcu[0] *= xstride_by_mcu/8;
bytes_per_mcu[1] *= xstride_by_mcu/8;
bytes_per_mcu[2] *= xstride_by_mcu/8;
/* Just the decode the image by macroblock (size is 8x8, 8x16, or 16x16) */
for (y=0; y < priv->height/ystride_by_mcu; y++)
{
//trace("Decoding row %d\n", y);
priv->plane[0] = priv->components[0] + (y * bytes_per_blocklines[0]);
priv->plane[1] = priv->components[1] + (y * bytes_per_blocklines[1]);
priv->plane[2] = priv->components[2] + (y * bytes_per_blocklines[2]);
for (x=0; x < priv->width; x+=xstride_by_mcu)
{
decode_MCU(priv);
//修改代码如下----------------------------------------------
#if ACDC_output
DCbuf = (unsigned char)((priv->component_infos->DCT[0] + 512) / 4);//将DCT[0]的范围压缩到0-255
fwrite(&DCbuf, 1, 1, DCFILE);
ACbuf = (unsigned char)(priv->component_infos->DCT[1] + 128);
fwrite(&ACbuf, 1, 1, ACFILE);
count++;
#endif
//-----------------------------------------------------------
convert_to_pixfmt(priv);
priv->plane[0] += bytes_per_mcu[0];
priv->plane[1] += bytes_per_mcu[1];
priv->plane[2] += bytes_per_mcu[2];
if (priv->restarts_to_go>0)
{
priv->restarts_to_go--;
if (priv->restarts_to_go == 0)
{
priv->stream -= (priv->nbits_in_reservoir/8);
resync(priv);
if (find_next_rst_marker(priv) < 0)
return -1;
}
}
}
}
#if TRACE
fprintf(p_trace,"Input file size: %d\n", priv->stream_length+2);
fprintf(p_trace,"Input bytes actually read: %d\n", priv->stream - priv->stream_begin + 2);
fflush(p_trace);
#endif
//修改代码如下--------------------------------------------------
#if ACDC_output
for (int k = 0; k < count / 4 * 2; k++)
{
fwrite(&uvbuf, sizeof(unsigned char), 1, DCFILE);
fwrite(&uvbuf, sizeof(unsigned char), 1, ACFILE);//added 4:2:0 output
}
#endif
//------------------------------------------------------------
return 0;
}
- 运行结果
(AC图像选取的是DCT[1]输出)
- 概率分布