1. 程式人生 > >【從零學習openCV】使用直方圖統計畫素

【從零學習openCV】使用直方圖統計畫素

1. 計算影象直方圖

影象是由畫素組成的,在一個單通道的灰度影象中,每個畫素的值介於0到255之間,而直方圖就是一個簡單的表,給出了一幅或者一組影象中擁有給定數值的畫素數量。當然直方圖也可以歸一化,歸一化後的所有項的和為1,在這種情況下,每一項給出的都是擁有特定數值的畫素在影象中佔的比例。

下面我們看看如何用opencv計算單通道影象的直方圖,我用一個Histogram1D封裝了與單通道直方圖操作相關的變數和函式:

#ifndef HISTOGRAM1D_H
#define HISTOGRAM1D_H

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;

class Histogram1D{
private:
    int histSize[1];       //項的數量
    float hranges[2];   //畫素的最小及最大值
    const float* ranges[1];
    int channels[1];    //僅用到一個通道
public:
    Histogram1D(){
        //準備1D直方圖的引數
        histSize[0] =256;
        hranges[0] = 0.0;
        hranges[1] = 255.0;
        ranges[0]  = hranges;
        channels[0] = 0;  //預設情況,我們考察0號通道
    }

    //計算1D直方圖
    MatND getHistogram(const Mat&image)
    {
        MatND  hist;
        //計算直方圖
        calcHist(&image,
                 1,                     //計算單張影象的直方圖
                 channels,        //通道的數量
                 Mat(),              //不使用影象作為掩碼
                 hist,                //返回的直方圖
                 1,                    //這是1D的直方圖
                 histSize,         //項的數量
                 ranges            //畫素值的範圍
                 );
        return hist;
    }

    //計算1D直方圖,並返回一幅影象
    Mat getHistogramImage(const Mat &image)
    {
        //首先計算直方圖
        MatND hist = getHistogram(image);
        //獲取最大值和最小值
        double maxVal = 0;
        double minVal = 0;
        minMaxLoc(hist,&minVal,&maxVal,0,0);
        //顯示直方圖的影象
        Mat histImg(histSize[0],histSize[0],CV_8U,Scalar(255));
        //設定最高點為nbins的90%
        int hpt  = static_cast<int>(0.9*histSize[0]);
        //每個條目都繪製一條垂直線
        for(int h = 0;h<histSize[0];h++){
            float binVal =hist.at<float>(h);
            int intensity = static_cast<int>(binVal*hpt/maxVal);
            //兩點之間繪製一條線
            line(histImg,Point(h,histSize[0]),
                    Point(h,histSize[0]-intensity),
                    Scalar::all(0));
        }
        return histImg;
    }
};
#endif // HISTOGRAM1D_H
main函式如下,直接呼叫getHistogramImage即可得到直方圖:
#include <QCoreApplication>
#include "Histogram1D.h"

int main(int argc, char *argv[])
{
    namedWindow( "src1", WINDOW_AUTOSIZE );
    namedWindow( "src2", WINDOW_AUTOSIZE );
    Mat image = imread( "test.jpg",0 );  //讀取灰度圖
    Histogram1D h;
    while(1)
    {
        imshow( "src1", image);
        imshow( "src2", h.getHistogramImage(image));
        char c =  waitKey(30);
        if( 27==c )
            return 0;
    }
}
效果如下:

原始灰度影象


直方圖


當然我們還可以用calcHist函式來計算彩色BGR影象的直方圖,這時候直方圖變成三維的了:

#ifndef COLORHISTOGRAM_H
#define COLORHISTOGRAM_H
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include<QDebug>

using namespace cv;

class ColorHistogram{
private:
    int histSize[3];       //項的數量
    float hranges[2];   //畫素的最小及最大值
    const float* ranges[3];
    int channels[3];    //用到三個通道
public:
    ColorHistogram(){
        //準備彩色直方圖的引數
        histSize[0] = histSize[1]= histSize[2]=256;
        hranges[0] = 0.0;
        hranges[1] = 255.0;
        ranges[0] =ranges[1]=ranges[2]=hranges;       //所有通道都有相同的範圍
        channels[0] = 0;  //3個通道
        channels[1] = 1;
        channels[2] = 2;
    }

    //計算三維直方圖
    MatND getHistogram(const Mat&image)
    {
        MatND  hist;
        //計算直方圖
        calcHist(&image,
                 1,                     //計算單張影象的直方圖
                 channels,        //通道的數量
                 Mat(),              //不使用影象作為掩碼
                 hist,                //返回的直方圖
                 3,                    //這是三維的直方圖
                 histSize,         //項的數量
                 ranges            //畫素值的範圍
                 );
        qDebug()<<hist.dims;
        return hist;
    }
};

#endif // COLORHISTOGRAM_H
這時候getHistogram返回的是一個256*256*256的矩陣,包含了超過1600萬個元素,這計算代價是非常大的,可以參考opencv操作畫素中所述的方法減少顏色的數量,或者可以使用cv::SparseMat資料結構,用於儲存大型的稀疏矩陣,這樣可以極大地減少記憶體的消耗。

2. 查詢表的使用

查詢表實際上就是一個簡單的一對一(或者多對一)的函式,定義瞭如何將畫素值轉換為新的值,本質上就是一個一維陣列。

openCV中用cv::LUT的方法對影象應用查詢表生成新影象,我們將這個功能加到Histogram1D類中:

//應用查詢表,image為輸入影象,lookup是1*256的unchar矩陣,代表查詢表
Mat applyLookUp(const Mat&image,const Mat&lookup)
{
    Mat result;
    //應用查詢表
    LUT(image,lookup,result);
    return result;
}
這時我們可以做個簡單的實驗,比如將原先每個畫素強度進行反轉,即x變成255-x:
    int dim(256);
    Mat lut(1,&dim,CV_8U);
    for(int i=0;i<256;i++)
    {
        lut.at<uchar>(i) = 255-i;
    }
    Mat reverse = h.applyLookUp(image,lut);

效果如下:


3. 直方圖均衡化

直方圖均衡化其實就是為了讓直方圖更加的平坦,能夠大幅改善影象的外觀。

openCV中提供了一個簡單易用的函式cv::equalizeHist來執行直方圖均衡化。

//直方圖均衡化
    Mat  equalize(const Mat &image)
    {
        Mat result;
        equalizeHist(image,result);
        return result;
    }

應用與之前的影象,效果如下:



可以看出影象的對比度增強了,直方圖也更加的平坦。

參考書籍 《openCV2計算機視覺程式設計手冊》