C# Bitmap圖像處理(含增強對比度的三種方法)
Bitmap類
Bitmap對象封裝瞭GDI+中的一個位圖,此位圖由圖形圖像及其屬性的像素數據組成.因此Bitmap是用於處理由像素數據定義的圖像的對象.該類的主要方法和屬性如下:
1. GetPixel方法和SetPixel方法:獲取和設置一個圖像的指定像素的顏色.
2. PixelFormat屬性:返回圖像的像素格式.
3. Palette屬性:獲取和設置圖像所使用的顏色調色板.
4. Height Width屬性:返回圖像的高度和寬度.
5. LockBits方法和UnlockBits方法:分別鎖定和解鎖系統內存中的位圖像素.在基於像素點的圖像處理方法中使用LockBits和UnlockBits是一個很好的方式,這兩種方法可以使我們指定像素的范圍來控制位圖的任意一部分,從而消除瞭通過循環對位圖的像素逐個進行處理,每調用LockBits之後都應該調用一次UnlockBits.
BitmapData類
BitmapData對象指定瞭位圖的屬性
1. Height屬性:被鎖定位圖的高度.
2. Width屬性:被鎖定位圖的寬度.
3. PixelFormat屬性:數據的實際像素格式.
4. Scan0屬性:被鎖定數組的首字節地址,如果整個圖像被鎖定,則是圖像的第一個字節地址.
5. Stride屬性:步幅,也稱為掃描寬度.
這裡要重點說說Stride屬性,這個和Width有什麼區別呢,可以這麼說,如果你的圖片大小也就是圖片字節是4的整數倍,那麼Stride與Width是相等的,否則Stride就是大於Width的最小4的整數倍。在處理過程中,Stride肯定是4的整數倍,這裡是個坑啊。。。
例1:有一個一維像素點陣數組,裡面放的是每個像素點的灰度值,知道寬和高,要轉換成bitmap
/// <summary> /// 像素點陣轉換為bitmap /// </summary> /// <param name="rawValues">byte[]數組</param> /// <param name="width">圖片的寬度</param> /// <param name="height">圖片的高度</param> /// <returns>bitmap圖片</returns> public static Bitmap ToGrayBitmap(byte[] rawValues, int width, int height) { Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format8bppIndexed); BitmapData bmpData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format8bppIndexed); 獲取圖像參數 //bmpData.Stride = width; int stride = bmpData.Stride; // 掃描線的寬度 int offset = stride - width; // 顯示寬度與掃描線寬度的間隙 IntPtr iptr = bmpData.Scan0; // 獲取bmpData的內存起始位置 int scanBytes = stride * height;// 用stride寬度,表示這是內存區域的大小 下面把原始的顯示大小字節數組轉換為內存中實際存放的字節數組 int posScan = 0, posReal = 0;// 分別設置兩個位置指針,指向源數組和目標數組 byte[] pixelValues = new byte[scanBytes]; //為目標數組分配內存 for (int x = 0; x < height; x++) { 下面的循環節是模擬行掃描 for (int y = 0; y < width; y++) { pixelValues[posScan++] = rawValues[posReal++]; } posScan += offset; //行掃描結束,要將目標位置指針移過那段“間隙” } 用Marshal的Copy方法,將剛才得到的內存字節數組復制到BitmapData中 System.Runtime.InteropServices.Marshal.Copy(pixelValues, 0, iptr, scanBytes); bmp.UnlockBits(bmpData); // 解鎖內存區域 下面的代碼是為瞭修改生成位圖的索引表,從偽彩修改為灰度 ColorPalette tempPalette; using (Bitmap tempBmp = new Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format8bppIndexed)) { tempPalette = tempBmp.Palette; } for (int i = 0; i < 256; i++) { tempPalette.Entries[i] = System.Drawing.Color.FromArgb(i, i, i); } bmp.Palette = tempPalette; 算法到此結束,返回結果 return bmp; }
至於24位位圖數據其實就是 一個像素點有rgb三個值而已,道理一樣。
例2::根據圖片得到他的灰度數組
//8位位圖得到除去文件頭信息的一位灰度數組 BitmapData bmpData = map.LockBits(new System.Drawing.Rectangle(0, 0, map.Width, map.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format8bppIndexed); 獲取圖像參數 int stride = bmpData.Stride; // 掃描線的寬度 int offset = stride - map.Width; // 顯示寬度與掃描線寬度的間隙 IntPtr iptr = bmpData.Scan0; // 獲取bmpData的內存起始位置 int scanBytes = stride * map.Height;// 用stride寬度,表示這是內存區域的大小 下面把原始的顯示大小字節數組轉換為內存中實際存放的字節數組 mapdata = new byte[scanBytes]; //為目標數組分配內存 System.Runtime.InteropServices.Marshal.Copy(iptr, mapdata, 0, scanBytes); //copy內存中數據到數組中
這裡對與bitmapdata的操作方式是ReadOnly
下面的三個例子分別基於像素(GetPixel和SetPixel)、基於內存、基於指針這三種方法增強圖片對比度。均測試通過
運行時間:
1)基於像素:400-600ms
2)基於內存:17-18ms
3)基於指針:20-23ms
利用LUT,應該可以進一步減少運行時間
// 第一種方法:像素提取法。速度慢 public Bitmap MethodBaseOnPixel(Bitmap bitmap,int degree) { Color curColor; int grayR, grayG, grayB; double Deg = (100.0 + degree) / 100.0; for (int i = 0; i < bitmap.Width; i++) { for (int j = 0; j < bitmap.Height; j++) { curColor = bitmap.GetPixel(i, j); grayR =Convert.ToInt32((((curColor.R / 255.0 - 0.5) * Deg + 0.5)) * 255); grayG = Convert.ToInt32((((curColor.G / 255.0 - 0.5) * Deg + 0.5)) * 255); grayB = Convert.ToInt32((((curColor.B / 255.0 - 0.5) * Deg + 0.5)) * 255); if (grayR < 0) grayR = 0; else if (grayR > 255) grayR = 255; if (grayB < 0) grayB = 0; else if (grayB > 255) grayB = 255; if (grayG < 0) grayG = 0; else if (grayG > 255) grayG = 255; bitmap.SetPixel(i, j, Color.FromArgb(grayR, grayG, grayB)); } } return bitmap; }
// 第二種方法:基於內存 public unsafe Bitmap MethodBaseOnMemory(Bitmap bitmap, int degree) { if (bitmap == null) { return null; } double Deg = (100.0 + degree) / 100.0; int width = bitmap.Width; int height = bitmap.Height; int length = height * 3 * width; byte[] RGB = new byte[length]; BitmapData data = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); System.IntPtr Scan0 = data.Scan0; System.Runtime.InteropServices.Marshal.Copy(Scan0, RGB, 0, length); double gray = 0; for (int i = 0; i < RGB.Length; i += 3) { for (int j = 0; j < 3; j++) { gray = (((RGB[i + j] / 255.0 -0.5) * Deg+0.5)) * 255.0; if (gray > 255) gray = 255; if (gray < 0) gray = 0; RGB[i + j] = (byte) gray; } } System.Runtime.InteropServices.Marshal.Copy(RGB, 0, Scan0, length);// 此處Copy是之前Copy的逆操作 bitmap.UnlockBits(data); return bitmap; } }
//第三種方法:基於指針 public unsafe Bitmap MethodBaseOnPtr(Bitmap b, int degree) { if (b == null) { return null; } try { double num = 0.0; double num2 = (100.0 + degree) / 100.0; num2 *= num2; int width = b.Width; int height = b.Height; BitmapData bitmapdata = b.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); byte* numPtr = (byte*)bitmapdata.Scan0; int offset = bitmapdata.Stride - (width * 3); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { for (int k = 0; k < 3; k++) { num = ((((((double)numPtr[k]) / 255.0) - 0.5) * num2) + 0.5) * 255.0; if (num < 0.0) { num = 0.0; } if (num > 255.0) { num = 255.0; } numPtr[k] = (byte)num; } numPtr += 3; } numPtr += offset; } b.UnlockBits(bitmapdata); return b; } catch { return b; } }
參考:
1. http://blog.csdn.net/jiangxinyu/article/details/6222302 (此博客的代碼中有錯誤,精簡代碼基於內存處理的copy順序有問題)
2. http://www.pin5i.com/showtopic-20228.html // C# 特效圖片:霧化、浮雕等。
到此這篇關於C# Bitmap圖像處理(含增強對比度的三種方法)的文章就介紹到這瞭,更多相關C# Bitmap圖像處理內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- C# Bitmap圖像處理加速的實現
- C# wpf Bitmap轉換成WriteableBitmap的方法
- C#合並BitMap圖像生成超大bitmap
- c# Bitmap轉bitmapImage高效方法
- C#實現簡易灰度圖和酷炫HeatMap熱力圖winform(附DEMO)