【OpenCV学习笔记 007】使用直方图统计像素

一、计算图像的直方图

1.概念及原理

(1)直方图是一个简单的表,它给出了一幅图像或一组图像中拥有给定数值的像素数量。例如灰度图像的直方图有265个条目(或称为容器)。0号容器给出值为0的像素个数,1号容器给出值为1的像素个数,依次类推。显然对直方图的所有项求和会得到像素的总数。直方图也可以被归一化,归一化后的所有项之和等于1,在这种情况下,每一项给出的都是拥有特定数值的像素在图像中占的比例。

(2)使用一个阈值来创建二值图像时可以使用cv::threshold。

2.实验

计算一张单通道的灰度图像的直方图。

源码:

#include    
#include     
#include  
#include 

using namespace std;

class Histogram1D{

private:
	int histSize[1];	//项的数量
	float hranges[2];	//像素的最小及最大值
	const float *ranges[1];
	int channels[1];	//仅用到一个通道
public:
	Histogram1D(){
		//准备1D直方图的参数
		histSize[0] = 256;
		hranges[0] = 0.0;
		hranges[1] = 255.0;
		ranges[0] = hranges;
		channels[0] = 0;	//默认情况下,我们考察0号通道
	}

	cv::Mat getHistogram(const cv::Mat &image);
	cv::Mat getHistogramImage(const cv::Mat &image);
};

//计算1D直方图
cv::Mat Histogram1D::getHistogram(const cv::Mat &image){
	
	cv::MatND hist;
	//计算直方图  计算任意像素类型的多通道图像
	cv::calcHist(&image, 
		1,	//计算单张图像的直方图 
		channels,	//通道数量
		cv::Mat(),	//不使用图像作为掩码
		hist,	//返回的直方图
		1,	//这是1D的直方图
		histSize,	//项的数量
		ranges	//像素值的范围
		);
	return hist;
}

//计算1D直方图,并返回一幅图像
cv::Mat Histogram1D::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));
	//设置最高点为nbins的90%
	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;
}

int main(){

	//1.装载图像
	cv::Mat srcImage = cv::imread("grayHorse.jpg");
	if (!srcImage.data) return 0;
	//2.histogram对象
	Histogram1D h;
	/*
	//3.计算直方图
	cv::MatND histo = h.getHistogram(srcImage);	//这里的histo对象是一个拥有256个条目的一维数组
	//4.遍历每个条目
	for (int  i = 0; i < 256; i++)
	{
		cout << "Value" << i << "=" << histo.at(i) << endl;
	}
	*/
	//5.以图形方式显示直方图
	//cv::namedWindow("Histogram");
	//cv::imshow("Histogram", h.getHistogramImage(srcImage));
	
	//6.使用一个阈值来创建二值图像分离背景和前景
	cv::Mat thresholded;
	cv::threshold(srcImage, thresholded, 60, 255, cv::THRESH_BINARY);
	cv::namedWindow("thresholded");
	cv::imshow("thresholded", thresholded);
	cv::waitKey();
	system("pause");
	return 0;
}
实验结果一:

步骤3和4运行后的结果:

【OpenCV学习笔记 007】使用直方图统计像素_第1张图片

这里的histo对象是一个拥有256个条目的一维数组,遍历数组即可得到每个条目的值。然而从这些值中并不能提取任何直观含义。

实验结果二:

步骤5使用柱状图将直方图可视化运行后的结果为:

【OpenCV学习笔记 007】使用直方图统计像素_第2张图片

实验结果三:

原图及步骤6使用一个阈值来创建二值图像分离背景和前景结果为:

【OpenCV学习笔记 007】使用直方图统计像素_第3张图片【OpenCV学习笔记 007】使用直方图统计像素_第4张图片

二、使用查找表修改图像外观

1.概念及原理

(1)查找表是一个简单的一对一(或多对一)函数,定义了如何将像素值转换为新的值。表的第i项表示相应对应灰度的新值。

newIntensity = lookup[oldIntensity];
(2)当一个查找表应用于一个图像时,结果是一个新的图像,它的像素强度值已经按照规定的查找表进行修改。

2.实验

我们的实验就是反转像素的强度,即0变为255,1变为254,应用这样一个查找表就会在一个图像上产生原图的负片。

我们添加这个功能到Histogram1D类

cv::Mat Histogram1D::applyLookUp(const cv::Mat&image/*输入图像*/, const cv::Mat&lookup/*1*256 uchar matrix*/){

	//输出图像
	cv::Mat result;
	//应用查找表
	cv::LUT(image, lookup, result);
	return result;
}
另外在main中调用该函数

