欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#進(jìn)行圖像處理的常見(jiàn)方法(Bitmap,BitmapData,IntPtr)使用詳解

 更新時(shí)間:2024年01月28日 14:06:41   作者:wangnaisheng  
這篇文章主要為大家詳細(xì)介紹了C#進(jìn)行圖像處理的幾個(gè)常見(jiàn)方法(Bitmap,BitmapData,IntPtr)具體使用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下

介紹

在C#中,進(jìn)行圖像處理時(shí)主要會(huì)使用到 System.Drawing 命名空間中的幾個(gè)關(guān)鍵類(lèi),其中Bitmap、BitmapData和IntPtr是進(jìn)行高效像素操作的重要工具。以下是如何利用這些類(lèi)進(jìn)行圖像處理的方法概述:

1.Bitmap 類(lèi):

System.Drawing.Bitmap 是一個(gè)封裝了位圖數(shù)據(jù)的類(lèi),它允許你加載、保存、顯示和操作圖像文件或內(nèi)存中的位圖資源。

使用 new Bitmap(width, height, pixelFormat) 創(chuàng)建一個(gè)新的空白位圖。

通過(guò) Bitmap.FromFile(path) 或 Bitmap.FromStream(stream) 加載圖片文件。

提供 GetPixel(x, y) 和 SetPixel(x, y, color) 方法用于獲取和設(shè)置單個(gè)像素的顏色,但請(qǐng)注意,這種方法對(duì)于大規(guī)模圖像處理效率較低。

2.BitmapData 類(lèi):

BitmapData 代表了一個(gè)鎖定的Bitmap對(duì)象的像素?cái)?shù)據(jù)緩沖區(qū),可以直接對(duì)像素進(jìn)行讀寫(xiě)操作以提高性能。

使用 Bitmap.LockBits(Rectangle area, ImageLockMode mode, PixelFormat format, out BitmapData data) 方法可以鎖定Bitmap的部分或全部區(qū)域,從而獲得指向該區(qū)域像素?cái)?shù)據(jù)的指針。

data.Scan0 屬性是一個(gè)IntPtr類(lèi)型,指向第一個(gè)像素的數(shù)據(jù)地址。

解鎖位圖數(shù)據(jù)需要調(diào)用 Bitmap.UnlockBits(BitmapData) 來(lái)釋放資源并確保圖形設(shè)備正確更新。

3.IntPtr 類(lèi)型與 Marshal 類(lèi):

IntPtr 是一個(gè)表示非托管指針(即內(nèi)存地址)的數(shù)據(jù)類(lèi)型。

在處理BitmapData時(shí),通常會(huì)將Scan0屬性所指向的內(nèi)存塊直接映射到托管代碼中,以便于進(jìn)行快速的像素操作。

使用 System.Runtime.InteropServices.Marshal.Copy(IntPtr source, byte[] destination, int startIndex, int length) 將未托管的內(nèi)存塊內(nèi)容復(fù)制到托管數(shù)組中,這樣可以在C#中更方便地進(jìn)行像素遍歷和修改。

修改完成后,可以通過(guò)類(lèi)似方法將修改后的數(shù)組內(nèi)容復(fù)制回BitmapData所指向的內(nèi)存區(qū)域。

綜合以上,一個(gè)高效的圖像處理流程可能是這樣的:

  • 創(chuàng)建或加載Bitmap對(duì)象。
  • 鎖定Bitmap的像素?cái)?shù)據(jù),得到BitmapData。
  • 使用Marshal.Copy將BitmapData的像素?cái)?shù)據(jù)復(fù)制到本地?cái)?shù)組中。
  • 對(duì)數(shù)組進(jìn)行所需的圖像處理操作(如:調(diào)整亮度、對(duì)比度、濾鏡等)。
  • 再次使用Marshal.Copy將處理過(guò)的數(shù)組數(shù)據(jù)復(fù)制回BitmapData的內(nèi)存中。
  • 解鎖BitmapData,使得GDI+能夠自動(dòng)更新對(duì)應(yīng)的Bitmap圖像。

這種方式避免了頻繁調(diào)用GetPixel和SetPixel函數(shù)帶來(lái)的性能瓶頸,實(shí)現(xiàn)了更快的圖像處理速度。

Bitmap類(lèi)

命名空間:System.Drawing

封裝 GDI+ 位圖,此位圖由圖形圖像及其屬性的像素?cái)?shù)據(jù)組成。

Bitmap 是用于處理由像素?cái)?shù)據(jù)定義的圖像的對(duì)象。 

利用C#類(lèi)進(jìn)行圖像處理,最方便的是使用Bitmap類(lèi),使用該類(lèi)的GetPixel()與SetPixel()來(lái)訪(fǎng)問(wèn)圖像的每個(gè)像素點(diǎn)。下面是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);   
}  

