计算图像的直方图是图像处理领域一个非常常见的基本操作。 OpenCV 中提供了 calcHist 函数来计算图像直方图。不过这个函数说实话挺难用的,研究了好久才掌握了些基本的用法。
calcHist 函数 C++ 的函数原型如下:
void calcHist(const Mat* images,
int nimages,
const int* channels,
InputArray mask,
SparseMat& hist,
int dims,
const int* histSize,
const float** ranges,
bool uniform=true,
bool accumulate=false )
各个参数的含义如下:
images:输入的图像的指针,可以是多幅图像,但是所有的图像必须有同样的深度(CV_8U or CV_32F)。一副图像可以有多个 channels。
nimages:输入的图像的个数。
channels:用来计算直方图的 channels 的数组。
mask:掩码,如果mask不为空,那么它必须是一个8位(CV_8U)的数组,并且它的大小的和输入的图像的大小相同,值非 0 的点将用来计算直方图。
hist:输出参数,计算出来的直方图。
dims:直方图的维数,不能大于 CV_MAX_DIMS (在现有版本中是 32)。
histSize: 在每一维上直方图的元素个数。
ranges: 所需计算直方图的每一维的范围。ranges 是个指向数组的数组。我们称它指向的那些数组为 ranges 的元素,元素大小也就是这些数组的长度。
如果参数 uniform 为 true,这此时的ranges 里元素的大小为 2(也就是说 ranges 指向一系列长度为 2 的数组),存储的是每一维的上下限这两个数字;如果参数 uniform 为 false,则此时的 ranges 的元素大小为 bin 的个数,存储的是一个个颜色值,即每一维的坐标值不一定是均匀的,需要人为指定。
uniform: 如果为 true 的话,则说明所需计算的直方图的每一维按照它的范围和尺寸大小均匀取值;如果为 false 的话,说明直方图的每一维不是均匀分布取值的,参考参数 ranges 的解释。
accumulate: 表示是否对传入的 hist 清零。不清零的话可以将多幅图像的直方图累加。
看上面这个解释大家应该就能感觉出这个函数有多麻烦了吧。不过好在我们一般只会用到一些最基本的功能。比如计算个单通道灰度图像的直方图。直方图的取值范围一般来说也会是 0 到 255。 这时我们可以把这个函数再进行一次封装,使其更好用一些。
下面的代码来自 《OpenCV 2 Computer Vision Application Programming Cookbook》。 书的作者写了一个类,叫做 Histogram1D。
这个类的声明如下:
class Histogram1D
{
public:
Histogram1D()
{
// Prepare arguments for 1D histogram
histSize[0] = 256;
hranges[0] = 0.0;
hranges[1] = 255.0;
ranges[0] = hranges;
channels[0] = 0; // by default, we look at channel 0
}
~Histogram1D();
// Computes the 1D histogram and returns an image of it.
cv::Mat getHistogramImage(const cv::Mat &image);
// Computes the 1D histogram.
cv::MatND getHistogram(const cv::Mat &image);
private:
int histSize[1]; // number of bins
float hranges[2]; // min and max pixel value
const float* ranges[1];
int channels[1]; // only 1 channel used here
};
getHistogram 函数用来计算直方图。实现如下:
// Computes the 1D histogram.
cv::MatND Histogram1D::getHistogram(const cv::Mat &image)
{
cv::MatND hist;
// Compute histogram
cv::calcHist(&image,
1, // histogram from 1 image only
channels, // the channel used
cv::Mat(), // no mask is used
hist, // the resulting histogram
1, // it is a 1D histogram
histSize, // number of bins
ranges // pixel value range
);
return hist;
}
getHistogramImage 函数用来生成直方图的图像:
// Computes the 1D histogram and returns an image of it.
cv::Mat Histogram1D::getHistogramImage(const cv::Mat &image)
{
// Compute histogram first
cv::MatND hist = getHistogram(image);
// Get min and max bin values
double maxVal = 0;
double minVal = 0;
cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);
// Image on which to display histogram
cv::Mat histImg(histSize[0], histSize[0], CV_8U, cv::Scalar(255));
// set highest point at 90% of nbins
int hpt = static_cast<int>(0.9 * histSize[0]);
// Draw a vertical line for each bin
for( int h = 0; h < histSize[0]; h++ )
{
float binVal = hist.at<float>(h);
int intensity = static_cast<int>(binVal * hpt / maxVal);
// This function draws a line between 2 points
cv::line(histImg, cv::Point(h, histSize[0]),
cv::Point(h,histSize[0]-intensity), cv::Scalar::all(0));
}
return histImg;
}
还有另外一个类可以计算彩色图像的直方图。不过这个用途不是很大。因为彩色图像的颜色空间太大了。而且形成的直方图是个三维的数组。用起来也不方便。为了完整,这里还是把代码列了出来。
class ColorHistogram
{
public:
ColorHistogram()
{
// Prepare arguments for a color histogram
histSize[0] = histSize[1] = histSize[2] = 256;
hranges[0] = 0.0; // BRG range
hranges[1] = 255.0;
ranges[0] = hranges; // all channels have the same range
ranges[1] = hranges;
ranges[2] = hranges;
channels[0] = 0; // the three channels
channels[1] = 1;
channels[2] = 2;
}
cv::MatND getHistogram(const cv::Mat &image) ;
cv::SparseMat getSparseHistogram(const cv::Mat &image) ;
private:
int histSize[3];
float hranges[2];
const float* ranges[3];
int channels[3];
};
这里实现了两个函数 getHistogram 和 getSparseHistogram。 唯一的区别就是 getSparseHistogram 返回的是个稀疏矩阵。
cv::MatND ColorHistogram::getHistogram(const cv::Mat &image)
{
cv::MatND hist;
// Compute histogram
cv::calcHist(&image,
1, // histogram of 1 image only
channels, // the channel used
cv::Mat(), // no mask is used
hist, // the resulting histogram
3, // it is a 3D histogram
histSize, // number of bins
ranges // pixel value range
);
return hist;
}
cv::SparseMat ColorHistogram::getSparseHistogram(const cv::Mat &image)
{
cv::SparseMat hist(3,histSize,CV_32F);
// Compute histogram
cv::calcHist(&image,
1, // histogram of 1 image only
channels, // the channel used
cv::Mat(), // no mask is used
hist, // the resulting histogram
3, // it is a 3D histogram
histSize, // number of bins
ranges // pixel value range
);
return hist;
}