【实现】
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
cv::Mat image = imread("1.jpg");
//转换成HSV色彩空间
cv::Mat hsv;
cv::cvtColor(image, hsv, CV_BGR2HSV);
//把3个通道分割进3幅图像中
std::vector<cv::Mat>channels;
cv::split(hsv, channels);
//channels[0]是色调
//channels[1]是饱和度
//channels[2]是亮度
cv::namedWindow("Value");
cv::imshow("Value", channels[0]);
cv::namedWindow("Saturation");
cv::imshow("Saturation", channels[1]);
cv::namedWindow("Hue");
cv::imshow("Hue", channels[2]);
cv::waitKey(0);
}
【实现原理】
直觉色彩空间基于以下三个属性。色调(hue)表示主色,我们使用的颜色名称(例如绿色、 黄色和红色)就对应了不同的色调值;饱和度(saturation)表示颜色的鲜艳程度,柔和的颜色饱和度较低,而彩虹的颜色饱和度就很高;亮度(brightness)是一个主观的属性,表示某种颜色的光亮程度。其他直觉色彩空间使用颜色明度(value)或颜色亮度(lightness)的概念描述有关颜色的强度。
色调、饱和度和亮度有多种不同的定义和计算公式。OpenCV 建议的两种直觉色彩空间的实现是 HSV 和 HLS 色彩空间,它们的转换公式略有不同,但是结果非常相似。
接下来我将向大家解释这三个属性是如何得出的。
1. 亮度
亮度成分可能是最容易解释的。在 OpenCV 对 HSV 的实现中,它被定义为三个 BGR 成分中的最大值。为了让定义更符合人类视觉系统,应该使用均匀感知的色彩空间 L*a*b*和 L*u*v*的 L 通道。举个例子,L 通道已经考虑到了,在强度相同的情况下,人们会觉得绿色比蓝色等颜色的亮度更高。
2. 饱和度
OpenCV 用一个公式来计算饱和度,该公式基于 BGR 组件的最小值和最大值:
灰度颜色包含的 R、G、B 的成分是相等的,相当于一种极不饱和的颜色,因此它的饱和度是 0(饱和度是一个 0~1.0 的值)。对于 8 位图像,饱和度被调节成一个 0~255 的值,并且作为灰度图像显示的时候,较亮区域对应的颜色具有较高的饱和度。
3. 色调
颜色的色调通常用 0~360 的角度来表示,其中红色是 0 度。对于 8 位图像,OpenCV 把角度除以 2,以适合单字节的存储范围。因此,每个色调值对应指定颜色的色彩,与亮度和饱和度无关。例如天空和水的色调是一样的,都约为 200 度(强度 100),对应色度为蓝色;背景树林的色调约为 90 度,对应色度为绿色。有一点要特别注意,如果颜色的饱和度很低,它计算出来的色调就不可靠。
HSB 色彩空间通常用一个圆锥体来表示,圆锥体内部的每个点代表一种特定的颜色,角度位置表示颜色的色调,到中轴线的距离表示饱和度,高度表示亮度。圆锥体的顶点表示黑色,它的色调和饱和度是没有意义的。
接下来我将人为生成一幅图像,来说明各种色调/饱和度组合
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
cv::Mat hs(128, 360, CV_8UC3);
for (int h = 0; h < 360; h++) {
for (int s = 0; s < 128; s++) {
hs.at<cv::Vec3b>(s, h)[0] = h / 2;//所有色调角度
//饱和度从高到低
hs.at<cv::Vec3b>(s, h)[1] = 255 - s * 2;
hs.at<cv::Vec3b>(s, h)[2] = 255;//常数
}
}
cv::namedWindow("Hue/Saturation");
cv::imshow("Hue/Saturation", hs);
cv::waitKey(0);
}
使用 HSV 的值可以生成一些非常有趣的效果。一些用照片编辑软件生成的色彩特效就是用 这个色彩空间实现的。你可以修改一幅图像,把它的所有像素都设置为一个固定的亮度,但不改 变色调和饱和度。
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
cv::Mat image = imread("1.jpg");
//转换成HSV色彩空间
cv::Mat hsv;
cv::cvtColor(image, hsv, CV_BGR2HSV);
//将3个通道分割到3幅图像中
std::vector<cv::Mat>channels;
cv::split(hsv, channels);
//所有像素的颜色亮度通道将变成255
channels[2] = 255;
//重新合并通道
cv::merge(channels, hsv);
//转换回BGR
cv::Mat newImage;
cv::cvtColor(hsv, newImage, CV_HSV2BGR);
cv::namedWindow("Fixed Value Image");
cv::imshow("Fixed Value Image", newImage);
cv::waitKey(0);
}
【拓展阅读】
在对特定物体做初步检测时,颜色信息非常有用。例如辅助驾驶程序中的路标检测功能,就要凭借标准路标的颜色快速识别可能是路标的信息。另一个例子是肤色检测,检测到的皮肤区域可作为图像中有人存在的标志。手势识别就经常使用肤色检测确定手的位置。
通常来说,为了用颜色来检测目标,首先需要收集一个存储有大量图像样本的数据库,每个样本包含从不同观察条件下捕捉到的目标,作为定义分类器的参数。你还需要选择一种用于分类的颜色表示法。肤色检测领域的大量研究已经表明,来自不同人种的人群的皮肤颜色,可以在色调-饱和度色彩空间中很好地归类。因此,在后面的图像中,我们将只使用色调和饱和度值来识别肤色。
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;
void detectHScolor(const cv::Mat& image, //输入图像
double minHue, double maxHue, //色调区间
double minSat, double maxSat, //饱和度区间
cv::Mat& mask) { //输出掩码
//转换到HSV空间
cv::Mat hsv;
cv::cvtColor(image, hsv, CV_BGR2HSV);
//将3个通到分割到3幅图像
std::vector<cv::Mat>channels;
cv::split(hsv, channels);
//channels[0]是色调
//channels[1]是饱和度
//channels[2]是亮度
//色调掩码
cv::Mat mask1;//小于maxHue
cv::threshold(channels[0], mask1, maxHue, 255,
cv::THRESH_BINARY_INV);
cv::Mat mask2;//大于minHue
cv::threshold(channels[0], mask2, minHue, 255, cv::THRESH_BINARY);
cv::Mat hueMask;//色调掩码
if (minHue < maxHue)
hueMask = mask1 & mask2;
else//如果区间穿越0度中轴线
hueMask = mask1 | mask2;
//饱和度掩码
//从minSat到maxSat
cv::Mat satMask;//饱和度掩码
cv::inRange(channels[1], minSat, maxSat, satMask);
//组合掩码
mask = hueMask & satMask;
}
int main()
{
cv::Mat image = imread("girl.jpg");
cv::Mat mask;
detectHScolor(image, 1160, 10, //色调为320-20度
25, 166, //饱和度为~0.1~0.65
mask);
//显示使用掩码后的图像
cv::Mat detected(image.size(), CV_8UC3, cv::Scalar(0, 0, 0));
image.copyTo(detected, mask);
cv::namedWindow("Detection result");
cv::imshow("Detection result", detected);
cv::waitKey(0);
}
我们定义了一个基于数值区间(最小和最大色调、最小和最大饱和度)的函数,把图像中的像素分为皮肤和非皮肤两类。
如果在处理时有了大量的皮肤(以及非皮肤)样本,我们就可以使用概率方法估算在皮肤样本中和非皮肤样本中发现指定颜色的可能性。此处,我们依据经验定义了一个合理的色调-饱和度区间,用于这里的测试图像(记住,8 位版本的色调在 0~180,饱和度在 0~255)
注意,为了简化,在检测时没有考虑颜色的亮度。在实际应用中,排除较高亮度的颜色可以降低把明亮的淡红色误认为皮肤的可能性。显然,要想对皮肤颜色进行可靠和准确的检测, 还需要更加精确的分析。对不同的图像进行检测,也很难保证效果都好,因为摄影时影响彩色再现的因素有很多,如白平衡和光照条件等。尽管如此,用这种只使用色调/饱和度信息做初步检测的方法也能得到一个比较令人满意的结果