花老湿学习OpenCV:直方图、直方图的计算、均衡化、对比、反向投影

直方图概述:

        在统计学中,直方图是一种对数据分布情况的图形表示,是一种二维统计图表,他的两个坐标分别是统计样本(图像、视频帧)和样本的某种属性(亮度,像素值,梯度,方向,色彩等等任何特征)。

        也可以这么理解,直方图是对数据的统计,并把统计值显示到事先设定好的bin(矩形条)中,bin中的数值是从数据中计算出的特征的统计量。总之,直方图获取的是数据分布的统计图,通常直方图的维数要低于原始数据。

        图像直方图是用一表示数字图像中亮度分布的直方图,标绘了图像中每个亮度值的像素数。可以借助观察该直方图了解需要如何调整亮度分布的直方图。这种直方图中,横坐标的左侧为纯黑、较暗的区域,而右侧为较亮、纯白的区域。因此,一张较暗图片的图像直方图中的数据多集中于左侧和中间部分,而整体明亮、只有少量阴影的图像则相反。计算机视觉邻域常借助图像直方图来实现图像的二值化。

        灰度直方图是一幅图像中个像素灰度值出现次数或频数的统计结果,它只反映该图像中灰度值出现的频率,而未反映某一灰度值像素所在的位置。也就是说,它只包含了该图像中某个灰度值的像素出现的概率,而丢失了其所在的位置的信息。

        任一幅图像,都能唯一地算出一幅与它对应的直方图。但不同的图像,可能有相同的直方图。即图像与直方图之间是多对一的映射关系。

直方图意义: 
        1. 直方图是图像中像素强度分布的图形表达方式。 
        2. 直方图统计了每一个强度值所具有的像素个数。

           直方图广泛应用于许多计算机视觉应用中。通过标记帧和帧之间显著的边缘和颜色的统计变化,来检测视频中场景的变换。通过在每个兴趣点设置一个有相近特征的直方图所构成的标签,用以确定图像中的兴趣点。边缘、色彩、角度等直方图构成了可以被传递给目标识别分类器的一个通用特征类型。色彩和边缘的直方图还可以用来识别网络视频是否被复制等。直方图是计算机视觉中最经典的工具之一,也是一个很好的图像特征表示手段。
 

一.直方图的计算与绘制:

API: 

 

 

将BGR图像划分为单通道,再分别计算直方图:

代码如下:


#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main()
{
	Mat src = imread("F:\\visual studio\\Image\\women2.jpg");
	if (src.empty())
	{
		cout << "Can't load the image" << endl;
		return -1;
	}
	//划分为单通道
	vector<Mat> bgr_planes;
	split(src, bgr_planes);
	//分别计算B、G、R通道的直方图
	//需要考虑的通道
	const int channels[1] = { 0 };
	//维数
	int dims = 1;
	//划分的子区间数 
	const int histsize[] = { 16 };
	//范围
	const float range[1][2] = { {0,256} };
	const float* histrange[] = { range[0] };
	Mat b_hist, g_hist, r_hist;
	//计算直方图
	calcHist(&bgr_planes[0], 1, channels, Mat(), b_hist, 1, histsize, histrange, true, false);
	calcHist(&bgr_planes[1], 1, channels, Mat(), g_hist, 1, histsize, histrange, true, false);
	calcHist(&bgr_planes[2], 1, channels, Mat(), r_hist, 1, histsize, histrange, true, false);

	//归一化
	int hist_h = 400;
	int hist_w = 512;
	int bin_w = cvRound(hist_w / histsize[0]);
	
	Mat histImage1(hist_h, bin_w*histsize[0], CV_8UC3, cv::Scalar(0, 0, 0));
	Mat histImage2(hist_h, bin_w*histsize[0], CV_8UC3, cv::Scalar(0, 0, 0));
	Mat histImage3(hist_h, bin_w*histsize[0], CV_8UC3, cv::Scalar(0, 0, 0));
	

	//归一化到直方图内
	normalize(b_hist, b_hist, 0, histImage1.rows, NORM_MINMAX, -1, Mat());
	normalize(g_hist, g_hist, 0, histImage2.rows, NORM_MINMAX, -1, Mat());
	normalize(r_hist, r_hist, 0, histImage3.rows, NORM_MINMAX, -1, Mat());
	

	/****对每个通道进行绘图****/  
	for (int i = 1; i <= histsize[0]; i++)
	{
		rectangle(histImage1, Rect(Point((i - 1)*bin_w, hist_h-1), Point((i*bin_w) - 1, hist_h - cvRound(b_hist.at<float>( i - 1)))), Scalar(255, 0, 0), FILLED, 8);
		rectangle(histImage2, Rect(Point((i - 1)*bin_w, hist_h - 1), Point((i*bin_w) - 1, hist_h - cvRound(g_hist.at<float>(i - 1)))), Scalar(0, 255, 0), FILLED, 8);
		rectangle(histImage3, Rect(Point((i - 1)*bin_w, hist_h - 1), Point((i*bin_w) - 1, hist_h - cvRound(r_hist.at<float>(i - 1)))), Scalar(0, 0, 255), FILLED, 8);
	}
	
	imshow("histImage1", histImage1);
	imshow("histImage2", histImage2);
	imshow("histImage3", histImage3);
	cv::waitKey(0);
	cv::destroyAllWindows();
	return 0;

}

 效果如下:

 

