在.NET編程中,由于GDI+的出現(xiàn),,使得對(duì)于圖像的處理功能大大增強(qiáng),。在文通過(guò)一個(gè)簡(jiǎn)單黑白處理實(shí)例介紹在.NET中常見(jiàn)的圖片處理方法和原理并比較各種方法的性能。
(1).最大值法: 使每個(gè)像素點(diǎn)的 R, G, B 值等于原像素點(diǎn)的 RGB (顏色值) 中最大的一個(gè),; (2).平均值法: 使用每個(gè)像素點(diǎn)的 R,G,B值等于原像素點(diǎn)的RGB值的平均值; (3).加權(quán)平均值法: 對(duì)每個(gè)像素點(diǎn)的 R, G, B值進(jìn)行加權(quán) 自認(rèn)為第三種方法做出來(lái)的黑白效果圖像最 "真實(shí)".
1.GetPixel方法GetPixel(i,j)和SetPixel(i, j,Color)可以直接得到圖像的一個(gè)像素的Color結(jié)構(gòu),,但是處理速度比較慢. /// <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做循環(huán)對(duì)性能有影響 { 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)); } } } 這里提一下,,在循環(huán)次數(shù)控制時(shí)盡量不要用i<curBitmap.Width做循環(huán)條件,而是應(yīng)當(dāng)將其取出保存到一個(gè)變量中,,這樣循環(huán)時(shí)不用每次從curBitmp中取Width屬性,,從而提高性能。 盡管如此,,直接提取像素法對(duì)大像素圖片處理力不從心,,處理一張1440*900的圖片耗時(shí)2182ms.本人配置單:
處理之前截圖:
可以直觀地看出用時(shí)間2056ms.多次測(cè)試有少許波動(dòng),。 2.內(nèi)存拷貝法內(nèi)存拷貝法就是采用System.Runtime.InteropServices.Marshal.Copy將圖像數(shù)據(jù)拷貝到數(shù)組中,然后進(jìn)行處理,,這不需要直接對(duì)指針進(jìn)行操作,,不需采用unsafe,處理速度和指針處理相差不大,,處理一副1440*900的圖像大約需要34ms,。
內(nèi)存拷貝發(fā)和指針?lè)ǘ夹栌玫降囊粋€(gè)類:BitmapData BitmapData類 BitmapData對(duì)象指定了位圖的屬性 1. Height屬性:被鎖定位圖的高度. 2. Width屬性:被鎖定位圖的高度. 3. PixelFormat屬性:數(shù)據(jù)的實(shí)際像素格式. 4. Scan0屬性:被鎖定數(shù)組的首字節(jié)地址,如果整個(gè)圖像被鎖定,,則是圖像的第一個(gè)字節(jié)地址. 5. Stride屬性:步幅,,也稱為掃描寬度.
如上圖所示,數(shù)組的長(zhǎng)度并不一定等于圖像像素?cái)?shù)組的長(zhǎng)度,還有一部分未用區(qū)域,這涉及到位圖的數(shù)據(jù)結(jié)構(gòu),系統(tǒng)要保證每行的字節(jié)數(shù)必須為4的倍數(shù). 假設(shè)有一張圖片寬度為6,因?yàn)槭荈ormat24bppRgb格式(每像素3字節(jié),。在以下的討論中,,除非特別說(shuō)明,否則Bitmap都被認(rèn)為是24位RGB)的,,顯然,,每一行需要6*3=18個(gè)字節(jié)存儲(chǔ)。對(duì)于Bitmap就是如此,。但對(duì)于BitmapData,,雖然BitmapData.Width還是等于Bitmap.Width,但大概是出于顯示性能的考慮,,每行的實(shí)際的字節(jié)數(shù)將變成大于等于它的那個(gè)離它最近的4的整倍數(shù),,此時(shí)的實(shí)際字節(jié)數(shù)就是Stride。就此例而言,,18不是4的整倍數(shù),,而比18大的離18最近的4的倍數(shù)是20,所以這個(gè)BitmapData.Stride = 20,。顯然,,當(dāng)寬度本身就是4的倍數(shù)時(shí),BitmapData.Stride = Bitmap.Width * 3,。畫個(gè)圖可能更好理解(此圖僅代表PixelFormat= PixelFormat. Format24bppRgb時(shí)適用,,每個(gè)像素占3個(gè)字節(jié)共24位)。R,、G,、B 分別代表3個(gè)原色分量字節(jié),,BGR就表示一個(gè)像素。為了看起來(lái)方便我在每個(gè)像素之間插了個(gè)空格,,實(shí)際上是沒(méi)有的,。X表示補(bǔ)足4的倍數(shù)而自動(dòng)插入的字節(jié),。為了符合人類的閱讀習(xí)慣我分行了,其實(shí)在計(jì)算機(jī)內(nèi)存中應(yīng)該看成連續(xù)的一大段,。Scan0||-------Stride-----------||-------Width---------| ?。拢牵摇。拢牵摇,。拢牵摇,。拢牵摇。拢牵摇,。拢牵摇,。兀兀拢牵摇。拢牵摇,。拢牵摇,。拢牵摇。拢牵摇,。拢牵摇,。兀兀拢牵摇。拢牵摇,。拢牵摇。拢牵摇,。拢牵摇,。拢牵摇,。兀?則對(duì)于Format24bppRgb格式,,滿足: BitmapData.Width*3 + 每行未使用空間(上圖的XX)=BitmapData.Stride
同理,,很容易推倒對(duì)于Format32bppRgb或Format32bppPArgb格式,滿足: BitmapData.Width*4 + 每行未使用空間(上圖的XX)=BitmapData.Stride /// <summary> /// 內(nèi)存拷貝法 /// </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.指針?lè)?/strong> 指針在c#中屬于unsafe操作,需要用unsafe括起來(lái)進(jìn)行處理,,速度最快,,處理一副180*180的圖像大約需要18ms。
采用byte* ptr = (byte*)(bmpData.Scan0); 獲取圖像數(shù)據(jù)根位置的指針,,然后用bmpData.Scan0獲取圖像的掃描寬度,,就可以進(jìn)行指針操作了。
/// <summary> /// 指針?lè)? /// </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格式每個(gè)像素占3字節(jié) } ptr += bmpData.Stride - bmpData.Width * 3 ;//每行讀取到最后“有用”數(shù)據(jù)時(shí),,跳過(guò)未使用空間XX } curBitmap.UnlockBits(bmpData); } 以下是多組測(cè)試數(shù)據(jù):
由此可見(jiàn),,指針?lè)ㄅc直接提取像素法效率竟隔兩個(gè)數(shù)量級(jí)!
比較以上方法優(yōu)缺點(diǎn):1.總體上性能 指針?lè)詮?qiáng)于內(nèi)存拷貝法,直接提取像素法性能最低,;2.對(duì)大圖片處理指針?lè)ê蛢?nèi)存拷貝法性能提升明顯,,對(duì)小圖片都比較快;3.直接提取像素法簡(jiǎn)單易用,,而且不必關(guān)注圖片像素格式(PixelFormat),為安全代碼,;內(nèi)存拷貝法和指針?lè)ㄈ绻桓淖冊(cè)瓐D片像素格式要針對(duì)不同的像素格式做不同的處理,且為不安全代碼,。 |
|
來(lái)自: _明心見(jiàn)性_ > 《C#圖像處理》