JPEG解码_C语言实现

一、实验原理

1.JPEG编解码原理

JPEG(Joint Photographic Experts Group)采用有损压缩方式去除冗余的图像和彩色数据,在获得极高压缩率的同时能展现十分丰富生动的图像。

编码原理图如下:JPEG解码_C语言实现_第1张图片

① Level Offset(零偏置

为了使使像素的绝对值出现3位10进制的概率大大减少,对于灰度级是2的n次方的像素,通过减去2的n-1次方,将无符号的整数值变成有符号数。

② 8✖️8 DCT变换

变换公式如下:

JPEG解码_C语言实现_第2张图片

DCT变换的优点:1、能量守恒  2、能量集中  3、去相关

经过DCT变换后,能量集中在左上角,使得左上角数值较大,右下角数值较小。


③Uniform scalar quantization(量化)

因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值。由于人眼对低频敏感,对高频不太敏感,因此对低频分量采取较细的量化,对高频分量采取较粗的量化。


dc quantization indices Differential coding(DC系数的差分编码

8×8图像块经过DCT变换之后得到的DC直流系数有两个特点:系数的数值比较大; 相邻8×8图像块的DC系数值变化不大(产生冗余)。

根据这个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:DIFFk =DCk - DCk-1。

码表如下(此编码表并不是一成不变的,不同的文件有不同的码表。以此码表为例,DC=8,上一DC=5时,则DIFF=8-5=3,类别ID=2,类内索引=3,则码流为10011):

JPEG解码_C语言实现_第3张图片

⑤Zig-zag scan(AC系数的Z字扫描

由于量化之后右下角高频系数大部分为0,在编码是为了制造更长的0游程提高编码效率,采用之字形扫描读取法。经过之字形扫描读出后把二维系数矩阵转换为一维数据序列。在最后, 如果都是零,给出 EOB (End of Block) 即可。 
zig-zag序

JPEG解码_C语言实现_第4张图片


AC系数的游程编码(Run-level coding

编码规则:(run,level) 
表示连续run个0,后面跟着值为level的AC系数。 

run:最多15个,故用4位表示。

level:类似DC系数,分为类别ID与类内索引。用4位表示类别ID。 

(run,level的类别ID)合成1字节存在AC系数码表的权值中

AC系数的之字形序列编码中有两个特殊符号——(0,0)和(15,0)。第一个特殊符号指的是块的结束(end-of-block,EOB),用来表明在之字形块中剩余的元素都是零。另一个特殊符号是指零游程长度(zero-run-length,ZRL),用来表明16个零游程。基线(baselinesequential)JPEG算法允许的零游程最大长度是16个。如果这里的零超过16个,那么这个游程分成几个长度为16的零游程。 
经过RLE编码的AC系数可以映射成两个标志(RUNLENGTH,CATEGORY)和(AMPLITUDE),前者采用的是霍夫曼编码,而后者采用的是VLI编码(直接二进制编码)。同理经过DPCM编码的DC系数同样可以映射成两个标志(CATEGORY)和(AMPLITUDE),,前者采用霍夫曼编码,后者采用VLI编码。

解码过程是编码过程的反过程


2. JPEG文件格式

英文缩写 英文全称 说明 标志代码
SOI Start of Image 图像开始 0xFFD8
APPn Application 应用程序保留标记n 0xFFEn
DQT Define Quantization Table 定义量化表 0XFFDB
SOF0 Start of Frame 帧图像开始 0xFFC0
DHT Define Huffman Table 定义哈夫曼表 0xFFC4
SOS Start of Scan 扫描开始,12字节 0xFFDA
EOI End of Image 图像结束,2字节 0xFFD9

JPEG文件以FFD8开头

APPn是文件细节信息,看图片时,没有点进去就显示出来的缩略图就是存在这部分里,下图FFE0为APP0,从FFE0依次向后读:00 10表示该部分占16字节;4A 46 49 4600表示JFIF0;01 01表示版本号;00 表示X和Y方向的密度单位(0:无单位、1:1像素/英寸、2:2像素/厘米);00 01表示X方向像素密度;00 01表示Y方向像素密度;最后的00和00分别表示缩略图的X和Y方向像素数,由于该文件没有缩略图,后面就没有缩略的RGB位图的数据。

文件中有两个FFDB,说明有两张量化表(DQT),以第一张量化表为例,从FFDB向后读:00 43表示量化表的长度为67字节;00的低四位表示第0张量化表,高四位表示8bit的量化精度(非0则16bit);最后是8*8的量化表实际数据,按之字形保存。

FFC0代表帧图像的开始(SOF0),向后读:00 11表示该部分的长度17字节;08表示每个颜色分量每个像素占8bit;04 00 04 00表示图像的高和宽分别为1024和1024像素;03 表示有3个颜色分量(YCbCr);后面9个字节,每3个一组,分别表示Y、Cb、Cr的如下信息,1字节的颜色ID(01、02、03)、1字节的采样因子(高四位水平 1、低四位垂直 1,说明MCU为8*8)、该分量使用的量化表(00、01、01)。

该文件有4个FFC4,表示有4张huffman码表,以第一张为例:001D表示这张表的长度为29字节;00高四位表示直流(1为交流),低四位表示第0张表;后面16字节为不同位数码字的数量,00 03 01 01……00等表示码字为1、2、3……16位的个数分别为0、3、1、1……0个,相加得到码字总数为10;最后是这10个码字对应的权重,表示解码时需要再读入的位数。

FFDA表示扫描开始(SOS):00 0C表示这部分长度12字节;03表示颜色分量数为3; 后面6个字节。每2个一组表示一种颜色分量的ID和该分量使用的直流与交流量化表,01 00表示Y分量,交流使用第0张量化表,直流使用第0张量化表,02 11表示Cb分量,交流直流均使用第1张量化表,03 11表示Cr分量,交流直流均使用第1张量化表;后面是压缩图像数据,00是固定值,表示谱选择开始,3F是固定值,表示谱选择结束,00是固定值,表示谱选择。

后面是图像流的组成部分,需要进行译码,直至遇到FF D9(EOI)结束。


二、代码分析

任务一:调试JPEG解码器程序,将输出文件保存为可供YUVViewer观看的YUV文件。

1. 定义输入参数的枚举类型enum tinyjpeg_fmt中增加TINYJPEG_FMT_YUV一项

enum tinyjpeg_fmt {
   TINYJPEG_FMT_GREY = 1,
   TINYJPEG_FMT_BGR24,
   TINYJPEG_FMT_RGB24,
   TINYJPEG_FMT_YUV420P,
   /*******************/
   TINYJPEG_FMT_YUV,//增加
   /*******************/
};

2.  convert_one_image函数的判断输出格式里增加caseTINYJPEG_FMT_YUV一项。
 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;
    /***************************************************************/
    case TINYJPEG_FMT_YUV:
        write_YUV(outfilename, width, height, components);
        break;//增加
    /***************************************************************/
    case TINYJPEG_FMT_GREY:
      write_pgm(outfilename, width, height, components);
      break;
   }

3. main函数,增加解析命令行。

if (strcmp(argv[current_argument+1],"yuv420p")==0)
    output_format = TINYJPEG_FMT_YUV420P;
  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 if (strcmp(argv[current_argument+1],"yuv")==0)
    output_format = TINYJPEG_FMT_YUV;//增加
  /*********************************************************/
  else
    exitmessage("Bad format: need to be one of yuv420p, rgb24, bgr24, grey\n");

4.在 tinyjpeg_decode函数中switch里增加caseTINYJPEG_FMT_YUV项

 

switch (pixfmt) {
        ......
        //增加start
     case TINYJPEG_FMT_YUV:
       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;
       //增加end
      ......
  }

任务二:

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

1. 定义指向txt文件的指针

FILE *p_ytxt;
#define YTXT 1 
#define YTXTFILE "ld.txt" 

2.  打开txt文件

#if YTXT
  p_ytxt = fopen(YTXTFILE, "w");
  if (p_ytxt == NULL)
  {
      printf("trace file open error!");
  }
#endif

3.  输出量化矩阵

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++) {
    #if YTXT
              fprintf(p_ytxt, "%d ", ref_table[*zz]);//输出以zigzag矩阵所规定的顺序的量化矩阵。原因是ref_table指针中存着的着量化表值是以z字形扫描的次序来进行存储的,因此需要利用zigzag进行正确的排序
              fflush(p_ytxt);
    #endif
          *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
          }
         #if YTXT
              fprintf(p_ytxt, "\n");
              fflush(p_ytxt);
         #endif

  }

}