将BGR转化为HSV,对H、S通道的像素进行统计,计算直方图,即色调-饱和度直方图,这是一个二维图像,我们用颜色的深浅表示该区域的分布情况。

#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main()
{
	Mat src = imread("F:\\visual studio\\Image\\bird.jpg");
	if (src.empty())
	{
		cout << "Can't load the image" << endl;
		return -1;
	}
	Mat hsv;
	cvtColor(src, hsv, COLOR_BGR2HSV);
	//选择前个通道0,1
	const int channels[] = { 0,1 };
	//维数等于chanels中元素的个数
	int dims = 2;
	//划分的子区间数 
	int hbins = 18;
	int sbins = 16;
	const int histsize[] = { hbins,sbins };
	//范围
	const float range[2][2] = { {0,180},{0,256}};
	const float* histrange[] = { range[0],range[1]};
	Mat hist;
	//计算直方图
	calcHist(&hsv, 1, channels, Mat(), hist, dims, histsize, histrange, true, false);

	int scale = 30;
	Mat histImage(sbins*scale, hbins*scale, CV_8UC3, Scalar(0, 0, 0));

	
	//获得其中的最大值 
	/*double maxVal = 0;
	minMaxLoc(hist, 0, &maxVal, 0, 0);*/
	
	//归一化
	normalize(hist, hist, 0, 255, NORM_MINMAX, -1, Mat());


	for (int i = 0; i < hbins; i++)
	{
		for (int j = 0; j < sbins; j++)
		{
			
			/*float binVal = hist.at<float>(i, j);
			int intensity = cvRound(binVal * 255 / maxVal);*/		
			int intensity = hist.at<float>(i, j);
			//左下角代表(hbin0,sbin0)
			rectangle(histImage, Rect(Point(i*scale, histImage.rows - 1-j*scale), Point(((i+1)*scale) - 1, histImage.rows -1-((j+1)*scale) )),Scalar(intensity,0,0), FILLED, 8);
		}
	}
	imshow("histImage", histImage);	
	waitKey(0);
	return 0;
}

效果如下: 

 

二.直方图均衡化:

       直方图均衡化是通过拉伸像素强度的分布范围,使得在0~255灰阶上的分布更加均衡,提高了图像的对比度,达到改善图像主观视觉效果的目的。对比度较低的图像适合使用直方图均衡化方法来增强图像细节。

       均衡的具体过程可以参考此博文(如果有更好的原理介绍,欢迎在下方评论):

      直方图均衡化原理 - 天涯路清晨 - 博客园 https://www.cnblogs.com/tianyalu/p/5687782.html

      直方图均衡化的数学原理 - mjiansun的专栏 - CSDN博客 https://blog.csdn.net/u013066730/article/details/82969768

      说明一下直方图均衡化如何提高图像对比度:

      以下图为例,图像的灰度值大都集中在中间部分,而在均衡化之后,增加了两端灰度值的分布,也就是增加了最亮的(白色)和最暗(黑色)的部分,可以提高对比度。(文采不行,大家知道啥意思就可以)

       

API:

 

直方图均衡化代码实例: 

对灰度图进行直方图均衡:

