直方图只是简单地将数据归入预定义的组,并在每个组内进行计数。也可以选择对数据提取特征,再对特征进行计数,这里的特征可以是梯度的长度、梯度的方向、颜色或其他任何可以反应数据特点的特征。也就是说,直方图是一种用来揭示数据分布的统计特性的工具。

直方图在计算机视觉中的应用:

  • 通过判断帧与帧之间边缘和颜色的统计量是否出现巨大变化,来检测视频中场景的变化。
  • 通过使用兴趣点邻域内的特征组成的直方图,来辨识兴趣点。若将边缘、颜色、角点等等的直方图作为特征,可以使用分类器来进行目标识别。
  • 提取视频中的颜色或边缘直方图序列,可以用来判断视频是否拷贝自网络。等等。

1 cv::calcHist():从数据创建直方图

函数cv::calcHist()可以从一个或者多个数组中创建直方图。直方图的维度和输入数组的维度或大小无关,而是取决于输入数组的数量。cv::calcHist()总共有三种形式,前两种使用“老式的”C风格数组,第三种使用STLvector模板类型的参数。

void calcHist( const Mat* images, int nimages,//指向C风格数组列表的指针,同时指定包含的数组个数
const int* channels, InputArray mask,//指定哪些通道要考虑,每个数组哪些像素要考虑
OutputArray hist, int dims, const int* histSize,//直方图计算的输出值,维度,维度中的区间个数
const float** ranges, bool uniform = true, bool accumulate = false );
        //区间上下界,区间是否等长,在images得到的数据被累加进hist之前不要将其中的元素删除,重新分配,或置为零
void calcHist( const Mat* images, int nimages,
const int* channels, InputArray mask,
SparseMat& hist, int dims,//计算结果保存在稀疏矩阵中
const int* histSize, const float** ranges,
bool uniform = true, bool accumulate = false );
void calcHist( InputArrayOfArrays images,
const std::vector<int>& channels,//channel中的元素个数正是直方图的维度
InputArray mask, OutputArray hist,
const std::vector<int>& histSize,//直方图每个维度需要分为多少个区间
const std::vector<float>& ranges,//指定最矮区间的下界,最高区间的上界
bool accumulate = false );

书P339示例13-1:从图片中计算色调(hue)-饱和度(saturation)直方图,然后绘制在网格中

#include <opencv.hpp>
using namespace cv;
using namespace std;
int main() {
Mat src = imread("D:\\Backup\\桌面\\a.PNG");
Mat hsv;
cvtColor(src, hsv, COLOR_BGR2HSV); float h_ranges[] = { 0,180 };//色调,取值0-180,主要调节颜色
float s_ranges[] = { 0,256 }; //饱和度,取值0 - 255,255饱和度好,0饱和度差
const float* ranges[] = { h_ranges,s_ranges };//两个通道放一起
int histSize[] = { 30,32 };//h,s通道分别取30,32个区间
int ch[] = { 0,1 };//hsv三个通道,选前两个
Mat hist; //计算直方图
calcHist(&hsv, 1, ch, noArray(), hist, 2, histSize, ranges, true);
normalize(hist, hist, 0, 255, NORM_MINMAX);//归一化

//画出直方图
int scale = 10;//二维直方图,每个格子10*10
Mat hist_img(histSize[0] * scale, histSize[1] * scale, CV_8UC3);//所需图片尺寸300*320
for (int h = 0; h < histSize[0]; h++) {
for (int s = 0; s < histSize[1]; s++) {
float hval = hist.at<float>(h, s);//取出hist
rectangle(hist_img, Rect(h * scale, s * scale, scale, scale), Scalar::all(hval), -1);
}
}
imshow("image", src);
imshow("H-S histogram", hist_img);
waitKey();
return 0;
}

再来一个RGB的

