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

本文深入探讨JPEG编码器的原理,包括零偏置、DCT变换、量化和编码等步骤。同时,详细解析JPEG文件格式,如SOI、APP0、DQT等标记。此外,介绍了JPEG解码流程,从读取文件到解码结束,涉及Huffman表重建、MCU解码等关键步骤。最后,简要概述了程序实现和实验结果。

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

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

一·、实验原理

1. 标题JPEG编码器

在这里插入图片描述
1)零偏置:即把[0,255]的像素值减128变为[-128,127];(对于灰度级是2n的像素,通过减去2n-1,将无符号 的整数值变成有符号数,使像素的绝对值出现3位10进制的概率大大 减少)
(2)8×8DCT变换:能量集中和去相关,减小空间冗余;
在这里插入图片描述

(3)量化:根据人眼视觉特性,低频细量化,高频粗量化,减小视觉冗余;
在这里插入图片描述

(4)编码:直流系数进行差分和VLC编码,交流系数进行之字形扫描、游程编码和VLC编码,减少数据冗余。

2.JPEG文件格式

SOI :Start of Image,图像开始;标记代码:2字节,固定值:0xFFD8

APP0:Application,应用程序,保留标记 0;标记代码 :2字节,固定值:0xFFE0
包含9个具体字段:

① 数据长度 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位图数据
DQT:Define Quantization Table,定义量化表 ;标记代码: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次
SOF0:Start of Frame,帧图像开始;标记代码:2字节;固定值:0xFFC0
包含9个具体字段:

① 数据长度 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
DHT:Define Huffman Table,定义哈夫曼表;标记代码:2字节;固定值:0xFFC4
包含2个具体字段:

① 数据长度 2字节
② huffman表 数据长度-2字节
表ID和表类型 1字节
高4位:类型,只有两个值可选
0:DC直流;1:AC交流 低4位:哈夫曼表ID,
注意,DC表和AC表分开编码
不同位数的码字数量 16字节
编码内容:16个不同位数的码字数量之和(字节)本标记段中,字段②可以重复出现(一般4次),也可以只出现1次。
SOS:Start of Scan,扫描开始:12字节;标记代码: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
EOI:End of Image,图像结束:2字节;标记代码:2字节,固定值:0xFFD9
在这里插入图片描述
在这里插入图片描述

3. JPEG 的解码流程

3.1 读取文件

3.2 解析 Segment Marker
3.2.1 解析 SOI
3.2.2 解析 APP0
检查标识“JFIF”及版本
得到一些参数
3.2.3 解析 DQT
得到量化表长度(可能包含多张量化表)
得到量化表的精度  得到及检查量化表的序号(只能是 0 —— 3)
得到量化表内容(64 个数据)
3.2.4 解析 SOF0
得到每个 sample 的比特数、长宽、颜色分量数
得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表 序号(与 DQT 中序号对应)
3.2.5 解析 DHT
得到 Huffman 表的类型(AC、DC)、序号
依据数据重建 Huffman 表 3.2.6 解析 SOS
得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号(与 DHT 中序号对应)

3.3 依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个 MCU 中 8*8 宏块的个数

3.4 对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU 中每个分量宏块解 码)
3.4.1 对每个宏块进行 Huffman 解码,得到 DCT 系数
3.4.2 对每个宏块的 DCT 系数进行 IDCT,得到 Y、Cb、Cr
3.4.3 遇到 Segment Marker RST 时,清空之前的 DC DCT 系数

3.5 解析到 EOI,解码结束 3.6 将 Y、Cb、Cr 转化为需要的色彩空间并保存

二、程序实现

main函数

int main(int argc, char *argv[])
{
   
  int output_format = TINYJPEG_FMT_YUV420P;
  char *output_filename, *input_filename;

  clock_t start_time, finish_time;
  unsigned int duration;
  int current_argument;
  int benchmark_mode = 0;

  if (argc < 3)//如果输入的参数小于3,显示使用指南
    usage();

  current_argument = 1;//目前进行到的argument
  while (1)
   {
   
     if (strcmp(argv[current_argument], "--benchmark")==0)//调试界面中的可选项[option]
       benchmark_mode = 1;
     else
       break;
     current_argument++;//指向下一串字符
   }

  if (argc < current_argument+2)//参数不合要求
    usage();//再次显示使用指南

  input_filename = argv[current_argument];//输入文件名
  //if判断要求哪种format,选择不同的输出形式
  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
    exitmessage("Bad format: need to be one of yuv420p, rgb24, bgr24, grey\n");//format错误
  output_filename = argv[current_argument+2];//输出文件名
  start_time = clock();

  if (benchmark_mode)
    load_multiple_times(input_filename, output_filename, output_format);
  else
    convert_one_image(input_filename, output_filename, output_format);
    
 finish_time = clock();
 duration = finish_time - start_time;
 snprintf(error_string, sizeof(error_string),"Decoding finished in %u ticks\n", duration);
#if TRACE
  fclose(p_trace);
#endif
  return 0;
}


load_multiple_times

/**
 * Load one jpeg image, and try to decompress 1000 times, and save the result.
 * This is mainly used for benchmarking the decoder, or to test if between each
 * called of the library the DCT is corrected reset (a bug was found).
 */
int load_multiple_times(const char *filename, const char *outfilename, int output_format)
{
   
  FILE *fp;
  int cout, length_of_file;//定义计数和输入文件长度
  unsigned int width, height;//定义宽和高
  unsigned char *buf;//定义一个buf的缓冲区
  struct jdec_private *jdec;//定义一个jdec_private类型的jdec
  unsigned char *components[4];//定义指针数组

  jdec = tinyjpeg_init();
  count = 0;

  /* Load the Jpeg into memory */
  fp = fopen(filename, "rb");
  if (fp == NULL)
    exitmessage("Cannot open filename\n");
  length_of_file = filesize(fp);
  buf = (unsigned char *)malloc(length_of_file + 4);
  fread(buf, length_of_file, 1, fp);
  fclose(fp);

  while (count<1000)//图像解码1000次
   {
   
     if (tinyjpeg_parse_header(jdec, buf, length_of_file)<0)
       exitmessage(tinyjpeg_get_errorstring(jdec));

     tinyjpeg_decode(jdec, output_format);

     count++;
   }

  /* 
   * 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);
  tinyjpeg_get_size(jdec, &width, &height);

  /* 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_GREY:
      write_pgm(outfilename, width, height, components);
      break;
   }

  free(buf);
  tinyjpeg_free(jdec);
  return 0;
}

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;//定义一个buf的缓冲区
  struct jdec_private *jdec;//定义一个jdec_private类型的jdec
  unsigned char *components[3];//定义指针数组

  /* Load the Jpeg into memory */
  //打开文件之后把图片的基本信息写入
  fp = fopen(infilename, "rb");
  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)//解压缩JPEG图像的头信息
    exitmessage(tinyjpeg_get_errorstring(jdec));

  /* Get the size of the image */
  tinyjpeg_get_size(jdec, &width, &height);

  DCT_0_mat = (short int*)malloc(width * height / 64 * sizeof(short int));
  DCT_1_mat = (
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值