YCbCr 色彩空间
在 JPEG 图像的编码流程中,一个关键步骤是将 RGB 转换为 YCbCr。Y 表示亮度信号(Luma),Cb 和 Cr 是两个色差分量(Color Difference):Cb 是蓝色色差,Cr 是红色色差;两者合起来构成色度(Chrominance,或者Chroma)。这样一来,图像的亮度与颜色信息就被分开了,后面便可以更大胆地压缩颜色信息,例如进行色度降采样(Chroma Subsampling)。
不过,YCbCr 转换本身并没有压缩图像。它只是先把亮度与色度分开,为后续压缩色度创造条件。
YCbCr 也并不是 JPEG 的强制要求:JPEG 本身不规定分量的含义,RGB、CMYK 乃至灰度图像同样可以用 JPEG 编码;YCbCr 只是 JFIF 为彩色图像指定的颜色空间。本文后续的讨论,都建立在“使用 YCbCr 的 JFIF 图像”这一前提上。
有些材料中会把 YCbCr 称为 YUV。但严格来说,YUV 是属于模拟信号时代的术语,在数字时代,用 YCbCr,或者更为严谨的 Y'CbCr (撇号强调 Gamma 校正),才更为合适。不过在现实中这些术语早就已经大量混用了,一般也不需要过于较真。我们这里所说的R、G、B、Y都是指经过 Gamma 校正后的数值,关于 Gamma 校正,我们日后再来讨论。
Y(Luma)
我们先来理解 YCbCr 中的 Y。在明视觉下,人眼对不同波长的光敏感度不同:对绿光最敏感,红光次之,对蓝光最不敏感。换句话说,不同颜色的光对“人眼感知到的亮度”的贡献并不相同。根据这个原理,我们可以把“人眼感知到的亮度”表示为 R、G、B 三个分量的加权和:
Y=KrR+KgG+KbB
这里的 R、G、B 表示取值范围为 [0,1] 的 RGB 分量(均已经过 Gamma 校正),Kr、Kg、Kb 则是三个分量的权重。绿色权重最大、红色次之、蓝色最小,正与前面的直觉相呼应。
JFIF 约定使用 Rec.601 的 Luma 系数:
Kr=0.299,Kg=0.587,Kb=0.114
因此,
Y=0.299R+0.587G+0.114B
因为 Kr+Kg+Kb=1,且 R、G、B 的取值范围为 [0,1],所以 Y 的取值范围也是 [0,1]。
Cb、Cr(Chroma)
有了 Y 之后,颜色信息又该怎么表示呢?这就需要引入色差的概念。色差,简单来说,就是表示某个颜色分量和 Luma 之间的差异。如果某个像素的蓝色分量比它的 Luma 更高,那么它就带有偏蓝的色差;如果它的红色分量比 Luma 更高,那么它就带有偏红的色差。
因此,我们很自然地会想到用下面的方法来表示色差:
Cb=B−Y
Cr=R−Y
这样的定义很简单,很直观,但是却存在着一个问题。
我们可以先分析一下 Cb 的取值范围。将 Y 的表达式代入:
Cb=B−Y=B−(KrR+KgG+KbB)=−KrR−KgG+(1−Kb)B
当 R=G=0、B=1 时,
(B−Y)max=1−Kb
当 R=G=1、B=0 时,
(B−Y)min=−Kr−Kg=−(1−Kb)
也就是说,B−Y 的取值范围为:
B−Y∈[−(1−Kb),1−Kb]
用相同方法可得:
R−Y∈[−(1−Kr),1−Kr]
可以看到,蓝色色差和红色色差的取值范围并不相同,这就可能带来很多使用上的不便。为了能更方便地使用它们,我们可以把它们都缩放到 [−0.5,0.5] 区间,于是我们可以改用下面的方法来表示色差:
Cb=211−KbB−Y=1.772B−Y≈0.564334(B−Y)
Cr=211−KrR−Y=1.402R−Y≈0.713266(R−Y)
将 Y 代入这两个式子,并整理同类项,就得到了可直接由 RGB 计算 Cb、Cr 的公式:
Cb=0.564334B−0.564334(0.299R+0.587G+0.114B)=−0.168736R−0.331264G+0.5B
Cr=0.713266R−0.713266(0.299R+0.587G+0.114B)=0.5R−0.418688G−0.081312B
到这里,亮度和两个色差分量就都能从 RGB 算出来了。
有些朋友可能发现了,YCbCr 的色差分量只有蓝色和红色,并没有单独的绿色分量。这并不意味着绿色信息被丢弃了,只是因为绿色的分量可以由 Y、Cb、Cr 计算出来,所以不需要单独储存。
到目前为止,我们已经得知了 Y、Cb、Cr 的计算方法:
YCbCr=0.299R+0.587G+0.114B=−0.168736R−0.331264G+0.5B=0.5R−0.418688G−0.081312B
且它们的取值范围分别是:
YCbCr∈[0,1]∈[−0.5,0.5]∈[−0.5,0.5]
Y、Cb、Cr 目前都还是方便推导的连续值。为了更容易地处理数字图像,我们希望产生的三个值都是 [0,255] 范围内的整数,这样便可以用无符号 8 位整数来分别表示这三个分量。
我们先定义取整、裁剪之前的连续值:
Y8∗=255Y
Cb8∗=128+255Cb
Cr8∗=128+255Cr
再对上述三个值进行取整与裁剪:
Y8Cb8Cr8=clamp(round(Y8∗),0,255)=clamp(round(Cb8∗),0,255)=clamp(round(Cr8∗),0,255)
其中
- round(x) 表示将 x 取为相近的整数;
- clamp(x,0,255) 表示将小于 0 的值裁剪为 0,将大于 255 的值裁剪为 255。
现在把前面的步骤合在一起,我们便得到了将数字图像从 RGB 转换为 YCbCr 的方法:
Y8∗Cb8∗Cr8∗=0.299−0.1687360.50.587−0.331264−0.4186880.1140.5−0.081312R8G8B8+0128128
其中 R8、G8、B8 是由 R、G、B 缩放取整到 [0,255] 区间上的整数值,而 Y8∗、Cb8∗、Cr8∗ 表示取整、裁剪之前的连续值。由于 Cb8∗ 和 Cr8∗ 的取值范围实际上是 [0.5,255.5],略微超出 [0,255],因此需要按照前面的规则取整并裁剪,才能得到最终的 8 位整数值 Y8、Cb8、Cr8。
熟悉视频编码的朋友应该知道,Rec.601 的数字视频通常不会用完整的 [0,255] 范围,Y 的范围是 [16,235]。JFIF 虽然借用了 Rec.601 的转换系数,却没有沿用它的取值范围,而是让 Y、Cb、Cr 使用完整的 [0,255] 范围。
色度降采样
现在,颜色信息已经分离到 Cb 和 Cr 两个分量,亮度信息则集中在 Y 分量中。因此就可以只降低 Cb、Cr 的空间分辨率,从而减少数据量。这个过程称为色度降采样(chroma subsampling)。
人眼对颜色变化的敏感度,通常不如对亮度变化的敏感度。一张照片的轮廓和纹理,大多由明暗变化来刻画;颜色往往只是附着在亮度结构之上的大块色调。因此,适度降低色度分量的分辨率,通常不会明显影响观感,却能显著减少需要编码的数据量——这正是编码器敢于压缩颜色信息的依据。
不过,当图像里出现颜色对比强烈的高频细节,比如细小的线条、锐利的边缘等时,色度降采样就会导致比较明显的伪影,因此这类内容通常更适合用 4:4:4 保存。
采样比例
色度的降采样有很多种方案,通常用 J:a:b 记号来描述,常见的有 4:4:4、4:2:2、4:1:1、4:2:0 这几种。
这种记号是以一个宽 J 像素、高 2 像素的参考块为基准:
- J:参考块的宽度,通常取 4;
- a:参考块中第一行的色度采样个数;
- b:参考块从第一行到第二行,色度采样发生变化的数量。
下面用一个宽 4、高 2 的像素块示意。每个格子是一个像素;YC 表示该像素同时有亮度和色度采样,Y 表示它只有亮度采样:
4:4:4
- 参考块第一行有 4 个色度采样,因此 a=4;
- 参考块从第一行到第二行,新增了 4 个色度采样,因此 b=4。
换句话说,该方式不降采样,每个像素都有独立的 Cb、Cr,色度分辨率与亮度相同。
YC YC YC YC
YC YC YC YC
4:2:2
- 参考块第一行有 2 个色度采样,因此 a=2;
- 参考块从第一行到第二行,新增了 2 个色度采样,因此 b=2。
换句话说,该方式只在水平方向将色度采样减半,垂直方向不变。
YC Y YC Y
YC Y YC Y
4:1:1
- 参考块第一行有 1 个色度采样,因此 a=1;
- 参考块从第一行到第二行,新增了 1 个色度采样,因此 b=1。
换句话说,该方式在水平方向将色度采样减到四分之一、垂直方向不变。它在静态 JPEG 图像中几乎不会用到,主要见于部分视频格式(如 DV)。
YC Y Y Y
YC Y Y Y
4:2:0
- 参考块第一行有 2 个色度采样,因此 a=2;
- 参考块从第一行到第二行,新增了 0 个色度采样,因此 b=0。
换句话说,该方式在水平和垂直方向都减半,因此每个 2×2 像素块共用一组 Cb、Cr。
YC Y YC Y
Y Y Y Y
值得注意的是,J:a:b 只规定了色度采样的大致数量关系,至于色度采样具体落在参考块的哪个位置,是居中在多个亮度采样中间,还是与某个亮度采样重合,不同格式的约定并不统一。
另外,虽然 J:a:b 是更为普适的记号,但是在现代语境下,人们似乎更喜欢使用水平、垂直采样因子来描述色度采样比例,JFIF 也是如此。将每个分量的水平、垂直采样因子写进码流的 SOF 标记,解码器就能按文件里写明的比例处理。其中 4:2:0 是照片场景中最常见的选择。
数据量减少了多少
在一个 4×2 的参考块里,Y 始终有 8 个采样,不同的比例只改变色度采样的个数。按每个采样 8 位来算:
- 4:4:4:Y 8 个、Cb 8 个、Cr 8 个,共 24 个采样;
- 4:2:2:Y 8 个、Cb 4 个、Cr 4 个,共 16 个采样,是 4:4:4 的 32;
- 4:2:0:Y 8 个、Cb 2 个、Cr 2 个,共 12 个采样,是 4:4:4 的 21。
也就是说,仅靠降采样这一步,4:2:0 就能把原始的像素数据量减少一半,而且这部分节省发生在任何压缩变换之前。