2010-11-08 49 views
0

我正在寫一個圖像效果庫,它使用流利的表示法公開功能。優化手動圖像處理(.Net 4)

一些簡單的效果是快速(邊框,陰影等),但一些更多的CPU調用都是慢(模糊,我看着你)現在

,以模糊爲例,我「已經得到了下面的方法:

Public Function Process(ByRef ImageEffect As Interfaces.IImageEffect) As Interfaces.IImageEffect Implements Interfaces.IEffect.Process 
     Dim Image As Bitmap = CType(ImageEffect.Image, Bitmap) 
     Dim SourceColors As New List(Of Drawing.Color) 
     For X = 0 To ImageEffect.Image.Width - 1 
      For Y = 0 To ImageEffect.Image.Height - 1 
       SourceColors.Clear() 
       For ScanX = Math.Max(0, X - Strength) To Math.Min(Image.Width - 1, X + Strength) 
        For ScanY = Math.Max(0, Y - Strength) To Math.Min(Image.Height - 1, Y + Strength) 
         SourceColors.Add(Image.GetPixel(ScanX, ScanY)) 
        Next 
       Next 
       Dim NewColor = Color.FromArgb(
       CInt(SourceColors.Average(Function(Z) Z.A)), 
       CInt(SourceColors.Average(Function(Z) Z.R)), 
       CInt(SourceColors.Average(Function(Z) Z.G)), 
       CInt(SourceColors.Average(Function(Z) Z.B)) 
       ) 

       Image.SetPixel(X, Y, NewColor) 

      Next 
     Next 
     Return ImageEffect 
    End Function 

我知道,我的代碼可以改進(數組不保存顏色列表等),但迄今爲止最CPU密集型的方法調用是Image.GetPixel - 而我更願意在接觸我的其他代碼之前修復此問題。

目前擊穿是:

  • Image.GetPixel:47%
  • Image.SetPixel:13%
  • LINQ的平均:11%
  • 其他:29%

假設模糊強度爲1,例如每個設置像素的讀數爲< = 9像素。

現在用其他語言,我已經從磁盤讀取圖像,並通過執行如下操作跳到適當的像素:(Y*Width+X)*PixelBytes這已經非常快。 .Net中是否有相同的內容(記住我的圖像可能只在內存中)。 GetPixel已經這樣做了嗎?如果是這樣,我該如何改進我的方法?

我錯過了一個明顯的技巧來優化這個?

解決方案:

Public Function Process(ByRef ImageEffect As Interfaces.IImageEffect) As Interfaces.IImageEffect Implements Interfaces.IEffect.Process 
    Dim bmp = DirectCast(ImageEffect.Image, Bitmap) 

    '' Lock the bitmap's bits. 
    Dim Dimensions As New Rectangle(0, 0, bmp.Width, bmp.Height) 
    Me.Dimensions = Dimensions 
    Dim bmpData As System.Drawing.Imaging.BitmapData = bmp.LockBits(Dimensions, Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat) 

    '' Get the address of the first line. 
    Dim ptr As IntPtr = bmpData.Scan0 

    '' Declare an array to hold the bytes of the bitmap. 
    '' This code is specific to a bitmap with 24 bits per pixels. 
    Dim bytes As Integer = Math.Abs(bmpData.Stride) * bmp.Height 
    Dim ARGBValues(bytes - 1) As Byte 

    '' Copy the ARGB values into the array. 
    System.Runtime.InteropServices.Marshal.Copy(ptr, ARGBValues, 0, bytes) 

    '' Call the function to actually manipulate the data (next code block) 
    ProcessRaw(bmpData, ARGBValues) 

    System.Runtime.InteropServices.Marshal.Copy(ARGBValues, 0, ptr, bytes) 

    bmp.UnlockBits(bmpData) 

    Return ImageEffect 
End Function 

而且實際操作圖像(我知道這是冗長,但它的快速)功能:

Protected Overrides Sub ProcessRaw(ByVal BitmapData As System.Drawing.Imaging.BitmapData, ByRef ARGBData() As Byte) 
     Dim SourceColors As New List(Of Byte()) 
     For Y = 0 To Dimensions.Height - 1 
      For X = 0 To Dimensions.Width - 1 
       Dim FinalA = 0.0 
       Dim FinalR = 0.0 
       Dim FinalG = 0.0 
       Dim FinalB = 0.0 

       SourceColors.Clear() 
       Dim SamplesCount = 
        (Math.Min(Dimensions.Height - 1, Y + Strength) - Math.Max(0, Y - Strength) + 1) * 
        (Math.Min(Dimensions.Width - 1, X + Strength) - Math.Max(0, X - Strength) + 1) 
       For ScanY = Math.Max(0, Y - Strength) To Math.Min(Dimensions.Height - 1, Y + Strength) 
        For ScanX = Math.Max(0, X - Strength) To Math.Min(Dimensions.Width - 1, X + Strength) 
         Dim StartPos = CalculatePixelPosition(ScanX, ScanY) 
         FinalB += ARGBData(StartPos + 0)/SamplesCount 
         FinalG += ARGBData(StartPos + 1)/SamplesCount 
         FinalR += ARGBData(StartPos + 2)/SamplesCount 
         FinalA += ARGBData(StartPos + 3)/SamplesCount 
        Next 
       Next 

       Dim OutputPos = CalculatePixelPosition(X, Y) 
       ARGBData(OutputPos + 0) = CByte(CInt(FinalB)) 
       ARGBData(OutputPos + 1) = CByte(CInt(FinalG)) 
       ARGBData(OutputPos + 2) = CByte(CInt(FinalR)) 
       ARGBData(OutputPos + 3) = CByte(CInt(FinalA)) 

      Next 
     Next 
    End Sub 

性能的提高是巨大的 - 至少30快40倍。最CPU密集現在的口號是計算陣列中的位置進行修改:

Protected Function CalculatePixelPosition(ByVal X As Integer, ByVal Y As Integer) As Integer 
    Return ((Dimensions.Width * Y) + X) * 4 
End Function 

這似乎相當優化,我:)

我現在可以處理20×20模糊下3秒一個800x600圖像:)

回答

1

您可以使用Ross建議的字節陣列。您可以使用Marshal.Copy將數據從非託管指針複製到字節數組。您可以使用LockBits/UnlockBits訪問位圖的非託管內存。

但是我個人更喜歡有一個有意義的數組32 bit color struct。 (你不能使用System.Drawing.Color,它更大更慢)。如果你想複製到未定義Marshal.Copy的數組類型,可以像我的Pixel.LoadFromBitmap函數那樣執行。

由於GC不能很好地處理這個問題,所以不應該將其分配給很多大數組/分配過多。所以你可能需要實現手動池。

+0

謝謝。我不確定如何從System.Drawing.Image獲取一個字節數組 - 另外,你會推薦什麼:池/「緩存」/?正如你所看到的,我重新使用相同的List來避免在內存中創建數千個。你認爲對GC.Collect()的先發制人的調用在這種方法的結尾可能是有益的嗎? – Basic 2010-11-08 12:04:26

+0

添加了一些關於複製的內容。我不認爲在這裏使用GC.Collect是個好主意。爲了組合,您需要一些全局池,它將WeakReference保存到您不再需要的像素中,並在需要時重用它們。 – CodesInChaos 2010-11-08 12:27:07

+0

本文介紹如何使用LockBits和圖形調用將8bpp圖像轉換爲1bpp。源代碼很容易遵循。 http://www.codeproject.com/KB/GDI-plus/BitonalImageConverter.aspx – 2010-11-08 12:51:18

1

你應該直接得到Bytes數組,而不是使用GetPixel。

+0

你可以舉一個如何獲取System.Drawing.Image的字節數組的例子嗎? – Basic 2010-11-08 11:39:31

+0

不,我不熟悉.net,但在進行圖像處理時,它在C++中很常見。對getPixel的調用非常昂貴。您獲取指向圖像字節的指針,對當前像素應用一些處理並將指針移動到下一個像素。此鏈接可以提供幫助:http://www.codeproject.com/KB/recipes/ImageConverter.aspx – Ross 2010-11-08 13:51:07