OpenCV4入门教程078:图像直方图

索引地址:系列索引

直方图

直方图(Histogram),又称质量分布图,是一种统计报告图,由一系列高度不等的纵向条纹或线段表示数据分布的情况。 一般用横轴表示数据类型,纵轴表示分布情况。

直方图是数值数据分布的精确图形表示。 这是一个连续变量(定量变量)的概率分布的估计,并且被卡尔·皮尔逊(Karl Pearson)首先引入。它是一种条形图。 为了构建直方图,第一步是将值的范围分段,即将整个值的范围分成一系列间隔,然后计算每个间隔中有多少值。 这些值通常被指定为连续的,不重叠的变量间隔。 间隔必须相邻,并且通常是(但不是必须的)相等的大小。

直方图也可以被归一化以显示“相对”频率。 然后,它显示了属于几个类别中的每个案例的比例,其高度等于1。

灰度直方图(Histogram)是数字图像处理中最简单、最有用的工具之一,它概括了一幅图像的灰度级内容此处对直方图的数学原理以及OpenCV中的示例进行了展示。

定义

灰度直方图(histogram)是灰度级的函数,描述的是图像中每种灰度级像素的个数,反映图像中每种灰度出现的频率。横坐标是灰度级,纵坐标是灰度级出现的频率(对数字图像来说,意味着该灰度级像素的个数)。

histogram

histogram

过通常会将纵坐标归一化到[0,1]区间内,也就是将灰度级出现的频率(像素个数)除以图像中像素的总数。

对于连续图像,平滑地从中心的高灰度级变化到边缘的低灰度级。直方图定义为:

H(D)=limδD+A(D)A(D+δD)δD=dδDA(D)H(D)={\lim_{\delta D \to +\infty}}{\frac {A(D)-A(D+\delta D)} {\delta D}}=-\frac {d} {\delta D} A(D)

其中A(D)A(D)为阈值面积函数:为一幅连续图像中被具有灰度级D的所有轮廓线所包围的面积。

上式说明,一幅连续图像的直方图是其面积函数的导数的负值。

直方图的绘制

将图像的灰度归一化

若图像的灰度级为0,1,……,L-1,令rk=kL1,k=0,1,,L1r_k=\frac {k} {L-1},k=0,1,⋯,L-1

0rk10≤r_k≤1,L为灰度级的层数,ΔLk=rk+1rkΔL_k=r_{k+1}-r_k为灰度间隔。

计算各灰度级的像素频数(或概率)

nkn_k表示灰度级为rkr_k的像素的个数,N为总的像素个数,计算像素频数的公式为:
pr(rk)=nkNp_r(r_k)=\frac {n_k} {N}

作图

建立直角坐标系,横轴表示灰度级rkr_k的取值,纵轴表示灰度级对应的概率pr(rk)p_r(r_k)

下面为对一幅10X10的图像求其8级灰度直方图的示例

histogram

直方图的性质

图像被缩减成直方图后,所有的空间信息都丢失了,也就是说,直方图不能提供像素在图像中的位置信息。因此不同的图像可以有相同的直方图。

OpenCV绘制直方图

直方图的计算是很简单的,无非是遍历图像的像素,统计每个灰度级的个数。在OpenCV中封装了直方图的计算函数calcHist,为了更为通用,该函数的参数有些复杂,其声明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
calcHist(
const Mat* images,//输入图像的数组,这些图像要有仙童大小、深度(CV_8U,CV_16U,CV_32F)
int images,// 图像数目
const int* channels,// 通道数,要计算的通道数的下标,可以传一个数组 {0, 1} 表示计算第0通道与第1通道的直方图,此数组长度要与histsize ranges 数组长度一致
InputArray mask,//输入mask,可选。如有,则表示只计算mask元素值为非0的位置的直方图
OutputArray hist,//输出的直方图数据
int dims,// 直方图的维度
const int* histsize,//在每一维上直方图的个数。
//简单把直方图看作一个一个的竖条的话,就是每一维上竖条的个数。
const float* ranges,// 直方图每个维度要统计的灰度级的范围
bool uniform,// true by default 是否归一化
bool accumulate// false by defaut
)

测试代码:

普通的一维直方图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//---------------------------------【头文件、命名空间包含部分】----------------------------
// 描述:包含程序所使用的头文件和命名空间
//------------------------------------------------------------------------------------------------
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;

