JPEG编码原理与解码分析

2023-11-20

JPEG编码原理

JPEG(Joint Photographic Experts Group)是JPEG标准的产物,该标准由国际标准化组织(ISO)制订,是面向连续色调静止图像的一种压缩标准。JPEG格式是最常用的图像文件格式,后缀名为.jpg或.jpeg。

JPEG标准中,规定了四种模式,以满足不同的需要:

  • 基于DPCM的无损编码模式:压缩比可达2:1
  • 基于DCT的有损顺序编码模式:压缩比可达10:1以上
  • 基于DCT的递增编码模式
  • 基于DCT的分层编码模式

基本的JPEG编码原理可以用图如下的流程图表示:
在这里插入图片描述
对于输入的图像,JPEG编码要经过以下的步骤:

1、零偏置

JPEG编码将图像分为8x8的块作为数据处理的最小单位,对于灰度级为 2 n 2^n 2n 的像素,通过减去 2 n − 1 2^{n-1} 2n1 ,将无符号数变成有符号数。以灰度级n=8的图像为例,将原来图像的灰度范围 [ 0 − 255 ] [0-255] [0255] 减去 128 128 128 后,范围变成了 [ − 128 − 127 ] [-128-127] [128127]
经过零偏置后,像素灰度的绝对值被控制在较小的范围内,便于后续的编码。

2、DCT变换

对零偏置后的图像进行DCT(Discrete Cosine Transform,离散余弦变换)变换,以进行能量集中和去相关,去除图像的空间冗余。变换后的图像能量集中在右上角。

DCT变换同样以8x8的块为单位,具体公式如下:
F ( u , v ) = 1 4 C ( u ) C ( v [ ∑ i = 0 7 ∑ j = 0 7 f ( x , y ) c o s ( 2 i + 1 ) u π 16 c o s ( 2 j + 1 ) u π 16 ] ) F(u,v)=\frac{1}{4}C(u)C\bigg(v \bigg[\sum_{i=0}^{7}\sum_{j=0}^{7}f(x,y)cos \frac{(2i+1)u\pi}{16}cos \frac{(2j+1)u\pi}{16} \bigg] \bigg) F(uv)=41C(u)C(v[i=07j=07f(x,y)cos16(2i+1)uπcos16(2j+1)uπ])

其中 C C C 为DCT变换矩阵:
C ( u ) , C ( v ) = 1 2 u , v = 0 C ( u ) , C ( v ) = 1 o t h e r w i s e C(u),C(v)=\frac{1}{2}\quad u,v=0\\C(u),C(v)=1\quad otherwise C(u),C(v)=21u,v=0C(u),C(v)=1otherwise
f ( x , y ) f(x,y) f(x,y)为原始图像。DCT变换是一种无损变换,在变换过程中没有精度损失。

3、量化

根据人眼的视觉特性,人眼对高频信息的不敏感,在JPEG中采用低频细量化,高频粗量化,以降低视觉冗余。同时由于人眼对色度信号的敏感度低于亮度信号,故对于亮度和色度信号有不同的量化系数 Q ( u , v ) Q(u,v) Q(u,v)

量化结果可表示为:
L ( u , v ) = [ F ( u , v ) Q ( u , v ) + 0.5 ] L(u,v)=\bigg[\frac{F(u,v)}{Q(u,v)}+0.5\bigg] L(u,v)=[Q(u,v)F(u,v)+0.5]
根据不同质量的要求,可以建立不同的量化系数表。

4、DC系数的差分编码

8×8图像块经过DCT变换之后得到的DC直流系数有两个特点

  • 系数的数值比较大

  • 相邻8×8图像块的DC系数值变化不大:冗余