可見(jiàn),Bitmap類(lèi)使用一種優(yōu)雅的方式來(lái)操作圖像,但是帶來(lái)的性能的降低卻是不可忽略的。比如對(duì)一個(gè)800*600的彩色圖像灰度化,其耗費(fèi)的時(shí)間都要以秒為單位來(lái)計(jì)算。在實(shí)際項(xiàng)目中進(jìn)行圖像處理,這種速度是決對(duì)不可忍受的。

BitmapData類(lèi)

命名空間:System.Drawing.Imaging

指定位圖圖像的屬性。BitmapData 類(lèi)由 Bitmap 類(lèi)的 LockBits 和 UnlockBits 方法使用。不可繼承。

好在我們還有BitmapData類(lèi),通過(guò)BitmapData BitmapData LockBits ( )可將 Bitmap 鎖定到系統(tǒng)內(nèi)存中。該類(lèi)的公共屬性有:

Width 獲取或設(shè)置 Bitmap 對(duì)象的像素寬度。這也可以看作是一個(gè)掃描行中的像素?cái)?shù)。

Height 獲取或設(shè)置 Bitmap 對(duì)象的像素高度。有時(shí)也稱(chēng)作掃描行數(shù)。

PixelFormat 獲取或設(shè)置返回此 BitmapData 對(duì)象的 Bitmap 對(duì)象中像素信息的格式。

Scan0 獲取或設(shè)置位圖中第一個(gè)像素?cái)?shù)據(jù)的地址。它也可以看成是位圖中的第一個(gè)掃描行。

Stride 獲取或設(shè)置 Bitmap 對(duì)象的跨距寬度(也稱(chēng)為掃描寬度)。

下面的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);  
}  
 
//或
 
Bitmap bitmap = new Bitmap("image.jpg");
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
// 對(duì) BitmapData 進(jìn)行圖像處理操作
bitmap.UnlockBits(bitmapData);

上面的代碼演示了如何用數(shù)組的方式來(lái)訪(fǎng)問(wèn)一幅圖像,而不在使用低效的GetPixel()和SetPixel()。

IntPtr

使用 IntPtr 可以直接操作圖像的內(nèi)存數(shù)據(jù)。

通過(guò)獲取圖像的句柄(IntPtr),可以使用指針操作來(lái)訪(fǎng)問(wèn)和修改圖像的像素值。

這種方法需要更高級(jí)的編程技能和對(duì)內(nèi)存管理的了解。

Bitmap bitmap = new Bitmap("image.jpg");
IntPtr ptr = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
// 使用 IntPtr 進(jìn)行圖像處理操作
bitmap.UnlockBits(ptr);

unsafe代碼

而在實(shí)際中上面的做法仍然不能滿(mǎn)足我們的要求,圖像處理是一種運(yùn)算量比較大的操作,不同于我們寫(xiě)的一般的應(yīng)用程序。我們需要的是一種性能可以同C++程序相媲美的圖像處理程序。C++是怎么提高效率的呢,答曰:指針。幸運(yùn)的是.Net也允許我們使用指針,只能在非安全代碼塊中使用指針。何謂非安全代碼?

為了保持類(lèi)型安全,默認(rèn)情況下,C# 不支持指針運(yùn)算。不過(guò),通過(guò)使用 unsafe 關(guān)鍵字,可以定義可使用指針的不安全上下文。在公共語(yǔ)言運(yùn)行庫(kù) (CLR) 中,不安全代碼是指無(wú)法驗(yàn)證的代碼。C# 中的不安全代碼不一定是危險(xiǎn)的,只是其安全性無(wú)法由 CLR 進(jìn)行驗(yàn)證的代碼。因此,CLR 只對(duì)在完全受信任的程序集中的不安全代碼執(zhí)行操作。如果使用不安全代碼,由您負(fù)責(zé)確保您的代碼不會(huì)引起安全風(fēng)險(xiǎn)或指針錯(cuò)誤。不安全代碼具有下列屬性:

  • 方法、類(lèi)型和可被定義為不安全的代碼塊。
  • 在某些情況下,通過(guò)移除數(shù)組界限檢查,不安全代碼可提高應(yīng)用程序的性能。
  • 當(dāng)調(diào)用需要指針的本機(jī)函數(shù)時(shí),需要使用不安全代碼。
  • 使用不安全代碼將引起安全風(fēng)險(xiǎn)和穩(wěn)定性風(fēng)險(xiǎn)。
  • 在 C# 中,為了編譯不安全代碼,必須用 /unsafe 編譯應(yīng)用程序。

