你有没有过这种经历:一张照片在相机里看锐利通透,天空的渐变也很自然,发完朋友圈再点开大图,边缘就像蒙了一层薄纱,天空还隐隐出现一道一道的横纹。大部分时候问题不在手机,而在一种已经用了三十多年的老格式——JPEG。
JPEG全称Joint Photographic Experts Group,1986年成立,1992年发布第一版标准。到今天,数码相机、网页、社交媒体的默认输出几乎全被它包揽。围绕它的争议也不小,有人嫌它有损、越存越糊,但实际上大多数人并没有真正搞清楚它到底损在哪、怎么损的。如果你做数字图像处理相关的工作,或者平时会批量处理照片、搭图片服务,把JPEG的原理摸透,能帮你少踩很多坑。
这篇文章我不打算把ISO/IEC 10918-1标准从头过一遍,那读起来像天书。我按自己这些年处理图片的经验,把JPEG从原理到文件结构再到实战保存策略,拆成几个能直接上手的部分。文里涉及的数据和流程我都实际验证过,你可以放心参考。
1. JPEG做了哪些"偷懒"决定:三处关键的信息丢弃
JPEG之所以能做到极高的压缩比,一台相机拍出来的12MB原图能压到2~3MB,甚至更小,靠的不是某个单一技巧,而是三处层层递进的"信息丢弃"。这三处丢弃都是在对人眼视觉系统做了深入研究之后设计的,本质上是在迎合你眼睛的"感知偏好"。
1.1 颜色空间转换:为什么要把RGB拆成亮度色度
日常显示设备用的是RGB三通道,每个像素的红、绿、蓝各占8比特,共24比特。但JPEG编码的第一步,就是把RGB转换到YCbCr颜色空间。Y是亮度分量,Cb和Cr是两个色度分量,分别表示蓝色差和红色差。转换公式是:
code复制Y = 0.299R + 0.587G + 0.114B
Cb = -0.1687R - 0.3313G + 0.5B + 128
Cr = 0.5R - 0.4187G - 0.0813B + 128
为什么非要换颜色空间?因为人眼对亮度的敏感程度远高于对颜色的敏感程度。你回想一下:大晚上路灯下看不清路人穿的是什么颜色的衣服,但你一眼就能看出那是个人——这就是亮度信息远比色度信息重要。把亮度单独抽出来,接下来就可以对不同分量区别对待,把精力集中在保留亮度细节上,色度细节则可以适当放水。
这个转换本身是固定的矩阵运算,不会损失信息,但它为后面"偷懒"创造了条件。现实中很多图像处理库在解码JPEG时会直接输出YCbCr数据,你转回RGB显示的时候会发现边缘略微有点偏色,这就是在色度分量上放水留下的痕迹。
1.2 色度下采样:人眼的天然漏洞
第二步是色度下采样,也叫色度抽稀。前面提到人眼对色度不敏感,所以JPEG允许把Cb和Cr分量的分辨率降低一半甚至更多。最常见的采样格式有四种:
| 采样格式 | 亮度分辨率 | 色度分辨率 | 说明 |
|---|---|---|---|
| 4:4:4 | 全分辨率 | 全分辨率 | 无损保留色度,文件较大 |
| 4:2:2 | 全分辨率 | 水平减半 | 专业视频常见 |
| 4:2:0 | 全分辨率 | 水平和垂直都减半 | 照片最常见 |
| 4:1:1 | 全分辨率 | 水平四分之一 | 很少见 |
你手机拍出来的JPEG,绝大多数是4:2:0。也就是说,色度信息其实只有亮度信息的四分之一。一张800x600的照片,亮度分量要存800x600个数值,而色度分量只需要存400x300个数值,三个通道加起来的信息总量直接从原来的三倍变成一点五倍。压缩还没正式开始,数据量已经打了一个六折。
这也是为什么有些摄影师在后期时会觉得JPEG的颜色"不够扎实":不是相机传感器不行,而是JPEG的采样机制本身就是以牺牲色度细节为代价换空间。
1.3 8x8分块:JPEG的基本操作单元
JPEG对每个分量(Y、Cb、Cr)都按8x8像素的小块为单位处理。为什么选8x8而不是16x16或4x4?这是基于对自然图像相关性的统计研究得出的折中方案——块越大,去相关效率越高,但局部细节的适应性越差;块太小,DCT变换的频域集中性不够,压缩率上不去。
你打开一张JPEG调试信息,能看到"MCU"(最小编码单元)这个词。在4:2:0采样下,一个MCU由四个Y块、一个Cb块、一个Cr块组成,对应原图的16x16像素区域。这个结构的直接后果是:任何JPEG的压缩操作都要按这个块网格进行,一旦块边界处的处理不当,就会留下"方格"痕迹。
注意,这里有两个方向的问题:一是编码时如果某个块的内容和周围差异太大,解码还原后块边界可能肉眼可见;二是编辑JPEG时,如果旋转了任意角度或者裁切尺寸不是16的倍数,解码器强制对齐到新的块网格,又会产生新的误差。后面讲块效应的时候我会再细说。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 编码流水线拆解:DCT变换、量化、Z字扫描到熵编码
理解了JPEG在哪些地方"故意丢信息"之后,我们顺着编码器的执行顺序走一遍完整的流水线。方便起见,这里以灰度图为例——一张灰度图只有一个亮度通道,没有色度下采样,这
