本页内容受版权保护 · 已添加水印 · 禁止任何形式转载
§1.1.1

数字图像的像素、通道、位深、色彩空间(RGB/BGR/HSV/Lab/YUV/YCbCr)含义与转换?

手写练习
  • 用 OpenCV 把 BGR 图转 HSV 并按色相阈值分割红色物体

核心概念

  • 像素 (Pixel):Picture Element 的缩写,是数字图像中最基本的单位。每个像素代表了图像在特定坐标 (x, y) 上的颜色或强度信息。
  • 通道 (Channel):表示每个像素颜色信息的组成部分。例如,灰度图只有一个通道(亮度),而彩色图像通常有三个通道(如 Red, Green, Blue)。通道数决定了图像的色彩维度。
  • 位深 (Bit Depth):用于表示每个像素每个通道值的二进制位数。它决定了单个通道能表示的颜色/灰度级数量。例如,8 位位深意味着每个通道有 28=2562^8 = 256 个级别(通常从 0 到 255)。
  • 色彩空间 (Color Space):一个用于描述和表示颜色的数学模型。它定义了一套坐标系统,使得每种颜色都可以用一组数值来唯一表示。常见的色彩空间有 RGB、HSV、Lab 等,它们基于不同的设计理念(硬件友好、人类感知等)。

原理与推导

数字图像可以被看作一个三维矩阵 I(H,W,C)I(H, W, C),其中 HH 是高度, WW 是宽度, CC 是通道数。


1. RGB 与 BGR 色彩空间

  • 原理:基于三原色光加成(Additive Color Model)原理。任何颜色都可以通过混合不同比例的红 (Red)、绿 (Green)、蓝 (Blue) 光来产生。这是最常用、最基础的色彩空间,与显示器、摄像头等硬件设备的工作方式直接对应。
  • 数学表示:一个颜色可以表示为一个三维向量 (R,G,B)(R, G, B)。对于 8 位位深,R, G, B 的取值范围都是 [0,255][0, 255]
    • (0,0,0)(0, 0, 0) 代表黑色。
    • (255,255,255)(255, 255, 255) 代表白色。
    • (255,0,0)(255, 0, 0) 代表纯红色。
  • BGR:是 RGB 的一种变体,仅仅是通道的存储顺序不同,变为 (Blue, Green, Red)。这是 OpenCV 库默认的图像通道顺序,源于早期相机制造商的硬件标准。
  • 几何解释:可以将 RGB 色彩空间想象成一个立方体,三个坐标轴分别是 R, G, B。立方体内的任意一点都对应一种颜色。

