图像处理之图像直方图

194 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天

1 图像直方图概述

直方图广泛运用于很多计算机视觉运用当中,通过标记帧与帧之间显著的边缘和颜色的统计变化,来检测视频中场景的变化。在每个兴趣点设置一个有相近特征的直方图所构成“标签”,用以确定图像中的兴趣点。边缘、色彩、角度等直方图构成了可以被传递给目标识别分类器的一个通用特征类型。色彩和边缘的直方图序列还可以用来识别网络视频是否被复制。如下图所示。

1.png

其实,简单点说,直方图就是对数据进行统计的一种方法,并且将统计值组织到一系列事先定义好的bin当中。其中,bin为直方图中经常用到的一个概念,可翻译为“直条”或“组距”,其数值是从数据中计算出的特征统计量,这些数据可以是诸如梯度、方向、色彩或任何其他特征。且无论如何,直方图获得的是数据分布的统计图。通常直方图的维数要低于原始数据。总而言之,直方图是计算机视觉中最经典的工具之一。

在统计学中,直方图(Histogram)是一种对数据分布情况的图形表示,是一种二维统计图表,它的两个坐标分别是统计样本和该样本对应的某个属性的度量。

我们在图像变换的那一章中讲过直方图的均衡化,它是通过拉伸像素强度分布范围来增强图像对比度的一种方法。大家在自己的心目中应该已经对直方图有一定的理解和认知。下面就来看一看对图像直方图比较书面化的解释。

图像直方图(Image Histogram)是用以表示数字图像中亮度分布的直方图,标绘了图像中每个亮度值的像素数。可以借助观察该直方图了解需要如何调整亮度分布。这种直方图中,横坐标的左侧为纯黑、较暗的区域,而右侧为较亮、纯白的区域。因此,一张较暗图片的图像直方图中的数据多集中于左侧和中间部分,而整体明亮、只有少量阴影的图像则相反。计算机视觉领域常借助图像直方图来实现图像的二值化。

直方图的意义如下。

  • 直方图是图像中像素强度分布的图形表达方式。
  • 它统计了每一个强度值所具有的像素个数。

上面已经讲到,直方图是对数据的统计集合,并将统计结果分布于一系列预定义的bins中。这里的数据不仅仅指的是灰度值,且统计数据可能是任何能有效描述图像的特征。下面看一个例子,假设有一个矩阵包含一张图像的信息(灰度值0—255),让我们按照某种方式来统计这些数字。既然已知数字的范围包含256个值,于是可以将这个范围分割成子区域(也就是上面讲到bins),如:

2.png

然后再统计每一个bin,的像素数目。采用这一方法来统计上面的数字矩阵,可以得到图9.2(其中x轴表示bin,y轴表示各个bin中的像素个数)。

3.jpg

以上就是一个说明直方图的用途的简单示例。其实,直方图并不局限于统计颜色灰度,而是可以统计任何图像特征,如梯度、方向等。

让我们具体讲讲直方图的一些术语和细节。

  • dims:需要统计的特征的数目。在上例中,dims=1因为我们仅仅统计了灰度值(灰度图像)。
  • bins:每个特征空间子区段的数目,可翻译为“直条”或“组距”。在上例中,bins=16。
  • range:每个特征空间的取值范围。在上例中,range=[0,255]。

2 直方图的计算与绘制

直方图的计算在OpenCV中可以使用calcHist()函数,而计算完成之后,可以采用OpenCV中的绘图函数,如绘制矩形的rectangle()函数,绘制线段的line()来完成。

2.1 计算直方图:calcHist()函数

在OpenCV中,calcHistO函数用于计算一个或者多个阵列的直方图。原型如下。

C++:

void 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)
  • 第一个参数,const Mat* 类型的images,输入的数组(或数组集),它们需为 相同的深度(CV_8U或CV_32F)和相同的尺寸。
  • 第二个参数,int类型的 nimages,输入数组的个数,也就是第一个参数中存放了多少张“图像”,有几个原数组。
  • 第三个参数,const int* 类型的channels,需要统计的通道(dim)索引。第一个数组通道从0到images[0].channels()-1,而第二个数组通道从images[0].channels()计算到images[0].channels()+images[1].channels()-1.
  • 第四个参数,InputArray 类型的mask,可选的操作掩码。如果此掩码不为空,那么它必须为8位,并且与images[i]有同样大小的尺寸。这里的非零掩码元素用于标记出统计直方图的数组元素数据。
  • 第五个参数,OutputArray类型的hist,输出的目标直方图,一个二维数组。
  • 第六个参数,int类型的dims,需要计算的直方图的维度,必须是正数,且不大于CV_MAX_DIMS(在当前版本的OpenCV中等于32)。
  • 第七个参数,const int*类型的 histSize,存放每个维度的直方图尺寸的数组。 ·第八个参数,const float* 类型的ranges,表示每一个维度数组(第六个参数dims)的每一维的边界阵列,可以理解为每一维数值的取值范围。
  • 第九个参数,bool类型的uniform,指示直方图是否均匀的标识符,有默认值true。
  • 第十个参数,bool类型的accumulate,累计标识符,有默认值false。若其为true,直方图在配置阶段不会被清零。此功能主要是允许从多个阵列中计算单个直方图,或者用于在特定的时间更新直方图。

