opencv学习笔记三:直方图的计算和绘制
前言
此代码可依次复制粘贴,拼接即可运行
直方图绘制时使用opencv的一项基本的能力,但当初接触直方图是我并没有整太明白,如今又好好研究了一遍代码,现在来总结一下。
直方图,顾名思义,就是类似于我们常规意义上的统计图,有三个术语:
- dims:需要统计的特征的数目。
- bins:每个特征空间子区段的数目,可以翻译为“直条”和“组距”。
- 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.9size);
当: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;
}
运行结果如下: