2、色彩空间转换:BGR与RGB、灰度图、HSV、LAB色彩空间,cvtColor函数应用

色彩空间转换,说白了就是让计算机用不同的方式去理解颜色。我刚开始接触OpenCV时,最常犯的一个错误就是——用OpenCV读进来的图片,直接拿去给matplotlib显示,结果颜色全乱了。嗯,这就是BGR和RGB的坑。

2.1 BGR与RGB:OpenCV的默认“脾气”

你想想看,我们平时用手机、用浏览器看到的图片,都是RGB格式——红绿蓝三个通道。但OpenCV偏不,它默认用的是BGR顺序。为什么?我查过资料,据说是因为早期一些相机的硬件厂商喜欢用BGR排列,OpenCV为了兼容性就沿用了这个习惯。

所以,当你用cv2.imread()读入一张彩色图片时,它实际上是以BGR顺序存储的。如果你直接用matplotlib的imshow()显示,蓝色通道会被当成红色显示,结果就是整张图偏蓝偏暗。

核心要点:OpenCV默认色彩通道顺序是BGR,不是RGB。

转换方法很简单:

import cv2

# 读取图片(默认BGR)
img_bgr = cv2.imread('example.jpg')

# 方法一:使用cvtColor转换
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)

# 方法二:手动翻转通道(不推荐,容易出错)
b, g, r = cv2.split(img_bgr)
img_rgb_manual = cv2.merge([r, g, b])

我的习惯:我一般会在读取图片后立刻转成RGB,这样后续所有处理都统一用RGB,避免混淆。尤其是在做深度学习数据预处理时,这个习惯帮我省了不少调试时间。

2.2 灰度图:丢掉颜色,保留结构

灰度图,说白了就是只有黑白灰的图片。每个像素用一个数值表示亮度,范围0(黑)到255(白)。

为什么要转灰度?我在做边缘检测、轮廓提取时,几乎都会先转灰度。因为颜色信息对形状分析来说往往是噪声,去掉颜色反而让算法更专注。

# BGR转灰度
gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)

# 或者直接读取时指定灰度模式
gray_direct = cv2.imread('example.jpg', cv2.IMREAD_GRAYSCALE)

灰度转换的公式其实很简单:Gray = 0.299 * R + 0.587 * G + 0.114 * B。人眼对绿色最敏感,所以绿色权重最高。这个权重是心理学实验测出来的,不是随便定的。

注意:如果你用cv2.imread()直接读成灰度图,那后续想再转回彩色是不可能的——信息已经丢失了。所以,如果你既需要彩色又需要灰度,建议先读彩色,再转灰度。

2.3 HSV色彩空间:更符合人类直觉

RGB虽然直观,但有个问题——它不适合做颜色分割。比如你想提取红色区域,在RGB空间里,红色可能是(255,0,0),但暗红色可能是(100,0,0),亮红色可能是(255,50,50),阈值很难定。

HSV就解决了这个问题。它把颜色分解成三个分量:

  • H(Hue,色调):颜色的种类,用角度表示,0°是红色,120°是绿色,240°是蓝色
  • S(Saturation,饱和度):颜色的纯度,0是灰色,255是最鲜艳
  • V(Value,明度):颜色的亮度,0是黑色,255是最亮

我在做颜色识别项目时,几乎都用HSV。比如识别交通信号灯:

# BGR转HSV
hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)

# 定义红色的HSV范围(注意:OpenCV中H范围是0-179)
lower_red1 = np.array([0, 50, 50])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([170, 50, 50])
upper_red2 = np.array([180, 255, 255])

# 提取红色区域
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
mask = cv2.bitwise_or(mask1, mask2)

避坑指南:我曾经在HSV阈值上卡了整整一天。原因是OpenCV的H范围是0-179,而其他库(比如matplotlib)的H范围是0-360。如果你在网上查到HSV阈值,记得先确认它是哪个范围。我习惯用OpenCV的官方文档查阈值表。

