/*
* 直方图均衡化
*什么是直方图(Histogram)
假设有图像数据8x8,像素值范围0~14共15个灰度等级,统计得到各个等级出现次数及直方图如下图
图像直方图,是指对整个图像像在灰度范围内的像素值(0~255)统计出现频率次数,据此生成的直方图,称为图像直方图-直方图。直方图反映了图像灰度的分布情况。
是图像的统计学特征。
任何一个统计出来的属于图像度量,有一定的范围值都可以对它进行一个直方图的输出,在实际的特征提取都非常有用
*直方图均衡化
是一种提高图像对比度的方法,拉伸图像灰度值范围。
*如何实现,通过上一课中的remap我们知道可以将图像灰度分布从一个分布映射到另外一个分布,然后在得到映射后的像素值即可。
*API说明cv::equalizeHist
equalizeHist(
InputArray src,//输入图像,必须是8-bit的单通道图像
OutputArray dst// 输出结果
)
*/
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat src, dst, gray_src;
src = imread("D:/A_Graduation/Learning/qt/image/tangwei.jpg");
imshow("src", src);
cvtColor(src, gray_src, CV_RGB2GRAY);
equalizeHist(gray_src, dst);
imshow("dst", dst);
waitKey(0);
return 0;
}
/*
*直方图计算 [ api:calcHist ]
假设图像数据8*8,像素值范围0-14共15个灰度等级,统计得到各个等级出现次数及直方图如右侧所示,每个紫色的长条叫BIN
上述直方图概念是基于图像像素值,其实对图像梯度、每个像素的角度、等一切图像的属性值,我们都可以建立直方图。
这个才是直方图的概念真正意义,不过是基于图像像素灰度直方图是最常见的。
直方图最常见的几个属性:
- dims 表示维度,对灰度图像来说只有一个通道值dims=1
- bins 表示在维度中子区域大小划分,bins=256,划分为256个级别, 256是16*16,那么我们可以划分成16个等级,每一个等级都有0-16个范围
- range 表示值得范围,灰度值范围为[0~255]之间
API学习
split( // 把多通道图像分为多个单通道图像
const Mat &src, //输入图像 多通道的
Mat* mvbegin // 输出的单通道图像数组 vector的数组
)
calcHist(
const Mat* images,//输入图像指针
int images,// 图像数目
const int* channels,// 通道数
InputArray mask,// 输入mask,可选,不用
OutputArray hist,//输出的直方图数据
int dims,// 维数 我们只有1维,直方图的频次
const int* histsize,// 直方图级数 256
const float* ranges,// 值域范围 0-255
bool uniform,// true by default
bool accumulate// false by defaut 是不是要累加,多通道才累加
)
*/
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat src, dst;
src = imread("D:/A_Graduation/Learning/qt/image/tangwei.jpg");
imshow("src", src);
// 分通道显示
vector<Mat> bgr_planes;
split(src, bgr_planes);
// imshow("single channel demo", bgr_planes[0]);
// 计算直方图
int histSize = 256;
float range[] = { 0, 256 }; //最多是255, 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); //r、g、b的直方图我们就计算出来了
//根据直方图来进行多通道图像的输出
int hist_h = 400; //直方图的高度
int hist_w = 512; //直方图的宽度,刚刚是256,这样好计算
int bin_w = hist_w / histSize; //bin的宽度
//创建显示直方图的画布:
Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
// 归一化
//构建的这个直方图的高度是400的,但是直方图算出来的频次的数据可能是几千几万,那么两个高怎么去匹配,
//所以我们把那个值归一化到我们最大400,最小0这个空间里来,normalize就是归一化
normalize(b_hist, b_hist,0, hist_h, NORM_MINMAX, -1 , Mat());
normalize(g_hist, g_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
normalize(r_hist, r_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
/*该函数接受下列参数:
r_hist: 输入数组
r_hist: 归一化后的输出数组(支持原地计算)
0 及 histImage.rows: 这里,它们是归一化 r_hist 之后的取值极限
NORM_MINMAX: 归一化方法 (例中指定的方法将数值缩放到以上指定范围)
-1: 指示归一化后的输出数组与输入数组同类型
Mat(): 可选的掩码
*/
// render histogram chart 绘制直方图//画上直方图的数据
for (int i = 1; i < histSize; i++) { //256个bin
line(histImage, Point((i - 1)*bin_w, hist_h - cvRound(b_hist.at<float>(i - 1))), //openCv中的一个函数:int cvRound(double value)对一个double型的数进行四舍五入,并返回一个整型数
Point((i)*bin_w, hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, LINE_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, LINE_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, LINE_AA);
}
imshow("output", histImage);
waitKey(0);
return 0;
}
/*
*直方图比较 [ api:compareHist ]
*概述
对输入的两张图像计算得到直方图H1与H2,归一化到相同的尺度空间,然后可以通过计算H1与H2的之间的距离得到两个直方图的相似程度进而比较图像本身的相似程度。
Opencv提供的比较方法有四种:
-Correlation 相关性比较
-Chi-Square 卡方比较
-Intersection 十字交叉性
-Bhattacharyya distance 巴氏距离
相关性计算(CV_COMP_CORREL)
卡方计算(CV_COMP_CHISQR)
十字计算(CV_COMP_INTERSECT) 得出来的结果不可预不一致
巴氏距离计算(CV_COMP_BHATTACHARYYA )
compareHist支持多通道的直方图的计算,那么我们只要给它传一个多通道的数据,然后让他多通道的直方图,然后两个通道计算一个直方图的然后用上述
几个方法进行直方图的比较
*步骤
1,首先把图像从RGB色彩空间转换到HSV色彩空间cvtColor,因为这个直方图主要基于亮度,就是灰度级别,H和S是这两个当中最敏感的表征色泽和饱和度的,
V呢我们去掉,就把三通道的变成两通道的,我们去H和S这两个通道,然后我们再从色彩空间cvtColor把它变过来,计算H和S这两个通道的直方图
2,计算图像的直方图,然后归一化到[0~1]之间 分别用calcHist和normalize;
3,使用上述四种比较方法之一进行比较 compareHist - 直方图比较的API
*相关API cv::compareHist
compareHist(
InputArray h1, // 直方图数据,下同
InputArray H2,
int method // 比较方法,上述四种方法之一
)
得到的结果是double的数值,拿着double的数值一判断一做就知道结论是什么样子了
*/
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
string convertToString(double d); //把double的值变成string,把string作为文字放到图像上面去,所以后面要puttext
int main()
{
Mat base, test1, test2;
Mat hsvbase, hsvtest1, hsvtest2;
base = imread("D:/A_Graduation/Learning/opencv/learningOpencv/Histogram_Comparison_Source_0.jpg");
if (!base.data)
{
printf("could not load image...\n");
return -1;
}
test1 = imread("D:/A_Graduation/Learning/opencv/learningOpencv/Histogram_Comparison_Source_1.jpg");
test2 = imread("D:/A_Graduation/Learning/opencv/learningOpencv/Histogram_Comparison_Source_2.jpg");
//从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; //设灰度等级,一个是50个等级,一个是60个等级,以前设的都是256
int histSize[] = { h_bins, s_bins }; //为了给下面传参数给地址
float h_ranges[] = { 0, 180 }; // hue varies from 0 to 179, saturation(饱和度) from 0 to 255
float s_ranges[] = { 0, 256 };
const float* ranges[] = { h_ranges, s_ranges };
// Use the o-th and 1-st channels
int channels[] = { 0, 1 }; //我们要求两个通道数,所以把0,1
MatND hist_base; //因为我们是多通道的所以用MatND, ND表示多维的
MatND hist_test1;
MatND hist_test2;
calcHist(&hsvbase, 1, channels, Mat(), hist_base,2, histSize, ranges, true, false);
normalize(hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat()); //归一化的0-1之间
calcHist(&hsvtest1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false);
normalize(hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat());
calcHist(&hsvtest2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false);
normalize(hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat());
//比较直方图并返回值
double basebase = compareHist(hist_base, hist_base, CV_COMP_CORREL);
double basetest1 = compareHist(hist_base, hist_test1, CV_COMP_CORREL);
double basetest2 = compareHist(hist_base, hist_test2, CV_COMP_CORREL);
double tes1test2 = compareHist(hist_test1, hist_test2, CV_COMP_CORREL);
printf("test1 compare with test2 correlation value :%f", tes1test2);
Mat test12;
test2.copyTo(test12);
putText(base, convertToString(basebase), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, LINE_AA);
putText(test1, convertToString(basetest1), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, LINE_AA);
putText(test2, convertToString(basetest2), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, LINE_AA);
putText(test12, convertToString(tes1test2), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, LINE_AA);
imshow("base", base);
imshow("test1", test1);
imshow("test2", test2);
imshow("test12", test12);
waitKey(0);
return 0;
}
string convertToString(double d) {
ostringstream os;
if (os << d)
return os.str();
return "invalid conversion";
}
/*
*直方图反向投影(Back Projection)
* 反向投影
在图像处理中,可以计算直方图,直方图也是数据,这些数据是有一些特征的,拿这个数据模型,到待检测的图像上去去给它
一个匹配,发现这个特征跟待检测图像上面在哪边匹配,如果是匹配到了,我们把这个特征再反向投影到我们待检测图像上面,
我们就得到一个最终的模型,那么这个模型就是跟我们模板中给出的那个模型是一致的,这个就是我们通过直方图的反响投影
实现了一个对象检测
反向投影是反映直方图模型在目标图像中的分布情况
简单点说就是用直方图模型去目标图像中寻找是否有相似的对象。通常用HSV色彩空间的HS两个通道直方图模型
*反向投影 – 步骤
1.建立直方图模型
2.计算待测图像直方图并映射到模型中
3.从模型反向计算生成图像
*实现步骤与相关API
1.加载图片imread
2.将图像从RGB色彩空间转换到HSV色彩空间cvtColor
3.计算直方图和归一化calcHist与normalize
4.Mat与MatND其中Mat表示二维数组calcBackProject,MatND表示三维或者多维数据,此处均可以用Mat表示。
5.计算反向投影图像 - calcBackProject
从一个原图像做到一个直方图的模型,从直方图模型通过计算Backprojection给它反射返回去,知道这个通路就好了
*/
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
Mat src, hsv, hue;
int bins = 12;
void Hist_And_Backprojection(int, void*);
int main()
{
src = imread("D:/A_Graduation/Learning/opencv/learningOpencv/Histogram_Comparison_Source_1.jpg");
cvtColor(src, hsv, CV_BGR2HSV);
//把其中一个通道给分出来,分到hue里面去,单通道只要获取它的深度,因为只有一个通道,这个就是它的type了
hue.create(hsv.size(), hsv.depth());
int nchannels[] = { 0, 0 }; //拷贝的那个从哪个通道到哪个通道,从它的第一个通道到它的第一个通道
mixChannels(&hsv, 1, &hue, 1, nchannels, 1); //hue的h通道就有数据了
/*&hsv: 一系列输入图像的数组, 被拷贝的通道的来源
1: 输入数组中图像的数目
&hue: 一系列目的图像的数组, 储存拷贝的通道
1: 目的数组中图像的数目
ch[] = {0,0}: 通道索引对的数组,指示如何将输入图像的某一通道拷贝到目的图像的某一通道。在这里,&hsv图像的Hue(0) 通道被拷贝到&hue图像(单通道)的0 通道。
1: 通道索引对德数目*/
createTrackbar("Histogram Bins:", "input", &bins, 180, Hist_And_Backprojection); //用滑块选择bins
Hist_And_Backprojection(0, 0);
imshow("input", src);
waitKey(0);
return 0;
}
void Hist_And_Backprojection(int, void*)
{
//对hue这个图像计算直方图并归一化到0-1之间
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()); //0-1值太小了,calcBackProject之后变成零点几的像素显示不出来
Mat backPrjImage;
calcBackProject(&hue, 1,0, h_hist, backPrjImage, &histRanges, 1, true);
//所有的实参都已经知道了(与计算直方图的实参一样), 仅仅增加了 backproj 矩阵,用来储存原图像(&hue)的反向投影。
imshow("BackProj", backPrjImage);
//绘制直方图
int hist_h = 400;
int hist_w = 400;
Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0)); //直方图的图像
int bin_w = (hist_w / bins);
for (int i = 1; i < bins; i++)
{
rectangle(histImage, //对每一个bins绘制一个rectangle
Point((i - 1)*bin_w, (hist_h - cvRound(h_hist.at<float>(i - 1) * (400 / 255)))), //400/255是归一化到400,除以255是归一化到0-1之间,乘以400是归一化到400
//Point(i*bin_w, (hist_h - cvRound(h_hist.at<float>(i) * (400 / 255)))),
Point(i*bin_w, hist_h),
Scalar(0, 0, 255), -1 //-1表示填充
);
}
imshow("Histogram", histImage);
return;
}