JPEG图像

-- TOC --

JPEG图像已经流行了几十年了,未来它也一定是主流图像格式之一。JPEG很适合用来压缩自然景物图像,一般JPEG压缩都是有损的,但标准也支持lossless。

本文部分内容和图片,来自项目:https://github.com/MROS/jpeg_tutorial

JPEG诞生于1992年,截止到2017年,JPEG标准有25年历史,论文《JPEG at 25: Still Going Strong》,DOI:10.1109/MMUL.2017.38,https://ieeexplore.ieee.org/document/7924246

JPEG标准会一直存在:https://news.itu.int/how-jpeg-gained-emmy-fame/

介绍

JPEG是第一个国际图像压缩标准,用于连续色调静态图像(即包括灰度图像和彩色图像),其最初目的是使用64Kbps的通信线路传输720×576分辨率(SDTV)压缩后的图像。通过损失极少的可视效果,可以将图像所需存储量减少至原大小的10%。由于其高效的压缩效率,早已广泛用于彩色传真、静止图像、电话会议、印刷及新闻图片的传送上。但那些被删除的信息无法在解压时还原,所以JPEG文件并不适合放大观看,输出成印刷品时品质也会受到影响。

JPEG的文件格式一般有两种文件扩展名:.jpg和.jpeg,这两种扩展名的实质是相同的,我们可以把.jpg的文件改名为.jpeg,而对文件本身不会有任何影响。

严格来讲,JPEG的文件扩展名应该为.jpeg,由于DOS时代的8.3文件名命名原则,就使用了.jpg的扩展名,这种情况类似于.htm和.html的区别。

原理

这张图是针对baseline格式的JPEG图片,说明压缩原理:

jpeg_yuanli.png

有损压缩来自RGB转YCbCr后的subsampling(一般使用420),以及DCT之后的量化过程,这跟视频编解码的原理一样。

据说JPEG有4种压缩模式:

后两种我都没有接触过。

最后的编码,除了霍夫曼编码之外,还有算术编码可以选择。

DCT转换,采用8x8大小的block

采样率,block组合成MCU, Minimum Coded Unit

mcu_block.png

MCU的大小,跟采样率有关系。

JPEG标准不指定任何固有的文件格式,它只定义压缩比特流的语法。

Baseline JPEG/基本JPEG

这种类型的JPEG文件存储方式是按从上到下的扫描方式,把每一行数据顺序地保存在JPEG文件中。打开这类JPEG文件显示它的内容时,数据将按照存储时的顺序从上到下一行一行的被显示出来,直到所有的数据都被读完,就完成了整张图片的显示。这种图片在web中,如果没有给图片指定宽和高,会造成重绘。

progressive jpeg/渐进式JPEG

这类JPEG文件包含多次扫描,这些扫描顺序存储在JPEG文件中。打开文件过程中,会先显示整个图片的模糊轮廓,随着扫描次数的增加,图片变得越来越清晰。该类型的图片是对标准JPEG格式的改进,当在网页上下载渐进式JPEG图片时,首先呈现图片的大概外貌,然后再逐渐呈现具体的细节部分,因而被称之为渐进式JPEG。

JFIF

JPEG File Interchange Format

JPEG 標準在 JPEG 的檔案結構中預留了數個可選的標頭,目的是供應用程式能夠進一步加以拓展、或是添加額外訊息,JFIF 就在是应用最广泛的一種,它在 JPEG 的標準之外定義了縮圖大小、在不同设备上的显示比例。除了這些額外訊息, JFIF 也對 JPEG 加以限縮,例如限制了色彩空間。

The JFIF format defines a number of details that had been left undefined in the initial JPEG format. Defining these details, including a standard header, resolution and aspect ratio, and color model, allowed users to actually save and share files using JPEG compression.

For a long time, many digital cameras saved images in the JFIF format. But over time, additional improvements to the JPEG format rendered the JFIF format mostly obsolete.

现在基本看不到JFIF后缀的文件,其实他们都是jpeg。

The most common filename extensions for files employing JPEG compression are .jpg and .jpeg, though .jpe, .jfif and .jif are also used. It is also possible for JPEG data to be embedded in other file types – TIFF encoded files often embed a JPEG image as a thumbnail of the main image; and MP3 files can contain a JPEG of cover art in the ID3v2 tag.

EXIF使用APP1.

JPEG文件格式

