【实验5】JPEG原理分析及JPEG解码器的调试


一、实验目的

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

二、实验内容

1.JPEG编解码原理

(1)编码原理

在这里插入图片描述

  • 将输入从RGB彩色空间转到YUV。
  • Level offset:零偏置,对于灰度级是 2 n 2^n 2n的像素,通过减去 2 n − 1 2^n-1 2n1,将无符号的整数值变成有符号数。对于n=8,即将0~ 255的灰度值,通过减去128,转换为值域在-128~127之间的值,使像素的绝对值出现3位10进制的概率大大减少。
  • 将输入图像分成88像素的块来处理,不足88的,取边缘像素补齐,对每个块做DCT变换,直流系数在每个块的左上角,右下角的分量频率高。
  • DCT变换后DC系数值较大,且相邻块变化不大,利用这个特性对其进行DPCM,对相邻块的DC差值huffman编码。
  • 对AC系数做之字形扫描,即按频率高低排列,再游程编码。

(2) 解码原理

   解码是编码的逆过程。

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

2.JPEG文件格式

JPEG在文件中以Segment的形式组织,它具有以下特点:

  • 均以 0xFF 开始,后跟 1 byte 的 Marker 和 2 byte 的 Segment length(包含表示Length 本身所占用的 2 byte,不含“0xFF” + “Marker” 所占用的 2 byte);
  • 采用 Motorola 序(相对于 Intel 序),即保存时高位在前,低位在后;
  • Data 部分中,0xFF 后若为 0x00,则跳过此字节不予处理;

JPEG 的 Segment Marker
在这里插入图片描述

三、实验步骤与结果

实验输入图像素材为 test.jpg (1024*1024)

1.调试JPEG解码器程序

(1)将输入的JPG文件进行解码,将输出文件保存为YUV文件。

源代码中没有输出为YUV文件的代码,只有输出为Y,U,V分量三个文件的代码。

  • 对write_yuv( )进行如下修改
static void write_yuv(const char *filename, int width, int height, unsigned char **components)
{
  FILE *F;
  char temp[1024];
  //输出为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);

  /**保存为 .Y .U .V 3个文件
  snprintf(temp, 1024, "%s.Y", filename);
  F = fopen(temp, "wb");
  fwrite(components[0], width, height, F);
  fclose(F);
  snprintf(temp, 1024, "%s.U", filename);
  F = fopen(temp, "wb");
  fwrite(components[1], width*height/4, 1, F);
  fclose(F);
  snprintf(temp, 1024, "%s.V", filename);
  F = fopen(temp, "wb");
  fwrite(components[2], width*height/4, 1, F);
  fclose(F); 
  */
}
  • 在命令行参数中进行设置
    从左到右以此为输入图像名称 输出格式 输出图像名称
    在这里插入图片描述
  • 运行结果
    在这里插入图片描述
    使用YUVviewer 查看图像
    在这里插入图片描述

(2)理解程序设计的整体框架

程序设定了两个解码模式,benchmark_mode和convert_one_image,选择哪个由变量benchmark确定。
只对convert_one_image进行分析。

大致流程如下

  1. 读取文件

  2. 解析 Segment Marker
    调用tinyjpeg_parse_header()解析JPEG流中的头部信息(marker),获取解码需要的信息。
    (1)解析 SOI(FFD8)
    (2)解析 APP0,检查APP0中的JFIF标识
    (3)解析 DQT,得到量化表长度、精度、序号等内容
      调用parse_DQT()找到了对应的需要创建的是哪张量化表,然后调用build_quantization_table()创建。
    (4) 解析 SOF0
      调用parse_SOF(),得到图像的宽度高度,颜色分量的比特数、每个颜色分量的 ID 、水平垂直采样因子及使用的量化表序号。
    (5) 解析 DHT
      调用parse_DHT()得到 Huffman 表的类型(AC/DC)、序号,并根据数据调用build_huffman_table()重建Huffman 表。
    (6)解析 SOS
      调用parse_SOS(),得到每个颜色分量的 DC 、AC 值所使用的Huffman 表序号。

  3. 解码
    调用tinyjpeg_decode()
    (1)依据每个分量的水平、垂直采样因子计算MCU(最小编码单元)的大小和输入图像的格式,并得到每个MCU中 8*8宏块的数量数量
    (2)对每个 MCU解码
      调用decode_MCU()进行解码,不同的MCU(不同的采样频率)有不同的算法。
      比如:decode_MCU_2x2_3planes对应的是4:2:0的采样格式;decode_MCU_2x1_3planes对应的是4:2:2的采样格式;decode_MCU_1x1_3planes对应的是4:4:4的采样格式。
      依照各分量水平、垂直采样因子对 MCU中每个分量宏块解码
      对每个宏块进行Huffman 解码,得到 DCT系数
      对每个宏块的DCT系数进行 IDCT,得到 Y、Cb 、Cr
      遇到 Segment Marker RST 时,清空之前的DCT系数
    (3)解析到 EOI,解码结束

  4. 将 Y、Cb 、Cr 转化为需要的色彩空间并保存

