色彩空间基础
比较常见的色彩空间包括:
GRAY色彩空间
XYZ色彩空间
YCrCb色彩空间
HSV色彩空间
HLS色彩空间
CIELab色彩空间
CIELuv色彩空间
Bayer色彩空间等等
GRAY色彩空间——灰度
- GRAY(灰度图像)通常指8位灰度图,其具有256个灰度级,像素值的范围是[0, 255]。当图像由RGB色彩空间转化位GRAY色彩空间时,其处理方式如下:
- Gray=0.299R+0.587G+0.114*B
- 上述是标准的转换方式,也是opencv中使用的转换方式。
- 有时,也可以采用简化形式完成转换:
- Gray=(R+G+B)/3
- 当图像由GRAY色彩空间转化位RGB色彩空间时,最终所有通道的值都将是相同的,其处理方式如下:
- R=Gray
- G=Gray
- B=Gray
XYZ色彩空间
- XYZ色彩空间是由CIE定义的,是一种更便于计算的色彩空间,它可以与RGB色彩空间相互转换。
- RGB—>XYZ
- XYZ—>RGB
YCrCb色彩空间
- 人眼视觉系统(HVS)对颜色的敏感度要低于对亮度的敏感度。在传统的RGB色彩空间内,RGB三原色具有相同的重要性,但是忽略了亮度信息。
- 在YCrCb色彩空间中,Y代表光源的亮度,色度信息保存在Cr和Cb中,其中,Cr表示红色分量信息,Cb表示蓝色分量信息。
- 亮度给出了颜色亮或暗的程度信息,该信息可以通过照明中强度成分的加权和来计算。在RGB光源中,绿色分量的影响最大,蓝色分量的影响最小。
- Y=0.299R+0.587G+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值相同
HSV色彩空间和HLS色彩空间
- RGB是从硬件的角度提出的颜色模型,在与人眼匹配的过程中可能存在一定的差异,HSV色彩空间是一种面向视觉感知的颜色模型。HSV色彩空间从心理学和视觉的角度出发,指出人眼的色彩知觉主要包含三要素:色调(Hue,也称为色相)、饱和度(Saturation)、亮度(Value),色调指光的颜色,饱和度是指色彩的深浅程度,亮度指人眼感受到的光的明暗程度。
- 色调:色调与混合光谱中的主要光波长相关,例如“赤橙黄绿青蓝紫”分别表示不同的色调。如果从波长的角度考虑,不同波长的光表现为不同的颜色,实际上它们体现的是色调的差异。
- 饱和度:指相对纯净度,或一种颜色混合白光的数量。纯谱色是全饱和的,像深红色(红加白)和淡紫色(紫加白)这样的彩色是欠饱和的,饱和度与所加白光的数量成反比。
- 亮度:反映的是人眼感受到的光的明暗程度,该指标与物体的反射度有关。对于色彩来讲,如果在其中掺入的白色越多,则其亮度越高;如果在其中掺入的黑色越多,则其亮度越低。
- 在具体实现上,我们将物理空间的颜色分布在圆周上,不同的角度代表不同的颜色。因此,通过调整色调值就能选取不同的颜色,色调的取值区间为[0,360]。色调取不同值时,所代表的颜色如表4-1所示,两个角度之间的角度对应两个颜色之间的过渡色。
- 饱和度为一比例值,范围是[0,1],具体为所选颜色的纯度值和该颜色最大纯度值之间的比值。饱和度的值为0时,只有灰度。亮度表示色彩的明亮程度,取值范围也是[0,1]。
- 在HSV色彩模型中,取色变得更加直观。例如,取值“色调=0,饱和度=1,亮度=1”,则当前色彩为深红色,而且颜色较亮;取值“色调=120,饱和度=0.3,亮度=0.4”,则当前色彩为浅绿色,而且颜色较暗。
- 在从RGB色彩空间转换到HSV色彩空间之前,需要先将RGB色彩空间的值转换到[0,1]之间,然后再进行处理。具体处理方法为:
CIELab色彩空间、CIELuv色彩空间、Bayer色彩空间
类型转换函数
- 在opencv中我们使用cv2.cvtColor()函数实现色彩空间的变换。该函数能够实现多个色彩空间之间的转换。其语法格式为:
- dst=cv2.cvtColor(src, code[,dstCn])
- 式中:
- dst:输出图像,与原始输入图像具有同样的数据类型和深度
- src:表示原始的输入图像。可以是8位无符号图像、16位无符号图像,或者单精度浮点数等
- code:是色彩空间转换码
- dstCn:是目标图像的通道数。如果参数位默认的0,则通道数自动通过原始输入图像和code得到
- 这里需要注意,BGR色彩空间与传统的RGB色彩空间不同。对于一个标准的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,65 535]。
- 浮点数图像值的范围是[0.0~1.0]。
- 对于线性转换来说,这些取值范围是无关紧要的。但是对于非线性转换来说,输入的RGB图像必须归一化到其对应的取值范围内,才能获取正确的转换结果。
- 例如,对于8位图,其能够表示的灰度级有28=256个,也就是说,在8位图中,最多能表示256个状态,通常是[0,255]之间的值。但是,在很多色彩空间中,值的范围并不恰好在[0,255]范围内,这时,就需要将该值映射到[0,255]内。
- 例如,在HSV或HLS色彩空间中,色调值通常在[0,360)范围内,在8位图中转换到上述色彩空间后,色调值要除以2,让其值范围变为[0,180),以满足存储范围,即让值的分布位于8位图能够表示的范围[0,255]内。又例如,在CIELab*色彩空间中,a通道和b通道的值范围是[−127,127],为了使其适应[0,255]的范围,每个值都要加上127。不过需要注意,由于计算过程存在四舍五入,所以转换过程并不是精准可逆的。
类型转换实例
通过数组观察转换效果
案例1:将BGR图像转化位灰度图像
1 2 3 4 5 6 7
| import cv2 import numpy as np
img = np.random.randint(0, 255, (5,5,3), dtype=np.uint8) gray = cv2.cvtColor(img, code=cv2.COLOR_BGR2GRAY) print('原始图像:\n', img.shape,'\n', img) print('灰度图像:\n', gray.shape, '\n', gray)
|
原始图像:
(5, 5, 3)
[[[244 90 138]
[200 87 208]
[ 19 178 133]
[ 12 241 82]
[244 82 135]]
[[128 139 221]
[113 103 113]
[139 210 168]
[ 67 41 2]
[194 79 236]]
[[121 144 166]
[147 133 140]
[182 48 25]
[127 224 138]
[115 205 145]]
[[ 97 179 240]
[ 73 113 227]
[ 69 157 154]
[ 44 109 55]
[ 64 67 239]]
[[ 74 113 216]
[ 63 82 85]
[155 220 149]
[ 23 73 101]
[137 84 56]]]
灰度图像:
(5, 5)
[[122 136 146 167 116]
[162 107 189 32 139]
[148 137 56 187 177]
[188 143 146 85 118]
[139 81 191 76 82]]
案例2:将灰度图像转化位BGR图像
1 2 3 4 5 6 7
| import cv2 import numpy as np
gray = np.random.randint(0, 255, size=(4,4), dtype=np.uint8) bgr = cv2.cvtColor(gray, code=cv2.COLOR_GRAY2BGR) print('灰度图像:\n', gray.shape, '\n', gray) print('原始图像:\n', bgr.shape,'\n', bgr)
|
灰度图像:
(4, 4)
[[155 250 53 39]
[ 38 43 14 50]
[242 89 92 150]
[ 79 197 8 139]]
原始图像:
(4, 4, 3)
[[[155 155 155]
[250 250 250]
[ 53 53 53]
[ 39 39 39]]
[[ 38 38 38]
[ 43 43 43]
[ 14 14 14]
[ 50 50 50]]
[[242 242 242]
[ 89 89 89]
[ 92 92 92]
[150 150 150]]
[[ 79 79 79]
[197 197 197]
[ 8 8 8]
[139 139 139]]]
案例3:将图像在BGR和RGB之间相互转化
1 2 3 4 5 6 7 8 9 10
| import cv2 import numpy as np
img = np.random.randint(0, 255, size=(4,4,3), dtype=np.uint8) bgr2rgb = cv2.cvtColor(img, code=cv2.COLOR_BGR2RGB) rgb2bgr = cv2.cvtColor(bgr2rgb, code=cv2.COLOR_RGB2BGR) print('原始图像:\n', img.shape,'\n', img) print('BGR2RGB图像:\n', bgr2rgb.shape,'\n', bgr2rgb) print('RGB2BGR原始图像:\n', rgb2bgr.shape,'\n', rgb2bgr) print('再转化回来的图像和原来的图像一样吗:',(rgb2bgr==img).all())
|
原始图像:
(4, 4, 3)
[[[147 19 239]
[ 25 66 220]
[ 50 241 99]
[113 242 128]]
[[154 102 243]
[150 122 183]
[ 85 107 204]
[ 18 225 246]]
[[ 42 71 251]
[140 122 251]
[104 84 195]
[168 122 245]]
[[168 127 46]
[240 46 43]
[252 75 114]
[ 51 205 40]]]
BGR2RGB图像:
(4, 4, 3)
[[[239 19 147]
[220 66 25]
[ 99 241 50]
[128 242 113]]
[[243 102 154]
[183 122 150]
[204 107 85]
[246 225 18]]
[[251 71 42]
[251 122 140]
[195 84 104]
[245 122 168]]
[[ 46 127 168]
[ 43 46 240]
[114 75 252]
[ 40 205 51]]]
RGB2BGR原始图像:
(4, 4, 3)
[[[147 19 239]
[ 25 66 220]
[ 50 241 99]
[113 242 128]]
[[154 102 243]
[150 122 183]
[ 85 107 204]
[ 18 225 246]]
[[ 42 71 251]
[140 122 251]
[104 84 195]
[168 122 245]]
[[168 127 46]
[240 46 43]
[252 75 114]
[ 51 205 40]]]
再转化回来的图像和原来的图像一样吗: True
图像处理实例
1
| from get_show_img import get_show
|
1 2 3 4 5 6 7
| import cv2
img = cv2.imread('data/cat.jpg') gray = cv2.cvtColor(img, code=cv2.COLOR_BGR2GRAY) img_rgb = cv2.cvtColor(gray, code=cv2.COLOR_GRAY2RGB) get_show(gray) get_show(img,img_rgb)
|
1 2 3 4 5
| import cv2
img = cv2.imread('data/dog.jpg') img_rgb = cv2.cvtColor(img, code=cv2.COLOR_BGR2RGB) get_show(img, img_rgb)
|
HSV色彩空间讨论
- HSV色彩空间从心理学和视觉的角度出发,提出人眼的色彩知觉主要包含三要素:
- H:色调(Hue,也称为色相)——[0, 360]
- S:饱和度(Saturation)——[0, 1]
- V:亮度(Value)——[0, 1]
获取指定颜色
-在opencv中,测试RGB色彩空间中不同颜色的值转换到HSV色彩空间后的对应值
1 2 3 4 5 6 7 8 9 10
| import cv2 import numpy as np from get_show_img import get_show
imgBlue = np.zeros([1,1,3], dtype=np.uint8) imgBlue[0,0,0] = 255 BlueHSV = cv2.cvtColor(imgBlue, cv2.COLOR_BGR2HSV) print('BGR:', imgBlue) print('HSV:', BlueHSV) get_show(imgBlue, BlueHSV)
|
BGR: [[[255 0 0]]]
HSV: [[[120 255 255]]]
1 2 3 4 5 6 7 8 9 10
| import cv2 import numpy as np from get_show_img import get_show
imgGreen = np.zeros([1,1,3], dtype=np.uint8) imgGreen[0,0,1] = 255 GreenHSV = cv2.cvtColor(imgGreen, cv2.COLOR_BGR2HSV) print('BGR:', imgGreen) print('HSV:', GreenHSV) get_show(imgGreen, GreenHSV)
|
BGR: [[[ 0 255 0]]]
HSV: [[[ 60 255 255]]]
1 2 3 4 5 6 7 8 9 10
| import cv2 import numpy as np from get_show_img import get_show
imgRed = np.zeros([1,1,3], dtype=np.uint8) imgRed[0,0,2] = 255 RedHSV = cv2.cvtColor(imgRed, cv2.COLOR_BGR2HSV) print('BGR:', imgRed) print('HSV:', RedHSV) get_show(imgRed, RedHSV)
|
BGR: [[[ 0 0 255]]]
HSV: [[[ 0 255 255]]]
1
| get_show(BlueHSV, GreenHSV, RedHSV)
|
标记指定颜色
- 指将图像内的特定颜色标记出来,即将一幅图像内的其他颜色屏蔽,仅仅将特定颜色显示出来
- opencv中通过函数cv2.Range()来判断图像内像素点的像素值是否在指定的范围内,其语法格式为:
- dst=cv2.inRange(src, lowerb, upperb)
- 式中:
- dst表示输出结果,大小和src一致。
- src表示要检查的数组或图像。
- lowerb表示范围下界
- upperb表示范围上界
- 返回值dst与src等大小,其值取决于src中对应位置上的值是否取决于区间[lowerb, upperb]内:
- 如果src值处于该指定区间内,则dst中对应位置上的值为255。
- 如果src值不处于该指定区间内,则dst中对应位置上的值为0。
使用inRange函数锁定特定值
使用函数cv2.inRange()将某个图像内的在[100, 200]内的值标注出来。
1 2 3 4 5 6 7 8 9
| 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)
|
img=
[[ 62 201 173 139 122]
[148 141 91 98 18]
[235 48 203 68 9]
[ 95 132 171 104 54]
[252 16 223 155 72]]
mask=
[[ 0 0 255 255 255]
[255 255 0 0 0]
[ 0 0 0 0 0]
[ 0 255 255 255 0]
[ 0 0 0 255 0]]
通过掩码的按位与显示ROI
正常显示某个图像内的感兴趣区域(ROI),而将其余区域显示为黑色。
1 2 3 4 5 6 7 8 9 10 11
| import cv2 import numpy as np
img = np.ones([5,5], dtype=np.uint8)*155 mask = np.zeros([5,5], dtype=np.uint8) mask[0:3, 0] = 1 mask[2:5, 2:4] = 1 roi = cv2.bitwise_and(img, img, mask=mask) print('img=\n', img) print('mask=\n', mask) print('roi=\n', roi)
|
img=
[[155 155 155 155 155]
[155 155 155 155 155]
[155 155 155 155 155]
[155 155 155 155 155]
[155 155 155 155 155]]
mask=
[[1 0 0 0 0]
[1 0 0 0 0]
[1 0 1 1 0]
[0 0 1 1 0]
[0 0 1 1 0]]
roi=
[[155 0 0 0 0]
[155 0 0 0 0]
[155 0 155 155 0]
[ 0 0 155 155 0]
[ 0 0 155 155 0]]
显示特定颜色值
- 分别提取opencv的logo图像内的红色,绿色,蓝色
- 需要注意,在实际提取颜色时,往往不是提取一个特定的值,而是提取一个颜色区间。
- 例如,在opencv中的HSV模式内,蓝色在H通道内的值是120。在提取蓝色时,通常将“蓝色值120”附近的一个区间的值作为提取范围。该区间的半径通常为10~20左右,例如通常提取[120-10, 120+10]范围内的值来指定蓝色。
- 相比之下,HSV模式中S通道、V通道的值的取值范围一般是[100, 255]。这主要是因为,当饱和度和亮度太低时,计算出来的色调可能就不可靠了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import cv2
img = cv2.imread('data/opencv-logo-small.png') hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) minBlue = np.array([100, 100, 100]) maxBlue = np.array([140, 255, 255]) blue_ind = cv2.inRange(hsv, minBlue, maxBlue) blue = cv2.bitwise_and(img, img, mask=blue_ind)
minGreen = np.array([50, 100, 100]) maxGreen = np.array([70, 255, 255]) green_ind = cv2.inRange(hsv, minGreen, maxGreen) green = cv2.bitwise_and(img, img, mask=green_ind)
minRed = np.array([150, 100, 100]) maxRed = np.array([178, 255, 255]) red_ind = cv2.inRange(hsv, minRed, maxRed) red = cv2.bitwise_and(img, img, mask=red_ind)
get_show(blue, green, red)
|
标记肤色
- 在标记特点颜色的基础上,可以将标注范围进一步推广到特定的对象上。例如,通过分析可以估算出肤色在HSV色彩空间内的范围值。在HSV空间内筛选出肤色范围内的值,即可将图像内包含肤色的部分提出来。
- 这里将肤色范围划定为:
- 色调值在[5, 170]之间
- 饱和度值在[25, 166]之间
提取一幅图像内的皮肤部分
1 2 3 4 5 6 7 8 9 10
| import cv2 import numpy as np
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) minFuSe = np.array([5, 25, 0]) maxFuSe = np.array([170, 166, 255]) img_pifu = cv2.inRange(hsv, minFuSe, maxFuSe) pifu = cv2.bitwise_and(img, img, mask=img_pifu) get_show(img, pifu)
|
实现艺术效果
调整HSV色彩空间内V通道的值,观察其处理结果
1 2 3 4 5 6 7
| import cv2
img = cv2.imread('data/dog.jpg') hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) hsv[:,:,2] = 255 art = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) get_show(art)
|
alpha通道
- 在RGB色彩空间三个通道的基础上,还可以加上一个A通道,也叫alpha通道,表示透明度。这种4个通道的色彩空间被称为RGBA色彩空间,PNG图像是一种典型的4通道图像。
- alpha通道的赋值范围是[0, 1], 或者是[0, 255], 表示从透明到不透明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import cv2 import numpy as np
img = cv2.imread('data/opencv-logo-small.png') img_rgba = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA) b,g,r,a = cv2.split(img_rgba) a[:,:] = 125 rgba125 = cv2.merge([b,g,r,a]) a[:,:] = 0 rgba0 = cv2.merge([b,g,r,a]) a[:,:] = 200 rgba200 = cv2.merge([b,g,r,a]) cv2.imshow('img', np.hstack([img_rgba, rgba0, rgba125, rgba200])) cv2.waitKey() cv2.destroyAllWindows() cv2.imwrite('tmp/rgb0.png', rgba0) cv2.imwrite('tmp/rgb125.png', rgba125) cv2.imwrite('tmp/rgb200.png', rgba200)
|
True