OpenCV计算图像直方图,图像由各种数值的像素构成。例如在单通道灰度图像中,每个像素都有一个0(黑色)~255(白色)的整数。对于每个灰度,都有不同数量的像素分布在图像内,具体取决于图片内容。
直方图是一个简单的表格,表示一幅图像(有时是一组图像)中具有某个值的像素的数量。因此,灰度图像的直方图有256个项目,也叫箱子(bin)。0号箱子提供值为0的像素的数量,1号箱子提供值为1的像素的数量,以此类推。很明显,如果把直方图的所有箱子进行累加,得到的结果就是像素的总数。你也可以把直方图归一化,即所有箱子的累加和等于1。这时,每个箱子的数值表示对应的像素数量占总数的百分比。
准备工作
本文会用到这幅图像
如何实现
要在OpenCV中计算直方图,可简单地调用cv::calcHist
函数。这是一个通用的直方图计算函数,可处理包含任何值类型和范围的多通道图像。为了简化,这里指定一个专门用于处理单通道灰度图像的类。cv::calcHist
函数非常灵活,在处理其他类型的图像时都可以直接使用它。
这个专用类的初始化代码为:
// 创建灰度图像的直方图
class Histogram1D {
private:
int histSize[1]; // 直方图中箱子的数量
float hranges[2]; // 值范围
const float* ranges[1]; // 值范围的指针
int channels[1]; // 要检查的通道数量
public:
Histogram1D() {
// 准备一维直方图的默认参数
histSize[0]= 256; // 256个箱子
hranges[0]= 0.0; // 从0开始(含)
hranges[1]= 256.0; // 到256(不含)
ranges[0]= hranges;
channels[0]= 0; // 先关注通道0
}
定义好成员变量后,就可以用下面的方法计算灰度直方图了:
// 计算一维直方图
cv::Mat getHistogram(const cv::Mat &image) {
cv::Mat hist;
// 用calcHist函数计算一维直方图
cv::calcHist(&image, 1, // 仅为一幅图像的直方图
channels, // 使用的通道
cv::Mat(), // 不使用掩码
hist, // 作为结果的直方图
1, // 这是一维的直方图
histSize, // 箱子数量
ranges // 像素值的范围
);
return hist;
}
程序只需要打开一幅图像,创建一个Histogram1D实例,然后调用getHistogram方法即可:
// 读取输入的图像
cv::Mat image= cv::imread("group.jpg", 0); // 以黑白方式打开
// 直方图对象
Histogram1D h;
// 计算直方图
cv::Mat histo= h.getHistogram(image);
这里的histo对象是一个一维数组,包含256个项目。因此只需遍历这个数组,就可以读取每个箱子:
// 循环遍历每个箱子
for (int i=0; i<256; i++)
cout << "Value " << i << " = "
<<histo.at<float>(i) << endl;
使用前面的图像,部分显示的值如下所示:
Value 7 = 159
Value 8 = 208
Value 9 = 271
Value 10 = 288
Value 11 = 340
Value 12 = 418
Value 13 = 432
Value 14 = 472
Value 15 = 525
显然,只看这一系列数值很难得到任何有意义的信息。因此比较实用的做法是以函数的方式显示直方图,例如用柱状图。用下面这几种方法可创建这种图形:
// 计算一维直方图,并返回它的图像
cv::Mat getHistogramImage(const cv::Mat &image, int zoom=1) {
// 先计算直方图
cv::Mat hist= getHistogram(image);
// 创建图像
return getImageOfHistogram(hist, zoom);
}
// 创建一个表示直方图的图像(静态方法)
static cv::Mat getImageOfHistogram (const cv::Mat &hist, int zoom) {
// 取得箱子值的最大值和最小值
double maxVal = 0;
double minVal = 0;
cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);
// 取得直方图的大小
int histSize = hist.rows;
// 用于显示直方图的方形图像
cv::Mat histImg(histSize*zoom, histSize*zoom,
CV_8U, cv::Scalar(255));
// 设置最高点为90%(即图像高度)的箱子个数
int hpt = static_cast<int>(0.9*histSize);
// 为每个箱子画垂直线
for (int h = 0; h < histSize; h++) {
float binVal = hist.at<float>(h);
if (binVal>0) {
int intensity = static_cast<int>(binVal*hpt / maxVal);
cv::line(histImg, cv::Point(h*zoom, histSize*zoom),
cv::Point(h*zoom, (histSize - intensity)*zoom),
cv::Scalar(0), zoom);
}
}
return histImg;
}
使用getImageOfHistogram方法可以得到直方图图像。它用线条画成,以柱状图形式展现:
// 以图像形式显示直方图
cv::namedWindow("Histogram");
cv::imshow("Histogram", h.getHistogramImage(image));
得到的结果如下图所示:
从上面图形化的直方图可以看出,在中等灰度值处有一个大的尖峰,并且比中等值更黑的像素有很多。巧的是,这两部分像素分别对应了图像的背景和前景。要验证这点,可以在这两部分的汇合处进行阈值化处理。OpenCV中的cv::threshold
函数可以实现这个功能。我们取直方图中在升高为尖峰之前的最小值的位置(灰度值为70),对其进行阈值化处理,得到二值图像:
cv::Mat thresholded; // 输出二值图像
cv::threshold(image, thresholded,70, // 阈值
255, // 对超过阈值的像素赋值
cv::THRESH_BINARY); // 阈值化类型
得到的二值图像清晰显示出背景/前景的分割情况。
酷客网相关文章:
评论前必须登录!
注册