JPEG解码实验

一、JPEG编解码原理

JPEG解码实验_第1张图片

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,则用边缘像素进行填充。

JPEG解码实验_第2张图片

总体来说,图像的低频部分集中在每个8*8块的左上角,高频部分在右下角。DCT变化可以做到以下三点:1、能量守恒  2、能量集中  3、去相关

3、量化 
根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化。 由于上面的人眼视觉特性,量化表左上角的值较小,右上角的值较大,这样就起到了保持低频分量,抑制高频分量的目的。 

4、DC系数的差分编码

8X8图像块经过DC丁变换之后得到的DC直流系数有两个特点:1.系数的数值比较大2.相邻8X8图像块的DC系数值变化不大。根据这个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:DIFFk =DCk - DCk-1。

JPEG解码实验_第3张图片

5、AC系数的Z字扫描

由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。尤其在最后,如果都是零,给出EOB (End of Block)即可。

JPEG解码实验_第4张图片

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文件格式

  • SOI,Start of Image,图像开始
  • APP0,Application,应用程序保留标记0
  • DQT,Define Quantization Table,定义量化表
  • SOF0,Start of Frame,帧图像开始
  • DHT,Define Huffman Table,定义哈夫曼表
  • SOS,Start of Scan,扫描开始12字节
  • EOI,End of Image,图像结束2字节 
SOI ,Start of Image,   图像开始

 标记代码  2字节  固定值0xFFD8


EOI,End of Image,   图像结束 2字节

 标记代码  2字节  固定值0xFFD9


APP0应用程序保留标记

标记代码  2字节  固定值0xFFE0
包含9个具体字段:
  ① 数据长度  2字节  ①~⑨9个字段的总长度
  ②标识符  5字节   固定值0x4A46494600,即字符串“JFIF0”
  版本号  2字节   一般是0x0102,表示JFIF的版本号1.2
  ④ X Y 的密度单位  1字节  只有三个值可选
  0:无单位;1:点数/英寸;2:点数/厘米
  X 方向像素密度      2字节  取值范围未知
  Y 方向像素密度         2字节  取值范围未知  
  ⑦ 缩略图水平像素数目  1字节  取值范围未知
  ⑧ 缩略图垂直像素数目  1字节  取值范围未知
  ⑨ 缩略图 RGB 位图        长度可能是3的倍数  缩略图RGB位图数据

DQT 定义量化表 

标记代码  2字节  固定值0xFFDB
包含9个具体字段:
  ① 数据长度  2字节  字段①和多个字段②的总长度
  ② 量化表    数据长度-2字节

    a)精度及量化表ID  1字节 

       高4位:精度,只有两个可选值     0:8位;1:16位
     低4位:量化表ID,取值范围为0~3

    b)表项          (64×(精度+1))字节

         例如8位精度的量化表,其表项长度为64×(0+1)=64字节

n本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次
SOF0  帧图像开始 
标记代码  2字节  固定值0xFFC0
包含9个具体字段:
  ① 数据长度  2字节  ①~⑥六个字段的总长度
  ②精度           1字节  每个数据样本的位数
   通常是8位,一般软件都不支持 12位和16位
  ③ 图像高度  2字节  图像高度(单位:像素)
  ④图像宽度  2字节  图像宽度(单位:像素)
  ⑤颜色分量数  1字节  只有3个数值可选
   1:灰度图;3:YCrCb或YIQ;4:CMYK
   而JFIF中使用YCrCb,故这里颜色分量数恒为3
  颜色分量信息  颜色分量数×3字节(通常为9字节)                                       
               a) 颜色分量 ID  1字节 

              b)水平/垂直采样因子  1字节 

                              高4位:水平采样因子
                            低4位:垂直采样因子
               c) 量化表  1字节                                                            当前分量使用的量化表的ID

DHT,定义哈夫曼表
标记代码   2字节  固定值0xFFC4
包含 2 个具体字段

     ① 数据长度      2字节 

     ② huffman 数据长度-2字节

ID和表类型 1字节  

                     高4位:类型,只有两个值可选

                                  0:DC直流;1:AC交流
        低4位:哈夫曼表ID,

                                  注意,DC表和AC表分开编码

不同位数的码字数量 16字节
编码内容  16个不同位数的码字数量之和(字节)
本标记段中,字段②可以重复出现(一般4次),也可以只出现1次。

SOS     扫描开始

标记代码         2字节  固定值0xFFDA
包含2个具体字段:
 ①数据长度           2字节  ①~④两个字段的总长度
 ②颜色分量数         1字节  应该和SOF中的字段⑤的值相同,即:
               1:灰度图是;3: YCrCb或YIQ;4:CMYK。
 ③颜色分量信息
    a)颜色分量ID   1字节
    b) 直流/交流系数表号  1字节 

           高4位:直流分量使用的哈夫曼树编号
        低4位:交流分量使用的哈夫曼树编号

  ④压缩图像数据
       a)谱选择开始  1字节  固定值0x00
        b)谱选择结束      1字节  固定值0x3F
        c)谱选择  1字节  在基本JPEG中总为00
三、代码及结果

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文件:

JPEG解码实验_第5张图片

以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。

JPEG解码实验_第6张图片JPEG解码实验_第7张图片

输出DC图像并经过huffman统计其概率分布:

JPEG解码实验_第8张图片JPEG解码实验_第9张图片

输出某一个AC值图像并统计其概率分布:

JPEG解码实验_第10张图片JPEG解码实验_第11张图片

你可能感兴趣的:(JPEG解码实验)