为什么红色要定义两个范围?因为红色在HSV色环上跨越了0°和180°的边界。说白了,红色既在0°附近,也在180°附近,所以需要两个区间来覆盖。

2.4 LAB色彩空间:感知均匀

LAB色彩空间的设计理念是——让颜色差异与人眼感知的差异成正比。什么意思呢?比如在RGB空间里,两个颜色相差10,人眼可能觉得差别很大;但在另一个区域,相差10可能人眼根本看不出来。LAB就是为了解决这个问题。

LAB的三个分量:

  • L(亮度):从0(黑)到100(白)
  • A(绿到红):负值表示绿色,正值表示红色
  • B(蓝到黄):负值表示蓝色,正值表示黄色

我一般在做图像质量评估、颜色迁移时会用到LAB。比如计算两张图片的颜色相似度,在LAB空间算欧氏距离,结果更符合人眼感受。

# BGR转LAB
lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB)

# 分离通道
l, a, b = cv2.split(lab)

# 对亮度通道做直方图均衡化(只调整亮度,不改变颜色)
l_eq = cv2.equalizeHist(l)
lab_eq = cv2.merge([l_eq, a, b])

# 转回BGR显示
img_eq = cv2.cvtColor(lab_eq, cv2.COLOR_LAB2BGR)

我的经验:LAB空间做图像增强特别好用。因为你可以只调整L通道(亮度),而不影响颜色。相比之下,在RGB空间做亮度调整,很容易出现颜色偏移。我做过一个夜间图像增强的项目,就是用LAB空间处理的。

2.5 cvtColor函数详解

cv2.cvtColor()是OpenCV里最常用的色彩空间转换函数。它的用法很简单:

dst = cv2.cvtColor(src, code)

# src: 输入图像
# code: 转换类型,比如cv2.COLOR_BGR2RGB

常用的转换类型:

转换类型 说明 应用场景
COLOR_BGR2RGB BGR转RGB matplotlib显示、深度学习预处理
COLOR_BGR2GRAY BGR转灰度 边缘检测、轮廓提取
COLOR_BGR2HSV BGR转HSV 颜色分割、目标跟踪
COLOR_BGR2LAB BGR转LAB 图像增强、颜色迁移
COLOR_GRAY2BGR 灰度转BGR 将灰度图转为三通道(但颜色信息已丢失)

重要提醒:转换是有损的。比如从BGR转到灰度,再转回BGR,颜色信息已经永久丢失了。所以,如果你需要保留原始颜色,一定要在转换前备份一份。

另外,cvtColor支持超过150种转换类型。你可以在OpenCV文档里查到完整列表。但我个人常用的也就上面这几种,其他的说实话很少用到。

2.6 实战:颜色识别小例子

最后,我分享一个我在项目中用过的颜色识别流程。这个流程帮我快速定位图片中的特定颜色区域:

import cv2
import numpy as np

def detect_color(image, lower_bound, upper_bound):
    """
    检测指定颜色范围
    lower_bound, upper_bound: HSV范围
    """
    # 1. 转HSV
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    
    # 2. 创建掩码
    mask = cv2.inRange(hsv, lower_bound, upper_bound)
    
    # 3. 形态学操作,去除噪声
    kernel = np.ones((5,5), np.uint8)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
    
    # 4. 在原图上标记
    result = cv2.bitwise_and(image, image, mask=mask)
    
    return mask, result

# 使用示例
img = cv2.imread('test.jpg')
# 检测蓝色(HSV范围)
lower_blue = np.array([100, 50, 50])
upper_blue = np.array([130, 255, 255])
mask, result = detect_color(img, lower_blue, upper_blue)

这个流程我用了很多年,基本没出过问题。核心思路就是:先转HSV,再用inRange做阈值分割,最后用形态学操作清理噪声。你想想看,是不是比在RGB空间里调三个通道的阈值要简单得多?

嗯,色彩空间转换就讲到这里。下一章我们会聊图像滤波,到时候你会看到,很多滤波操作其实也跟色彩空间的选择有关。