計算圖像的直方圖是圖像處理領域一個非常常見的基本操作。 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(0.9 * histSize[0]);
// Draw a vertical line for each bin
for( int h = 0; h < histSize[0]; h++ )
{
float binVal = hist.at(h);
int intensity = static_cast(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;
}