JPEG 原理分析及 JPEG 解码器的调试
来源:互联网 发布:虹云网络 编辑:程序博客网 时间:2024/06/18 14:47
实验原理
JPEG简介
JPEG是数字图像的有损压缩的常用方法,特别是对于由数码摄影产生的图像。JPEG(Joint Photographic Experts Group)是在国际标准化组织(ISO)领导之下制定静态图像压缩标准的委员会,第一套国际静态图像压缩标准ISO 10918-1(JPEG)就是该委员会制定的。由于JPEG优良的品质,使他在短短几年内获得了成功,被广泛应用于互联网和数码相机领域,网站上80%的图像都采用了JPEG压缩标准。
JPEG编解码流程
编码流程
彩色空间转换
首先,图像应该从RGB转换成不同的颜色空间,称为Y’CBCR(或非正式地,YCbCr)。它具有三个分量Y’,CB和CR:Y’分量表示像素的亮度,CB和CR分量表示色度
下采样
由于人眼对亮度比对色度更加敏感的视觉特性,因此通过降低Cb和Cr分量的空间分辨率(称为“下采样”)来进行编码。 4:4:4(无下采样),4:2:2(在水平方向上减少2倍),或(最常见)4:2: 0(在水平和垂直两个方向上减少2倍)。
分块
下采样后,每个通道必须分为8×8块。 取决于色度下采样,产生大小为8×8(4:4:4 - 无下采样),16×8(4:2:2)或最通常为16×16(4:4)的最小编码单元(MCU)2:0)。 在视频压缩中,MCU被称为宏块。本实验中采取的是8×8的宏块。
零偏置level offset
对于灰度级是
8×8的离散余弦变换
将每个8×8的块进行DCT变换。DCT的变换公式:
量化
1.采用中平型均匀量化器量化
2.步距是按照系数(所在的位置 、颜色分量 )来确定。
因为人眼对亮度信号比对色差信号更敏感,因此使用了两 种量化表:亮度量化值和色差量化值。
3. 根据人眼的视觉特性(对低频敏感,对高频不太敏感)对 低频分量采取较细的量化,对高频分量采取较粗的量化(如果原始图象中细节丰富,则去掉的数据较多,量化后的系数与 量化前差别;反之,细节少的原始图象在压缩时去掉的数据少些)
真正的量化表=缩放因子×基本量化表
质量因子≤ 50:缩放因子= 50 / 质量因子;
质量因子> 50:缩放因子 = 2 – 质量 因子/ 50
DC系数的差分编码
8×8图像块经过DCT变换之后得到的DC直流系数有两个特点 :1.系数的数值比较大2.相邻8×8图像块的DC系数值变化不大:冗余
根据这个特点,JPEG算法使用了差分脉冲调制编码 (DPCM)技术,对相邻图像块之间量化DC系数的差值 DIFF进行编码:
AC系数的Z字扫描
由于经DCT变换后 ,系数大多数集中 在左上角,即低频 分量区,因此采用Z 字形按频率的高低 顺序读出,可以出 现很多连零的机会 。可以使用游程编 码。尤其在最后, 如果都是零,给出 EOB (End of Block) 即可。
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) ,…
编码:
Run: 最多15个,用4位表示RRRR
Level:类似DC
分成16个类别,用4位表示SSSS表示类别号
类内索引 对(RRRR, SSSS)联合用Huffman编码
对类内索引用定长码编码
解码流程
解码Huffman数据
解码DC差值
重构量化后的系数
DCT逆变换
丢弃填充的行/列
反0偏置
对丢失的CbCr分量差值(下采样的逆过程)
YCbCr to RGB
JPEG文件格式
代码及分析
任务一
将输出文件保存为可供YUVViewer观看的YUV文件
enum tinyjpeg_fmt { TINYJPEG_FMT_GREY = 1, TINYJPEG_FMT_BGR24, TINYJPEG_FMT_RGB24, TINYJPEG_FMT_YUV420P, TINYJPEG_FMT_YUV,//add by ying};
当输入的指令为yuv时,那么将会输出一个yuv的图像。
if (strcmp(argv[current_argument+1],"yuv420p")==0) output_format = TINYJPEG_FMT_YUV420P; else if (strcmp(argv[current_argument + 1], "yuv") == 0) output_format = TINYJPEG_FMT_YUV;//add by ying 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];
case TINYJPEG_FMT_YUV时,不break,与case TINYJPEG_FMT_YUV420P执行相同的操作。
switch (pixfmt) { case TINYJPEG_FMT_YUV://add by ying 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;
当output_format为新定义的TINYJPEG_FMT_YUV时,则进入重新编写的write_yuv2函数。
/* 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_YUV: write_yuv2(outfilename, width, height, components); break;//add by ying case TINYJPEG_FMT_GREY: write_pgm(outfilename, width, height, components); break; }
自己定义的写入yuv文件的函数,即将y、u、v写入同一个文件中即可
static void write_yuv2(const char *filename, int width, int height, unsigned char **components){ FILE *F; char temp[1024]; snprintf(temp, 1024, "%s.yuv", filename); F = fopen(temp, "wb"); fwrite(components[0],1,width* height, F); fwrite(components[1], 1, width* height/4, F); fwrite(components[2], 1, width* height/4, F);}
任务二
以txt文件输出
所有的量化矩阵
所有的HUFFMAN码表
定义指向txt文件的指针
FILE *p_ytxt;//add by ying
#define YTXT 1 //add by ying#define YTXTFILE "ying.txt" //add by ying
打开txt文件
#if YTXT p_ytxt = fopen(YTXTFILE, "w"); if (p_ytxt == NULL) { printf("trace file open error!"); }#endif
输出量化矩阵
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 YTXT fprintf(p_ytxt, "%d ", ref_table[*zz]);//输出以zigzag矩阵所规定的顺序的量化矩阵。原因是ref_table指针中存着的着量化表值是以z字形扫描的次序来进行存储的,因此需要利用zigzag进行正确的排序 fflush(p_ytxt); #endif *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j]; } #if YTXT fprintf(p_ytxt, "\n"); fflush(p_ytxt); #endif }}
zigzag矩阵
static const unsigned char zigzag[64] = { 0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42, 3, 8, 12, 17, 25, 30, 41, 43, 9, 11, 18, 24, 31, 40, 44, 53, 10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60, 21, 34, 37, 47, 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63};
输出Huffman码表,包含长度、AC/DC信息
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#if YTXT fprintf(p_ytxt, "> DHT marker (length=%d)\n", length); fflush(p_ytxt);#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 YTXT fprintf(p_ytxt, "Huffman table %s[%d] length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count); fflush(p_ytxt);#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; }
输出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 YTXT fprintf(p_ytxt, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size); fflush(p_ytxt); #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 */ } }}
任务三
输出DC图像并经过huffman统计其概率分布
输出某一个AC值图像并统计其概率分布
输出DC和AC的yuv文件指针
FILE *dc_file;//add by yingqiFILE *ac_file;//add by yingqi
打开AC和DC文件
#if DC dc_file = fopen(DCFILE, "wb"); if (dc_file == NULL) { printf("trace file open error!"); }#endif //add by yingqi#if AC ac_file = fopen(ACFILE, "wb"); if (ac_file == NULL) { printf("trace file open error!"); }#endif
在JPEG结构体中定义指针,用于写入AC和DC图像
struct jdec_private{ int *acimage,*dcimage;//add by yingqi
在函数tinyjpeg_decode中给两个指针开辟空间
priv->dcimage = (int *)malloc(sizeof(int)*priv->width * priv->height/64);//add by yingqi priv->acimage = (int *)malloc(sizeof(int)*priv->width * priv->height / 64);// add by yingqi
用于将dc和ac系数写入指针指向的空间
static void DC_image(struct jdec_private *priv){ static int i=0; if ( i < (priv->height*priv->width / 64)) { priv->dcimage[i] = priv->component_infos[cY].DCT[0]; } i++;}//add by yingqistatic void AC_image(struct jdec_private *priv){ static int i = 0; if (i < (priv->height*priv->width / 64)) { priv->acimage[i] = priv->component_infos[cY].DCT[1]; } i++;}//add by yingqi
将其归一化后输出为yuv文件
int acmax, dcmax, acmin, dcmin; int tmp; acmax = priv->acimage[0]; acmin = priv->acimage[0]; dcmax = priv->dcimage[0]; dcmin = priv->dcimage[0]; for (i = 0; i < priv->width*priv->height / 64; i++) { if (priv->acimage[i] >=acmax) acmax = priv->acimage[i]; if (priv->dcimage[i] >= dcmax) dcmax = priv->dcimage[i]; if (priv->acimage[i] <= acmin) acmin = priv->acimage[i]; if (priv->dcimage[i] <=dcmin) dcmin = priv->dcimage[i]; } for (i = 0; i < priv->width*priv->height / 64; i++) { tmp = priv->acimage[i] - acmin; acfileout[i] = (unsigned char)(255 *(priv->acimage[i]-acmin)/ (acmax - acmin)); } fwrite(acfileout, 1, priv->width*priv->height / 64, ac_file); if (acfileout) free(acfileout); for (i = 0; i < priv->width*priv->height / 64; i++) { dcfileout[i] = (unsigned char)(255 *(priv->dcimage[i]-dcmin)/ (dcmax - dcmin)); } fwrite(dcfileout, 1, priv->width*priv->height / 64, dc_file); if (dcfileout) free(dcfileout);
实验结果
输出的可供yuvviewer观看的yuv图像
输出的量化矩阵和霍夫曼码表
输出的DC图像及AC图像
AC图像概率分布
DC图像概率分布
蚊子噪声
蚊子噪声是有压缩引起的产生与相邻像素之间的量化错误。场景内容不同,量化间隔的尺度会发生变化。具体表现为图像上出现微微发光的小亮块,像蚊子一样闪现在轮廓的周围。
具体细节:
- JPEG原理分析及JPEG解码器调试
- 【数据压缩】JPEG原理分析及JPEG解码器的调试
- 【实验六】JPEG原理分析及JPEG解码器的调试
- JPEG 原理分析及 JPEG 解码器的调试
- 实验五 JPEG原理分析及JPEG 解码器的调试
- 实验五 JPEG原理分析及JPEG解码器的调试
- 实验五 JPEG原理分析及JPEG解码器的调试
- 数据压缩实验五 JPEG原理分析JPEG解码器的调试
- 数据压缩 实验五 JPEG原理分析 JPEG解码器的调试
- 数据压缩 实验五 JPEG原理分析JPEG解码器的调试
- 数据压缩原理与应用 实验五 JPEG 原理分析及 JPEG 解码器的调试
- 数据压缩原理 实验五 JPEG原理分析及JPEG解码器的调试
- 数据压缩实验五 JPEG原理分析及JPEG解码器的调试
- 数据压缩实验五——JPEG原理分析及JPEG解码器的调试
- 苏泊尔耗的JPEG解码器[一]
- 苏泊尔耗的JPEG解码器[二]
- 苏泊尔耗的JPEG解码器[三]
- 苏泊尔耗的JPEG解码器[四]
- 句柄的理解
- 编程第二十二天
- Shader
- Json转移符问题
- OWASP top 10 漏洞的总结笔记
- JPEG 原理分析及 JPEG 解码器的调试
- Sublime Text 3 常用插件以及安装方法(转)
- windows集群 未安装虚拟机连接工具 若要查看更多数据 请查看
- 编程第二十三、二十四天
- ==与equals()的区别
- PHP使用七牛云存储之图片的上传、下载、303重定向教程,CI框架实例
- 【三】、整合SpringMVC(基本配置和thymeleaf模板选择)
- 毕设论文
- UE4 如何写插件