3、色彩空间与通道操作:BGR与RGB转换、灰度图、HSV色彩空间、通道拆分与合并
说到色彩空间,我刚开始接触OpenCV时就被坑过一次。那时候我以为OpenCV读进来的图片就是RGB,结果用matplotlib一显示,颜色全乱了。后来才明白——OpenCV默认用的是BGR,不是我们熟悉的RGB。这个坑,几乎每个初学者都会踩一次。
3.1 BGR与RGB:OpenCV的“反直觉”设计
你想想看,我们平时在屏幕上看到的颜色,红绿蓝(RGB)是标准。但OpenCV偏偏反着来,用蓝绿红(BGR)。为什么?
其实这跟历史有关。早期的一些图像采集设备和显示硬件,底层存储顺序就是BGR。OpenCV为了兼容这些硬件,就沿用了这个习惯。说白了,就是个历史遗留问题。
那怎么转换?很简单:
import cv2
import matplotlib.pyplot as plt
# 读取图片,默认是BGR
img_bgr = cv2.imread('example.jpg')
# 方法一:用cv2.cvtColor转换
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
# 方法二:手动翻转通道(不推荐,容易出错)
# img_rgb = img_bgr[:, :, ::-1]
# 显示对比
plt.subplot(1, 2, 1)
plt.imshow(img_bgr) # 颜色会偏蓝
plt.title('BGR (直接显示)')
plt.subplot(1, 2, 2)
plt.imshow(img_rgb) # 颜色正常
plt.title('RGB (转换后)')
plt.show()
3.2 灰度图:丢掉颜色,保留结构
灰度图就是把彩色图像变成黑白。每个像素只有一个值,范围0(黑)到255(白)。
为什么要用灰度图?
- 计算量小:一个通道 vs 三个通道,速度差三倍
- 很多算法只认灰度:比如边缘检测、直方图均衡化,都是基于灰度设计的
- 去除颜色干扰:有些场景下颜色反而是噪声,比如文字识别
# 转灰度图
img_gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
# 或者直接读成灰度
# img_gray = cv2.imread('example.jpg', cv2.IMREAD_GRAYSCALE)
print(f'原图尺寸: {img_bgr.shape}') # (H, W, 3)
print(f'灰度图尺寸: {img_gray.shape}') # (H, W)
3.3 HSV色彩空间:更符合人眼直觉
RGB虽然直观,但有个问题——它把颜色和亮度混在一起了。你想调亮一点,结果颜色也跟着变了。
HSV就解决了这个问题。它把颜色拆成三个分量:
- H(Hue,色调):0-180,代表颜色种类,比如红色、绿色
- S(Saturation,饱和度):0-255,颜色鲜艳程度
- V(Value,明度):0-255,亮度
我在做颜色识别项目时,HSV简直是神器。比如识别红色交通灯,用RGB你得考虑R通道高、G和B通道低,但光照一变就失效。用HSV,直接锁定H在0-10和170-180之间,S和V设个阈值,稳得很。
# BGR转HSV
img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
# 提取红色区域(红色在HSV中分布在0-10和170-180)
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(img_hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(img_hsv, lower_red2, upper_red2)
mask = cv2.bitwise_or(mask1, mask2)
# 提取红色区域
result = cv2.bitwise_and(img_bgr, img_bgr, mask=mask)
3.4 通道拆分与合并:玩转颜色分量
有时候我们需要单独处理某个颜色通道。比如想增强蓝色,或者把红色通道置零。这时候就要用到通道拆分。
# 方法一:用cv2.split(推荐)
b, g, r = cv2.split(img_bgr)
# 方法二:用numpy索引(速度更快)
# b = img_bgr[:, :, 0]
# g = img_bgr[:, :, 1]
# r = img_bgr[:, :, 2]
# 修改通道:把红色通道置零
img_no_red = img_bgr.copy()
img_no_red[:, :, 2] = 0 # 红色通道归零
# 合并通道
img_merged = cv2.merge([b, g, r])
你可能会问:用split还是用numpy索引?
我个人习惯:小项目用split,代码更清晰;大项目用numpy索引,速度快。cv2.split会创建三个新的数组,内存开销大。numpy索引只是视图,不复制数据。
3.5 实战:颜色提取与替换
来,我们做个综合练习。把图片中的蓝色物体变成红色。
import cv2
import numpy as np
# 读取图片
img = cv2.imread('objects.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 定义蓝色范围(HSV)
lower_blue = np.array([100, 50, 50])
upper_blue = np.array([130, 255, 255])
# 创建掩码
mask = cv2.inRange(hsv, lower_blue, upper_blue)
# 把蓝色区域变成红色
img_result = img.copy()
img_result[mask > 0] = [0, 0, 255] # BGR格式,红色是(0,0,255)
# 显示结果
cv2.imshow('Original', img)
cv2.imshow('Result', img_result)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.6 本章总结
色彩空间和通道操作,是图像处理的基础中的基础。记住几个要点:
- OpenCV默认BGR,显示前记得转RGB
- 灰度图是二维数组,不是三维
- HSV适合做颜色识别,H范围0-180
- 通道拆分用numpy索引更快,用split更清晰
嗯,这些概念看起来简单,但实际项目中处处都用得到。下一章我们会讲图像滤波,到时候这些色彩空间的知识还会用到。先消化好今天的内容,动手敲一遍代码,比看十遍都管用。