

























JPEG 的核心目标是:
在人眼不太容易察觉的地方丢掉一些信息,从而大幅减小照片体积。
所以 JPEG 通常是 有损压缩。它不是简单地把文件“打包变小”,而是会真的改变图像数据。
可以把 JPEG 压缩理解成几个步骤:
1 | RGB 图像 |
我们平时看到的图片通常可以理解成 RGB:
1 | R = 红色 |
但 JPEG 不直接用 RGB 压缩,而是会先转换成 YCbCr:
1 | Y = 亮度信息 |
这么做是因为人眼对 亮度变化 很敏感,但对 颜色细节 没那么敏感。
比如一张人脸照片:
1 | 轮廓、明暗、边缘变化很重要 |
所以 JPEG 会把图像拆成:
1 | 更重要的亮度通道 Y |
后面压缩时,颜色通道可以压得更狠一些。
既然人眼对颜色细节没那么敏感,JPEG 就可以减少颜色信息的采样数量。
常见色度抽样有:
1 | 4:4:4 不减少颜色信息 |
最常见的是 4:2:0。
它大概意思是:
1 | 亮度信息完整保留 |
举个简化例子:
1 | 原始: |
这一步对照片很有效,因为自然照片里颜色通常是连续变化的。
但如果是文字截图、UI 图标、红蓝边缘特别明显的图,色度抽样可能会让边缘发糊、出现彩边。
所以:
1 | 照片适合 JPEG |
这里再补充一个,就是4:2:0究竟是啥意思
YCbCr 会把一个颜色拆成三部分:
1 | Y = 亮度,也就是明暗 |
注意:Cb / Cr 不是直接等于蓝色和红色,而是 颜色差异信息。
可以粗略理解为:
1 | Y 管黑白明暗 |
所以一张 RGB 图片转成 YCbCr 后,可以想象成三张灰度图:
1 | 原图 |
其中最重要的是 Y 亮度图。
因为人眼看图时,最敏感的是亮度边缘,而不是颜色边缘。
比如一张白底黑字图片:
1 | 白底 + 黑字 |
你能很清楚看见文字,是因为亮度差异很大。
再比如一张人像照片:
1 | 脸部轮廓 |
这些主要靠 Y 亮度信息 表达。
但颜色细节稍微粗糙一点,比如皮肤颜色、天空蓝色、墙面颜色,人的感知没那么敏锐。
所以 JPEG 的策略是:
1 | Y 亮度:尽量保留清楚 |
这就是色度抽样的基础。
近似公式是:
1 | Y = 0.299R + 0.587G + 0.114B |
这里可以看到G是权重最大的,因为人眼对绿色最敏感,所以绿色对亮度感知贡献最大。
例如:
1 | R = 200 |
那么亮度 Y 大概是:
1 | Y = 0.299 × 200 + 0.587 × 120 + 0.114 × 80 |
所以这个像素的亮度大概是 139。
然后 Cb / Cr 负责记录它相对于这个亮度来说,颜色偏向哪里。
简单理解:
1 | Y 记录:这个点有多亮 |
色度通道 Cb / Cr 相对于亮度通道 Y,被保留了多少采样点。
先看一个 2×2 像素块。
原始情况下,每个像素都有:
1 | Y + Cb + Cr |
也就是:
1 | 像素 A: Y1 Cb1 Cr1 |
4:4:4 表示:
1 | 每个像素都有自己的 Y |
用 2×2 看:
1 | Y: |
也就是亮度和颜色分辨率一样高。
所以:
1 | 4:4:4 = 不做色度抽样 |
质量最好,体积也更大。
4:2:2 表示:
水平方向的颜色采样减半,垂直方向不减。
可以理解成:横向相邻两个像素共用一组 Cb / Cr。
比如 2×2 像素:
1 | Y: |
也就是:
1 | 第一行:两个像素共用一组颜色 |
亮度 Y 仍然每个像素都有。
所以:
1 | 4:2:2 = 横向颜色分辨率减半,纵向颜色分辨率不变 |
如果原来颜色采样是:
1 | 宽 1000,高 1000 |
4:2:2 的颜色通道大概变成:
1 | 宽 500,高 1000 |
4:2:0 最容易误解。它不是说“颜色没有了”,也不是说“第二行没有颜色”。
它的意思是:
色度信息在横向减半,纵向也减半。
也就是一个 2×2 像素块共用一组 Cb / Cr。
看 2×2:
1 | Y: |
也就是说:
1 | 4 个像素各自有自己的亮度 Y |
所以颜色通道的分辨率变成原来的:
1 | 宽度 1/2 |
假设原图是:
1 | 1920 × 1080 |
Y 通道仍然是:
1 | 1920 × 1080 |
但 Cb 通道变成:
1 | 960 × 540 |
Cr 通道也变成:
1 | 960 × 540 |
这就是“横向纵向都减少颜色信息”。
这是一个前置的理解,那么关于图片格式,也想聊下怎么识别这些格式,光是后缀好像是可以被随意改动,比如这个jpeg的格式,那么我们可以从文件头来看
1 | FF D8 FF E0 ... 4A 46 49 46 |
后面就代表的是 JFIF 这是文件头
我们也可以用一个小工具来识别
1 | <?php |
这是通过文件头和brand来一起识别,brand是针对那些跟视频格式一致的可能包含容器的格式
比如HEIC / AVIF,对于JPEG、PNG、GIF、WebP这类常规格式,是一般从文件头识别
而后面的容器型则需要结合容器的brand标识,
比如一个AVIF的文件头是
1 | 00 00 00 20 66 74 79 70 61 76 69 66 00 00 00 00 61 76 69 66 6D 69 66 31 |
拆开以后就是
1 | 00 00 00 20 = box size |
也就是
1 | box type: ftyp |
这样就大致知道了图片格式的识别
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。