根据这个特点,JPEG算法使用了差分脉冲调制编码 (DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF 进行编码:
D I F F = D C k − D C k − 1 DIFF=DC_k-DC_{k-1} DIFF=DCkDCk1
采用熵编码(huffman编码)的方式,亮度信号与色度信号的DC系数采用不同的Huffman编码表

5、AC系数的Zig-zag扫描与游程编码

由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形(Zig-zag)扫描按频率的高低顺序读出,可以出现很多连零的机会,以便使用RLE游程编码。

在这里插入图片描述

尤其在最后, 如果都是零,给出EOB (End of Block)即可。

在JPEG中的游程编码规定为:(run,level)

  • 表示连续run个0,后面跟值为level的系数
  • 如: −3 0 −3 −2 −6 2 −4 1 −4 1 1 5 1 2 −1 1 −1 2 0 0 0 0 0 −1 −1 EOB
  • 表示为(0,-3) ; (1,-3) ; (0,-2) ; (0,-6) ; (0,2) ; (0,-4) ; (0,1) ;
    (0,-4) ; (0,1) (0,1) ; (0,5) ; (0,1); (0,2) ; (0,-1) ; (0,1) ; (0,-1)
    ; (0,2) ; (5,-1) ; (0,-1) ; EOB

编码:

  • Run: 最多15个,用4位表示Z

  • Level:类似DC ,分成16个类别,用4位表示表示类别号C

  • 对(Z, C)联合用Huffman编码

  • 对类内索引用定长码编码

同DC系数一样,亮度信号和色度信号的AC系数也有不同的huffman编码表

JPEG解码

JPEG解码为编码的逆过程,做简单的流程介绍:

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

JPEG文件解析

JPEG文件以segment为数据流的组织形式,定义了一系列的标记(Marker)放在每一个segment的开头。
Marker均以0xFF开始,后跟1字节的标记标识符和2字节的标记长度以及该标记所对应的payload。标记长度部分高位在前,低位在后,不包含该标记的头 两个字节 。
常见的Maker如下表所示:

Name Bytes Payload Comments
SOI (Start of Image) 0xFFD8 none 文件开头
SOF0 (Start of Image Baseline DCT) 0xFFC0 variable size Baseline DCT所用的开头
SOF2(Start of Image Progressive DCT) 0xFFC2 variable size Progressive DCT所用的开头
DHT(Define Huffman Table) 0xFFC4 variable size 指定Huffman表
DQT(Define Quantization Table) 0xFFDB variable size 指定量化表
DRI (Define Restart Interval) 0xFFDD 2 bytes RST中的marker
SOS(Start of Scan) 0xFFDA variable size 扫描的开头
RSTn(Restart) 0xFFDn variable size 插入r个块到DRImarker中
APPn (Application Specific) 0xFFEn variable size Exif JPEG使用APP1,JFIF JPEG使用APP0
COM (Comment) 0xFFFE variable size 注释
EOI(End of Image) 0xFFD9 none 文件结束

其中要注意的有:

  • 熵编码部分的数据在0xFF后由编码器插入0x00,解码器 解码时跳过此字节不予处理
  • SOI( Start Of Image)和EOI( End Of Image)标记没有 payload

JPEG解码器C++程序分析

程序整体框架

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 TRACE
  p_trace=fopen(TRACEFILE,"w");
  if (p_trace==NULL)
  {
	  printf("trace file open error!");
  }
#endif
  if (argc < 3)
    usage();

  current_argument = 1;
  while (1)
   {
     if (strcmp(argv[current_argument], "--benchmark")==0)
       benchmark_mode = 1;
     else
       break;
     current_argument++;
   }

  if (argc < current_argument+2)
    usage();

  input_filename = argv[current_argument];
  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");
  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;
}





在主程序中,通过benchmark的设置进行了对解码函数的选择,若参数设置了benchmark,则调用load_multiple_times函数,若未设置,则调用convert_one_image函数。同时通过参数设置进行了图像格式的选择,主程序中的默认格式为YUV420P。
本次实验中未设置参数benchmark。

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;
  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)
    exitmessage(tinyjpeg_get_errorstring(jdec));

  /* Get the size of the image */
  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);

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

  /* Only called this if the buffers were allocated by tinyjpeg_decode() */
  tinyjpeg_free(jdec);
  /* else called just free(jdec); */

  free(buf);
  return 0;
}

