EmguCV 2.3 Application Programming : Manipulating the pixels
EmguCV的安裝教學請看這篇
參考OpenCV 2 Computer Vision Application Programming Cookbook第二章
來做一些EmguCV上的簡單示範,主題如下:
Create Image Class
Accessing Pixel Values
Scanning an Image with Points
Writing Efficient Image Scanning Loops
Create Image Class
這次的範例專案需要參考下列兩個dll:Emgu.CV.dll、Emgu.Util.dll跟一個.NET的元件:System.Drawing
並且在執行目錄下放置:Emgu.CV.dll、Emgu.Util.dll、opencv_core231.dll、opencv_highgui231.dll
然後將上圖抓下來,存放到E:\101.jpg,便可透過下面程式載入此圖:
using Emgu.CV; using Emgu.Util; using Emgu.CV.Structure;
... Image<Bgr, Byte> image = new Image<Bgr, Byte>(@"E:\101.jpg");
Image這個類別可讀取多種的影像格式
這裡的Bgr代表每個pixel的struct是由blue、green、red三個顏色來控制
如果是用Image<Gray, Byte>讀取影像
每個pixel的灰階值就是由Gray這個struct來控制
下列是Image所支援的色域結構:
Gray
Bgr (Blue Green Red)
Bgra (Blue Green Red Alpha)
Hsv (Hue Saturation Value)
Hls (Hue Lightness Saturation)
Lab (CIE L*a*b*)
Luv (CIE L*u*v*)
Xyz (CIE XYZ.Rec 709 with D65 white point)
Ycc (YCrCb JPEG)
而Byte代表著每一個色域的顏色深度(Image Depth)採用8bits的容量來存放
也就是0~255的值域,而一個pixel有bgr三色就需要24bits
一般的使用下都是以Byte來實作!
下列是Image所支援的影像深度:
Byte
SByte
Single (float)
Double
UInt16
Int16
Int32 (int)
想要有更了解Image可以到EmguCV的官網參考原文
Accessing Pixel Values
影像的左上角是[0, 0],右下角是[431, 640]而最簡單對影像的像素值控制的方法如下:
for (int height = 0; height < image.Height; height++) { for (int width = 0; width < image.Width; width++) { image[height, width] = new Bgr(blue, green, red); } }blue、green、red分別就是對應到藍色、綠色、紅色的值
利用兩個for迴圈來對每個pixel做存取
pixel的對應很直覺地採用二維陣列
第一欄的值為垂直方向:height
第二欄的值為水平方向:width
由於這邊是載入彩色的影像Image<Bgr, Byte>
所以每個pixel的顏色值是透過Bgr這個struct來做設定
這樣便可對整張影像的所有pixel值來做存取或設定!
Scanning an Image with Points
那要比較快的方法就得透過pointer方式來存取
原本的存取方式是不斷透過封裝過的Image類別來做存取
不斷的呼叫Image的方法來對pixel做控制
以筆者電腦CPU:Intel I5-560M 2.66GHz,記憶體:DDR3 2GB
這張640*431的影像跑完一輪需要67ms
若是改成pointer的方法會差多少呢??
這時必須在專案的屬性中,把容許unsafe程式碼打勾!!
MIplImage MIpImg = (MIplImage)System.Runtime.InteropServices.Marshal.PtrToStructure(image.Ptr, typeof(MIplImage)); unsafe { byte* npixel = (byte*)MIpImg.imageData; for (int height = 0; height < image.Height; height++) { for (int width = 0; width < image.Width; width++) { npixel[0] = 255; //blue npixel++; npixel[0] = 255; //green npixel++; npixel[0] = 255; //red npixel++; } } }
這裡關鍵的步驟就是在於使用了byte的指標:byte*
透過指標,一個pixel一個pixel的去移動並設定值
這對C#來說是unsafe的語法,所以必須用unsafe包起來
而在每個迴圈中,都對blue、green、red三種顏色做設定(這裡都設定成255)
這裡透過一個MIplImage類別的轉換
影像所有的pixel值會存放在一個byte陣列裡,並且連續分布在記憶體中
所以就不斷地將指標指向下一個位置,就可以輪完所有的pixel
這樣的方式比原先的速度快上很多,大約只要15ms的時間!
在這邊是一個pixel一個pixel來設定
那如果要一次跳一行來設定呢?
例如原本是設定image[0, 0],直接跳到image[1, 0]
這在原本的二維陣列架構中很容易,但在指標裡卻是一維陣列
你可以直接
npixel+=MIpImg.width*3;
但如果現在處理的是灰值影像,乘以3就不對了!
npixel+=MIpImg.width*MIpImg.nChannels;
在MIplImage類別中有一個nChannels的屬性
他會告訴你這個image有幾個色域,Gray就是一,BGR就是三
或者更直接的方法就是
npixel+=MIpImg.widthStep;
就可以直接跳下一行了!!
Writing Efficient Image Scanning Loops
但在這過程中還是有些地方可以改進尤其在for loop的設計上
原先的做法會不斷的使用image.Height跟image.Width這兩個屬性
這是封裝在Image類別中所實作的CvArray抽象類別裡頭
public int Height { get; } public int Width { get; }
所以在for loop就會不斷的get!!
所以這裡需要做一些修正
在迴圈裡頭,原先的作法是每設定一個色域的值就將指標+1
所以在一個迴圈裡頭就會做三次運算!!
這裡也要做一些修正
MIplImage MIpImg = (MIplImage)System.Runtime.InteropServices.Marshal.PtrToStructure(image.Ptr, typeof(MIplImage)); int imageHeight = MIpImg.height; int imageWidth = MIpImg.width; unsafe { byte* npixel = (byte*)MIpImg.imageData; for (int height = 0; height < imageHeight; height++) { for (int width = 0; width < imageWidth; width++) { npixel[0] = 255; //blue npixel[1] = 255; //green npixel[2] = 255; //red npixel+=3; } } }
先將影像的高、寬用兩個integer的變數(imageHeight、imageWidth)來儲存
所以在for loop中就是直接採用這兩個integer的變數
而迴圈內則直接設定三個色域的值
在一次將指標移動三個位址,到下一個pixel
這樣在一次迴圈內只會有一次運算
這樣的修正的運算速度會降低到1ms左右!
若是對影像的存取速度有興趣的,可看這篇C# 影像處理的速度極限!
下一篇就會開始介紹簡單的影像處理
應該會有趣一點~XD
沒有留言:
張貼留言