第二讲:图像基础与色彩空间——RGB、HSV、灰度图,以及 cv2.cvtColor 的实战用法
各位同学,欢迎来到第二讲。
上一讲我们聊了图像的本质——像素矩阵。今天咱们深入一步,聊聊色彩空间。说白了,就是计算机怎么“理解”颜色。
你可能会想:“颜色不就是红绿蓝吗?”嗯,对,也不全对。RGB 只是其中一种表达方式。在实际项目中,我经常遇到 RGB 搞不定的事情——比如做颜色识别,或者图像增强。这时候,HSV 和灰度图就派上用场了。
1. RGB 色彩空间——最熟悉的陌生人
RGB 是 Red、Green、Blue 的缩写。每个像素由三个通道组成,取值范围 0-255。显示器、摄像头、手机屏幕,底层都是 RGB。
但说实话,RGB 并不“直观”。
举个例子:你想把一张图片里的红色物体提取出来。在 RGB 空间里,红色是 (255,0,0),但实际拍摄时,光照一变,红色可能变成 (200,50,30)。你很难用一个简单的阈值把它框出来。
我在项目中遇到过类似问题——做交通标志识别,红色圆牌在阴影下变成了暗红色,RGB 阈值怎么调都不准。后来换了 HSV,问题迎刃而解。
2. HSV 色彩空间——更接近人眼的直觉
HSV 代表 Hue(色调)、Saturation(饱和度)、Value(明度)。
你想想看,人眼描述颜色时,会说“这个红色偏橙,饱和度不高,有点暗”。这不就是 HSV 吗?
- H(色调):0-180(OpenCV 里是 0-180,不是 0-360)。0 是红色,30 是橙色,60 是黄色,90 是绿色,120 是青色,150 是蓝色。
- S(饱和度):0-255。数值越大,颜色越纯。
- V(明度):0-255。数值越大,越亮。
我个人习惯在做颜色识别时,先用 HSV 空间。因为 H 通道对光照变化不敏感——你调亮或调暗,H 值基本不变。这就很香了。
3. 灰度图——丢掉颜色,保留结构
灰度图只有一个通道,值 0-255,0 是黑,255 是白。
为什么要用灰度图?
- 计算量小——一个通道 vs 三个通道,速度差三倍。
- 很多算法只关心亮度信息,比如边缘检测、特征匹配。
- 减少干扰——颜色信息有时是噪声。
我记得有一次做工业缺陷检测,产品表面有轻微划痕。用 RGB 图像做边缘检测,结果被颜色纹理干扰得一塌糊涂。转成灰度图后,划痕清晰可见。嗯,这就是灰度图的威力。
4. 实战:cv2.cvtColor 的使用
OpenCV 提供了 cv2.cvtColor() 函数,专门做色彩空间转换。用法很简单:
import cv2
# 读取图像(默认是 BGR 格式,不是 RGB!)
img = cv2.imread('example.jpg')
# BGR 转灰度
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# BGR 转 HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# BGR 转 RGB(如果需要显示或保存)
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
这里有个坑,我必须提醒你:OpenCV 默认读取的图像是 BGR 格式,不是 RGB。很多新手直接拿 OpenCV 读图,然后用 matplotlib 显示,结果颜色全偏蓝——因为 matplotlib 期望的是 RGB。
我曾经在这个坑里摔过一次。调试了半天,最后发现是通道顺序的问题。从那以后,我每次用 OpenCV 读图,都会确认一下通道顺序。
5. 常用转换对照表
| 转换类型 | OpenCV 标志 | 应用场景 |
|---|---|---|
| BGR → 灰度 | COLOR_BGR2GRAY | 边缘检测、特征提取 |
| BGR → HSV | COLOR_BGR2HSV | 颜色识别、颜色分割 |
| BGR → RGB | COLOR_BGR2RGB | 与 matplotlib 等库交互 |
| 灰度 → BGR | COLOR_GRAY2BGR | 需要三通道输入的算法 |
| HSV → BGR | COLOR_HSV2BGR | 处理完 HSV 后转回显示 |
6. 一个完整的例子:提取红色物体
咱们来写个小程序,把图像中的红色区域提取出来。这是 HSV 的经典应用。
import cv2
import numpy as np
# 读取图像
img = cv2.imread('traffic_sign.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 定义红色的 HSV 范围(注意:红色在 H 通道两端)
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)
# 提取红色区域
result = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow('Original', img)
cv2.imshow('Mask', mask)
cv2.imshow('Result', result)
cv2.waitKey(0)
为什么红色要定义两个范围?因为 HSV 的 H 通道是环形的,0 和 180 都是红色。所以红色分布在两端。我第一次写这个代码时,只定义了一个范围,结果红色只提取了一半。后来查资料才明白这个环形特性。
7. 总结一下
这一讲我们聊了三个核心概念:
- RGB:适合显示,不适合分析。
- HSV:适合颜色识别,对光照鲁棒。
- 灰度图:丢掉颜色,保留结构,计算快。
以及最重要的工具——cv2.cvtColor()。记住 BGR 是 OpenCV 的默认格式,别搞混了。
下一讲,我们会深入图像预处理的核心操作——滤波与去噪。到时候我会分享一个我踩过的坑,关于中值滤波和椒盐噪声的。敬请期待。