Skip to main content

DynamicX 视觉组学习笔记 - 基于颜色的图像分割

· 13 min read

图像处理是一个我之前没有接触过的领域,对于数字图像的了解也仅限于 RGB,色品图见过很多次但几乎不明白什么意思,HSV 也在 PS 里见过但完全不知道那是 HSV,对于 RGB 转 CMYK 的色差问题也摸不着头脑。这段时间读到了章佳杰的专栏文章感觉茅塞顿开,文中详细地讲解了从选取三基色到 HSV、HSL 色彩空间的知识,让我感觉目前的数字图像选择了RGB作为最常见的色彩表现方式很大程度上是实验与实践的结果。但关于各个色彩空间的转换计算,以我目前的知识还不足以看懂,而在 OpenCV 的代码实现上也不会涉及具体计算,都是封装好的函数。

理论基础#

?这部分基本上是对章佳杰的专栏文章、维基百科的转述和引用

CIE 1931 RGB#

中学的生物课本上便讲过人类的三种视锥细胞 L/M/S 对应了三种最敏感颜色 R/G/B,让人很自然地认为数字图像使用 RGB 就是因为这个生物学基础。实际上确实如此,选择 RGB 可以更容易地实现单独刺激三种视锥细胞,利用格拉斯曼定律(人眼对颜色混合的反应是线性的)可以得到 RGB 混合成其他各种颜色的色匹配函数。色匹配函数C=rR+gG+bB是通过实验获得的 RGB 混合为其他颜色光的强度系数函数,通过色匹配函数定义的色彩空间就是 CIE RGB 色彩空间。CIE RGB 包含了所有人类可以感知的色彩,是最接近人眼实际的颜色空间。

色彩空间#

自然界本身是没有「颜色」这个属性的,只有对不同波长光线的反射率/透过率,到达人眼中的,显然是一个连续的光谱分布函数。数学上,这是一个无穷维的函数空间(巴拿赫空间)。而人眼内的三种视锥细胞,它们的感光特性曲线相当于是在这个无穷维的函数空间中建立了三个基底。任何一个光谱分布进来,三种视锥细胞被激发。由于色视觉响应的线性性,这一过程相当于光谱分布函数与三个基底做内积,或者说,「投影」到这三个基底上。

从这个观点看,人类的色视觉,是相当于在自然界所有颜色的无穷维函数空间中取了一个三维的投影。这个三维空间的基底,既可以是视锥细胞的感光特性曲线(我们的大脑就用的是这套),当然也可以是选取三种颜色的光进行组合(CIE RGB 空间),甚至还可以是用实际中不存在的「光线」进行组合 (CIE XYZ 空间)。既然这几个空间实际上是同一个线性空间,只不过由于选择了不同的基底而有不同的表达形式,那么根据线性代数的结论,这几个空间的表述形式之间,只需要通过矩阵乘法就可以完成转换,这是完全的线性变换。

CIE 1931 XYZ#

由于色匹配函数中存在负数值,所以为了方便计算又定义了通过线性变换得到的 CIE XYZ 色彩空间,同样包含了所有人类可以感知的色彩,但没有负数值。 同时为了方便电子器件的颜色表达还定义了与设备相关的 sRGB、Adobe RGB 等色彩空间,这些色彩空间往往没有包含所有人眼可见颜色。虽然在数学上各个色彩空间可以相互转换,但由于现实中不存在负数值的颜色,所以为了减少颜色损失和增强工程稳定性,CIE XYZ 常用来定义其他设备相关的色彩空间。精确定义每个色彩空间与 CIE XYZ 的转换关系,就可以将 CIE XYZ 作为媒介实现各个色彩空间的转换。

色品图与 sRGB#

由于色彩空间是一个三维空间,CIE XYZ 的形状也非常不规则,所以为了方便表达将 CIE XYZ 投影到x+y+z=1平面上得到色品图,CIE XYZ 的形状是舌型的,而其他几个常见的 RGB 色彩空间都是三角形的。这里有一个很有趣的事我认为原文没有描述清晰,直角坐标中的色品图可以描述各个 RGB 色彩空间的关系,我们常用的 sRGB 色彩空间的显示器却能够显示 CIE XYZ 色彩空间的色品图,色品图中填充的颜色到底是什么颜色?某些色品图确实是选取了少量的颜色方便直观理解,但原文中的这张图是通过 CIE XYZ 转换到 sRGB 得来的,可以看到图中 R G B 三个点围成的三角形有明显的边界,这是因为 sRGB 牺牲了色彩准确性,将色彩空间内的一部分用来表达真正的 sRGB 色彩空间,将另一部分用来表达 sRGB 色彩空间外的颜色。这也说明了为什么用 sRGB 显示器不做转换地显示 Adobe RGB 图像会显得灰暗。我感觉伽马矫正和这种处理方式有着相似之处。

