乞丐哥的私房菜(Ubuntu OpenCV篇——Image Processing 节 之 Histogram Calculation 直方图计算)二十二

  • 操作系统:手机安装 Termux ,Termux 内安装 ubuntu
    • Termux 0.119.0-beta.3
    • 内嵌 ubuntu 24.04
  • 编译器:GNU g++ 14.2.0
  • 编辑器:Emacs 29.3
  • OpenCV:4.13.0

目标

  • 使用 OpenCV 函数 cv::split 将图像划分为其对应的平/层面
  • 使用 OpenCV 函数 cv::calcHist 计算图像数组的直方图
  • 使用函数 cv::normalize 规范化数组

理论

再谈什么是直方图?

  • 直方图是收集的数据计数,组织到一组预定义的箱中
  • 当我们说数据时,我们并没有将其限制为强度值(正如我们在之前的教程直方图均衡中看到的那样)。收集的数据可以是您认为对描述图像有用的任何特征。
  • 让我们看一个例子。想象一下,矩阵包含图像信息(即范围内的强度(0-255)):
    在这里插入图片描述
  • 如果我们想以有组织的方式计算这些数据会发生什么?由于我们知道这种情况下的信息值范围是 256 个值,因此我们可以将范围分割为子部分(称为箱),如下所示:
    [0,255]=[0,15]∪[16,31]∪....∪[240,255]range=bin1∪bin2∪....∪binn=15 \begin{array}{l} [0, 255] = { [0, 15] \cup [16, 31] \cup ....\cup [240,255] } \\ range = { bin_{1} \cup bin_{2} \cup ....\cup bin_{n = 15} } \end{array} [0,255]=[0,15][16,31]....[240,255]range=bin1bin2....binn=15
  • 我们可以计算落在每个范围内的像素数 binibin_ibini 。将其应用于上面的示例,我们得到下图(轴 x 代表箱,轴 y 代表每个箱中的像素数)
    在这里插入图片描述
  • 这只是直方图如何工作以及为什么它有用的一个简单示例。直方图不仅可以计算颜色强度,还可以计算我们想要测量的任何图像特征(即渐变、方向等)
  • 让我们确定直方图的某些部分:
    1. dims:要收集数据的参数数。在我们的示例中,dims = 1,因为我们只计算每个像素的强度值(在灰度图像中)
    2. bins:它是每个 dim 中的细分数。在我们的示例中,bins = 16
    3. range:要测量的值的限制。在本例中:范围 = [0,255]

OpenCV 为您提供什么?

出于简单的目的,OpenCV 实现了函数 cv::calcHist ,它计算一组数组(通常是图像或图像平面)的直方图。它最多可以在 32 个尺寸下运行。我们将在下面的代码中看到它

源代码

  #include "opencv2/highgui.hpp"
  #include "opencv2/imgcodecs.hpp"
  #include "opencv2/imgproc.hpp"
  #include <iostream>

  using namespace std;
  using namespace cv;

  int main(int argc, char** argv)
  {
    CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" );
    Mat src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR );
    if( src.empty() )
      {
        return EXIT_FAILURE;
      }

    vector<Mat> bgr_planes;
    split( src, bgr_planes );

    int histSize = 256;

    float range[] = { 0, 256 }; //the upper boundary is exclusive
    const float* histRange[] = { range };

    bool uniform = true, accumulate = false;

    Mat b_hist, g_hist, r_hist;
    calcHist( &bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, histRange, uniform, accumulate );
    calcHist( &bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, histRange, uniform, accumulate );
    calcHist( &bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, histRange, uniform, accumulate );

    int hist_w = 512, hist_h = 400;
    int bin_w = cvRound( (double) hist_w/histSize );

    Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );

    normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
    normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
    normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );

    for( int i = 1; i < histSize; i++ )
      {
        line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1)) ),
              Point( bin_w*(i), hist_h - cvRound(b_hist.at<float>(i)) ),
              Scalar( 255, 0, 0), 2, 8, 0  );
        line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1)) ),
              Point( bin_w*(i), hist_h - cvRound(g_hist.at<float>(i)) ),
              Scalar( 0, 255, 0), 2, 8, 0  );
        line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) ),
              Point( bin_w*(i), hist_h - cvRound(r_hist.at<float>(i)) ),
              Scalar( 0, 0, 255), 2, 8, 0  );
      }

    imshow("Source image", src );
    imshow("calcHist Demo", histImage );
    waitKey();

    return EXIT_SUCCESS;
  }

解释说明

  • 加载图像
      CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" );
      Mat src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR );
      if( src.empty() )
        {
          return EXIT_FAILURE;
        }
    
  • 将源图像的三个 R、G 和 B 平面分开。为此,我们使用 OpenCV 函数 cv::split :
      vector<Mat> bgr_planes;
      split( src, bgr_planes );
    
  • 现在我们准备好开始为每个平面配置直方图。由于我们正在使用 B、G 和 R 平面,因此我们知道我们的值将在区间内
  • 确定箱数 (5, 10…):
      int histSize = 256;
    
  • 设置值范围
   float range[] = { 0, 256 }; //the upper boundary is exclusive
   const float* histRange[] = { range };
  • 我们希望我们的箱具有相同的大小(统一),并在开始时清除直方图,因此:
      bool uniform = true, accumulate = false;
    
  • 我们继续使用 OpenCV 函数 cv::calcHist 计算直方图:
      Mat b_hist, g_hist, r_hist;
      calcHist( &bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, histRange, uniform, accumulate );
      calcHist( &bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, histRange, uniform, accumulate );
      calcHist( &bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, histRange, uniform, accumulate );
    
  • 创建图像以显示直方图:
      int hist_w = 512, hist_h = 400;
      int bin_w = cvRound( (double) hist_w/histSize );
      Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
    
  • 请注意,在绘制之前,我们首先对直方图进行 cv::normalize ,使其值落在输入的参数指示的范围内:
      normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1Mat() );
      normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1Mat() );
      normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1Mat() );
    
  • 注意,要访问 bin
      for( int i = 1; i < histSize; i++ )
        {
          line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1)) ),
                Point( bin_w*(i), hist_h - cvRound(b_hist.at<float>(i)) ),
                Scalar( 255, 0, 0), 2, 8, 0  );
          line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1)) ),
                Point( bin_w*(i), hist_h - cvRound(g_hist.at<float>(i)) ),
                Scalar( 0, 255, 0), 2, 8, 0  );
          line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) ),
                Point( bin_w*(i), hist_h - cvRound(r_hist.at<float>(i)) ),
                Scalar( 0, 0, 255), 2, 8, 0  );
        }
    
  • 最后,我们显示直方图并等待用户退出:
      imshow("Source image", src );
      imshow("calcHist Demo", histImage );
      waitKey();
    

结果

  • 源图像
    在这里插入图片描述
  • 生成的直方图
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值