JPEG(Joint Photographic Experts Group 联合图像专家小组)是一种针对照片影像而广泛使用的有损压缩标准方法,面向连续色调静止图像的一种压缩标准。1992年发布了JPEG的标准而在1994年获得了ISO 10918-1的认定。和相同图象质量的其它常用文件格式(如GIF,TIFF,PCX)相比,JPEG是目前静态图象中压缩比最高的。JPEG格式是最常用的图像文件格式,后缀名为.jpg或.jpeg。
JFIF:JPEG File Interchange Format JPEG文件交换格式。JPEG本身只有描述如何将一个图像转换为字节的数据串流,但并没有说明这些字节如何在任何特定的储存媒体上存储展现。一个由独立JPEG小组(Independent JPEG Group)所建立的额外标准,称为JFIF(JPEG File Interchange Format,JPEG档案交换格式),详细说明如何从一个JPEG串流,产出一个适合于电脑储存和传输的文件。可以见 后文 文件格式介绍部分。
Baseline:基于DCT的连续模式(Sequential DCT-based mode of operation,基本线性格式,一次将图像由左到右、由上到下顺序处理(一次扫描处理)。Progressive:基于DCT的渐进模式(Progressive DCT-based mode of operation),渐进JPEG,当图像传输的时间较长时,将图像分数次处理,以从模糊到清晰的方式来传送和显示图像(多次扫描处理)。
libjpeg-turbo是libjpeg的一个复刻,它采用单指令流多数据流(SIMD)指令来加速JPEG编码和解码基础效率。许多项目现在使用libjpeg-turbo而不是libjpeg,包括流行的GNU/Linux发行版(Fedora、Debian、Mageia、OpenSUSE等)、Mozilla和Chrome。除了性能方面,部分项目也因它允许向后保留与旧的libjpeg v6b版本的ABI(application binary interface)兼容性而选择使用libjpeg-turbo。libjpeg v7、v8和v9已打破与早期版本的ABI兼容性。
MJPEG(Motion Joint Photographic Experts Group )是一种视频压缩格式,技术即运动静止图像(或逐帧)压缩技术,其中每一帧图像都分别使用JPEG编码,不使用帧间编码,压缩率通常在20:1-50:1范围内(相比之下 MPEG4可以到200:1-500:1,帧间压缩)。广泛应用于非线性编辑领域可精确到帧编辑和多层图像处理,把运动的视频序列作为连续的静止图像来处理,这种压缩方式单独完整地压缩每一帧,在编辑过程中可随机存储每一帧,可进行精确到帧的编辑。
JPEG 2000是基于小波变换的图像压缩标准,由联合图像专家小组创建和维护。JPEG 2000通常被认为是未来取代JPEG(基于离散余弦变换)的下一代图像压缩标准,文件后缀.jp2。JPEG 2000的压缩比更高,而且不会产生原先的基于离散余弦变换的JPEG标准产生的块状模糊瑕疵。 JPEG 2000同时支持有损数据压缩和无损数据压缩。另外,JPEG 2000也支持更复杂的渐进式显示和下载。
编码流程简介:转YUV-下采样-分块-DCT-量化-hufman编码,用一张图简单描述编解码流程:
将RGB的数据转换到YUV色彩空间,基于人眼对亮度相对色度更敏感特性,方便亮色分离后对色度下采样降低数据量。举例的典型转换公式如下。转换后一个RGB像素点,对应一个YUV像素点(包含一个Y数据、U数据、V数据)。
接下来,下采样主要是对色度数据采样率降低 来降低数据大小,从YUV444(每个亮度Y对应一个U和一个V)下采样到YUV420(四个Y对应一个UV组合)或者YUV422(两个Y对应一个UV组合)。如果降低到420,数据量就只有444的一半大小。一般是按8bit存储YUV数据每个分量。
可以参考博文:https://blog.csdn.net/runafterhit/article/details/82917345
先按像素区域划分方便后续处理,先要把YUV这3个分量分开,存放到3张表中去(分别存储 Y数据表 U数据表 V数据表)。然后由左及右,由上到下依次读取8x8的子块,存放在长度为64的表中。先进行数据偏移得到-128~128的对称区域数据,然后进行DCT离散余弦变换 Discrete cosine transform, 将图像的低频(人眼敏感)和高频(人眼不敏感)部分进行分离。这样得到的结果是每个 88 小块得到 88 的系数矩阵。
将DCT后得到的每个系数都除以量化矩阵中对应的值,然后进行取整。通常来说频率较高的部分对应的量化参数比较大,这样一来能够在较好地保留图像的低频部分并去除一些高频部分。JPEG中压缩率的调整是在这一步中,量化参数越大,压缩后的大小就会越小,但信息的损失也就越多失真更严重,这一步是有损的。
熵编码是无损资料压缩的一个特别形式。它影像成分以Z字体(zigzag)排列,把相似频率组群在一起(矩阵中往左上方向是越低频率之系数,往右下较方向是较高频率之系数),插入长度编码的零结束标志,且接着对剩下的使用霍夫曼编码。 JPEG标准也允许在数学上优于霍夫曼编码的算术编码之使用(一般让文件更小约5%),然而由于专利原因很少使用,且相较霍夫曼编码在编解码上更慢。
哈夫曼编码一句话描述:先统计使用到全部字符独自的数量,构建一个哈夫曼树使每个字符都对应唯一的二进制编码,然后就用这些二进制翻译原先的数据 得到编码后data,反向解码也就是把 data通过哈夫曼树反向得到字符。从树介绍到哈夫曼树 可以了解:https://blog.csdn.net/runafterhit/article/details/96769885
JPEG文件的格式是分段来存储的,每个段都一定包含两部分一个是段的标识,由两个字节构成:第一个字节是0xFF,第二个字节标识段类型, 部分段紧接着的两个字节存放的是这个段的长度。标识表格如下:
备注:由于jpeg用FFxx作为标识,为了避免真正压缩数据FF冲突,数据段的FF使用FF00表示,相当于只要FF后面不是00就是一个真正的标号 而非 FF数据。
jpeg文件格典型组成描述:SOI(文件开始标识-FFD8,是不是一个jpeg文件用这个判断)+APP0(应用程序保留标记0xFFE0-如分辨率/采样率等,方便应用查看)+ DQT(定义量化表FFDB)+ SOF0(图像基本信息)+ DHT(定义Huffman表FFC4) + DRI(定义重新开始间隔FFDD-可选的)+ SOS(扫描行开始FFDA)+ EOI(文件结束标识FFD9)。
从最顶层看:文件起始SOI FFD8和结束EOI FFD9之间,就是jpeg的文件数据frame部分;把jpeg文件数据展开,先有一个APPn用作应用保留信息 方便应用软件读取,和 各类表格(如DQT定义量化表FFDB) 放在Tables/misc部分,然后是真正文件头frame header,接着就是扫描段scan组合,扫描段内部可以通过RST标识 FFD0~FFD7 表示 不同熵编码段 之间的 复位标志。如下我们打开一个图片。二进制通过UltraEdit、解析工具JPEGsnoop 见 第四部分查看工具。
1、开始标志SOI FFD8。
2、APPn标记,APP0 应用保留信息信息常见格式(包含9个字段):固定值0xFFE0,1 数据长度(2 bytes,1~9段字节总长度),2标识符(5 bytes,内容0x4A46494600,即字符串JFIF0),3 版本号码(2 bytes,比如0x0102表示JFIF的版本号1.2),4 X和Y的密度单位(1 bytes,0:无单位;1:点数/英寸;2:点数/厘米),5 X方向像素密度(2 bytes),6 Y方向像素密度(2 bytes),7 缩略图水平像素数目(1 bytes),8 缩略图垂直像素数目(1 bytes),9缩略图RGB位图(rgb数据,任意长度)。
3、APP0~APPn之后是各类表项。一个或者多个量化表DQT:固定0xFFDB,1 量化表长度(2 bytes), P/T(1 bypte,高四位:精度 0 表8bit 1表16bit,低四位:表ID),表项内容;
3、展开文件头frame header,里面通过SOFn(FFC0~FFCF) 开始表示start of frame market帧标识起始,里面n用来描述是哪种基础编码格式,然后是头长度Lf(头长度),P采样精度(常见8-表示8bit位深),Y帧的行数即图片高度,X图片宽度,Nf 分量的个数(如 Y、U、V 3个分量,或者纯灰度图1)。接下来 每个分量的描述参数,主要包含 (Ci分量类型标识ID,Hi分量水平采样,Vi分量垂直采样,Tqi当前分量量化表ID)。
3、文件头之后,是一个或者多个霍夫曼表DHT:固定FFC4,1 长度(2 bytes),2 每个霍夫曼表内容(A ID号;B类型 0:DC表,1:AC表;C 长16个字节的编码,其代码代数和为接下来的编码的长度;D 内容编码)
4、DRI (Define Restart Interval) 复位段,可选:固定FFDD,1 长度(2 bytes),2 MCU 块的单元中的重新开始间隔(设为n,则意思是说,每n个MCU块就有一个RSTn标记。第一个标记是RST0,然后是RST1等,RST7后再从RST0重复)。
5、SOS(Start of Scan) 扫描数据段:固定FFDA,1 长度(2 bytes),2 分量数(1 bytes,灰度1分量,YUV3分量),3 每个颜色分量(A ID,B 交流系数AC表号,C 直流系数表DC表号)4 压缩数据(A 谱选择开始 bh 0x00,B.谱选择结束 ch 0x3F,C. 两个4位字段,高位和低位的谱选择 dh 1字节在基本JPEG中总为0x00, D 数据)
6、图像结束EOI:FFD9。
在网上找了两款,可以用一下。
介绍网页:https://blog.csdn.net/leixiaohua1020/article/details/84520117
SourceForge:https://sourceforge.net/projects/jpeganalysis/
中文,还配有注释,比较好理解。
官网:https://www.impulseadventure.com/photo/jpeg-snoop-source.html
gitbub链接:https://github.com/ImpulseAdventure/JPEGsnoop
百度经验使用指导:https://jingyan.baidu.com/article/37bce2be53f88f1002f3a212.html
libjpeg-turbo介绍主页:https://libjpeg-turbo.org/
libjpeg-turbo下载github链接:https://github.com/libjpeg-turbo/libjpeg-turbo
1、下载源码:在github上找到源码链接,我的调试环境是linux,下载时的版本是2.1.1(见ChangeLog.md)
root@ubuntu:~/libjpeg/code# git clone https://github.com/libjpeg-turbo/libjpeg-turbo.git
2、readme有提示编译看BUILDING.md 文件,基本用法梳理如下(依赖cmake,apt-get install cmake下载)
#创建并进入构建文件夹
mkdir build
cd build/
# 用命令创建makefile,格式:cmake -G"Unix Makefiles" [additional CMake flags] {source_directory}
# CMake flags这里可以指定编译参数,source_directory是libjpeg源码目录
# 常见编译参数:-DCMAKE_INSTALL_PREFIX=xxx指定安装目录,如是交叉编译-DCMAKE_C_COMPILER指定编译器
cmake -G"Unix Makefiles" -DCMAKE_INSTALL_PREFIX=./ /root/libjpeg/code/libjpeg-turbo/
# 编译,可以带-j多线程加速编译
make -j
# 查看编译后内容
root@ubuntu:~/libjpeg/code/libjpeg-turbo/build# ls
cjpeg cmake_install.cmake djpeg jcstest libjpeg.map libturbojpeg.a Makefile sharedlib tjbenchtest tjunittest-static
cjpeg-static cmake_uninstall.cmake djpeg-static jpegtran libjpeg.so libturbojpeg.so md5 simd tjexample wrjpgcom
CMakeCache.txt croptest jconfig.h jpegtran-static libjpeg.so.62 libturbojpeg.so.0 pkgscripts tjbench tjexampletest
CMakeFiles CTestTestfile.cmake jconfigint.h libjpeg.a libjpeg.so.62.3.0 libturbojpeg.so.0.2.0 rdjpgcom tjbench-static tjunittest
3、编译后最关注的libjpeg.a静态链接库,libjpeg.so动态链接库 就是我们需要的 库了,还有一些小工具。
有了动态链接库了,我们来写一个最简单的解码sample试试(在源码目录下example.txt有很详细的各种用法用例描述)
写一个sample_jpeg.c文件:
#include "stdio.h"
#include "jpeglib.h"
#include
struct my_error_mgr {
struct jpeg_error_mgr pub; /* "public" fields */
jmp_buf setjmp_buffer; /* for return to caller */
};
typedef struct my_error_mgr * my_error_ptr;
void my_error_exit (j_common_ptr cinfo) {
my_error_ptr myerr = (my_error_ptr) cinfo->err;
(*cinfo->err->output_message) (cinfo);
longjmp(myerr->setjmp_buffer, 1);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("please intput like: ./a.out xxx.jpeg\n");
}
// 打开输入文件
FILE *infile = fopen(argv[1], "rb");
if (infile == NULL) {
printf("intput file %s open failed!\n", argv[1]);
return -1;
}
// 创建输出文件
FILE *outfile = fopen("./output.bit", "w+");
if (outfile == NULL) {
printf("out file open failed!\n");
return -1;
}
// 定义cinfo数据结构,libjpeg定义的解码器上下文
struct jpeg_decompress_struct cinfo;
// 设置报错回调,如果libjpeg解析过程出错,会调用
struct my_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr.pub);
if (setjmp(jerr.setjmp_buffer)) {
printf("set jmp error failed!\n");
fclose(infile);
return -1;
}
// 创建解码器
jpeg_create_decompress(&cinfo);
// 设置解码器输入,绑定文件
jpeg_stdio_src(&cinfo, infile);
// 读取文件头,这一步之后cinfo中文件信息才可信
jpeg_read_header(&cinfo, TRUE);
// 开始解码,默认输出是RGB,还可以cinfo.out_color_space设置
jpeg_start_decompress(&cinfo);
// 计算输出的行stride,jpeg库是按行往外读取,要计算每行读取多少数据
int row_stride = cinfo.output_width * cinfo.output_components;
// 调用libjpeg内存申请接口,申请一个一行长的buffer
JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
// 循环从libjpeg读取行 到 buffer,然后将buffer内容写入文件
while (cinfo.output_scanline < cinfo.output_height) {
jpeg_read_scanlines(&cinfo, buffer, 1);
fwrite(buffer[0], row_stride, 1, outfile);
}
// 关闭全部文件
close(infile);
close(outfile);
return 0;
}
编译sample,注意带上libjpeg库,然后执行sample,把jpeg图片作为输入。
gcc sample_jpeg.c -ljpeg
./a.out ./test3.jpeg
wiki百科介绍:https://en.wikipedia.org/wiki/JPEG (备注:页面有ITU-T81标准文档)
ITU标准官网:https://www.itu.int/zh/ITU-T/Pages/default.aspx
http://www.360doc.com/content/07/0703/09/2228_591692.shtml
https://blog.csdn.net/yun_hen/article/details/78135122
https://blog.csdn.net/huangxy10/article/details/8117469
https://zhuanlan.zhihu.com/p/27296876
https://blog.csdn.net/carson2005/article/details/7753499
http://blog.csdn.net/shelldon/article/details/54234433
JFIF文件格式:https://blog.csdn.net/johnny_83/article/details/2252599