数据压缩实验五 JPEG原理分析JPEG解码器的调试

一、实验原理

1.JPEG原理及编码流程

  JPEG是常见的一种图像格式,由ISO与CCITT建立并开发,是一个国际数字图像压缩标准。JPEG文件的扩展名为.jpg或.jpeg,用有损方式去除冗余的图像与彩色数据,在获取极高的压缩率同时能展现十分生动的静态图像,JPEG被认为是目前压缩比最高的静态图像,它被广泛地应用于多媒体与网络程序中。
  根据人眼视觉特性:眼睛对亮度的敏感程度要大于对色彩的敏感程度。在图像中,为了利用人类的种视角特性,从而降低数据量,通常将RGB空间表示的彩色图像变换到YCbCr颜色空间中。由于人眼对亮度Y的敏感度大于色差CrCb,因此可以在适当程度上对CrCb进行削弱以达到压缩的目的。由于原始图像是由很多独立的像素组成的,其实人眼对于每个细微像素的分辨能力很弱,只有众多像素集合一块,才能呈现出颜色连续变化的图像,因此图像中相邻两像素点,其彩色分量在很大程度上是接近的。在一幅图像内,包含了各种频率的分量,但大多数分量都属于低频信号,只在占图像区域比例很小的图像边缘的像素才含有高频信号。因此在对图像编码的时候,在图像质量不出现可察觉损失的情况下,对包含信息量大的低频谱区分配较多比特数,对包含信息量较低的高频谱区域分配较少的比特数,就能达到数据压缩目的。
  DCT变换是将图像的色彩空间域转换到频谱域。DCT过程并不产生压缩作用,其作用是将图像数据去相关化、能量集中,去除图像数据内部的相关性后,以便在对这些图像数据分类处理——即对不同的频路部分进行不同的量化。
  量化编码是JPEG编码中产生信息损失的根源,也是图像质量下降的最主要原因。简单的说,就是将频谱领域中的每个值,除以量化表中对应的常数,且四舍五入取最接近的整数,这样会把很多高频的成分四舍五入为0。量化后左上角的值较小,右下角的值较大,这样就保持低频分量、抑制高频分量的目的。这一步在实现的时候会对Y进行细量化,对Cr、Cb采用粗量化,依次来提高压缩比。因此存在两张不同的表。
  经过DCT变换后,图像中的低频分量会集中在左上角,而右下角有较多的0值,因此采用Z字形编排。JPEG算法使用了差分脉冲编码(DPCM)技术,对相邻图像块之间量化DC洗漱的差值进行单独编码,从而再次利用相邻特性简化数据。并对剩余的63个交流(AC)系数进行游程编码。
  为了进一步提高压缩比例,JPEG算法对DPCM编码后的直流系数与游程编码后的交流系数使用Huffman熵编码。使用huffman码表可以简单的查表进行编码。对于AC与DC所采用的码表是不同的,对于色差和亮度的霍夫曼码表也不同。因此应该有四个霍夫曼码表。
JPEG编码流程:


这里写图片描述
JPEG编码流程如图,解码为逆过程

(1)零偏置(level offset)
  对于灰度级是2n的像素,通过减去2n-1,将无符号的整数值变成有符号数;对于n=8,即将0~255的值域,通过减去128,转换为值域在-128~127之间的值。这样做的目的是: 使像素的绝对值出现3位10进制的概率大大减少。
(2)8x8 DCT变换
  DCT变换是指对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,再以8x8的图像块为一个单位进行量化和编码处理。我们可以利用DCT变换去相关的特性,去除冗余信息,提高编码效率。
(3)量化
  我们可以通过量化减少数据的编码位数,提高编码效率;因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值;根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化。
(4)DC系数差分编码
  8×8图像块经过DCT变换之后得到的DC直流系数有两个特点:系数的数值比较大和相邻8×8图像块的DC系数值变化不大:冗余;根据这个特点, JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码: DIFFk=DCKDCK1 ,再对DIFF进行Huffman编码。
(5)AC系数的之字形扫描与游程编码
  由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。尤其在最后,如果都是零,给出EOB (End of Block)即可。zigzag扫描如下图:
这里写图片描述
  在经过之字形扫描排序后的AC系数,存在很多连0。为了进一步提高编码效率,因此对AC系数进行游程编码(RLC)处理之后,再进一步进行Huffman编码。
(6)AC和DC系数分别进行Huffman编码
  JPEG中共采用了四张Huffman码表:亮度DC、亮度AC、色度DC、色度AC,即分别对图像的亮度和色度,直流和交流数据进行编码处理。

2.JPEG文件格式分析

  JPEG文件的存储格式有很多种,但最常用的是JFIF格式,即JPEG File Interchange Format。JPEG文件大体可以分为两个部分:

(1)标记码:由两个字节构成,其中,前一个字节是固定值0XFF代表了一个标记码的开始,后一个字节不同的值代表着不同的含义。需要提醒的是,连续的多个0XFF可以理解为一个0XFF,并表示一个标记码的开始。另外,标记码在文件中一般是以标记代码的形式出现的。例如,SOI的标记代码是0XFFD8,即,如果JPEG文件中出现了0XFFD8,则代表此处是一个SOI标记。

(2)压缩数据:一个完整的两字节标记码的后面,就是该标记码对应的压缩数据了,它记录了关于文件的若干信息。


这里写图片描述
常见的标记码

3.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 转化为需要的色彩空间并保存。

二、关键代码分析

JPEG解码程序工程文件目录如下:
这里写图片描述
在tinyjpeg_internal文件中定义了三个结构体:


struct huffman_table(Huffman码表结构体)

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 (8*8宏块结构体)

<span style="font-weight: normal;">struct component 

{
  unsigned int Hfactor;//水平采样因子
  unsigned int Vfactor;//垂直采样因子
  float *Q_table; //指向该宏块使用的量化表
  struct huffman_table *AC_table;//指向该宏块直流系数的Huffman码表
  struct huffman_table *DC_table;//指向该宏块交流系数的Huffman码表
  short int previous_DC; /* Previous DC coefficient *///前一个块的DC系数
  short int DCT[64]; /* DCT coef *///该块的DCT系数,其中DCT[0]为该块直流,其他为交流
#if SANITY_CHECK
  unsigned int cid;
#endif
};</span>


struct jdec_private(文件解码信息结构体)

struct jdec_private(文件解码信息结构体)

{
  /* Public variables */
  uint8_t *components[COMPONENTS];//分别指向YUV分量结构体的指针数组
  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]; //对YUV进行量化的量化表
  struct huffman_table HTDC[HUFFMAN_TABLES]; //DC系数编码的Huffman码表
  struct huffman_table HTAC[HUFFMAN_TABLES]; //AC系数编码的Huffman码表
  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];//反DCT之后存三个分量的数组

  jmp_buf jump_state;
  /* Internal Pointer use for colorspace conversion, do not modify it !!! */
  uint8_t *plane[COMPONENTS];

};
1.读取文件

在主函数main函数中,我们打开对输入输出文件,并解析了输出格式:

int main(int argc, char *argv[])
{
  int output_format = TINYJPEG_FMT_YUV420P;//将输出格式初始化为yuv420P
  char *output_filename, *input_filename;//定义输入文件和输出文件指针
  clock_t start_time, finish_time;
  unsigned int duration;
  int current_argument;
  int benchmark_mode = 0;

#if TRACE//TRACE=1,则中间代码会编译,TRACE=0,则会忽略
  p_trace=fopen(TRACEFILE,"w");
  if (p_trace==NULL)
  {
      printf(
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值