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

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

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

#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

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





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值