Opencv二维直方图(C++)

贾志刚老师在opencv30讲中讲到二维直方图没有具体解释代码细节,很多小伙伴的博客也没有对其具体解释,本文会对其中细节详细介绍;
同时引个流,贾老师在讲图像旋转的函数也没有对其具体解释,因为博客打字繁琐,我就做了个视频,给需要的小伙伴们。
【getRotationMatrix2D()和warpAffine()函数的讲解】 https://www.bilibili.com/video/BV1jM411t7Tt/?share_source=copy_web&vd_source=503f513ad0808abf9fb631e90be3ea2c

void Quickdemo::histogram_2d_demo(Mat & image)
{
	Mat hsv, hs_hist;
	cvtColor(image, hsv, COLOR_BGR2HSV);
	int hbins = 30, sbins = 32;
	int histbins[] = { hbins,sbins };
	float h_range[] = { 0,180 };
	float s_range[] = { 0,256 };
	const float* hs_range[] = { h_range ,s_range };
	int hs_channels[] = { 0,1 };
	calcHist(&hsv, 1, hs_channels, Mat(), hs_hist, 2, histbins, hs_range, true, false);//一张图,h和s通道
	double maxval = 0;
	minMaxLoc(hs_hist, 0, &maxval, 0, 0);
	int scale = 10;
	Mat hist2d_image = Mat::zeros(sbins*scale, hbins*scale,CV_8UC3);
	for (int h = 0; h < hbins; h++)
	{
		for (int s = 0; s < sbins; s++)
		{
			float binval = hs_hist.at<float>(h, s);
			int intensity = cvRound(binval * 255 / maxval);
			//normalize(intensity, intensity, 0, 255, NORM_MINMAX, -1, Mat());//输入输出,下限上限

			rectangle(hist2d_image, Point(h*scale, s*scale),
				Point((h + 1)*scale - 1, (s + 1)*scale - 1),
				Scalar::all(intensity), -1);
			//std::cout << binval << std::endl;
		}
	}
	applyColorMap(hist2d_image, hist2d_image, COLORMAP_JET);
	imshow("h-s", hist2d_image); //X轴显示S值,Y轴显示色相。
	//std::cout << hs_hist << std::endl;
	
	//imshow("hs_hist", hs_hist);

}

Mat类型变量hs_hist是由calcHist()函数生成的,想要知道他究竟生成了个什么,可以用cout输出hs_hist的内容并复制到matlab中。
从matlab生成的表格左上角可以知道hs_hist是30行32列的,也就是标志着把色相分成多少份的hbins和和把饱和度分成多少份的sbins的数值。
Opencv二维直方图(C++)_第1张图片


float h_range[] = { 0,180 };
float s_range[] = { 0,256 };
可知色相值的范围是0到180,要分成30份也就是第一个位置储存了色相为0到5的像素点总数,第二个位置储存了色相为5到10的像素点总数,以此类推,对于范围是0到255的饱和度,分成32份,则第一个位置储存了饱和度为0到8的像素点总数,第二个位置储存了饱和度为8到16的像素点总数等等。

观察matlab生成的hs_hist矩阵,这个矩阵里面的数字都很大,他们代表的就是色相为多少范围内、饱和度为多少范围内的像素点的总数。(可以根据一维直方图是灰度值为多少的像素点的总数来类比理解)

float binval = hs_hist.at(h, s);
这句话是在遍历hs_hist时将hs_hist的第h行第s列取出来,
int intensity = cvRound(binval * 255 / maxval);就是实现将hs_hist中对应第h行第s列的数值限制到0到255。如果只除以maxval(用minMaxLoc找到的矩阵中的最大值)就是归一化,限制到0到1,但乘上255就限制到255了。
有的同学就想问为什么不用我们前面学的normalize函数?
不能在for内使用的原因,是normalize()只能对Mat类型归一化,我们可以在for循环之外使用normalize,提前对hs_hist归一化,就像下面这样:

