本篇博客主要介绍利用OpenCV工具提取一幅图像中的颜色直方图特征。所谓颜色直方图,指的是一幅图像中的颜色分布,与图像中的特定的物体无关,只是用来表示人的眼睛观察到的图像中的颜色分布情况,例如说,一幅图中红色占了多少比例,绿色占了多少比例等。
我们知道,计算机色彩显示器采用R、G、B相加混色的原理,通过发射出三种不同强度的电子束,使屏幕内侧覆盖的红、绿、蓝磷光材料发光而产生色彩。在RGB颜色空间中,任意色光F都可以用RGB三色不同分量的相加混合而成。也就是说,图像中每个像素的颜色值都可以用一个三元值(R,G,B)来表示,例如(0,0,0)表示黑色,(0,0,255)表示蓝色,……每个分量的大小从0~255.
那么,我们可以知道一张彩色图像可以由R、G、B三个通道堆叠而成。可以想象成将三张大小相同的纸叠放,然后人眼从上往下看到的图像就是原来的彩色图像。这里,假设图像的分辨率为720*576,那么每个通道的长为720,宽为576,单位是像素,总的像素个数就是3*720*576.
下面具体介绍颜色直方图,横轴表示bins,也就是颜色分为多少块。假设bins=256,也就是横坐标上每个点表示一个数值,纵坐标表示一幅图中有多少个像素点的值是该数值。如果bins=16,也就是把颜色范围0~255从小到大分为16块,然后图像中落在每块对应的范围的像素点的个数就是每块对应的直方图的高度。
因为opencv默认一张图像是由R、G、B三个通道堆叠形成的,所以首先提取出每个通达的直方图,最后将三个直方图揉在一块。
其中,最主要的就是一个calcHist函数,
void calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false )
参数较多。
第一个参数表示输入的图像;
第二个参数表示输入的图像个数,一般是 1;
第三个参数表示需要处理的是图像的第几个通道;
第四个参数表示掩模,一般是Mat();
第五个参数是要输出的直方图数组;
第六个参数表示需要统计的直方图通道个数,一般是1;
第七个参数histSize表示划分的区间数,即bins的个数;
第八个参数表示像素的变化范围;后两个参数分别表示是否归一化,和是否累计计算像素个数。
重点关注我标红的参数即可。
代码展示:
1 #include "stdafx.h"
2 #include <iostream>
3 #include <opencv2/opencv.hpp>
4 using namespace cv;
5 using namespace std;
6
7 int main()
8 {
9 //读取本地的一张图片
10 Mat srcimage = imread("F:\\6030.png");
11 imshow("原图", srcimage);
12 int channels = 0;
13 int histsize[] = { 256 };
14 float midranges[] = { 0,255 };
15 const float *ranges[] = { midranges };
16 MatND dsthist; //要输出的直方图
17 //重点关注calcHist函数,即为计算直方图的函数
18 calcHist(&srcimage, 1, &channels, Mat(), dsthist, 1, histsize, ranges, true, false);
19
20 Mat b_drawImage = Mat::zeros(Size(256, 256), CV_8UC3);
21 double g_dhistmaxvalue;
22 minMaxLoc(dsthist, 0, &g_dhistmaxvalue, 0, 0);
23 for (int i = 0;i < 256;i++) {
24 //这里的dsthist.at<float>(i)就是每个bins对应的纵轴的高度
25 int value = cvRound(256 * 0.9 *(dsthist.at<float>(i) / g_dhistmaxvalue));
26 line(b_drawImage, Point(i, b_drawImage.rows - 1), Point(i, b_drawImage.rows - 1 - value), Scalar(255, 0, 0));
27 }
28 imshow("B通道直方图", b_drawImage);
29
30 channels = 1;
31 calcHist(&srcimage, 1, &channels, Mat(), dsthist, 1, histsize, ranges, true, false);
32 Mat g_drawImage = Mat::zeros(Size(256, 256), CV_8UC3);
33 for (int i = 0;i < 256;i++) {
34 int value = cvRound(256 * 0.9 *(dsthist.at<float>(i) / g_dhistmaxvalue));
35 line(g_drawImage, Point(i, g_drawImage.rows - 1), Point(i, g_drawImage.rows - 1 - value), Scalar(0, 255, 0));
36 }
37 imshow("G通道直方图", g_drawImage);
38
39 channels = 2;
40 calcHist(&srcimage, 1, &channels, Mat(), dsthist, 1, histsize, ranges, true, false);
41 Mat r_drawImage = Mat::zeros(Size(256, 256), CV_8UC3);
42 for (int i = 0;i < 256;i++) {
43 int value = cvRound(256 * 0.9 *(dsthist.at<float>(i) / g_dhistmaxvalue));
44 line(r_drawImage, Point(i, r_drawImage.rows - 1), Point(i, r_drawImage.rows - 1 - value), Scalar(0, 0, 255));
45 }
46 imshow("R通道直方图", r_drawImage);
47
48 add(b_drawImage, g_drawImage, r_drawImage); //将三个直方图叠在一块
49 imshow("RGB直方图", r_drawImage);
50 waitKey(0);
51 return 0;
52 }
结果展示:
接下来,绘制HSV颜色空间的直方图。有了RGB颜色直方图,还要HSV干什么。下面介绍HSV颜色空间模型。
HSV是一种将RGB色彩空间中的点在倒圆锥体中的表示方法。HSV即色相(Hue)、饱和度(Saturation)、亮度(Value),色相即颜色的基本属性,饱和度指色彩的纯度,越高则色彩越纯,亮度指色彩的明亮程度。
在RGB颜色空间中,三种颜色分量的取值与所生成的颜色之间的联系并不直观。而HSV颜色空间,更类似于人类感觉颜色的方式,封装了关于颜色的信息:“这是什么颜色?深浅如何?明暗如何?”HSV颜色空间规定的取值范围: H:0~360,S:0~1,V:0~1
在OpenCV中,H:0~180,S:0~255,V:0~255, H/2,S*255,V*255
也就是说,这里H通道的取值是和其他两个通道有所不同的,它的范围是0~180,因为如果为了堆叠三个通道,强行设置256个bins,那么在第180~255个bins上,H通道上会出现为空的情况。
HSV颜色空间特征的提取方法和RGB类似,关键一点就是要将原图像转化为HSV颜色空间的图像,之后再对三个通道分别进行直方图绘制操作即可。
代码展示:
1 #include "stdafx.h"
2 #include <iostream>
3 #include <opencv2/opencv.hpp>
4 using namespace cv;
5 using namespace std;
6
7 int main()
8 {
9 Mat srcimage = imread("F:\\6030.png");
10 imshow("原图", srcimage);
11 Mat srcimageHSV;
12 //图像转化HSV颜色空间图像
13 cvtColor(srcimage, srcimageHSV, COLOR_BGR2HSV);
14 imshow("HSV空间图像", srcimageHSV);
15 int channels = 0;
16 int histsize[] = { 256 };
17 float midranges[] = { 0,255 };
18 const float *ranges[] = { midranges };
19 MatND dsthist;
20 calcHist(&srcimageHSV, 1, &channels, Mat(), dsthist, 1, histsize, ranges, true, false);
21 Mat b_drawImage = Mat::zeros(Size(256, 256), CV_8UC3);
22
23 double g_dhistmaxvalue;
24 minMaxLoc(dsthist, 0, &g_dhistmaxvalue, 0, 0);
25 for (int i = 0;i < 256;i++) {
26 int value = cvRound(256 * 0.9 *(dsthist.at<float>(i) / g_dhistmaxvalue));
27 line(b_drawImage, Point(i, b_drawImage.rows - 1), Point(i, b_drawImage.rows - 1 - value), Scalar(255, 0, 0));
28 }
29 imshow("H通道直方图", b_drawImage);
30
31 channels = 1;
32 calcHist(&srcimageHSV, 1, &channels, Mat(), dsthist, 1, histsize, ranges, true, false);
33 Mat g_drawImage = Mat::zeros(Size(256, 256), CV_8UC3);
34 for (int i = 0;i < 256;i++) {
35 int value = cvRound(256 * 0.9 *(dsthist.at<float>(i) / g_dhistmaxvalue));
36 line(g_drawImage, Point(i, g_drawImage.rows - 1), Point(i, g_drawImage.rows - 1 - value), Scalar(0, 255, 0));
37 }
38 imshow("S通道直方图", g_drawImage);
39
40 channels = 2;
41 calcHist(&srcimageHSV, 1, &channels, Mat(), dsthist, 1, histsize, ranges, true, false);
42 Mat r_drawImage = Mat::zeros(Size(256, 256), CV_8UC3);
43 for (int i = 0;i < 256;i++) {
44 int value = cvRound(256 * 0.9 *(dsthist.at<float>(i) / g_dhistmaxvalue));
45 line(r_drawImage, Point(i, r_drawImage.rows - 1), Point(i, r_drawImage.rows - 1 - value), Scalar(0, 0, 255));
46 }
47 imshow("V通道直方图", r_drawImage);
48 add(b_drawImage, g_drawImage, r_drawImage);
49 imshow("HSV直方图", r_drawImage);
50 waitKey(0);
51 return 0;
52 }
结果展示:
说明:HSV三个通道的直方图绘制,我为了区分,用了红绿蓝三种颜色,和RGB通道没有关系。颜色可以自己在程序中更改。其实每个通道都是从不同的角度(色相、饱和度、亮度)来分析图像的。