在.NET編程中,因爲GDI+的出現,使得對於圖像的處理功能大大加強。在文經過一個簡單黑白處理實例介紹在.NET中常見的圖片處理方法和原理並比較各類方法的性能。算法
黑白處理原理:彩色圖像處理成黑白效果一般有3種算法;編程
(1).最大值法: 使每一個像素點的 R, G, B 值等於原像素點的 RGB (顏色值) 中最大的一個;c#
(2).平均值法: 使用每一個像素點的 R,G,B值等於原像素點的RGB值的平均值;數組
(3).加權平均值法: 對每一個像素點的 R, G, B值進行加權安全
自認爲第三種方法作出來的黑白效果圖像最 "真實".數據結構
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.屢次測試有少量波動。
內存拷貝法就是採用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 |
因而可知,指針法與直接提取像素法效率竟隔兩個數量級!