opencv学习笔记三:直方图的计算和绘制

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_43667130/article/details/102755801

前言

此代码可依次复制粘贴,拼接即可运行

直方图绘制时使用opencv的一项基本的能力,但当初接触直方图是我并没有整太明白,如今又好好研究了一遍代码,现在来总结一下。
直方图,顾名思义,就是类似于我们常规意义上的统计图,有三个术语:

  1. dims:需要统计的特征的数目。
  2. bins:每个特征空间子区段的数目,可以翻译为“直条”和“组距”。
  3. range:每个特征空间的取值范围。例如:range = [0,255]。

直方图的计算与绘制

直方图的计算使用到了calcHist()函数,函数原型:

	calcHist(
				const Mat*   images,    //输入数组
				int          nimages,   //输入数组个数
				const int*   channels,  //通道索引
				InputArray   mask;      //Mat(),  //不使用腌膜
				OutputArray  hist,      //输出的目标直方图,一个二维数组
				int			 dims,      //需要计算的直方图的维度  例如:灰度,R,G,B,H,S,V等数据
				congst int*  histSize,   //存放每个维度的直方图尺寸的数组
				const float**    ranges, //每一维数组的取值范围数组
				bool          uniform=true,   
				bool          accumulate = false
			);

这里会用到一个返回最大值最小值的函数,在我的上篇博文《opencv学习笔记二:角点检测》中有介绍:

void cv::MinMaxLoc(
					arr ,  //输入单通道数组
	     double*	min_val, //返回最小值的指针
		 double*	max_val,//返回最大值的指针
		 Point*		min_loc,//指向返回最小值的位置指针
		 Point*		max_loc,//指向返回最大值的位置指针
)

注意:它的输入为单通道数组

示例程序一:计算并绘制图像的一维数组

我们先从绘制一维图像开始,一维直方图就像我们在word里用的柱状图一样,有这类似的思路:

  • 定义直方图需要统计的特征数目,如灰度,RGB,HSV等衡量标准
  • 定义每个特征的直方图尺寸大小
  • 定义通道索引,当计算RGB或HSV等特征时会用到
  • 定义每个特征的取值范围数组,即高度
  • 进行绘制
    下面看程序:
#include "pch.h"
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main()
{
	//【1】读取原图并显示
	Mat srcImage = imread("5.jpg", 0);
	imshow("原图:", srcImage);
	if (!srcImage.data) {
		cout << "fail to load image" << endl;
		return 0;
	}

首先,上面的是开头,将需要计算的图片载入并显示,原图如下图:
在这里插入图片描述
但我们载入的时候为: Mat srcImage = imread(“5.jpg”, 0); 是按灰度图载入的,因此显示出来为:
在这里插入图片描述

//【2】定义变量
	MatND dstHist;   
	int dims = 1;  //特征数目(直方图维度)
	float hranges[] = { 0,255 }; //特征空间的取值范围
	const float *ranges[] = { hranges };
	int size = 256;  //存放每个维度的直方图的尺寸的数组
	int channels = 0;  //通道数

然后就需要定义变量了,MatND为多维,多通道的密集数组类型
dims为特征数目,此程序只计算该图片的一个特征,且图片是一张灰度图,由后面的int channals = 0我们可以看出,计算的是该图片的通道0,也就是灰度的直方图。
hranges[]为特征空间的取值范围数组,为0-255;有几个特征就需要定义几个这样的数组,然后将这些数组存到const float *ranges[] = { hranges }中
size为存放每个维度的直方图的尺寸的数组

    //【3】计算直方图
	calcHist(&srcImage, 1, &channels, Mat(), dstHist, dims, &size, ranges);
	int scale = 1;

	cout << dstHist << endl;

	Mat dstImage(size * scale, size, CV_8U, Scalar(0));
	//【4】获取最大值和最小值
	double minValue = 0;
	double maxValue = 0;
	minMaxLoc(dstHist, &minValue, &maxValue, 0, 0);

接下来我们计算直方图并获取脂肪图的最大,最小值。我并不清楚计算出来的直方图是啥子?所以我在中间cout<<desHist<<endl;看了一下,如下图:
在这里插入图片描述
图片很长,是一个一列很多行的数组,这我们就明白了,每一行数都表示一个像素点的灰度值。

//【5】绘制直方图
	int hpt = saturate_cast<int>(0.9*size);
	for (int i = 0; i < 256; i++)
	{
		float binValue = dstHist.at<float>(i);
		int realValue = saturate_cast<int>(binValue*hpt / maxValue);
		rectangle(dstImage, Point(i*scale, size - 1), Point((i + 1)*scale - 1, size - realValue), Scalar(255));
	}

	imshow("一维直方图", dstImage);
	waitKey(0);

	return 0;
}