(3)理解三个结构体的设计目的

  • struct huffman_table
    存储Huffman码表,分为快速查找表和慢速查找表。
struct huffman_table
{
  /* 快速查找表 */
    short int lookup[HUFFMAN_HASH_SIZE];
  /* 编码大小:给出符号被编码的比特数*/
    unsigned char code_size[HUFFMAN_HASH_SIZE];
  /* 慢速查找表*/
    uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];
};
  • struct component
    存储当前有关解码的信息、DCT变换后的值;并指向量化表和DC、AC的Huffman表。
struct component 
{
    unsigned int Hfactor; //水平采样
    unsigned int Vfactor; //垂直采样
    float *Q_table;		/* 指向要使用的量化表的指针 */
    struct huffman_table *AC_table;
    struct huffman_table *DC_table;
    short int previous_DC;	/* 前一个块的DC系数,用于后续的DPCM编码*/
    short int DCT[64];		/* DCT系数矩阵 */
#if SANITY_CHECK
    unsigned int cid;
#endif
};
  • struct jdec_private
    JPEG码流结构体,用于存储JPEG图像的宽高、码流长度、数据流指针、量化表、Huffman码表等内容,且包含了struct huffman_table和struct component。
struct jdec_private
{
  /* Public variables */
  uint8_t *components[COMPONENTS];
  unsigned int width, height;	/*图像宽、高*/
  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];		/*量化表 */
  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 */

  /* 在IDCT之后用于存储每个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];

};

(4)理解在视音频编解码调试中TRACE的目的和含义

在程序的很多部分中都有如下的预处理命令块:

#if TRACE
...
#endif

TRACE在程序运行过程中可以记录下重要的信息,如解析DHT,解析DQT,采用的采样频率等等。

  • 在tinyjpeg.h定义了TRACE相关内容
#define TRACE 1  //开启TRACE 
#define  TRACEFILE "trace_jpeg.txt"//定义存储文件
  • 在main()中使用了TRACE
#if TRACE
  p_trace=fopen(TRACEFILE,"w");
  if (p_trace==NULL)
  {
	  printf("trace file open error!");
  }
#endif
......
#if TRACE
  fclose(p_trace);
#endif

  • 运行结果
    可以看到文件中记录了对marker解析,Huffman表等内容。
    在这里插入图片描述
  • 关闭TRACE

要关闭TRACE功能,在tinyjpeg.h中进行修改:

#define TRACE 0

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

代码中已经有存储变量,添加输出Huffman表和量化表的函数即可。

  • 在tinyjpeg.h中定义check_table相关内容
#define check_table 1
FILE* Q_table;//add by nxn
FILE* H_table;//add by nxn
#define QTABLEFILE "qtable_jpeg.txt"//量化表的文件名
#define HTABLEFILE "htable_jpeg.txt"//Huffman表的文件名
  • 在build_quantization_table()中添加输出量化表内容
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 check_table
	    fprintf(Q_table, "%d\t", ref_table[*zz]);//输出表的内容
        fflush(Q_table);
        if (j == 7)
        {
           fprintf(Q_table,"\n");
           fflush(Q_table);
        }
		#endif
		//=======================================================================
        *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
     }
   }

}
  • 在parse_DQT()的循环中添加输出量化表id内容
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
    int qi;
    float *table;
    const unsigned char *dqt_block_end;
#if TRACE
    fprintf(p_trace,"> DQT marker\n");
    fflush(p_trace);
#endif
    dqt_block_end = stream + be16_to_cpu(stream);
    stream += 2;	/* Skip length */

    while (stream < dqt_block_end)
    {
        qi = *stream++;
        #if SANITY_CHECK
            if (qi>>4)
                snprintf(error_string, sizeof(error_string),"16 bits quantization table is not supported\n");
            if (qi>4)
                snprintf(error_string, sizeof(error_string),"No more 4 quantization table is supported (got %d)\n", qi);
        #endif
        //修改代码如下===========================================================
            fprintf(Q_table, "Quantization_table [%d]:\n", qi);// 输出量化表id
            fflush(Q_table);
        #endif
        //=======================================================================
        table = priv->Q_tables[qi];
        build_quantization_table(table, stream);
        stream += 64;
    }