对一个JPEG文件进行解码,读取数据存放在buf中,调用的三个函数:
tinyjpeg_parse_header:获取marker信息,得到解码所需要的数据
tinyjpeg_get_size:得到数据长度信息
tinyjpeg_decode:对JPEG数据流进行解码,结果存放在结构体变量jdec中
再根据output_format的格式选择进行相应的数据输出

tinyjpeg_decode

int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
  unsigned int x, y, xstride_by_mcu, ystride_by_mcu;
  unsigned int bytes_per_blocklines[3], bytes_per_mcu[3];
  decode_MCU_fct decode_MCU;
  const decode_MCU_fct *decode_mcu_table;
  const convert_colorspace_fct *colorspace_array_conv;
  convert_colorspace_fct convert_to_pixfmt;

  if (setjmp(priv->jump_state))
    return -1;

  /* To keep gcc happy initialize some array */
  bytes_per_mcu[1] = 0;
  bytes_per_mcu[2] = 0;
  bytes_per_blocklines[1] = 0;
  bytes_per_blocklines[2] = 0;

  decode_mcu_table = decode_mcu_3comp_table;
  switch (pixfmt) {
     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;

     case TINYJPEG_FMT_RGB24:
       colorspace_array_conv = convert_colorspace_rgb24;
       if (priv->components[0] == NULL)
	 priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);
       bytes_per_blocklines[0] = priv->width * 3;
       bytes_per_mcu[0] = 3*8;
       break;

     case TINYJPEG_FMT_BGR24:
       colorspace_array_conv = convert_colorspace_bgr24;
       if (priv->components[0] == NULL)
	 priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);
       bytes_per_blocklines[0] = priv->width * 3;
       bytes_per_mcu[0] = 3*8;
       break;

     case TINYJPEG_FMT_GREY:
       decode_mcu_table = decode_mcu_1comp_table;
       colorspace_array_conv = convert_colorspace_grey;
       if (priv->components[0] == NULL)
	 priv->components[0] = (uint8_t *)malloc(priv->width * priv->height);
       bytes_per_blocklines[0] = priv->width;
       bytes_per_mcu[0] = 8;
       break;

     default:
#if TRACE
		 fprintf(p_trace,"Bad pixel format\n");
		 fflush(p_trace);
#endif
       return -1;
  }

  xstride_by_mcu = ystride_by_mcu = 8;
  if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {
     decode_MCU = decode_mcu_table[0];
     convert_to_pixfmt = colorspace_array_conv[0];
#if TRACE
     fprintf(p_trace,"Use decode 1x1 sampling\n");
	 fflush(p_trace);
#endif
  } else if (priv->component_infos[cY].Hfactor == 1) {
     decode_MCU = decode_mcu_table[1];
     convert_to_pixfmt = colorspace_array_conv[1];
     ystride_by_mcu = 16;
#if TRACE
     fprintf(p_trace,"Use decode 1x2 sampling (not supported)\n");
	 fflush(p_trace);
#endif
  } else if (priv->component_infos[cY].Vfactor == 2) {
     decode_MCU = decode_mcu_table[3];
     convert_to_pixfmt = colorspace_array_conv[3];
     xstride_by_mcu = 16;
     ystride_by_mcu = 16;
#if TRACE 
	 fprintf(p_trace,"Use decode 2x2 sampling\n");
	 fflush(p_trace);
#endif
  } else {
     decode_MCU = decode_mcu_table[2];
     convert_to_pixfmt = colorspace_array_conv[2];
     xstride_by_mcu = 16;
#if TRACE
     fprintf(p_trace,"Use decode 2x1 sampling\n");
	 fflush(p_trace);
#endif
  }

  resync(priv);

  /* Don't forget to that block can be either 8 or 16 lines */
  bytes_per_blocklines[0] *= ystride_by_mcu;
  bytes_per_blocklines[1] *= ystride_by_mcu;
  bytes_per_blocklines[2] *= ystride_by_mcu;

  bytes_per_mcu[0] *= xstride_by_mcu/8;
  bytes_per_mcu[1] *= xstride_by_mcu/8;
  bytes_per_mcu[2] *= xstride_by_mcu/8;

  /* Just the decode the image by macroblock (size is 8x8, 8x16, or 16x16) */
  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);
	convert_to_pixfmt(priv);
	priv->plane[0] += bytes_per_mcu[0];
	priv->plane[1] += bytes_per_mcu[1];
	priv->plane[2] += bytes_per_mcu[2];
	if (priv->restarts_to_go>0)
	 {
	   priv->restarts_to_go--;
	   if (priv->restarts_to_go == 0)
	    {
	      priv->stream -= (priv->nbits_in_reservoir/8);
	      resync(priv);
	      if (find_next_rst_marker(priv) < 0)
		return -1;
	    }
	 }
      }
   }
