色彩模型是描述使用一组值(通常使用三个、四个值或者颜色成分)表示颜色方法的抽象数学模型。
比较常见的色彩空间包括:
色彩空间类型转换是指,将图像从一个色彩空间转换到另外一个色彩空间。
在使用OpenCV处理图像时,可能会在RGB色彩空间和HSV色彩空间之间进行转换。
在进行图像的特征提取、距离计算时,往往先将图像从RGB色彩空间处理为灰度色彩空间。
在一些应用中,可能需要将色彩空间的图像转换为二值图像。
色彩空间也称为颜色空间、彩色空间、颜色模型、彩色系统、彩色模型、色彩模型等。
GRAY(灰度图像)通常指8位灰度图,其具有256个灰度级,像素值的范围是[0,255]。
当图像由RGB色彩空间转换为GRAY色彩空间时,其处理方式如下:
Gray=0.299*R+0.587*G+0.114*B
当图像由GRAY色彩空间转换为RGB色彩空间时,最终所有通道的值都将是相同的,其处理方式如下:
R=Gray
G=Gray
B=Gray
XYZ色彩空间是由CIE(International Commission on Illumination)定义的,是一种更便于计算的色彩空间,它可以与RGB色彩空间相互转换。
将RGB色彩空间转换为XYZ色彩空间,其转换形式为:
将XYZ色彩空间转换为RGB色彩空间,其转换形式为:
人眼视觉系统(HVS, Human Visual System)对颜色的敏感度要低于对亮度的敏感度。
在YCrCb色彩空间中,Y代表光源的亮度,色度信息保存在Cr和Cb中,其中,Cr表示红色分量信息,Cb表示蓝色分量信息。
亮度给出了颜色亮或暗的程度信息,该信息可以通过照明中强度成分的加权和来计算。在RGB光源中,绿色分量的影响最大,蓝色分量的影响最小。
从RGB色彩空间到YCrCb色彩空间的转换公式为:
Y=0.299·R+0.587·G+0.114·B
Cr=(R-Y)×0.713+delta
Cb=(B-Y)×0.564+delta
式中delta的值为:
从YCrCb色彩空间到RGB色彩空间的转换公式为:
R=Y+1.403*(Cr-delta)
G=Y-0.714*(Cr-delta)-0.344·(Cb-delta)
B=Y+1.773*(Cb-delta)
式中,delta的值与上面公式中的delta值相同。
RGB是从硬件的角度提出的颜色模型,在与人眼匹配的过程中可能存在一定的差异,HSV色彩空间是一种面向视觉感知的颜色模型
指出人眼的色彩知觉主要包含三要素:色调(Hue,也称为色相)、饱和度(Saturation)、亮度(Value)。
色调指光的颜色,饱和度是指色彩的深浅程度,亮度指人眼感受到的光的明暗程度。
● 色调:色调与混合光谱中的主要光波长相关,例如“赤橙黄绿青蓝紫”分别表示不同的色调。如果从波长的角度考虑,不同波长的光表现为不同的颜色,实际上它们体现的是色调的差异。
● 饱和度:指相对纯净度,或一种颜色混合白光的数量。纯谱色是全饱和的,像深红色(红加白)和淡紫色(紫加白)这样的彩色是欠饱和的,饱和度与所加白光的数量成反比。
● 亮度:反映的是人眼感受到的光的明暗程度,该指标与物体的反射度有关。对于色彩来讲,如果在其中掺入的白色越多,则其亮度越高;如果在其中掺入的黑色越多,则其亮度越低。
饱和度为一比例值,范围是[0, 1],具体为所选颜色的纯度值和该颜色最大纯度值之间的比值。饱和度的值为0时,只有灰度。
亮度表示色彩的明亮程度,取值范围也是[0, 1]。
在从RGB色彩空间转换到HSV色彩空间之前,需要先将RGB色彩空间的值转换到[0, 1]之间,然后再进行处理。具体处理方法为:
计算结果可能存在H<0的情况,如果出现这种情况,则需要对H进行进一步计算,如下。
由上述公式计算可知:
S∈[0,1]
V∈[0,1]
H∈[0,360]
所有这些转换都被封装在OpenCV的cv2.cvtColor()函数内。通常情况下,我们都是直接调用该函数来完成色彩空间转换的,而不用考虑函数的内部实现细节。
HLS色彩空间包含的三要素是色调H(Hue)、光亮度/明度L(Lightness)、饱和度S(Saturation)。
与HSV色彩空间类似,只是HLS色彩空间用“光亮度/明度L(lightness)”替换了“亮度(Value)”。
● 色调:表示人眼所能感知的颜色,在HLS模型中,所有的颜色分布在一个平面的色调环上,整个色调环为360度的圆心角,不同的角度代表不同的颜色
● 光亮度/明度:用来控制色彩的明暗变化,它的取值范围也是[0, 1]。我们通过光亮度/明度的大小来衡量有多少光线从物体表面反射出来。光亮度/明度对于眼睛感知颜色很重要,因为当一个具有色彩的物体处于光线太强或者光线太暗的地方时,眼睛是无法准确感知物体颜色的。
● 饱和度:使用[0, 1]的值描述相同色调、相同光亮度/明度下的色彩纯度变化。饱和度的值越大,表示颜色的纯度越高,颜色越鲜艳;反之,饱和度的值越小,色彩的纯度越低,颜色越暗沉。通常用该属性表示颜色的深浅,比如深绿色、浅绿色。
CIEL*a*b*色彩空间是均匀色彩空间模型,它是面向视觉感知的颜色模型。
从视觉感知均匀的角度来讲,人所感知到的两种颜色的区别程度,应该与这两种颜色在色彩空间中的距离成正比。在某个色彩空间中,如果人所观察到的两种颜色的区别程度,与这两种颜色在该色彩空间中对应的点之间的欧式距离成正比,则称该色彩空间为均匀色彩空间。
CIEL*a*b*色彩空间中的L*分量用于表示像素的亮度,取值范围是[0,100],表示从纯黑到纯白;a*分量表示从红色到绿色的范围,取值范围是[-127,127]; b*分量表示从黄色到蓝色的范围,取值范围是[-127,127]。
在从RGB色彩空间转换到CIEL*a*b*色彩空间之前,需要先将RGB色彩空间的值转换到[0, 1]之间,然后再进行处理。
由于CIEL*a*b*色彩空间是在CIE的XYZ色彩空间的基础上发展起来的,在具体处理时,需要先将RGB转换为XYZ色彩空间,再将其转换到CIEL*a*b*色彩空间。具体实现方法为:
所得结果中各个值的取值范围为:
L∈[0,100]
a∈[-127,127]
b∈[-127,127
CIEL*u*v*色彩空间同CIEL*a*b*色彩空间一样,都是均匀的颜色模型。CIEL*u*v*色彩空间与设备无关,适用于显示器显示和根据加色原理进行组合的场合,该模型中比较强调对红色的表示,即对红色的变化比较敏感,但对蓝色的变化不太敏感。
从RGB色彩空间到CIEL*u*v*色彩空间的转换过程,要先转换到XYZ色彩空间。
从RGB色彩空间到XYZ色彩空间的转换:
从XYZ色彩空间到CIEL*u*v*色彩空间的转换:
所得结果中各个值的取值范围分别为:
L∈[0,100]
u∈[-134,220]
v∈[-140,122]
Bayer色彩空间(Bayer模型)被广泛地应用在CCD和CMOS相机中。它能够从如图4-1所示的单平面R、G、B交错表内获取彩色图像。
输出的RGB图像的像素点值,是根据当前点的1个、2个或4个邻域像素点的相同颜色的像素值获得的。
上述模式能够通过移动一个左边的像素或者上方的像素来完成修改。
在函数cv2.cvtColor()的色彩空间转换参数中,通常使用两个特定的参数x和y来表示特定的模式。
该模式组成通过上图第二行中的第2列与第3列的值来指定。上图就是典型的“BG”模式。
常见的模式还有很多,cv2.COLOR_BayerBG2BGR、cv2.COLOR_BayerGB2BGR、cv2.COLOR_BayerRG2BGR、cv2.COLOR_BayerGR2BGR、cv2.COLOR_BayerBG2RGB、cv2.COLOR_BayerGB2RGB、cv2.COLOR_BayerRG2RGB、cv2.COLOR_BayerGR2RGB等。
在OpenCV内,使用cv2.cvtColor()函数实现色彩空间的变换。
该函数能够实现多个色彩空间之间的转换。
dst = cv2.cvtColor( src, code [, dstCn] )
dst表示输出图像,与原始输入图像具有同样的数据类型和深度。
src表示原始输入图像。可以是8位无符号图像、16位无符号图像,或者单精度浮点数等。
code是色彩空间转换码
dstCn是目标图像的通道数。如果参数为默认的0,则通道数自动通过原始输入图像和code得到。
对于一个标准的24位位图, BGR色彩空间中第1个8位(第1个字节)存储的是蓝色组成信息(Blue component),第2个8位(第2个字节)存储的是绿色组成信息(Green component),第3个8位(第3个字节)存储的是红色组成信息(Red component)。同样,其第4个、第5个、第6个字节分别存储蓝色、绿色、红色组成信息,以此类推。
颜色空间的转换都用到了如下约定:
8位图像值的范围是[0,255]。
16位图像值的范围是[0,65535]。
浮点数图像值的范围是[0.0~1.0]。
对于线性转换来说,这些取值范围是无关紧要的。但是对于非线性转换来说,输入的RGB图像必须归一化到其对应的取值范围内,才能获取正确的转换结果。
说明:
将BGR图像转换为灰度图像。
rst=cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
import cv2
import numpy as np
img=np.random.randint(0,256, size=[2,4,3], dtype=np.uint8)
rst=cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print("img=\n", img)
print("rst=\n", rst)
print("像素点(1,0)直接计算得到的值=",
img[1,0,0]*0.114+img[1,0,1]*0.587+img[1,0,2]*0.299)
print("像素点(1,0)使用公式cv2.cvtColor()转换值=", rst[1,0])
在OpenCV中,灰度图像是按照行列直接存储的。而BGR模式的图像会依次将它的B通道、G通道、R通道中的像素点,以行为单位按照顺序存储在ndarray的列中。
当图像由RGB色彩空间转换到GRAY色彩空间时,其处理方式如下:
Gray=0.299·R+0.587·G+0.114·B
将灰度图像转换为BGR图像
import cv2
import numpy as np
img=np.random.randint(0,256, size=[2,4], dtype=np.uint8)
rst=cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
print("img=\n", img)
print("rst=\n", rst)
当图像由GRAY色彩空间转换到RGB/BGR色彩空间时,最终所有通道的值都是相同的。其处理方式如下:
R=Gray
G=Gray
B=Gray
将图像在BGR和RGB模式之间相互转换
import cv2
import numpy as np
img=np.random.randint(0,256, size=[2,4,3], dtype=np.uint8)
rgb=cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
bgr=cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
print("img=\n", img)
print("rgb=\n", rgb)
print("bgr=\n", bgr)
将图像在BGR模式和灰度图像之间相互转换
import cv2
lena=cv2.imread("lenacolor.png")
gray=cv2.cvtColor(lena, cv2.COLOR_BGR2GRAY)
rgb=cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
#==========打印shape============
print("lena.shape=", lena.shape)
print("gray.shape=", gray.shape)
print("rgb.shape=", rgb.shape)
#==========显示效果============
cv2.imshow("lena", lena)
cv2.imshow("gray", gray)
cv2.imshow("rgb", rgb)
cv2.waitKey()
cv2.destroyAllWindows()
在通过“rgb=cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)”得到的RGB图像中,B通道、G通道、R通道的值都是一样的,所以其看起来仍是灰度图像。
图像从BGR模式转换为RGB模式
import cv2
lena=cv2.imread("lenacolor.png")
rgb = cv2.cvtColor(lena, cv2.COLOR_BGR2RGB)
cv2.imshow("lena", lena)
cv2.imshow("rgb", rgb)
cv2.waitKey()
cv2.destroyAllWindows()
通过HSV色彩空间,够更加方便地通过色调、饱和度和亮度来感知颜色。
HSV色彩空间从心理学和视觉的角度出发,提出人眼的色彩知觉主要包含三要素
在HSV色彩空间中,色调H的取值范围是[0,360]。
8位图像内每个像素点所能表示的灰度级有28=256个,所以在8位图像内表示HSV图像时,要把色调的角度值映射到[0,255]范围内。在OpenCV中,可以直接把色调的角度值除以2,得到[0,180]之间的值,以适应8位二进制(256个灰度级)的存储和表示范围。
在HSV空间中,色调值为0表示红色,色调值为300表示品红色
每个色调值对应一个指定的色彩,而与饱和度和亮度无关。
在OpenCV中,将色调值除以2之后,会得到如下的色调值与对应的颜色。
饱和度值的范围是[0,1]
cv2.cvtColor()函数,进行色彩空间转换后,为了适应8位图的256个像素级,需要将新色彩空间内的数值映射到[0,255]范围内。所以,同样要将饱和度S的值从[0,1]范围映射到[0,255]范围内。
亮度的范围与饱和度的范围一致,都是[0,1]。同样,亮度值在OpenCV内也将值映射到[0,255]范围内。
亮度值越大,图像越亮;亮度值越低,图像越暗。当亮度值为0时,图像是纯黑色。
可以通过多种方式获取RGB色彩空间的颜色值在HSV色彩空间内所对应的值。例如,可以通过图像编辑软件或者在线网站获取RGB值所对应的HSV值。
需要注意,在从RGB/BGR色彩空间转换到HSV色彩空间时,OpenCV为了满足8位图的要求,对HSV空间的值进行了映射处理。所以,通过软件或者网站获取的HSV值还需要被进一步映射,才能与OpenCV中的HSV值一致
例子
对蓝色、绿色、红色三种不同的颜色分别进行转换,将它们从BGR色彩空间转换到HSV色彩空间,并观察转换后所得到的HSV空间的对应值。
import cv2
import numpy as np
#=========测试一下OpenCV中蓝色的HSV模式值=============
imgBlue=np.zeros([1,1,3], dtype=np.uint8)
imgBlue[0,0,0]=255
Blue=imgBlue
BlueHSV=cv2.cvtColor(Blue, cv2.COLOR_BGR2HSV)
print("Blue=\n", Blue)
print("BlueHSV=\n", BlueHSV)
#=========测试一下OpenCV中绿色的HSV模式值=============
imgGreen=np.zeros([1,1,3], dtype=np.uint8)
imgGreen[0,0,1]=255
Green=imgGreen
GreenHSV=cv2.cvtColor(Green, cv2.COLOR_BGR2HSV)
print("Green=\n", Green)
print("GreenHSV=\n", GreenHSV)
#=========测试一下OpenCV中红色的HSV模式值=============
imgRed=np.zeros([1,1,3], dtype=np.uint8)
imgRed[0,0,2]=255
Red=imgRed
RedHSV=cv2.cvtColor(Red, cv2.COLOR_BGR2HSV)
print("Red=\n", Red)
print("RedHSV=\n", RedHSV)
在HSV色彩空间中,H通道(饱和度Hue通道)对应不同的颜色。换个角度理解,颜色的差异主要体现在H通道值的不同上。所以,通过对H通道值进行筛选,便能够筛选出特定的颜色。
在一幅HSV图像中,如果通过控制仅仅将H通道内值为240(在OpenCV内被调整为120)的像素显示出来,那么图像中就会仅仅显示蓝色部分。
可以将一幅图像内的其他颜色屏蔽,仅仅将特定颜色显示出来。
通过inRange函数锁定特定值
OpenCV中通过函数cv2.inRange()来判断图像内像素点的像素值是否在指定的范围内,其语法格式为:
dst = cv2.inRange( src, lowerb, upperb )
● dst表示输出结果,大小和src一致。
● src表示要检查的数组或图像。
● lowerb表示范围下界。
● upperb表示范围上界。
返回值dst与src等大小,其值取决于src中对应位置上的值是否处于区间[lowerb, upperb]内:
● 如果src值处于该指定区间内,则dst中对应位置上的值为255。
● 如果src值不处于该指定区间内,则dst中对应位置上的值为0。
使用函数cv2.inRange()将某个图像内的在[100,200]内的值标注出来。
import cv2
import numpy as np
img=np.random.randint(0,256, size=[5,5], dtype=np.uint8)
min=100
max=200
mask = cv2.inRange(img, min, max)
print("img=\n", img)
print("mask=\n", mask)
返回的结果mask可以理解为一个掩码数组,其大小与原始数组一致。
通过基于掩码的按位与显示ROI
正常显示某个图像内的感兴趣区域(ROI),而将其余区域显示为黑色。
import cv2
import numpy as np
opencv=cv2.imread("opencv.jpg")
hsv = cv2.cvtColor(opencv, cv2.COLOR_BGR2HSV)
cv2.imshow('opencv', opencv)
#=============指定蓝色值的范围=============
minBlue = np.array([110,50,50])
maxBlue = np.array([130,255,255])
#确定蓝色区域
mask = cv2.inRange(hsv, minBlue, maxBlue)
#通过掩码控制的按位与运算,锁定蓝色区域
blue = cv2.bitwise_and(opencv, opencv, mask= mask)
cv2.imshow('blue', blue)
#=============指定绿色值的范围=============
minGreen = np.array([50,50,50])
maxGreen = np.array([70,255,255])
#确定绿色区域
mask = cv2.inRange(hsv, minGreen, maxGreen)
#通过掩码控制的按位与运算,锁定绿色区域
green = cv2.bitwise_and(opencv, opencv, mask= mask)
cv2.imshow('green', green)
#=============指定红色值的范围=============
minRed = np.array([0,50,50])
maxRed = np.array([30,255,255])
#确定红色区域
mask = cv2.inRange(hsv, minRed, maxRed)
#通过掩码控制的按位与运算,锁定红色区域
red= cv2.bitwise_and(opencv, opencv, mask= mask)
cv2.imshow('red', red)
cv2.waitKey()
cv2.destroyAllWindows()
通过分析可以估算出肤色在HSV色彩空间内的范围值。在HSV空间内筛选出肤色范围内的值,即可将图像内包含肤色的部分提取出来。
import cv2
img=cv2.imread("lesson2.jpg")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v=cv2.split(hsv)
minHue=5
maxHue=170
hueMask=cv2.inRange(h, minHue, maxHue)
minSat=25
maxSat=166
satMask = cv2.inRange(s, minSat, maxSat)
mask = hueMask & satMask
roi = cv2.bitwise_and(img, img, mask= mask)
cv2.imshow("img", img)
cv2.imshow("ROI", roi)
cv2.waitKey()
cv2.destroyAllWindows()
在HSV色彩空间内进行分量值的调整能够生成一些有趣的效果
改变V通道的值,让其值均变为255,观察图像处理结果。
import cv2
img=cv2.imread("barbara.bmp")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v=cv2.split(hsv)
v[:, :]=255
newHSV=cv2.merge([h, s, v])
art = cv2.cvtColor(newHSV, cv2.COLOR_HSV2BGR)
cv2.imshow("img", img)
cv2.imshow("art", art)
cv2.waitKey()
cv2.destroyAllWindows()
在RGB色彩空间三个通道的基础上,还可以加上一个A通道,也叫alpha通道,表示透明度。
这种4个通道的色彩空间被称为RGBA色彩空间,PNG图像是一种典型的4通道图像。alpha通道的赋值范围是[0, 1],或者[0, 255],表示从透明到不透明。
import cv2
import numpy as np
img=np.random.randint(0,256, size=[2,3,3], dtype=np.uint8)
bgra = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
print("img=\n", img)
print("bgra=\n", bgra)
b, g, r, a=cv2.split(bgra)
print("a=\n", a)
a[:, :]=125
bgra=cv2.merge([b, g, r, a])
print("bgra=\n", bgra)
使用语句bgra=cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)将img从BGR色彩空间转换到BGRA色彩空间。在转换后的BGRA色彩空间中,A是alpha通道,默认值为255。
对图像的alpha通道进行处理
import cv2
img=cv2.imread("lenacolor.png")
bgra = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
b, g, r, a=cv2.split(bgra)
a[:, :]=125
bgra125=cv2.merge([b, g, r, a])
a[:, :]=0
bgra0=cv2.merge([b, g, r, a])
cv2.imshow("img", img)
cv2.imshow("bgra", bgra)
cv2.imshow("bgra125", bgra125)
cv2.imshow("bgra0", bgra0)
cv2.waitKey()
cv2.destroyAllWindows()
cv2.imwrite("bgra.png", bgra)
cv2.imwrite("bgra125.png", bgra125)
cv2.imwrite("bgra0.png", bgra0)
各个图像的alpha通道值虽然不同,但是在显示时是没有差别的。
打开当前文件夹,可以看到当前文件夹下保存了三幅图像,有差别。
需要注意,在图像bgra0处于预览模式时,看起来可能是一幅黑色的图像,将其打开后就会看到它实际上是纯色透明的。