//--------------------------------------【main( )函数】-----------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-------------------------------------------------------------------------------------------------
int main() {
//【1】载入原图并显示
Mat srcImage = imread("1.jpg", 0);
imshow("Original", srcImage);
if (!srcImage.data) {
cout << "fail to load image" << endl;
return 0;
}

//【2】定义变量
MatND dstHist; // 在cv中用CvHistogram *hist = cvCreateHist
int dims = 1;
float hranges[] = {0, 255};
const float *ranges[] = {hranges}; // 这里需要为const类型
int size = 256;
int channels = 0;

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

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

//【5】绘制出直方图
int hpt = saturate_cast<int>(0.9 * size);
for (int i = 0; i < 256; i++) {
float binValue =
dstHist.at<float>(i); // 注意hist中是float类型 而在OpenCV1.0版中用cvQueryHistValue_1D
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("Result", dstImage);
waitKey(0);
return 0;
}

测试结果:

histogram

动态调整直方图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//====================================================================//
// Created by liheng on 19-3-6.
//Program:图像灰度直方图示例,及动态调整“竖条”数量,观察直方图效果
//Data:2019.3.6
//Author:liheng
//Version:V1.0
//====================================================================//

#include <opencv2/opencv.hpp>


cv::Mat src;//需要计算直方图的灰度图像
cv::Mat histimg;//进行直方图展示的图
cv::MatND hist;//计算得到的直方图结果

int histSize = 50; // 划分HIST的初始个数,越高越精确

//滚动条函数
void HIST(int t,void*)
{
char string[10];

if(histSize==0)
{
printf("直方图条数不能为零!\n");
}
else
{
int dims = 1;
float hranges[2] = {0, 255};
const float *ranges[1] = {hranges}; // 这里需要为const类型
int channels = 0;

histimg.create(512,256*4,CV_8UC3);
histimg.setTo(cv::Scalar(0,0,0));

//计算图像的直方图
calcHist(&src, 1, &channels, cv::Mat(), hist, dims, &histSize, ranges); // cv 中是cvCalcHist
//normalize(hist, hist, 0, histimg.rows*0.5, NORM_MINMAX, -1, Mat());//将直方图归一化,防止某一条过高,显示不全


double maxVal = 0;
cv::Point maxLoc;
cv::minMaxLoc(hist, NULL, &maxVal, NULL, &maxLoc);//寻找最大值及其位置


double bin_w =(double) histimg.cols / histSize; // histSize: 条的个数,则 bin_w 为条的宽度
double bin_u = (double)histimg.rows/ maxVal; // maxVal: 最高条的像素个数,则 bin_u 为单个像素的高度

// 画直方图
for(int i=0;i<histSize;i++)
{
cv::Point p0=cv::Point(i*bin_w,histimg.rows);

float binValue = hist.at<float>(i); // 注意hist中是float类型
cv::Point p1=cv::Point((i+1)*bin_w,histimg.rows-binValue*bin_u);

cv::rectangle(histimg,p0,p1,cv::Scalar(0,255,0),2,8,0);
}

//曲线形式的直方图
for (int i = 0; i < histSize; i++)
{
cv::line(histimg,
cv::Point(bin_w*i+bin_w/2, histimg.rows-hist.at<float>(i)*bin_u),
cv::Point(bin_w*(i+1)+bin_w/2, histimg.rows-hist.at<float>(i+1)*bin_u),
cv::Scalar(255, 0, 0), 2, 8, 0);//bin_w/2是为了保证折现位于直方图每条的中间位置
}

//画纵坐标刻度(像素个数)
int kedu=0;
for(int i=1;kedu<maxVal;i++)
{
kedu=i*maxVal/10;
sprintf(string,"%d",kedu);//把一个整数转换为字符串
//在图像中显示文本字符串
cv::putText(histimg, string , cv::Point(0,histimg.rows-kedu*bin_u), cv::FONT_HERSHEY_SIMPLEX,0.5,cv::Scalar(0,255,255),1);

cv::line( histimg,cv::Point(0,histimg.rows-kedu*bin_u),cv::Point(histimg.cols-1,histimg.rows-kedu*bin_u),cv::Scalar(0,0,255));
}
//画横坐标刻度(像素灰度值)
kedu=0;
for(int i=1;kedu<256;i++)
{
kedu=i*20;
sprintf(string,"%d",kedu);//把一个整数转换为字符串
//在图像中显示文本字符串
putText(histimg, string , cv::Point(kedu*(histimg.cols / 256),histimg.rows), cv::FONT_HERSHEY_SIMPLEX,0.5,cv::Scalar(0,255,255),2);
}

cv::imshow( "Histogram", histimg );
}
}

int main( int argc, char** argv )
{

src = cv::imread("../pictures/lena.jpg",0);

cv::namedWindow( "src", 1);
cv::imshow( "src", src);
cv::namedWindow( "Histogram", 1 );

int maxvalue = 256;
cv::createTrackbar( "histSize", "src", &histSize, maxvalue, HIST );
HIST(0,0);
cv::waitKey(0);

cv::destroyWindow("src");
cv::destroyWindow("Histogram");

return 0;
}

测试结果:

histogram

对一副彩色图的每个通道进行绘制直方图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//---------------------------------【头文件、命名空间包含部分】----------------------------
// 描述:包含程序所使用的头文件和命名空间
//------------------------------------------------------------------------------------------------
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/opencv.hpp>
using namespace cv;

//--------------------------------------【main( )函数】-----------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-----------------------------------------------------------------------------------------------
int main() {

//【1】载入素材图并显示
Mat srcImage;
srcImage = imread("1.jpg");
imshow("Original", srcImage);

//【2】参数准备
int bins = 256;
int hist_size[] = {bins};
float range[] = {0, 256};
const float *ranges[] = {range};
MatND redHist, grayHist, blueHist;
int channels_r[] = {0};

//【3】进行直方图的计算(红色分量部分)
calcHist(&srcImage, 1, channels_r, Mat(), //不使用掩膜
redHist, 1, hist_size, ranges, true, false);

//【4】进行直方图的计算(绿色分量部分)
int channels_g[] = {1};
calcHist(&srcImage, 1, channels_g, Mat(), // do not use mask
grayHist, 1, hist_size, ranges,
true, // the histogram is uniform
false);

//【5】进行直方图的计算(蓝色分量部分)
int channels_b[] = {2};
calcHist(&srcImage, 1, channels_b, Mat(), // do not use mask
blueHist, 1, hist_size, ranges,
true, // the histogram is uniform
false);

//-----------------------绘制出三色直方图------------------------
//参数准备
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));
}

//在窗口中显示出绘制好的直方图 图像的RGB直方图
imshow("Result", histImage);
waitKey(0);
return 0;
}

测试结果:

histogram


OpenCV4入门教程078:图像直方图
https://feater.top/opencv/opencv-image-histogram/
作者
JackeyLea
发布于
2020年6月21日
许可协议