OpenCV直方圖計(jì)算

2018-09-19 10:49 更新

目標(biāo)

在本教程中,您將學(xué)習(xí)如何:

  • 使用OpenCV函數(shù)cv :: split將圖像分割成對(duì)應(yīng)的平面。
  • 使用OpenCV函數(shù)cv :: calcHist來(lái)計(jì)算圖像數(shù)組的直方圖
  • 使用函數(shù)cv :: normalize對(duì)數(shù)組進(jìn)行歸一化
注意
在上一個(gè)教程(直方圖均衡)中,我們討論了一種稱為“ 圖像直方圖”的特定類型的直方圖?,F(xiàn)在我們將在其更一般的概念中思考。

什么是直方圖?

  • 直方圖是將數(shù)據(jù)組織成一組預(yù)定義倉(cāng)的收集計(jì)數(shù)
  • 當(dāng)我們說(shuō)數(shù)據(jù)時(shí),我們并不把它限制為強(qiáng)度值(正如我們?cè)谇懊娴慕坛讨锌吹降模J占臄?shù)據(jù)可以是您發(fā)現(xiàn)有用的描述您的圖像的任何功能。
  • 我們來(lái)看一個(gè)例子。假設(shè)矩陣包含圖像的信息(即范圍內(nèi)的強(qiáng)度):0?255

OpenCV直方圖計(jì)算

  • 如果我們想以有組織的方式計(jì)算這些數(shù)據(jù)會(huì)怎么樣?由于我們知道這種情況下的信息值范圍是256個(gè)值,所以我們可以在子部分(稱為bins)中劃分我們的范圍,如:

OpenCV直方圖計(jì)算

我們可以保持落在每個(gè)范圍內(nèi)的像素?cái)?shù)量。將其應(yīng)用于上述示例,我們得到下面的圖像(x軸表示bins和軸y表示每個(gè)像素的數(shù)目)。

OpenCV直方圖計(jì)算

  • 這只是一個(gè)簡(jiǎn)單的直方圖的例子,為什么它是有用的。直方圖不僅可以保持顏色強(qiáng)度的計(jì)數(shù),還可以保持我們想要測(cè)量的任何圖像特征(即梯度,方向等)。
  • 我們來(lái)確定直方圖的一些部分:
  1. dims:要收集數(shù)據(jù)的參數(shù)數(shù)。在我們的示例中,dims = 1,因?yàn)槲覀冎挥?jì)算每個(gè)像素的強(qiáng)度值(在灰度圖像中)。
  2. bins:每個(gè)暗淡的細(xì)分?jǐn)?shù)。在我們的例子中,bin = 16
  3. range:要測(cè)量的值的限制。在這種情況下:range = [0,255]

如果你想計(jì)算兩個(gè)功能怎么辦?在這種情況下,您生成的直方圖將是一個(gè)3D圖(其中x和y將為和  ,而z將每個(gè)組合的計(jì)數(shù)。這同樣適用于更多的功能(當(dāng)然會(huì)變得更棘手)。

OpenCV為您提供什么

為了簡(jiǎn)單起見(jiàn),OpenCV實(shí)現(xiàn)了函數(shù)cv :: calcHist,它計(jì)算一組數(shù)組(通常是圖像或圖像平面)的直方圖。它最多可以操作32個(gè)維度。我們將在下面的代碼中看到它!

Code

  • 這個(gè)程序是做什么的?加載圖像使用函數(shù)cv :: split將圖像分解為R,G和B平面通過(guò)調(diào)用函數(shù)cv :: calcHist計(jì)算每個(gè)1通道平面的直方圖在窗口中繪制三個(gè)直方圖
  • 可下載的代碼:點(diǎn)擊這里
  • 代碼一覽:
#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)
{
  Mat src, dst;
  String imageName( "../data/lena.jpg" ); // by default
  if (argc > 1)
  {
      imageName = argv[1];
  }
  src = imread( imageName, IMREAD_COLOR );
  if( src.empty() )
    { return -1; }
  vector<Mat> bgr_planes;
  split( src, bgr_planes );
  int histSize = 256;
  float range[] = { 0, 256 } ;
  const float* histRange = { range };
  bool uniform = true; bool 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 );
  // Draw the histograms for B, G and R
  int hist_w = 512; int 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  );
  }
  namedWindow("calcHist Demo", WINDOW_AUTOSIZE );
  imshow("calcHist Demo", histImage );
  waitKey(0);
  return 0;
}