2.2 找寻最值:minMaxLoc()函数

minMaxLoc()函数的作用是在数组中找到全局最小值和最大值。它有两个版本的原型,在此介绍常用的那一个版本。

C++:

void minMaxLoc(InputArray src, double* minVal,double* maxVal=0, Point* minLoc=0,Point* maxLoc=0, InputArray mask=noArray())
  • 第一个参数,InputArray类型的src,输入的单通道阵列。
  • 第二个参数,double* 类型的minVal,返回最小值的指针。若无须返回,此值置为NULL。
  • 第三个参数,double* 类型的maxVal,返回的最大值的指针。若无须返回,此值置为NULL。
  • 第四个参数,Point* 类型的minLoc,返回最小位置的指针(二维情况下)。若无须返回,此值置为NULL。
  • 第五个参数,Point* 类型的maxLoc,返回最大位置的指针(二维情况下)。若无须返回,此值置为NULL。
  • 第六个参数,InputArray类型的mask,用于选择子阵列的可选掩膜。

3 示例

下面示例程序,绘制H-S直方图。

下面的示例说明如何计算彩色图像的色调,饱和度二维直方图。

注意:色调(Hue),饱和度(Saturation)。所以“H-S直方图”就是“色调-饱和度直方图”

代码:

//---------------------------------【头文件、命名空间包含部分】----------------------------
//		描述:包含程序所使用的头文件和命名空间
//--------------------------------------------------------------------------------------
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;

//--------------------------------------【main( )函数】-----------------------------------------
//          描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//--------------------------------------------------------------------------------------------
int main( )
{

	//【1】载入源图,转化为HSV颜色模型
	Mat srcImage, hsvImage;
	srcImage=imread("1.jpg");
	cvtColor(srcImage,hsvImage, CV_BGR2HSV);

	system("color 2F");

	//【2】参数准备
	//将色调量化为30个等级,将饱和度量化为32个等级
	int hueBinNum = 30;//色调的直方图直条数量
	int saturationBinNum = 32;//饱和度的直方图直条数量
	int histSize[ ] = {hueBinNum, saturationBinNum};
	// 定义色调的变化范围为0到179
	float hueRanges[] = { 0, 180 };
	//定义饱和度的变化范围为0(黑、白、灰)到255(纯光谱颜色)
	float saturationRanges[] = { 0, 256 };
	const float* ranges[] = { hueRanges, saturationRanges };
	MatND dstHist;
	//参数准备,calcHist函数中将计算第0通道和第1通道的直方图
	int channels[] = {0, 1};

	//【3】正式调用calcHist,进行直方图计算
	calcHist( &hsvImage,//输入的数组
		1, //数组个数为1
		channels,//通道索引
		Mat(), //不使用掩膜
		dstHist, //输出的目标直方图
		2, //需要计算的直方图的维度为2
		histSize, //存放每个维度的直方图尺寸的数组
		ranges,//每一维数值的取值范围数组
		true, // 指示直方图是否均匀的标识符,true表示均匀的直方图
		false );//累计标识符,false表示直方图在配置阶段会被清零

	//【4】为绘制直方图准备参数
	double maxValue=0;//最大值
	minMaxLoc(dstHist, 0, &maxValue, 0, 0);//查找数组和子数组的全局最小值和最大值存入maxValue中
	int scale = 10;
	Mat histImg = Mat::zeros(saturationBinNum*scale, hueBinNum*10, CV_8UC3);

	//【5】双层循环,进行直方图绘制
	for( int hue = 0; hue < hueBinNum; hue++ )
		for( int saturation = 0; saturation < saturationBinNum; saturation++ )
		{
			float binValue = dstHist.at<float>(hue, saturation);//直方图组距的值
			int intensity = cvRound(binValue*255/maxValue);//强度

			//正式进行绘制
			rectangle( histImg, Point(hue*scale, saturation*scale),
				Point( (hue+1)*scale - 1, (saturation+1)*scale - 1),
				Scalar::all(intensity),
				CV_FILLED );
		}

		//【6】显示效果图
		imshow( "素材图", srcImage );
		imshow( "H-S 直方图", histImg );

		waitKey();
}

原图

3.PNG

效果图

4.PNG

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天