简介
在上一篇文章中,我们知道了如何去进行直方图均衡化来增强图像。但是,相信大家会有一个疑惑,那就是如何获取一幅图像的直方图,今天,我们就简单的讲一下如何获取一幅图像的直方图。
想得到一幅图像的直方图很容易,简单的讲:我们可以通过下面几步来得到想要的结果。
1、加载一幅图像
2、设置一些参数
3、计算直方图
4、归一化(可选)
5、定义画布
6、绘制直方图
加载一幅图像很简单,我们已经知道,使用imread()函数来实现。那么第二步的设置参数,需要设置哪些参数呢?首先我们需要了解几个概念。
我们知道灰度图像的直方图就是对灰度图像的强度进行统计。那么首先我们要确定一下,我们需要把灰度值化为几类?如果化为两类,则可以定义为[0-127],[128-255]。所划分区间的个数就称为bins。而要就算的的灰度的强度范围就称为range。对于灰度强度即为[0-255]。当然了,我们不仅可以利用直方图来统计灰度信息。还可以统计一副图像的其他信息。我们把要统计的每类信息称为特征空间。
我们在程序中这样来设置这些参数:
/// 设置 bins
int histSize = 256;
/// 设置区间
float range[] = { 0, 256 } ;
const float* histRange = { range };
了解这些参数之后,我们进入下一步,计算直方图。毫无疑问,这是我们本篇中最主要的内容。也是我们最无从下手的内容。不过不用担心,OpenCV为我们包揽了这一切。我们可以使用calcHist()函数来轻松的完成这一步。calcHist()函数的原型如下:
//! computes the joint dense histogram for a set of images.
CV_EXPORTS void calcHist( const Mat* images, int nimages,
const int* channels, InputArray mask,
OutputArray hist, int dims, const int* histSize,
const float** ranges, bool uniform=true, bool accumulate=false );
images:原图像数组,(我们输入图像可以不止一个)。
nimages:输入图像的个数。
channels:用于计算直方图的特征通道,第一个数组通道是从0到images[0].channels()-1,第二个数组通道是从images[0].channels()到images[0].channels+images[1].channels -1等等。
mask:掩码。如果mask不为空,那么它必须是一个8位(CV_8U)的数组,并且它的大小的和arrays[i]的大小相同,值为1的点将用来计算直方图。
hist:输出的直方图
dims:直方图的维数,必须为正数。
histSize:每一维直方图的尺寸,简单地讲,如果dims为1,则histSize即为bins。
ranges:用于进行统计的范围。
uniform:决定是否均匀分布bins。
accumulate:再次绘制直方图时,决定是否清除原来的痕迹。
我们在程序中这样使用该函数:
bool uniform = true;
bool accumulate = false;
Mat hist;
/// 计算直方图
calcHist( &gray_image, 1, 0, Mat(), hist, 1, &histSize, &histRange, uniform, accumulate );
紧接着我们需要进行归一化,之所以进行归一化是为了更好的去显示图像。我们可以使用normalize()函数来完成这一功能:我们在程序中这样来使用:
/// 归一化到 [ 0, histImage.rows ]
normalize( hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
第一个hist为输入数组,第二个hist为归一化后的输出数组,(支持就地计算)。0和histImage.rows为归一化的范围。
NORM_MINMAX为归一化的方法。-1表示归一化后的输出数组和输入数组同类型。
Mat()是可选的掩码。
之后我们就可以定义画布。即声明一个Mat类型的数组。
// 创建画布
int hist_w = 512; int hist_h = 400;
int bin_w = cvRound( (double) hist_w/histSize );
Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
下面我们在画布上进行绘制直方图
/// 绘制直方图
for( int i = 1; i < histSize; i++ )
{
rectangle( histImage, Point( bin_w*(i-1), hist_h - cvRound(hist.at<float>(i-1)) ),
Point( bin_w*(i), hist_h ), Scalar( 0, 0, 255 ), -1 );
}
完整程序:
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace std;
using namespace cv;
/**
* @function main
*/
int main( int argc, char** argv )
{
Mat src;
if( argc != 2 )
{
cout << "输入参数个数错误" << endl;
return -1;
}
/// 加载图像
src = imread( argv[1], CV_LOAD_IMAGE_COLOR );
Mat gray_image;
cvtColor(src, gray_image, CV_BGR2GRAY);
if( !gray_image.data )
{
cout << "图像加载失败" << endl;
return -1;
}
/// 设置 bins
int histSize = 256;
/// 设置区间
float range[] = { 0, 256 } ;
const float* histRange = { range };
bool uniform = true;
bool accumulate = false;
Mat hist;
/// 计算直方图
calcHist( &gray_image, 1, 0, Mat(), hist, 1, &histSize, &histRange, uniform, accumulate );
// 创建画布
int hist_w = 512; int hist_h = 400;
int bin_w = cvRound( (double) hist_w/histSize );
Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
/// 归一化到 [ 0, histImage.rows ]
normalize( hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
/// 绘制直方图
for( int i = 1; i < histSize; i++ )
{
rectangle( histImage, Point( bin_w*(i-1), hist_h - cvRound(hist.at<float>(i-1)) ),
Point( bin_w*(i), hist_h ), Scalar( 0, 0, 255 ), -1 );
}
namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE );
imshow("calcHist Demo", histImage );
waitKey(0);
return 0;
}
运行结果:
我们使用上一篇文章中的图像进行测试,结果如下:
而均衡化后的图像的直方图如下: