4.4 HSV色彩空间讨论
RGB色彩空间是一种被广泛接受的色彩空间,但是该色彩空间过于抽象,我们不能够直接通过其值感知具体的色彩。我们更习惯使用直观的方式来感知颜色,HSV色彩空间提供了这样的方式。通过HSV色彩空间,我们能够更加方便地通过色调、饱和度和亮度来感知颜色。
其实,除了HSV色彩空间,我们讨论的其他大多数色彩空间都不方便人们对颜色进行理解和解释。例如,现实中我们根本不可能用每种颜料的百分比(RGB色彩空间)来形容一件衣服的颜色。
4.4.1 基础知识
4.1.4节已经对HSV色彩空间进行了简单介绍,为了方便读者更好地理解后续例题,本节将从其值范围的角度对相关知识点分析说明。
HSV色彩空间从心理学和视觉的角度出发,提出人眼的色彩知觉主要包含三要素:
● H:色调(Hue,也称为色相)。
● S:饱和度(Saturation)。
● V:亮度(Value)。
1.色调H
在HSV色彩空间中,色调H的取值范围是[0,360]。8位图像内每个像素点所能表示的灰度级有28=256个,所以在8位图像内表示HSV图像时,要把色调的角度值映射到[0,255]范围内。在OpenCV中,可以直接把色调的角度值除以2,得到[0,180]之间的值,以适应8位二进制(256个灰度级)的存储和表示范围。
在HSV空间中,色调值为0表示红色,色调值为300表示品红色,具体如表4-3所示。
表4-3 色调值及对应颜色
根据上述分析可知,每个色调值对应一个指定的色彩,而与饱和度和亮度无关。在OpenCV中,将色调值除以2之后,会得到如表4-4所示的色调值与对应的颜色。
表4-4 映射后色调值及对应颜色
确定值范围后,就可以直接在图像的H通道内查找对应的值,从而找到特定的颜色。例如,在HSV图像中,H通道内值为120的像素点对应蓝色。查找H通道内值为120的像素点,找到的就是蓝色像素点。
在上述基础上,通过分析各种不同对象对应的HSV值,便可以查找不同的对象。例如,通过分析得到肤色的HSV值,就可以直接在图像内根据肤色的HSV值来查找人脸(等皮肤)区域。
2.饱和度S
通过4.1.4节中的介绍可知,饱和度值的范围是[0,1],所以针对饱和度,需要说明以下问题:
● 灰度颜色所包含的R、G、B的成分是相等的,相当于一种极不饱和的颜色。所以,灰度颜色的饱和度值是0。
● 作为灰度图像显示时,较亮区域对应的颜色具有较高的饱和度。
● 如果颜色的饱和度很低,那么它计算所得色调就不可靠。
我们在4.3节介绍cv2.cvtColor()函数时曾指出,进行色彩空间转换后,为了适应8位图的256个像素级,需要将新色彩空间内的数值映射到[0,255]范围内。所以,同样要将饱和度S的值从[0,1]范围映射到[0,255]范围内。
3.亮度V
通过4.1.4节的介绍可知,亮度的范围与饱和度的范围一致,都是[0,1]。同样,亮度值在OpenCV内也将值映射到[0,255]范围内。
亮度值越大,图像越亮;亮度值越低,图像越暗。当亮度值为0时,图像是纯黑色。
4.4.2 获取指定颜色
可以通过多种方式获取RGB色彩空间的颜色值在HSV色彩空间内所对应的值。例如,可以通过图像编辑软件或者在线网站获取RGB值所对应的HSV值。
需要注意,在从RGB/BGR色彩空间转换到HSV色彩空间时,OpenCV为了满足8位图的要求,对HSV空间的值进行了映射处理。所以,通过软件或者网站获取的HSV值还需要被进一步映射,才能与OpenCV中的HSV值一致。
在本节中,我们通过程序查看一幅图像在OpenCV内从RGB色彩空间变换到HSV色彩空间后,各个分量的值。
【例4.6】在OpenCV中,测试RGB色彩空间中不同颜色的值转换到HSV色彩空间后的对应值。
为了方便理解,这里分别对蓝色、绿色、红色的单个像素点进行测试。
首先,使用np.zeros([1,1,3], dtype=np.uint8)来生成一幅仅有一个像素点的图像(数组)。
接下来,通过对其中的通道分量赋值,将其设定为指定的颜色。例如:
● 语句imgBlue[0,0,0]=255可以将该像素点的第0个通道(即B通道)的值设置为255,即将该点的颜色指定为蓝色。
● 语句imgGreen[0,0,1]=255可以将该像素点的第1个通道(即G通道)的值设置为255,即将该点的颜色指定为绿色。
● 语句imgRed[0,0,2]=255可以将该像素点的第2个通道(即R通道)的值设置为255,即将该点的颜色指定为红色。
然后,可以通过语句cv2.cvtColor(Blue, cv2.COLOR_BGR2HSV)将Blue从BGR色彩空间转换到HSV色彩空间。
最后,通过打印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)
运行程序,会显示如下所示的运行结果:
Blue= [[[255 0 0]]] BlueHSV= [[[120255255]]] Green= [[[ 0255 0]]] GreenHSV= [[[ 60255255]]] Red= [[[ 0 0255]]] RedHSV= [[[ 0255255]]]
从运行结果可以看到,各种颜色的值与表4-4所列出的情况一致。
4.4.3 标记指定颜色
在HSV色彩空间中,H通道(饱和度Hue通道)对应不同的颜色。或者换个角度理解,颜色的差异主要体现在H通道值的不同上。所以,通过对H通道值进行筛选,便能够筛选出特定的颜色。例如,在一幅HSV图像中,如果通过控制仅仅将H通道内值为240(在OpenCV内被调整为120)的像素显示出来,那么图像中就会仅仅显示蓝色部分。
本节将首先通过例题展示一些实现上的细节问题,然后通过具体例题展示如何将图像内的特定颜色标记出来,即将一幅图像内的其他颜色屏蔽,仅仅将特定颜色显示出来。
1.通过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。
【例4.7】使用函数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)
运行程序,会显示如下所示的运行结果。
img= [[129155 99 51182] [ 57130235135110] [232182194 13 26] [111 7136190 55] [ 35144 9255187]] mask= [[255255 0 0255] [ 0255 0255255] [ 0255255 0 0] [255 0255255 0] [ 0255 0 0255]]
通过本例题可以看出,通过函数cv2.inRange()可以将数组(图像)内指定范围内的值标注出来,在返回的mask中,其值取决于img中对应位置上的值是否在inRange所指定的[100,200]内:
● 如果img值位于该指定区间内,则mask对应位置上的值为255。
● 如果img值不在该指定区间内,则mask对应位置上的值为0。
返回的结果mask可以理解为一个掩码数组,其大小与原始数组一致。
2.通过基于掩码的按位与显示ROI
【例4.8】正常显示某个图像内的感兴趣区域(ROI),而将其余区域显示为黑色。
为了方便理解,这里采用一个二维数组模拟图像,完成操作。题目中要求将不感兴趣区域以黑色显示,可以通过设置掩码的方式将该区域的值置为0来实现。
根据题目要求,设计程序如下:
import cv2 import numpy as np img=np.ones([5,5], dtype=np.uint8)*9 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)
在本例中,通过mask设置了两个感兴趣区域(掩模)。后续通过在按位与运算中设置掩模的方式,将原始图像img内这两部分的值保留显示,而将其余部分的值置零。
运行程序,会显示如下所示的运行结果:
img= [[9 9 9 9 9] [9 9 9 9 9] [9 9 9 9 9] [9 9 9 9 9] [9 9 9 9 9]] 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= [[9 0 0 0 0] [9 0 0 0 0] [9 0 9 9 0] [0 0 9 9 0] [0 0 9 9 0]]
3.显示特定颜色值
【例4.9】分别提取OpenCV的logo图像内的红色、绿色、蓝色。
需要注意,在实际提取颜色时,往往不是提取一个特定的值,而是提取一个颜色区间。例如,在OpenCV中的HSV模式内,蓝色在H通道内的值是120。在提取蓝色时,通常将“蓝色值120”附近的一个区间的值作为提取范围。该区间的半径通常为10左右,例如通常提取[120-10,120+10]范围内的值来指定蓝色。
相比之下,HSV模式中S通道、V通道的值的取值范围一般是[100,255]。这主要是因为,当饱和度和亮度太低时,计算出来的色调可能就不可靠了。
根据上述分析,各种颜色的HSV区间值分布在[H-10,100,100]和[H+10,255,255]之间。因此,各种颜色值的范围为:
● 蓝色:值分布在[110,100,100]和[130,255,255]之间。
● 绿色:值分布在[50,100,100]和[70,255,255]之间。
● 红色:值分布在[0,100,100]和[10,255,255]之间。
根据前述例题的相关介绍,首先利用函数cv2.inRange()查找指定颜色区域,然后利用基于掩码的按位与运算将指定颜色提取出来。
根据题目要求,设计程序如下:
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()
运行程序,结果如图4-4所示,其中:
图4-4 【例4.9】程序的运行结果
● 图(a)是原始图像。
● 图(b)是从图(a)中提取得到的蓝色部分。
● 图(c)是从图(a)中提取得到的绿色部分。
● 图(d)是从图(a)中提取得到的红色部分。
由于本书为黑白印刷,所以为了更好地观察运行效果,请大家亲自上机运行程序。
4.4.4 标记肤色
在标记特定颜色的基础上,可以将标注范围进一步推广到特定的对象上。例如,通过分析可以估算出肤色在HSV色彩空间内的范围值。在HSV空间内筛选出肤色范围内的值,即可将图像内包含肤色的部分提取出来。
这里将肤色范围划定为:
● 色调值在[5, 170]之间
● 饱和度值在[25, 166]之间
【例4.10】提取一幅图像内的肤色部分。
根据题目要求,设计程序如下:
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()
运行程序,结果如图4-5所示,程序实现了将人的图像从背景内分离出来。其中:
图4-5 【例4.10】程序的运行结果
● 左侧是原始图像,图像背景是白色的。
● 右侧是提取结果,提取后的图像仅保留了人像肤色(包含衣服)部分,背景为黑色。
由于本书是黑白印刷,无法很好地感受到颜色的变化。为了更好地观察上述效果,请大家亲自上机运行程序。
4.4.5 实现艺术效果
在HSV色彩空间内进行分量值的调整能够生成一些有趣的效果。一些图像处理软件正是利用对HSV色彩空间内值的调整来实现各种艺术效果的。
在本节中,将一幅图像的H通道和S通道的值保持不变,而将其V通道的值都调整为255,即设置为最亮,观察得到的艺术效果。
【例4.11】调整HSV色彩空间内V通道的值,观察其处理结果。
可以任意改变图像内各个通道的值,观察其最终的显示效果。本例中,我们改变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()
运行程序,结果如图4-6所示。其中,左侧是原始图像,右侧是艺术效果。
图4-6 艺术效果