程序到此就结束了,在绘制直方图的这一段程序里,我们运用了rectangle()函数,函数原型如下:是一个画矩形的函数

rectangle(
		img,  //输入图像
		pt1,  //矩阵的一个定点
		pt2,  //矩阵对角线上另一个顶点
		color, //线条颜色(RGB)或亮度(灰度图像)(grayscale image)
		thickness,  //组成矩形的线条的粗细程度。取负值时函数绘制填充了色彩的矩形
		line_type,  //线条的类型  
		shift  //坐标点的小数点位数
		);

在其中有一句 int hpt = saturate_cast(0.9size);
感觉0.9出现的很突然,这一句其实是可以调整直方图绘制的大小的,看了下面截图应该就明白了:
当:int hpt = saturate_cast(0.9
size);
在这里插入图片描述
当:int hpt = saturate_cast(0.5*size);
在这里插入图片描述
看到这里就应该明白了吧?

示例程序二:H-S 二维直方图的计算与绘制

接下来我们看看包含H-S两个特征的二维直方图的绘制///

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

using namespace std;
using namespace cv;

int main()
{
	//【1】输入原图像,并转换为HSV颜色模型
	Mat srcImage, hsvImage;
	srcImage = imread("5.jpg");
	cvtColor(srcImage, hsvImage, COLOR_BGR2HSV);

还是,先读取一张图片,然后转换为HSV模型。H为色调,S为饱和度

//【2】参数准备
	//将色调量化为30个等级,将饱和度量化为32个等级
	int hueBinNum = 30;  //色调的直方图直条数量
	int saturationBinNum = 32;  //饱和度的直方图直条数量
	int histSize[] = { hueBinNum,saturationBinNum };    //存放每个维度的直方图尺寸的数组

	float hueRanges[] = { 0,180 }; //色调变化范围为0-179
	float saturationRanges[] = { 0,256 }; //定义饱和度变化范围为0(黑,白,灰)到255(纯光谱颜色)
	const float* ranges[] = { hueRanges,saturationRanges }; //每一位数值的取值范围数组

还是我们示例一程序中的思路,先定义bins,就是区间个数,示例一中我们定义为了256,在这儿我们将色调的设为30,饱和度的设为32;
然后将这两个尺寸放进int histSize[] = { hueBinNum,saturationBinNum };中用来传给直方图计算函数;
接着定义特征值的取值范围,色调的取值范围为0-180,饱和度的为0-256,同样,装进const float* ranges[] = { hueRanges,saturationRanges }; 数组中传给直方图计算函数;

//输出目标直方图
	MatND dstHist;
	//参数准备,calcHist函数中将计算第0通道和第一通道的直方图
	int channels[] = { 0,1 };
	//【3】 正式调用calcHist,进行直方图计算
	calcHist(
		&hsvImage,  //输入数组
		1,   //输入数组个数
		channels, //通道索引  
		Mat(),  //不使用腌膜 
		dstHist, //输出的目标直方图,一个二维数组  
		2,  //需要计算的直方图的维度为2
		histSize,  //存放每个维度的直方图尺寸的数组
		ranges,  //每一维数组的取值范围数组
		true,  
		false
	);
//打印直方图
	cout << dstHist<<endl;

然后就是直方图计算了,这里就不多说了。同样,我们还打印一下看看直方图计算出来是个什么东西:是一个比较大的二维数组,就是H-S的大小啦
在这里插入图片描述

	double maxValue = 0;   //最大值
	minMaxLoc(dstHist, 0, &maxValue, 0, 0);  //查找数组和子数组的全局最小值和最大值存入maxValue中
	int scale = 10;
	Mat histImg = Mat::zeros(saturationBinNum *scale, hueBinNum * 10, CV_8UC3);  //每个bin分配10个像素宽度
	//【5】双层循环,进行直方图绘制
	for (int hue = 0; hue < hueBinNum; hue++)
	{
		for (int saturation = 0; saturation < saturationBinNum; saturation++)
		{
			float binValue = dstHist.at<float>(hue, saturation);  //直方图直条的值  //访问像素值
			int intensity = cvRound(binValue * 255 / maxValue);  //强度    cvRound返回和参数最接近的整数值
			//正式进行绘制 
			rectangle(histImg, Point(hue* scale, saturation*scale),
				Point((hue + 1)*scale - 1, (saturation + 1)*scale - 1),
				Scalar::all(intensity), FILLED);
		}
	}
	//【6】显示效果图
	imshow("素材图", srcImage);
    imshow("H-S直方图", histImg);

	waitKey();

}

