C#進行影像處理的幾種方法(Bitmap,BitmapData,IntPtr)

來源:互聯網
上載者:User

標籤:

轉自 http://blog.sina.com.cn/s/blog_628821950100wh9w.html

C#進行影像處理的幾種方法

本文討論了C#影像處理中Bitmap類、BitmapData類和unsafe代碼的使用以及位元組對齊問題。

Bitmap類

命名空間:System.Drawing

封裝 GDI+ 位元影像,此位元影像由圖形映像及其屬性的像素資料群組成。Bitmap 是用於處理由像素資料定義的映像的對象。 

利用C#類進行影像處理,最方便的是使用Bitmap類,使用該類的GetPixel()與SetPixel()來訪問映像的每個像素點。下面是MSDN中的範例程式碼:

public void GetPixel_Example(PaintEventArgs e)   {       // Create a Bitmap object from an image file.       Bitmap myBitmap = new Bitmap("Grapes.jpg");       // Get the color of a pixel within myBitmap.       Color pixelColor = myBitmap.GetPixel(50, 50);       // Fill a rectangle with pixelColor.       SolidBrush pixelBrush = new SolidBrush(pixelColor);       e.Graphics.FillRectangle(pixelBrush, 0, 0, 100, 100);   }  

可見,Bitmap類使用一種優雅的方式來操作映像,但是帶來的效能的降低卻是不可忽略的。比如對一個800*600的彩色映像灰階化,其耗費的時間都要以秒為單位來計算。在實際項目中進行影像處理,這種速度是決對不可忍受的。

BitmapData類

命名空間:System.Drawing.Imaging

指定位元影像映像的屬性。BitmapData 類由 Bitmap 類的 LockBits 和 UnlockBits 方法使用。不可繼承。

    好在我們還有BitmapData類,通過BitmapData BitmapData LockBits ( )可將 Bitmap 鎖定到系統記憶體中。該類的公用屬性有:

  • Width           擷取或設定 Bitmap 對象的像素寬度。這也可以看作是一個掃描行中的像素數。
  • Height          擷取或設定 Bitmap 對象的像素高度。有時也稱作掃描行數。
  • PixelFormat  
    擷取或設定返回此 BitmapData 對象的 Bitmap 對象中像素資訊的格式。
  • Scan0            擷取或設定位元影像中第一個像素資料的地址。它也可以看成是位元影像中的第一個掃描行。
  • Stride            擷取或設定 Bitmap 對象的跨距寬度(也稱為掃描寬度)。

    下面的MSDN中的範例程式碼示範了如何使用 PixelFormat、Height、Width 和 Scan0 屬性;LockBits 和 UnlockBits 方法;以及 ImageLockMode 枚舉。

private void LockUnlockBitsExample(PaintEventArgs e)   {      // Create a new bitmap.       Bitmap bmp = new Bitmap("c:\\fakePhoto.jpg");      // Lock the bitmap‘s bits.        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);       System.Drawing.Imaging.BitmapData bmpData =           bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,           bmp.PixelFormat);       // Get the address of the first line.      IntPtr ptr = bmpData.Scan0;      // Declare an array to hold the bytes of the bitmap.       int bytes  = bmpData.Stride * bmp.Height;       byte[] rgbValues = new byte[bytes];      // Copy the RGB values into the array.       System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);      // Set every red value to 255.        for (int counter = 0; counter < rgbValues.Length; counter+=3)           rgbValues[counter] = 255;       // Copy the RGB values back to the bitmap       System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);      // Unlock the bits.       bmp.UnlockBits(bmpData);      // Draw the modified image.       e.Graphics.DrawImage(bmp, 0, 150);  }  

上面的代碼示範了如何用數組的方式來訪問一幅映像,而不在使用低效的GetPixel()和SetPixel()。

 

unsafe代碼

    而在實際中上面的做法仍然不能滿足我們的要求,影像處理是一種運算量比較大的操作,不同於我們寫的一般的應用程式。我們需要的是一種效能可以同C++程式相媲美的影像處理程式。C++是怎麼提高效率的呢,答曰:指標。幸運的是.Net也允許我們使用指標,只能在非安全的程式碼塊中使用指標。何謂非安全的程式碼?

    為了保持型別安全,預設情況下,C# 不支援指標運算。不過,通過使用 unsafe 關鍵字,可以定義可使用指標的不安全上下文。在公用語言運行庫 (CLR) 中,不安全的程式碼是指無法驗證的代碼。C# 中的不安全的程式碼不一定是危險的,只是其安全性無法由 CLR 進行驗證的代碼。因此,CLR 只對在完全受信任的程式集中的不安全的程式碼執行操作。如果使用不安全的程式碼,由您負責確保您的代碼不會引起安全風險或指標錯誤。不安全的程式碼具有下列屬性:

  • 方法、類型和可被定義為不安全的代碼塊。
  • 在某些情況下,通過移除數組界限檢查,不安全的程式碼可提高應用程式的效能。
  • 當調用需要指標的本機函數時,需要使用不安全的程式碼。
  • 使用不安全的程式碼將引起安全風險和穩定性風險。
  • 在 C# 中,為了編譯不安全的程式碼,必須用 /unsafe 編譯應用程式。

    正如《C#語言規範》中所說無論從開發人員還是從使用者角度來看,不安全的程式碼事實上都是一種“安全”功能。不安全的程式碼必須用修飾符 unsafe 明確地標記,這樣開發人員就不會誤用不安全功能,而執行引擎將確保不會在不受信任的環境中執行不安全的程式碼。

    以下代碼示範如何藉助BitmapData類採用指標的方式來遍曆一幅映像,這裡的unsafe代碼塊中的代碼就是非安全的程式碼。

//建立映像   Bitmap image =  new Bitmap( "c:\\images\\image.gif" );   //擷取映像的BitmapData對像   BitmapData data = image.LockBits( new Rectangle( 0 , 0 , image.Width , image.Height ) , ImageLockMode.ReadWrite  , PixelFormat.Format24bppRgb  );    //迴圈處理   unsafe   {           byte* ptr = ( byte* )( data.Scan0 );           for( int i = 0 ; i < data.Height ; i ++ )          {             for( int j = 0 ;  j < data.Width ;  j ++ )              {                // write the logic implementation here                ptr += 3;                }            ptr += data.Stride - data.Width * 3;          }   }  

毫無疑問,採用這種方式是最快的,所以在實際工程中都是採用指標的方式來訪問映像像素的。

 

位元組對齊問題 
    上例中ptr += data.Stride - data.Width * 3,表示跨過無用的地區,其原因是映像資料在記憶體中儲存時是按4位元組對齊的,具體解釋如下:

    假設有一張圖片寬度為6,假設是Format24bppRgb格式的(每像素3位元組,在以下的討論中,除非特別說明,否則Bitmap都被認為是24位RGB)。顯然,每一行需要6*3=18個位元組儲存。對於Bitmap就是如此。但對於BitmapData,雖然data.Width還是等於image.Width,但大概是出於顯示效能的考慮,每行的實際的位元組數將變成大於等於它的那個離它最近的4的整倍數,此時的實際位元組數就是Stride。就此例而言,18不是4的整倍數,而比18大的離18最近的4的倍數是20,所以這個data.Stride = 20。顯然,當寬度本身就是4的倍數時,data.Stride = image.Width * 3。

    畫個圖可能更好理解。R、G、B 分別代表3個原色分量位元組,BGR就表示一個像素。為了看起來方便我在們每個像素之間插了個空格,實際上是沒有的。X表示補足4的倍數而自動插入的位元組。為了符合人類的閱讀習慣我分行了,其實在電腦記憶體中應該看成連續的一大段。

|-------Stride-----------| 
|-------Width---------| 
Scan0: 
BGR BGR BGR BGR BGR BGR XX 
BGR BGR BGR BGR BGR BGR XX 
BGR BGR BGR BGR BGR BGR XX 


.

    首先用data.Scan0找到第0個像素的第0個分量的地址,這個地址指向的是個byte類型,所以當時定義為byte* ptr。行掃描時,在當前指標位置(不妨看成當前像素的第0個顏色分量)連續取出三個值(3個原色分量。注意,0 1 2代表的次序是B G R。在取指標指向的值時,貌似p[n]和p += n再取p[0]是等價的),然後下移3個位置(ptr += 3,看成指到下一個像素的第0個顏色分量)。做過Bitmap.Width次操作後,就到達了Bitmap.Width * 3的位置,應該要跳過圖中標記為X的位元組了(共有Stride - Width * 3個位元組),代碼中就是 ptr += dataIn.Stride - dataIn.Width * 3。

    通過閱讀本文,相信你已經對使用C#進行影像處理可能用到的幾種方法有了一個瞭解。至於採用哪種方式,取決於你的效能要求。其中第一種方式最優雅;第三種方式最快,但不是安全的程式碼;第二種方式取了個折中,保證是安全的程式碼的同時又提高了效率。熟悉C/C++編程的人可能會比較偏向於第三種方式,我個人也比較喜歡第三種方式。

(轉)C#進行影像處理的幾種方法(Bitmap,BitmapData,IntPtr)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.