OpenCV10-图像直方图:直方图绘制、直方图归一化、直方图比较、直方图均衡化、直方图规定化、直方图反射投影

本文围绕OpenCV展开图像直方图处理的介绍。涵盖直方图的绘制、归一化、比较、均衡化、规定化及反向投影等内容。阐述各处理方法的原理,如通过直方图分析图像亮暗对比度,利用相关函数实现不同处理操作,还给出部分示例程序。

OpenCV10-图像直方图:直方图绘制、直方图归一化、直方图比较、直方图均衡化、直方图规定化、直方图反射投影


1.直方图的绘制

图像直方图就是统计图像中每个灰度值的个数,之后将灰度值作为横轴,以灰度值个数或者灰度值所占比率作为纵轴的统计图。通过直方图,可以看出图像中哪些灰度值数目较多,哪些较少,可以通过一定的方法将灰度值较为集中的区域映射到较为稀疏的区域,从而使图像在像素灰度值上的分布更加符合期望状态。在通常情况下,像素灰度值代表亮暗程度,因此通过直方图,可以分析图像亮暗对比度,并调整图像的亮暗程度。

在OpenCV中,只提供了图像直方图的统计函数calcHist(),该函数能够统计出图像中每个灰度值的个数,但是,对于直方图的绘制,需要自行进行:

void calcHist(
    const Mat* images,   // 待统计直方图的图像数组,数据类型只能是CV_8U、CV_16U、CV_32F,不同图像的通道数可以不同
    int nimages,         // 输入图像数量
    const int* channels, // 需要统计的通道索引数组,第一个图像的通道索引号从0到images[0].channels()-1,第二个图像的通道索引从image[0].channels()到image[0].channels()+image[1].channels()-1,以此类推
    InputArray mask,      // 可选的操作掩码,如果是空矩阵表示图像中所有位置的像素都计入直方图中
    OutputArray hist,     // 输出的统计直方图结果,时dims维度的数组
    int dims,  // 需要计算直方图的维度,必须是整数,不能大于CV_MAX_DIMS
    const int* histSize,  // 存放每个维度直方图的数组的尺寸
    const float** ranges, // 每个图相同道中灰度值的取值范围
    bool uniform = true,  // 直方图是否均匀的标志
    bool accumulate = false // 是否累积统计直方图的标志,如果累积,那么在统计新图像时,之前的统计结果不会被清除,该参数主要用于统计多个图像整体的直方图。
);

由于该函数具有较多参数,并且每个参数都较为复杂,因此在使用该函数时只统计单通道图像的灰度值分布,对于多个通道图像,可以将图像每个通道分离后再进行统计。

#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main()
{
   
   
	cout << "OpenCV Version: " << CV_VERSION << endl;
	utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);

	Mat img = imread("apple.jpg");
	if (img.empty())
	{
   
   
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	//设置提取直方图的相关变量
	Mat hist;  //用于存放直方图计算结果
	const int channels[1] = {
   
    0 };  //通道索引
	float inRanges[2] = {
   
    0,255 };
	const float* ranges[1] = {
   
    inRanges };  //像素灰度值范围
	const int bins[1] = {
   
    256 };  //直方图的维度,其实就是像素灰度值的最大值
	calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);  //计算图像直方图

	cout << "hist.channels(): " << hist.channels() << endl; // 1

	double maxval = 0.0;
	minMaxLoc(hist, 0, &maxval);
	cout << "hist maxval: " << maxval << endl; // 最多的一个灰度值数量有3896

	//准备绘制直方图
	int hist_w = 512;   // 256个灰度值,每个灰度值画成矩形,矩形宽度为2
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
	for (int i = 1; i <= hist.rows; i++)
	{
   
   
		rectangle(histImage, Point(width * (i - 1), hist_h - 1),
			// cvRound四舍五入取整,因为灰度值数量太多,所以除以了15
			Point(width * i - 1, hist_h - cvRound(hist.at<float>(i - 1) / 15)),
			Scalar(255, 255, 255), -1);
	}
	namedWindow("histImage", WINDOW_AUTOSIZE);
	imshow("histImage", histImage);
	imshow("gray", gray);

	int k = waitKey(0); // Wait for a keystroke in the window
	return 0;
}

由于图像中灰度值像素数目较多,因此将每个灰度值数目缩小为原来的 1 15 \frac{1}{15} 151 后再进行绘制。

2.直方图归一化

前面完成了对一幅图像像素灰度值的统计,并成功绘制了图像的直方图。直方图可以用来表示图像的明亮程度,从理论上讲,通过对同一个图像缩放后得到的两幅尺寸不一样的图像将具有大致相似的直方图分布特性,因此用灰度值的数目作为统计结果具有一定的局限性。

OpenCV提供了normalize函数实现多种形式的归一化功能:

void normalize(
    InputArray src, 
    InputOutputArray dst, 
    double alpha = 1, // 在范围归一化的情况下,归一化到下限边界的标准值
    double beta = 0,  // 在范围归一化时的上限范围,他不用于标准规范化 
    int norm_type = NORM_L2,  // 归一化过程中数据范数种类标志,常用的可选参数如下所示
    int dtype = -1, // 输出数据类型,默认即可 
    InputArray mask = noArray() // 掩码矩阵
);
/*
该函数通过alpha设置将数据缩放到最大范围,通过norm_type参数选择计算范数的种类,之后将输入矩阵中的每个数据分别除以求取得范数数值,最后得到缩放的结果。


norm_type:计算不同的范数,最后的结果也不相同。
NORM_INF: x/max
NORM_L1: x/sum(ary)
NORM_L2: x/sqrt( sum(pow(i,2)) )
NORM_MINMAX: (x-min)/(max-min)
*/

下面通过不同方式归一化数组 [2.0, 8.0, 10.0],并且分别用灰度值所占比例除以数据最大值的方式对图像进行归一化操作。为了更加直观展示直方图归一化后的结果,将每个灰度值所占比例放大了30倍,并将直方图的图像高度作为1进行绘制:

#include <opencv2/core/utils/logger.hpp>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main()
{
   
   
	cout << "OpenCV Version: " << CV_VERSION << endl;
	utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);

	//system("color F0");  //更改输出界面颜色
	vector<double> positiveData = {
   
    2.0, 8.0, 10.0 };
	vector<double> normalized_L1, normalized_L2, normalized_Inf, normalized_L2SQR;
	//测试不同归一化方法
	normalize(positiveData, normalized_L1, 1.0, 0.0, NORM_L1);  //绝对值求和归一化
	cout << "normalized_L1=[" << normalized_L1[0] << ", "
		<< normalized_L1[1] << ", " << normalized_L1[2] << "]" << endl;
	normalize(positiveData, normalized_L2, 1.0, 0.0, NORM_L2);  //模长归一化
	cout << "normalized_L2=[" << normalized_L2[0] << ", "
		<< normalized_L2[1] << ", " << normalized_L2[2] << "]" << endl;
	normalize(positiveData, normalized_Inf, 1.0, 0.0, NORM_INF);  //最大值归一化
	cout << "normalized_Inf=[" << normalized_Inf[0] << ", "
		<< normalized_Inf[1] << ", " << normalized_Inf[2] << "]" << endl;
	normalize(positiveData, normalized_L2SQR, 1.0, 0.0, NORM_MINMAX);  //偏移归一化
	cout << "normalized_MINMAX=[" << normalized_L2SQR
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shlyyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值