4. 色彩空间

色彩空间转换,说白了就是让计算机用不同的方式去理解颜色。我刚开始接触工业视觉时,总觉得这玩意儿不就是换个颜色看看嘛,后来才发现——选错色彩空间,算法直接报废。

举个例子。你在流水线上检测一个红色标签的位置。用BGR直接做阈值分割?光照一变,红色就飘了。换成HSV,只取H通道,光照再变,红色还是那个红色。这就是色彩空间的价值。

4.1 BGR与RGB:OpenCV的默认姿势

OpenCV读图默认是BGR顺序,不是RGB。这个坑我踩过不止一次。

⚠️ 注意: 用OpenCV读图,然后用matplotlib显示,颜色会偏蓝偏黄。因为matplotlib默认是RGB。必须做一次转换。
import cv2
import matplotlib.pyplot as plt

# 读图默认BGR
img_bgr = cv2.imread('sample.jpg')

# 转RGB用于显示
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)

plt.imshow(img_rgb)
plt.show()

工业场景里,BGR和RGB的转换主要发生在两个地方:

  • 与深度学习模型对接:很多预训练模型(比如ResNet、YOLO)训练时用的是RGB,推理前必须转。
  • 与上位机/显示器交互:HMI界面通常用RGB,传给OpenCV处理前要转回BGR。

我个人习惯在代码开头就统一色彩空间,避免后面到处转来转去。

4.2 灰度图:工业视觉的基石

灰度图只有亮度信息,没有颜色。你可能会问:颜色信息丢了,还能用吗?

能。而且很多场景下,灰度图比彩色图更稳定。

我记得有一次做手机中框划痕检测。彩色图下,划痕和背景的色差很小,阈值分割死活分不开。转成灰度图后,划痕的亮度突变反而更明显,一个简单的Canny边缘检测就搞定了。

# BGR转灰度
gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)

# 或者直接读灰度图
gray = cv2.imread('sample.jpg', cv2.IMREAD_GRAYSCALE)
💡 经验: 如果检测目标与背景的亮度差异大于颜色差异,优先用灰度图。处理速度快,内存占用小,算法也更简单。

灰度图在工业视觉中的典型应用:

  • 边缘检测、轮廓提取
  • 模板匹配(基于灰度值的相关性)
  • 二值化前的预处理
  • OCR文字识别(大部分OCR引擎用灰度图)

4.3 HSV:光照变化的克星

HSV把颜色拆成色调(H)、饱和度(S)、明度(V)。

为什么它在工业视觉里这么重要?因为H通道对光照变化不敏感。

你想想看,一个红色零件,在强光下是亮红,在弱光下是暗红。BGR空间里,这两个红色的RGB值差很远。但在HSV空间里,它们的H值几乎一样。

# BGR转HSV
hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)

# 提取红色区域(H在0-10和160-180之间)
lower_red1 = np.array([0, 50, 50])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([160, 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做颜色阈值分割时,H通道决定颜色种类,S和V通道控制容忍度。S太低(接近灰色)或V太低(太暗)时,H通道会不稳定,需要适当放宽S和V的范围。

我曾经做过一个项目,检测PCB板上不同颜色的电阻。车间灯光从上午到下午变化很大。用BGR做颜色分类,准确率只有70%。换成HSV后,准确率直接跳到95%以上。

HSV的工业应用场景:

  • 颜色分类与分拣
  • 彩色标签定位
  • 光照变化环境下的目标检测
  • 肤色检测(人机协作场景)

4.4 Lab:人眼感知的模拟器

Lab色彩空间的设计目标是模拟人眼对颜色的感知。L是亮度,a和b是颜色对立通道。

为什么工业视觉要用Lab?因为Lab空间里的欧氏距离,近似于人眼感知的颜色差异。

举个例子。你要检测两个蓝色瓶盖的颜色是否一致。在BGR空间里,两个蓝色可能距离很远,但人眼看差不多。在Lab空间里,它们的距离就和人眼判断一致。

# BGR转Lab
lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2Lab)

# 提取L通道(亮度)
L = lab[:,:,0]

# 计算两个颜色的Lab距离
color1 = np.array([50, 20, -30])  # Lab值
color2 = np.array([52, 18, -28])
distance = np.sqrt(np.sum((color1 - color2) ** 2))
⚠️ 注意: OpenCV的Lab范围与标准Lab不同。OpenCV中L范围0-255,a范围0-255(映射到-128到127),b范围0-255(映射到-128到127)。做阈值分割时要注意这个偏移。

Lab的典型工业应用:

  • 颜色一致性检测(化妆品、汽车漆面)
  • 色差测量(Delta E计算)
  • 印刷品质量检测
  • 食品成熟度判断

4.5 色彩空间选择决策框架

说了这么多,到底该用哪个?我整理了一个决策流程,你直接套用就行。

色彩空间选择决策流程 输入图像(BGR) 是否需要颜色信息? 否 灰度图 是 光照是否稳定? 不稳定 HSV 稳定 需要人眼感知一致性? 是 Lab 否 BGR/RGB 适用场景 边缘检测、模板匹配、二值化 适用场景 颜色分类、光照变化环境 色差检测、印刷品质检

4.6 各色彩空间对比总结

色彩空间 通道含义 光照鲁棒性 颜色区分度 计算速度 典型工业场景
BGR/RGB 红、绿、蓝三通道 低 中等 最快(无转换) 深度学习推理、显示
灰度 单通道亮度 中等 无(只有亮度) 最快 边缘检测、模板匹配
HSV 色调、饱和度、明度 高(H通道) 高 快 颜色分割、光照变化场景
Lab 亮度、绿-红、蓝-黄 中等 最高(感知均匀) 中等 色差检测、颜色一致性

4.7 实战避坑指南

最后分享几个我踩过的坑,你遇到了直接绕开。

💡 技巧: 做HSV阈值分割时,先用cv2.imshow()显示H、S、V三个单通道图像,观察目标在哪个通道最明显。我一般先调H通道,再微调S和V。
⚠️ 坑1: 我曾经直接用cv2.cvtColor()把BGR转成灰度,然后做二值化。结果发现有些区域死活分不开。后来检查发现,原图是8位深度,但某些像素值超出了0-255范围。转灰度前先检查数据类型和范围。
⚠️ 坑2: Lab空间的a和b通道在OpenCV中是0-255范围,但标准Lab是-128到127。如果你要计算Delta E色差,记得先做映射:a_std = a_opencv - 128。我当年没注意这个,算出来的色差全是错的。
⚠️ 坑3: HSV的H通道是0-180(不是0-360),因为OpenCV用uint8存储。做红色阈值时,红色在0-10和160-180两个区间,别忘了合并两个mask。

嗯,色彩空间这块就讲到这里。记住一句话:没有最好的色彩空间,只有最合适的。根据你的光照条件、检测目标和精度要求,选对了,算法就成功了一半。