4、HSV色彩空间:色调(H)、饱和度(S)、明度(V)的含义与取值范围

讲完了RGB,咱们来聊聊HSV。说实话,RGB虽然直观,但在做图像处理时经常让人抓狂。你想想看,要调个颜色稍微亮一点,R、G、B三个值都得跟着变,特别麻烦。

HSV就是来解决这个问题的。它把颜色拆成了三个更符合人类直觉的维度:色调(Hue)、饱和度(Saturation)、明度(Value)。我个人习惯叫它「色相、纯度、亮度」,这样更好记。

4.1 色调(H)—— 颜色是什么

色调说白了就是「这是什么颜色」。红色、绿色、蓝色、紫色……这些都属于色调的范畴。

在OpenCV里,H的取值范围是 0 ~ 179。注意,不是0~360!这是很多新手踩的第一个坑。我记得第一次用OpenCV做颜色检测时,按网上教程设了H=350,结果啥也检测不到,折腾了半天才发现范围不对。

颜色 H值(OpenCV) H值(常规)
红色 0 ~ 10 或 170 ~ 179 0° ~ 20° 或 340° ~ 360°
橙色 11 ~ 25 21° ~ 50°
黄色 26 ~ 34 51° ~ 68°
绿色 35 ~ 77 69° ~ 154°
青色 78 ~ 99 155° ~ 198°
蓝色 100 ~ 124 199° ~ 248°
紫色 125 ~ 155 249° ~ 310°
粉红/品红 156 ~ 179 311° ~ 360°
⚠️ 重要提醒: 红色在H通道上是个「环」,它既在0附近,也在179附近。做红色检测时,通常需要取两个区间合并处理。我曾经在项目里只取了H<10,结果暗红色全漏了,后来才发现红色区域要分段处理。

4.2 饱和度(S)—— 颜色有多纯

饱和度描述的是颜色的「鲜艳程度」。饱和度越高,颜色越纯、越浓烈;饱和度越低,颜色越灰、越接近黑白。

在OpenCV中,S的取值范围是 0 ~ 255

  • S = 0:纯灰色,完全失去色彩
  • S = 255:最纯的颜色,比如纯红、纯蓝
  • S 在中间值:比如S=128,就是半鲜艳的颜色

举个例子:同样是红色,H=0, S=255, V=255 是刺眼的大红色;而 H=0, S=50, V=255 就是那种发白的粉红色。嗯,这里要注意,饱和度低不等于颜色暗,它只是「不纯」。

💡 实战技巧: 做肤色检测时,我一般会把S的下限设在30~40左右。因为肤色通常饱和度不高,但也不是完全灰色。设得太低会把背景也识别进来,设得太高又会漏掉真实肤色。

4.3 明度(V)—— 颜色有多亮

明度就是颜色的「亮度」。V值越高,颜色越亮;V值越低,颜色越暗,最终变成黑色。

在OpenCV中,V的取值范围也是 0 ~ 255

  • V = 0:纯黑色,不管H和S是什么,看起来都是黑的
  • V = 255:最亮的状态
  • V 在中间值:比如V=128,就是中等亮度的颜色

这里有个容易混淆的点:明度(V)和亮度(Lightness)不是一回事。HSV的V是取RGB三个通道的最大值,而HSL的L是取平均值。所以HSV的V对高光更敏感,做高光检测时用HSV的V通道效果更好。

🔑 核心要点:
  • H(0~179):决定颜色种类,红色在两端
  • S(0~255):决定颜色纯度,0为灰色,255为纯色
  • V(0~255):决定颜色明暗,0为黑色,255为最亮

4.4 为什么用HSV而不是RGB?

这个问题我经常被问到。说白了,RGB的三个通道都跟亮度强相关,你调任何一个值,颜色的「感觉」都会变。而HSV把「颜色是什么」「颜色有多纯」「颜色有多亮」分开了,互不干扰。

举个例子:你想把一张图片里所有红色物体找出来。

  • 用RGB:你得判断R > G 且 R > B,还得考虑光照变化,阈值很难定
  • 用HSV:直接限定H在0~10或170~179,S和V设个范围就行,光照变化主要影响V,不影响H

我在做交通标志识别项目时,就是用HSV做颜色分割的。红色禁令标志、蓝色指示标志,用H通道一区分,准确率直接拉满。

4.5 代码示例:查看任意像素的HSV值

光说不练假把式。咱们写段代码,看看实际图片里每个像素的HSV值长什么样。

import cv2
import numpy as np

# 创建一个纯红色的图片
img = np.zeros((100, 100, 3), dtype=np.uint8)
img[:] = [0, 0, 255]  # BGR格式的纯红色

# 转成HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# 查看第一个像素的值
print("BGR:", img[0, 0])      # 输出: [0 0 255]
print("HSV:", hsv[0, 0])      # 输出: [  0 255 255]

# 再试试暗红色
img2 = np.zeros((100, 100, 3), dtype=np.uint8)
img2[:] = [0, 0, 128]  # 暗红色
hsv2 = cv2.cvtColor(img2, cv2.COLOR_BGR2HSV)
print("暗红色 HSV:", hsv2[0, 0])  # H不变,V变小了

看到了吗?纯红色和暗红色,H值都是0,区别只在V值上。这就是HSV的威力——色调不受光照影响

💡 个人习惯: 我写代码时,经常先用 cv2.imshow() 分别显示H、S、V三个通道的灰度图,看看哪个通道的对比度最明显。这样能快速确定用哪个通道做阈值分割。

4.6 避坑指南

最后分享几个我踩过的坑:

  • H范围别搞错:OpenCV的H是0~179,不是0~360。如果你从别的库迁移代码,记得除以2
  • 红色要分段:检测红色时,H<10 和 H>170 都要考虑,别漏了
  • S和V的阈值:S太低(<20)时颜色接近灰色,H值已经没意义了;V太低(<30)时接近黑色,H和S也基本失效
  • 数据类型:HSV和RGB一样,都是uint8类型,范围0~255。别想着用float,OpenCV默认不认

嗯,掌握了这些,HSV这块基本就稳了。下一节咱们用HSV做个实战项目——颜色物体追踪,到时候你就知道这玩意儿有多好用了。