//创建图像的反向查找表
	int dim(256);
	cv::Mat lut(1, &dim, CV_8U);
	for (int i = 0; i < 256; i++)
	{
		lut.at(i) = 255 - i;
	}
	
	cv::namedWindow("lut");
	cv::imshow("lut", h.applyLookUp(srcImage, lut));
其结果如图

【OpenCV学习笔记 007】使用直方图统计像素_第5张图片

三、直方图均衡化

1.概念及原理

(1)在前面我们通过拉伸直方图,使它覆盖所有的取值范围来提高图像对比度。其实,在很多情况下图像在视觉的陷阱并非源于使用过窄的强度范围,而是由于某些颜色值得出现频率高于另一些。事实上,我们可以认为一幅高质量的图像应该平均使用所有的像素强度。这也是直方图均衡化背后的理念。

(2)在一个完全均衡的直方图中,所有的容器有同等数量的像素。因此,所需的查找表由以下等式构建。

    lookup.at(i) = static_cast(255.0*p[i]);

2.实验

利用opencv实现直方图均衡化。

我们添加这个功能到Histogram1D类

//直方图均衡化 增加图像的对比度  
cv::Mat Histogram1D::equalize(const cv::Mat &image){

	cv::Mat result;
	cv::equalizeHist(image, result);
	return result;
}
在main中调用该函数:

int main(){

	cv::Mat srcImage = cv::imread("grayHorse.jpg",0);
	if (!srcImage.data) return 0;
	Histogram1D h;
	//直方图均衡化
	cv::namedWindow("srcImage");
	cv::imshow("srcImage", srcImage);
	cv::namedWindow("resultImage");
	cv::imshow("resultImage", h.equalize(srcImage));
	cv::namedWindow("showImage");
	cv::imshow("showImage", h.getHistogramImage(srcImage));
	cv::namedWindow("showImage2");
	cv::imshow("showImage2", h.getHistogramImage(h.equalize(srcImage)));
	cv::waitKey(0);
	return 0;
}
运行结果如下

【OpenCV学习笔记 007】使用直方图统计像素_第6张图片

【OpenCV学习笔记 007】使用直方图统计像素_第7张图片

四、反投影直方图以检测特定的图像内容

1.概念及原理

(1)如果一幅图像的区域中显示的是一种独特的纹理或是一个独特的物体,那么这个区域的直方图可以看作是一个概率函数,它给出的是某个像素属于该纹理或物体的概率。

(2)反投影直方图可以替换一个输入图像中每个像素值,使其变成归一化直方图中对应的概率值。

2.实验

int main(){

	//1.装载图像
	cv::Mat srcImage = cv::imread("cloud.jpg",0);
	if (!srcImage.data) return 0;
	//2.获取感兴趣区域
	cv::Mat imageROI;
	imageROI = srcImage(cv::Rect(10, 30, 400, 200));
	//3.提取该ROI的直方图
	Histogram1D h;
	cv::Mat histImage = h.getHistogram(imageROI);
	//4.归一化该直方图
	cv::normalize(histImage,histImage,1.0);

	int channels[] = { 0 };
	cv::Mat result;
	float granges[] = { 0, 255 };
	const float *ranges[] = { granges };
	//5.反投影直方图
	cv::calcBackProject(&srcImage,
		1,	//一幅图像
		channels,	//通道数量
		histImage,	//进行反投影的直方图
		result,	//生成反投影图像
		ranges,	//每个维度的值域
		255.0	//缩放因子
		);

	cv::namedWindow("srcImage");
	cv::imshow("srcImage", srcImage);
	cv::namedWindow("result", 0);
	cv::imshow("result", result);
	cv::waitKey(0);
	return 0;
}
实验结果

【OpenCV学习笔记 007】使用直方图统计像素_第8张图片

五、使用(Mean Shift)算法跟踪物体

1.概念及原理

(1)反投影直方图的结果是一个概率映射,体现了已知图像内容出现在图像中特定位置的概率。假设我们知道近似位置,概率映射可用于找到对象的确切位置,及最有可能的位置就是得到最大概率的位置。这样我们可以从初始位置开始迭代移动,找到精确位置。这便是均值漂移算法的执行过程。

(2)Meanshift算法就是以迭代的方式锁定概率函数的局部最大值。主要分为这三个步骤:首先需要得到目标的直方图,其次用该直方图对图片进行反投影,后对反投影的图像进行meanshift算法。其中迭代的次数和精度作为收敛的条件。可以参考http://blog.csdn.net/lu597203933/article/details/17042331

#include    
#include     
#include  
#include 

using namespace std;

