您的位置:首页 > 运维架构

OpenCV图像直方图的理解和验证

2017-03-22 20:55 183 查看

    昨天看到了图像直方图的东西,看了很久都没看懂,今天接着看,看懂了一部分,虽然理解的可能不完善,但也贴出来分享一下,帮助一下后人吧

首先贴出代码计算一维直方图

#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>

using namespace cv;

#include<iostream>

using namespace std;
int main(int argc, char** argv)
{
Mat src, hsv;
if (argc != 2 || !(src = imread(argv[1], 1)).data || src.channels() != 3)
return -1;

//颜色空间的转换BGR转HSV
cvtColor(src, hsv, CV_BGR2HSV);

//把H通道分为30个bin,把S通道等分为32bin
int hbins = 30;
//int sbins = 32;
//int histSize[] = {hbins,sbins};
int histSize[] = { hbins };
//H的取值范围 0-179
float hranges[] = { 0, 180 };
//S的取值范围 0-255
//float sranges [] ={0,255};
//const float* ranges [] = {hranges,sranges};
const float* ranges[] = { hranges };
MatND hist;
//我们根据图像第一个通道一维直方图
int channels[] = { 0 };
calcHist(&hsv, 1, channels, Mat(), hist, 1, histSize, ranges, true, false);

//输出直方图
cout << hist << endl;
return 0;
}
/*
运算的结果
===========================================
[60571;
12194;
180311;
1035634;
172737;
23720;
6520;
4793;
3744;
2124;
3045;
2641;
2475;
2896;
2941;
7646;
228236;
2946426;
3027;
1289;
3502;
1012;
1256;
640;
322;
200;
830;
865;
1160;
1645]

通过这个答案,我们可以看到,对应于hsv的通道0来说,histSize数组的的长度表示直方图的维数
这个程序中,histSize数组长度为1,只有一个元素,表明是一维直方图
histSize[0]的长度,即30表示这个一维直方图的所分成的桶的数目
输出直方图,之后,我们可以看到
[60571;12194;180311;1035634;172737;23720;6520;4793;3744;2124;3045;2641;2475;2896;2941;7646;228236;2946426;3027;1289;3502;1012;1256;640;322;200;830;865;1160;1645]
正好一共30个数据,表示的是相对于第i个桶的像素个数;
例如第一数是60571,因为算法中使用的是平均划分,即180/30等6,
所划分的区间为:[0,6),[6,12),[12,18)……[174,180)
表示的是H在[0,6)之间的像素点个数为60571
===========================================
*/

然后进一步二维直方图的代码

 

#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>

using namespace cv;

#include<iostream>

using namespace std;
int main(int argc, char** argv)
{
Mat src, hsv;
if (argc != 2 || !(src = imread(argv[1], 1)).data || src.channels() != 3)
return -1;

//颜色空间的转换BGR转HSV
cvtColor(src, hsv, CV_BGR2HSV);

//把H通道分为30个bin,把S通道等分为32bin
cout << hsv.rows<<" "<<hsv.cols<<" "<<hsv.rows*hsv.cols << endl;
int hbins = 5;
int sbins = 4;
int histSize[] = { hbins, sbins };

//H的取值范围 0-179
float hranges[] = { 0, 180 };
//S的取值范围 0-255
float sranges[] = { 0, 256 };
//每一维直方图的大小
const float* ranges[] = { hranges, sranges };

MatND hist;
//我们根据图像第一个通道和第二通道,计算二维直方图
int channels[] = { 0, 1 };
//第二个数组表示源图像的个数
//channels表示使用图像的对应通道数来进行多维直方图的计算,均匀划分
calcHist(&hsv, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);

//输出直方图
cout << hist << endl;
return 0;
}
/*
====================
480 640 307200
[71449, 12113, 641, 56;
138975, 35175, 1653, 32;
37541, 5286, 27, 0;
2825, 18, 0, 0;
1390, 19, 0, 0],这列数字的所有和正好是307200;

====================

这是一个五行四列的二维直方图,第一行第一列的71449
代表的是H和S的值在[0,36)X[0,63),第二行第一列的12113代表H和S的值在[36,72)X[0,63)
。
也就是说这个二维数组的第一行为H的值在[0,36)
第二行H在[36,72)
第三行H在[72,108)
第四行H在[108,144)
第五行在[144,180)

*/

插入所使用的图像



在看代码的过程中,我始终弄不明白这个二维直方图是怎么计算的。虽然看到上面两个代码时,我已经感觉理解有5,6分了,但还是无法理解的透彻,所以我进一步的创建了一个矩阵,使用的是5X5的尺寸;数据如下

 