#if TRACE
    fprintf(p_trace,"< DQT marker\n");
    fflush(p_trace);
#endif
  return 0;
}
  • parse_DHT()中添加输出Huffman表的类型和id
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

    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 check_table
        fprintf(H_table, "Huffman table %s[%d] length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);//added 输出huffman表类型和id
        fflush(H_table);
    #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;
    }
#if TRACE
  fprintf(p_trace,"< DHT marker\n");
  fflush(p_trace);
#endif
  return 0;
}
  • 在build_huffman_table()中添加输出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 check_table
            fprintf(H_table, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);//added 输出Huffman表内容
            fflush(H_table);
        #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 */
        }

    }
}
  • 在main()中使用check_table
//在源代码中添加如下内容
#if check_table
    Q_table=fopen(QTABLEFILE,"w");
    if (Q_table==NULL)
    {
	    printf("Q_table file open error!");
    }
    H_table=fopen(HTABLEFILE,"w");
    if (H_table==NULL)
    {
  	  printf("H_table file open error!");
    }
#endif
...
...
#if check_table
    fclose(Q_table);
    fclose(H_table);
#endif

  • 运行结果
    输出Huffman表
    在这里插入图片描述
    在这里插入图片描述
    输出量化矩阵
    在这里插入图片描述
    在这里插入图片描述

3.输出DC/AC图像并统计其概率分布

  • 在tinyjpeg中定义ACDC_output相关内容
FILE* DCFILE;
FILE* ACFILE;
#define ACDC_output 1
#define DCoutputFILE "output_dc.yuv"
#define ACoutputFILE "output_ac.yuv"

  • 在main()中使用ACDC_output
#if ACDC_output
    DCFILE=fopen(DCoutputFILE,"w");
    if (DCFILE==NULL)
    {
	    printf("DC output file open error!");
    }
    ACFILE=fopen(ACoutputFILE,"w");
    if (ACFILE==NULL)
    {
	    printf("AC output file open error!");
    }
#endif
...
...
#if ACDC_output
    fclose(DCFILE);
    fclose(ACFILE);
#endif

  • 在tinyjpeg_decode()中添加输出DC、AC图像代码
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 ACDC_output
    unsigned char* DCbuf;
    unsigned char* ACbuf;
    unsigned char* uvbuf = 128;
    int count;
#endif
//-------------------------------------------------------------
    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);
            //修改代码如下----------------------------------------------
            #if ACDC_output
                DCbuf = (unsigned char)((priv->component_infos->DCT[0] + 512) / 4);//将DCT[0]的范围压缩到0-255
                fwrite(&DCbuf, 1, 1, DCFILE);
                ACbuf = (unsigned char)(priv->component_infos->DCT[1] + 128);
                fwrite(&ACbuf, 1, 1, ACFILE);
                count++;
            #endif
           //-----------------------------------------------------------
	        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
    //修改代码如下--------------------------------------------------
    #if ACDC_output
        for (int k = 0; k < count / 4 * 2; k++)
        {
            fwrite(&uvbuf, sizeof(unsigned char), 1, DCFILE);
            fwrite(&uvbuf, sizeof(unsigned char), 1, ACFILE);//added 4:2:0 output
        }
    #endif
    //------------------------------------------------------------
        
    return 0;
}
  • 运行结果
    (AC图像选取的是DCT[1]输出)
    在这里插入图片描述
  • 概率分布
    在这里插入图片描述
    在这里插入图片描述
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值