【计算机视觉(二)】常用颜色空间及其转换

前情提要

在【计算机视觉(一)图像数据表示】中,我介绍了RGB灰度两种颜色空间,并且介绍了像素的概念以及在程序上如何访问。

本期内容

接下来介绍从RGB到灰度的转换,以及两种我常用的颜色空间HSV二值空间(严格来说属于灰度,只是只有0和255两个值)。

一、RGB转灰度

假如先不谈原理,RGB转灰度你会怎么做?先从我们知道的信息入手,RGB是三通道的,灰度只有一个通道,很自然的会联想到怎么把三个通道“融合”成一个通道,最直接的想法,也许是对于同一个RGB像素值,我们把这三个通道值求一个平均值作为灰度值。用公式表示一下就是:Gray = R * 1/3 + G * 1/ 3 + B * 1/3 。
好,那我们先来写这个程序看看效果。

# coding: utf-8
import cv2
import numpy as np

'''
函数名:rgb2gray_mean
功能:通过求通道平均值得到灰度图
输入:
img    输入的彩图
返回:
result    灰度图
'''
def rgb2gray_mean(img):
    ratio = 1.0 / 3
    # 转换类型
    int_img = img.astype(np.int32)
    
    result = ratio * (int_img[...,0]+int_img[...,1]+int_img[...,2])
    return result.astype(np.uint8)

# 程序入口
def main():
    # 读取lena图
    color = cv2.imread('../lena.jpg')
    # 转灰度
    gray = rgb2gray_mean(color)

    # 显示
    cv2.imshow('color', color)
    cv2.imshow('gray', gray)
    cv2.waitKey(0)

if __name__ == '__main__':
    main()

注意:在rgb2gray_mean函数中我对img做了一个类型转换并存放在int_img中,img的类型是numpy.uint8,也就是8位无符号整数,直接对里面的值进行相加很可能造成数值溢出,也就是255+1会变成0,因此要先用一个能容纳更大的值范围的矩阵装起来,比如int32类型的。这是新手经常会遇到的坑,切记切记。

好那我们来看看运行效果,再一次使用lena测试。


【计算机视觉(二)】常用颜色空间及其转换_第1张图片
lena

运行的结果:


【计算机视觉(二)】常用颜色空间及其转换_第2张图片
均值灰度

看起来还行。
OpenCV中也有自带的转换灰度的函数,cvtColor,代码也贴在这里:

gray = cv2.cvtColor(color, cv2.COLOR_BGR2GRAY)

注意第二个参数是BGR2GRAY,是的,OpenCV中默认的彩图通道排列是BGR(蓝绿红)而不是RGB,具体原因我也是道听途说,是因为一开始相机制造商从Sensor拿到的数据就是BGR的,这是他们制定的标准,尽管后来有很多软件也是默认采用RGB。
那么这个cvtColor的效果怎么样呢,请看


【计算机视觉(二)】常用颜色空间及其转换_第3张图片
OpenCV转灰度

不知道你能否看出区别,不能的话请注意看头发的明暗交界处,cvtColor在本来该黑的地方更黑,该白的地方更白,就是对比度更强烈,还是看不出来的话用程序帮我们看看,数一下灰度值不相等的地方有多少个。

print np.sum(gray != cv_gray)

结果是:244157, 原图的大小是512x512,也就是总共262144个像素点,那这么看下来绝大部分的像素值都不一样,虽然我们看起来也要费点力才能看出来不同。这就说明OpenCV转灰度的方法跟我们拍脑门想的是不一样的,也就是对应到三个通道各自乘的系数是不一样的,假设:

Gray = a * R + b * G + c * B (a + b + c = 1)

a、b、c三个值应该怎么取能让图像看起来比较舒服呢?其实人眼对三原色的“偏好”是不一样的,研究表明人眼对红绿蓝的权重接近3:6:1,更精确的,对于上面的公式,a=0.299,b=0.587,c=0.114,这就是通常所说的心理学模型。在OpenCV中,为了减少浮点运算(浮点运算在一般的CPU中很耗时),使用了类似下面的转换方式:

'''
函数名:心理学模型转换灰度
输入:
img    输入的彩图
返回:
result    灰度图
'''
def rgb2gray_mental(img):
    # 转换类型
    int_img = img.astype(np.int32)
    result = (int_img[...,2]*299 + int_img[...,1]*587 + int_img[...,0]*114 + 500) / 1000
    return result.astype(np.uint8)

