常用色彩空间
在OpenCV中,正常读取的图像是BGR色彩空间,但面对不同的图像识别处理任务时,常常需要根据任务与环境需求,将图像转换为其他色彩空间。
常用的色彩空间有三种:RGB、HSV、LAB。
色彩空间介绍
RGB色彩空间
-
人类视觉会感知到红色、绿色、蓝色三原色,三种颜色通过加色混合可以产生其他不同的颜色。
-
三个颜色通道:
R通道、G通道、B通道。分别为红色、绿色、蓝色通道。
每个通道值的范围为[0,255]。
例如,纯白色的RGB值为(255,255,255);纯黑色的RGB值为(0,0,0);纯红色的RGB值为(255,0,0);纯绿色的RGB值为(0,255,0);纯蓝色的RGB值为(0,0,255)。
HSV色彩空间
-
人的眼睛在对色彩感知时,色调、饱和度、亮度会产生影响。这三要素即是HSV色彩空间的组成部分。
-
三个通道:
-
色调H(Hue):与光的波长有关,决定是什么颜色。
用角度度量,取值范围为0~360°,从红色开始按逆时针方向旋转。值范围为[0,360],在opencv中便于计算,压缩到[0,180]
-
-
饱和度S(Saturation):颜色的深浅和纯度,鲜艳与暗淡。值范围在[0,1],opencv中为了便于计算,扩展到[0,255]
-
亮度V(Value):颜色的亮与暗。值范围在[0,1],opencv中为了便于计算,扩展到[0,255]
LAB色彩空间
-
有时候也写成CIE L*a*b*,是均匀色彩空间模型,即人所观察到的两种颜色的区别程度,与这两种颜色在该色彩空间中对应的点之间的欧式距离成正比
-
三个通道分量:
-
L**分量:像素的亮度,取值范围是[0,100],表示从纯黑到纯白*
-
a*分量:从绿色到红色的范围,取值范围是[-127,127]。负轴为绿,正轴为红
-
b*分量:从蓝色到黄色的范围,取值范围是[-127,127]。负轴为蓝,正轴为黄
-
注意将LAB色彩空间中的图像通道分割后,若想进行进一步处理,需要将三个分量映射到对应范围
l, a, b = cv2.split(roi) # 将图像拆分为l、a、b三个通道 l = l / 256 * 100 a = (a / 256 - 0.5) * 255 # 将a、b通道值从[0,255]转换到[-127,127] b = (b / 256 - 0.5) * 255
-
通过直方图观察不同色彩空间下的图像的特点
基本函数
先要了解一些会用到的函数。
色彩空间转换
-
dst = cv2.cvtColor( src, code [, dstCn] )
返回值:dst:返回转换后的图像
参数:
-
src:源图像
-
code:色彩空间转换码。常用
cv2.COLOR_BGR2GRAY
、cv2.COLOR_BGR2HSV
、cv2.COLOR_BGR2RGB
等,表示从什么色彩空间转换到什么色彩空间 -
dstCn:输出图像的通道数。默认为0,自动计算得到
-
直方图
-
直方图:统计图像中的信息。
在这里,x轴为图像的长即每一列
对于RGB图像和HSV图像,y轴表示图像的每一列中像素点对应通道值的和;
对于二值图像,y轴表示图像得每一列中白色像素点的个数和。
-
使用matplotlib.pyplot模块
import matplotlib.pyplot as plt
-
创建窗口:
窗口用于放置图像、直方图等。
plt.figure(num, figsize)
参数:
- num:字符串为窗口名称,整数为窗口编号
- figsize:窗口的大小,例如
figsize=(5, 3)
表示窗口大小为5英寸长,3英寸宽
-
添加子窗口:
在大窗口中插入子窗口,便于观察与对比。
plt.subplot(nrows, ncols, index)
参数:
- nrows:行数
- ncols:列数
- index:子窗口序号
例如,
plt.subplot(2,3,4)
或plt.subplot(234)
表示有2行3列的第四个位置添加一个子窗口
如果三个参数都小于10,之间的逗号可以省略。
-
绘制直方图:
plt.bar(x, height, color)
参数:
- x:数组,x轴的数据类别
- height:数组,y轴的数值
- color:条形的颜色
-
动态画图
灰常重要!!!用于实时画图!
matplotlib画图有两种模式:
- 阻塞模式:使用matplotlib显示图像后,之后的代码会阻塞不运行,这是默认模式
- 交互模式:使用matplotlib显示图像后代码继续运行,意味着可以继续绘制下一帧图像的直方图
要绘制视频的动态直方图,得开启交互模式。下列是动态画图的重要函数:
plt.ion()
开启交互模式。plt.ioff()
关闭交互模式。plt.cla()
清空创建的axes轴上的内容,如果需要更新轴的信息可以使用,否则轴信息容易重叠混乱。plt.clf()
清空创建的figure窗口中的内容,如果需要更新整个窗口内的信息可以使用,否则窗口内的显示容易重叠混乱。由于我们的窗口中还有子窗口,使用该函数清除得彻底一点。plt.pause()
等待一段时间,使图像更新。
效果显示
在代码中(放在文末),开启摄像头,将实时图像转化为RGB、HSV色彩空间和二值化图像并展示。同时将RGB、HSV图像拆分通道,使用matplotlib的交互模式,绘制每个通道像素值的动态直方图。
我将摄像头对准开着灯的教室中,放置有一个蓝色盒子的带有线条的地板上,效果如下:
直方图会随视频实时变化。
上面四个窗口从左至右分别是源图像、RGB图像、HSV图像、二值化图像。
下面三个窗口从左至右分别是RGB三个通道的色彩直方图、HSV三个通道的色彩直方图、二值化图像的直方图。
可以看到,rgb图像中,蓝色盒子所在区域,BLUE通道的直方图对应区域突出;hsv图像中,对应区域的HUE通道和SATURATION通道突出;二值图像中,对应区域也有明显区别,线条部分亦是。
综合分析
通过摄像头识别不同的物体及观察直方图的变化,可以发现,当我改变物体的颜色,RGB三个通道的直方图会改变趋势相似,但对于不那么纯净的颜色,观察的时候不直观。hsv三个通道的直方图,改变物体颜色时色调变化明显,在我对同一物体施加不同光照时,饱和度和亮度也会有所改变。
-
RGB
- 优点:可以组成256x256x256=16777216种颜色。应用普遍与广泛,显示图像直观。
- 缺点:对环境光照等条件不敏感,在光线对颜色造成影响的时候判断不灵敏。观察指定颜色不方便。
- 适用情况:在电子显示设备、一般的图像显彩中会使用RGB;在预处理图像阶段,RGB仍然是一个重要的起点
-
HSV
- 优点:色调不易受饱和度和亮度的影响,更方便识别特定颜色。加上饱和度和亮度,可以处理一定的光照影响。
- 缺点:若遇到极端的强光或者弱光环境,色相可能会偏移。且不适合对颜色精确度要求高的任务
-
LAB
- 优点:具有与设备无关的特点,在不同的设备上保持一致。色域宽广。允许单独编辑亮度(L通道)和色彩平衡(a和b通道),为调色提供了更多的灵活性
- 缺点:虽然更符合人类视觉感知,但是其值不如RGB等色彩空间直观易懂
- 适用情况:颜色分割更细致,在检测植物叶片、水果成熟度等背景比较复杂任务中效果好
适用情况:环境会受一定程度光照的影响
- LAB
-
优点:具有与设备无关的特点,在不同的设备上保持一致。色域宽广。允许单独编辑亮度(L通道)和色彩平衡(a和b通道),为调色提供了更多的灵活性
-
缺点:虽然更符合人类视觉感知,但是其值不如RGB等色彩空间直观易懂
-
适用情况:颜色分割更细致,在检测植物叶片、水果成熟度等背景比较复杂任务中效果好
-
代码如下:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def draw_hist(rgb, hsv, binary):
y, x = rgb.shape[:2] # 640,480
# 绘制直方图
# rgb图像
plt.figure("RGB", figsize=(5, 3))
plt.clf() # 清空该窗口内的图像,重新绘制,避免图像叠加
plt.subplot(131)
r_pixel = np.sum(rgb[:, :, 0], axis=0) # 计算图像中r通道每列像素点的和
plt.bar(range(x), r_pixel, color='red')
plt.title("RED")
plt.subplot(132)
g_pixel = np.sum(rgb[:, :, 1], axis=0) # 计算图像中g通道每列像素点的和
plt.bar(range(x), g_pixel, color='green')
plt.title("GREEN")
plt.subplot(133)
b_pixel = np.sum(rgb[:, :, 2], axis=0) # 计算图像中b通道每列像素点的和
plt.bar(range(x), b_pixel, color='blue')
plt.title("BLUE")
# hsv图像
plt.figure("HSV", figsize=(5, 3))
plt.clf()
plt.subplot(131)
h_pixel = np.sum(hsv[:, :, 0], axis=0) # 计算图像中h通道每列像素点的和
plt.bar(range(x), h_pixel, color='yellow')
plt.title("HUE")
plt.subplot(132)
s_pixel = np.sum(hsv[:, :, 1], axis=0) # 计算图像中s通道每列像素点的和
plt.bar(range(x), s_pixel, color='purple')
plt.title("SATURATION")
plt.subplot(133)
v_pixel = np.sum(hsv[:, :, 2], axis=0) # 计算图像中v通道每列像素点的和
plt.bar(range(x), v_pixel, color='orange')
plt.title("VALUE")
white_pixel = np.sum(binary == 255, axis=0) # 计算每一列白色像素点的总数
plt.figure("BINARY", figsize=(5, 3))
plt.clf()
plt.bar(range(x), white_pixel, color='gray')
plt.title("BINARY")
plt.pause(1) # 等待图像更新
if __name__ == "__main__":
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
plt.ion() # 开启交互模式
while True:
ret, frame = cap.read()
if not ret:
break
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
cv2.imshow('frame', frame)
cv2.imshow('rgb', rgb)
cv2.imshow('hsv', hsv)
cv2.imshow('binary', binary)
cv2.waitKey(1) # 摄像头获取图像更新
draw_hist(rgb, hsv, binary)
plt.ioff() # 关闭交互模式
cap.release()
cv2.destroyAllWindows()