data0,即src0
5203041
916182023
2240708099
204312
870906040
data1,即src1
2030494020
19178733
24261952
4739211312
51123
通过重新合并成新的矩阵,并在纸上推演运算的过程,终于弄清楚了里面的计算过程,先上代码

#include<highgui.h>
#include<cv.h>
using namespace std;
using namespace cv;

int main(void) {

Mat src,src0,src1,src2;
src = cvCreateMat(5, 5, CV_8UC3);
src0 = cvCreateMat(5, 5, CV_8UC1);
src1 = cvCreateMat(5, 5, CV_8UC1);
src2 = cvCreateMat(5, 5, CV_8UC1);
src.empty();
uchar data0[] = {
5,20,30,4,1,
9,16,18,20,23,
22,40,70,80,99,
20,4,3,1,2,
8,70,90,60,40
};
src0.data = data0;
uchar data1[] = {
20,30,49,40,20,
19,17,8,7,33,
24,26,19,5,2,
47,39,21,13,12,
5,1,1,2,3
};
src1.data = data1;

std::vector<cv::Mat> m;
m.push_back(src0);
m.push_back(src1);
m.push_back(src2);
merge(m, src);
imshow("zhou",src);
waitKey(0);
imwrite("zhou.jpg",src);
int hbins = 5;
int sbins = 4;
const int histSize[] = { hbins, sbins };
float hranges[] = { 0, 100 };
float sranges[] = { 0, 50 };
//为何必须是const
const float *ranges[] = {hranges, sranges};

int channels[] = {0, 1};
MatND mHist;
calcHist(&src,1,channels,Mat(),mHist,2,histSize,ranges,true,false);
cout << mHist << endl;

return 0;
}
/*
==========================
[3, 6, 0, 2;
1, 1, 2, 2;
1, 0, 1, 0;
2, 1, 0, 0;
3, 0, 0, 0]
实验证明确实是每一行的和为11,6,2,3,3,与我在纸上所的出的结论一致
第一行的3,6,0,2也与我得出的结论一致。
这就说明,我们在计算二维直方图的过程中
计算每一维每个桶中的个数时,以本例子中的5X5,5X5两个通道为例。
使用均匀分布,所以0-100,在使用五个桶统计时,分别划分的区间是
[0,19],[20, 39],[40,59],[60,79],[80,99]
{
5,20,30,4,1,
9,16,18,20,23,
22,40,70,80,99,
20,4,3,1,2,
8,70,90,60,40
}
data0数组中的这25个数,分别投入相应的桶,可以得到各自桶中的数目为11,6,2,3,3
接着看第二维,以第一维第一个桶为例:
我们可以看到5,4,1,9,16,18,4,3,1,2,8
它们的位置分别为[1,1],[1,4],[1,5],[2,1],[2,2],[2,3],[4,2],[4,3],[4,4],[4,5],[5,1]
所以对这11个数再进行第二维等分的时候,由于4个桶,4等分
所以每个桶以此存放的区间是[0,12],[13,25],[26,39],[40,52]
我们以第二维第一个桶为例子,即在sranges中属于[0,12]的个数
{
20,30,49,40,20,
19,17,8,7,33,
24,26,19,5,2,
47,39,21,13,12,
5,1,1,2,3
};
==========================
对于5对应的位置[1,1]而言,它的s值为20,不在[0,12]区间内
对于4对应的位置[1,4]而言,它的s值为40,不在[0,12]区间内
……
而对于[2,3],它的s值为8,在区间[0,12]内
对于2对应的位置[4,5]而言,它的s值为12,在区间[0,12]
对于最后数字8而言,它对应的位置[5,1]而言,这个位置的s值为5,在区间[0,12]
所以对于第二维第一个桶[0,12],一共有3个像素满足
其他以此可得出结论
*/


 算法的结果的得出,在实验结果的分析中较为详细,尤其要感谢

前两个代码的来源,我也是在这个代码开始理解的

这个是opencv的官网上给出的例子

通道合并参考的是http://blog.csdn.net/rongrongyaofeiqi/article/details/52575717

另外要读懂直方图的含义,可以读读这个文档:http://academy.fengniao.com/533/5339606.html

我对于opencv图像直方图中,开始理解二维直方图是在这个文档上

http://lib.csdn.net/article/opencv/25683

弄懂了这些,大概大家的直方图就差不多理解了。


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: