C#圖片處理常見方法性能比較

在.NET編程中,因爲GDI+的出現,使得對於圖像的處理功能大大加強。在文經過一個簡單黑白處理實例介紹在.NET中常見的圖片處理方法和原理並比較各類方法的性能。算法

 

黑白處理原理:彩色圖像處理成黑白效果一般有3種算法編程

(1).最大值法: 使每一個像素點的 R, G, B 值等於原像素點的 RGB (顏色值) 中最大的一個;c#

(2).平均值法: 使用每一個像素點的 R,G,B值等於原像素點的RGB值的平均值;數組

(3).加權平均值法: 對每一個像素點的 R, G, B值進行加權安全

自認爲第三種方法作出來的黑白效果圖像最 "真實".數據結構

 

1.GetPixel方法

 GetPixel(i,j)和SetPixel(i, j,Color)能夠直接獲得圖像的一個像素的Color結構,可是處理速度比較慢.ide

複製代碼
複製代碼
 /// <summary>
        /// 像素法
        /// </summary>
        /// <param name="curBitmap"></param>
        private void PixelFun(Bitmap curBitmap)
        {
            int width = curBitmap.Width;
            int height = curBitmap.Height;
            for (int i = 0; i <width; i++) //這裏若是用i<curBitmap.Width作循環對性能有影響
            {
                for (int j = 0; j < height; j++)
                {
                   Color  curColor = curBitmap.GetPixel(i, j);
                   int ret = (int)(curColor.R * 0.299 + curColor.G * 0.587 + curColor.B * 0.114);
                    curBitmap.SetPixel(i, j, Color.FromArgb(ret, ret, ret));
                }
            }
 }
複製代碼
複製代碼

這裏提一下,在循環次數控制時儘可能不要用i<curBitmap.Width作循環條件,而是應當將其取出保存到一個變量中,這樣循環時不用每次從curBitmp中取Width屬性,從而提升性能。性能

儘管如此,直接提取像素法對大像素圖片處理力不從心,處理一張1440*900的圖片耗時2182ms.本人配置單:測試

處理以前截圖:spa


處理後:

 

能夠直觀地看出用時間2056ms.屢次測試有少量波動。

2.內存拷貝法

內存拷貝法就是採用System.Runtime.InteropServices.Marshal.Copy將圖像數據拷貝到數組中,而後進行處理,這不須要直接對指針進行操做,不需採用unsafe,處理速度和指針處理相差不大,處理一副1440*900的圖像大約須要34ms。

 
內存拷貝發和指針法都需用到的一個類:BitmapData
 

BitmapData類

 

BitmapData對象指定了位圖的屬性

 

1.       Height屬性:被鎖定位圖的高度.

 

2.       Width屬性:被鎖定位圖的高度.

 

3.       PixelFormat屬性:數據的實際像素格式.

 

4.       Scan0屬性:被鎖定數組的首字節地址,若是整個圖像被鎖定,則是圖像的第一個字節地址.

 

5.       Stride屬性:步幅,也稱爲掃描寬度.

如上圖所示,數組的長度並不必定等於圖像像素數組的長度,還有一部分未用區域,這涉及到位圖的數據結構,系統要保證每行的字節數必須爲4的倍數.

假設有一張圖片寬度爲6,由於是Format24bppRgb格式(每像素3字節。在如下的討論中,除非特別說明,不然Bitmap都被認爲是24位RGB)的,顯然,每一行須要6*3=18個字節存儲。對於Bitmap就是如此。但對於BitmapData,雖然BitmapData.Width仍是等於Bitmap.Width,但大概是出於顯示性能的考慮,每行的實際的字節數將變成大於等於它的那個離它最近的4的整倍數,此時的實際字節數就是Stride。就此例而言,18不是4的整倍數,而比18大的離18最近的4的倍數是20,因此這個BitmapData.Stride = 20。顯然,當寬度自己就是4的倍數時,BitmapData.Stride = Bitmap.Width * 3。畫個圖可能更好理解(此圖僅表明PixelFormat= PixelFormat. Format24bppRgb時適用,每一個像素佔3個字節共24位)。R、G、B 分別表明3個原色份量字節,BGR就表示一個像素。爲了看起來方便我在每一個像素之間插了個空格,其實是沒有的。X表示補足4的倍數而自動插入的字節。爲了符合人類的閱讀習慣我分行了,其實在計算機內存中應該當作連續的一大段。Scan0||-------Stride-----------||-------Width---------|  |BGR BGR BGR BGR BGR BGR XXBGR BGR BGR BGR BGR BGR XXBGR BGR BGR BGR BGR BGR XX.則對於Format24bppRgb格式,知足:

