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空间里调三个通道的阈值要简单得多?
嗯,色彩空间转换就讲到这里。下一章我们会聊图像滤波,到时候你会看到,很多滤波操作其实也跟色彩空间的选择有关。