第四章: 色彩空间类型转换
-
为什么有色彩空间这个概念?
世界本是无颜色的,我们人类看到的各种有色光只是特定波长的电磁波能够刺激人眼的锥体细胞,进而在人脑中形成颜色信号而已,实际上电磁波的波长域是非常广的,而我们人类只能感知很小一块区域,并且我们眼睛的光感细胞把特定波长的电磁波刺激信号传递给大脑,大脑反馈出不同的颜色。所以对于人类来讲人类的色彩空间就是一个特定的空间。
但是如果对于打印机来讲,它的色彩空间和人类的就完全不一样,比如,在黑暗中,光越强,人类就看到越清晰的色彩,但是对于打印机来说某种颜色的墨汁越多,如果有多种颜色,那么它显示出来的色彩就是黑色的。
同理,对于显示器来说,不同显示器由于发光物理原理不同,比如液晶显示器显示屏上的色彩是通过控制电压的高低来控制每个像素的颜色的,而CRT显示器是靠发射电子束来打击荧幕上面的荧光粉来达到显示的,所以不同显示器,即使你输入的相同信号的数据,它显示出来的效果都是不一样的。
同理,很多不同摄像机,通过不同的原理捕捉出来的图像的色彩即使数据一样,但呈现给我们人眼的色彩都是不一样的。比如军事上的图片、气象上的图片、我们日常用的普通相机的图片,用我们人类肉眼看都是不一样的。
所以,为了让人眼看到的图片和打印机打印出来的图片、不同显示器显示出来的图片、不同相机拍摄出来的图片、不同图片显示软件显示出来的图片,人类看着是统一的,就是对人类来说,同一张照片的色准是一致的,这样我们人类看到的风景就和打印机里打印出来的风景是一样的、和显示器显示出来的风景是一致的、和看图软件比如微信传输后显示的图片是一致的,我们就需要定义不同的色彩空间。相反,比如一张图片里面有只猫,如果在某个色彩空间人类看这张照片是只白猫,假如经过微信这张照片传输到另一个人手机上,这张图片还是这张图片,即使这张图片的数据没有变化,但是另一个人的手机的图片显示是在另外一个色彩空间上,比如那个人把手机色彩空间调成护眼模式、夜间模式等等,就是不同的色彩空间,那这只猫很可能那个人的眼睛看到的就是一个别的颜色的猫,这样图片信息在人与人之间的传递中,由于介质————微信的彩色空间不一致而导致两个人收到不一致的信息,就是色准发生了变化。所以我们要定义色彩空间。所以不同色彩空间需要进行转化。
上述的解释只是老师为了同学们简单直白的去类比理解,其实色彩方面的知识是非常专业和复杂的,涉及到生物学(人类视觉细胞)、信号学、光谱、材料学(图像生成器材、图像显示器材)以及复杂的光谱实验和数学计算等,很多东西是非常难理解的。所以老师的类比同学们仅作参考。有志于更专业的认识事物本质的同学,请参考下面的网址:
色彩空间基础 - 知乎
色彩空间表示与转换 - 知乎 -
常见的色彩空间有哪些?
1、RGB
2、GRAY
3、XYZ
4、CIEL*a*b*
5、CIEL*u*v*
6、YCrCb
7、HSV
8、HLS
9、Bayer
色彩空间又叫色彩模型。每个色彩空间自己的优势和劣势,都有自己擅长处理的领域。所以我们经常要用到色彩空间转换。
由于我们这门课程是为深度学习铺垫的,不是为比如服装设计、印刷等行业铺垫的,所以仅关心的如何通过图像(一堆数据)提取图像的特征,让计算机去认识图像而非人类的眼睛去欣赏色彩的目的,而计算机识别图像的模式和我们人类是完完全全、彻彻底底的不一样!!!所以,本章节只需了解色彩空间如何转换即可,不深究背后的理论、原理等内容。 -
色彩空间转换函数:cv2.cvtColor()
一、GRAY色彩空间
GRAY就是灰度图像,一般指是8为灰度图像,就是有256个灰度级,像素值的范围[0, 255] ,灰度图像是一个二维数据。
- RGB和GRAY之间的转换:
标准的转换方式,也就是opencv里使用的转换方式:gray = 0.299R + 0.587G + 0.114*B
有时,也可以采用简单粗暴的转换公式:gray = (R+G+B)/3
通过上面的转换,RGB的三通道彩色图像,也就是三维数据,就变成了两维数据了。 - GRAY和RGB之间的转换:
就是把二维数据升级成三维数据。所以R=gray , G=gray , B=gray , 一个二维灰度图像就变成三维彩色图像。但是这里要说明的是,转化后的这个图像虽是三通道的彩图,但是它呈现给我们肉眼的颜色却是黑灰白的色彩,但它的确是一张彩图,因为它三个通道的值都相等,这意味着rgb三种色彩的光的强度都一样,那我们肉眼看到的效果就是白光,所以图像呈现出来的就是黑白图像。#例4.1 将RGB\BGR图像转化为灰度图像 import cv2 import numpy as np img = np.random.randint(0,256, size=(2,4,3), dtype=np.uint8) img print('\n','---------------BGR转GRAY--------------------') cvt1 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) cvt1 print(img[1,0,0]*0.114 + img[1,0,1]*0.587 + img[1,0,2]*0.299 ) print('\n','----------------RGB转GRAY-------------------') cvt2 = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) cvt2 print(img[1,0,0]*0.299 + img[1,0,1]*0.587 + img[1,0,2]*0.114 )
#例4.2 将灰度图像转化为BGR图像 import cv2 import numpy as np img = np.random.randint(0, 256, size=(2,4), dtype=np.uint8) img cvt = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) cvt
灰度图像转到RGB/BGR色彩空间,所有通道的值都是相同的。
#例4.3 将RGB与BGR之间互相转换 import cv2 import numpy as np img = np.random.randint(0, 256, size=(2,4,3), dtype=np.uint8) img cvt_bgr2rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) cvt_bgr2rgb cvt_bgr2rgb = cv2.cvtColor(cvt_bgr2rgb, cv2.COLOR_RGB2BGR) cvt_bgr2rgb
#例4.4 将lenacolor转换为灰度图像 import cv2 lenacolor = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png') #默认的情况下就是BGR格式 print(lenacolor[0,0]) lenacolor1 = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png',0) #自动转换的情况下,也是由BGR格式通过公式转换 print(lenacolor1[0,0]) gray1 = cv2.cvtColor(lenacolor, cv2.COLOR_BGR2GRAY) print('226*0.299 + 137*0.587 + 125*0.114 = 162.243', '红+绿+蓝') gray1[0,0] gray2 = cv2.cvtColor(lenacolor, cv2.COLOR_RGB2GRAY) #默认原图像是BGR,此时原图像就强制变成RGB了. print('125*0.299 + 137*0.587 + 226*0.114 = 143.558', '红+绿+蓝') gray2[0,0] cv2.imshow('gray1', gray1) cv2.imshow('gray2', gray2) cv2.waitKey(10000) cv2.destroyAllWindows()
#例4.5 将lenacolor从BGR模式转为RGB模式 import cv2 lenacolor = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png') rgb_lenacolor = cv2.cvtColor(lenacolor, cv2.COLOR_BGR2RGB) cv2.imshow('lenacolor', lenacolor) cv2.imshow('rgb_lenacolor', rgb_lenacolor) cv2.waitKey(10000) cv2.destroyAllWindows()
二、HSV色彩空间
RGB是从硬件角度提出的颜色模型,我们人眼看到一个RGB像素点的像素值并不能立刻反应出这个值对应的颜色是什么,所以我们需要一种人类视觉可以直观的感知的一种颜色模型,这个颜色模型就是HSV,在HSV色彩空间中,用人眼去提取图像中特定的颜色的操作就非常直观。
H: Hue,色调,也称色相,色调的取值在[0, 360], 就是把所有的颜色分布在圆周上,不同角度代表不同的颜色。 但是,在8位图像内表示HSV图像时,要让色调的角度值映射到[0,255]范围内。在opencv中采取的方式是,色调的角度值/2,得到[0,180]之间的值,来适应8位二进制的图像数据存储的表示范围。所以H=0表示红色,H=60表示绿色,H=120表示蓝色,H=150是品红色,如此循环颜色。
所以,当一幅rgb图像转化为hsv图像后,我们只需要在H通道上截取特定的值,就能提取特定的颜色。换句话说,就是在hsv色彩空间,只有H通道决定颜色,图像颜色的差异只体现在H通道值的不同。所以,比如一幅人物图像,我们只需分辨人物的皮肤、头发、衣服等不同部位的H通道值,就可以提取出人物的不同部位。S: saturation,饱和度,就是颜色的纯度,当某个颜色中参入混合色(白色)时,饱和度就越小,当没有任何添加时,饱和度是1。饱和度的取值范围在[0,1]。在opencv中把饱和度的值映射到[0,255]范围内。
V: value,亮度,指人眼能感受到的色彩的明暗程度,亮度与物体的反射度有关。但是对于图片来讲,如果一个像素点的饱和度越小,就是渗入的白色越多,则其亮度就越高。亮度的取值范围也在[0,1]。在opencv中也把亮度值映射到[0,255]范围内。亮度值越大,图像越亮,亮度值越低,图像越暗。当亮度值=0,图像是纯黑色。#例4.6 测试RGB色彩空间中不同颜色的值转化到HSV色彩空间后的对应值: img = np.zeros((200,200,3), dtype=np.uint8) # bgr图片中的蓝色映射到hsv中: img_blue = img.copy() img_blue[:,:,0]=255 #把img的b通道值置为255,此时图像就是一张蓝色图像。 print('bgr图片中的蓝色数据=\n',img_blue[:3, :3, :]) #把b的前3行前3列切出来展示 img_blue_hsv = cv2.cvtColor(img_blue, cv2.COLOR_BGR2HSV) print('\nbgr中的蓝色映射到hsv中的数据=\n',img_blue_hsv[:3, :3, :]) #把转化位HSV的b的前3行前3列切出来展示 # bgr图片中的绿色映射到hsv中: img_green = img.copy() img_green[:,:,1]=255 print('\nbgr图片中的绿色数据=\n',img_green[:3, :3, :]) img_green_hsv = cv2.cvtColor(img_green, cv2.COLOR_BGR2HSV) print('\nbgr中的绿色映射到hsv中的数据=\n',img_green_hsv[:3, :3, :]) # bgr图片中的红色映射到hsv中: img_red = img.copy() img_red[:,:,2]=255 print('\nbgr图片中的红色数据=\n',img_red[:3, :3, :]) img_red_hsv = cv2.cvtColor(img_red, cv2.COLOR_BGR2HSV) print('\nbgr中的红色映射到hsv中的数据=\n',img_red_hsv[:3, :3, :]) cv2.imshow('img_blue', img_blue) cv2.imshow('img_blue_hsv', img_blue_hsv) cv2.imshow('img_green', img_green) cv2.imshow('img_green_hsv', img_green_hsv) cv2.imshow('img_red', img_red) cv2.imshow('img_red_hsv', img_red_hsv) cv2.waitKey(50000) cv2.destroyAllWindows()
三、通过图像的颜色(也就是H通道值)标记图像特定区域
根据图像的H值将我们想要的颜色显示出来,我们不想要的颜色屏蔽掉。具体操作步骤如下:
1、把BGR图像转化为HSV图像,用cv2.inRange()函数找到要显示的像素点,生成一个mask图像。
2、将原BGR图像与自身进行有掩码的按位与操作,就提取出了我们想要的像素点。 - cv2.inRange(parm1, parm2, parm3)函数
第一个参数位上是要查找的图像对象,第二个参数位表示查找范围的下界,第三个参数位表示要查找的范围的上界。
函数实现的功能是:将处于上下界之间的数置为255,将上下界之外的数置为0。
函数返回一个和parm1形状相同的对象。#例4.7 练习cv2.inRange()函数,将图像内[100,200]内的像素点提取出来 import cv2 import numpy as np img = np.random.randint(0,256,(5,5), dtype=np.uint8) img mask = cv2.inRange(img, 100, 200) #制作掩码图像 mask img_show = cv2.bitwise_and(img, img, mask=mask) #按位与运算提取特定像素点 img_show
array([[ 0, 0, 0, 0, 0], [ 0, 136, 162, 180, 0], [ 0, 0, 0, 0, 165], [ 0, 191, 165, 0, 0], [151, 0, 102, 0, 123]], dtype=uint8)
#例4.8 将感兴趣的区域显示出来,不感兴趣的区域置为0 import cv2 import numpy as np img = np.ones((5,5), dtype=np.uint8)*9 img mask = np.zeros((5,5), dtype=np.uint8) mask[0:3,0]=1 mask[2:5, 2:4]=1 mask img_show = cv2.bitwise_and(img, img, mask=mask) #一个数和自己按位与运算还是这个数本身 img_show
#例4.9 提取logo中的红色、绿色、蓝色 import cv2 import numpy as np logo = cv2.imread(r'C:\Users\25584\Desktop\opencv.jpg') hsv_logo = cv2.cvtColor(logo, cv2.COLOR_BGR2HSV) #先把bgr的logo图像转化到hsv色彩空间,因为这样我们才能提取到不同的颜色 cv2.imshow('yuan tu', logo) cv2.imshow('hsv_logo', hsv_logo) mask_blue = cv2.inRange(hsv_logo, np.array([110, 50, 50]), np.array([130, 255, 255])) #确定蓝色区域 blue_logo = cv2.bitwise_and(logo, logo, mask = mask_blue) #原图自己和自己按位与运算,参数设为上面的蓝色区域 cv2.imshow('blue_logo', blue_logo) mask_green = cv2.inRange(hsv_logo, np.array([50, 50, 50]), np.array([70, 255, 255])) #确定绿色区域 green_logo = cv2.bitwise_and(logo, logo, mask = mask_green) cv2.imshow('green_logo', green_logo) mask_red = cv2.inRange(hsv_logo, np.array([0, 50, 50]), np.array([30, 255, 255])) #确定红色区域 red_logo = cv2.bitwise_and(logo, logo, mask = mask_red) cv2.imshow('red_logo', red_logo) cv2.waitKey(10000) cv2.destroyAllWindows()
#例4.10 将人从图像的背景里分离出来,也就是将图像里面包含肤色的像素提取出来 import cv2 img = cv2.imread(r'C:\Users\25584\Desktop\lesson2.jpg') img[0,0:5,:] #切第1行的前5列这2个像素点看看 img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) img_hsv[0,0:5,:] #切第1行的前5列这2个像素点看看 h,s,v = cv2.split(img_hsv) #按通道把每个像素点的通道分离 hueMask = cv2.inRange(h, lowerb=5, upperb=170) #提取色调在[5, 170]之间 satMask = cv2.inRange(s, 25, 166) #提取饱和度在[25, 166]之间 mask = hueMask & satMask #两个掩膜取共同部分 roi = cv2.bitwise_and(img, img, mask=mask) cv2.imshow('img', img) cv2.imshow('roi', roi) cv2.waitKey(10000) cv2.destroyAllWindows()
#例4.11 调整hsv图像的v值,实现艺术效果 import cv2 img = cv2.imread(r'C:\Users\25584\Desktop\barbara.bmp') img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) h,s,v = cv2.split(img_hsv) v[:,:]=255 img_new_hsv = cv2.merge([h,s,v]) img_new = cv2.cvtColor(img_new_hsv, cv2.COLOR_HSV2BGR) cv2.imshow('img', img) cv2.imshow('img_new', img_new) cv2.waitKey(10000) cv2.destroyAllWindows()
四、alpha通道
在RGB色彩空间三个通道的基础上再增加一个透明度alpha通道,这种4通道的色彩空间称为RGBA色彩空间。PNG图像是一种典型的四通道图像。
alpha通道的取值范围是[0,1],或者是[0,255], 0表示完全透明,1(255)表示完全不透明。BGR/RGB图像转RGBA图像时,alpha通道默认为255,就是完全不透明。#例4.12 观察alpha通道对图像的影响 import cv2 import numpy as np import matplotlib.pyplot as plt img = np.random.randint(0,256, (2,3,3), np.uint8) img_bgra = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) b,g,r,a = cv2.split(img_bgra) a[:,:]=100 img_bgra_new = cv2.merge([b,g,r,a]) plt.subplot(131), plt.imshow(img) plt.subplot(132), plt.imshow(img_bgra) plt.subplot(133), plt.imshow(img_bgra_new)
#例4.13 观察alpha通道对lenacolor的影响 import cv2 import numpy as np import matplotlib.pyplot as plt lenacolor = cv2.imread(r'C:\Users\25584\Desktop\lenacolor.png') lenacolor_bgra = cv2.cvtColor(lenacolor, cv2.COLOR_BGR2BGRA) b,g,r,a = cv2.split(lenacolor_bgra) lenacolor_bgra = cv2.merge([r,g,b,a]) a[:,:]=100 lenacolor_bgra_new = cv2.merge([r,g,b,a]) a[:,:]=20 lenacolor_bgra_new1 = cv2.merge([r,g,b,a]) plt.subplot(141), plt.imshow(lenacolor[:,:,::-1]) plt.subplot(142), plt.imshow(lenacolor_bgra) plt.subplot(143), plt.imshow(lenacolor_bgra_new) plt.subplot(144), plt.imshow(lenacolor_bgra_new1)