目录
一、HSV颜色系统简介
二、HSV值对颜色的影响
三、HSV和RGB的互相转化
四、OpenCV中的HSV颜色体系
五、OpenCV实战——两种方法使用OpenCV进行颜色分割
六、inRange函数用法介绍
一、HSV颜色系统简介
HSV是一种在人们生活中甚至更常用的颜色系统,在电视遥控器上、在画画的调色板中、在你用爱某艺视频调整亮度时都很常见,因为它更符合人们描述颜色的方式——是什么颜色、颜色有多深、颜色有多亮。
H——Hue即色相,就是我们平时所说的红、绿,如果你分的更细的话可能还会有洋红、草绿等等;在HSV模型中,用度数来描述色相,其中红色对应0度,绿色对应120度,蓝色对应240度。
S——Saturation即饱和度,色彩的深浅度(0-100%) ,对于一种颜色比如红色,我们可以用浅红——大红——深红——红得发紫等等语言来描述它(请原谅一个纯理科生的匮乏的颜色系统),对应在画水彩的时候即一种颜料加上不同分量的水形成不同的饱和度。
V——Value即色调,纯度,色彩的亮度(0-100%) ,这个在调整屏幕亮度的时候比较常见。
注:在模型2中:
H是色彩点在对应圆形切面上与红色半径(对于H=0度)所形成的圆心角。
V是色彩点所在圆形切面到圆锥顶点的距离。在顶面上V=1 顶点V=0
S是色彩点到所在圆形切面圆心的距离与该圆半径的比例值,在圆锥表面上S=1,在圆心处S=0
二、HSV值对颜色的影响
如上图是H=120时的S—V平面,S和V值分别从左至右、从下至上由0增大至1。我们可以发现其规律:
-
在图片的左侧S值为0,呈现不同程度的灰色,由V值决定。
-
在图片的下侧V值为0,呈现出黑色。
-
在图片的右上角S和V值都为1,呈现出纯色,其RGB值为(0, 255, 0)。
因此对HSV我们的结论如下:
当S=1 V=1时,H所代表的任何颜色被称为纯色;
当S=0时,即饱和度为0,颜色最浅,最浅被描述为灰色(灰色也有亮度,黑色和白色也属于灰色),灰色的亮度由V决定,此时H无意义;
当V=0时,颜色最暗,最暗被描述为黑色,因此此时H(无论什么颜色最暗都为黑色)和S(无论什么深浅的颜色最暗都为黑色)均无意义。
三、HSV和RGB的互相转化
RGB➡HSV
1. V = max(R, G, B)/255.0f——亮度V就是RGB值中最大的那个值进行归一化。
推论:纯色的RGB中肯定有一个是255。同时RGB值也不可能有3个255,因为3个255为白色,白色为对于任何色彩H,V=1而S=0时的产物。而V=1 S=0并不是纯色。同时如果V=0,那么RGB三者中的最大值是0,即GRB都为0,也就是说该像素是黑色。
2. S = (max(R, G, B) - min(R, G, B))/(float)max(R, G, B)——饱和度S是RGB中最大值和最小值的差值与最大值的比值。
推论:
-
纯色(S=1 V=1)的RGB值中必定有一个0,因为当S=1,max(R, G, B) - min(R, G, B) == max(R, G, B),即RGBMin=0。这也说明了白色(RGB(255,255,255)并不是纯色)。
-
当S=0时,RGBMax-RGBMin==0,即R==G==B,此时颜色呈不同程度的灰色(由白到黑,亮度由V而定,因为V=RGBMax*100/255,V越高,RGBMax==R==G==B就越高,灰色越亮))。这也可以从上面给出的矩形图看出。
因此对于纯色来说,RGB中必有一个255和一个0。
公式换算:
HSV➡RGB
四、OpenCV中的HSV颜色体系
与上述HSV颜色系统不同的是,如果直接使用OpenCV中cvtColor函数,并设置参数为CV_BGR2HSV,那么所得的H、S、V值范围分别是[0,180),[0,255),[0,255),而非[0,360],[0,1],[0,1];这时我们可以查下面的表格来确定颜色的大致区间。
代码如下:
Mat src = imread("test.jpg");
Mat hsv;
cvtColor(src, hsv, CV_BGR2HSV); //直接转换
cout<<hsv<<endl; //可以直接在控制台输出hsv查看结果
从输出的结果来看,和我们上边所说的是相符的。
另一种hsv方法——当我们想恢复到我们一开始介绍的体系时,我们只需要加一步——对像素的bgr进行归一化,再转到hsv时得到的结果就和我们介绍的就相同了。代码和输出结果如下
Mat src = imread("test.jpg");
Mat bgr; //归一化后的RGB图片
src.convertTo(bgr, CV_32FC3, 1.0 / 255.0); //对每个像素进行归一化
Mat hsv;
cvtColor(bgr, hsv, CV_BGR2HSV); //向hsv空间转化
cout<<hsv<<endl;
此时输出结果范围和一开始所述就符合了。
五、OpenCV实战——两种方法使用OpenCV进行颜色分割
目标图像是自己用画图画的(超级丑):
第一种方法——直接用OpenCV中的HSV体系,代码如下:
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
using namespace cv;
#include<iostream>
#include<string>
using namespace std;
//输入图像
Mat img;
//灰度值归一化
Mat bgr;
//HSV图像
Mat hsv;
//色相
int hmin = 0;
int hmin_Max = 360;
int hmax = 180;
int hmax_Max = 180;
//饱和度
int smin = 0;
int smin_Max = 255;
int smax = 255;
int smax_Max = 255;
//亮度
int vmin = 106;
int vmin_Max = 255;
int vmax = 255;
int vmax_Max = 255;
//显示原图的窗口
string windowName = "src";
//输出图像的显示窗口
string dstName = "dst";
//输出图像
Mat dst;
//回调函数
void callBack(int, void*)
{
//输出图像分配内存
dst = Mat::zeros(img.size(), img.type());
//掩码
Mat mask;
inRange(hsv, Scalar(hmin, smin, vmin), Scalar(hmax, smax, vmax), mask);
//掩模到原图的转换
for (int r = 0; r < bgr.rows; r++)
{
for (int c = 0; c < bgr.cols; c++)
{
if (mask.at<uchar>(r, c) == 255)
{
dst.at<Vec3b>(r, c) = bgr.at<Vec3b>(r, c);
}
}
}
//输出图像
imshow(dstName, dst);
//保存图像
//dst.convertTo(dst, CV_8UC3, 255.0, 0);
imwrite("HSV_inRange.jpg", dst);
}
int main(int argc, char** argv)
{
//输入图像
img = imread("test.jpg");
if (!img.data || img.channels() != 3)
return -1;
imshow(windowName, img);
bgr = img.clone();
//颜色空间转换
cvtColor(bgr, hsv, CV_BGR2HSV);
cout << hsv << endl;
//定义输出图像的显示窗口
namedWindow(dstName, WINDOW_GUI_EXPANDED);
//调节色相 H
createTrackbar("hmin", dstName, &hmin, hmin_Max, callBack);
createTrackbar("hmax", dstName, &hmax, hmax_Max, callBack);
//调节饱和度 S
createTrackbar("smin", dstName, &smin, smin_Max, callBack);
createTrackbar("smax", dstName, &smax, smax_Max, callBack);
//调节亮度 V
createTrackbar("vmin", dstName, &vmin, vmin_Max, callBack);
createTrackbar("vmax", dstName, &vmax, vmax_Max, callBack);
callBack(0, 0);
waitKey(0);
return 0;
}
由结果我们可以看出,当hmax从0依次变为30、60、90、120、150时,各种颜色区域不断被包括进去。
第二种方法——先归一化后再转乘HSV体系,代码如下:请注意对Mat对象BGR和dst的图像类型设置细节以及at函数实例化参数的不同(Vec3f和Vec3b)。
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
using namespace cv;
#include<iostream>
#include<string>
using namespace std;
//输入图像
Mat img;
//灰度值归一化
Mat bgr;
//HSV图像
Mat hsv;
//色相
int hmin = 0;
int hmin_Max = 360;
int hmax = 360;
int hmax_Max = 360;
//饱和度
int smin = 0;
int smin_Max = 255;
int smax = 255;
int smax_Max = 255;
//亮度
int vmin = 106;
int vmin_Max = 255;
int vmax = 250;
int vmax_Max = 255;
//显示原图的窗口
string windowName = "src";
//输出图像的显示窗口
string dstName = "dst";
//输出图像
Mat dst;
//回调函数
void callBack(int, void*)
{
//输出图像分配内存
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)
{
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);
}
int main(int argc, char*argv[])
{
//输入图像
img = imread(argv[1], 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);
//调节色相 H
createTrackbar("hmin", dstName, &hmin, hmin_Max, callBack);
createTrackbar("hmax", dstName, &hmax, hmax_Max, callBack);
//调节饱和度 S
createTrackbar("smin", dstName, &smin, smin_Max, callBack);
createTrackbar("smax", dstName, &smax, smax_Max, callBack);
//调节亮度 V
createTrackbar("vmin", dstName, &vmin, vmin_Max, callBack);
createTrackbar("vmax", dstName, &vmax, vmax_Max, callBack);
callBack(0, 0);
waitKey(0);
return 0;
}
代码来自https://blog.csdn.net/zhangping1987/article/details/74452562
六、inRange函数用法介绍
void inRange(InputArray src, InputArray lowerb, InputArray upperb, OutputArray dst);
通俗的来讲,这个函数就是判断src中每一个像素是否在[lowerb,upperb]之间,注意集合的开闭。如果结果为是,那么在dst相应像素位置填上255,反之则是0。一般我们把dst当作一个mask来用,如上例所示。
官方的解释为:Checks if array elements lie between the elements of two other arrays.即检查数组元素是否在另外两个数组元素值之间。这里的数组通常也就是矩阵Mat或向量。要特别注意的是:该函数输出的dst是一幅二值化之后的图像。
-
针对单通道图像
dst(I) = lowerb(I)0 ≤ src(I)0 < upperb(I)0
即,如果一幅灰度图像的某个像素的灰度值在指定的高、低阈值范围之内,则在dst图像中令该像素值为255,否则令其为0,这样就生成了一幅二值化的输出图像。
-
针对三通道图像
dst(I) = lowerb(I)0 ≤ src(I)0 < upperb(I)0 ∧ lowerb(I)1 ≤ src(I)1 < upperb(I)1 ∧lowerb(I)2 ≤ src(I)2 < upperb(I)2
即,每个通道的像素值都必须在规定的阈值范围内!
转载请注明出处。