一、JPEG编码原理
1.将RGB转换为YUV空间。(相关性较小)
2.零偏置。(使最大的绝对值大的无符号数变为绝对值小的有符号数,减小数据平均的位数,例如0~255变为-128~127)
3.做8*8DCT变换。(区分DC与AC成分,便于后续处理)
4.细量化低频粗量化高频。(利用人眼对高频不敏感的特性节省空间)
5.针对DC系数使用DPCM编码。(相邻变化幅度较小,适合DPCM)
6.针对AC系数使用Z型扫描随后使用游程编码。(压缩掉大量的0)
7.对DC与AC分别做哈夫曼编码。(减少空间)
JPEG解码为编码的逆过程,了解编码方式方便解码。
二、JPEG解码原理
1.JPEG文件格式
2.整体分析
查阅loadjpeg.c中的主函数得知该程序先通过输入的参数来决定输出的格式,然后load_multiple_times函数多次使用tinyjpeg_parse_header函数读取jpeg文件头,tinyjpeg_parse_header使用parse_JFIF函数来解析每个标识符,依次解析SOF、DQT、SOS、DHT、DRI。
parse_JFIF:
static int parse_JFIF(struct jdec_private *priv, const unsigned char *stream)
{
int chuck_len;
int marker;
int sos_marker_found = 0;
int dht_marker_found = 0;
const unsigned char *next_chunck;
/* Parse marker */
while (!sos_marker_found)
{
if (*stream++ != 0xff)
goto bogus_jpeg_format;
/* Skip any padding ff byte (this is normal) */
while (*stream == 0xff)
stream++;
marker = *stream++;
chuck_len = be16_to_cpu(stream);
next_chunck = stream + chuck_len;
switch (marker)
{
case SOF:
if (parse_SOF(priv, stream) < 0)
return -1;
break;
case DQT:
if (parse_DQT(priv, stream) < 0)
return -1;
break;
case SOS:
if (parse_SOS(priv, stream) < 0)
return -1;
sos_marker_found = 1;
break;
case DHT:
if (parse_DHT(priv, stream) < 0)
return -1;
dht_marker_found = 1;
break;
case DRI:
if (parse_DRI(priv, stream) < 0)
return -1;
break;
default:
#if TRACE
fprintf(p_trace,"> Unknown marker %2.2x\n", marker);
fflush(p_trace);
#endif
break;
}
stream = next_chunck;
}
if (!dht_marker_found) {
#if TRACE
fprintf(p_trace,"No Huffman table loaded, using the default one\n");
fflush(p_trace);
#endif
build_default_huffman_tables(priv);
}
#ifdef SANITY_CHECK
if ( (priv->component_infos[cY].Hfactor < priv->component_infos[cCb].Hfactor)
|| (priv->component_infos[cY].Hfactor < priv->component_infos[cCr].Hfactor))
snprintf(error_string, sizeof(error_string),"Horizontal sampling factor for Y should be greater than horitontal sampling factor for Cb or Cr\n");
if ( (priv->component_infos[cY].Vfactor < priv->component_infos[cCb].Vfactor)
|| (priv->component_infos[cY].Vfactor < priv->component_infos[cCr].Vfactor))
snprintf(error_string, sizeof(error_string),"Vertical sampling factor for Y should be greater than vertical sampling factor for Cb or Cr\n");
if ( (priv->component_infos[cCb].Hfactor!=1)
|| (priv->component_infos[cCr].Hfactor!=1)
|| (priv->component_infos[cCb].Vfactor!=1)
|| (priv->component_infos[cCr].Vfactor!=1))
snprintf(error_string, sizeof(error_string),"Sampling other than 1x1 for Cr and Cb is not supported");
#endif
return 0;
bogus_jpeg_format:
#if TRACE
fprintf(p_trace,"Bogus jpeg format\n");
fflush(p_trace);
#endif
return -1;
}
3.结构体功能
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];
};
component结构体:
存储水平采样因子、垂直采样因子,前一个直流系数,dct变换后的值,哈夫曼表和量化表。
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
};
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];
};
4.TRACE的功能
#if XXX
...
#endif
该格式可根据if后面的条件是否为真来决定是否编译#if与#endif中间的代码,所以程序中的#if TRACE即为一部分代码的开关,TRACE为1时执行那些代码,输出trace_jpeg.txt文件。
TRACE的定义在tinyjpeg.h中,将TRACE修改为0即可关闭部分代码。
5.输出量化矩阵
该程序使用build_quantization_table生成量化矩阵,在该函数下方添加输出代码即可。
#if TRACE
const unsigned char* outzigzag = zigzag;
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
fprintf(p_trace, "%d ", ref_table[*outzigzag++]);
}
fprintf(p_trace, "\n");
}
#endif
输出的量化矩阵:
6.输出哈夫曼码表
该程序使用build_huffman_table生成哈夫曼码表。
输出的哈夫曼码表:
7.输出解码图像
原程序将yuv分别输出到了三个文件中,更改后将yuv均写入同一个文件即可得到yuv文件。
查阅实验所用jpg文件得知图像分辨率为1024*1024,用该分辨率查看得到的yuv文件即可得到正确图像。
8.输出DC、AC图像
由JPEG编码过程得知DC分量经过DPCM与哈夫曼编码后变为jpeg直流数据,解码时jpeg数据经过哈夫曼解码与dpcm解码后得到DC分量。该程序中tinyjpeg_decode用于解码,所以修改tinyjpeg_decode函数。
tinyjpeg_decode:
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;
/*---------------here------------------*/
FILE* out_DC;
FILE* out_AC;
out_DC = fopen("out_DC.yuv", "wb");
out_AC = fopen("out_AC.yuv", "wb");
unsigned char* out_DC_Buf;
unsigned char* out_AC_Buf;
/*---------------here------------------*/
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);
/*-----------------------------------here-------------------------------------*/
out_DC_Buf = (unsigned char)((priv->component_infos->DCT[0] + 512) / 4 + 0.5);
out_AC_Buf = (unsigned char)(priv->component_infos->DCT[1] + 128);
fwrite(&out_DC_Buf, 1, 1, out_DC);
fwrite(&out_AC_Buf, 1, 1, out_AC);
/*-----------------------------------here-------------------------------------*/
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
return 0;
}
DC分量为原图的88数据得到,原图为10241024,所以得到的图像为128*128。
因为只有y分量没有uv分量,所以得到的是黑白图像,使用Luminance Only来显示仅有亮度分量的图片。
AC与DC同理。
所的图片:
9.DC、AC图像概率分布
将out_DC.yuv与out_AC.yuv放到matlab运行目录中,运行以下代码:
所得结果:
(蓝色为DC分量,橙色为AC分量)
DC分量较为分散,AC分量较集中,两者均大致关于中心位置对称。