jpeg文件是按照段的格式来组织存储的,每一个文件由多个段组成,每个段代表不同的信息。同时,每个段也有自己唯一的标识符。标识符是由两个字节所组成,格式如0xFFXX,其中XX代表的是不同的类型。例如,SOI(start of image),表示图像的开始,其段头的标识符为0xFFD8。而整个jpeg图片的组织便是由诸多这些不同类型的段和经过JPEG压缩后的数据而组成。如果解析,需要根据这些段不同的头类型来做相应的处理。

压缩后的JPEG文件,有自己的格式:

jpeg_format.png

APP0段的数据,在解码时不会用到。

在 SOF0 區段標記了它的內容包含算法名稱,其實這並不太精確,一個 JPEG 圖檔中,一定會出現一個 SOF0, SOF1, SOF2, ... SOF16 其中一個區段,如果出現的是 SOF0 ,那就代表我們使用的是 baseline 。另一件值得注意的事情是 DQT 跟 DHT 都可能不止一個。

所有的區段都會以 0xFF 開頭,緊接着的一個 byte 表示了它是哪一種區段。如果这个段有数据,接下来的2个byte表示段的长度(不含段标识的2bytes)。数据全部按big-endian方式存放。当数据中也存在0xFF时,后面一定跟一个0x00,来与段标识码区分。

段名 标识码 Payload Comment
SOI 0xFFD8 No
EOI 0xFFD9 No
DQT 0xFFDB Yes Specifies one or more quantization tables.
DHT 0xFFC4 Yes Specifies one or more Huffman tables.
SOF0 0xFFC0 Yes Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components, and component subsampling (e.g., 4:2:0).
SOF2 0xFFC2 Yes Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, and component subsampling (e.g., 4:2:0).
SOS 0xFFDA Yes Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it will contain, and is immediately followed by entropy-coded data
COM 0xFFFE Yes Contains a text comment.
APPn 0xFFEn Yes For example, an Exif JPEG file uses an APP1 marker to store metadata, laid out in a structure based closely on TIFF. JFIF使用APP0.
RSTn 0xFFDn No Inserted every r macroblocks, where r is the restart interval set by a DRI marker. Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
DRI 0xFFDD 4bytes Define Restart Interval . Specifies the interval between RSTn markers, in Minimum Coded Units (MCUs). This marker is followed by two bytes indicating the fixed size so it can be treated like any other variable size segment.

**There are other Start Of Frame markers that introduce other kinds of JPEG encodings. **

SOF0/SOF2

这两个section分别定义了baseline和progressive图片尺寸和颜色采样信息,

marker后的2byte是length,这个length减去2就是剩下的字节数;

然后1个byte,表示bit-depth,baseline的bit-depth固定为8;

然后2个byte,图片高度;

然后2个byte,图片宽度;

然后1个byte,有几个color channel,最可能是3,也可能是1;

后面的字节数,是color channel*3,即每个channel对应3个byte,这3个byte分别表示:

  1. channel id,1 is Y, 2 is Cb, 3 is Cr;
  2. 采样率,高4bit是水平采样率,低4bit是垂直采样率;采样率可以使1,2,3,4;
  3. 量化表id, 解码时所使用的量化表;

SOS

SOS,Start Of Scan,真正存放压缩数据的section,由0xFFDA开始。

libjpeg