class ColorHistogram{

private:
	int histSize[3];
	float hranges[2];
	const float* ranges[3];
	int channels[3];
public:
	ColorHistogram(){
	
		//准备彩色直方图的参数
		histSize[0] = histSize[1] = histSize[2] = 256;
		hranges[0] = 0.0;	//BGR的范围
		hranges[1] = 255.0;
		ranges[0] = hranges;	//所有通道拥有相同的范围
		ranges[1] = hranges;
		ranges[2] = hranges;
		channels[0] = 0;
		channels[1] = 1;
		channels[2] = 2;
	}
	cv::SparseMat getHistogram(const cv::Mat &image);
	cv::MatND getHueHistogram(const cv::Mat &image, int);
};

cv::SparseMat ColorHistogram::getHistogram(const cv::Mat &image){

	cv::SparseMat hist(3,histSize,CV_32F);
	//计算直方图
	cv::calcHist(&image,
		1,	//仅计算一张图
		channels,	//通道数量
		cv::Mat(),	//不使用掩码图像
		hist,	//返回的直方图
		3,	//这是三维直方图
		histSize,	//项的数量
		ranges	//像素值的范围
		);
		return hist;
}

//1.使用掩码计算1D色调直方图  2.BGR图像转换为HSV色彩空间并去除低饱和度的像素
cv::MatND ColorHistogram::getHueHistogram(const cv::Mat &image, int minSaturation = 0){

	cv::MatND hist;
	//转换为HSV色彩空间
	cv::Mat hsv;
	cv::cvtColor(image, hsv, CV_BGR2HSV);
	//是否使用掩码
	cv::Mat mask;
	if (minSaturation > 0)
	{
		//分割三通道为三幅图像
		std::vectorv;
		cv::split(hsv, v);
		//标出低饱和度的像素
		cv::threshold(v[1], mask, minSaturation, 255, cv::THRESH_BINARY);
	}
	//1D色调直方图的参数
	hranges[0] = 0.0;
	hranges[1] = 180.0;
	channels[0] = 0;	//色调通道
	//计算直方图
	cv::calcHist(
		&hsv,
		1,	//仅计算一幅图
		channels,	//通道数量
		mask,	//二值掩码
		hist,	//返回的直方图
		1,	//这是1D的直方图
		histSize,	//项的数量
		ranges	//像素值的范围
		);
	return hist;
}

class ContentFinder{

private:
	float hranges[2];
	const float *ranges[3];
	int channels[3];
	float threshold;
	cv::MatND histogram;
public:
	ContentFinder() :threshold(-1.0f){
		ranges[0] = hranges;	//所有通道的值相同
		ranges[1] = hranges;
		ranges[2] = hranges;
	}
	void setThreshold(float t);
	float getThreshold();
	void setHistogram(const cv::MatND&h);
	cv::Mat find(const cv::Mat &image, float minValue, float maxValue, int *channels, int dim);
};

//设置直方图的阈值[0,1]
void ContentFinder::setThreshold(float t){

	threshold = t;
}

//获取阈值
float ContentFinder::getThreshold(){

	return threshold;
}

//设置参考直方图
void ContentFinder::setHistogram(const cv::MatND&h){

	histogram = h;
	cv::normalize(histogram, histogram, 1.0);
}

//为了反投影直方图,我们需要指定图像、范围及所用范围通道的列表
cv::Mat ContentFinder::find(const cv::Mat &image, float minValue, float maxValue, int *channels, int dim){

	cv::Mat result;
	hranges[0] = minValue;
	hranges[1] = maxValue;
	for (int i = 0; i < dim; i++)
		this->channels[i] = channels[i];

	cv::calcBackProject(&image,
		1,	//输入图像
		channels,	//所用通道的列表
		histogram,	//直方图
		result,	//反投影的结果
		ranges,	//值域
		255.0	//缩放因子
		);

	//进行阈值化以得到二值图像
	if (threshold > 0.0)
		cv::threshold(result, result, 255 * threshold, 255, cv::THRESH_BINARY);
	
	return result;
}

