索引目录

  • 1.功能
  • 1.1直方图均衡
  • 1.2直方图匹配
  • 2.函数
  • 2.1 计算直方图
  • 2.2 直方图均衡化
  • 2.3 直方图匹配(规定化)
  • 2.4 直方图二值化
  • 2.5 直方图最值
  • 3.直方图的计算与绘制
  • 4.直方图对比
  • 5.直方图的反向投影
  • 6.局部直方图处理
  • 7.Demo
  • 参考


1.功能

1.1直方图均衡

低对比度图像具有较窄的直方图,且集中于灰度级的中部;高对比度图像的直方图分量则覆盖了很宽的灰度级范围。假如图像的灰度分布不均匀,其灰度分布集中在较窄的范围内,使图像的细节不够清晰,对比度较低。通常采用直方图均衡化及直方图规定化两种变换,使图像的灰度范围拉开或使灰度均匀分布,从而增大反差,使图像细节清晰,以达到增强的目的。直方图均衡化是通过非线性拉伸像素强度的分布范围,使得分布更加均衡,增加了像素灰度值的动态范围从而可达到增强图像整体对比度,补偿图像在视觉上难以区分灰度级的差别,直方图均衡可以作为自适应对比度增强工具。

1.2直方图匹配

有时候,需要图像具有某一特定的直方图形状(也就是灰度分布),而不是均匀分布的直方图,这时候可以使用直方图规定化。直方图规定化,也叫做直方图匹配,用于将图像变换为某一特定的灰度分布,也就是其目的的灰度直方图是已知的。这其实和均衡化很类似,均衡化后的灰度直方图也是已知的,是一个均匀分布的直方图;而规定化后的直方图可以随意的指定,也就是在执行规定化操作时,首先要知道变换后的灰度直方图,这样才能确定变换函数。规定化操作能够有目的的增强某个灰度区间,相比于,均衡化操作,规定化多了一个输入,但是其变换后的结果也更灵活。

2.函数

2.1 计算直方图

直方图的计算是很简单的,无非是遍历图像的像素,统计每个灰度级的个数

C++ Void calcHist(
	        const Mat* images,//输入图像指针
	        int images,// 图像数目
	        const int* channels,// 通道数
	        InputArray mask,// 输入mask,可选,不用
	        OutputArray hist,//输出的直方图数据
	        int dims,// 维数,dims=1,表示我们仅统计灰度值
	        const int* histsize,// 直方图级数
	        const float* ranges,// 值域范围
	        bool uniform,// true by default
	        bool accumulate)// false by defaut

2.2 直方图均衡化

void equalizeHist(InputArray src, //输入单通道阵列
                  OutputArray dst)

2.3 直方图匹配(规定化)

normalize(InputArry src,// 输入数组;
          InputOutputArray dst,//输出数组,数组的大小和原数组一致;
          double alpha=1,//用来规范围最小值
          double beta=0,//规范范围最大值
          int norm_type,//归一化选择的数学公式类型;NORM_MINMAX
          int dtype=-1,//当为负,输出在大小深度通道数都等于输入,
                       //当为正,输出只在深度与输入不同,不同的地方由dtype决定;
          InputArray mark)//掩码。选择感兴趣区域,选定后只能对该区域进行操作。
