一个像素是由不同颜色的像素组成的,那么像素在图像中的分布就称为这个图像的重要特征。直方图不仅仅可以用来观察图像,还可以起到改善图像外观、描述直方图内容的作用。
在一个单通道的灰度图像中,每个像素的值都介于0(黑色)——255(白色)之间。根据这点,灰度图像的直方图拥有256个条目,这些条目也称之为容器。分别称为0号容器到255号容器。
现在,我们以灰度形式读取一张图片,并且定义一个直方图处理类来方便的处理直方图操作。
在这个类中,我们先声明变量并编写构造函数:
private:
int histSize[1];
float hranges[2];
const float *ranges[1];
int channels[1];
public:
Histogram1D()
{
histSize[0] = 256;
hranges[0] = 0.0;
hranges[1] = 255.0;
ranges[0] = hranges;
channels[0] = 0;
}
这是一个处理单通道灰度图像类的成员变量,之后我们使用以下方法计算灰度直方图。
其中最主要的函数就是calcHist
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是图像的数量,输入1就是一张图片
channels是指针类型,因此在变量声明中使用数组进行声明,代表着图像的通道数,例如灰度图就是单通道
mask是掩码,这里可以默认为cv::Mat
hist是返回的直方图,推荐采用MatND进行定义
dims是维数,一维直方图就输入1就好
histSize是项的数量,也就是容器的数量。但注意它是指针类型,因此变量声明也采用了数组形式。这里有256个容器,因此构造时为256
ranges是像素范围,注意这是一个二维数组。在这个图像中,像素范围是0 - 255
这里要注意,ranges是float型的,如果使用.at操作获取数值,<>中应为float,后面代码中会看到
这样,我们只需要初始化对象并调用方法,就可以获取直方图了。
cv::Mat image = cv::imread("..\\group.jpg");
Histogram1D h;
cv::MatND histo = h.getHistogram(image);
上面的代码获取到了直方图,但其是以向量方式储存起来的。我们需要将其以直方图的形式呈现使其变得更加直观。
在这个类中,我们首先获取传入图像的直方图数据,并且获得直方图每个容器中最大值和最小值,这里采取minMaxLoc函数。之后,生成一个长宽都为256的白色底板图像histImg。
由于每个直方图都必然有一个最高点,而这个最高点如果触顶了显得不那么美观。所以,我们设置一个直方图最高点(hpt)来避免这种事情。
之后,就是逐点画出直线了,我们采取比例的办法。例如:
某一个“容器”,例如128这个灰度值,它容纳了1000个点。上文已经知道,最大值是maxVal,那么我们的线的相对长度就是:
hist.at
绘制出直方图后,我们可以看出,灰度的中间位置存在一个大的峰值,同时有大量深色像素。这两组像素对应的基本是图像的背景和前景,在区分时,我们可以使用此处的过渡阈值(峰值上升前的容器最小值)(灰度值为60)作为区分阈值。
源代码:
.h文件
#pragma once
#include
class Histogram1D
{
private:
int histSize[1];
float hranges[2];
const float *ranges[1];
int channels[1];
public:
Histogram1D()
{
histSize[0] = 256;
hranges[0] = 0.0;
hranges[1] = 255.0;
ranges[0] = hranges;
channels[0] = 0;
}
cv::MatND getHistogram(const cv::Mat &image);
cv::Mat getHistogramImage(const cv::Mat &image, int normal_type = 0);
};
.cpp文件
#include "stdafx.h"
#include "Histogram1D.h"
cv::MatND Histogram1D::getHistogram(const cv::Mat &image)
{
cv::MatND hist;
cv::calcHist(&image, 1, channels, cv::Mat(), hist, 1, histSize, ranges);
return hist;
}
cv::Mat Histogram1D::getHistogramImage(const cv::Mat &image, int normal_type = 0)
{
cv::MatND hist = getHistogram(image);
if (normal_type != 0)
{
cv::normalize(hist, hist, 1.0);
}
double maxVal;
double minVal;
cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);
cv::Mat histImg(histSize[0], histSize[0], CV_8U, cv::Scalar(255));
int hpt = static_cast(0.9 * histSize[0]);
for (int h = 0; h < histSize[0]; h++)
{
float binVal = hist.at(h);
int intensity = static_cast(binVal * hpt / maxVal);
cv::line(histImg, cv::Point(h, histSize[0]), cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0));
}
return histImg;
}
//主函数
#include "stdafx.h"
#include
#include "Histogram1D.h"
int main()
{
cv::Mat image = cv::imread("..\\group.jpg");
Histogram1D h;
cv::MatND histo = h.getHistogram(image);
return 0;
}