2012年9月23日 星期日

EmguCV Image Process: Counting the Pixels with Histograms part1

再度攜手步入森林

EmguCV Image Process: Counting the Pixels with Histograms

參考OpenCV 2 Computer Vision Application Programming Cookbook第四章

介紹內容如下:

Computing the image histogram

Applying look-up tables to modify image appearance

Equalizing the image histogram

Backprojecting a histogram to detect specific image content

Using the mean shift algorithm to find an object

一張影像是由許多pixels帶有不同的值(顏色)所組成的

這些pixels的值的分布在一張影像中構成了相當重要的角色

此章節會介紹影像histogram的概念

將會提到如何計算histogram和如何使用它來修改一張影像所呈現的效果

histogram也可以用來表示一張影像的內容的特色(特徵)

也可用來偵測一張影像中明確的物體或是紋路


Computing the image histogram

一張影像是由許多pixels所組成

每個pixel可能都擁有不同的值(value)

或是以一個灰階的影像(1-channel-gray-level)來看

每一個pixel的值會落在0(黑)~255(白)之間

histogram就是一個簡單的表格(table)來呈現在每一個值(0~255)有多少個pixels

所以histogram通常會有256個數值(entries、bins)

分別表示0~255的值的pixels的個數

所以把histogram256個值累加起來就是一張影像的pixels總數

更多詳細關於histogram可以看這,不過是原文的!!


用封面這張圖來作範例

在openCV中要取得一張影像的histogram是非常簡單的

//Read input image
Image<Gray, Byte> image = new Image<Gray, byte>(fileName);
//Create a histogram
DenseHistogram histogram = 
    new DenseHistogram(
        256,               //number of bins
        new RangeF(0, 255) //pixel value range
        );
//Compute histogram
histogram.Calculate(
    new Image<Gray, Byte>[] { image }, //input image
    true, //If it is true, the histogram is not cleared in the beginning
    null  //no mask is used
    );
float[] grayHist = new float[256]; //the resulting histogram array
histogram.MatND.ManagedArray.CopyTo(grayHist, 0); //copy array
//Loop over each bin
for (int i = 0; i < 256; i++)
{
    Console.WriteLine("value " + i + " = " + grayHist[i]);
}
透過上面這段程式就可以把histogram的值印出來

這裡建立了一個DenseHistogram的類別

建構式傳入這個histogram的大小(256)、和pixel的值的範圍(0~255)

透過方法Calculate來計算影像的histogram

由於DenseHistogram可以一次計算好幾張影像的histogram

所以第一個引數是透過影像陣列(Image<Gray, Byte>[])的方式傳入

這邊只計算一張灰階影像,所以陣列中就擺一張影像進去

第二個引數就是DenseHistogram在計算每一次histogram是否要清除之前的紀錄

若是true,就會保留上一次的內容,繼續累計下去

第三個引數是否採用影像遮罩(mask)

這在前一章有提到,可以利用遮罩過濾影像某些部分!


那如何把histogram的值用陣列的表示方式取出來

先建立一個float[]長度是256

在把array的值copy過去

float[] grayHist = new float[256]; //the resulting histogram array
histogram.MatND.ManagedArray.CopyTo(grayHist, 0); //copy array
把histogram的值取出來

最後再透過一個For迴圈把值一個一個印出來

如下圖所示:
列出0~255的value有多少個pixels

這樣的顯示方式雖然簡單

但卻很不直覺,如果要透過圖像式的方式來呈現

必須利用windows form的方式來做

先把原本的GetHistogram的方法稍微改一下

public DenseHistogram GetHistogram(Image<Gray, Byte> image)
{
    //Create a histogram
    DenseHistogram histogram = 
        new DenseHistogram(
            256,               //number of bins
            new RangeF(0, 255) //pixel value range
            );
    //Compute histogram
    histogram.Calculate(
        new Image<Gray, Byte>[] { image }, //input image
        true, //If it is true, the histogram is not cleared in the beginning
        null  //no mask is used
        );
    return histogram;
}
方法改成直接傳入影像,回傳DenseHistogram

然後建立一個windows form

配置樣式如下:
跟上一章的很像,有一個open的button跟process的button

在這裡不考慮MVC的設計架構

直接用直覺的方式來處理

建立一個全域變數的類別:_Image

private Image<Gray, Byte> _Image;
用來儲存使用者開啟的影像

