總算有時間繼續EmguCV的文章了,在不紀錄一下都要生鏽了,這次要進入的題目是透過EmguCV的CvInvoke中的函式來完成值方圖的計算。
那麼,為什麼,這篇文章我會來介紹使用CvInvoke呢?
因為透過CvInvoke的方式,換句話說就是呼叫OpenCV來直接幫你運算,而OpenCV中有些方法可以傳入更多細微的調整參數,如此可以幫你做出最想要的運算。
前言
總算有時間繼續EmguCV的文章了,在不紀錄一下都要生鏽了,這次要進入的題目是透過EmguCV的CvInvoke中的函式來完成值方圖的計算。
那麼,為什麼,這篇文章我會來介紹使用CvInvoke呢?
因為透過CvInvoke的方式,換句話說就是呼叫OpenCV來直接幫你運算,而OpenCV中有些方法可以傳入更多參數做調整,如此可以幫你做出最想要的運算。
CvInovke類別介紹
這邊再次介紹一下CvInovke類別
CvInvoke裡面提供了很多影像處理的基本方法,基本上CvInvoke在EmguCV中可以當作是一個把OpenCV函示庫做接口(下圖來自EmguCV官網)
當你在呼叫EmguCV中的CvInvoke方法,並傳入參數時,其實實際的運算還是透過OpenCV中的函式在幫你計算,而且如果你親自去看的話也會發現其實裡面也呼叫openCV的函式庫方法來做處理
透過CvInvoke的方式,換句話說就是呼叫OpenCV來直接幫你運算,而OpenCV中有些方法可以傳入更多細微的調整參數,如此可以幫你做出最想要的運算。
如何使用CvInvoke計算圖像的值方圖
1. 轉換處理CvInovkec中函式可用的參數型態
首先,因為我們是使用EmguCV,所以原先的儲存圖片的結構會是:
Image<Bgr, Byte> srcImg
又或者你是給檔案的字串路徑:
string srcFileName
我們的第一步就是把上述兩個格式轉換成InPtr型態,因為這個型態可以用來餵CvInvoke中許多函式的傳入參數型態
所以如果是Image<Bgr, Byte> srcImg 就變成如下:
IntPtr srcImage = srcImg.Copy().Ptr;
如果傳入的是檔案路徑字串:
IntPtr srcImage = CvInvoke.cvLoadImage(srcFileName, Emgu.CV.CvEnum.LOAD_IMAGE_TYPE.CV_LOAD_IMAGE_ANYCOLOR);
InPtr是一種取代指標用的型態,所以當你呼叫後面的方法時,你會看到如下圖片
2.建立一個可以計算值方圖的函式
這邊我已Hsv色彩空間來計算,因為並且我只取了Hue色相的統計資料(如果不知道什麼事Hsv色彩空間可以看我這篇文章-使用EmguCV的DenseHistogram類別計算與紀錄圖像直方圖-直方圖(Histogram)系列(2))
為什麼呢?
因為通常我們在做影像中的識別或追蹤的時候,多數都會把色彩作為一種辨識或追蹤的依據,但是用顏色做別依據卻容易色彩的明暗度所干擾!
假若原先我在學習要追蹤或辨識的物件是在一片光明的地點中拍下來,學習色彩分布圖作為之後辨識的資料來源,可能到了一樣的光線環境下找得出來
但是假若今天到了在夜晚,一片暗暗的環境下,或是到了更亮更亮的地方時,通常這樣會和我們原先想要辨識而記錄的物件色彩資訊有所不相符,導致追蹤或辨識失敗
所以通常我們會希望把明暗度這樣的因素從色彩資訊中排除,而容易想到的方式,辨識透過把原先RGB的色彩資訊轉換成Hsv色彩資訊,因為Hsv中有把飽和度與明暗度抽離出來,所以比較可以保留原先的色彩資訊
(不過相對的,如果你要辨識本身就是全黑或全白,灰色類的物體,可能就不適合,或是你還要再用別的方式處理)
回過頭來
在這邊我是把計算的程式區塊包成函式來使用,傳入的是來源影像(IntPtr型態)與hue色相所要的Bin值(如果不清楚Bin值是什麼的可以看-使用EmguCV的DenseHistogram類別計算與紀錄圖像直方圖-直方圖(Histogram)系列(2))),回傳則是EmguCV所使用的DenseHistogram 類別
private static DenseHistogram Cal1DHsvHist(IntPtr srcImage, int h_bins)
(1.) 創建存放轉換至Hsv色彩空間的影像與分割取得Hue通道的影像以及相關參數設定
DenseHistogram histDense; //最後計算完存放回DenseHistogram類別
//使用CvInvoke的cvCreateImage方法分別創建存放轉換至Hsv色彩空間的影像與分割取得Hue通道的影像空間
IntPtr hsv = CvInvoke.cvCreateImage(CvInvoke.cvGetSize(srcImage), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 3);
IntPtr h_plane = CvInvoke.cvCreateImage(CvInvoke.cvGetSize(srcImage), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 1);
/** Hue的變化範圍 */
float[] h_ranges = new float[2] { 0, h_max_range }; //max_range = 180
IntPtr[] h_plane_ptr = new IntPtr[] { h_plane };
(2.) 轉換至Hsv空間
CvInvoke.cvCvtColor(srcImage, hsv, Emgu.CV.CvEnum.COLOR_CONVERSION.CV_BGR2HSV); //轉換成Hsv色彩空間
CvInvoke.cvSplit(hsv, h_plane, IntPtr.Zero, IntPtr.Zero, System.IntPtr.Zero); // 分離取出色相空到的影像資料
(3.) 配置Histogarm空間 與計算Hue的色彩分布值方圖資料
RangeF hRange = new RangeF(0f, h_max_range); //H色調分量的變化範圍
histDense = new DenseHistogram(h_bins, hRange); //初始化,配置空間(告知色相的範圍與切割的bin值)
CvInvoke.cvCalcHist(h_plane_ptr, histDense, true, System.IntPtr.Zero); //把色相的影像資料傳,透過計算取出DenseHistogram 類別的值方圖資訊histDense
如此便可以完成計算,取得色相值方圖!
完整程式碼:
private static DenseHistogram Cal1DHsvHist(IntPtr srcImage, int h_bins)
{
try
{
DenseHistogram histDense; //最後計算完存放回DenseHistogram類別
//使用CvInvoke的cvCreateImage方法分別創建存放轉換至Hsv色彩空間的影像與分割取得Hue通道的影像空間
IntPtr hsv = CvInvoke.cvCreateImage(CvInvoke.cvGetSize(srcImage), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 3);
IntPtr h_plane = CvInvoke.cvCreateImage(CvInvoke.cvGetSize(srcImage), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 1);
/** Hue的變化範圍 */
float[] h_ranges = new float[2] { 0, h_max_range };
IntPtr[] h_plane_ptr = new IntPtr[] { h_plane };
//轉換成Hsv色彩空間
CvInvoke.cvCvtColor(srcImage, hsv, Emgu.CV.CvEnum.COLOR_CONVERSION.CV_BGR2HSV);
// 分離取出色相空到的影像資料
CvInvoke.cvSplit(hsv, h_plane, IntPtr.Zero, IntPtr.Zero, System.IntPtr.Zero);
//創建值方圖
RangeF hRange = new RangeF(0f, h_max_range); //H色調分量的變化範圍
//初始化,配置空間(告知色相的範圍與切割的bin值)
histDense = new DenseHistogram(h_bins, hRange);
//把色相的影像資料傳入,透過計算存入DenseHistogram 類別的histDense值方圖資訊
CvInvoke.cvCalcHist(h_plane_ptr, histDense, true, System.IntPtr.Zero);
return histDense;
} catch (Exception ex) { throw new InvalidOperationException(ex.Message);
}
}
另外附上計算HS值方圖的部分:
private static DenseHistogram Cal2DHsvHist(IntPtr srcImage, int h_bins, int s_bins)
{
try
{
DenseHistogram histDense;
int[] hist_size = new int[2] { h_bins, s_bins };
IntPtr hsv = CvInvoke.cvCreateImage(CvInvoke.cvGetSize(srcImage), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 3);
//創建存放色相與飽和度影像資訊的空間
IntPtr h_plane = CvInvoke.cvCreateImage(CvInvoke.cvGetSize(srcImage), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 1);
IntPtr s_plane = CvInvoke.cvCreateImage(CvInvoke.cvGetSize(srcImage), Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_8U, 1);
IntPtr[] planes = new IntPtr[2] { h_plane, s_plane };
/** Hue的變化範圍 */
float[] h_ranges = new float[2] { 0, h_max_range };
/** 飽和度的變化範圍 */
float[] s_ranges = new float[2] { 0, s_max_range };
CvInvoke.cvCvtColor(srcImage, hsv, Emgu.CV.CvEnum.COLOR_CONVERSION.CV_BGR2HSV); //轉換成Hsv色彩空間
CvInvoke.cvSplit(hsv, h_plane, s_plane, v_plane, System.IntPtr.Zero); // 分離取出色相空到的影像資料
//計算
RangeF hRange = new RangeF(0f, h_max_range); //H色調分量的變化範圍
RangeF sRange = new RangeF(0f, s_max_range); //S飽和度分量的變化範圍
histDense = new DenseHistogram(hist_size, new RangeF[] { hRange, sRange });
CvInvoke.cvCalcHist(planes, histDense, false, System.IntPtr.Zero);
return histDense;
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message);
}
}
結論
希望此篇能幫助想要使用CvInvoke計算值方圖的朋友們了解如何計算 =)
下一篇終於可以進入如何不透過EmguCV內建繪製值方圖工具,來繪製值方圖,如此便可以產色比較具有視覺效果的分布資料(如下)
參考資料
文章中的敘述如有觀念不正確錯誤的部分,歡迎告知指正 謝謝 =)
另外要轉載請附上出處 感謝