本章我将为大家介绍直方图的相关知识,相关内容分别有:直方图均衡化、直方图计算、直方图比较,直方图反向投射。
1.什么是直方图(Histogram)?
图像直方图,是指对整个图像像在灰度范围内的像素值(0~255)统计出现频率次数,据此生成的直方图,称为图像直方图-直方图。直方图反映了图像灰度的分布情况。是图像的统计学特征。
2.直方图均衡化
是一种提高图像对比度的方法,拉伸图像灰度值范围。
如何实现,通过上一课中的remap我们知道可以将图像灰度分布从一个分布映射到另外一个分布,然后在得到映射后的像素值即可。
cv::equalizeHist
equalizeHist(
InputArray src,//输入图像,必须是8-bit的单通道图像
OutputArray dst// 输出结果
)
#include
#include
using namespace cv;
using namespace std;
Mat src, dst, map_x, map_y;
const char* OUTPUT_TITLE = "equalizeHist demo";
int main(int argc, char** argv) {
src = imread("E:/photoss/dog1.jpeg");
if (!src.data) {
printf("Could not load image...\n");
return 1;
}
char input_win[] = "input image";
namedWindow(input_win, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_TITLE, CV_WINDOW_AUTOSIZE);
cvtColor(src, src, CV_BGR2GRAY);
imshow(input_win, src);
equalizeHist(src, dst);//直方图均衡化
imshow(OUTPUT_TITLE, dst);
waitKey(0);
return 0;
}
1.直方图概念
上述直方图概念是基于图像像素值,其实对图像梯度、每个像素的角度、等一切图像的属性值,我们都可以建立直方图。这个才是直方图的概念真正意义,不过是基于图像像素灰度直方图是最常见的。
直方图最常见的几个属性:
1.cv::split
split(// 把多通道图像分为多个单通道图像
const Mat &src, //输入图像
Mat* mvbegin)// 输出的通道图像数组
2.cv::calcHist
calcHist(
const Mat* images,//输入图像指针
int images,// 图像数目
const int* channels,// 通道数
InputArray mask,// 输入mask,可选,不用则输入Mat()
OutputArray hist,//输出的直方图数据
int dims,// 维数
const int* histsize,// 直方图级数
const float* ranges,// 值域范围
bool uniform,// true by default,是否归一化
bool accumulate// false by defaut,是否累计各通道值
)
#include
#include
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
Mat src, dst, map_x, map_y;
src = imread("E:/photoss/dog1.jpeg");
if (!src.data) {
printf("Could not load image...\n");
return 1;
}
char input_win[] = "input image";
const char* OUTPUT_TITLE = "histogram demo";
namedWindow(input_win, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_TITLE, CV_WINDOW_AUTOSIZE);
imshow(input_win, src);
//分通道显示
vector<Mat> bgr_planes;
split(src, bgr_planes);//对src图像进行通道分离至bgr_planes图像数组中
//imshow("single channel demo",bgr_planes[0]);
//计算直方图
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);
//输入图像为bgr_planes[0],输入图像个数为1,对图像的第一通道进行直方图统计,掩膜设置为Mat()即不设置掩膜,输出图像为b_hist,需要统计直方图个数为1维数为1,划分的区间数即bins为255,统计像素区间为0-255,进行归一化处理,不累加计算像素值得个数
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;//直方图每一区间宽度为总宽度除以bins总个数
Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));//定义图像高为hist_h,宽为hist_w
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());
//做出各通道直方图,折线图
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, LINE_AA);//b通道
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);//g通道
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);//r通道
}
imshow(OUTPUT_TITLE, histImage);
waitKey(0);
return 0;
}
1.直方图比较方法-概述
对输入的两张图像计算得到直方图H1与H2,归一化到相同的尺度空间
然后可以通过计算H1与H2的之间的距离得到两个直方图的相似程度进
而比较图像本身的相似程度。Opencv提供的比较方法有四种:
2.相关性计算(CV_COMP_CORREL)
3.卡方计算(CV_COMP_CHISQR)
4.十字计算(CV_COMP_INTERSECT)
5.巴氏距离计算(CV_COMP_BHATTACHARYYA )
1.直方图比较相关步骤
2.cv::compareHist
compareHist(
InputArray h1, // 直方图数据,下同
InputArray H2,
int method// 比较方法,上述四种方法之一
)
#include
#include
#include
using namespace std;
using namespace cv;
string convertToString(double d);
int main(int argc, char** argv) {
Mat base, test1, test2;
Mat hsvbase, hsvtest1, hsvtest2;
base = imread("D:/photos/2.jpg");
if (!base.data) {
printf("could not load image...\n");
return -1;
}
test1 = imread("D:/photos/333.png");
test2 = imread("D:/photos/1112.jpg");
//直方图比较步骤,需要将图像转变为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 };//色调取值范围为0-180度
float s_ranges[] = { 0, 256 };//饱和度取值范围为0-256
const float* ranges[] = { h_ranges, s_ranges };//定义取值范围区间
// Use the o-th and 1-st channels
int channels[] = { 0, 1 };//两通道
MatND hist_base;//MatND是多维矩阵(>=3维)。Mat特指二维矩阵
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());
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_INTERSECT);//进行直方图比较
double basetest1 = compareHist(hist_base, hist_test1, CV_COMP_INTERSECT);
double basetest2 = compareHist(hist_base, hist_test2, CV_COMP_INTERSECT);
double tes1test2 = compareHist(hist_test1, hist_test2, CV_COMP_INTERSECT);
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);
namedWindow("base", CV_WINDOW_AUTOSIZE);
namedWindow("test1", CV_WINDOW_AUTOSIZE);
namedWindow("test2", CV_WINDOW_AUTOSIZE);
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";
}
运行效果:
1.反向投影
反向投影是一种记录给定图像中的像素点如何适应直方图模型像素分布的方式,简单来讲,反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的特征。反向投影在某一位置的值就是原图对应位置像素值在原图像中的总数目。
具体形象理解见博客:opencv学习(三十九)之反向投影calcBackProject()
2.实施步骤
1.处理步骤
2.cv::normalize
//该函数归一化输入数组使它的范数或者数值范围在一定的范围内。
normalize(
InputArray src,//输入数组
OutputArray dst, // 输出数组,支持原地运算
double alpha, // range normalization模式的最小值
doublebeta, //range normalization模式的最大值,不用于norm normalization(范数归一化)模式。
int norm_type, // 归一化的类型
int dtype=-1, // dtype为负数时,输出数组的type与输入数组的type相同
InputArray mask=noArray()// 操作掩膜,用于指示函数是否仅仅对指定的元素进行操作。
)
Parameters:
src
输入数组
dst
输出数组,支持原地运算
alpha
range normalization模式的最小值
beta
range normalization模式的最大值,不用于norm normalization(范数归一化)模式。
normType
归一化的类型,可以有以下的取值:
NORM_MINMAX:数组的数值被平移或缩放到一个指定的范围,线性归一化,一般较常用。
NORM_INF: 此类型的定义没有查到,根据OpenCV 1的对应项,可能是归一化数组的C-范数(绝对值的最大值)
NORM_L1 : 归一化数组的L1-范数(绝对值的和)
NORM_L2: 归一化数组的(欧几里德)L2-范数
dtype
dtype为负数时,输出数组的type与输入数组的type相同;
否则,输出数组与输入数组只是通道数相同,而tpye=CV_MAT_DEPTH(dtype).
mask
操作掩膜,用于指示函数是否仅仅对指定的元素进行操作。
3.cv::calcBackProjection()
calcBackProjection()函数共有三种形式,根据传入参数的不同选择不同的调用,为重载函数。
void cv::calcBackProject ( const Mat * images,
int nimages,
const int* channels,
InputArray hist,
OutputArray backProject,
const float** ranges,
double scale = 1,
bool uniform = true
)
参数解释:
const Mat* images:输入图像,图像深度必须位CV_8U,CV_16U或CV_32F中的一种,尺寸相同,每一幅图像都可以有任意的通道数
int nimages:输入图像的数量
const int* channels:用于计算反向投影的通道列表,通道数必须与直方图维度相匹配,第一个数组的通道是从0到image[0].channels()-1,第二个数组通道从图像image[0].channels()到image[0].channels()+image[1].channels()-1计数
InputArray hist:输入的直方图,直方图的bin可以是密集(dense)或稀疏(sparse)
OutputArray backProject:目标反向投影输出图像,是一个单通道图像,与原图像有相同的尺寸和深度
const float ranges**:直方图中每个维度bin的取值范围
double scale=1:可选输出反向投影的比例因子
bool uniform=true:直方图是否均匀分布(uniform)的标识符,有默认值true
#include
#include
#include
using namespace std;
using namespace cv;
Mat src; Mat hsv; Mat hue;
int bins = 12;
void Hist_And_Backprojection(int, void*);
int main(int argc, char** argv) {
src = imread("D:/photos/2.jpg");
if (src.empty()) {
printf("could not load image...\n");
return -1;
}
const char* window_image = "input image";
namedWindow(window_image, CV_WINDOW_NORMAL);
namedWindow("BackProj", CV_WINDOW_NORMAL);
namedWindow("Histogram", CV_WINDOW_NORMAL);
cvtColor(src, hsv, CV_BGR2HSV);//将源图像转换为hsv图像通道
hue.create(hsv.size(), hsv.depth());
cout << "hsv的depth为" << hsv.depth();
int nchannels[] = { 0, 0 };
mixChannels(&hsv, 1, &hue, 1, nchannels, 1);//将hsv中第一个通道的矩阵复制给hue第一个通道
createTrackbar("Histogram Bins:", window_image, &bins, 180, Hist_And_Backprojection);//动态调整bin值
Hist_And_Backprojection(0, 0);
imshow(window_image, 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);//&bin为每个维度bin的取值范围
normalize(h_hist, h_hist, 0, 255, NORM_MINMAX, -1, Mat());
Mat backPrjImage;
calcBackProject(&hue, 1, 0, h_hist, backPrjImage, &histRanges, 1, true);
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,
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(i) * (400 / 255)))),
Point(i*bin_w, hist_h),
Scalar(0, 0, 255), -1);//绘制矩形来绘制直方分布图
}
imshow("Histogram", histImage);
return;
}