private void OpenButton_Click(object sender, EventArgs e)
{
    if (OpenFile.ShowDialog(this) == DialogResult.Cancel)
    {
        return;// User selects nothing
    }
    String imagePath = OpenFile.FileName;
    //Input image
    this._Image = new Image<Gray, byte>(imagePath);
    //Show the image
    PictureBox.Image = this._Image.ToBitmap();
}
將使用者選取的影像儲存至_Image並顯示出來

這時候需要用到一個類別叫做:HistogramViewer

必須參考Emgu.CV.UI.dll 這個元件

private void ProcessButton_Click(object sender, EventArgs e)
{
    //Compute histogram
    DenseHistogram hist = GetHistogram(this._Image);
    //show histogram
    HistogramViewer.Show(hist, "histogram");
}
把影像丟入剛剛寫的方法GetHistogram

把回傳的DenseHistogram丟入HistogramViewer

這裡的show是靜態方法(static method)
(其實可以直接傳入影像~ XD)

這樣就能夠顯示出這張影像的histogram

如下圖所示:
按下Open Image開啟影像

按下Process就會跳出Histogram的圖表出來


HistogramViewer呈現的圖表非常精緻

可以拉動大小,透過滑鼠選取放大的範圍等等

關於HistogramViewer的詳細功能可以參考這裡

透過histogram的呈現

可以發現影像的顏色分布

由於影像中大部分都是深色的樹

所以有很多的pixels分布在gray level 50以下

超過gray level 200以上的部分應該是天空和白紗的部分就顯得比較少!!


如果我們要呈現彩色影像的histogram

把GetHisogram的方法做一些調整!

public DenseHistogram GetHistogram(Image<Bgr, Byte> image)
{
    //Create a histogram
    DenseHistogram histogram = 
        new DenseHistogram(
            //number of bins
            new int[] {256,256,256},               
            //pixel value range
            new RangeF[] { new RangeF(0, 255), new RangeF(0, 255), new RangeF(0, 255) } 
            );
    Image<Gray, byte>[] images = image.Split();
    //Compute histogram
    histogram.Calculate(
        images, //Split current Image into an array of gray scale images
        true, //If it is true, the histogram is not cleared in the beginning
        null  //no mask is used
        );
    return histogram;
}
這時傳入的影像就是彩色影像(RGB)

所以在DenseHistogram的宣告時,也要有所改變

然後在傳入Calculate時用Split方法把彩色影像拆成多個灰階影像的陣列

在全域變數中,把原本的灰階影像_Image改成彩色影像

private Image<Bgr, Byte> _Image;

這時候執行

會發現影像的顏色變成彩色了,可是怎麼怪怪!?

執行後會發現,跳出的histogram圖表不見了!!?

是不是哪裡程式出了問題??

程式沒有問題,只是HistogramViewer.Show只能顯示一維的histogram!
(1 dimension histogram)

那這樣有什麼方法可以顯示出彩色影像呢?

若只是要單純的show出histogram的圖表

只需要一行!!

private void ProcessButton_Click(object sender, EventArgs e)
{
    HistogramViewer.Show(this._Image, 256);
}
直接用HistogramViewer.Show代入影像跟設定histogram的bin size!

這時候就會跳出三個一維的histogram,分別代表RGB三色的分佈!


那麼想要直接顯示三維DenseHistogram怎麼辦呢?

唯一的辦法就是:自己寫程式來顯示!!!

那如果想要利用DenseHistogram來計算histogram

方便取得陣列數值

又想要顯示出三張一維的histogram呢?

這時候就可以把原本的GetHistogram的方法改回原本的灰階影像

然後再分別把RGB三色的灰階影像傳入

再由HistogramViewer來顯示

private void ProcessButton_Click(object sender, EventArgs e)
{
    Image<Gray, byte>[] images = this._Image.Split();
    DenseHistogram hist = GetHistogram(images[0]);
    HistogramViewer.Show(hist, "Blue");
    hist = GetHistogram(images[1]);
    HistogramViewer.Show(hist, "Green");
    hist = GetHistogram(images[2]);
    HistogramViewer.Show(hist, "Red");
}
先將彩色影像拆解成三張灰階影像(RGB)

在傳入GetHistogram取得DenseHistogram

再由HistogramViewer分別顯示個別的histogram!

獨立的視窗個別顯示RGB三色的histogram


之後再繼續往下介紹嘍!

下一篇介紹如何利用histogram來做一些簡單影像分析和處理

沒有留言:

張貼留言