4.zigzag矩阵

static const unsigned char zigzag[64] = 
{
   0,  1,  5,  6, 14, 15, 27, 28,
   2,  4,  7, 13, 16, 26, 29, 42,
   3,  8, 12, 17, 25, 30, 41, 43,
   9, 11, 18, 24, 31, 40, 44, 53,
  10, 19, 23, 32, 39, 45, 52, 54,
  20, 22, 33, 38, 46, 51, 55, 60,
  21, 34, 37, 47, 50, 56, 59, 61,
  35, 36, 48, 49, 57, 58, 62, 63
};

5.  输出Huffman码表(包含长度、AC/DC信息),输出Huffman码表的码长、码字、对应的符号

static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)
{
  unsigned int count, i;
  unsigned char huff_bits[17];
  int length, index;

  length = be16_to_cpu(stream) - 2;
  stream += 2;  /* Skip length */
#if TRACE
  fprintf(p_trace,"> DHT marker (length=%d)\n", length);
  fflush(p_trace);
#endif
#if YTXT
  fprintf(p_ytxt, "> DHT marker (length=%d)\n", length);
  fflush(p_ytxt);
#endif
  while (length>0) {
     index = *stream++;

     /* We need to calculate the number of bytes 'vals' will takes */
     huff_bits[0] = 0;
     count = 0;
     for (i=1; i<17; i++) {
    huff_bits[i] = *stream++;
    count += huff_bits[i];
     }
#if SANITY_CHECK
     if (count >= HUFFMAN_BITS_SIZE)
       snprintf(error_string, sizeof(error_string),"No more than %d bytes is allowed to describe a huffman table", HUFFMAN_BITS_SIZE);
     if ( (index &0xf) >= HUFFMAN_TABLES)
       snprintf(error_string, sizeof(error_string),"No more than %d Huffman tables is supported (got %d)\n", HUFFMAN_TABLES, index&0xf);
#if TRACE
     fprintf(p_trace,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);
     fflush(p_trace);
#endif
#if YTXT
     fprintf(p_ytxt, "Huffman table %s[%d] length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);
     fflush(p_ytxt);
#endif
#endif

     if (index & 0xf0 )
       build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);
     else
       build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);

     length -= 1;
     length -= 16;
     length -= count;
     stream += count;
  }


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
    #if YTXT
         fprintf(p_ytxt, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
         fflush(p_ytxt);
    #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 */
      }

   }
}