有两个版本的libjpeg,一个来自IJG(http://www.ijg.org/),另一个叫libjpeg-turbo,地址:https://libjpeg-turbo.org/

libjpeg-turbo is a JPEG image codec that uses SIMD instructions (MMX, SSE2, AVX2, Neon, AltiVec) to accelerate baseline JPEG compression and decompression on x86, x86-64, Arm, and PowerPC systems, as well as progressive JPEG compression on x86 and x86-64 systems. On such systems, libjpeg-turbo is generally 2-6x as fast as libjpeg, all else being equal. On other types of systems, libjpeg-turbo can still outperform libjpeg by a significant amount, by virtue of its highly-optimized Huffman coding routines. In many cases, the performance of libjpeg-turbo rivals that of proprietary high-speed JPEG codecs.

完全可以考虑直接使用libjpeg-turbo,它已经是ISO/ITU的标准参考实现。Fedora的libjpeg就是turbo版本,据说IJG的社区治理不太好。

mozjpeg项目也值得关注:

https://libjpeg-turbo.org/About/Mozjpeg

https://github.com/mozilla/mozjpeg

JPEG系列

https://jpeg.org/index.html

JPEG现在已经是一个系列标准了,好复杂!

JPEG2000

压缩品质更好,并且改善了无线传输时,因信号不稳定而造成的马赛克及位置错乱等问题。另外,作为JPEG的升级版,JPEG2000的压缩率比标准JPEG高约30%,同时支持有损压缩和无损压缩。它还支持渐进式传输,即,先传输图片的粗略轮廓,然后,逐步传输细节数据,使得图片由模糊到清晰逐步显示。此外,JPEG2000还支持感兴趣区域,也就是说,可以指定图片上感兴趣区域的压缩质量,还可以选择指定的部分先进行解压。还有个优势就是,JPEG2000从无损压缩到有损压缩都可以兼容。

其它资料

https://johnloomis.org/ece563/notes/compression/jpeg/tutorial/jpegtut1.html

jpeg_parser

自制一个小工具,将jpeg文件的基本信息和格式解析出来,不是decode,这个太难了。

import sys
import re


mdict = {
    b'\xFF\xD8': 'SOI',
    b'\xFF\xD9': 'EOI',
    b'\xFF\xC0': 'SOF0',
    b'\xFF\xC2': 'SOF2',
    b'\xFF\xDB': 'DQT',
    b'\xFF\xC4': 'DHT',
    b'\xFF\xDA': 'SOS',
    b'\xFF\xFE': 'COM',
}


fmt = 12


with open(sys.argv[1].strip(), 'rb') as f:
    cont = f.read()


def sofx(pos):
    print(' '*fmt, 'bit depth', cont[pos+4])
    height = int(cont[pos+5:pos+7].hex(), 16)
    width = int(cont[pos+7:pos+9].hex(), 16)
    print(' '*fmt, 'size', f'{width}x{height}')
    print(' '*fmt, 'color channel', cc:=cont[pos+9])
    print(' '*fmt, '# channel h_samp_r v_samp_r quant_table_id')
    for i in range(cc):
        ycc = cont[pos+10+i*3:pos+10+i*3+3]
        if ycc[0] == 1:
            channel = 'Y'
        elif ycc[0] == 2:
            channel = 'Cb'
        else:
            channel = 'Cr'
        print(' '*fmt, end='')
        print(' '*4+channel, (ycc[1]&0xF0)>>4, ycc[1]&0x0F, ycc[2])


markers = re.finditer(rb'(\xFF[^\x00])', cont)
for m in markers:
    try:
        print(hex(m.span()[0]).upper().ljust(fmt,' '), mdict[m.group(0)])
        if mdict[m.group(0)] == 'SOF0':
            print(' '*fmt, 'baseline')
            sofx(m.span()[0])
        elif mdict[m.group(0)] == 'SOF2':
            print(' '*fmt, 'processive')
            sofx(m.span()[0])
    except KeyError:
        if int(m.group(0).hex(),16) & 0xFFF0 == 0xFFE0:
            marker = 'APP' + str(int(m.group(0).hex(),16)&0x000F)
            print(hex(m.span()[0]).upper().ljust(fmt,' '), marker)
        else:
            print(hex(m.span()[0]).upper().ljust(fmt,' '), m.group(0))

运行效果:

D:\temp>python jpeg_parser.py Indian_leopard.jpg
0X0          SOI
0X2          APP0
0X14         DQT
0X59         DQT
0X9E         SOF0
             baseline
             bit depth 8
             size 4096x2160
             color channel 3
             # channel h_samp_r v_samp_r quant_table_id
                Y 2 2 0
                Cb 1 1 1
                Cr 1 1 1
0XB1         DHT
0XD2         DHT
0X189        DHT
0X1AA        DHT
0X261        SOS
0X1C33EA     EOI

面对黑白图片时:

D:\temp>python jpeg_parser.py peter_drucker.jpg
0X0          SOI
0X2          APP0
0X14         DQT
0X59         SOF2
             processive
             bit depth 8
             size 590x222
             color channel 1
             # channel h_samp_r v_samp_r quant_table_id
                Y 1 1 0
0X66         DHT
0X83         SOS
0X578        DHT
0X5A9        SOS
0XFF8        DHT
0X1036       SOS
0X1AFE       DHT
0X1B29       SOS
0X2A19       SOS
0X2B26       DHT
0X2B51       SOS
0X41E4       EOI

本文链接:https://cs.pynote.net/ag/image/202206181/

-- EOF --

-- MORE --