#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main()
{
	Mat src = imread("F:\\visual studio\\Image\\desert.jpg");
	if (src.empty())
	{
		cout << "Can't load the image" << endl;
		return -1;
	}
	//转化为灰度图
	Mat graysrc;
	cvtColor(src, graysrc, COLOR_BGR2GRAY);
	imshow("graysrc", graysrc);
	//计算直方图
	Mat hist;
	const int channels[] = { 0 };
	const int histsize[] = { 32 };
	const float range[] = { 0,256 };
	const float* histrange[] = { range };
	calcHist(&graysrc, 1, channels, Mat(), hist, 1, histsize, histrange, true, false);

	//归一化
	int hist_h = 400;
	int hist_w = 512;
	int bin_w = cvRound(hist_w / histsize[0]);
	normalize(hist, hist, 0, hist_h, NORM_MINMAX, -1, Mat());
	
	Mat histImage(hist_h, bin_w*histsize[0], CV_8UC3, cv::Scalar(0, 0, 0));

	//绘制直方图
	for (int i = 0; i < histsize[0]; i++)
	{
		rectangle(histImage, Rect(Point(i*bin_w,hist_h-1), Point((i+1)*bin_w-1,hist_h-1-hist.at<float>(i))), Scalar(255, 0, 0), FILLED, LINE_AA);
	}
	imshow("hist1", histImage);

	//直方图均衡化
	Mat dst;
	equalizeHist(graysrc, dst);
	imshow("dst", dst);
	
	//再计算直方图并绘制 观察区别
	Mat dsthist;
	calcHist(&dst, 1, channels, Mat(), dsthist, 1, histsize, histrange, true, false);
	
	normalize(dsthist, dsthist, 0, hist_h, NORM_MINMAX, -1, Mat());

	Mat dsthistImage(hist_h, bin_w*histsize[0], CV_8UC3, cv::Scalar(0, 0, 0));
	for (int i = 0; i < histsize[0]; i++)
	{
		rectangle(dsthistImage, Rect(Point(i*bin_w, hist_h - 1), Point((i + 1)*bin_w - 1, hist_h - 1 - dsthist.at<float>(i))), Scalar(255, 0, 0), FILLED, LINE_AA);
	}
	imshow("hist2", dsthistImage);



	waitKey(0);

}

效果如下:

 

 

 对BGR图像进行直方图均衡,划为分多个单通道后,分别进行直方图均衡,然后再融合

#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

void calcuateHist(vector<Mat> &,const string * win);

int main()
{
	Mat src = imread("F:\\visual studio\\Image\\desert.jpg");
	if (src.empty())
	{
		cout << "Can't load the image" << endl;
		return -1;
	}
	imshow("src", src);
	
	//划分为单通道
	vector<Mat> bgr_planes;
	split(src, bgr_planes);
	string win[3] = { "hist1","hist2","hist3" };
	//计算直方图
	calcuateHist(bgr_planes,win);

	//直方图均衡
	equalizeHist(bgr_planes[0], bgr_planes[0]);
	equalizeHist(bgr_planes[1], bgr_planes[1]);
	equalizeHist(bgr_planes[2], bgr_planes[2]);
	string dstwin[3] = { "dsthist1","dsthist2","dsthist3" };
	//计算直方图
	calcuateHist(bgr_planes, dstwin);
	Mat dst;
	//通道融合
	merge(bgr_planes,dst);
	imshow("dst", dst);

	cv::waitKey(0);
	cv::destroyAllWindows();
	return 0;
}

void calcuateHist(vector<Mat> & bgr_planes,const string * win)
{
	//分别计算B、G、R通道的直方图
	//需要考虑的通道
	const int channels[1] = { 0 };
	//维数
	int dims = 1;
	//划分的子区间数 
	const int histsize[] = { 16 };
	//范围
	const float range[1][2] = { {0,256} };
	const float* histrange[] = { range[0] };
	Mat b_hist, g_hist, r_hist;
	//计算直方图
	calcHist(&bgr_planes[0], 1, channels, Mat(), b_hist, 1, histsize, histrange, true, false);
	calcHist(&bgr_planes[1], 1, channels, Mat(), g_hist, 1, histsize, histrange, true, false);
	calcHist(&bgr_planes[2], 1, channels, Mat(), r_hist, 1, histsize, histrange, true, false);

	//归一化
	int hist_h = 400;
	int hist_w = 512;
	int bin_w = cvRound(hist_w / histsize[0]);
	
	Mat histImage1(hist_h, bin_w*histsize[0], CV_8UC3, cv::Scalar(0, 0, 0));
	Mat histImage2(hist_h, bin_w*histsize[0], CV_8UC3, cv::Scalar(0, 0, 0));
	Mat histImage3(hist_h, bin_w*histsize[0], CV_8UC3, cv::Scalar(0, 0, 0));
	

	//归一化到直方图内
	normalize(b_hist, b_hist, 0, histImage1.rows, NORM_MINMAX, -1, Mat());
	normalize(g_hist, g_hist, 0, histImage2.rows, NORM_MINMAX, -1, Mat());
	normalize(r_hist, r_hist, 0, histImage3.rows, NORM_MINMAX, -1, Mat());
	

	/****对每个通道进行绘图****/
	for (int i = 1; i <= histsize[0]; i++)
	{
		rectangle(histImage1, Rect(Point((i - 1)*bin_w, hist_h-1), Point((i*bin_w) - 1, hist_h - cvRound(b_hist.at<float>( i - 1)))), Scalar(255, 0, 0), FILLED, 8);
		rectangle(histImage2, Rect(Point((i - 1)*bin_w, hist_h - 1), Point((i*bin_w) - 1, hist_h - cvRound(g_hist.at<float>(i - 1)))), Scalar(0, 255, 0), FILLED, 8);
		rectangle(histImage3, Rect(Point((i - 1)*bin_w, hist_h - 1), Point((i*bin_w) - 1, hist_h - cvRound(r_hist.at<float>(i - 1)))), Scalar(0, 0, 255), FILLED, 8);
	}
	
	imshow(win[0], histImage1);
	imshow(win[1], histImage2);
	imshow(win[2], histImage3);
}

