2012年8月1日 星期三

C# 影像處理的速度極限

20111106044.jpg
最近開始整理過去的程式

有同事說有比指標更快的處理方式

所以就來個效能大比拚,看看到底是怎麼回事!!

為了求速度上的顯著影響

又不想花太多時間

這次測試的影像大小為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結構是否會比較快呢??

Image img = 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的平行迴圈方式來加快速度

Image
img = 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的設定速度上幾乎一樣!!

但...

只有這樣嗎??

還有沒有更快的方法呢???



如果仔細去推敲上面所寫的程式

可以發現有些小地方可以再修正

而這些小地方的修正可以達到相當大程度上的效能進展

Image img = 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

有興趣的朋友可以先自己寫來練習

相信會有意想不到的發現喔!!

沒有留言:

張貼留言