說(shuō)明

  • 創(chuàng)建必要的矩陣:
Mat src,dst;
  • 加載源圖像
src = imread( argv[1], 1 );
if( !src.data )
  { return -1; }
  • 在三個(gè)R,G和B平面中分離源圖像。為此,我們使用OpenCV函數(shù)cv :: split
vector<Mat> bgr_planes;
split( src, bgr_planes );

我們的輸入是要分割的圖像(這種情況下有三個(gè)通道),輸出是Mat的向量)

  • 現(xiàn)在我們準(zhǔn)備開(kāi)始為每個(gè)平面配置直方圖。由于我們正在使用B,G和R planes,我們知道我們的值將在間隔范圍內(nèi)[0,255]

建立箱數(shù)(5,10 ...):

int histSize = 256; //from 0 to 255

設(shè)置值的范圍(如我們所說(shuō),在0到255之間)

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

我們希望我們的箱子具有相同的尺寸(均勻),并在開(kāi)始時(shí)清除直方圖,所以:

bool uniform = true ; bool accumulate = false ;

最后,我們創(chuàng)建Mat對(duì)象來(lái)保存直方圖。創(chuàng)建3(每個(gè)planes一個(gè)):

Mat b_hist,g_hist,r_hist;

我們繼續(xù)使用OpenCV函數(shù)cv :: calcHist計(jì)算直方圖:

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);

其中的論點(diǎn)是:

  1. **&bgr_planes [0]:**源數(shù)組(s)
  2. 1:源數(shù)組的數(shù)量(在這種情況下,我們正在使用1.我們可以在這里輸入一個(gè)數(shù)組列表)
  3. 0:要測(cè)量的通道(dim)。在這種情況下,它只是強(qiáng)度(每個(gè)陣列是單通道),所以我們只寫(xiě)0。
  4. Mat():在源數(shù)組上使用的掩碼(指示要忽略的像素的零)。如果未定義,則不使用它
  5. b_hist:要存儲(chǔ)直方圖的Mat對(duì)象
  6. 1:直方圖維度。
  7. histSize:每個(gè)使用的維數(shù)的數(shù)量
  8. histRange:每個(gè)維度要測(cè)量的值的范圍
  9. uniformaccumulate:紙箱尺寸相同,直方圖在開(kāi)始時(shí)清除。
  • 創(chuàng)建一個(gè)圖像以顯示直方圖:
// Draw the histograms for R, G and B
int hist_w = 512; int hist_h = 400;
int bin_w = cvRound( (double) hist_w/histSize );
Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
  • 請(qǐng)注意,在繪制之前,我們首先cv ::歸一化直方圖,使其值落在由輸入的參數(shù)指示的范圍內(nèi):
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() );

此函數(shù)接收這些參數(shù):

  1. b_hist:輸入數(shù)組
  2. b_hist:輸出歸一化數(shù)組(可以相同)
  3. 0和** histImage.rows:對(duì)于這個(gè)例子,它們是對(duì)r_hist **的值進(jìn)行歸一化的下限和上限
  4. NORM_MINMAX:指示歸一化類型的參數(shù)(如上所述,它調(diào)整之前設(shè)置的兩個(gè)限制之間的值)
  5. ** - 1:**意味著輸出歸一化數(shù)組將與輸入的類型相同
  6. Mat():可選掩碼
  • 最后,觀察到訪問(wèn)bin(在這種情況下在這個(gè)1D直方圖中):
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  );
}

我們使用表達(dá)式:

b_hist.at < float >(i)

其中 i 表示尺寸。如果它是一個(gè)二維直方圖,我們將使用如下:

b_hist.at<float>( i, j )

最后我們顯示直方圖,等待用戶退出:

namedWindow("calcHist Demo", WINDOW_AUTOSIZE );
imshow("calcHist Demo", histImage );
waitKey(0);
return 0;

結(jié)果

  • 作為輸入?yún)?shù)使用如下所示的圖像:

OpenCV直方圖計(jì)算

  • 生成以下直方圖:

OpenCV直方圖計(jì)算


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)