用直方图统计像素

https://blog.csdn.net/qq_30815237/article/details/86768547

用直方图统计像素

图像是由不同数值(颜色)的像素构成的,
像素值在整幅图像中的分布情况是该图像的一个重要属性。
本章将介绍图像直方图的概念,你将学会如何计算直方图、如何用直方图修改图像的外观,
还可以用直方图来标识图像的内容,检测图像中特定的物体或纹理。
本章将讲解其中的部分技术。

计算图像直方图

图像由各种数值的像素构成。例如在单通道灰度图像中,每个像素都有一个 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); // 对超过阈值的像素

图像直方图

图像直方图提供了利用现有像素强度值进行场景渲染的方法。
通过分析图像中像素值的分布情况,你可以利用这个信息来修改图像,甚至提高图像质量。
解释如何用一个简单的映射函数(称为查找表)来修改图像的像素值。

物体的图片主要靠形状和纹理区分彼此,
这些特征可以用方向梯度直方图(Histogram of14.4 行人检测Oriented Gradients,HOG)模型表示。
正如其名,这种模型的基础就是图像梯度的直方图;
具体来说是梯度方向的分布图,因为我们更加关注形状和纹理。
此外,为了观察这些梯度的空间分布,需要把图像划分成网格,并以此计算多个直方图。
构建 HOG 模型的第一步就是计算图像的梯度。
把图像分割成小的单元格(例如 8 像素×8 像素),并针对每个单元格计算方向梯度直方图。
方向的值会被分割成多个箱子。通常只考虑梯度的方向,不考虑正负(称作无符号梯度)。
这里的方向值范围是 0 度~180 度。采用 9 个箱子的直方图,方向值的分割间距为 20 度。
每个单元格的梯度向量产生一个箱子,该箱子的权重对应梯度的幅值。

图像 HOG 模型的向量的维度非常高。
这个向量就代表了图像的特征,可用于各种物体图像的分类。
为此,我们需要一种能处理这种高维向量的机器学习方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章