经过测试,这跟OpenCV自带的转换函数cvtColor的效果是一模一样的。

二、HSV空间

HSV空间是由RGB空间演变过来的,RGB空间在几何上是一个正方体(详情请查阅【计算机视觉(一)图像数据表示】),而当你从正方体的一个顶点看向离它最远的另一个顶点时,就会看到一个六角锥体,这就是HSV空间的几何表达,如下图:

【计算机视觉(二)】常用颜色空间及其转换_第4张图片
六角锥体

通常,我们会把顶上的这个六边形近似成一个圆,就变成了一个圆锥,像这样:
【计算机视觉(二)】常用颜色空间及其转换_第5张图片
HSV圆锥

HSV三个轴的走动方向也在图上标出了,可以理解为一个有深度的极坐标系,我们只看Hue轴,沿着圆周方向走,可以看到每转动一定的角度,所表示的颜色就变了,而且不止红绿蓝,事实上这对应了我们认知上的所有颜色,因此,使用HSV空间的好处就是只要一个维度我们就能表示物体本来的颜色。那其他两个轴是表达什么信息的呢?具体可以参考 百度百科,我这里只说些个人见解。S维度表示饱和度,更通俗的说,是打在物体上的白灯的亮度,比如你在黑夜中用手电筒照着个苹果,电量足够的话苹果看上去当然是正常的红色,电量不够也许看上去是暗红色,关掉手电也就啥都看不到黑漆漆一片了,这就是S维度的信息。V维度表示了一种物体自身的材质信息,是不是透明的,透明度有多少,同样拿照苹果这个例子,如果现在这个苹果换成了水晶苹果,灯光打上去以后看到的颜色也是不一样的,即使当初涂的颜色跟真实的苹果颜色一样,这是由于一些漫反射和折射造成的。

HSV的值域跟RGB的并不一样,其中H维度的值域是0-180,其他两个维度都是0-255,H不取到0-360也许是因为超过了uchar的范围。

HSV空间对颜色的描述是用户友好的,而RGB是硬件友好的。HSV空间在我日常学习中更多是作为颜色筛选的基础,人眼能区分的不同颜色的范围对应HSV的值都比较固定,下面是一个对照表:


【计算机视觉(二)】常用颜色空间及其转换_第6张图片
HSV颜色对照表

现在举个实际应用的案例,比如我要对下面的图片做车牌识别,我的第一步是要把车牌的区域抠出来,车牌的底色是蓝色的,当然宝马的标志也有蓝色,但我们可以不管三七二十一先把蓝色的东西都抠出来再做筛选,这时候就可以用HSV空间。


【计算机视觉(二)】常用颜色空间及其转换_第7张图片
车车

图片刚读入的时候是BGR格式的,这时候又要用到cvtColor转换到HSV,示例代码如:

hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV)

显示HSV的效果如下:


【计算机视觉(二)】常用颜色空间及其转换_第8张图片
hsv.jpg

现在我们要的是蓝色区域,查找上面的对照表就可以知道,我们要的是H值在100到124,S值在43到255,V值在46到255之间的像素点。怎么表达这个“要”跟“不要”呢?我们可以这么考虑,对于每个像素点只有“要”和“不要”两种状态,就像一盏灯的开关一样,于是我们可以创建一幅等大的图,在上面,“要”的像素点设为一个值,“不要”的像素点设为另一个值,这就是接下来要说的二值图,我也喜欢称其为掩码图。

三、二值图(掩码图)

二值图本质上是灰度图,只是只用了0和255两个值,用0表示“不要”,255表示“要”,整幅图看起来就是符合条件的区域是白色的,不符合的是黑色的。对上面的HSV图作这种条件筛选处理,叫做二值化,OpenCV已经为我们准备了对应的函数cv2.inRange,示例代码如下:

mask = cv2.inRange(hsv, np.array([100,43,46]), np.array([124,255,255]))

处理完的结果如下图:


【计算机视觉(二)】常用颜色空间及其转换_第9张图片
mask.jpg

可以看到,尽管有很多干扰的白色区域,车牌区域还是在里面,我们只要采取进一步的手段就可以把车牌区域筛选出来,这就要涉及一些形态学处理,敬请期待下一篇文章。

你可能感兴趣的:(【计算机视觉(二)】常用颜色空间及其转换)