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來做一些簡單影像分析和處理
沒有留言:
張貼留言