#if TRACE
  fprintf(p_trace,"Input file size: %d\n", priv->stream_length+2);
  fprintf(p_trace,"Input bytes actually read: %d\n", priv->stream - priv->stream_begin + 2);
  fflush(p_trace);
#endif

  return 0;
}

得到头部信息后可以用此函数对JPEG数据流进行解码

程序的三个结构体设计

1、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];
};

建立Huffman码表,其中lookup为快速查找表,在程序进行huffman解码时能够提高解码效率

2、component

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

结构体存放8x8宏块的信息,定义了水平采样因子,垂直采样因子,量化表指针,AC系数、DC系数的Huffman码表的指针,前一个DC系数等。
与此同时,结构体中嵌套了huffman_table结构体。

3、jdec_private

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

};

JPEG数据流结构体,存放了关于JPEG图像的宽、高,量化表,Huffman编码表,数据流起始、结束指针等通用信息。
与此同时,结构体中嵌套了component结构体与huffman_table结构体。

实验结果

1、调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。

程序设置输出Y\U\V三个分量,要输出完整的YUV文件,需要在write_yuv函数中添加代码:

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

同时在命令行中添加参数:

  • test.jpg yuv420p test

运行程序得到yuv文件,用yuvviewer打开:
在这里插入图片描述
2、Trace调试
程序中的许多地方都加入了trace模块:

#if TRACE
 /*、、、*/
#endif

trace的目的是记录程序运行中中间变量的变化和解析Huffman码表、marker等信息,以便观察解码过程中某一环节是否有错误产生。

打开trace的方法是在tinyjpeg.h文件中:

#define TRACE 1//add by nxn
#define TRACEFILE "trace_jpeg.txt"//add by nxn

将#define TRACE 1改为#define TRACE 0即可关闭trace。在#define TRACEFILE "trace_jpeg.txt"中可以修改输出的文件名。

在main函数中,trace记录输出为txt文件:
在这里插入图片描述
3.以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。
为了实现量化矩阵和HUFFMAN码表的输出,需要在程序中添加输出文件的相关代码:

