第三章 图像基础与色彩空间:RGB、BGR、YUV、HSV、灰度图转换原理、色彩空间转换的GPU加速思路

各位同学,今天我们来聊聊图像处理中最基础、也最绕不开的话题——色彩空间。

说实话,我刚入行那会儿,觉得色彩空间不就是RGB嘛,红绿蓝三个通道,有啥好学的?直到我在一个嵌入式项目中,把YUV数据当RGB直接显示,结果屏幕上全是诡异的紫色条纹……嗯,那次教训挺深刻的。

3.1 为什么需要不同的色彩空间?

你想想看,人眼看颜色和摄像头“看”颜色,完全是两码事。RGB适合显示器,但压缩效率低。YUV适合视频传输,因为人眼对亮度更敏感。HSV适合做图像分割,因为色调和饱和度更直观。

说白了,不同的色彩空间,就是为不同的任务准备的“方言”。

3.2 RGB与BGR:谁在前谁在后?

RGB是标准的红绿蓝顺序。但OpenCV默认用的是BGR,也就是蓝绿红。我第一次用OpenCV读图,然后直接传给一个RGB格式的显示库,结果图像偏蓝得离谱。排查了半天,才发现是通道顺序搞反了。

注意: 在嵌入式GPU上,很多硬件加速器默认使用BGR格式。如果你从摄像头拿到的是RGB,记得做一次通道重排。

转换其实很简单:

// 伪代码:RGB ↔ BGR 转换
for (int i = 0; i < width * height; i++) {
    unsigned char tmp = data[i * 3 + 0]; // R
    data[i * 3 + 0] = data[i * 3 + 2];   // B
    data[i * 3 + 2] = tmp;               // R
}

在GPU上,这个操作可以用一个简单的kernel并行完成。每个线程处理一个像素,效率极高。

3.3 YUV:视频领域的“老大哥”

YUV把亮度(Y)和色度(U、V)分开。人眼对亮度变化敏感,对颜色变化不敏感。所以视频压缩时,可以降低U、V的分辨率,这就是所谓的“色度子采样”。

常见的格式有:

格式 描述 常见场景
YUV420 每4个Y共享一组UV H.264/H.265视频编码
YUV422 每2个Y共享一组UV 专业视频采集
YUV444 每个像素都有独立的YUV 高质量图像处理

YUV转RGB的公式,我建议你直接背下来:

R = Y + 1.402 * (V - 128)
G = Y - 0.344 * (U - 128) - 0.714 * (V - 128)
B = Y + 1.772 * (U - 128)
我的经验: 在GPU上做YUV转RGB时,建议把浮点系数转成定点数,比如乘以1024再右移10位。这样能避免浮点运算带来的性能损失。

3.4 HSV:做图像分割的利器

HSV把颜色分解为色调(Hue)、饱和度(Saturation)和明度(Value)。

为什么说它适合做分割?因为你可以直接根据H值来筛选颜色范围。比如要检测红色物体,只需要判断H在0~10度或340~360度之间即可。这在RGB空间里很难做到,因为红色在RGB中可能是(255,0,0),也可能是(200,50,30)。

转换公式稍微复杂一点:

// RGB → HSV 核心逻辑
float r, g, b; // 归一化到[0,1]
float max = max(r, g, b);
float min = min(r, g, b);
float delta = max - min;

// 计算H
if (delta == 0) H = 0;
else if (max == r) H = 60 * ((g - b) / delta + 0);
else if (max == g) H = 60 * ((b - r) / delta + 2);
else H = 60 * ((r - g) / delta + 4);
if (H < 0) H += 360;

// 计算S
S = (max == 0) ? 0 : delta / max;

// 计算V
V = max;

在GPU上,这个转换需要处理分支语句。我建议用条件赋值代替if-else,避免线程束发散。

3.5 灰度图:最简单的降维

灰度图就是把彩色图像变成单通道的亮度图。常用的加权公式是:

Gray = 0.299 * R + 0.587 * G + 0.114 * B

为什么权重不一样?因为人眼对绿色最敏感,对蓝色最不敏感。这个权重是ITU-R BT.601标准规定的。

GPU加速思路: 灰度转换是典型的“每个像素独立计算”的任务。在GPU上,每个线程处理一个像素,直接读取RGB值,计算加权和,写入灰度图。带宽利用率很高。

3.6 色彩空间转换的GPU加速思路

好了,前面讲了各种转换的原理。现在聊聊怎么在GPU上加速。

核心思路就一句话:每个像素独立计算,并行执行

具体来说:

  1. 数据布局:确保输入输出数据在显存中连续排列。比如RGB格式,最好用平面格式(Planar)而不是交错格式(Interleaved),这样GPU的缓存命中率更高。
  2. 线程映射:每个线程处理一个像素。对于大图像,可以用二维线程块,每个块处理一个tile。
  3. 常量内存:把转换系数(比如YUV转RGB的矩阵)放到常量内存中,所有线程共享,减少寄存器压力。
  4. 向量化加载:用float4或uchar4一次加载4个像素,提高内存带宽利用率。

我曾经在一个项目中,把YUV420转RGB的CPU实现改成GPU实现,速度提升了30倍。关键就在于用向量化加载和共享内存来减少全局内存访问次数。

避坑指南: 我曾经在GPU上做HSV转RGB时,发现结果总是不对。后来发现是因为H值在[0,360]范围,而GPU的三角函数对角度范围有要求。解决办法是把H归一化到[0,1]再计算。

3.7 总结与建议

色彩空间转换是图像处理流水线的“第一关”。我的建议是:

  • 先搞清楚你的数据来源是什么格式(摄像头、视频文件、网络流)
  • 再确定目标格式(显示、编码、分析)
  • 最后选择合适的转换算法和加速策略

在嵌入式GPU上,内存带宽往往是瓶颈。所以尽量用定点数代替浮点数,用向量化加载代替标量加载。这些细节,决定了你的流水线能不能跑满帧率。

下一章,我们会深入聊聊图像滤波与卷积操作。到时候你会发现,色彩空间转换只是热身运动。