目录
4. 以txt文件输出所有的量化矩阵和所有的HUFFMAN码表
一、JPEG编码原理
1. 零偏置Level Offset
-
对于灰度级是 的像素,通过减去 ,将无符号的整数值变成有符号数。
例如:n=8,灰度级0 ~ 255,通过减去128,转化为-128 ~ 127. -
目的:使像素的绝对值出现3位10进制的概率大大降低。
2. DCT变换
- 对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,并进行8×8DCT变换,目的是去除图像数据的相关性,便于量化过程去除图像数据的空间冗余。
3. 量化
- 采用中平型均匀量化器
- 因为人眼对亮度信号比对色差信号敏感,因此使用两种量化表:亮度量化值和色差量化值。
- 根据人眼视觉特性(对低频敏感,对高频不太敏感),对低频分量采取较细的量化,对高频分量采取较粗的量化。
4. DC系数的差分编码
-
8 * 8图像块经过DCT变换后得到的DC系数有两个特点:系数数值比较大、相邻 8 * 8 图像块的DC系数值变化不大。
-
根据这个特点,JPEG算法使用了DPCM技术,对相邻图像块之间量化DC系数的差值DIFF进行Huffman编码:
5. AC系数的Z字扫描和游程编码
- Z字扫描:由于DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会,可以使用游程编码。
- 游程编码:在JPEG和MPEG编码中规定为(run,level):表示连续run个0,后面跟值为level的系数。
二、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
三、JPEG解码流程
1. 读取文件
2. 解析 Segment Marker
① 解析SOI
② 解析APP0
- 检查标识“JFIF”及版本
- 得到一些参数
③ 解析DQT
- 得到量化表长度(可能包含多张量化表)
- 得到量化表的精度
- 得到及检查量化表的序号(只能是 0 - 3)
- 得到量化表内容(64 个数据)
④ 解析SOF0
- 得到每个 sample 的比特数、长宽、颜色分量数
- 得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表序号(与 DQT 中序号对应)
⑤ 解析DHT
- 得到 Huffman 表的类型(AC、DC)、序号
- 依据数据重建 Huffman 表
⑥ 解析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文件输出为YUV文件
在loadjpeg.c中的write_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);
//add
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);
//end
}
修改命令行参数:
运行程序,成功生成了YUV文件,并在YUVViewer中观看此YUV文件:
2. 三个结构体
① struct huffman_table
用来存储哈夫曼码表,分为快查找表和慢查找表,主要目的是提高解码效率。
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
定义了水平、垂直采样因子、AC和DC哈夫曼表、前一个块和当前块的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
定义了图像宽高、码流长度、数据流指针、量化表、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];
};
3. TRACE
Trace主要用于输出各种中间变量,将Trace设置为1时打开,设置为0时关闭。
#define TRACE 1//add by nxn
#define TRACEFILE "trace_jpeg.txt"//add by nxn
4. 以txt文件输出所有的量化矩阵和所有的HUFFMAN码表
①量化矩阵
(1)在 tinyjpeg.h 中增加量化表的文件指针:
FILE *p_trace;//add by nxn
//add
FILE* quantization_table;
(2)在loadjpeg.c的主函数中增加如下代码,将量化表信息写入txt文件:
#if TRACE
p_trace=fopen(TRACEFILE,"w");
if (p_trace==NULL)
{
printf("trace file open error!");
}
//add
quantization_table = fopen("quantization_table.txt", "w+");
//end
#endif
(3)在tinyjpeg.c中添加如下代码:
函数 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++) {
*qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
}
}
//add
#if TRACE
for (i = 0; i < 8; ++i) {
for (j = 0; j < 8; ++j) {
if (j == 7)
{
fprintf(quantization_table, "%d\n", (int)ref_table[zigzag[i * 8 + j]]);
}
else
{
fprintf(quantization_table, "%d\t", (int)ref_table[zigzag[i * 8 + j]]);
}
}
}
#endif
//end
}
函数 parse_DQT 中:
#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
//add
#if TRACE
fprintf(quantization_table, "Quantization table [%d]\n", qi);
#endif
//end
输出的量化矩阵保存在quantization_table.txt中:
②HUFFMAN码表
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
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 */
}
}
}
输出的HUFFMAN码表保存在trace_jpeg.txt中:
5. 输出DC和AC图像并统计其概率分布
在tinyjpeg.c中的tinyjpeg_decode()函数内增加代码:
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
//add
FILE* DCFile;
FILE* ACFile_1, * ACFile_10, * ACFile_20;
DCFile = fopen("DC.yuv", "w");
ACFile_1 = fopen("AC1.yuv", "w");
unsigned char* uvbuf = 128;
unsigned char* DCbuf, * ACbuf_1;
int count = 0;
//end
...
...
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);
//add
DCbuf = (unsigned char)((priv->component_infos->DCT[0] + 512) / 4.0);
fwrite(&DCbuf, 1, 1, DCFile);
ACbuf_1 = (unsigned char)((priv->component_infos->DCT[1] + 128));
fwrite(&ACbuf_1, 1, 1, ACFile_1);
//end
convert_to_pixfmt(priv);
...
...
//add
for (int j = 0; j < count * 0.25 * 2; j++)
{
fwrite(&uvbuf, sizeof(unsigned char), 1, DCFile);
fwrite(&uvbuf, sizeof(unsigned char), 1, ACFile_1);
}
fclose(DCFile);
fclose(ACFile_1);
//end
return 0;
}