最近開始整理過去的程式
有同事說有比指標更快的處理方式
所以就來個效能大比拚,看看到底是怎麼回事!!
為了求速度上的顯著影響
又不想花太多時間
這次測試的影像大小為3000*2000
並把全黑的影像轉成全白的影像
並計算從無到有到全部的點都設定完後的時間來比對
並用迴圈跑十次,看看平均的計算時間
這樣的題目很簡單:建立一個3000*2000影像
並把所有的pixel都設為白色
看所需的時間:
一般使用C#的人,會採用
SetPixel的方法,程式碼如下:
Bitmap source = new Bitmap(3000, 2000, PixelFormat.Format32bppArgb); for (int h = 0; h < source.Height; h++) { for (int w = 0; w < source.Width; w++) { source.SetPixel(w, h, Color.White); } }
二十次的運算時間,單位毫秒
我想這應該是相當吃力的一件事
一張影像需要11秒多的時間!!!
那一般用C#做影像處理時
很多人會使用Emgu CV這個Open CV的Wrapper
那如果是用Emgu CV的Image結構是否會比較快呢??
Imageimg = new Image (3000, 2000); for (int h = 0; h < img.Height; h++) { for (int w = 0; w < img.Width; w++) { img[h, w] = new Bgr(255, 255, 255); } }
二十次的運算時間,單位毫秒
看起來是快很多了!!
比原先用Bitmap的SetPixel快上將近十倍!!!!
但由於Image的結構其實已經是對陣列做存取
所以可以透過Parallel的平行迴圈方式來加快速度
Imageimg = new Image (3000, 2000); Parallel.For(0, img.Height, h => { Parallel.For(0, img.Width, w => { img[h, w] = new Bgr(255, 255, 255); }); });
二十次的運算時間,單位毫秒
看起來又更快了些
現在處理一張影像已經不到一秒了!!
現在看起來是已經相當快速了!!
一張影像的處理時間已經不到100毫秒!
不過如果整個系統架構還是以Bitmap為主的話
還需要計算把Image轉成Bitmap的轉換時間!!
所以再回過頭來看看Bitmap的處理方式
是不是也能夠直接從陣列來存取影像的值
Bitmap source = new Bitmap(3000, 2000, PixelFormat.Format32bppArgb); MemoryStream memoryStream = new MemoryStream(); source.Save(memoryStream, ImageFormat.Bmp); byte[] byteArray = memoryStream.ToArray(); int totalPixels = source.Height * source.Width; for (int p = 0; p < totalPixels; p++) { byteArray[54 + p] = 255; } memoryStream = new MemoryStream(byteArray); source = new Bitmap(memoryStream);
二十次的運算時間,單位毫秒
用Byte[]來處理是比原先SetPixel快超過50倍!!!
但這邊如果跟Emgu CV做比較是有點不公平
畢竟在Emgu CV的每個迴圈上
還分別對RGB三種顏色額外做處理,速度上當然慢上很多
而Bitmap這邊只採用一個迴圈跑完整個陣列
這樣的方式也比較難去控制各pixel各個RGB的值
但如果這樣就因此而滿足是不夠的
最快的方式還不是這樣
而是透過Byte*指標來存取每個pixel的值
Bitmap source = new Bitmap(3000, 2000,PixelFormat.Format32bppArgb); BitmapData sourceData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); IntPtr source_scan = sourceData.Scan0; unsafe { byte* source_p = (byte*)source_scan.ToPointer(); for (int h = 0; h < source.Height; h++) { for (int w = 0; w < sourceData.Width; w++) { source_p[0] = 255; //A source_p++; source_p[0] = 255; //R source_p++; source_p[0] = 155; //G source_p++; source_p[0] = 55; //B source_p++; } } } source.UnlockBits(sourceData);
二十次的運算時間,單位毫秒
這樣的處理速度真的是快啊!!!
已經壓到一張不到100豪秒的計算時間,非常驚人!!!
而且這個方法還是分別對ARGB的值做處理
既容易理解,速度又快!
而想當然,Emgu CV也能採取一樣的方式來做
Image二十次的運算時間,單位毫秒img = new Image (3000, 2000); MIplImage MIpImg = (MIplImage)System.Runtime.InteropServices.Marshal.PtrToStructure(img.Ptr, typeof(MIplImage)); unsafe { byte* npixel = (byte*)MIpImg.imageData; int totalHeight = img.Height; int totalWidth = img.Width; for (int h = 0; h < totalHeight; h++) { for (int w = 0; w < totalWidth; w++) { npixel[0] = 255; npixel++; npixel[0] = 255; npixel++; npixel[0] = 255; npixel++; } } }
速度也是非常的快,而且看起來好像還更快!!
原因就在於Bitmap處理的是32bpp的影像
分別對ARGB等四個值做設定
而在Emgu CV只有對RGB做設定
若是把Bitmap的影像改成24bpp並只做RGB的設定速度上幾乎一樣!!
但...
只有這樣嗎??
還有沒有更快的方法呢???
如果仔細去推敲上面所寫的程式
可以發現有些小地方可以再修正
而這些小地方的修正可以達到相當大程度上的效能進展
Imageimg = new Image (3000, 2000); MIplImage MIpImg =(MIplImage)System.Runtime.InteropServices.Marshal.PtrToStructure(img.Ptr, typeof(MIplImage)); unsafe { int height = img.Height; int width = img.Width; byte* npixel = (byte*)MIpImg.imageData; for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) { int point = w * 3; npixel[point] = 255; npixel[point + 1] = 255; npixel[point + 2] = 255; } npixel = npixel + MIpImg.widthStep; } }
這裡將指標的迴圈內讀取指標的方法做了點修正
直接透過int point來取得指標的位置
直接做存取,避免在迴圈內不斷地對指標做累加!!
這樣的修正能將原本約60ms上下的速度提升至45ms左右!!
而目前測出最快的方法
就是加上Parallel.For
有興趣的朋友可以先自己寫來練習
相信會有意想不到的發現喔!!
沒有留言:
張貼留言