Gamma 空间

HSV 和 HSL#

RGB 虽然是最贴近电子设备和人眼的表达方式,但并不适合进行图像处理,所以通过将直角坐标的色彩空间转换到圆柱坐标中得到了 HSV(又称 HSB) 和 HSL 色彩空间(下面仅讨论 HSV 色彩空间),这是一种非线性变换,变换得到的三个值分别表示色相(H)、饱和度(S)、明度(V),这种变换的一个好处在于用单个值表示颜色,非常适合本文的基于颜色的图像分割的需求。

但是色彩属性和物理学中的光谱并不是完全对应的,物理学的人类可见光谱是有两个端点的直线形,并不能形成一个环。当然每种颜色都可以找到相应的光波长,但都有一个范围,并不是单一的波长。明度一般和具体某种颜色的光波能量相当,但和整个光谱的能量无关(因为每种波长的光的能量都不相同)。HSV颜色空间在技术上不支持到辐射测定中测量的物理能量谱密度的一一映射。所以一般不建议做在HSV坐标和物理光性质如波长和振幅之间的直接比较。

代码实践#

通过上面的理论分析,分割颜色的方法已经非常明显了,就是通过将图片转换为 HSV 颜色空间后选取特定数值范围的图片内容实现分割。具体的实现代码参考了网络上的例子。代码并不长,主要的工作是读取图片,归一化以使后面 HSV 的取值范围为 [0, 360][0, 1] [0, 1],将颜色空间转换为 HSV,使用 inRange 函数获取掩码,根据掩码保留或去除像素,输出图像。

#include<opencv2/core.hpp>#include<opencv2/highgui.hpp>#include<opencv2/imgproc.hpp>using namespace cv;#include<iostream>#include<string>using namespace std;
//输入图像,灰度值归一化,HSV图像,输出图像Mat img, bgr, hsv, dst;//色相int hmin = 0;int hmax = 360;//饱和度int smin = 0;int smin_Max = 255;int smax = 255;int smax_Max = 255;//亮度int vmin = 0;int vmin_Max = 255;int vmax = 255;int vmax_Max = 255;//显示原图的窗口string windowName = "src";//输出图像的显示窗口string dstName = "dst";
int main(int argc, char*argv[]){    //输入图像    img = imread("test.jpg", IMREAD_COLOR);    if (!img.data || img.channels() != 3)        return -1;    imshow(windowName, img);    //彩色图像的灰度值归一化    img.convertTo(bgr, CV_32FC3, 1.0 / 255, 0);    //颜色空间转换    cvtColor(bgr, hsv, COLOR_BGR2HSV);    //定义输出图像的显示窗口    namedWindow(dstName, WINDOW_GUI_EXPANDED);    //输出图像分配内存    dst = Mat::zeros(img.size(), CV_32FC3);    //掩码    Mat mask;    inRange(hsv, Scalar(hmin, smin / float(smin_Max), vmin / float(vmin_Max)), Scalar(hmax, smax / float(smax_Max), vmax / float(vmax_Max)), mask);    //像素处理    for (int r = 0; r < bgr.rows; r++)    {        for (int c = 0; c < bgr.cols; c++)        {            if (mask.at<uchar>(r, c) == 255)// 255 为保留,0 为去除            {                dst.at<Vec3f>(r, c) = bgr.at<Vec3f>(r, c);            }        }    }    //输出图像    imshow(dstName, dst);    //保存图像    dst.convertTo(dst, CV_8UC3, 255.0, 0);    imwrite("HSV_inRange.jpg", dst);    waitKey(0);    return 0;}

结果#

需要处理的图像有两个,一个是从荔枝树枝的照片中提取荔枝,一个是分割不同颜色不同形状的图形。前者的处理比较简单,只调整 H 值的范围就可以得到比较好的效果。后者的处理比较有趣,因为颜色比较集中,所以同一个提取结果可以对应一个范围的值,下面仅列出来我找到的一些值,并不完整。红叉的提取最麻烦,因为红叉与蓝星星的 H 值范围非常近,很难一次提取获得干净的结果,所以我选择通过两次提取得到结果。

截取内容模式H minH maxS minS maxV minV max
提取荔枝去除1830802550255
蓝方块保留62~237242~355140255219255
红三角保留241~355360140255219255
蓝星星保留7~230245~35102550154
绿对号保留0186154255149255
黄五边保留0~5460~237140255219255
红叉一去除03600255165255
红叉二去除1632802550255