• 【图像处理】基于OpenCV实现图像直方图的原理


    背景

    图像的直方图是衡量图像像素分布的一种方式,可以通过分析像素分布,使用直方图均衡化对图像进行优化,让图像变的清晰。

    opencv官方对图像直方图的定义如下:

    • 直方图是图像中像素强度分布的图形表达方式.
    • 它统计了每一个强度值所具有的像素个数.

    一、直方图计算的原理

    一副图像实际上就是一个数字矩阵。

    3x3的灰度图像由9个像素组成,每个像素都取值0-255中的一个值,0表示黑色,255表示白色,中间值是介于黑色和白色之间的灰度值。

    如下以一个高度为3,宽度为3的图片为例说明直方图的计算。

    • 定义一个255大小的数组,用于保存灰度值出现的次数
    • 遍历图像的每一个元素,将像素的灰度值出现的次数统计到对应的灰度次数中
    • 将灰度值次数统计数组进行归一化处理(归一化到0-255范围内,便于绘图使用)
    • 将归一化的灰度次数进行绘图展示

    如下图是计算直方图的过程。

    二、直方图计算步骤

    根据直方图计算的原理,如下我们就开始动手写一个计算图像直方图代码实现。

    1. 加载图像

    加载图像,并显示

        cv::Mat rawImage = cv::imread("demo1/leopard2.png", cv::IMREAD_ANYCOLOR);
        cv::imshow("rawImage", rawImage);
    

    图像显示图像(我喜欢的那个小豹子)

    2. 定义统计图像三个通道灰度值出现次数和归一化数的数组

    定义并初始化次数数组,按照灰度值255,用于统计每个像素灰度值出现的次数。

            int histSize = 255;
            int histValues[3][255] = {};
            int histNormalizeValues[3][255] = {};
            for (int k = 0; k < histSize; ++k) {
                histValues[0][k] = 0;
                histValues[1][k] = 0;
                histValues[2][k] = 0;
                histNormalizeValues[0][k] = 0;
                histNormalizeValues[1][k] = 0;
                histNormalizeValues[2][k] = 0;
            }
    

    3. 遍历图像,计算三个通道灰度值出现的次数

    彩色图像由BGR三个通道构成,分别计算统计这三个通道的灰度值次数

           cv::Vec3b rgbPixel;
            // 遍历图像,统计BGR三个通道的图像的灰度值出现的次数
            for (int i = 0; i < rgbImage.rows; ++i) {
                for (int j = 0; j < rgbImage.cols; ++j) {
                    // B G R
                    rgbPixel = rgbImage.at<cv::Vec3b>(i, j);
                    histValues[2][rgbPixel[2]] += 1;
                    histValues[1][rgbPixel[1]] += 1;
                    histValues[0][rgbPixel[0]] += 1;
                }
            }
    

    4. 将上一步图像灰度值次数归一化到0-255之间

    归一化方法的算法见之前的文章 https://www.cnblogs.com/voipman/p/5046153.html

            // 把如上的统计值归一化到0-255范围内
            calcNormalize(histValues[0], histNormalizeValues[0]);
            calcNormalize(histValues[1], histNormalizeValues[1]);
            calcNormalize(histValues[2], histNormalizeValues[2]);
    

    归一化代码实现

        /**
         * 计算一个数组的归一化,此处归一化到0-255之间
         * @param srcValues
         * @param dstValues
         */
        void calcNormalize(int srcValues[255], int dstValues[255]) {
            int minValue = srcValues[0];
            int maxValue = srcValues[0];
    
            for (int i = 1; i < 255; ++i) {
                if (minValue > srcValues[i]) {
                    minValue = srcValues[i];
                }
                if (maxValue < srcValues[i]) {
                    maxValue = srcValues[i];
                }
            }
            int minMaxDiff = maxValue - minValue;
            for (int j = 0; j < 255; ++j) {
                dstValues[j] = static_cast<int>((float)(srcValues[j] - minValue) / (float)minMaxDiff * 255.);
            }
        }
    

    5. 绘制直方图到页面

    如下划线代码逻辑是画出3条线,分别是蓝绿红三条,每一条线连接前后两个点,依次连接0-254点形成对应的线。

            // 创建直方图画布
            int hist_w = 400; int hist_h = 400;
            int bin_w = cvRound( (double) hist_w/histSize );
    
            cv::Mat histImage( hist_w, hist_h, CV_8UC3, cv::Scalar( 255,255,255) );
            // 把三个通道的直方图归一化数据绘制在直方图上
            for (int i = 1; i < histSize; ++i) {
                cv::line(histImage,
                        cv::Point(bin_w * (i-1), hist_h - cvRound(histNormalizeValues[0][i-1])),
                        cv::Point(bin_w * (i), hist_h - cvRound(histNormalizeValues[0][i])),
                        cv::Scalar(0, 0, 255), 2,cv::LINE_AA, 0);
                cv::line(histImage,
                        cv::Point(bin_w * (i-1), hist_h - cvRound(histNormalizeValues[1][i-1])),
                        cv::Point(bin_w * (i), hist_h - cvRound(histNormalizeValues[1][i])),
                        cv::Scalar(0, 255, 0), 2,cv::LINE_AA, 0);
                cv::line(histImage,
                        cv::Point(bin_w * (i-1), hist_h - cvRound(histNormalizeValues[2][i-1])),
                        cv::Point(bin_w * (i), hist_h - cvRound(histNormalizeValues[2][i])),
                        cv::Scalar(255, 0, 0), 2,cv::LINE_AA, 0);
            }
            cv::imshow("histImage", histImage);
    

    绘图中的绘线逻辑如下图中的绿线线段所示(连接前后两个点形成对应的线段):

    6. 绘制直方图显示

    直方图结果解析和说明:

    • 从这个直方图可以看出原始图像三个通道的数据都比较集中
    • 红色通道的数据集中在中间130左右,太黑和太白的数据比较少。
    • 绿色通道的数据集中在180左右,两边数据比较少。
    • 蓝色通道的数据集中在210作用的数值内,黑色的数据很少。

    图像优化

    使用直方图均衡化算法对图像进行均衡处理

        void EqualizeHist(cv::Mat &rgbImage) {
            std::vector<cv::Mat> rgbImages;
            cv::split(rgbImage, rgbImages);
            /// 应用直方图均衡化
    
            cv::Mat dstR, dstG, dstB;
            equalizeHist(rgbImages[0], dstB);
            equalizeHist(rgbImages[1], dstG);
            equalizeHist(rgbImages[2], dstR);
    
            std::vector<cv::Mat> grayHistImages;
            grayHistImages.push_back(dstB);
            grayHistImages.push_back(dstG);
            grayHistImages.push_back(dstR);
            cv::merge(grayHistImages, rgbImage);
        }
    

      

    对图像做了直方图均衡化处理后的效果如下:

    图像分析:

    • 图像看起来黑白分明,小豹子图像很清晰。

    经过直方图均衡化处理后的图像,重新计算直方图,观察灰度值分布

    图像分析:

    • 均衡化后的直方图均匀的分布在0-255之间。

    OpenCV提供了一个简单的计算数组集(通常是图像或分割后的通道)的直方图,步骤如下

    • cv::split拆分图像到多个通道
    • 使用计算直方图函数 calcHist 计算图像的直方图
    • 使用函数 cv::normalize 归一化数组
    • 使用cv::line绘制直方图

    参考材料:

    opencv直方图均衡化处理 

    opencv直方图计算

    如下完整代码见 https://github.com/gityf/img-video/blob/master/opencv/hist.hpp

    done.

    祝玩的开心~

  • 相关阅读:
    2019年下半年学习总结
    要看的积累
    【长期积累】Java
    【长期积累】数据库
    一些知识总结
    一些小总结
    优秀前端框架Semantic UI
    windows nodejs express的安装
    形象的讲解angular中的$q与promise(转)
    centos6.5 install mongodb
  • 原文地址:https://www.cnblogs.com/voipman/p/15380707.html
Copyright © 2020-2023  润新知