〇 引子
图像有多种色彩模型,如灰度图、RGB、HSV、YCbCr等等。这些图片文件在计算机中以不同的格式存在,如JPEG、png。图像的原始存储格式限定了读取到内存后的存在形式,在图像处理、计算机视觉中通常需要处理特定格式的图像,这就需要经常转换图像格式。
如对于彩色图像的直方图均衡需要图像的luma层,但大多图像为JPEG格式,只有R、G、B三个通道,在进行直方图均衡化之前需要转换为YCrCb格式。下图对比了对彩色图像R、G、B三个通道单独直方图均衡化(上)和对luma层直方图均衡化(下)的效果。
本文第一节将简单介绍常见的图像色彩空间,第二节介绍OpenCV文件读取、色彩模型转换,最后是直方图均衡化的应用case。
一 色彩模型
色彩模型、色彩空间和色彩系统是三个不同的概念。这三个概念往往会同时出现,经常彼此代替,理解其中的区别更利于以后的工作。
归根到底,色彩是人(动物)对光的感受,眼睛作为传感器接收外界的光波,传递给大脑,大脑反映出具体的色彩感受。所以对色彩的研究至少包括以下三个方面:
- 真实世界的光波(波长、强度)
- 眼睛(晶状体、视网膜、视神经细胞等)
- 大脑(感觉)
灰度图
单通道图像。每个像素由一个8bit
整数表示灰阶,像素值范围是[0, 255]
。 0
表示黑色, 255
表示白色,中间值表示过渡。
RGB
每个图像由R(red)、G(green)、B(blue)三个通道组成,一个像素点的像素值用一个三维向量表示[r, g, b]
。向量中的每个分量表示在对应通道上的颜色值,也是8bit
整数,范围是[0,255]
。web前端和手机app开发的同学应该对这种色彩空间不会陌生。
HSV
H (Hue)是色调、色相。S(Saturation)是饱和度,V(Value)表示黑暗或明亮的程度。H色相是HSV中最重要的特征,简单讲,H决定了红、橙、黄、绿的特征。S饱和度又称为彩度、色彩浓度(chroma),从色彩最大到无色彩(黑/白/灰)的程度。
Y'CbCr
严格来说,这不是一种色彩模型,而是一种luma+chroma的编码方案。Y'CbCr
和YCbCr
的概念也有一段历史。Y
是颜色空间内的luminance 流明、亮度
分量,Y'
表示luma 亮度
分量,这两个概念有差别。大部分时候,我们实际中用到的都是luma
,所以很多库和程序中都按luma
分量计算,不区分lumiance
和luma
。如果你对Y luminance
和Y' luma
的内容感兴趣,请阅读Understanding Luminance and Chrominance。Cb
和Cr
分别是蓝色和红色的色彩偏移量。由于人眼对Y
分量最敏感,因此基于JPEG格式的压缩技术通常压缩非Y
分量。
这里只是略微讲了一下色彩模型和理论,强烈推荐UI 设计知识库 [01] 色彩 · 理论加强学习。
二 OpenCV
图像文件读取
import cv2
image = cv2.imread('./path/to/image', flag)
以flag
的方式读取image
文件。flag
包括以下几种常见值:
-
cv2.IMREAD_COLOR
, 1, 默认值,返回BGR图像,会丢弃图像的透明通道。 -
cv2.IMREAD_GRAY
,0, 灰度图。 -
cv2.IMREAD_UNCHANGED
,-1,保持图像的透明通道。
在默认cv2.IMREAD_COLOR
方式下,返回的图像是BGR图像,注意区分OpenCV返回的不是RGB图像,通道顺序相反。即使是灰度图,也会返回BRG格式。在施加变换的时候就要特别注意通道顺序。
BGR图像的三个通道像素值大多采用[0, 255]
的范围,但也有在[0, 65535]
或者[0, 1]
。值域范围对线性变化没有任何影响,但是如果对图像进行非线性变换就要特别注意,例如BGR图像到LUV图像的变换。
彩色空间转化
OpenCV中转化图像格式使用cvtColor(img, flag)
,下面的代码演示了转换BGR图像到HSV图像的过程。
import cv2
img_bgr = cv2.imread('lena.jpg') # 默认BGR
# convert BGR image to HSV image
img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
方法很简单,重点是你要清醒地知道你需要什么样的图像格式才能完成转化。OpenCV中提供了数百种图像彩色空间的转换方法,也就是flag
有数百种选择,下面代码会输出所有支持的参数:
import cv2
flags = [ flag for flag in dir(cv2) if flag.startswith('COLOR_') ]
for flag in flags:
print('flag: cv2.{0}, value = {1}'.format(flag, eval('cv2.'+flag)))
执行上面代码会输出258种参数(OpenCV 3.1.0),部分截图如下:
是不是被这么多的方法吓到了?如果直接输出上面参数的值,你会发现一些参数是相等的,例如cv2.COLOR_BGR2YCR_CB
和cv2.COLOR_BGR2CrCb
都是36
,因为这两种方法是一样的,这样的设计应该只是为了输入方便而已【微笑】。
具体的转换公式需要更多专业的图像处理知识,OpenCV说明文档给出了一些简单的说明可供参考。
BGR和YCrCb色彩空间转换
OpenCV中BGR
和YCrCb
的色彩空间转换公式:
公式中的delta
值:
维基百科中JPEG转化公式:
可以看出OpenCV中的Y
即维基百科中的Y'
。
下面的代码片段演示了如何分理彩色图像的Y
通道:
import cv2
img_bgr = cv2.imread('./deer.jpg', cv2.IMREAD_COLOR)
img_ycrcb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2YCrCb)
y, cr, cb = cv2.split(img_ycrcb)
cv2.imwrite('./deer_y.jpg')
cv2.imshow('y', y)
cv2.waitKey()
cv2.destroyAllWindows()
三 彩色图像直方图均衡化
对彩色图像的直方图均衡化步骤如下:
- 读取彩色图像,得到BGR格式的图像矩阵
- 转化色彩空间,BGR 到 YCrCb
- 分离(split)YCrCb图像中的Y分量,直方图均衡化
- 合并(merge)Y分量和Cr、Cb分量,得到直方图均衡化后的YCrCb格式的图像矩阵
- 转化YCrCb格式到BGR格式并保存
import cv2
img = cv2.imread('./deer.jpg', cv2.IMREAD_COLOR)
# 单独对B、G、R三个通道进行直方图均衡化
b, g, r = cv2.split(img)
b_eqhist = cv2.equalizeHist(b)
g_eqhist = cv2.equalizeHist(g)
r_eqhist = cv2.equalizeHist(r)
img_eqhist_bgr = cv2.merge([b_eqhist, g_eqhist, r_eqhist])
cv2.imwrite('deer_eq_bgr.jpg', img_eqhist_bgr)
# 转换到YCrCb色彩空间,对Y分量进行直方图均衡化
img_ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
y, cr, cb = cv2.split(img_ycrcb)
y = cv2.equalizeHist(y)
img_ycrcb = cv2.merge([y, cr, cb])
img = cv2.cvtColor(img_ycrcb, cv2.COLOR_YCrCb2BGR)
cv2.imwrite('deer_eq.jpg', img)