效果如下:   (并不是对所有图像使用直方图均衡都可以提高对比度,有的会降低对比度或者使图像失真,一般直方图分布情况如下时,使用直方图均衡化可以提高对比度) 

绘制直方图时,当时for循环i变量的取值范围写错了,所以直方图最右面的部分没画出来!!

直方图匹配(规定化):

OpenCV直方图(直方图、直方图均衡,直方图匹配,原理、实现) - konglongdanfo - 博客园 https://www.cnblogs.com/konglongdanfo/p/9215091.html

 

直方图的对比:

直方图比较,是用一定的标准来判断两个直方图的相似度方法;

Opencv提供的比较方法有四种:

匹配方法有四种 :

CV_COMP_CHISQ卡方,返回值越小匹配度越高

CV_COMP_CORREL相关性匹配,返回值越大匹配程度越高

CV_COMP_INTERSECT 直方图相交,返回值越大匹配度越高

CV_COMP_BHATTACHARYYA返回值越小匹配度越高.

API:

 

代码示例:

 

#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main()
{
	Mat src1, src2, hsv1, hsv2;
	src1 = imread("F:\\visual studio\\Image\\compare1.jpg");
	if (src1.empty())
	{
		cout << "Can't load the image" << endl;
		return -1;
	}

	src2 = imread("F:\\visual studio\\Image\\compare2.jpg");
	if (src1.empty())
	{
		cout << "Can't load the image" << endl;
		return -1;
	}

	
	//转化到HSV色彩空间
	cvtColor(src1, hsv1, COLOR_BGR2HSV);
	cvtColor(src2, hsv2, COLOR_BGR2HSV);
	
	//选择HSV图像的前个通道0,1
	const int channels[] = { 0,1 };
	//维数等于chanels中元素的个数
	int dims = 2;
	//划分的子区间数 
	int hbins = 50;
	int sbins = 60;
	const int histsize[] = { hbins,sbins };
	
	//范围
	const float range[2][2] = { {0,180},{0,256} };
	const float* histrange[] = { range[0],range[1] };
	
	Mat hist1,hist2;
	//计算直方图
	calcHist(&hsv1, 1, channels, Mat(), hist1, dims, histsize, histrange, true, false);
	normalize(hist1, hist1, 0, 1, NORM_MINMAX, -1, Mat());
	
	calcHist(&hsv2, 1, channels, Mat(), hist2, dims, histsize, histrange, true, false);
	normalize(hist2, hist2, 0, 1, NORM_MINMAX, -1, Mat());
	
	//按顺序使用4种对比标准将src1图像的直方图与src1,src2的直方图进行对比
	for (int i = 0; i < 4; i++)
	{
		int compare_method = i;
		double src1src1 = compareHist(hist1, hist1, compare_method);
		double src1src2 = compareHist(hist1, hist2, compare_method);
		putText(src1, to_string(src1src1), Point(20, 40 * i + 30), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(255, 0, 0), 2, LINE_AA);
		putText(src2, to_string(src1src2), Point(20, 40 * i + 30), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(255, 0, 0), 2, LINE_AA);
	}
	imshow("src1", src1);
	imshow("src2", src2);
	waitKey(0);
	return 0;
}

我们利用四种方法将src1分别与自身和src2进行比较,得出的结果显示在图片上,效果如下:

 

直方图反向投影:

如果一幅图像的区域中显示的是一种结构纹理或者一个独特的物体,那么这个区域的直方图可以看做是一个概率函数,其表现形式是某个像素属于该纹理或物体的概率。而反向投影就是一种记录给定图像中的像素点如何适应直方图模型像素分布方式的一种方法。 
简单的讲,所谓反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的该特征的方法。

使用统计学语言进行分析,反向投影中储存的数值代表了图像中该像素属于区域的概率。

工作原理:

以使用H-S肤色直方图为例来解释反向投影的工作原理:

 

1.代码实现:

这里是在代码中选取ROI,之前这种方式一直得不到我想要的结果,后来找到了错误原因,已经记录在代码注释中。

被测图像是博主的手部图像,截取一部分区域作为目标图像,建立H-S直方图,反向变换后,显示结果。

#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

 int hbins = 6;
 int sbins = 8;
 Mat srchsv;
 Mat hsv;

void calcBackPro_demo(int pos, void* userdata);

int main()
{
	Mat src = imread("F:\\visual studio\\Image\\hand1.jpg");
	if (src.empty())
	{
		cout << "Can't load the image" << endl;
		return -1;
	}
	
	//长宽都变为原来的0.5倍 (我加载的图片太大了 不好显示)
	resize(src, src, Size(), 0.5, 0.5);
	imshow("src", src);
	
	
	//利用代码截取ROI
	//为了显示所取的区域
	Mat src1;
	src.copyTo(src1);
	rectangle(src1, Rect(src.cols / 3  ,  2 * src.rows / 3 , 50, 50), Scalar(0, 0, 255), 2, LINE_AA);
	imshow("src", src1);
	Mat base = src( Rect(src.cols / 3  ,  2 * src.rows / 3 , 50, 50));


	手动截取ROI 并保存 
	//Mat base  = imread("F:\\visual studio\\Image\\hand3.jpg");
	//if (base.empty())
	//{
	//	cout << "Can't load the image" << endl;
	//	return -1;
	//}
	
	imshow("base", base);
	//转化至HSV色彩空间
	//记录一下易错点:cvtColor(src, src, COLOR_BGR2HSV);
	
	//若这些写,src被转化为hsv空间,但是我们的base是通过ROI方式获得的,与src共享同一块区域,
	//此举也会更改base,最后得不到我们想要的效果!!
	
	cvtColor(src, srchsv, COLOR_BGR2HSV);
	cvtColor(base, hsv, COLOR_BGR2HSV);

	namedWindow("dst", WINDOW_AUTOSIZE);
	createTrackbar("Hbins", "dst", &hbins, 60, calcBackPro_demo);
	setTrackbarMin("Hbins", "dst", 2);
	createTrackbar("Sbins", "dst", &sbins, 64, calcBackPro_demo);
	setTrackbarMin("Sbins", "dst", 2);
	calcBackPro_demo(0, 0);


	waitKey(0);

}

void calcBackPro_demo(int pos, void* userdata)
{
	//计算直方图
	const int channels[] = { 0,1 };
	const int histsize[] = { hbins,sbins };
	const float range[2][2] = { {0,180},{0,256} };
	const float * histranges[] = { range[0],range[1] };
	Mat hist;
	calcHist(&hsv, 1, channels, Mat(), hist, 2, histsize, histranges, true, false);
	//归一化直方图数值至0-255;
	normalize(hist, hist, 0, 255, NORM_MINMAX);
	
	//绘制直方图
	int hist_h = 512;
	int hist_w = 540;
	int bin_h  = cvRound( hist_w / hbins);   //hbin区间的宽度
	int bin_s  = cvRound( hist_h / sbins);  //sbin区间的宽度
	Mat histImage(hbins*bin_h, sbins*bin_s, CV_8UC3, Scalar(0,0,0));
	for (int i = 0; i < hbins; i++)
	{
		for (int j = 0; j < sbins; j++)
		{
			int intensity = cvRound(hist.at<float>(i, j));
		
			rectangle(histImage, Rect(Point(i*bin_h, histImage.rows - 1 - j * bin_s), Point((i + 1)*bin_h -1, histImage.rows - (j + 1)*bin_s)), Scalar(intensity,0,0), FILLED, LINE_AA);
		}
	}
	imshow("histImage", histImage);

	//反向投影
	Mat backProject;
	calcBackProject(&srchsv, 1, channels, hist, backProject, histranges, 1, true);
	imshow("dst", backProject);

}

效果如下:

通过调节Hbins,Sbins,可以改变反向投影的效果: 

 

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值