Оптимизация ручной обработки изображений (.Net 4) - PullRequest
0 голосов
/ 08 ноября 2010

Я пишу библиотеку эффектов изображений, которая предоставляет функциональные возможности с использованием беглой записи.

Некоторые простые эффекты бывают быстрыми (границы, тени и т. Д.), Но некоторые из более интенсивных вызовов ЦП медленные (размытие, я смотрю на вас)

Теперь, взяв размытие в качестве примера, я получил следующий метод:

    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

Я знаю, что мой код можно улучшить (массив, а не список для хранения цветов и т. Д.), Но с помощью FAR наиболее ресурсоемким вызовом метода является Image.GetPixel - и я бы предпочел исправить это раньше касаясь остальной части моего кода.

В настоящее время разбивка:

  • Image.GetPixel: 47%
  • Image.SetPixel: 13%
  • Средний балл: 11%
  • Разное: 29%

Предполагается, что сила размытия равна 1, например, чтение <= 9 пикселей для каждого установленного пикселя. </p>

Теперь с другими языками я прочитал изображения с диска и перешел к соответствующему пикселю, выполнив что-то вроде: (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 раз быстрее. Наиболее требовательным к процессору вызовом сейчас является вычисление позиции в массиве для изменения:

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

Что мне кажется довольно оптимизированным:)

Теперь я могу обработать 20x20 размытия менее чем за 3 секунды для изображения 800x600:)

Ответы [ 2 ]

1 голос
/ 08 ноября 2010

Вы можете использовать байтовый массив, как предложил Росс. Вы можете использовать Marshal.Copy для копирования данных из неуправляемого указателя в байтовый массив. Вы получаете доступ к неуправляемой памяти растрового изображения с помощью LockBits / UnlockBits.

Но лично я предпочитаю иметь массив осмысленных 32-битных цветовых структур . (Вы не можете использовать System.Drawing.Color для этого, он намного больше и медленнее). Если вы хотите скопировать в тип массива, для которого не определен Marshal.Copy, вы можете сделать это, как моя Pixel.LoadFromBitmap функция.

Вы не должны выделять много больших массивов / распределять слишком часто, так как GC плохо справляется с этим. Поэтому вам может потребоваться реализовать ручной пул.

1 голос
/ 08 ноября 2010

Вы должны получить массив байтов напрямую, не используя GetPixel.

...