void Quickdemo::histogram_2d_demo(Mat & image)
{
	Mat hsv, hs_hist;
	cvtColor(image, hsv, COLOR_BGR2HSV);
	int hbins = 30, sbins = 32;
	int histbins[] = { hbins,sbins };
	float h_range[] = { 0,180 };
	float s_range[] = { 0,256 };
	const float* hs_range[] = { h_range ,s_range };
	int hs_channels[] = { 0,1 };
	calcHist(&hsv, 1, hs_channels, Mat(), hs_hist, 2, histbins, hs_range, true, false);//一张图,h和s通道
	double maxval = 0;
	minMaxLoc(hs_hist, 0, &maxval, 0, 0);
	int scale = 10;
	Mat hist2d_image = Mat::zeros(sbins*scale, hbins*scale,CV_8UC3);
	normalize(hs_hist, hs_hist, 0, 255, NORM_MINMAX, -1, Mat());//输入输出,下限上限
	for (int h = 0; h < hbins; h++)
	{
		for (int s = 0; s < sbins; s++)
		{
			float binval = hs_hist.at<float>(h, s);
			rectangle(hist2d_image, Point(h*scale, s*scale),
				Point((h + 1)*scale - 1, (s + 1)*scale - 1),
				Scalar::all(binval), -1);
			//std::cout << binval << std::endl;
		}
	}
	applyColorMap(hist2d_image, hist2d_image, COLORMAP_JET);
	imshow("h-s", hist2d_image); //X轴显示S值,Y轴显示色相。
	//std::cout << hs_hist << std::endl;
	
	//imshow("hs_hist", hs_hist);
}

在for循环的外面加入归一化函数normalize(),将hs_hist的所有内容都约束在0到255中,也就是这个时候,hs_hist就已经可以展示成灰度图像了。
Opencv二维直方图(C++)_第2张图片
我们先跑一下程序,证明我改动并不会影响结果,从这个图片上(左改动前;右改动后)可以看出两个归一化方法是没有区别的。

再来分析这一句:

Mat hist2d_image = Mat::zeros(sbins*scale, hbins*scale,CV_8UC3);

我们之前设定hbins和sbins分别是30和32,如果直接用这个尺寸生成图像会很小,不便于观看,因此添加scale这个放缩的系数,设定为10,hist2d_image图像就变成300X320了,之后的矩形会基于这个图像绘制。

再来看这一句:

		float binval = hs_hist.at(h, s);
		rectangle(hist2d_image, Point(h*scale, s*scale),
			Point((h + 1)*scale - 1, (s + 1)*scale - 1),
			Scalar::all(binval), -1);

当我们遍历到hs_hist的第h行和第s列的时候,如第2行第2列,即h=1&&s=1,也就是色相位于5到10,饱和度是8到16之间的像素点总数我们可以根据前面的矩阵得到。

这个数量在归一化到255范围之后,可以用灰度图像的灰度值反应像素点总数多少,也就是可以用直方图的某个位置越白(越接近255)代表你要分析的图像位于这个位置对应的色相和饱和度范围内的像素点的数量越多。

具体在图像上,直方图上的这些色块是用矩形画出来的。如对于直方图的第2行第2列,即h=1&&s=1,则矩形的起点是(10,10),终点是(19,19),为什么要减一,而不是(20,20)呢?因为对角线上第一个矩形是从(0,0)到(9,9),边长都是10个像素格子,下一个自然是从(10,10),到(19,19)。矩形的灰度值就是hs_hist矩阵归一化之后的对应值。
最后用了一个色彩映射的函数:applyColorMap,类型是COLORMAP_JET使灰度图越白的地方越红,使下图左边的灰度图变成右边的伪彩色图。
在这里插入图片描述

Opencv二维直方图(C++)_第3张图片

总之,二维直方图本质就是将calcHist生成的hs_hist矩阵用灰度图像的形式表达!!!

参考资料
[1]【OpenCV4 C++ 快速入门视频30讲 - 系列合集】 https://www.bilibili.com/video/BV1i54y1m7tw?p=26&vd_source=524a4e2d655f8a5f4a9b5d45f1da0345
[2]【OpenCV 4】伪色彩 applyColorMap() 函数使用https://blog.csdn.net/kingkee/article/details/92785118

你可能感兴趣的:(视觉,opencv,计算机视觉,人工智能)