2. HSV (Hue, Saturation, Value) 色彩空间

  • 原理:相比于 RGB,HSV 更符合人类对颜色的直观描述:色调 (Hue)、饱和度 (Saturation)、明度 (Value)。它将颜色信息与亮度信息分离,在很多计算机视觉任务中非常有用。
  • 直观解释
    • H (色调):颜色的基本属性,即“什么颜色”,如红色、黄色、蓝色。它被表示为一个角度,范围是 [0,360][0, 360^\circ]
    • S (饱和度):颜色的纯度,表示颜色中混入灰色的程度。范围是 [0,1][0, 1][0,100%][0, 100\%]。饱和度为 0 时,颜色变为灰色。
    • V (明度):颜色的明亮程度。范围是 [0,1][0, 1][0,100%][0, 100\%]。明度为 0 时,无论 H 和 S 是什么,颜色都是黑色。
  • 几何解释:HSV 空间可以被想象成一个倒置的圆锥体或圆柱体。H 是绕着中心轴的角度,S 是到中心轴的距离(半径),V 是沿中心轴的高度。
  • 从 RGB 到 HSV 的推导(以 R,G,B 值在 [0,1][0, 1] 范围内为例):
    1. 首先计算最大值和最小值: M=max(R,G,B)M = \max(R, G, B) m=min(R,G,B)m = \min(R, G, B)
    2. 明度 (Value) V 直接等于最大值: V=MV = M
    3. 饱和度 (Saturation) S 的计算: S={MmMif M00if M=0S = \begin{cases} \frac{M - m}{M} & \text{if } M \neq 0 \\ 0 & \text{if } M = 0 \end{cases}M=0M=0 时,图像为纯黑,饱和度无意义,定义为 0。
    4. 色调 (Hue) H 的计算(结果为角度): H={undefinedif M=m (灰度)60×(GBMm)if M=R60×(BRMm+2)if M=G60×(RGMm+4)if M=BH = \begin{cases} \text{undefined} & \text{if } M = m \text{ (灰度)} \\ 60^\circ \times \left( \frac{G - B}{M - m} \right) & \text{if } M = R \\ 60^\circ \times \left( \frac{B - R}{M - m} + 2 \right) & \text{if } M = G \\ 60^\circ \times \left( \frac{R - G}{M - m} + 4 \right) & \text{if } M = B \end{cases} 如果计算出的 H 为负数,则加上 360360^\circ 使其落在 [0,360)[0, 360^\circ) 范围内。

3. YUV 与 YCbCr 色彩空间

  • 原理:同样是将亮度信息与色度信息分离的色彩空间。其核心动机源于人类视觉系统(Human Visual System, HVS)对亮度的敏感度远高于对色度的敏感度。这使得在视频压缩和传输中,可以对色度信息进行“降采样”(Chroma Subsampling),在人眼几乎无法察觉差异的情况下,大幅度减少数据量。
  • YUV vs YCbCr
    • YUV:主要用于模拟信号领域(如 PAL, NTSC 电视系统)。
    • YCbCr:是 YUV 的数字版本,用于数字视频和图像压缩(如 JPEG, MPEG)。在实践中,人们常混用这两个术语,但计算机视觉中打交道的几乎都是 YCbCr。
  • 分量解释
    • Y:Luma,亮度分量,是 R, G, B 的加权平均值。
    • Cb (或 U):Chrominance-blue,蓝色分量与亮度值的差。
    • Cr (或 V):Chrominance-red,红色分量与亮度值的差。
  • 从 RGB 到 YCbCr 的推导(基于 BT.601 标准,R,G,B 范围 [0,255][0, 255]): Y=0.299R+0.587G+0.114BY' = 0.299 R' + 0.587 G' + 0.114 B' Cb=0.1687R0.3313G+0.5B+128Cb = -0.1687 R' - 0.3313 G' + 0.5 B' + 128 Cr=0.5R0.4187G0.0813B+128Cr = 0.5 R' - 0.4187 G' - 0.0813 B' + 128 这里的 R,G,BR', G', B' 是经过 Gamma 校正的值,在 8-bit sRGB 中就是我们通常用的 0-255 的值。加上 128 是为了使 Cb, Cr 的值偏移到正数范围。

4. Lab (CIELAB) 色彩空间

  • 原理:Lab 空间被设计为感知均匀 (Perceptually Uniform) 的。这意味着在 Lab 空间中,两个颜色之间的欧氏距离与人眼感知到的颜色差异大致成正比。这使得它在衡量颜色相似度、色彩迁移等任务中非常强大。
  • 分量解释
    • L*:Lightness,亮度。从 0 (黑) 到 100 (白)。
    • a*:表示颜色在红-绿轴上的位置。负值偏绿,正值偏红。
    • b*:表示颜色在黄-蓝轴上的位置。负值偏蓝,正值偏黄。
  • 推导:Lab 的转换比较复杂,通常需要先从 RGB 转换到 XYZ 空间(一个标准的、与设备无关的色彩空间),再从 XYZ 转换到 Lab。
    1. RGB -> XYZ: 这是一个线性变换,由一个 3×33 \times 3 的矩阵完成。
    2. XYZ -> Lab: 这是一个非线性变换。 L=116f(Y/Yn)16L^* = 116 f(Y/Y_n) - 16 a=500[f(X/Xn)f(Y/Yn)]a^* = 500 [f(X/X_n) - f(Y/Y_n)] b=200[f(Y/Yn)f(Z/Zn)]b^* = 200 [f(Y/Y_n) - f(Z/Z_n)] 其中 Xn,Yn,ZnX_n, Y_n, Z_n 是参考白点(通常是 D65 光源)的 XYZ 值。函数 f(t)f(t) 定义为: f(t)={t1/3if t>(6/29)313(296)2t+429otherwisef(t) = \begin{cases} t^{1/3} & \text{if } t > (6/29)^3 \\ \frac{1}{3} (\frac{29}{6})^2 t + \frac{4}{29} & \text{otherwise} \end{cases} 这个非线性变换是为了匹配人眼对亮度的非线性感知。

代码实现

以下代码使用 OpenCV 将一张 BGR 图像转换为 HSV,并根据色相(Hue)阈值分割出图像中的红色物体。

python
1import cv2
2import numpy as np
3
4def segment_red_object():
5 """
6 创建一个带有红色圆形的图像,将其从 BGR 转换为 HSV,
7 并根据 HSV 阈值分割出红色部分。
8 """
9 # 1. 创建一个示例图像
10 # 创建一个 300x500 的蓝色背景图像 (BGR格式)
11 height, width = 300, 500
12 # np.uint8 是图像数据最常用的类型,表示 0-255 的无符号整数
13 image_bgr = np.full((height, width, 3), (255, 0, 0), dtype=np.uint8)
14 # 在图像中心画一个红色的实心圆
15 center_x, center_y = width // 2, height // 2
16 radius = 80
17 cv2.circle(image_bgr, (center_x, center_y), radius, (0, 0, 255), -1) # BGR 红色是 (0, 0, 255)
18
19 # 2. 将图像从 BGR 转换到 HSV 色彩空间
20 # 为什么这样做:HSV 空间中的 H (色调) 分量对光照变化不敏感,
21 # 使用 H 分量可以更稳定地识别特定颜色,而不用关心是深红还是亮红。
22 image_hsv = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2HSV)
23
24 # 3. 定义红色的 HSV 阈值范围
25 # 为什么需要两个范围:红色在 HSV 的 H 通道中位于 0 度附近,它环绕了 0/360 度的边界。
26 # OpenCV 中 H 的范围是 [0, 179] (为了适应 uint8 类型),所以红色对应 0-10 和 170-179 两个区域。
27 # S(饱和度) 和 V(明度) 的阈值也需要设置,以过滤掉过暗、过亮或褪色的区域。
28 lower_red1 = np.array([0, 70, 50])
29 upper_red1 = np.array([10, 255, 255])
30
31 lower_red2 = np.array([170, 70, 50])
32 upper_red2 = np.array([179, 255, 255])
33
34 # 4. 根据阈值创建掩码 (mask)
35 # 为什么这样做:cv2.inRange 会检查 HSV 图像中的每个像素是否在指定的阈值范围内。
36 # 如果在,掩码的对应位置为 255 (白色),否则为 0 (黑色)。
37 mask1 = cv2.inRange(image_hsv, lower_red1, upper_red1)
38 mask2 = cv2.inRange(image_hsv, lower_red2, upper_red2)
39
40 # 5. 合并两个掩码
41 # 为什么这样做:因为红色被分成了两个区域,所以需要将两个区域的掩码合并成一个。
42 # bitwise_or 操作可以将两个掩码中任意一个为白色的区域都变成白色。
43 final_mask = cv2.bitwise_or(mask1, mask2)
44
45 # 6. 使用掩码从原图中提取红色物体
46 # 为什么这样做:bitwise_and 操作会将掩码和原图进行逐像素的与操作。
47 # 只有当掩码的像素值为 255 (即我们找到的红色区域) 时,才会保留原图的颜色,否则变为黑色。
48 result = cv2.bitwise_and(image_bgr, image_bgr, mask=final_mask)
49
50 # 7. 显示结果 (在本地运行时取消注释)
51 # cv2.imshow("Original BGR Image", image_bgr)
52 # cv2.imshow("HSV Image", image_hsv)
53 # cv2.imshow("Red Mask", final_mask)
54 # cv2.imshow("Segmented Red Object", result)
55 # cv2.waitKey(0)
56 # cv2.destroyAllWindows()
57
58 # 为了让函数可测试,我们返回结果
59 print("代码执行成功,已生成原图、掩码和分割结果。")
60 return image_bgr, final_mask, result
61
62if __name__ == '__main__':
63 segment_red_object()

工程实践

  • 数据准备与色彩空间选择
    • RGB/BGR:是大多数深度学习模型的默认输入格式。模型会自己学习对颜色和光照变化的适应性。除非有特殊先验,否则直接使用即可。
    • HSV:在传统 CV 任务中,当需要根据特定颜色进行分割或跟踪时(如车道线检测、特定颜色物体跟踪),HSV 是首选。它的 H 通道对光照变化鲁棒。
    • YUV/YCbCr:主要用于视频处理领域。如果你的输入是视频流,解码器可能直接输出 YUV 格式,直接在此空间处理可以省去一次到 RGB 的转换,提升性能。
    • Lab:当任务对颜色差异的精确度量要求很高时使用,例如计算两张图片的颜色相似度(计算 Delta E 2000 色差)、工业质检中的颜色匹配、艺术风格迁移中的颜色传递等。
  • 训练 Tricks
    • 在深度学习中,我们很少将整个数据集预先转换到 HSV 等空间。更常见的做法是在数据增强(Data Augmentation)阶段,对 RGB 图像进行色彩抖动 (Color Jitter),即在 HSV 空间中随机改变 H, S, V 的值,再转回 RGB。这能让模型学习到对颜色和光照变化的鲁棒性。PyTorch 的 transforms.ColorJitter 就是这样实现的。
  • 超参数选择
    • 在使用 HSV 进行颜色分割时,lowerbupperb 阈值是关键的超参数。手动设定很困难。一个实用的方法是编写一个带有滑动条 (Trackbar) 的 GUI 界面,实时调整阈值并观察分割效果,从而找到最佳范围。
  • 部署与性能
    • cv2.cvtColor 是一个计算密集型操作,尤其对于高分辨率图像。在性能敏感的推理流程中,应尽量减少不必要的色彩空间转换。
    • 位深:大多数应用使用 8-bit uint8 图像。但在医疗(如 X 光片)、遥感、天文学等领域,会使用 16-bit uint16 或 32-bit float32 来表示更高的动态范围。处理这些数据时,必须注意数据类型匹配和归一化,否则会导致数值溢出或精度丢失。

常见误区与边界情况

  • RGB vs BGR 混淆:这是 OpenCV 新手最常犯的错误。cv2.imread() 读入的是 BGR,而 Matplotlib、PIL 等库默认处理 RGB。如果用 matplotlib.pyplot.imshow() 显示 cv2.imread() 的结果,颜色会很奇怪。正确做法是转换:img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)img_rgb = img_bgr[:, :, ::-1]
  • OpenCV 的 HSV 范围:为了将 H, S, V 都存入一个 uint8 类型的三通道图像,OpenCV 对 HSV 的范围做了调整:
    • H: 0-179 (而不是 0-359,所以真实角度要除以 2)
    • S: 0-255
    • V: 0-255 在设置阈值时必须使用 OpenCV 的范围,否则会出错。
  • HSV 的不确定性
    • 当饱和度 S 接近 0(灰度颜色)或明度 V 接近 0(黑色)时,色调 H 的值是不稳定或无意义的。例如,黑色的 H 是多少?这没有定义。
    • 因此,在做颜色分割时,除了设置 H 范围,通常也要设置 S 和 V 的下限(如 S > 50, V > 50)来排除这些灰度/黑色区域的干扰。
  • YUV 和 YCbCr 的混淆:在面试中,能清晰地区分 YUV(模拟)和 YCbCr(数字),并解释其设计初衷(利用人眼特性进行压缩),会是一个加分项。
  • 面试追问:为什么 HSV 在光照变化下比 RGB 更鲁棒?
    • 回答要点:光照强度的变化主要影响 RGB 三个通道的值等比例地增大或减小,这在 RGB 立方体中表现为点沿着原点方向的向量移动。而在 HSV 空间中,这种变化主要体现在 V (明度) 分量上,而 H (色调) 和 S (饱和度) 相对稳定。因此,通过固定 H 和 S 的范围,我们可以在不同光照下稳定地识别同一种颜色。
  • 面试追问:什么场景下 Lab 空间是最佳选择?
    • 回答要点:当需要量化两种颜色之间的“看起来”有多大差异时。例如,评估一个图像超分辨率算法在色彩还原上做得好不好,不能简单地在 RGB 空间计算 MSE,因为这不符合人类感知。在 Lab 空间计算欧氏距离(即 CIEDE2000 等色差公式的基础)是更权威的评价指标。
相关题目