7、JPEG编解码

目录

一、JPEG编码原理

二、JPEG文件格式

三、JPEG解码流程

四、实验内容

1. 将JPEG文件输出为YUV文件

2. 三个结构体

① struct huffman_table

② struct component 

③ struct jdec_private

3. TRACE

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

①量化矩阵

②HUFFMAN码表

 5. 输出DC和AC图像并统计其概率分布


一、JPEG编码原理

1. 零偏置Level Offset

  • 对于灰度级是 2^{n}的像素,通过减去 2^{n-1},将无符号的整数值变成有符号数。
    例如: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;
}

猜你喜欢

转载自blog.csdn.net/m0_51333023/article/details/125666088