一个图像是由不同颜色的像素组成。像素值在图像中的分布情况是这幅图像的一个重要特征。我们将学习如何计算并使用直方图来修改图像的外观。直方图也可用于描述图像的内容,并且检测图像中特定的对象或纹理。
1【计算图像的直方图】
图像是由像素组成的,在一个单通道的灰度图像中,每个像素的值介于0(黑色)~255(白色)之间。根据图像的内容,我们会发现每个灰度值的像素数目是不同的。直方图是一个简单的表,它给出了一幅图像或一组图像中拥有给定数值的像素数量。因此,灰度图像的直方图有256个条目(容器)。0号容器给出值为0的像素个数,1号容器给出值为1的像素个数,以此类推。显然,如果我们对直方图的所有项求和,会得到像素的总数。直方图也可以被归一化,归一化后的所有项之和等于1。在该情况下,每一项给出的都是拥有特定数值的像素在图像中占的比例。
在opencv中计算直方图,通过cv::calcHist函数。这是一个通用函数,可以计算任意像素类型的多通道图像。下面代码是实现一个单通道直方图的简单代码实现。
cv::MatND CowisHistogram1D::getHistogram(const cv::Mat &image)
{
cv::MatND hist;
cv::calcHist(&image,//输入的图像
1, //计算几张图像
channels,//通道数量
cv::Mat(),//掩码
hist, //返回直方图的矩阵
1, //通道数量
histSize,//项的数量
rang);//像素值的范围
return hist;
}
获取到直方图的数据是一个矩阵,方便我们观察,我们把数据画出来,主要代码如,运行的效果如下图所示
cv::Mat CowisHistogram1D::gethistogramImage(const cv::Mat &image)
{
cv::MatND hist = getHistogram(image);
double maxVal = 0;
double minVal = 0;
cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);
cv::Mat histImg(histSize[0], histSize[0], CV_8U, cv::Scalar(255));
int hpt = static_cast<int>(0.9 * histSize[0]);
for(int h = 0; h < histSize[0]; h++)
{
float binVal = hist.at<float>(h);
int intensity = static_cast<int>(binVal * hpt / maxVal);
cv::line(histImg, cv::Point(h, histSize[0]),cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0));
}
return histImg;
}
从直方图可以直观的看到,灰度的中间位置存在一个大的峰值,同时有大量深色的像素。这两组像素基本对应的是图像的背景和前景。通过这两组像素之间的过渡处进行阈值可以证实这一点。有一个方便的函数cv::threshold函数。当需要使用一个阈值来创建二值图像时可以使用该函数。生成的图像清晰的显示了分段的背景与前景。
cv::calcHist有许多参数,大多数时候,我们的直方图将是一个单通道或者三通道图像。然而,该函数允许我们指定一个分布在几个图像中的多通道图像。这就是为何一组图像被作为该函数的输入。第6个参数指定直方图的维度,例如1指的是一维直方图。直方图计算中要考虑的通道列在一个数组中,它有指定的维度。在实现单通道时,默认使用的是通道0(第3个参数)。直方图本身是通过每个维度的条目数量,每个维度的最小及最大值进行描述的,前者位于第7个参数,是一组整数,后者位于第8个参数,是一组每项包含两个元素的数组。可以指定一个掩码,指明哪些像素需要进行统计。还可以指定两个额外的可选参数,都是布尔值。第一个表面直方图是否归一化的(默认是true),第2个值允许积累多个直方图计算结果。如果这个参数为true,那么图像的像素统计值会加到输入直方图中当前的值上。需要主要的是,直方图中的值是浮点数。
下面是计算三通道直方图的代码。
std::vector<cv::Mat> bgr_planes;
cv::split(image, bgr_planes);
cv::Mat hist1;
cv::calcHist(&bgr_planes[0], 1, channels, cv::Mat(), hist1, 1, histSize, ranges);
cv::Mat hist2;
cv::calcHist(&bgr_planes[1], 1, channels, cv::Mat(), hist2, 1, histSize, ranges);
cv::Mat hist3;
cv::calcHist(&bgr_planes[2], 1, channels, cv::Mat(), hist3, 1, histSize, ranges);
cv::Mat imageChann1(histSize[0], histSize[0], CV_8U, cv::Scalar(255));
cv::Mat imageChann2(histSize[1], histSize[1], CV_8U, cv::Scalar(255));
cv::Mat imageChann3(histSize[2], histSize[2], CV_8U, cv::Scalar(255));
double minVal = 0.;
double maxVal = 0.;
cv::minMaxLoc(hist1, &minVal, &maxVal, 0 ,0);
int hpt = static_cast<int>(0.9 * histSize[0]);
for(int i = 0; i < 3; i++)
{
for(int h = 0; h < histSize[0]; h++)
{
if(i == 0)
{
float binVal = hist1.at<float>(h);
int intensity = static_cast<int>(binVal * hpt / maxVal);
cv::line(imageChann1, cv::Point(h, histSize[0]),cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0));
}
else if(i == 1)
{
float binVal = hist2.at<float>(h);
int intensity = static_cast<int>(binVal * hpt / maxVal);
cv::line(imageChann2, cv::Point(h, histSize[0]), cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0));
}
else {
float binVal = hist3.at<float>(h);
int intensity = static_cast<int>(binVal * hpt / maxVal);
cv::line(imageChann3, cv::Point(h, histSize[0]), cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0));
}
}
}
cv::namedWindow("Channl1");
cv::namedWindow("Channl2");
cv::namedWindow("Channl3");
cv::imshow("Channl1", imageChann1);
cv::imshow("Channl2", imageChann2);
cv::imshow("Channl3", imageChann3);
运行结果如下