JPEG 原理分析及 JPEG 解码器的调试

来源:互联网 发布:虹云网络 编辑:程序博客网 时间:2024/06/18 14:47

实验原理

JPEG简介

JPEG是数字图像的有损压缩的常用方法,特别是对于由数码摄影产生的图像。JPEG(Joint Photographic Experts Group)是在国际标准化组织(ISO)领导之下制定静态图像压缩标准的委员会,第一套国际静态图像压缩标准ISO 10918-1(JPEG)就是该委员会制定的。由于JPEG优良的品质,使他在短短几年内获得了成功,被广泛应用于互联网和数码相机领域,网站上80%的图像都采用了JPEG压缩标准。

JPEG编解码流程

图片来自维基百科

编码流程

这里写图片描述

彩色空间转换

首先,图像应该从RGB转换成不同的颜色空间,称为Y’CBCR(或非正式地,YCbCr)。它具有三个分量Y’,CB和CR:Y’分量表示像素的亮度,CB和CR分量表示色度

下采样

由于人眼对亮度比对色度更加敏感的视觉特性,因此通过降低Cb和Cr分量的空间分辨率(称为“下采样”)来进行编码。 4:4:4(无下采样),4:2:2(在水平方向上减少2倍),或(最常见)4:2: 0(在水平和垂直两个方向上减少2倍)。

分块

下采样后,每个通道必须分为8×8块。 取决于色度下采样,产生大小为8×8(4:4:4 - 无下采样),16×8(4:2:2)或最通常为16×16(4:4)的最小编码单元(MCU)2:0)。 在视频压缩中,MCU被称为宏块。本实验中采取的是8×8的宏块。

零偏置level offset

对于灰度级是2n的像素,通过减去2(n1),将无符号 的整数值变成有符号数。 对于n=8,即将0~255的值域,通过减去128,转换为值 域在-128~127之间的值。目的:使像素的绝对值出现3位10进制的概率大大 减少。

8×8的离散余弦变换

将每个8×8的块进行DCT变换。DCT的变换公式:
这里写图片描述

量化

1.采用中平型均匀量化器量化
2.步距是按照系数(所在的位置 、颜色分量 )来确定。
因为人眼对亮度信号比对色差信号更敏感,因此使用了两 种量化表:亮度量化值和色差量化值。
3. 根据人眼的视觉特性(对低频敏感,对高频不太敏感)对 低频分量采取较细的量化,对高频分量采取较粗的量化(如果原始图象中细节丰富,则去掉的数据较多,量化后的系数与 量化前差别;反之,细节少的原始图象在压缩时去掉的数据少些)
真正的量化表=缩放因子×基本量化表
质量因子≤ 50:缩放因子= 50 / 质量因子;
质量因子> 50:缩放因子 = 2 – 质量 因子/ 50

DC系数的差分编码

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

AC系数的Z字扫描

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

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编码
对类内索引用定长码编码

解码流程

解码Huffman数据
解码DC差值
重构量化后的系数
DCT逆变换
丢弃填充的行/列
反0偏置
对丢失的CbCr分量差值(下采样的逆过程)
YCbCr to RGB

JPEG文件格式

这里写图片描述

名称 标记 全称 SOI 0xFF, 0xD8 Start Of Image SOF0 0xFF, 0xC0 Start Of Frame (baseline DCT) DHT 0xFF, 0xC4 Define Huffman Table(s) DQT 0xFF, 0xDB Define Quantization Table(s) SOS 0xFF, 0xDA Start Of Scan APP0 0xFF, 0xEn Application-specific EOI 0xFF, 0xD9 End Of Image

这里写图片描述

代码及分析

任务一

将输出文件保存为可供YUVViewer观看的YUV文件

enum tinyjpeg_fmt {   TINYJPEG_FMT_GREY = 1,   TINYJPEG_FMT_BGR24,   TINYJPEG_FMT_RGB24,   TINYJPEG_FMT_YUV420P,   TINYJPEG_FMT_YUV,//add by ying};

当输入的指令为yuv时,那么将会输出一个yuv的图像。

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

case TINYJPEG_FMT_YUV时,不break,与case TINYJPEG_FMT_YUV420P执行相同的操作。

switch (pixfmt) {     case TINYJPEG_FMT_YUV://add by ying     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;

当output_format为新定义的TINYJPEG_FMT_YUV时,则进入重新编写的write_yuv2函数。

  /* Save it */  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_yuv2(outfilename, width, height, components);        break;//add by ying    case TINYJPEG_FMT_GREY:      write_pgm(outfilename, width, height, components);      break;   }

自己定义的写入yuv文件的函数,即将y、u、v写入同一个文件中即可

static void write_yuv2(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],1,width* height, F);    fwrite(components[1], 1, width* height/4, F);    fwrite(components[2], 1, width* height/4, F);}

任务二

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

定义指向txt文件的指针

FILE *p_ytxt;//add by ying
#define YTXT 1 //add by ying#define YTXTFILE "ying.txt" //add by ying

打开txt文件

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

输出量化矩阵

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  }}

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};

输出Huffman码表,包含长度、AC/DC信息

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;  }

输出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    #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值图像并统计其概率分布

输出DC和AC的yuv文件指针

FILE *dc_file;//add by yingqiFILE *ac_file;//add by yingqi

打开AC和DC文件

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

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

struct jdec_private{  int  *acimage,*dcimage;//add by yingqi

在函数tinyjpeg_decode中给两个指针开辟空间

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

用于将dc和ac系数写入指针指向的空间

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++;}//add by yingqistatic 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++;}//add by yingqi

将其归一化后输出为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);

实验结果

输出的可供yuvviewer观看的yuv图像

这里写图片描述

输出的量化矩阵和霍夫曼码表

这里写图片描述

输出的DC图像及AC图像

这里写图片描述
AC图像概率分布
这里写图片描述
DC图像概率分布
这里写图片描述

蚊子噪声

蚊子噪声是有压缩引起的产生与相邻像素之间的量化错误。场景内容不同,量化间隔的尺度会发生变化。具体表现为图像上出现微微发光的小亮块,像蚊子一样闪现在轮廓的周围。

图像质量 具体图像 高质量 right-aligned 较高质量 centered 低质量 are neat

具体细节:
这里写图片描述

阅读全文
0 0
原创粉丝点击