任务三:输出DC图像并经过huffman统计其概率分布,并输出某一个AC值图像并统计其概率分布。

FILE *dc_file;
FILE *ac_file;

#if DC
  dc_file = fopen(DCFILE, "wb");
  if (dc_file == NULL)
  {
      printf("trace file open error!");
  }
#endif
 
#if AC
  ac_file = fopen(ACFILE, "wb");
  if (ac_file == NULL)
  {
      printf("trace file open error!");
  }
#endif
1

在JPEG结构体中定义指针,用于写入AC和DC图像

struct jdec_private
{
  int  *acimage,*dcimage;
}

priv->dcimage = (int *)malloc(sizeof(int)*priv->width * priv->height/64);
priv->acimage = (int *)malloc(sizeof(int)*priv->width * priv->height / 64);



static void DC_image(struct jdec_private *priv)
{
    static int i=0;
    if ( i < (priv->height*priv->width / 64))
    {
        priv->dcimage[i] = priv->component_infos[cY].DCT[0];
    }
    i++;
}
static void AC_image(struct jdec_private *priv)
{
    static int i = 0;
    if (i < (priv->height*priv->width / 64))
    {
        priv->acimage[i] = priv->component_infos[cY].DCT[1];
    }
    i++;
}

输出为yuv文件

 int acmax, dcmax, acmin, dcmin;
  int tmp;
  acmax = priv->acimage[0];
  acmin = priv->acimage[0];
  dcmax = priv->dcimage[0];
  dcmin = priv->dcimage[0];
  for (i = 0; i < priv->width*priv->height / 64; i++)
  {
      if (priv->acimage[i] >=acmax)
          acmax = priv->acimage[i];
      if (priv->dcimage[i] >= dcmax)
          dcmax = priv->dcimage[i];
      if (priv->acimage[i] <= acmin)
          acmin = priv->acimage[i];
      if (priv->dcimage[i] <=dcmin)
          dcmin = priv->dcimage[i];
  }
  for (i = 0; i < priv->width*priv->height / 64; i++)
  {
      tmp = priv->acimage[i] - acmin;
      acfileout[i] = (unsigned char)(255 *(priv->acimage[i]-acmin)/ (acmax - acmin));
  }
  fwrite(acfileout, 1, priv->width*priv->height / 64, ac_file);
  if (acfileout)
      free(acfileout);
  for (i = 0; i < priv->width*priv->height / 64; i++)
  {
      dcfileout[i] = (unsigned char)(255 *(priv->dcimage[i]-dcmin)/ (dcmax - dcmin));
  }
  fwrite(dcfileout, 1, priv->width*priv->height / 64, dc_file);
  if (dcfileout)
      free(dcfileout);

三、实验结果

输出量化表

JPEG解码_C语言实现_第5张图片

输出的Huffman码表:

JPEG解码_C语言实现_第6张图片

JPEG解码_C语言实现_第7张图片JPEG解码_C语言实现_第8张图片


原始图像YUV:

JPEG解码_C语言实现_第9张图片

输出的DC图像及其概率分布图:

 JPEG解码_C语言实现_第10张图片JPEG解码_C语言实现_第11张图片


输出的AC图像及其概率分布图:

 JPEG解码_C语言实现_第12张图片JPEG解码_C语言实现_第13张图片


JPEG解码_C语言实现_第14张图片JPEG解码_C语言实现_第15张图片

JPEG解码_C语言实现_第16张图片JPEG解码_C语言实现_第17张图片


蚊子噪声的图像 

JPEG解码_C语言实现_第18张图片

压缩程度越剧烈,量化的步长越大,高频系数为零的可能性就越大,即高频截止,于是轮廓周围会出现许多蚊子似的点,即空间域出现蚊子噪声。

你可能感兴趣的:(JPEG解码_C语言实现)