BitmapData.Width*3 + 每行未使用空間(上圖的XX)=BitmapData.Stride

 

同理,很容易推倒對於Format32bppRgb或Format32bppPArgb格式,知足:

BitmapData.Width*4 + 每行未使用空間(上圖的XX)=BitmapData.Stride

複製代碼
複製代碼
  /// <summary>
        /// 內存拷貝法
        /// </summary>
        /// <param name="curBitmap"></param>
        private unsafe void MemoryCopy(Bitmap curBitmap)
        {
            int width = curBitmap.Width;
            int height = curBitmap.Height;

            Rectangle rect = new Rectangle(0, 0, curBitmap.Width, curBitmap.Height);
            System.Drawing.Imaging.BitmapData bmpData = curBitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,PixelFormat.Format24bppRgb);//curBitmap.PixelFormat

            IntPtr ptr = bmpData.Scan0;
            int bytesCount = bmpData.Stride * bmpData.Height;
            byte[] arrDst = new byte[bytesCount];
            Marshal.Copy(ptr, arrDst, 0, bytesCount);
         
            for (int i = 0; i < bytesCount; i+=3)
            {
                byte colorTemp = (byte)(arrDst[i + 2] * 0.299 + arrDst[i + 1] * 0.587 + arrDst[i] * 0.114);
                arrDst[i] = arrDst[i + 1] = arrDst[i + 2] = (byte)colorTemp;

            }
            Marshal.Copy(arrDst, 0, ptr, bytesCount);
            curBitmap.UnlockBits(bmpData);
        }
複製代碼
複製代碼

 3.指針法

指針在c#中屬於unsafe操做,須要用unsafe括起來進行處理,速度最快,處理一副180*180的圖像大約須要18ms。

 

採用byte* ptr = (byte*)(bmpData.Scan0); 獲取圖像數據根位置的指針,而後用bmpData.Scan0獲取圖像的掃描寬度,就能夠進行指針操做了。

 

複製代碼
複製代碼
    /// <summary>
        /// 指針法
        /// </summary>
        /// <param name="curBitmap"></param>
        private unsafe void PointerFun(Bitmap curBitmap)
        {
            int width = curBitmap.Width; 
            int height = curBitmap.Height;

            Rectangle rect = new Rectangle(0, 0, curBitmap.Width, curBitmap.Height);
            System.Drawing.Imaging.BitmapData bmpData = curBitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,PixelFormat.Format24bppRgb );//curBitmap.PixelFormat
            byte temp = 0;
                int w = bmpData.Width;
                int h = bmpData.Height;
                byte* ptr = (byte*)(bmpData.Scan0);
                for (int i = 0; i < h; i++)
                {
                    for (int j = 0; j <w; j++)
                    {
                       temp = (byte)(0.299 * ptr[2] + 0.587 * ptr[1] + 0.114 * ptr[0]);
                        ptr[0] = ptr[1] = ptr[2] = temp;
                        ptr +=3; //Format24bppRgb格式每一個像素佔3字節
                    }
                    ptr += bmpData.Stride - bmpData.Width * 3 ;//每行讀取到最後「有用」數據時,跳過未使用空間XX
                }
            curBitmap.UnlockBits(bmpData);    
        }
複製代碼
複製代碼
如下是多組測試數據:
 
1920*1080
1440*900
1208*800
1024*768
500*544
200*169
直接提取像素法
1705ms
1051ms
1710ms
1340ms
450ms
32ms
內存拷貝法
54ms
33ms
26ms
20ms
7ms
0ms
指針法
28ms
17ms
14ms
10ms
3ms
0ms
 
因而可知,指針法與直接提取像素法效率竟隔兩個數量級!

 

比較以上方法優缺點:

1.整體上性能 指針法略強於內存拷貝法,直接提取像素法性能最低;

2.對大圖片處理指針法和內存拷貝法性能提高明顯,對小圖片都比較快;

3.直接提取像素法簡單易用,並且沒必要關注圖片像素格式(PixelFormat),爲安全代碼;內存拷貝法和指針法若是不改變原圖片像素格式要針對不一樣的像素格式作不一樣的處理,且爲不安全代碼。

相關文章
相關標籤/搜索