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

本文介绍了JPEG编解码的基本原理,包括JPEG文件格式的各个组成部分,如SOI、EOI、DQT、SOF0、DHT和SOS。详细阐述了JPEG编码步骤,如零偏置、DCT变换、量化和AC/DC系数编码。同时,探讨了解码流程,包括读取文件信息、解码颜色分量和反量化等,以实现图像的正确复原。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(一)实验目的

掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。

JPEG( Joint Photographic Experts Group)是最常用的图像文件格式,其主要是采用预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式,以去除冗余的图像和彩色数据,属于有损压缩格式,它能够将图像压缩在很小的储存空间,一定程度上会造成图像数据的损伤。

(二)JPEG编解码原理

编码器
在这里插入图片描述

解码器为编码器的逆过程

2.1 JPEG文件格式介绍

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

2.1.1 SOI & EOI

SOI ,Start of Image, 图像开始
标记代码 2字节 固定值0xFFD8
EOI,End of Image, 图像结束 2字节
标记代码 2字节 固定值0xFFD9

2.1.2 APP0 应用程序保留标记0

标记代码 2字节 固定值0xFFE0
① 数据长度 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位图数据

2.1.3 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字节

本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次

2.1.4 SOF0 帧图像开始

标记代码 2字节 固定值0xFFC0
① 数据长度 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

2.1.5 DHT 定义哈夫曼表

标记代码 2字节 固定值0xFFC4
包含2个具体字段:
① 数据长度 2字节
② huffman表 数据长度-2字节

表ID和表类型 1字节
高4位:类型,只有两个值可选
0:DC直流;1:AC交流 低4位:哈夫曼表ID,
注意,DC表和AC表分开编码
不同位数的码字数量 16字节
编码内容 16个不同位数的码字数量之和(字节)本标记段中,字段②可以重复出现(一般4次),也可以只出现1次

2.1.6 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

2.2 JPEG编码步骤

  1. 零偏置(Level Offset)
    对于灰度级是2n的像素,通过减去2(n-1),将无符号的整数值变成有符号数,使像素的绝对值出现3位10进制的概率大大减少
  2. DCT变换
    对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,并作为二维离散余弦变换DCT的输入。将原本的像素数值进行DCT变换,后续直接对DCT分量进行处理。
  3. 量化
    对DCT分量进行量化,由于人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值。
    根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化。
    如果原始图象中细节丰富,则去掉的数据较多,量化后的系数与量化前差别。反之,细节少的原始图象在压缩时去掉的数据少些。
  4. DC系数差分编码
    DC直流系数有两个特点:系数的数值比较大,相邻DC系数值变化不大。
    根据上述两个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:
    DIFF_k=DC_k-DC_{k-1}
  5. AC系数之字形扫描
    由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。尤其在最后,如果都是零,给出 EOB (End of Block)即可。
    在这里插入图片描述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)

在这里插入图片描述

2.3 JPEG文件解码流程

1.读入文件的相关信息
2.初步了解图像数据流的结构
3.颜色分量单元的内部解码
4.直流系数的差分编码
5.反量化 & 反Zig-zag编码
6.反离散余弦变换

3 JPEG文件解析

3.1 理解三个结构体的设计目的

  1. struct huffman_table:存储Huffman码表
/* tinyjpeg-internal.h */
 
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];
};
  1. struct component:储存当前8×8像块中有关解码的信息
/* tinyjpeg-internal.h */
 
struct component 
{
   
  unsigned int Hfactor; // 水平采样因子
  unsigned int Vfactor; // 垂直采样因子
  float* Q_table;   // 指向该8×8块使用的量化表
  struct huffman_table *AC_table;   // 指向该块使用的AC Huffman表
  struct huffman_table *DC_table;   // 指向该块使用的DC Huffman表
  short int previous_DC;    // 前一个块的直流DCT系数
  short int DCT[64];    // DCT系数数组
    
#if SANITY_CHECK
  unsigned int cid;
#endif
};
  1. struct jdec_private:JPEG数据流结构体,用于存储JPEG图像宽高、数据流指针、Huffman码表等内容,并包含struct huffman_table和struct component
/* tinyjpeg-internal.h */
 
struct jdec_private
{
   
  /* Public variables */
  uint8_t *components[COMPONENTS];  /* 分别指向YUV三个分量的三个指针 */
  unsigned int width, height;	/* 图像宽高 */
  unsigned int flags;
 
  /* Private variables */
  const unsigned char *stream_begin, *stream_end;
  unsigned int stream_length;
 
  const unsigned char *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.2 理解整体框架

convert_one_image函数:读取判断输入文件格式,解码并按照输出格式输出

int convert_one_image(const char *infilename, const char *outfilename, int output_format)
{
   //输入文件名称、输出文件名称、输出文件格式
  FILE *fp;
  unsigned int length_of_file;  // 文件大小
  unsigned int width, height;   // 图像宽、高
  unsigned char *buf;   
  struct jdec_private *jdec; //JPEG数据数据流
  unsigned char *components[3];
 
  fp = fopen(infilename, "rb");//读取JPEG图像
  if (fp == NULL)
    exitmessage("Cannot open filename\n");
  length_of_file = filesize(fp);//输入文件的字节大小
  buf = (unsigned char *)malloc(length_of_file + 4);
  if (buf == NULL)
    exitmessage("Not enough memory for loading file\n");
  fread(buf, length_of_file, 1, fp);
  fclose(fp);
 
  /* Decompress it */
  jdec = tinyjpeg_init();   // 初始化
  if (jdec == NULL)
    exitmessage("Not enough memory to alloc the structure need for decompressing\n");
 
  if (tinyjpeg_parse_header(jdec, buf, length_of_file)<0)//读取头文件,检查图像格式
    exitmessage(tinyjpeg_get_errorstring(jdec));
 
  tinyjpeg_get_size(jdec, &width, &height);// 计算图像宽高
  snprintf(error_string, sizeof(error_string),"Decoding JPEG image...\n");
 
  if (tinyjpeg_decode(jdec, output_format) < 0) // 解码实际数据
    exitmessage(tinyjpeg_get_errorstring(jdec));
 
  /* 
   * Get address for each plane (not only max 3 planes is supported), and
   * depending of the output mode, only some components will be filled 
   * RGB: 1 plane, YUV420P: 3 planes, GREY: 1 plane
   */
  tinyjpeg_get_components(jdec, components);//将compoents结构体的数据传入jdec
 
  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_GREY:
      write_pgm(outfilename, width, height, components);
      break;
   }
 
  /* Only called this if the buffers were allocated by tinyjpeg_decode() */c
  tinyjpeg_free(jdec);
  /* else called just free(jdec); */
 
  free(buf);
  return 0;
}

tinyjpeg_init初始化函数:动态分配存储内存

struct jdec_private *tinyjpeg_init(void)
{
   
  struct jdec_private *priv;//声明JPEG数据结构体
 
  priv = (struct jdec_private *)calloc(1, sizeof(struct jdec_private));//在内存的动态存储区中分配1个struct jdec_private大小的连续空间
  if (priv == NULL)
    return NULL;
  return priv;
}

tinyjpeg_parse_header函数:解码JPEG头信息(FFD8-SOI)
读取完前两字节后,开始指针后移两位priv->stream_begin=buffer+2,文件未读长度-2,接parse_JFIF函数遍历整个文件,找到不同的标识码,并解析相应标识码对应的信息

int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size)
{
   
  int ret;
 
  
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值