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

若该文为原创文章,未经允许不得转载
原博主博客地址: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<uchar>(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<int>& channels,
               InputArray mask,
               OutputArray hist,
               const std::vector<int>& histSize,
               const std::vector<float>& 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<float>(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<float>(index)),
                         cv::Scalar(0, 0, 255));

                // 右上角,三色统计图
                cv::circle(allMat,
                           cv::Point(15 + index, srcMat.rows - dstHistRed.at<float>(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<float>(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<float>(index)),
                         cv::Scalar(0, 255, 0));

                // 右上角,三色统计图
                cv::circle(allMat,
                           cv::Point(15 + index, srcMat.rows - dstHistGreen.at<float>(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<float>(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<float>(index)),
                         cv::Scalar(255, 0, 0));
                // 右上角,三色统计图
                cv::circle(allMat,
                           cv::Point(15 + index, srcMat.rows - dstHistBlue.at<float>(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万+

猜你喜欢

转载自blog.csdn.net/qq21497936/article/details/105797267
今日推荐