Transform 实验室
色彩空间转换:RGB、HSL 与 YCbCr 的数学之美
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);
色彩空间转换是图像处理的"货币兑换" -- 不同场景用不同货币,但汇率永远是那几个精心设计的常数。