正如《C#語(yǔ)言規(guī)范》中所說(shuō)無(wú)論從開(kāi)發(fā)人員還是從用戶(hù)角度來(lái)看,不安全代碼事實(shí)上都是一種“安全”功能。不安全代碼必須用修飾符 unsafe 明確地標(biāo)記,這樣開(kāi)發(fā)人員就不會(huì)誤用不安全功能,而執(zhí)行引擎將確保不會(huì)在不受信任的環(huán)境中執(zhí)行不安全代碼。

以下代碼演示如何借助BitmapData類(lèi)采用指針的方式來(lái)遍歷一幅圖像,這里的unsafe代碼塊中的代碼就是非安全代碼。

//創(chuàng)建圖像   
Bitmap image =  new Bitmap( "c:\\images\\image.gif" );   
//獲取圖像的BitmapData對(duì)像   
BitmapData data = image.LockBits( new Rectangle( 0 , 0 , image.Width , image.Height ) , ImageLockMode.ReadWrite  , PixelFormat.Format24bppRgb  );    
//循環(huán)處理   
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;   
       }   
}  

毫無(wú)疑問(wèn),采用這種方式是最快的,所以在實(shí)際工程中都是采用指針的方式來(lái)訪(fǎng)問(wèn)圖像像素的。

字節(jié)對(duì)齊問(wèn)題 

上例中ptr += data.Stride - data.Width * 3,表示跨過(guò)無(wú)用的區(qū)域,其原因是圖像數(shù)據(jù)在內(nèi)存中存儲(chǔ)時(shí)是按4字節(jié)對(duì)齊的,具體解釋如下:

    假設(shè)有一張圖片寬度為6,假設(shè)是Format24bppRgb格式的(每像素3字節(jié),在以下的討論中,除非特別說(shuō)明,否則Bitmap都被認(rèn)為是24位RGB)。顯然,每一行需要6*3=18個(gè)字節(jié)存儲(chǔ)。對(duì)于Bitmap就是如此。但對(duì)于BitmapData,雖然data.Width還是等于image.Width,但大概是出于顯示性能的考慮,每行的實(shí)際的字節(jié)數(shù)將變成大于等于它的那個(gè)離它最近的4的整倍數(shù),此時(shí)的實(shí)際字節(jié)數(shù)就是Stride。就此例而言,18不是4的整倍數(shù),而比18大的離18最近的4的倍數(shù)是20,所以這個(gè)data.Stride = 20。顯然,當(dāng)寬度本身就是4的倍數(shù)時(shí),data.Stride = image.Width * 3。

    畫(huà)個(gè)圖可能更好理解。R、G、B 分別代表3個(gè)原色分量字節(jié),BGR就表示一個(gè)像素。為了看起來(lái)方便我在們每個(gè)像素之間插了個(gè)空格,實(shí)際上是沒(méi)有的。X表示補(bǔ)足4的倍數(shù)而自動(dòng)插入的字節(jié)。為了符合人類(lèi)的閱讀習(xí)慣我分行了,其實(shí)在計(jì)算機(jī)內(nèi)存中應(yīng)該看成連續(xù)的一大段。

|-------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個(gè)像素的第0個(gè)分量的地址,這個(gè)地址指向的是個(gè)byte類(lèi)型,所以當(dāng)時(shí)定義為byte* ptr。行掃描時(shí),在當(dāng)前指針位置(不妨看成當(dāng)前像素的第0個(gè)顏色分量)連續(xù)取出三個(gè)值(3個(gè)原色分量。注意,0 1 2代表的次序是B G R。在取指針指向的值時(shí),貌似p[n]和p += n再取p[0]是等價(jià)的),然后下移3個(gè)位置(ptr += 3,看成指到下一個(gè)像素的第0個(gè)顏色分量)。做過(guò)Bitmap.Width次操作后,就到達(dá)了Bitmap.Width * 3的位置,應(yīng)該要跳過(guò)圖中標(biāo)記為X的字節(jié)了(共有Stride - Width * 3個(gè)字節(jié)),代碼中就是 ptr += dataIn.Stride - dataIn.Width * 3。

總結(jié)

通過(guò)閱讀本文,相信你已經(jīng)對(duì)使用C#進(jìn)行圖像處理可能用到的幾種方法有了一個(gè)了解。至于采用哪種方式,取決于你的性能要求。其中第一種方式最優(yōu)雅;第三種方式最快,但不是安全代碼;第二種方式取了個(gè)折中,保證是安全代碼的同時(shí)又提高了效率。熟悉C/C++編程的人可能會(huì)比較偏向于第三種方式,我個(gè)人也比較喜歡第三種方式。

以上就是C#進(jìn)行圖像處理的常見(jiàn)方法(Bitmap,BitmapData,IntPtr)使用詳解的詳細(xì)內(nèi)容,更多關(guān)于C#圖像處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論