若该文为原创文章,未经允许不得转载
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105797267
各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究
目录
前言
相关博客
Demo
直方图
概述
原理
编码一个通道的直方图计算代码
重载函数原型1
重载函数原型2(参照函数原型1)
重载函数原型3(参照函数原型1)
归一化
概述
函数原型
Demo源码
工程模板:对应版本号v1.42.0
红胖子(红模仿)的博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中...(点击传送门)
OpenCV开发专栏(点击传送门) OpenCV开发笔记(四十七):红胖子8分钟带你深入了解直方图(图文并茂+浅显易懂+程序源码) 前言红胖子来也!!!
在之前接触的相机录像中,遇到过白平衡,白平衡其实就是调整整个界面的亮度平衡,达到一个对比度合适的过程,原理其实就是直方图均衡化,在这之前先理解直方图的概念。
单独深入理解直方图,是因为直方图不仅在静态图均衡化的时候有作用,在后面的识别和跟踪等更多的过程中,部分算法都需要直方图,比如相邻帧间的直方图分析对比等等,是后续动态视频识别处理的一个重要概念。
相关博客
关于直方图,其实在经典OTSU中的代码实现了计算,OTSU算法阈值化其实就是使用灰度图结合方差,计算出一个合适的阈值,使用该阈值做阈值化。
《OpenCV开发笔记(三十):带你学习图像识别之经典OTSU算法阈值化》
Demo
直方图 概述
直方图是一种对数据分布情况的图形表示,是一种二维统计图表,有两个坐标,按照当前图像出来来说,主要是氛围属性和值,属性可理解为灰度级(从0到255),值可理解为分布的数量(概率,该等级的灰度点数量占所有数量中的点比例),所以:
直方图是图像中像素强度分布的图形表达方式;
统计了每一个属性值的个数;
原理(注意:可以各种彩色向量都可以做直方图处理,此处以灰度图举例)
计算灰度级中每个像素在整幅图像中的个数;
编码一个通道的直方图计算代码
int grayScale[256] = {0}; // 每个灰度级所占像素的
double grayPro[256] = {0}; // 每个灰度级所占像素比例: 为 该像素出现的次数/总像素
// 步骤一:计算灰度级中每个像素在整幅图像中的个数;灰度级8位为256级别0~255
for(int row = 0; row < srcMat.rows; row++)
{
for(int col = 0; col < srcMat.cols; col++)
{
grayScale[srcMat.at(row, col)]++;
}
}
重载函数原型1
void calcHist( const Mat* images,
int nimages,
const int* channels,
InputArray mask,
OutputArray hist,
int dims,
const int* histSize,
const float** ranges,
bool uniform = true,
bool accumulate = false );
参数一:const Mat*类型的images,它们都应该具有相同的深度,CV_8U、CV_16U或CV_32F类型尺寸是相同的大小。每一个image都可以有任意数量的通道;
参数二:int类型的nimages,参数一输图像的数量;
参数三:const int *类型的channels,用于计算直方图的dims通道的通道列表。第一个阵列通道从0计算到images [0].channels()-1,第二个数组通道从images[0].channels到images[0].channels()+images[1].channels()-1,依此类推;
参数四:InputArray类型的mask,如果矩阵不是空的,它必须是相同大小的8位数组作为image[i]。非零掩码元素用于标记出统计直方图的数组元素数据。
参数五:OuputArray类型的hist,hist输出直方图,是一个密集或稀疏的dims维数组;
参数六:int类型的dims,需要统计的特征的数目,dims直方图维数必须为正且不大于CV_MAX_DIMS(在当前的OpenCV3.4.0版本中等于32);
参数七:const int *类型的histSize,在每一维上直方图的个数。简单把直方图看作一个一个的竖条的话,就是每一维上竖条的个数;
参数八:const float**类型的ranges,在每个维度中直方图边界的dims数组的范围数组,如灰度图则是range=[0,255],可以理解为每一维数值的取值范围;
参数九:bool类型的uniform,指示直方图是否一致的统一标志,默认值为true;
参数十:bool类型的accumulate,累加标志。如果设置了直方图,则不会在开始时清除它,默认值为false;
重载函数原型2(参照函数原型1)
较函数1重载了参数5输出类型,由OutputArray改为SparseMat;
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 );
重载函数原型3(参照函数原型1)
重载了输入和输出的类型,为stl版本的,本身vector带了数量,因此少了几个参数;
void calcHist( InputArrayOfArrays images,
const std::vector& channels,
InputArray mask,
OutputArray hist,
const std::vector& histSize,
const std::vector& ranges,
bool accumulate = false );
归一化
概述
归一化是指对矩阵cv::Mat进行归一化操作。
归一化是一种无量纲处理手段,使物理系统数值的绝对值变成某种相对值关系。简化计算,缩小量值的有效办法。 例如,滤波器中各个频率值以截止频率作归一化后,频率都是截止频率的相对值,没有了量纲。阻抗以电源内阻作归一化后,各个阻抗都成了一种相对阻抗值,“欧姆”这个量纲也没有了。等各种运算都结束后,反归一化一切都复原了。信号处理工具箱中经常使用的是nyquist频率,它被定义为采样频率的二分之一,在滤波器的阶数选择和设计中的截止频率均使用nyquist频率进行归一化处理。例如对于一个采样频率为500hz的系统,400hz的归一化频率就为400/500=0.8,归一化频率范围在[0,1]之间。
函数原型
void normalize( InputArray src,
InputOutputArray dst,
double alpha = 1,
double beta = 0,
int norm_type = NORM_L2,
int dtype = -1,
InputArray mask = noArray());
参数一:InputArray类型的src,一般为mat;
参数二:InputOutputArray类型的dst,一般为mat,大小与src一样;
参数三:double类型的alpha,归一化的最大值,默认值1;
参数四:double类型的beta,归一化的最大值,默认值0;
参数五:int类型的norm_type,归一化类型,具体查看cv::NormTypes,默认为;
参数六:int类型的dtype,默认值-1,负数时,其输出矩阵与src类型相同,否则它和src有同样的通道数,且此时图像深度为CV_MAT_DEPTH。
参数七:InputArray类型的mask,可选的操作掩膜,默认值为noArray();
Demo源码
void OpenCVManager::testCalcHist()
{
QString fileName1 =
"E:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/2.jpg";
cv::Mat srcMat = cv::imread(fileName1.toStdString());
cv::Mat dstMat;
int width = 400;
int height = 300;
cv::resize(srcMat, srcMat, cv::Size(width, height));
cv::String windowName = _windowTitle.toStdString();
cvui::init(windowName);
cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2,
srcMat.rows * 4),
srcMat.type());
cv::Mat allMat = cv::Mat(srcMat.rows, srcMat.cols, srcMat.type());
allMat = cv::Scalar(0, 0, 0);
while(true)
{
// 刷新全图黑色
windowMat = cv::Scalar(0, 0, 0);
// 原图复制
cv::Mat mat = windowMat(cv::Range(srcMat.rows * 0, srcMat.rows * 1),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::addWeighted(mat, 0.0f, srcMat, 1.0f, 0.0f, mat);
// 计算直方图
{
// 直方图存放,需要有东西,所以使用cv::MatND,其等于createHist
cv::MatND dstHistRed;
// 计算通道0,1,2(brg三个通道)
int channels[] = {2};
// 直方图的条数
int hueBinNum = 256;
int histSize[] = {hueBinNum};
// 变化范围
float range[] = {0, 256};
const float *ranges[] = {range};
// brg三个
cv::calcHist(&srcMat, // 只有1个mat
1, // 只有1个mat
channels, // 只有1个mat的3个通道,bgr
cv::Mat(), // 不使用掩码
dstHistRed, // 输出的目标直方图
1, // 计算直方图的维度
histSize, // 每个维度的直方图条数(例如灰度为一维,多少条)
ranges, // 每个维度的范围
true, // 直方图是否均匀
false); // 累计标识符,false表示直方图在配置阶段会被清零
dstMat = cv::Mat(srcMat.rows, srcMat.cols, srcMat.type());
dstMat = cv::Scalar(0, 0, 0);
for(int index = 0; index < hueBinNum; index++)
{
cv::line(dstMat,
cv::Point(15 + index, srcMat.rows),
cv::Point(15 + index, srcMat.rows - dstHistRed.at(index)),
cv::Scalar(0, 0, 255));
}
// 原图复制
cv::Mat mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::addWeighted(mat, 0.0f, dstMat, 1.0f, 0.0f, mat);
// 计算出最大的,进行归一化操作
cv::normalize(dstHistRed, dstHistRed, 0, srcMat.rows, cv::NORM_MINMAX, -1, cv::Mat());
dstMat = cv::Mat(srcMat.rows, srcMat.cols, srcMat.type());
dstMat = cv::Scalar(0, 0, 0);
// 此处并没有进行归一化操作,像素总共为300*200个点
// 如果全是红色,则该店的数据能得到60000个点,实际显示图片的高度为300
for(int index = 0; index < hueBinNum; index++)
{
cv::line(dstMat,
cv::Point(15 + index, srcMat.rows),
cv::Point(15 + index, srcMat.rows - dstHistRed.at(index)),
cv::Scalar(0, 0, 255));
// 右上角,三色统计图
cv::circle(allMat,
cv::Point(15 + index, srcMat.rows - dstHistRed.at(index)),
1,
cv::Scalar(0, 0, 255));
}
// 原图复制
mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
cv::Range(srcMat.cols * 1, srcMat.cols * 2));
cv::addWeighted(mat, 0.0f, dstMat, 1.0f, 0.0f, mat);
}
// 计算直方图
{
// 直方图存放,需要有东西,所以使用cv::MatND,其等于createHist
cv::MatND dstHistGreen;
// 计算通道0,1,2(brg三个通道)
int channels[] = {1};
// 直方图的条数
int hueBinNum = 256;
int histSize[] = {hueBinNum};
// 变化范围
float range[] = {0, 256};
const float *ranges[] = {range};
// brg三个
cv::calcHist(&srcMat, // 只有1个mat
1, // 只有1个mat
channels, // 只有1个mat的3个通道,bgr
cv::Mat(), // 不使用掩码
dstHistGreen, // 输出的目标直方图
1, // 计算直方图的维度
histSize, // 每个维度的直方图条数(例如灰度为一维,多少条)
ranges, // 每个维度的范围
true, // 直方图是否均匀
false); // 累计标识符,false表示直方图在配置阶段会被清零
dstMat = cv::Mat(srcMat.rows, srcMat.cols, srcMat.type());
dstMat = cv::Scalar(0, 0, 0);
for(int index = 0; index < hueBinNum; index++)
{
cv::line(dstMat,
cv::Point(15 + index, srcMat.rows),
cv::Point(15 + index, srcMat.rows - dstHistGreen.at(index)),
cv::Scalar(0, 255, 0));
}
// 原图复制
cv::Mat mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::addWeighted(mat, 0.0f, dstMat, 1.0f, 0.0f, mat);
// 计算出最大的,进行归一化操作
cv::normalize(dstHistGreen, dstHistGreen, 0, srcMat.rows, cv::NORM_MINMAX, -1, cv::Mat());
dstMat = cv::Mat(srcMat.rows, srcMat.cols, srcMat.type());
dstMat = cv::Scalar(0, 0, 0);
for(int index = 0; index < hueBinNum; index++)
{
cv::line(dstMat,
cv::Point(15 + index, srcMat.rows),
cv::Point(15 + index, srcMat.rows - dstHistGreen.at(index)),
cv::Scalar(0, 255, 0));
// 右上角,三色统计图
cv::circle(allMat,
cv::Point(15 + index, srcMat.rows - dstHistGreen.at(index)),
1,
cv::Scalar(0, 255, 0));
}
// 原图复制
mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
cv::Range(srcMat.cols * 1, srcMat.cols * 2));
cv::addWeighted(mat, 0.0f, dstMat, 1.0f, 0.0f, mat);
}
// 计算直方图
{
// 直方图存放,需要有东西,所以使用cv::MatND,其等于createHist
cv::MatND dstHistBlue;
// 计算通道0,1,2(brg三个通道)
int channels[] = {0};
// 直方图的条数
int hueBinNum = 256;
int histSize[] = {hueBinNum};
// 变化范围
float range[] = {0, 256};
const float *ranges[] = {range};
// brg三个
cv::calcHist(&srcMat, // 只有1个mat
1, // 只有1个mat
channels, // 只有1个mat的3个通道,bgr
cv::Mat(), // 不使用掩码
dstHistBlue, // 输出的目标直方图
1, // 计算直方图的维度
histSize, // 每个维度的直方图条数(例如灰度为一维,多少条)
ranges, // 每个维度的范围
true, // 直方图是否均匀
false); // 累计标识符,false表示直方图在配置阶段会被清零
dstMat = cv::Mat(srcMat.rows, srcMat.cols, srcMat.type());
dstMat = cv::Scalar(0, 0, 0);
// 此处并没有进行归一化操作,像素总共为300*200个点
// 如果全是红色,则该店的数据能得到60000个点,实际显示图片的高度为300
for(int index = 0; index < hueBinNum; index++)
{
cv::line(dstMat,
cv::Point(15 + index, srcMat.rows),
cv::Point(15 + index, srcMat.rows - dstHistBlue.at(index)),
cv::Scalar(255, 0, 0));
}
// 原图复制
cv::Mat mat = windowMat(cv::Range(srcMat.rows * 3, srcMat.rows * 4),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::addWeighted(mat, 0.0f, dstMat, 1.0f, 0.0f, mat);
// 计算出最大的,进行归一化操作
cv::normalize(dstHistBlue, dstHistBlue, 0, srcMat.rows, cv::NORM_MINMAX, -1, cv::Mat());
dstMat = cv::Mat(srcMat.rows, srcMat.cols, srcMat.type());
dstMat = cv::Scalar(0, 0, 0);
// 此处并没有进行归一化操作,像素总共为300*200个点
// 如果全是红色,则该店的数据能得到60000个点,实际显示图片的高度为300
for(int index = 0; index < hueBinNum; index++)
{
cv::line(dstMat,
cv::Point(15 + index, srcMat.rows),
cv::Point(15 + index, srcMat.rows - dstHistBlue.at(index)),
cv::Scalar(255, 0, 0));
// 右上角,三色统计图
cv::circle(allMat,
cv::Point(15 + index, srcMat.rows - dstHistBlue.at(index)),
1,
cv::Scalar(255, 0, 0));
}
// 原图复制
mat = windowMat(cv::Range(srcMat.rows * 3, srcMat.rows * 4),
cv::Range(srcMat.cols * 1, srcMat.cols * 2));
cv::addWeighted(mat, 0.0f, dstMat, 1.0f, 0.0f, mat);
}
// 原图复制
mat = windowMat(cv::Range(srcMat.rows * 0, srcMat.rows * 1),
cv::Range(srcMat.cols * 1, srcMat.cols * 2));
cv::addWeighted(mat, 0.0f, allMat, 1.0f, 0.0f, mat);
// 更新
cvui::update();
// 显示
cv::imshow(windowName, windowMat);
// esc键退出
if(cv::waitKey(25) == 27)
{
break;
}
}
}
工程模板:对应版本号v1.42.0
对应版本号v1.42.0
.
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105797267
红胖子(红模仿)
博客专家
原创文章 275获赞 499访问量 57万+
关注
他的留言板
关注博主即可阅读全文
作者:红胖子(红模仿)