Transform 实验室

色彩空间转换:RGB、HSL 与 YCbCr 的数学之美

2025-02-14 wangjun 15 min read
T()

为什么需要多种色彩空间?

同一种颜色可以有不同的"描述方式"。RGB 适合显示器,HSL 适合人操作,YCbCr 适合压缩 -- 每种色彩空间都在为某个特定场景做优化,转换本身是一套线性/非线性变换。

RGB ↔ HSL

HSL(色相/饱和度/亮度)把颜色拆成三个直观分量,方便取色器与主题色调整:

// RGB(0-255) → HSL
function rgbToHsl(r, g, b) {
  r /= 255; g /= 255; b /= 255;
  const max = Math.max(r, g, b), min = Math.min(r, g, b);
  const l = (max + min) / 2;
  if (max === min) return { h: 0, s: 0, l };  // 灰色
  const d = max - min;
  const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  let h;
  if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) * 60;
  else if (max === g) h = ((b - r) / d + 2) * 60;
  else h = ((r - g) / d + 4) * 60;
  return { h: Math.round(h), s: +(s*100).toFixed(1), l: +(l*100).toFixed(1) };
}

调"亮度"(调 L 不动 H/S)、做"柔和配色"(固定 H 扫 S/L)在 HSL 下一行代码搞定。

RGB ↔ YCbCr:压缩的基石

人眼对亮度(Y)比对色彩(Cb/Cr)更敏感,所以把亮度与色度分开,就可以对色度做重采样压缩。JPEG / H.264 都这么做:

// RGB → YCbCr(BT.601 标准)
Y  =  0.299R + 0.587G + 0.114B
Cb = -0.169R - 0.331G + 0.500B + 128
Cr =  0.500R - 0.419G - 0.081B + 128


R = Y + 1.402 * (Cr - 128)
G = Y - 0.344 * (Cb - 128) - 0.714 * (Cr - 128)
B = Y + 1.772 * (Cb - 128)

关键点:亮度通道保留了几乎全部视觉信息,色度通道可以降到 4:2:0(每 4 个像素共享 1 组色度),体积直接砍半而观感几乎不变。

实际应用场景

  • 灰度化:直接取 Y 通道即可得到高质量灰度图
  • 视频编码:H.264/H.265 一律走 YUV420,转换矩阵是编码器的第一道工序
  • 亮度调节:在 YCbCr 域调 Y 不会产生色偏,比在 RGB 域乘系数更科学
  • 颜色抠像:在色度通道上做阈值分割,比 RGB 空间更稳定
// Canvas 灰度化一行版
ctx.drawImage(img, 0, 0);
const data = ctx.getImageData(0, 0, w, h).data;
for (let i = 0; i < data.length; i += 4) {
  const y = 0.299*data[i] + 0.587*data[i+1] + 0.114*data[i+2];
  data[i] = data[i+1] = data[i+2] = y;  // R=G=B=亮度
}
ctx.putImageData(new ImageData(data, w, h), 0, 0);
色彩空间转换是图像处理的"货币兑换" -- 不同场景用不同货币,但汇率永远是那几个精心设计的常数。