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來做一些簡單影像分析和處理







沒有留言:
張貼留言