//tinyjpeg.h中添加
FILE *Qfile;
FILE *Hfile;
//main函数中添加
Qfile = fopen("q_matrix.txt","w");
Hfile = fopen("h_table.txt", "w");
fclose(Hfile);
fclose(Qfile);
//parse_DQT函数的循环中添加,输出量化表的id
 fprintf(Qfile, "Quantization_table [%d]:\n", qi);
 fflush(Qfile);
 // parse_DHT函数中添加,输出Huffman表的类型与id
 fprintf(Hfile, "Huffman table %s[%d] length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count)
fflush(Hfile);
//build_huffman_table函数中添加,输出Huffman码表
fprintf(Hfile, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
fflush(Hfile);
//build_quantization_table函数的循环中添加,输出量化表
 for (i=0; i<8; i++) {
     for (j=0; j<8; j++) {
		 fprintf(Qfile, "%d\t", ref_table[*zz]);
		 fflush(Qfile);
		 if (j == 7)
		 {
			 fprintf(Qfile, "\n");
			 fflush(Qfile);
		 }
		
       *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
     }
   }

重新调试程序后,得到txt文件:
在这里插入图片描述
在这里插入图片描述

4. 输出DC图像并统计其概率分布;输出某一个AC值图像并统计其概率分布。
在得到的DCT数组中存储了图像DCT变换后的数据,将这些数据输出即可,其中DCT[0]为图像的直流分量,其余为交流分量,实验中取DCT[1]作为输出的AC分量值。
对于8x8的宏块,取每一个宏块DCT变换后左上角的第一个值为DC分量,输出图像的大小为128x128(原图像为1024x1024)。
为了便于观察,将DC分量的值(范围-512~512)调整到 0~255。AC分量的值较小,+128处理。
在程序中添加输出AC,DC图像的代码:

//在main函数中添加:
FILE *DCfile;
FILE *ACfile;
DCfile = fopen("dc.yuv","w");
ACfile = fopen("ac.yuv", "w");
........
fclose(DCfile);
fclose(ACfile);
//在tinyjpeg_decode函数中添加:
unsigned char *DCbuf, *ACbuf; 

unsigned char *uvbuf = 128;
int count=0;
....
decode_MCU(priv);

	DCbuf= (unsigned char)((priv->component_infos->DCT[0] + 512.0) / 4 + 0.5);  
	ACbuf= (unsigned char)(priv->component_infos->DCT[1] + 128);   
	fwrite(&DCbuf, 1, 1,DCfile);
	fwrite(&ACbuf, 1, 1,ACfile);
	count++;

.....
 for (int j = 0; j < count *0.25 * 2; j++)
  {
	  fwrite(&uvbuf, sizeof(unsigned char), 1, DCfile);
	  fwrite(&uvbuf, sizeof(unsigned char), 1, ACfile);
  }

  return 0;

输出图像如下:
在这里插入图片描述
最后对图像进行概率分布统计,调用如下的程序:

#include<stdio.h>
#include<math.h>
#include <iostream>
#pragma warning(disable:4996)

int main()
{
	const int width = 128; const int height = 128; int y = 0;
	unsigned char dcybuf[width*height] = { 0 };
	unsigned char acybuf[width*height] = { 0 };
	
	FILE *dcfp,*acfp;
	dcfp = fopen("dc.yuv", "rb");
	if (dcfp == NULL)
		printf("Open file error!\n");
	else
		printf("File opened!\n");
	acfp = fopen("ac.yuv", "rb");
	if (dcfp == NULL)
		printf("Open file error!\n");
	else
		printf("File opened!\n");

	fread(dcybuf, sizeof(unsigned char), width*height, dcfp);
	fread(acybuf, sizeof(unsigned char), width*height, acfp);


	int dc[256] = { 0 }; int ac[256] = { 0 }; 
	for (int i = 0; i < 256; i++)
	{
		for (int j = 0; j < width*height; j++)
		{
			if (i == dcybuf[j])
				dc[i]++;
			if (i == acybuf[j])
				ac[i]++;
		}
	}
	double freqdc[256] = { 0 };
	double freqac[256] = { 0 };
	for (int i = 0; i < 256; i++)
	{
		freqdc[i] = double(dc[i]) / (width*height);
		freqac[i] = double(ac[i]) / (width*height);
	}
	
	FILE *DCF; FILE *ACF;
	if ((DCF = fopen("DC.txt", "w")) == NULL)
		printf("fail\n");
	else
		printf("success\n");
	if ((ACF = fopen("AC.txt", "w")) == NULL)
		printf("fail\n");
	else
		printf("success\n");
	char s[] = "symbol\frequency\n";
	fprintf(DCF, s); fprintf(ACF, s);
	for (int i = 0; i < 256; i++)
	{
		fprintf(DCF, "%d\t%f\n", i, freqdc[i]);
		fprintf(ACF, "%d\t%f\n", i, freqac[i]);
	}
	
	fclose(dcfp);
	fclose(acfp);

	fclose(DCF);
	fclose(ACF);
	return 0;
}

得到的概率分布图如下:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JPEG编码原理与解码分析 的相关文章

  • Asp.net core默认路由

    简化版Startup code public void ConfigureServices IServiceCollection services services AddMvc public void Configure IApplica
  • 调用许多网络服务的最佳方式?

    我有 30 家子公司 每家都实施了他们的 Web 服务 使用不同的技术 我需要实现一个Web服务来聚合它们 例如 所有子公司的Web服务都有一个名为的Web方法GetUserPoint int nationalCode 我需要实现我的网络服
  • 从实体获取单列

    如何从查询中获取单个列而不是整个对象 我可以这样做来获取整个对象 但我想要的只是名称 IList
  • 从 Invoke 方法获取 RETURN

    我正在尝试从另一个线程上的列表框项目中读取值 我尝试创建一种新方法来运行调用命令 我可以设法将命令发送到列表框 例如通过调用方法添加 但我似乎无法得到响应 我似乎无法获取该项目的值 我尝试了几种方法 一旦我将它从空变为字符串 事情就开始变得
  • 通过 SOAP 的 Gmt php 或 UTC C# 等效项

    is C DateTime UtcNow和 PHPdate c 是等价的 我怀疑 因为当我肥皂时 我得到了 C
  • 使用 Xamarin.Forms 和 Zxing 生成 QR 码

    我在网上看到了很多关于这个的内容 旧帖子 但似乎没有什么对我有用 我正在尝试从字符串中生成二维码并将其显示在应用程序中 这就是我一开始的情况 qrCode new ZXingBarcodeImageView BarcodeFormat Ba
  • .pdbs 会减慢发布应用程序的速度吗?

    如果 dll 中包含 pdb 程序调试 文件 则行号将出现在引发的任何异常的堆栈跟踪中 这会影响应用程序的性能吗 这个问题与发布与调试 即优化 无关 这是关于拥有 pdb 文件的性能影响 每次抛出异常时都会读取 pdb 文件吗 加载程序集时
  • 如何使用 C# 以编程方式编辑 Power BI Desktop 文档参数或数据源?

    我有一个在 Power BI Desktop 中内置的报告模板 并保存为 pbix 或 pbit 文件 该模板使用DirectQuery SQL数据库作为数据源 而服务器地址和数据库名称被提取到参数中 还有一个参数包含一个ReportId
  • 如何调整 Windows 窗体以适应任何屏幕分辨率?

    我知道这是重复的问题 但我检查了所有其他相关问题 他们的答案没有帮助 结果仍然与屏幕截图 2 中所示相同 我是 C Windows 窗体新手 如截图1所示 我有Form1有一些控件 每组控件都放在一个面板中 我在 PC1 中设计了应用程序
  • 判断串口是普通COM还是SPP

    我正在寻找一种方法来确定 COM 是标准 COM 还是 SPP COM 也称为 COM 设备的电缆替换蓝牙适配器 我有一个可以在 USB COM gt USB 和蓝牙下工作的设备 并且蓝牙接口可以与 SPP 一起工作 我目前正在使用Syst
  • 如何增加ofstream的缓冲区大小

    我想增加 C 程序的缓冲区大小 以便它不会过于频繁地写入 默认缓冲区是 8192 字节 我尝试使用 pubsetbuf 将其增加到 200K 原始代码 ofstream fq fastq1 cstr ios out fastq1 is a
  • 从点云检测平面集

    我有一组点云 我想测试3D房间中是否有角落 所以我想讨论一下我的方法 以及在速度方面是否有更好的方法 因为我想在手机上测试它 我将尝试使用霍夫变换来检测线 然后我将尝试查看是否有三条线相交 并且它们也形成了两个相交的平面 如果点云数据来自深
  • 如何使用 C# 查询远程 MS ACCESS .mdb 数据库

    我正在尝试使用 C 查询 mote MS ACCESS 数据库 mdb 文件 将文件复制到本地计算机时可以成功查询它 我只想远程放置文件 所以我的客户端程序不包含原始数据 static string m path http www xyz
  • 如何对STL向量进行排序?

    我想排序一个vector vector
  • WinForms - 加载表单时如何使用 PaintEventArgs 运行函数?

    我试图理解图形 在 Graphics FromImage 文档中 它有这样的示例 private void FromImageImage PaintEventArgs e Create image Image imageFile Image
  • 使用 Unity 在 C# 中发送 http 请求

    如何使用 Unity 在 C 中发送 HTTP GET 和 POST 请求 我想要的是 在post请求中发送json数据 我使用Unity序列化器 所以不需要 新的 我只想在发布数据中传递一个字符串并且能够 将 ContentType 设置
  • 如何测试某些代码在 C++ 中无法编译? [复制]

    这个问题在这里已经有答案了 可能的重复 单元测试编译时错误 https stackoverflow com questions 605915 unit test compile time error 我想知道是否可以编写一种单元测试来验证给
  • 使用 IdentityDbContext 和 Code First 自动迁移表位置和架构的实体框架?

    我正在尝试使用 IdentityDbContext 类设置自动迁移更新 并将更改传播到整个数据库的实际 DbContext 在进入代码之前 在使用自动迁移实现 IdentityDbContext 时 我收到此错误 影响迁移历史系统表位置的自
  • 运行 xunit 测试时无法将输出打印到控制台窗口

    public class test2InAnotherProject private readonly ITestOutputHelper output public test2InAnotherProject ITestOutputHel
  • 是否可以使用 Dapper 流式传输大型 SQL Server 数据库结果集?

    我需要从数据库返回大约 500K 行 请不要问为什么 然后 我需要将这些结果保存为 XML 更紧急 并将该文件通过 ftp 传输到某个神奇的地方 我还需要转换结果集中的每一行 现在 这就是我正在做的事情 TOP 100结果 使用 Dappe

随机推荐

  • unity后台加密时间锁

    前言 在做一些项目的时候 有些不良甲方在给完项目后会有不给尾款的情况 之前都是加一些水印啥的 感觉不是很方便 第一不美观 第二如果甲方给完尾款后还得重新打包去水印 然后又做过一个本地的时间锁 等到时间 程序直接退出 但是感觉还是不方便 有时
  • PowerVR开发工具和SDK 2020 Release 1发布啦!

    期待已久的PowerVR开发工具和SDK 2020年的第一个发行版本终于面市了 尽管面临来自世界各地的挑战 DevTech团队一直在努力工作 为整个市场提供最好的图形开发工具 任何想体验我们开发工具最新版本的用户请直接点击下方链接 下载Po
  • C语言打印数据的二进制格式-原理解析与编程实现

    问题引出 C语言中 在需要用到16进制数据的时候 可以通过printf函数的 x格式打印数据的16进制形式 在某些位标记 位操作的场合 需要用到2进制格式的数据 但printf函数不能输出2进制格式 虽然可以通过使用itoa或 itoa的方
  • 集合框架之Connection(马士兵教育视频教程总结笔记)

    when to use 当不知道程序运行时会需要多少对象 或者需要以更复杂的形式来储存对象时 可以使用java集合框架 e g 假定学员数 接口 4个 Collection List Set Map 1 Collection单值 List
  • Python浪漫七夕:可爱的卡通图案合集分享

    一个浪漫的日子 七夕 每个学科都有属于自己的浪漫 理工科的程序员们被大众普遍认为是直男直女 但其实我们才是最浪漫的 还不抓住机会展现一把 用专属于程序员的烂漫锁住爱 下面是我搜集到的一些Python绘制可爱的卡通图代码 我写成合集分享给大家
  • Java----AWT组件开发和Swing界面编程(一)

    一 AWT组件开发 1 AWT入门 AWT是抽象窗口工具箱的缩写 它为编写图形用户界面提供了用户接口 通过这个接口就可以继承很多方法 省去了很多工作 AWT还能使应用程序更好地同用户进行交互 AWT中的容器是一种特殊的组件 他可以包含其他组
  • STM32CubeMX HAL库串口+DMA+IDLE空闲中断不定长度数据接收和发送

    本文开发环境 MCU型号 STM32F103ZET6 IDE环境 MDK 5 29 代码生成工具 STM32CubeMx 5 3 0 HAL库版本 STM32Cube FW F1 V1 8 0 STM32Cube MCU Package f
  • 【转】Linux---centos安装配置并挂载NFS

    转自 http blog csdn net loyachen article details 51010688 系统环境 CentOS release 6 7 Final 服务端配置 1 安装nfs utils和rpcbind yum in
  • oracle批量修改某个字段的值的一部分

    有个字段是id aaa10 aaa12 aaa13 要改成 kkk10 kkk12 kkk13 UPDATE tablename SET id REPLACE id aaa kkk
  • Java与C#一些区别(学习笔记)

    一 基础语法 1 Console表示控制台 2 输入 Console ReadLine 格式 string name name Console ReadLine 3 输出 Console WriteLine 这个自带换行 Console W
  • Vscode中Python无法将pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称

    在Python需要pip下载插件时报错 是因为没有把Python安装路径下的Scripts添加到系统的path路径中 设置完之后重新启动Vscode就可以使用pip了
  • [需求管理-4]:需求分析全过程:需求分析+资源评估+项目计划

    前言 需求收集后 需要经过漫长的需求分析和所需要评估过程 才能正式在某个软件版本中实现需求 在软件开发人员通过编程实现需求前 中间经过了多种角色的辛苦劳动 最终才会生成需要规格说明书 需求规格说明书是逐步由粗到细的分解过程 一个需求 要进入
  • Postgresql vacuum 和 vacuum full

    转载自 https www modb pro db 63663 dead tuples tuple 元组 也就是一行数据 首先 简要解释什么是 死元组 和 膨胀 当您在PostgreSQL中执行DELETE时 行不会立即从数据文件中删除 而
  • 11.面向对象概述,类的创建,对象的创建

    一 面向对象程序设计概述 1 知识点面向对象程序设计的目的 1 从程序设计的角度来看 事物的属性就可以用变量来表示 行为则可以用方法来反映 2 客观世界中事物的属性和行为可以进行传递 当某一个事物得到另一个事物传给它的属性和行为 我们就说该
  • 证书 vivo_vivo官宣首款商用5G手机iQOO Pro,8月上市

    环球网科技综合报道 7月30日 vivo子品牌iQOO通过官微宣布支持5G网络的iQOO Pro将于8月发布 即vivo首款商用5G手机将是iQOO Pro iQOO自发布之初便备受关注 首款产品以游戏手机的定位切入市场对当时首批商用高通骁
  • 优雅,在SpringBoot项目中一键轻松接入ChartGPT

    最近体验了火爆全网的 ChartGPT 深刻体会了其强大的能力 这让我们程序猿对AI的未来突然有了广大的畅想空间 我也在网上看到不少大牛通过 ChartGPT 来获取收益 就寻思着能否自己接入 ChartGPT 的 API 来做些什么事情
  • Linux宝塔面板命令大全,快速学会

    cd www server panel python tools py panel 123456 查看宝塔日志 cat tmp panelBoot pl 查看软件安装日志 cat tmp panelExec log 站点配置文件位置 www
  • AppsFlyer 研究(三)OneLink平台归因、跳转、深度链接

    一 OneLink 简介 OneLink是AppsFlyer的对平台归因 跳转和深度链接的解决方案 OneLink在设备点击时检测到设备类型 并将用户重定向到匹配的目的地 例如Google Play商店 iOS应用商店 第三方应用商店 或者
  • "我为区块链赋能实体经济代言"第二批代言人:复旦大学张江研究院教授陈文君

    我为区块链赋能实体经济代言 推出以来 颇受行业关注 为进一步规范行业发展 落实区块链赋能实体经济的目标 为行业发展 正本清源而发声 新一轮的 我为区块链赋能实体经济代言 于9月3日重磅启动 我为区块链赋能实体经济代言100人第二批 第3位代
  • JPEG编码原理与解码分析

    JPEG编码原理 JPEG Joint Photographic Experts Group 是JPEG标准的产物 该标准由国际标准化组织 ISO 制订 是面向连续色调静止图像的一种压缩标准 JPEG格式是最常用的图像文件格式 后缀名为 j