第二讲:图像基础与色彩空间——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,问题迎刃而解。

核心要点:RGB 适合显示,不适合做颜色分析。

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 值基本不变。这就很香了。

小技巧:在 OpenCV 中,H 的范围是 0-180,不是 0-360。这是为了用 uint8 存储。别搞错了,否则颜色会乱套。

3. 灰度图——丢掉颜色,保留结构

灰度图只有一个通道,值 0-255,0 是黑,255 是白。

为什么要用灰度图?

  • 计算量小——一个通道 vs 三个通道,速度差三倍。
  • 很多算法只关心亮度信息,比如边缘检测、特征匹配。
  • 减少干扰——颜色信息有时是噪声。

我记得有一次做工业缺陷检测,产品表面有轻微划痕。用 RGB 图像做边缘检测,结果被颜色纹理干扰得一塌糊涂。转成灰度图后,划痕清晰可见。嗯,这就是灰度图的威力。

注意:灰度图不是简单的“取平均”。OpenCV 用的是加权公式:Gray = 0.299*R + 0.587*G + 0.114*B。因为人眼对绿色最敏感,对蓝色最不敏感。

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 都是红色。所以红色分布在两端。我第一次写这个代码时,只定义了一个范围,结果红色只提取了一半。后来查资料才明白这个环形特性。

避坑指南:我曾经用 HSV 做肤色检测,发现不同光照下 H 值变化不大,但 S 和 V 变化很大。所以做颜色识别时,可以适当放宽 S 和 V 的范围,只严格限制 H。

7. 总结一下

这一讲我们聊了三个核心概念:

  • RGB:适合显示,不适合分析。
  • HSV:适合颜色识别,对光照鲁棒。
  • 灰度图:丢掉颜色,保留结构,计算快。

以及最重要的工具——cv2.cvtColor()。记住 BGR 是 OpenCV 的默认格式,别搞混了。

下一讲,我们会深入图像预处理的核心操作——滤波与去噪。到时候我会分享一个我踩过的坑,关于中值滤波和椒盐噪声的。敬请期待。

课后练习:找一张包含绿色物体的图片,用 HSV 空间提取绿色区域。提示:绿色的 H 值大约在 40-80 之间。