#include <opencv.hpp>
using namespace cv;
using namespace std;
int main() {
Mat src = imread("D:\\Backup\\桌面\\a.PNG");
vector<Mat> bgrPlanes;
split(src, bgrPlanes); float range[] = { 0,256 };
const float* ranges = { range };
int histSize = 256; vector<Mat> hist(3);
vector<Scalar> color = { Scalar(255, 0, 0),Scalar(0, 255, 0),Scalar(0, 0, 255) };
Mat hist_img(histSize, histSize, CV_8UC3);//256*256的图放结果
//计算并绘制直方图
for (int i = 0; i < 3; i++) {
calcHist(&bgrPlanes[i], 1, 0, noArray(), hist[i], 1, &histSize, &ranges, true);
normalize(hist[i], hist[i], 0, 255, NORM_MINMAX);//归一化
for (int j = 1; j < histSize; j++)
{
line(hist_img, Point(j - 1, 256 - cvRound(hist[i].at<float>(j - 1))),
Point(j, 256 - cvRound(hist[i].at<float>(j))), color[i], 2);
}
}
imshow("image", src);
imshow("BGR histogram", hist_img);
waitKey();
return 0;
}

2 基本直方图操作

2.1 归一化

可以简单使用数组的代数算子和操作来完成直方图归一化:

Mat normalized = my_hist / sum(my_hist)[0];

或者:

void normalize( InputArray src, InputOutputArray dst, double alpha = 1, double beta = 0,
int norm_type = NORM_L2, int dtype = -1, InputArray mask = noArray());
void normalize( const SparseMat& src, SparseMat& dst, double alpha, int normType );

示例:

Possible usage with some positive example data:
@code{.cpp}
vector<double> positiveData = { 2.0, 8.0, 10.0 };
vector<double> normalizedData_l1, normalizedData_l2, normalizedData_inf, normalizedData_minmax; // Norm to probability (total count)
// sum(numbers) = 20.0
// 2.0 0.1 (2.0/20.0)
// 8.0 0.4 (8.0/20.0)
// 10.0 0.5 (10.0/20.0)
normalize(positiveData, normalizedData_l1, 1.0, 0.0, NORM_L1); // Norm to unit vector: ||positiveData|| = 1.0
// 2.0 0.15
// 8.0 0.62
// 10.0 0.77
normalize(positiveData, normalizedData_l2, 1.0, 0.0, NORM_L2);//对应L2范数 // Norm to max element
// 2.0 0.2 (2.0/10.0)
// 8.0 0.8 (8.0/10.0)
// 10.0 1.0 (10.0/10.0)
normalize(positiveData, normalizedData_inf, 1.0, 0.0, NORM_INF); // Norm to range [0.0;1.0]
// 2.0 0.0 (shift to left border)
// 8.0 0.75 (6.0/8.0)
// 10.0 1.0 (shift to right border)
normalize(positiveData, normalizedData_minmax, 1.0, 0.0, NORM_MINMAX);
@endcode

2.2 二值化

将直方图二值化,并可以丢弃所有其中元素个数少于个给定值的区间,和归一化相同,二值化也可以不用任何特定的直方图方法来完成。用平时常用的标准数组二值化函数即可:

threshold( my_hist , my_threshold_hist , threshold , 0 , THRESH_TOZERO);//这个0此处没用

2.3 找出最显著的区间

有时你希望能找出所有元素个数高于某个给定阈值的区间,有时你只是希望能找出有最多元素的区间。这种情况多发生在使用直方图来表示概率分布的时候。这时你可以选择使用:

  • cv::minMaxLoc()

void minMaxLoc(InputArray src, double* minVal,
double* maxVal = 0, Point* minLoc = 0,
Point* maxLoc = 0, InputArray mask = noArray());

如果不需要计算某个结果,只需向其对应的变量传入NULL。如果有一个一维的vector<>数组,则可以使用cv::Mat(vec).reshape(1)来将其转为一个Nx1的二维数组。

稀疏数组的版本:

 void minMaxLoc(const SparseMat& a, double* minVal,
double* maxVal, int* minIdx = 0, int* maxIdx = 0);

如果是想找出一个n维非稀疏数组中的最大值或是最小值,则需要使用另一个函数:

  • cv::minMaxIdx()

void minMaxIdx(InputArray src, double* minVal, double* maxVal = 0,
int* minIdx = 0, int* maxIdx = 0,
                InputArray mask = noArray());