void drawHist(Mat &hist, int type, string name)  //归一化并绘制直方图函数
{
	int hist_w = 512;
	int hist_h = 400;
	int width = 2;
	Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);

	/*double minVal, maxVal;
	Point minLoc, maxLoc;
	minMaxLoc(hist, &minVal, &maxVal, &minLoc, &maxLoc);
	double tt = 0;
	for (int r = 0; r < hist.rows; r++)
	{
		for (int c = 0; c < hist.cols; c++)
		{
			tt += hist.at<float>(r,c);
		}
	}*/

	normalize(hist, hist, 1, 0, type, -1, Mat());

	//minMaxLoc(hist, &minVal, &maxVal, &minLoc, &maxLoc);

	for (int i = 1; i <= hist.rows; i++)
	{
		rectangle(histImage, Point(width*(i - 1), hist_h - 1),
			Point(width*i - 1, hist_h - cvRound(20 * hist_h*hist.at<float>(i - 1)) - 1),
			Scalar(255, 255, 255), -1);
	}
	imshow(name, histImage);
}
//主函数
int main()
{
	Mat img1 = imread("src.png");
	Mat img2 = imread("template.png");
	if (img1.empty() || img2.empty())
	{
		cout << "找不到图像!" << endl;
		return -1;
	}
	Mat hist1, hist2;
	//计算两张图像直方图
	const int channels[1] = { 0 };
	float inRanges[2] = { 0,255 };
	const float* ranges[1] = { inRanges };
	const int bins[1] = { 256 };
	calcHist(&img1, 1, channels, Mat(), hist1, 1, bins, ranges);
	calcHist(&img2, 1, channels, Mat(), hist2, 1, bins, ranges);
	//归一化两张图像的直方图
	drawHist(hist1, NORM_L1, "hist1");
	drawHist(hist2, NORM_L1, "hist2");
	//计算两张图像直方图的累积概率
	float hist1_cdf[256] = { hist1.at<float>(0) };
	float hist2_cdf[256] = { hist2.at<float>(0) };
	for (int i = 1; i < 256; i++)
	{
		hist1_cdf[i] = hist1_cdf[i - 1] + hist1.at<float>(i);
		hist2_cdf[i] = hist2_cdf[i - 1] + hist2.at<float>(i);
	}
	//构建累积概率误差矩阵
	float diff_cdf[256][256];
	for (int i = 0; i < 256; i++)
	{
		for (int j = 0; j < 256; j++)
		{
			diff_cdf[i][j] = fabs(hist1_cdf[i] - hist2_cdf[j]);
		}
	}

	//生成LUT映射表
	Mat lut(1, 256, CV_8U);
	for (int i = 0; i < 256; i++)
	{
		// 查找源灰度级为i的映射灰度
		// 和i的累积概率差值最小的规定化灰度
		float min = diff_cdf[i][0];
		int index = 0;
		//寻找累积概率误差矩阵中每一行中的最小值
		for (int j = 1; j < 256; j++)
		{
			if (min > diff_cdf[i][j])
			{
				min = diff_cdf[i][j];
				index = j;
			}
		}
		lut.at<uchar>(i) = (uchar)index;
	}
	Mat result, hist3;
	LUT(img1, lut, result);
	imshow("待匹配img", img1);
	imshow("匹配的模板img", img2);
	imshow("直方图匹配(规定化)结果", result);
	calcHist(&result, 1, channels, Mat(), hist3, 1, bins, ranges);
	drawHist(hist3, NORM_L1, "hist3");  //绘制匹配后的图像直方图
	waitKey(0);
	return 0;
}

2.4 直方图二值化

threshold(my_hist,my_threshold_hist,threshold,0,cv::THRESH_TOZERO);

2.5 直方图最值

void minMaxLoc(InputArray src, //输入单通道阵列
                 double* minVal, //返回最小值的指针,若无需返回,此值置为NULL
                 double* maxVal=0, //返回最大值的指针,若无需返回,此值置为NULL
                 Point* minLoc=0,//返回最小位置的指针(二维情况下),若无需返回,此值置为NULL
                 Point* maxLoc=0,//返回最大位置的指针(二维情况下),若无需返回,此值置为NULL
                 InputArray mask=noArray())//用于选择子阵列的可选掩膜

3.直方图的计算与绘制

//直方图的计算与绘制
 
#include "stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
 
using namespace cv;
using namespace std;
 
const char*output = "histogram iamge";
 
