实验目的
掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
方法解释
JPEG文件格式
SOI
APP0
DQT
SOF0
DHT
SOS
EOI
JPEG编码原理
零偏置(Level Offset)
对于灰度级是2n的像素,通过减去2n-1,将无符号的整数值变成有符号数。
对于n=8,即将0 ~ 255的值域,通过减去128,转换为
值域在-128~127之间的值。
目的:使像素的绝对值出现3位10进制的概率大大减少
DCT变换
对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,如图所示,并作为两维离散余弦变换DCT的输入。
量化
根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化
对DC系数编码
DC系数的差分编码
根据这个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:
DC系数的熵编码(huffman编码)
对DIFF用Huffman编码:分成类别,类似指数Golomb编码
➢类别ID:一元码编码
➢ 类内索引:采用定长码
对AC系数编码
AC系数的Z字扫描
由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。尤其在最后,如果都是零,给出EOB (End of Block)即可。
AC系数的游程编码、熵编码(huffman编码)
实验步骤
1.逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。
参数项目设置
修改load.jpeg中的write_yuv函数,添加注释代码以输出yuv文件
/**
* Save a buffer in three files (.Y, .U, .V) useable by yuvsplittoppm
*/
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);
fopen_s(&F,temp, "wb");
fwrite(components[0], width, height, F);
fclose(F);
snprintf(temp, 1024, "%s.U", filename);
fopen_s(&F,temp, "wb");
fwrite(components[1], width * height / 4, 1, F);
fclose(F);
snprintf(temp, 1024, "%s.V", filename);
fopen_s(&F,temp, "wb");
fwrite(components[2], width * height / 4, 1, F);
fclose(F);
//输出yuv文件
snprintf(temp, 1024, "%s.yuv", filename);
fopen_s(&F, 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);
}
输入test.jpg如下图
输出test.yuv,并用YUV viewer打开
2. 程序调试过程中,应做到:
理解程序设计的整体框架
理解三个结构体的设计目的
struct huffman_table
存储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];
};
struct component
储存8×8图像块中解码用表的信息
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数据流
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的目的和含义
会打开和关闭TRACE
TRACE置1,就是打开;置0,就是关闭
#define TRACE 1
会根据自己的要求修改TRACE
(若TRACE置0,则以下代码就会标灰色,变成注释一样的存在
若置1,则会以有效代码存在
#if TRACE
fopen_s(&p_trace,TRACEFILE, "w");
if (p_trace == NULL)
{
printf("trace file open error!");
}
#endif
)
在本程序中,打开TRACE,运行程序就会输出trace_jpeg.txt
默认包含解码结构信息和具体huffman表
3.以txt文件输出所有的量化矩阵和所有的HUFFMAN码表
trace_jpeg.txt
中已有huffman表,只需修改代码,同时输出量化表
修改tinyjpeg.c中parse_DQT函数,添加如下代码
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
/*所添加代码<--*/
#if TRACE
fprintf(p_trace, "Quantization_table [%d]:\n", qi);
fflush(p_trace);
#endif
/*-->所添加代码*/
table = priv->Q_tables[qi];
build_quantization_table(table, stream);
stream += 64;
}
修改tinyjpeg.c中build_quantization_table函数,添加如下代码
for (i = 0; i < 8; i++) {
for (j = 0; j < 8; j++) {
/*所添加代码<--*/
#if TRACE
fprintf(p_trace, "%d\t", ref_table[*zz]);
fflush(p_trace);
if (j == 7)
{
fprintf(p_trace, "\n");
fflush(p_trace);
}
#endif
/*-->所添加代码*/
*qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
}
}
运行程序,输出trace_jpeg.txt
> Unknown marker e0
> DQT marker
Quantization_table [0]:
2 1 1 2 2 4 5 6
1 1 1 2 3 6 6 6
1 1 2 2 4 6 7 6
1 2 2 3 5 9 8 6
2 2 4 6 7 11 10 8
2 4 6 6 8 10 11 9
5 6 8 9 10 12 12 10
7 9 10 10 11 10 10 10
< DQT marker
> DQT marker
Quantization_table [1]:
2 2 2 5 10 10 10 10
2 2 3 7 10 10 10 10
2 3 6 10 10 10 10 10
5 7 10 10 10 10 10 10
10 10 10 10 10 10 10 10
10 10 10 10 10 10 10 10
10 10 10 10 10 10 10 10
10 10 10 10 10 10 10 10
< DQT marker
> SOF marker
> SOF marker
Size:1024x1024 nr_components:3 (YCbCr) precision:8
Component:1 factor:1x1 Quantization table:0
Component:2 factor:1x1 Quantization table:1
Component:3 factor:1x1 Quantization table:1
< SOF marker
> DHT marker (length=27)
Huffman table DC[0] length=10
val=04 code=00000000 codesize=02
val=05 code=00000001 codesize=02
val=06 code=00000002 codesize=02
val=03 code=00000006 codesize=03
val=02 code=0000000e codesize=04
val=01 code=0000001e codesize=05
val=00 code=0000003e codesize=06
val=09 code=0000007e codesize=07
val=07 code=000000fe codesize=08
val=08 code=000001fe codesize=09
< DHT marker
> DHT marker (length=60)
Huffman table AC[0] length=43
val=00 code=00000000 codesize=02
val=01 code=00000002 codesize=03
val=03 code=00000003 codesize=03
val=02 code=00000008 codesize=04
val=04 code=00000009 codesize=04
val=05 code=0000000a codesize=04
val=11 code=0000000b codesize=04
val=21 code=0000000c codesize=04
val=22 code=0000001a codesize=05
val=31 code=0000001b codesize=05
val=61 code=0000001c codesize=05
val=06 code=0000003a codesize=06
val=12 code=0000003b codesize=06
val=a1 code=0000003c codesize=06
val=32 code=0000007a codesize=07
val=41 code=0000007b codesize=07
val=62 code=0000007c codesize=07
val=13 code=000000fa codesize=08
val=51 code=000000fb codesize=08
val=23 code=000001f8 codesize=09
val=42 code=000001f9 codesize=09
val=71 code=000001fa codesize=09
val=81 code=000001fb codesize=09
val=91 code=000001fc codesize=09
val=15 code=000003fa codesize=10
val=52 code=000003fb codesize=10
val=63 code=000003fc codesize=10
val=07 code=000007fa codesize=11
val=14 code=000007fb codesize=11
val=33 code=000007fc codesize=11
val=53 code=000007fd codesize=11
val=16 code=00000ffc codesize=12
val=43 code=00000ffd codesize=12
val=08 code=00001ffc codesize=13
val=b1 code=00001ffd codesize=13
val=34 code=00003ffc codesize=14
val=c1 code=00003ffd codesize=14
val=24 code=00007ffc codesize=15
val=d1 code=0000fffa codesize=16
val=09 code=0000fffb codesize=16
val=72 code=0000fffc codesize=16
val=f0 code=0000fffd codesize=16
val=a2 code=0000fffe codesize=16
< DHT marker
> DHT marker (length=28)
Huffman table DC[1] length=11
val=04 code=00000000 codesize=02
val=05 code=00000001 codesize=02
val=06 code=00000002 codesize=02
val=03 code=00000006 codesize=03
val=02 code=0000000e codesize=04
val=01 code=0000001e codesize=05
val=00 code=0000003e codesize=06
val=07 code=0000007e codesize=07
val=0a code=000000fe codesize=08
val=09 code=000001fe codesize=09
val=08 code=000003fe codesize=10
< DHT marker
> DHT marker (length=45)
Huffman table AC[1] length=28
val=00 code=00000000 codesize=02
val=04 code=00000001 codesize=02
val=01 code=00000004 codesize=03
val=02 code=00000005 codesize=03
val=03 code=00000006 codesize=03
val=31 code=0000001c codesize=05
val=61 code=0000001d codesize=05
val=11 code=0000003c codesize=06
val=12 code=0000003d codesize=06
val=05 code=0000007c codesize=07
val=21 code=0000007d codesize=07
val=13 code=000000fc codesize=08
val=14 code=000000fd codesize=08
val=41 code=000000fe codesize=08
val=51 code=000001fe codesize=09
val=06 code=000007fc codesize=11
val=22 code=000007fd codesize=11
val=32 code=000007fe codesize=11
val=07 code=00001ffc codesize=13
val=15 code=00001ffd codesize=13
val=42 code=00001ffe codesize=13
val=08 code=0000fff8 codesize=16
val=71 code=0000fff9 codesize=16
val=23 code=0000fffa codesize=16
val=24 code=0000fffb codesize=16
val=33 code=0000fffc codesize=16
val=81 code=0000fffd codesize=16
val=a1 code=0000fffe codesize=16
< DHT marker
> SOS marker
ComponentId:1 tableAC:0 tableDC:0
ComponentId:2 tableAC:1 tableDC:1
ComponentId:3 tableAC:1 tableDC:1
< SOS marker
Use decode 1x1 sampling
Input file size: 170416
Input bytes actually read: 170415
4&5.输出DC图像并统计其概率分布,输出某一个AC值图像并统计其概率分布
在得到的DCT数组中存储了图像DCT变换后的数据,将这些数据输出即可,其中DCT[0]为图像的直流分量,其余为交流分量,实验中取DCT[1]作为输出的AC分量值。
对于8x8的宏块,取每一个宏块DCT变换后左上角的第一个值为DC分量,输出图像的大小为128x128(原图像为1024x1024)。
为了便于观察,将DC分量的值(范围-512~512)调整到 0 ~ 255。AC分量的值较小,+128处理。
修改tinyjpeg.c中tinyjpeg_decode函数,添加如下代码
/****在tinyjpeg.c文件头部定义全局变量,文件指针
*FILE* dcImage;
*FILE* acImage;
*/
......
//添加start
unsigned char* dc_buffer, * ac_buffer, * p_dc, * p_ac, * dcbuffer_del, * acbuffer_del;
fopen_s(&dcImage,"test_dc.yuv","wb");
fopen_s(&acImage,"test_ac.yuv", "wb");
//添加end
if (setjmp(priv->jump_state))
return -1;
......
//添加start
dc_buffer = (unsigned char*)malloc(sizeof(unsigned char) * (priv->height / ystride_by_mcu) * (priv->width / xstride_by_mcu) * 3 / 2);
ac_buffer = (unsigned char*)malloc(sizeof(unsigned char) * (priv->height / ystride_by_mcu) * (priv->width / xstride_by_mcu) * 3 / 2);
p_dc = dc_buffer;
p_ac = ac_buffer;
//添加end
for (y = 0; y < priv->height / ystride_by_mcu; y++)
{
......
if (find_next_rst_marker(priv) < 0)
return -1;
}
}
//添加start
*p_dc = (unsigned char)((priv->component_infos[cY].DCT[0] + 512) / 4);
*p_ac = (unsigned char)(priv->component_infos[cY].DCT[1] + 128);
p_dc++;
p_ac++;
//添加end
}
}
//添加start
for (int i = 0; i < ((priv->height / ystride_by_mcu) * (priv->width / xstride_by_mcu) / 2); i++)
{
*p_dc = (unsigned char)128;
*p_ac = (unsigned char)128;
p_dc++;
p_ac++;
}
fwrite(dc_buffer, sizeof(unsigned char), (priv->height / ystride_by_mcu)* (priv->width / xstride_by_mcu) * 3 / 2, dcImage);
fwrite(ac_buffer, sizeof(unsigned char), (priv->height / ystride_by_mcu)* (priv->width / xstride_by_mcu) * 3 / 2, acImage);
fclose(dcImage);
fclose(acImage);
//添加end
#if TRACE
fprintf(p_trace, "Input file size: %d\n", priv->stream_length + 2);
......
输出为