如果输入的src是一维的,应该将minIdx,maxIdx置为二维的。 因为函数内部将一维数组视为二维数组。

int max_idx[2];
double max_val;
minMaxIdx(hist[0], NULL, &max_val, NULL, max_idx, noArray());
cout << "max_val = " << max_val << "at " << *max_idx;

2.4 比较两个直方图 cv::compareHist()

通过特殊的判据对两个直方图的相似度进行度量。

double compareHist( InputArray H1, InputArray H2, int method );
double compareHist( const SparseMat& H1, const SparseMat& H2, int method );

可用的方法如下

  • COMP_CORRL 相关性方法
  • COMP_CHISQR_ALT 卡方方法,好但慢
  • COMP_INTERSECT 交集法,在快速而粗略的匹配中效果很好
  • COMP_BHATTACHARYYA 巴氏距离,好但慢
  • EMD 最符合直觉的匹配效果,但计算速度更慢

3 复杂的直方图方法

3.1 EMD距离cv::EMD()

光照的变化会使颜色值产生大量的偏移,但这种偏移不改变颜色直方图的形状。核心的困难是对于两个形状相同、但只是相对平移的两个直方图,距离度量会给出一个很大的值。EMD是对平移不敏感的距离度量方法,基本思路是,度量将一部分(或全部)直方图搬到一个新位置要花的功夫。

float EMD( InputArray signature1, InputArray signature2,//以签名的方式传入数组参数
int distType, InputArray cost=noArray(),
float* lowerBound = 0, OutputArray flow = noArray() );

首先,在调用函数前必须要将直方图转为签名的形式。签名要求是float类型的数组,每行包括直方图区间的计数值,接下来是该区间的坐标。

