在图像处理的过程中,非常重要的一部分便是对图像所包含的色彩进行分析,从而可以获得该图像在整体层面上的相关特征(Feature)。而在色彩分析过程中,色深分布的分析又是极为基础、重要的一部分。在这篇文章里,我们就一起去了解一下色深以及色彩分布直方图的概念,并进一步研究一下怎样用 Python 来对图像进行色彩取值分布分析,并最终将结果转化为分布直方图(使用了 Python 和 SimpleCV 后,这真的是一个很简单的过程)。

色深

在介绍绘制色彩分布直方图之前,我们先回顾下色深(Color Depth)的概念(我们只粗糙地介绍下,不深究更多的技术细节)。我们已经知道一个常识,那就是,一张最终展示给我们的图片,最终都是由许许多多的像素所组成的二维平面。不同格式、类型的图片是拥有不同类型的像素单元的——对于灰度图片,每个像素只需要表示该位置的灰度等级,所以只需要一个“数据单元”便可以记录下色彩数据;对于基于最常见的 RGB 色彩空间的彩色图片,我们需要在每个单元格里面保存红、绿、蓝三种色彩的色彩信息,最终通过不同的“色彩深度”来完成其它颜色的混合生成,所以对于彩色图片而言,至少需要三个色彩单元来分别记录红、绿、蓝色的“色彩深度”。而无论是对于灰度图片像素色彩的“灰度”,还是彩色图片像素三基础色的“色彩深度”,都存在一个取值的问题——也就是我们的“数据单元”保存的数据所能表示的颜色值的值域(0-1、0-15 还是 0-255,或者更大)——通过取不同的值,表达不同等级的灰度或者不同等级的基础色混合方案,最终会显示不同的色彩。而我们知道,计算机中的数据最终都是以二进制位集合的形式来存储的,这里的图像数据存储单元也是如此——灰度或者基础色“色彩深度”的取值最终都是存储在二进制位集合中的。

保存色彩基本信息的“数据单元”的位数便被称之为色深。

拿我们或多或少听说过的“真彩色”(True Colors)色彩存储方案来举例说明:它一共使用了包含 3 个“数据单元”的 24 位来存储色彩信息,而其中用于储存红色、绿色、蓝色的“色彩深度”取值的“数据单元”各占 8 位,那么,“真彩色”色彩存储方案的色深就是 8 位。“真彩色”是现在使用较为广泛的色彩存储方案之一,因为它兼顾了占用空间和显示颜色的广度(也就是色域)——“真彩色”所展示的颜色细腻程度已经让人眼无法感觉出色彩和真实色彩之间的差别(这也是它被成为“真彩色”的原因),而使用的 24 位存储空间也符合现实世界中存储容量的情况。当然,除了“真彩色”存储方案外,还有其它色域更窄但占存储空间小或者色域更广但占存储空间更大的存储方案存在,它们的色深进而也是更“浅”或者更“深”的。

我们可以这样简单地理解:一张图片的色深代表了它能表达色彩的广度,色深越深,所能表达的色彩广度越大,能够展示的色彩种类越多,但同时所占用的空间越大

对于不严谨表述的备注

我们在这里进行的概念解释极为粗糙、感性,出现了“色彩深度”、“数据单元”这些很不规范的表达。更严谨的介绍请参考各关键概念给出的维基百科链接,并进一步根据索引来查找相关权威专著。

图像色彩分布直方图

接下来我们再来了解一下色彩分布直方图(Image Histogram)简而言之,色彩分布直方图是图像数据在相应色深下色彩取值的分布统计直方图。例如,下图便是一张图片仅打开某个基础色通道)转化而来的灰度图片的色彩分布直方图(没错,它来自 Photoshop):

从这张色彩分布直方图中,我们可以看到这个基础色在自己的可取值值域内的分布情况。这样的基础色取值分布情况是图像很重要的全局特性之一,在图像数据分析、处理上有很重要的应用

利用 SimpleCV 获取图像的色彩分布信息

接下来我们将利用 SimpleCV 获取图像的基础色的色彩分布直方图。对于编写代码而言,这是一个非常简单的过程:

1
2
3
4
5
6
7
8
from SimpleCV import *
lena = Image('lenna') #我们使用 Lena 图作演示

(red, green, blue) = lena.splitChannels(False) #获取 Lena 图的三基础色通道

red_histogram = red.histogram(256) #获取红色通道的分布直方图,参数表示直方图的横轴长度,8 位则有 256 种取值,下同
green_histogram = green.histogram(256)
blue_histogram = blue.histogram(256)

是的,就是这么简单。具体步骤的解释在源码中已经用注释的方式给出,不再赘述。接下来,我们要做的工作便是将这些直方图信息使用工具展示出来。我们使用 matplotlib 来完成这个任务。如果你没有安装这个库的话,请运行如下命令来安装它:

1
sudo pip install matplotlib

我们将使用 matplotlib 的 pyplot 来完成直方图的绘制,并最终保存为图片数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from SimpleCV import *
import matplotlib.pyplot as ppt
lena = Image('lenna') #我们使用 Lena 图作演示

(red, green, blue) = lena.splitChannels(False) #获取 Lena 图的三基础色通道

red_histogram = red.histogram(256) #获取红色通道的分布直方图,参数表示直方图的横轴长度,8 位则有 256 种取值,下同
green_histogram = green.histogram(256)
blue_histogram = blue.histogram(256)

ppt.bar(range(len(red_histogram)), red_histogram) #将红色通道的分布数据绘制为直方图并保存为图片,下同
ppt.savefig('red_histogram.png')

ppt.bar(range(len(green_histogram)), green_histogram)
ppt.savefig('green_histogram.png')

ppt.bar(range(len(blue_histogram)), blue_histogram)
ppt.savefig('blue_histogram.png')

我们最终获得的三个基础色颜色分布直方图如下所示:

红色通道颜色分布直方图

绿色通道颜色分布直方图

蓝色通道颜色分布直方图

到了这里,我们所有的工作便已经完成了——我们成功获取了 Lena 图的三个基础色通道的色彩分布直方图。从图中不难看出,横轴代表了相应基础色的某个取值,而纵轴则代表了整个图片中相应基础色的取值为该值的像素的个数。真的不得不赞一下——这个过程在 SimpleCV 和 matplotlib 的帮助下,变得很简单、很优雅了。

小结

回顾一下我们现在所做的工作,其实也不算复杂——不过是对于一张图片内的像素进行了一次简单统计工作。但是,问题的关键在于,我们理解了这里面所包含的概念、原理没有。另外,基于色彩分布的图像处理方法是非常多的——譬如图片全局的色温调整、偏色图片的修复等——可见,学会以上所说的这些,还远远不够。