int main(){
	
	//读取第一张参考图像
	cv::Mat srcImage = cv::imread("Baboon.jpg");
	if (!srcImage.data) return 0;
	//狒狒脸部的ROI
	cv::Mat imageROI = srcImage(cv::Rect(200, 60, 80, 80));
	//获取色调通道的直方图
	int minSat = 65;
	ColorHistogram hc;
	cv::MatND colorhist = hc.getHueHistogram(imageROI, minSat);

	//读取需要跟踪的图片
	cv::Mat srcImage2 = cv::imread("Baboon2.jpg");
	cv::Mat hsvColor;
	//转到HSV空间
	cv::cvtColor(srcImage2, hsvColor, CV_BGR2HSV);
	//分割三通道为三幅图像
	std::vectorhsv;
	cv::split(srcImage2, hsv);

	//产生的直方图作为  ContentFinder实例
	ContentFinder finder;
	finder.setHistogram(colorhist);

	//得到目标图像的反投影直方图
	cv::Mat result;
	int ch[255] = { 0 };
	result = finder.find(hsvColor, 0.0f, 180.0f, ch, 1);
	
	//识别低饱和度的像素
	cv::threshold(hsv[1], hsv[1], minSat, 255, cv::THRESH_BINARY);
	//去除低饱和度的像素
	cv::bitwise_and(result, hsv[1], result);
	
	cv::Rect rect(200, 60, 80, 80);
	cv::rectangle(srcImage, rect, cv::Scalar(0, 0, 255));
	cv::rectangle(srcImage2, rect, cv::Scalar(0, 0, 255));
	//定义迭代的次数以及迭代的精度
	cv::TermCriteria criteria(cv::TermCriteria::MAX_ITER, 10, 0.01);
	//meanShift算法 跟踪目标 并且得到新目标的位置rect
	cv::meanShift(result, rect, criteria);

	cv::rectangle(srcImage2, rect, cv::Scalar(0, 0, 255));
	//显示图像
	cv::namedWindow("result");
	cv::imshow("result", result);
	cv::namedWindow("srcImage2");
	cv::imshow("srcImage2", srcImage2);
	cv::waitKey(0);
	return 0;
}

运行结果:

【OpenCV学习笔记 007】使用直方图统计像素_第9张图片

六、通过比较直方图检索相似图片

1.概念及原理

(1)基于内容的图像检索是计算机视觉中的一个重要问题。我们可以通过简单地比较它们的直方图来测量两个图像的相似性。Opencv中cv::compareHist函数实现了其中一些。

2.实验

将一组图像与参考图像作比较,找到那些最像查询的图像。

在此我们添加一个ImageComparator类用来实现

源码

class ImageComparator{

private:
	cv::SparseMat refH;
	cv::SparseMat inputH;
	ColorHistogram hist;
	int div;
public:
	ImageComparator():div(32){
	}
	void setColorReduction(int factor);
	int getColorReduction();
	void setReferenceImage(const cv::Mat &image);
	double compare(const cv::Mat &image);
};

//为了获得可靠的相似度 必须降低颜色的数量,这里我们需要一个减色因子
//比较的将是减色后的图像 色彩空间中的每个维度都将按照该变量进行减色
void ImageComparator::setColorReduction(int factor){
	div = factor;
}

int ImageComparator::getColorReduction(){

	return div;
}

//减少图像的颜色 同时指定查询图像
void ImageComparator::setReferenceImage(const cv::Mat &image){

	refH = hist.getHistogram(image);
}

double ImageComparator::compare(const cv::Mat &image){
	
	inputH = hist.getHistogram(image);
	return cv::compareHist(refH, inputH, CV_COMP_INTERSECT);
}

int main(){

	//读取第一张参考图像
	cv::Mat srcImage = cv::imread("cloud.jpg");
	if (!srcImage.data) return 0;
	//读取第一张比较图像
	cv::Mat srcImage2 = cv::imread("picture.jpg");
	//读取第二张比较图像
	cv::Mat srcImage3 = cv::imread("Baboon.jpg");
	//读取第三张比较图像
	cv::Mat srcImage4 = cv::imread("lol.jpg");
	//读取第四张比较图像
	cv::Mat srcImage5 = cv::imread("horse.jpg");
	ImageComparator ic;
	ic.setReferenceImage(srcImage);
	double a = ic.compare(srcImage2);
	double b = ic.compare(srcImage3);
	double c = ic.compare(srcImage4);
	double d = ic.compare(srcImage5);
	cout << "a=" << a << endl << "b=" << b << endl << "c=" << c << endl << "d=" << d << endl;
	cv::waitKey(0);
	system("pause");
	return 0;
}
结果:原图(结果)+图像的显示顺序按相似度由高到低。

【OpenCV学习笔记 007】使用直方图统计像素_第10张图片【OpenCV学习笔记 007】使用直方图统计像素_第11张图片【OpenCV学习笔记 007】使用直方图统计像素_第12张图片【OpenCV学习笔记 007】使用直方图统计像素_第13张图片【OpenCV学习笔记 007】使用直方图统计像素_第14张图片【OpenCV学习笔记 007】使用直方图统计像素_第15张图片


【OpenCV学习笔记 007】使用直方图统计像素 配套的源码下载


你可能感兴趣的:(OpenCV,opencv编程笔记)