最后,就是绘制直方图了:运行结果如下:
在这里插入图片描述
对于二维的绘制过程,和画图表也很相似,原理就是下图:
在这里插入图片描述

示例程序三:绘制RGB三色直方图

最后我们绘制RGB三色直方图,虽然是三色,但也是一维的,而不像示例二中是二维直方图。

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

using namespace std;
using namespace cv;

int main()
{
	//【1】载入原图并显示
	Mat srcImage;
	srcImage = imread("5.jpg");
	imshow("素材图", srcImage);
	//【2】参数准备
	int bins = 256; //直条数 
	int hist_size[] = {bins}; //存放每个维度的直方图尺寸数组 //  均为256条宽度
	float range[] = { 0,256 };  //每一维数组的取值范围  // 均为0-255高度
	const float* ranges[] = { range }; 
	MatND redHist, grayHist, blueHist;  //定义三个图像数组

开头我们就不多说了,还是千篇一律的设置尺寸等,我们至此还没有定义通道;

//计算红色分量
	int channels_r[] = { 0 };  //每个图像数组一个通道
	calcHist(&srcImage, 1,channels_r, Mat(), redHist, 1, hist_size, ranges, true, false);
	//计算绿色分量
	int channels_g[] = { 1 };
	calcHist(&srcImage, 1, channels_g, Mat(), grayHist, 1, hist_size, ranges, true, false);
	//计算蓝色分量
	int channels_b[] = { 2 };
	calcHist(&srcImage, 1, channels_b, Mat(), blueHist, 1, hist_size, ranges, true, false);

然后我们分别定义了通道0,1,2并分别进行了计算;
最后就是绘制了:

//参数准备
	double maxValue_red, maxValue_green, maxValue_blue;
	minMaxLoc(redHist, 0, &maxValue_red, 0, 0);
	minMaxLoc(grayHist, 0, &maxValue_green, 0, 0);
	minMaxLoc(blueHist, 0, &maxValue_blue, 0, 0);
	int scale = 1;
	int histHeight = 256;
	Mat histImage = Mat::zeros(histHeight, bins * 3, CV_8UC3);
	//绘制直方图
	for (int i = 0; i < bins; i++)
	{
		float binValue_red = redHist.at<float>(i);
		float binValue_green = grayHist.at<float>(i);
		float binValue_blue = blueHist.at<float>(i);
		int intensity_red = cvRound(binValue_red*histHeight / maxValue_red);
		int intensity_green = cvRound(binValue_green*histHeight / maxValue_green);
		int intensity_blue = cvRound(binValue_blue*histHeight / maxValue_blue);

		rectangle(histImage, Point(i*scale, histHeight - 1), Point((i + 1)*scale - 1, histHeight - intensity_red), Scalar(255, 0, 0));
		rectangle(histImage, Point((i+bins)*scale, histHeight - 1), Point((i+bins+ 1)*scale - 1, histHeight - intensity_green), Scalar(0, 255, 0));
		rectangle(histImage, Point((i+bins*2)*scale, histHeight - 1), Point((i + bins*2+1)*scale - 1, histHeight - intensity_blue), Scalar(0, 0, 255));


	} 

	imshow("图像的RGB直方图", histImage);
	waitKey(0);
	return 0;

}

运行结果如下:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43667130/article/details/102755801