参数distType可以是曼哈顿距离(DIST_L1)、欧几里得距离(DIST_L2)、棋盘距离(DIST_C)或自定义距离(DIST_USER)。当使用自定义的距离度量时,用户通过cost参数传进一个(预计算好的)费用矩阵(这时费用矩阵是一个n1xn2的矩阵,n1和n2是signature1和signature2的大小。

参数lowerBound有两个功能(一个是作为输入,另一个是作为输出)。作为返回值时,它是两个直方图重心距离的下界。 为了计算这个下界,必须提供一个标准的距离度量方法(不可以是DIST_USER) ,同时两个签名的总权重必须相同(正如直方图归一化后的情况)。 如果选择提供一个下界,你必须将该值初始化为一个有意义的值。 低于这个下界的,EMD距离才会被计算。例如,如果你想无论何种情况都计算EMD距离,将lowerBound初始化为0即可。

下一个参数flow是一个可选的n1xn2矩阵,用来记录从signature1的第i个点流向 signature2的第j个点的质量。 本质上,这给出的是在计算整个EMD中质量的具体安排情况。

在上文第一个例子色调(hue)-饱和度(saturation)直方图上用EMD方法:

1. 载入多张图:本文用三张图作比较

(image0和image1相同,image2加了滤镜)

2. 创建空签名

vector<Mat> sig(3);

3. 签名要求是float类型的数组,每行包括直方图区间的计数值,接下来是该区间的坐标

vector<Vec3f> sigv;//sigv中的而每一个元素都是,3通道float类型的 Vect(Vec3f)
sigv.push_back(Vec3f(hval, (float)h, (float)s)); //在sigv尾部插入

4. 把vector类型的sigv,变为Mat,每一行三个值。注意:局部变量函数结束堆栈会销毁,必须返回堆上的对象,利用clone深拷贝对象。

sig[i] = Mat(sigv).clone().reshape(1);

5. 全代码

#include <opencv.hpp>
using namespace cv;
using namespace std;
int main() {
vector<Mat> sig(3);
//为calcHist()准备参数
float h_ranges[] = { 0,180 };//色调,取值0-180,主要调节颜色
float s_ranges[] = { 0,256 }; //饱和度,取值0 - 255,255饱和度好,0饱和度差
const float* ranges[] = { h_ranges,s_ranges };//两个通道放一起
int histSize[] = { 30,32 };//h,s通道分别取30,32个区间
int ch[] = { 0,1 };//hsv三个通道,选前两个 //加载三张图,计算三个直方图
vector<Mat> src(3);
vector<Mat> hsv(3);
vector<Mat> hist(3);
for (int i = 0; i < 3; i++) {
src[i] = imread("D:\\Backup\\桌面\\"+to_string(i+1)+".PNG");
cvtColor(src[i], hsv[i], COLOR_BGR2HSV);
calcHist(&hsv[i], 1, ch, noArray(), hist[i], 2, histSize, ranges, true);
normalize(hist[i], hist[i], 0, 255, NORM_MINMAX);//归一化
} //画出三个直方图
int scale = 10;//二维直方图,每个格子10*10
vector<Mat> hist_img(3);
for (int i = 0; i < 3; i++) {
vector<Vec3f> sigv;
hist_img[i] = Mat(histSize[0] * scale, histSize[1] * scale, CV_8UC3);
for (int h = 0; h < histSize[0]; h++) {
for (int s = 0; s < histSize[1]; s++) {
float hval = hist[i].at<float>(h, s);
rectangle(hist_img[i], Rect(h * scale, s * scale, scale, scale), Scalar::all(hval), -1);
if (hval != 0)
sigv.push_back(Vec3f(hval, (float)h, (float
)s));
}
}
sig[i] = Mat(sigv).clone().reshape(1);
imshow("image" + to_string(i), src[i]);
imshow("H-S histogram" + to_string(i), hist_img[i]);
}
cout << EMD(sig[0], sig[2], DIST_L2);
waitKey();
return 0;
}

结果:

EMD(sig[0], sig[1], DIST_L2)=0

EMD(sig[0], sig[2], DIST_L2)=1.94666

3.2 反向投影cv::calcBackProject()

void calcBackProject( const Mat* images, int nimages,
const int* channels, InputArray hist,
OutputArray backProject, const float** ranges,
double scale = 1, bool uniform = true );
void calcBackProject( const Mat* images, int nimages,
const int* channels, const SparseMat& hist,
OutputArray backProject, const float** ranges,
double scale = 1, bool uniform = true );
void calcBackProject( InputArrayOfArrays images, const std::vector<int>& channels,
InputArray hist, OutputArray dst,
const std::vector<float>& ranges,
double scale );

示例:https://blog.csdn.net/keith_bb/article/details/70154219

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp> using namespace std;
using namespace cv; //定义全局变量
Mat srcImage, hsvImage, hueImage;
const int hueBinMaxValue = 180;
int hueBinValue = 25; //声明回调函数
void Hist_and_Backprojection(int, void*); int main()
{
srcImage = imread("E:\\解条形码\\barcode_new\\crop\\1.bmp"); //判断图像是否加载成功
if (srcImage.empty())
{
cout << "图像加载失败" << endl;
return -1;
}
else
cout << "图像加载成功..." << endl << endl; //将图像转化为HSV图像
cvtColor(srcImage, hsvImage, CV_BGR2HSV);
resize(hsvImage, hsvImage, Size(hsvImage.cols / 20, hsvImage.rows / 20));
//只使用图像的H参数
hueImage.create(hsvImage.size(), hsvImage.depth());
int ch[] = { 0,0 };
mixChannels(&hsvImage, 1, &hueImage, 1, ch, 1); //轨迹条参数设置
char trackBarName[20];
sprintf_s(trackBarName, "Hue bin:%d", hueBinMaxValue);
namedWindow("SourceImage", WINDOW_AUTOSIZE); //创建轨迹条并调用回调函数
createTrackbar(trackBarName, "SourceImage", &hueBinValue, hueBinMaxValue, Hist_and_Backprojection);
Hist_and_Backprojection(hueBinValue, 0); imshow("SourceImage", srcImage); waitKey(0); return 0;
} void Hist_and_Backprojection(int, void*)
{
MatND hist;
int histsize = MAX(hueBinValue, 2);
float hue_range[] = { 0,180 };
const float* ranges = { hue_range }; //计算图像直方图并归一化处理
calcHist(&hueImage, 1, 0, Mat(), hist, 1, &histsize, &ranges, true, false);
normalize(hist, hist, 0, 255, NORM_MINMAX, -1, Mat()); //获取反向投影
MatND backProjection;
calcBackProject(&hueImage, 1, 0, hist, backProjection, &ranges, 1, true); //输出反向投影
imshow("BackProjection", backProjection); //绘制图像直方图
int w = 400;
int h = 400;
int bin_w = cvRound((double)w / histsize);
Mat histImage = Mat::zeros(w, h, CV_8UC3);
for (int i = 0; i < hueBinValue; i++)
{
rectangle(histImage, Point(i * bin_w, h), Point((i + 1) * bin_w, h - cvRound(hist.at<float>(i) * h / 255.0)), Scalar(0, 0, 255), -1);
}
imshow("HistImage", histImage);
}

4 模板匹配cv::matchTemplate()

通过cv::matchTemplate()进行模板匹配并不基于直方图,而是使用一个图像块在输入图像上进行“滑动”,并使用下文要介绍的匹配方法来进行比较。

void matchTemplate( InputArray image, //单字节8位或浮点型灰度或彩色图片
            InputArray templ,//一个包含给定物体的(和当前图片相似的)另一张图片上取的图片块。
OutputArray result, //结果存放在result中,它是大小为(image.width-templ.width + 1, image. height -templ.height + 1)的单通道以整数字节或浮点存储的图片。
            int method, //匹配方法
            InputArray mask = noArray() );

匹配方法:

enum TemplateMatchModes {
TM_SQDIFF = 0, //方差匹配法
TM_SQDIFF_NORMED = 1, //归一化方差匹配法
TM_CCORR_NORMED = 3, //归一化互相关匹配法
TM_CCOEFF = 4, //相关系数匹配法
TM_CCOEFF_NORMED = 5 //归一化相关系数匹配法
};

一旦使用cv: :matchTemplate()获得result,我们就可以使用cv: :minMaxloc()或是cv: :minMaxidx()找到最优匹配出现的位置。 同样,为了避免随机性引起的该位置恰好匹配得很好, 我们希望确保在找到的最优匹配的邻域内也有不错的匹配结果。 好的匹配点附近也应该有很好的匹配值, 因为在进行匹配时,对模板位置进行轻微的扰动,并不会引起结果的剧烈变化。你可以在寻找最大匹配值(对于选用相关性判据或是相关系数判据)或者最小值(对于选取方差判据)前,先对结果进行轻微的平滑。 这时,形态学算子可以帮你的忙。

OpenCV笔记(9) calcHist绘制直方图的更多相关文章

  1. OpenCV笔记(4)(直方图、傅里叶变换、高低通滤波)

    一.直方图 用于统计图片中各像素值: # 画一个图像各通道的直方图 def draw_hist(img): color = ('b', 'g', 'r') for i, col in enumerat ...

  2. opencv学习笔记(六)直方图比较图片相似度

    opencv学习笔记(六)直方图比较图片相似度 opencv提供了API来比较图片的相似程度,使我们很简单的就能对2个图片进行比较,这就是直方图的比较,直方图英文是histogram, 原理就是就是将 ...

  3. Opencv笔记(十九)——直方图(一)

    直方图概念 图像的构成是有像素点构成的,每个像素点的值代表着该点的颜色(灰度图或者彩色图).所谓直方图就是对图像的中的这些像素点的值进行统计,得到一个统一的整体的灰度概念.一般情况下直方图都是灰度图像 ...

  4. numpy和matplotlib绘制直方图

    使用 Matplotlib Matplotlib 中有直方图绘制函数:matplotlib.pyplot.hist()它可以直接统计并绘制直方图.你应该使用函数 calcHist() 或 np.his ...

  5. OpenCV笔记大集锦(转载)

    整理了我所了解的有关OpenCV的学习笔记.原理分析.使用例程等相关的博文.排序不分先后,随机整理的.如果有好的资源,也欢迎介绍和分享. 1:OpenCV学习笔记 作者:CSDN数量:55篇博文网址: ...

  6. matplotlib绘制直方图【柱状图】

    代码: def drawBar(): xticks = ['A', 'B', 'C', 'D', 'E']#每个柱的下标说明 gradeGroup = {'A':200,'B':250,'C':330 ...

  7. opencv笔记6:角点检测

    time:2015年10月09日 星期五 23时11分58秒 # opencv笔记6:角点检测 update:从角点检测,学习图像的特征,这是后续图像跟踪.图像匹配的基础. 角点检测是什么鬼?前面一篇 ...

  8. opencv笔记5:频域和空域的一点理解

    time:2015年10月06日 星期二 12时14分51秒 # opencv笔记5:频域和空域的一点理解 空间域和频率域 傅立叶变换是f(t)乘以正弦项的展开,正弦项的频率由u(其实是miu)的值决 ...

  9. opencv笔记4:模板运算和常见滤波操作

    time:2015年10月04日 星期日 00时00分27秒 # opencv笔记4:模板运算和常见滤波操作 这一篇主要是学习模板运算,了解各种模板运算的运算过程和分类,理论方面主要参考<图像工 ...

  10. opencv笔记3:trackbar简单使用

    time:2015年 10月 03日 星期六 13:54:17 CST # opencv笔记3:trackbar简单使用 当需要测试某变量的一系列取值取值会产生什么结果时,适合用trackbar.看起 ...

随机推荐

  1. vscode 编辑python 如何格式化

    正文 今天同事说我的代码的格式不对,其实就是几个空格忘了空4格了,但是代码可运行. 那么如何帮我们检测呢? pip install yapf 然后打开setting: 输入:ython.formatt ...

  2. Vue 数据更新但页面没有更新的 7 种情况,你遇到过几种

    1. Vue 无法检测实例被创建时不存在于 data 中的 property 原因:由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property ...

  3. 3.CSS三种基本选择器

    三种选择器的优先级: id选择器 > class选择器 > 标签选择器 1.标签选择器:会选择到页面上所有的该类标签的元素 格式: 标签{} 1 <!DOCTYPE html> ...

  4. 力扣378(java&python)-有序矩阵中第 K 小的元素(中等)

    题目: 给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素.请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素. 你必须找到一个 ...

  5. 第五章:SQL高级处理

    第五章:SQL高级处理 5.1 窗口函数 5.1.1 窗口函数概念及基本的使用方法 窗口函数也称为OLAP函数.OLAP 是 OnLine AnalyticalProcessing 的简称,意思是对数 ...

  6. 高效使用Java构建工具|Maven篇|云效工程师指北

    ​简介:高效使用Java构建工具|Maven篇.众所周知,当前最主流的Java构建工具为Maven/Gradle/Bazel,针对每一个工具,我将分别从日常工作中常见的场景问题切入,例如依赖管理.构建 ...

  7. Fluid — 云原生环境下的高效“数据物流系统”

    简介: 为了解决大数据.AI 等数据密集型应用在云原生计算存储分离场景下,存在的数据访问延时高.联合分析难.多维管理杂等痛点问题,南京大学 PASALab.阿里巴巴.Alluxio 在 2020 年 ...

  8. 几种Java常用序列化框架的选型与对比

    简介: 序列化与反序列化是我们日常数据持久化和网络传输中经常使用的技术,但是目前各种序列化框架让人眼花缭乱,不清楚什么场景到底采用哪种序列化框架.本文会将业界开源的序列化框架进行对比测试,分别从通用性 ...

  9. Flink 在 58 同城的应用与实践

    ​简介: 58 同城的实时 SQL 建设以及如何从 Storm 迁移至 Flink. 本文整理自 58 同城实时计算平台负责人冯海涛在 Flink Forward Asia 2020 分享的议题< ...

  10. [Go] CORS 支持多个 origin 访问的思路 (Access-Control-Allow-Origin 部分)

    以下为局部伪代码,仅供参考: var allowOrigin string allowOrigins := config.AppConf.Get("middleware.cors.allow ...