JPEG(Joint Photographic Experts Group)是联合图像专家小组的英文缩写。它由国际电话与电报咨询委员会CCITT(The International Telegraph and Telephone Consultative Committee)与国际标准化组织ISO于1986年联合成立的一个小组,负责制定静态数字图像的编码标准

在记录计算机图像时,最常见的是采用RGB(红、绿,蓝)颜色分量来保存颜色信息,例如非压缩的24位的BMP图像就采用RGB空间来保存图像。一个像素24位,每8位保存一种颜色强度(0-255),例如红色保存为 0xFF0000

YUV是被欧洲电视系统所采用的一种颜色编码方法,我国广播电视也普遍采用这类方法。其中“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V”表示的则是色度(Chrominance或Chroma)。彩色电视采用YUV空间正是为了用亮度信号Y解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号。

YUV与RGB相互转换的公式如下(RGB取值均为0-255):

Y = 0.299R + 0.587G + 0.114B U = -0.147R - 0.289G + 0.436B V = 0.615R - 0.515G - 0.100B R = Y + 1.14V G = Y - 0.39U - 0.58V B = Y + 2.03U

RGB转YCbCr:因为人眼对亮度比较敏感,而对于色度不那么敏感,所以,我们就先将RGB的数据转换到YCbCr色彩空间,便于下面的处理。 降采样:转到YCbCr色彩空间后,就可以将 Cb 和 Cr 这两个通道进行降采样,这里一般是将 22 个像素变为 11 个像素,虽然分辨率下降到了四分之一,但对于人眼来说差别是不大的。(这一步是有损的) 分块:顾名思义,将图像分为若干个 88 的小块,方便下面的处理。 DCT:这一步的目的和RGB转YCbCr有一些相似之处,都是将人眼较为敏感和不敏感的部分进行分离,然后就可以对减少人眼不敏感的部分的信息量。在这一步中,DCT可以将图像的低频(人眼敏感)和高频(人眼不敏感)部分进行分离。这样得到的结果是每个 88 小块得到 88 的系数矩阵。 量化:将DCT后得到的每个系数都除以量化矩阵中对应的值,然后进行取整。通常来说频率较高的部分对应的量化参数比较大,这样一来就能够在较好地保留图像的低频部分并去除一些高频部分。这一步下来得到的矩阵中高频部分几乎全部变为0,这也为进一步的操作提供了便利。值得注意的是,JPEG中压缩率的调整是在这一步中,量化参数越大,压缩后的大小就会越小,但信息的损失也就越多,图片的失真也会更严重。(这一步是有损的) *Huffman编码:准确地说是Huffman编码和RLE,将上一步得到的矩阵进行进一步地压缩(这一步是无损的)。量化后得到的矩阵左上角的那个数比其他数来得大得多,所以我们将它单独拿出来进行编码,称之为直流分量(DC),将剩下的称之为交流系数(AC)

在C语言中没有C++的异常处理机制,但是提供了setjmp和longjmp机制来实现类似的功能,如果你对这个机制不熟悉的话,请查阅C语言手册

  1. /* We set up the normal JPEG error routines, then override error_exit. */
  2. cinfo.err = jpeg_std_error(&jerr.pub);
  3. jerr.pub.error_exit = my_error_exit;
  4. /* Establish the setjmp return context for my_error_exit to use. */
  5. if (setjmp(jerr.setjmp_buffer)) {
  6. /* If we get here, the JPEG code has signaled an error.
  7. * We need to clean up the JPEG object, close the input file, and return.
  8. */
  9. jpeg_destroy_decompress(&cinfo);
  10. fclose(infile);
  11. return 0;
  12. }

向libjpeg注册了一个my_error_exit回调函数,当发生错误的时候,该回调函数将会被调用。然后我们调用 setjmp函数,设置一个返回点。这样我们在my_error_exit回调函数处理完错误信息之后,就可以调用longjmp函数返回到这里,在这个 返回点进行资源的释放(非常重要,否则将会内存泄漏)

setjmp和longjmp(值得仔细研究一番)

  • setjmp属于C函数库,作用是分别承担非局部标号和goto作用。
  • 与刺激的abort()和exit()相比,goto语句看起来是处理异常的更可行方案。不幸的是,goto是本地的:它只能跳到所在函数内部的标号上,而不能将控制权转移到所在程序的任意地点(当然,除非你的所有代码都在main体中)
  • 为了解决这个限制,C函数库提供了setjmp()和longjmp()函数,它们分别承担非局部标号和goto作用。头文件申明了这些函数及同时所需的jmp_buf数据类型
  • setjmp和longjmp其实就是goto的升级版,goto只能在同一个函数中跳转,但是setjmp和longjmp可以实现在不同的函数之间跳转。

设置解码参数

很多情况下,这步非常重要。比如设置输出格式,设置scale(缩放)等等功能都是在这一步设置。参数设置通过修改上步得到cinfo的值来实现

  • 一些常用字段

out_color_space:输出的颜色格式,libjpeg定义如下:

  1. /* Known color spaces. */
  2. typedef enum {
  3. JCS_UNKNOWN, /* error/unspecified */
  4. JCS_GRAYSCALE, /* monochrome */
  5. JCS_RGB, /* red/green/blue */
  6. JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */
  7. JCS_CMYK, /* C/M/Y/K */
  8. JCS_YCCK, /* Y/Cb/Cr/K */
  9. #ifdef ANDROID_RGB
  10. JCS_RGBA_8888, /* red/green/blue/alpha */
  11. JCS_RGB_565 /* red/green/blue in 565 format */
  12. #endif
  13. } J_COLOR_SPACE;

scale_num,scale_denom:因为实际的显示设备千变万化,我们可能需要根据实际情况对输出数据进行一些缩放才能够显示。libjpeg支持对输出数据进行缩放(scale),这个变量就是用来设置缩放的参数。目前libjpeg支持1/2,1/4,1/8三种缩放(实际上有些缩放是为实现的,程序中应该进行判断是否真的实现了缩放大小)。
mem:可以指定内存管理相关的内容,比如分配和释放内存,指定libjpeg 可以使用的最大内存。请参考jmemxxxx.c文件中的DEFAULT_MAX_MEM,了解不同平台的默认最大内存值。通过修改mem->pub.max_memory_to_use 的值,库的使用者可以自定义libjpeg可以使用的最大内存值。

libjpeg对于baseline的jpeg数据解码比较好,但是解码progressive的jpeg数据的时候,对内存的需求比较大