JPEG 并不是简单地“降低分辨率”。典型 Baseline JPEG 会把 RGB 转换到 YCbCr,可选地降低色度分辨率,将每个分量切成 8×8 块,再用 DCT 把空间像素转换成频率系数。真正有损的是量化;Zigzag、游程和 Huffman 编码主要负责把量化后产生的规律压得更紧。
本文讨论常见的 8-bit、8×8、顺序式 DCT Baseline JPEG。Progressive JPEG 会改变扫描组织,JPEG XL、JPEG 2000 也不是这里这条算法链。
为什么切成 8×8 块
对每个分量,编码器先把样本减去 128,使其大致以 0 为中心,然后对 8×8 块计算二维 DCT:
其中 ,其余为 1。DCT 不会凭空压缩数据,它只是把自然图像中缓慢变化的能量集中到左上角低频。
8×8 DCT 基函数
每个系数描述一种水平与垂直余弦频率;左上角 DC 表示块平均值。
是 DC 系数,描述块平均值;向右表示更高水平频率,向下表示更高垂直频率。平坦块通常只有少数低频系数显著,复杂纹理则会把能量铺向右下角。
量化:不可逆的一步
每个 DCT 系数除以对应量化步长并取整:
解码时只能得到 ,舍掉的小数无法恢复。常见量化表给高频更大的步长,因为精细振荡更容易被掩蔽。
量化才是 JPEG 的主要有损步骤
DCT 只换坐标;除以量化步长并取整,才会把较弱的高频系数变成零。
JPEG 文件不保存统一的“质量值”,而是保存量化表。不同编码器即使显示相同 quality,也可能生成不同表和不同结果。
所谓 JPEG “质量 80”不是文件格式中的标准字段。编码器根据自己的公式从 quality 生成亮度/色度量化表,真正写入 JPEG 的是表本身。比较不同软件时,应查看量化表和色度抽样,而不是只比较滑杆数字。
教学式伪代码:
shifted = block.astype(float) - 128.0coeff = dct2(shifted)quantized = np.round(coeff / quant_table).astype(int)
# 解码端restored = idct2(quantized * quant_table) + 128.0pixels = np.clip(np.round(restored), 0, 255).astype(np.uint8)实际实现还要满足 JPEG 对 DCT 精度、码流标记、采样因子和 Huffman 表的要求。
Zigzag 不负责丢高频
Zigzag 为什么沿对角线扫描
扫描顺序大致从低频走向高频,把量化后的连续零集中到序列尾部,便于游程编码。
Zigzag 本身不丢数据,也不会让系数自动变零;它只是让 DC/低频靠前、高频靠后,为差分编码、RLE 和 Huffman 编码创造更有利的统计分布。
量化后,块的右下高频区域常出现大片零。Zigzag 大致按总频率从低到高读出 64 个系数,使连续零聚到序列尾部。
DC 系数通常与前一个块的 DC 做差分编码;AC 序列则把“前面有多少个零”和下一个非零值组合成符号,再进行 Huffman 编码。若使用算术编码,后端不同,但 DCT 与量化思想相同。
无损的熵编码只消除统计冗余。决定画质的主要旋钮仍是量化表和色度抽样。
方块效应与振铃从哪里来
低质量下为什么出现方块和振铃
每个 8×8 块独立量化;高频被削弱后,块边界和强边缘附近的误差会变得可见。
图形是机制示意,不是完整 JPEG 解码器。真实伪影还取决于图像内容、色度抽样、量化表、熵编码优化和解码后处理。
每个 8×8 块独立变换、独立量化。低质量下,相邻块重建到边界时不再连续,于是网格可见。强边缘需要许多高频基函数共同逼近;削掉它们后会在边缘附近出现过冲与波纹,即 ringing。
色度 4:2:0 还可能让彩色边缘更软。解码器的 deblocking 或显示端缩放能够掩盖部分伪影,却不能恢复被量化为零的系数。
把一张实拍照片真正编码成 JPEG
浏览器会按当前质量参数完成一次 JPEG 编码;低质量时可在树叶、花朵和人物轮廓附近寻找块效应与振铃。


这是浏览器 Canvas 的真实 JPEG 编码结果,但不同浏览器可能采用不同量化表和色度抽样;因此质量数字适合观察趋势,不适合跨编码器逐项比较。
样片:Baap8969 / Wikimedia Commons,CC0 1.0;本站缩放并转为 WebP。
完整的 Baseline 流程
RGB → YCbCr → 可选 4:2:2 / 4:2:0→ 分成 MCU 与 8×8 block → 减 128 → DCT→ 量化 → Zigzag → DC 差分 / AC 游程→ Huffman 熵编码 → JPEG 码流MCU(minimum coded unit)的尺寸取决于采样因子。4:4:4 时一个 MCU 通常含各分量一个 8×8 块;4:2:0 时常见 MCU 对应 16×16 亮度区域,包含四个 Y 块和各一个 Cb、Cr 块。图像边缘不足整块时需要 padding。
质量、体积与内容相关
提高质量意味着缩小多数步长,保留更多非零系数,熵编码要写更多符号。纹理、噪声和锐化边缘会产生大量高频,即使同一量化表也比平滑图像更难压缩。
因此“质量 90 一定是多少 KB”没有答案。编码器还可能优化 Huffman 表、改变色度抽样或去除元数据。目标文件大小模式通常会根据首遍统计反复调整量化表。
JPEG 编码前的锐化与降噪
过强锐化增加边缘高频和 halo:文件更大,量化后振铃也更明显。适度降噪减少随机高频,常能同时改善主观画质和压缩率;但过度降噪会抹去真实纹理。
管线需要按最终观看尺寸调整。先缩放到目标分辨率、做适量输出锐化,再只编码一次,通常优于多次 JPEG 解码—编辑—重编码。
最容易踩的坑
- 认为 DCT 本身有损;
- 把 quality 数字当跨软件统一标准;
- 忽略 4:4:4、4:2:2、4:2:0 对彩边的影响;
- 对已经有 block/halo 的 JPEG 继续强锐化;
- 反复保存造成 generation loss;
- 用 PSNR 单独判断文字、肤色和纹理的主观质量;
- 忽略 EXIF 方向、ICC profile 和范围/矩阵等元数据;
- 把 Progressive JPEG 误认为不同的有损算法——它主要改变扫描顺序和渐进显示。
验证清单
- 平坦渐变、自然纹理、噪声、斜线和高对比文字;
- 4:4:4 与 4:2:0 在彩色边缘上的差异;
- 量化表、非零系数比例与实际码率;
- 8×8 边界、ringing、banding 和颜色偏移;
- 单次编码与多代重编码;
- ICC profile、EXIF 和 alpha 的处理(经典 JPEG 不支持 alpha);
- 解码结果的 PSNR/SSIM 与 100% 视觉检查。
JPEG 的效率来自一组协作:YCbCr 让色度可以单独处理,DCT 集中能量,量化主动丢弃不重要精度,Zigzag 和熵编码再利用大量零。理解每一步负责什么,就能针对伪影调对参数,而不是只盯着一个含义并不统一的“质量”滑杆。