一、JPEG编解码原理
JPEG编码的过程如上图所示。解码是编码的逆过程。
以下是编码具体说明:
1、零偏置(Level Offset)
对于灰度级是2的n次方的像素,通过减去2的n-1次方,将无符号的整数值变成有符号数。例如:对于n=8,即将0~255的值域,通过减去128,转换为值域在-128~127之间的值。这样做的目的是:使像素的绝对值出现3位10进制的概率大大减少。
2、8*8DCT
将输入图像分为8*8的小块, 每个块里有64个像素。若边缘未满8*8,则用边缘像素进行填充。
总体来说,图像的低频部分集中在每个8*8块的左上角,高频部分在右下角。DCT变化可以做到以下三点:1、能量守恒 2、能量集中 3、去相关。
3、量化
根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化。 由于上面的人眼视觉特性,量化表左上角的值较小,右上角的值较大,这样就起到了保持低频分量,抑制高频分量的目的。
4、DC系数的差分编码
8X8图像块经过DC丁变换之后得到的DC直流系数有两个特点:1.系数的数值比较大2.相邻8X8图像块的DC系数值变化不大。根据这个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:DIFFk =DCk - DCk-1。
5、AC系数的Z字扫描
由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。尤其在最后,如果都是零,给出EOB (End of Block)即可。
6、AC系数的游程编码
在JPEG和MPEG编码中规定为:(run, level),表示连续run个0,后面跟值为level的系数。
如:0,2,0,0,3,0,-4,0,0,0,-6,0,0,5,7表示为(1, 2), (2, 3) ,…
编码:
Run: 最多15个,用4位表示RRRR。
Level:类似DC:分成16个类别,用4位表示SSSS表示类别号;类内索引。对(RRRR, SSSS)联合用Huffman编码;对类内索引用定长码编码
AC系数的游程编码
在JPEG和MPEG编码中规定为:(run, level),表示连续run个0,后面跟值为level的系数。
如:0,2,0,0,3,0,-4,0,0,0,-6,0,0,5,7表示为(1, 2), (2, 3) ,…
编码:
Run: 最多15个,用4位表示RRRR。
Level:类似DC:分成16个类别,用4位表示SSSS表示类别号;类内索引。对(RRRR, SSSS)联合用Huffman编码;对类内索引用定长码编码
二、JPEG文件格式
标记代码 2字节 固定值0xFFD8
标记代码 2字节 固定值0xFFD9
APP0应用程序保留标记0
a)精度及量化表ID 1字节
高4位:精度,只有两个可选值 0:8位;1:16位
低4位:量化表ID,取值范围为0~3
b)表项 (64×(精度+1))字节
例如8位精度的量化表,其表项长度为64×(0+1)=64字节
b)水平/垂直采样因子 1字节
高4位:水平采样因子① 数据长度 2字节
② huffman表 数据长度-2字节
高4位:类型,只有两个值可选
0:DC直流;1:AC交流
低4位:哈夫曼表ID,
注意,DC表和AC表分开编码
高4位:直流分量使用的哈夫曼树编号
低4位:交流分量使用的哈夫曼树编号
struct huffman_table,struct component ,struct jdec_private三个结构体设计的目的如下:struct jdec_private处于最上层,支配整个图像,其中包含有 struct huffman_table和struct component。 struct huffman_table用来存放DC系数和AC系数的Huffman表;struct component用来存放8x8数据块组成的MCU即最小数据单元的数值,相当于移动像块,每次访问下一像块就给该结构体更新数值。
{
/* 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
{
unsigned int Hfactor;//水平采样因子
unsigned int Vfactor;//垂直采样因子
float *Q_table; /* Pointer to the quantisation table to use */
struct huffman_table *AC_table;//DC系数的Huffman表
struct huffman_table *DC_table;//AC系数的Huffman表
short int previous_DC; /* Previous DC coefficient */
short int DCT[64]; /* DCT coef */
#if SANITY_CHECK
unsigned int cid;
#endif
};
typedef void (*decode_MCU_fct) (struct jdec_private *priv);
typedef void (*convert_colorspace_fct) (struct jdec_private *priv);
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];
/*by liushasha*///添加两个指针用来存放将要输出的DC、AC的图像。
short int*DC_Image;
short int*AC_Image;
/*by liushasha*/
jmp_buf jump_state;
/* Internal Pointer use for colorspace conversion, do not modify it !!! */
uint8_t *plane[COMPONENTS];
}
enum tinyjpeg_fmt {
TINYJPEG_FMT_GREY = 1,
TINYJPEG_FMT_BGR24,
TINYJPEG_FMT_RGB24,
TINYJPEG_FMT_YUV420P,
/*by liushasha*///将YUV分量合成YUV文件输出
TINYJPEG_FMT_YUV,
/*by liushasha*/
}
{
......
switch (pixfmt) {
/*by liushasha*///对TINYJPEG_FMT_YUV的处理和对TINYJPEG_FMT_YUV420P的处理完全一致
case TINYJPEG_FMT_YUV:
......
break;
case TINYJPEG_FMT_YUV420P:
......
break;
case TINYJPEG_FMT_RGB24:
......
break;
case TINYJPEG_FMT_BGR24:
......
break;
case TINYJPEG_FMT_GREY: ......
break;
default;......}...... return 0;
}
int convert_one_image(const char *infilename, const char *outfilename, int output_format)
{
......
switch (output_format)
{
case TINYJPEG_FMT_RGB24:
case TINYJPEG_FMT_BGR24:
write_tga(outfilename, output_format, width, height, components);
break;
case TINYJPEG_FMT_YUV420P:
write_yuv(outfilename, width, height, components);
break;
/*by liushasha*/
case TINYJPEG_FMT_YUV:
write_add_yuv(outfilename, width, height, components);
break;
/*by liushasha*/
case TINYJPEG_FMT_GREY:
write_pgm(outfilename, width, height, components);
break;
}
......
}
/*by liushasha*///YUV文件写出
static void write_add_yuv(const char *filename, int width, int height, unsigned char **components)
{
FILE *F;
char temp[1024];
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);
}
/*by liushasha*/
static void usage(void)
{
fprintf(stderr, "Usage: loadjpeg [options] \n");
fprintf(stderr, "options:\n");
fprintf(stderr, " --benchmark - Convert 1000 times the same image\n");
fprintf(stderr, "format:\n");
fprintf(stderr, " yuv420p - output 3 files .Y,.U,.V\n");
/*by liushasha*/
fprintf(stderr, " yuv - output a files.YUV\n");
/*by liushasha*/
fprintf(stderr, " rgb24 - output a .tga image\n");
fprintf(stderr, " bgr24 - output a .tga image\n");
fprintf(stderr, " gray - output a .pgm image\n");
exit(1);
}
int main(int argc, char *argv[])
{ ......
/*by liushasha*/
#if TABLES
char temp[1024];
snprintf(temp,1024,"%s_tables.txt",ouput_filename);
#endif
/*by liushasha*/
......
if (strcmp(argv[current_argument+1],"yuv420p")==0)
output_format = TINYJPEG_FMT_YUV420P;
/*by liushasha*/
else if (strcmp(argv[current_argument+1],"yuv")==0)
output_format = TINYJPEG_FMT_YUV;
/*by liushasha*/
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
......
/*by liushasha*/
#if TABLES
fclose(f_tables);
#endif
/*by liushasha*/
return 0;
}
在tinyjepg.h中定义
/*by liushasha*/
#define TABLES1
FILE *f_tables;
/*by liushasha*/
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
......
while (stream < dqt_block_end)//检查是否还有表
{......
/*by liushasha*/
#if TABLES
fprintf(f_tables,"Quantization_table[%d]:\n",qi);
fflush(f_tables);
#endif
/*by liushasha*/
......
}
......
}
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++) {
/*by liushasha*/
#if TABLES
fprintf(f_tables,"%d\t",ref_table[*zz]);
fflush(f_tables);
if(j==7)
{
fprintf(f_tables,"%\n");
fflush(f_tables);
}
#endif
/*by liushasha*/
*qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j]; } }}
static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)
{
......
if (index & 0xf0 )
{
/*by liushasha*/
#if TABLES
fprintf(f_tables,"Huffman table AC[%d]length=%d\n",index & 0xf,count);
fflush(f_tables);
#endif
/*by liushasha*/
build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);
}
else
{
/*by liushasha*/
#if TABLES
fprintf(f_tables,"Huffman table DC[%d]length=%d\n",index & 0xf,count);
fflush(f_tables);
#endif
/*by liushasha*/
build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);
}
......
}
......
}
static void build_huffman_table(const unsigned char *bits, const unsigned char *vals, struct huffman_table *table)
{
......
for (i=0; huffsize[i]; i++)
{
......
/*by liushasha*/
#if TABLES
fprintf(f_tables,"val==%2.2x code=%8.8x codesize=%2.2d\n",val, code, code_size);
fflush(p_tables);
#endif
/*by liushasha*/
......
}
}
tinyjpeg.h
/*liushasha*/
FILE *DC_FILE;
FILE *AC_FILE;
static void output_DC_Image(struct jdec_private *priv);
static void output_AC_Image(struct jdec_private *priv);
static void output_DC_AC_Image(struct jdec_private *priv);
/*liushasha*/
tinyjepg-internal.h
struct jdec_private
{
......
/*by liushasha*/
short int *DC_Imaage;
short int *AC_Imaage;
/*by liushasha*/
......
};
int main(int argc, char *argv[])
{
......
/*by liushasha*/
char temp[1024];
snprintf(temp,1024,"%s_dc.y",output_filename);
DC_FILE=fopen(temp,"wb");
snprintf(temp,1024,"%s_ac.y",output_filename);
AC_FILE=fopen(temp,"wb");
/*by liushasha*/
......
/*by liushasha*/
fclose(AC_FILE);
fclose(DC_FILE);
/*by liushasha*/
return 0;
}
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
......
/*by liushasha*/
priv->DC_Image=(short int *)malloc(sizeof(short int)*priv->height*priv->width/64);
priv->AC_Image=(short int *)malloc(sizeof(short int)*priv->height*priv->width/64);
/*by liushasha*/
......
return 0;
}
以decode_MCU_1x1plane为例添加代码,其他的类似。
static void decode_MCU_1x1_1plane(struct jdec_private *priv)
{
// Y
process_Huffman_data_unit(priv, cY);
/*by liushasha*/
output_DC_Image(priv);
output_AC_Image(priv);
/*by liushasha*/
IDCT(&priv->component_infos[cY], priv->Y, 8);
// Cb
process_Huffman_data_unit(priv, cCb);
IDCT(&priv->component_infos[cCb], priv->Cb, 8);
// Cr
process_Huffman_data_unit(priv, cCr);
IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}
/*by liushasha*/
static void output_DC_Image(struct jdec_private *priv)
{
static long int i=0;
if(iheight*priv->width/64)
priv->DC_Image[i]=priv->component_infos[0].DCT[0];
i++;
}
static void output_AC_Image(struct jdec_private *priv)
{
static long int i=0;
if(iheight*priv->width/64)
priv->AC_Image[i]=priv->component_infos[0].DCT[1];
i++;
}
/*by liushasha*/
void tinyjpeg_free(struct jdec_private *priv)
{
int i;
for (i=0; icomponents[i])
free(priv->components[i]);
priv->components[i] = NULL;
}
/*by liushasha*/
if(priv->AC_Image) free(priv->AC_Image);
if(priv->DC_Image) free(priv->DC_Image);
/*by liushasha*/
free(priv);
}
/*by liushasha*/
static void write_DC_AC_Image(struct jdec_private *priv)
{
int i;
short int DC_min, DC_max, AC_min, AC_max;
unsigned char *temp;
DC_min = priv->DC_Image[0];
DC_max = priv->DC_Image[0];
AC_min = priv->AC_Image[0];
AC_max = priv->AC_Image[0];
temp = (unsigned char*)malloc(priv->height*priv->width / 64);
for (i = 0; i < (priv->height*priv->width/64); i++)
{
if (priv->DC_Image[i] > DC_max)
DC_max = priv->DC_Image[i];
if (priv->DC_Image[i] < DC_min)
DC_min = priv->DC_Image[i];
if (priv->AC_Image[i] > AC_max)
AC_max = priv->AC_Image[i];
if (priv->AC_Image[i] < AC_min)
AC_min = priv->AC_Image[i];
}
for (i = 0; i < (priv->height*priv->width/64); i++)
{
temp[i] = (unsigned char)255 * (priv->DC_Image[i] - DC_min) / (DC_max - DC_min);
}
fwrite(temp, 1, priv->width*priv->height / 64, DC_FILE);
for (i = 0; i < (priv->height*priv->width / 64); i++)
{
temp[i] = (unsigned char)255 * (priv->AC_Image[i] - AC_min) / (AC_max - AC_min);
}
fwrite(temp, 1, priv->width*priv->height / 64, AC_FILE);
if (temp) free(temp);
}
可观看的 yuv文件:
以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。输出DC图像并经过huffman统计其概率分布:
输出某一个AC值图像并统计其概率分布: