OpenCV开发笔记(四十七):红胖子8分钟带你深入了解直方图(图文并茂+浅显易懂+程序源码)

Penny ·
更新时间:2024-11-01
· 998 次阅读

若该文为原创文章,未经允许不得转载
原博主博客地址: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万+ 关注 他的留言板 关注博主即可阅读全文
作者:红胖子(红模仿)



直方图 opencv 源码 程序

需要 登录 后方可回复, 如果你还没有账号请 注册新账号