int main(int argc, char*argv)
{
	Mat src, dst, dst1;
	src = imread("C:\\Users\\zhj\\Desktop\\image\\heart.jpg");
	if (!src.data)
	{
		printf("could not load image...\n");
		return -1;
	}
	char input[] = "input image";
	namedWindow(input, CV_WINDOW_AUTOSIZE);
	namedWindow(output, CV_WINDOW_AUTOSIZE);
	imshow(input, src);
 
	//步骤一:分通道显示
	vector<Mat>bgr_planes;
	split(src, bgr_planes);
	//split(// 把多通道图像分为多个单通道图像 const Mat &src, //输入图像 Mat* mvbegin)// 输出的通道图像数组
 
	//步骤二:计算直方图
	int histsize = 256;
	float range[] = { 0,256 };
	const float*histRanges = { range };
	Mat b_hist, g_hist, r_hist;
	calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histsize, &histRanges, true, false);
	calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histsize, &histRanges, true, false);
	calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histsize, &histRanges, true, false);
	
 
	//归一化
	int hist_h = 400;//直方图的图像的高
	int hist_w = 512;直方图的图像的宽
	int bin_w = hist_w / histsize;//直方图的等级
	Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));//绘制直方图显示的图像
	normalize(b_hist, b_hist, 0, hist_h, NORM_MINMAX, -1, Mat());//归一化,得到的数据类型为float
	normalize(g_hist, g_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
	normalize(r_hist, r_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
 
	//步骤三:绘制直方图(render histogram chart)
	for (int i = 1; i < histsize; i++)
	{
		//绘制蓝色分量直方图
		line(histImage, Point((i - 1)*bin_w, hist_h - cvRound(b_hist.at<float>(i - 1))),
			Point((i)*bin_w, hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, CV_AA);
		//绘制绿色分量直方图
		line(histImage, Point((i - 1)*bin_w, hist_h - cvRound(g_hist.at<float>(i - 1))),
			Point((i)*bin_w, hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, CV_AA);
		//绘制红色分量直方图
		line(histImage, Point((i - 1)*bin_w, hist_h - cvRound(r_hist.at<float>(i - 1))),
			Point((i)*bin_w, hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, CV_AA);
	}
	imshow(output, histImage);
	waitKey(0);
	return 0;
}

函数cvRound,cvFloor,cvCeil 都是用一种舍入的方法将输入浮点数转换成整数:
cvRound 返回跟参数最接近的整数值(四舍五入);
cvFloor 返回不大于参数的最大整数值;
cvCeil 返回不小于参数的最小整数值。

4.直方图对比

cv::compareHist();

//对输入的两张图像计算得到直方图H1与H2,归一化到相同的尺度空间然后可以通过计算H1与H2的之间的距离得到两个直方图的相似程度进而比较图像本身的相似程度。
//Opencv提供的比较方法有四种:Correlation 相关性比较 Chi - Square 卡方比较 Intersection 十字交叉性 Bhattacharyya distance 巴氏距离
//步骤:首先把图像从RGB色彩空间转换到HSV色彩空间cvtColor
//计算图像的直方图,然后归一化到[0~1]之间calcHist和normalize;
//使用上述四种比较方法之一进行比较compareHist

//直方图比较
 
#include "stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
 
using namespace cv;
using namespace std;
 
string convertToString(double d);
int main(int argc, char*argv)
{
	Mat base, test1, test2;
	Mat hsvbase, hsvtest1, hsvtest2;
	base = imread("C:\\Users\\zhj\\Desktop\\image\\test.jpg");
	test1 = imread("C:\\Users\\zhj\\Desktop\\image\\SaltNoise.jpg");
	test2 = imread("C:\\Users\\zhj\\Desktop\\image\\GaussianNoise.jpg");
	if (!base.data)
	{
		printf("could not load image...\n");
		return -1;
	}
	//步骤一:从RGB空间转换到HSV空间
	cvtColor(base, hsvbase, CV_BGR2HSV);
	cvtColor(test1, hsvtest1, CV_BGR2HSV);
	cvtColor(test2, hsvtest2, CV_BGR2HSV);
 
	//步骤二:计算直方图与归一化
	int h_bins = 50;
	int s_bins = 60;
	int histsize[] = { h_bins,s_bins };
	//hue varies from 0 to 179,saturation from 0 to 255
	float h_ranges[] = { 0,180 };
	float s_ranges[] = { 0,256 };
	const float*histRanges[] = { h_ranges,s_ranges };
	//use the 0-th and 1-st channels
	int channels[] = { 0,1 };
	MatND hist_base;
	MatND hist_test1;
	MatND hist_test2;
	//计算直方图
	calcHist(&hsvbase, 1, channels, Mat(), hist_base, 2, histsize, histRanges, true, false);
	calcHist(&hsvtest1, 1, channels, Mat(), hist_test1, 2, histsize, histRanges, true, false);
	calcHist(&hsvtest2, 1, channels, Mat(), hist_test2, 2, histsize, histRanges, true, false);
 
	//归一化
	normalize(hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat());//归一化
	normalize(hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat());
	normalize(hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat());
 
	//步骤三:比较直方图,并返回值
	double basebase = compareHist(hist_base, hist_base, CV_COMP_BHATTACHARYYA);//比较直方图
	double basetest1 = compareHist(hist_base, hist_test1, CV_COMP_BHATTACHARYYA);
	double basetest2 = compareHist(hist_base, hist_test2, CV_COMP_BHATTACHARYYA);
	double test1test2 = compareHist(hist_test1, hist_test2, CV_COMP_BHATTACHARYYA);
	printf("test1 with test2 correlation value :%f", test1test2);
 
	//在原图中显示相关性参数
	putText(base, convertToString(basebase), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, CV_AA);
	putText(test1, convertToString(basetest1), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, CV_AA);
	putText(test2, convertToString(basetest2), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, CV_AA);
	putText(test2, convertToString(test1test2), Point(100, 100), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, CV_AA);
 
	namedWindow("base", CV_WINDOW_AUTOSIZE);
	namedWindow("test1", CV_WINDOW_AUTOSIZE);
	namedWindow("test2", CV_WINDOW_AUTOSIZE);
 
	imshow("base", base);
	imshow("test1", test1);
	imshow("test2", test2);
 
	waitKey(0);
	return 0;
}
 
//由于comparehist计算出来的相关性的值是一个double型,这个函数就是把double转变为string
string convertToString(double d)
{
	ostringstream os;
	if (os << d)
	return os.str();
	return "invalid conversion";
}

5.直方图的反向投影

反向投影是反映直方图模型在目标图像中的分布情况;简单点说就是用直方图模型去目标图像中寻找特征。通常用HSV色彩空间的HS两个通道直方图模型
假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:
(1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;
(2)生成临时图像的直方图;
(3)用临时图像的直方图和模板图像的直方图对比,对比结果记为c;
(4)直方图对比结果c,就是结果图像(0,0)处的像素值;
(5)切割输入图像从(0,1)至(10,11)的临时图像,对比直方图,并记录到结果图像;
(6)重复(1)~(5)步直到输入图像的右下角。

void calcBackProject(const Mat* arrays,//图像深度必须位CV_8U,CV_16U或CV_32F中的一种
                         int narrays, //是一个单通道32位浮点图像,它的宽度为W-w+1,高度为H-h+1,这里的W和H是输入图像的宽度和高度,w和h是模板图像的宽度和高度
                         const int* channels,
                         InputArray hist, 
                         OutputArray backProject, 
                         const float** ranges, 
                         double scale=1, bool uniform=true )
  C++ void mixChannels(const Mat*src, //输入矩阵,可以为一个也可以为多个,但是矩阵必须有相同的大小和深度
                       size_t nsrcs, //输入矩阵的个数
                       Mat* dst, //输出矩阵,可以为一个也可以为多个,但是所有的矩阵必须事先分配空间(如用create),大小和深度须与输入矩阵等同.
                       size_t ndsts, //输出矩阵的个数
                       const int* fromTo, 
                       size_t npairs)//参数fromTo中的有几组输入输出通道关系,其实就是参数fromTo的数组元素个数除以2.
    //fromTo –设置输入矩阵的通道对应输出矩阵的通道,规则如下:首先用数字标记输入矩阵的各个通道。输入矩阵个数可能多于一个并且每个矩阵的通道可能不一样,
    //第一个输入矩阵的通道标记范围为:0 ~src[0].channels() - 1,第二个输入矩阵的通道标记范围为:src[0].channels() ~src[0].channels() + src[1].channels() - 1,
    //以此类推;其次输出矩阵也用同样的规则标记,第一个输出矩阵的通道标记范围为:0 ~dst[0].channels() - 1,第二个输入矩阵的通道标记范围为:dst[0].channels()
    //~dst[0].channels() + dst[1].channels() - 1, 以此类推;最后,数组fromTo的第一个元素即fromTo[0]应该填入输入矩阵的某个通道标记,而fromTo的第二个元素即
    //fromTo[1]应该填入输出矩阵的某个通道标记,这样函数就会把输入矩阵的fromTo[0]通道里面的数据复制给输出矩阵的fromTo[1]通道。fromTo后面的元素也是这个
    //道理,总之就是一个输入矩阵的通道标记后面必须跟着个输出矩阵的通道标记。
//直方图的反向投影
//加载图片imread
//将图像从RGB色彩空间转换到HSV色彩空间cvtColor
//计算直方图和归一化calcHist与normalize
//Mat与MatND其中Mat表示二维数组,MatND表示三维或者多维数据,此处均可以用Mat表示。
//计算反向投影图像 - calcBackProject
 
#include "stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
 
using namespace std;
using namespace cv;
Mat src, hsv_src;
Mat hue;
int bins = 12;
void Hist_And_Backprojection(int, void*);
int main(int argc, char*argv)
{
	src = imread("C:\\Users\\59235\\Desktop\\imag\\test.jpg");
	if (!src.data)
	{
		printf("could not load image...\n");
		return -1;
	}
	namedWindow("input", CV_WINDOW_AUTOSIZE);
 
	//将图像从RGB色彩空间转换到HSV色彩空间
	cvtColor(src, hsv_src, CV_BGR2HSV);
 
	hue.create(hsv_src.size(), hsv_src.depth());
	int nchannels[] = { 0,0 };
 
	//mixChannels主要就是把输入的矩阵(或矩阵数组)的某些通道拆分复制给对应的输出矩阵(或矩阵数组)的某些通道中,其中的对应关系就由fromTo参数制定.
	mixChannels(&hsv_src, 1, &hue, 1, nchannels, 1);
 
	createTrackbar("Histogram Bins:", "input", &bins, 180, Hist_And_Backprojection);
	Hist_And_Backprojection(0, 0);
 
	imshow("input", src);
	waitKey(0);
	return 0;
}
 
void Hist_And_Backprojection(int, void*)
{
	//计算直方图
	float range[] = { 0,180 };
	const float *histRanges = { range };
	Mat h_hist;
	calcHist(&hue, 1, 0, Mat(), h_hist, 1, &bins, &histRanges, true, false);
	//归一化
	normalize(h_hist, h_hist, 0, 255, NORM_MINMAX, -1, Mat());
 
	//计算反向投影图像 - calcBackProject
	Mat backProjectIamge;
	calcBackProject(&hue, 1, 0, h_hist, backProjectIamge, &histRanges, 1, true);
 
	namedWindow("BackProjectIamge", CV_WINDOW_AUTOSIZE);
	imshow("BackProjectIamge", backProjectIamge);
 
	//画直方图
	int hist_h = 400;
	int hist_w = 400;
	int bin_w = (hist_w / bins);
	Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
	for (size_t i = 1; i < bins; i++)
	{
		rectangle(histImage,
			Point((i - 1)*bin_w, (hist_h - cvRound(h_hist.at<float>(i - 1)*(400 / 255)))),
			Point(i*bin_w, (hist_h - cvRound(h_hist.at<float>(i)*(400 / 255)))),
			Scalar(0, 0, 255), 2, LINE_AA);
	}
	imshow("Histogram", histImage);
	return;
}

6.局部直方图处理

直方图均衡是一种用于整个图像增强的全局方法,但存在这样的情况,增强图像中小区域的细节也是需要的。这些区域中,一些像素的影响在全局变换的计算中可能被忽略了,因为全局变换没有必要保证期望的局部增强。解决方法是以图像中每个像素的邻域中的灰度分布为基础设计变换函数。有时用于减少计算量的另一种方法是使用非重叠区域,但这种方法通常会产生我们不希望的“棋盘”效应。

7.Demo

参考