代码之家  ›  专栏  ›  技术社区  ›  Basic

优化手动图像处理(.NET 4)

  •  0
  • Basic  · 技术社区  · 15 年前

    我正在编写一个图像效果库,它使用流畅的表示法公开功能。

    一些简单的效果很快(边框、阴影等),但是一些CPU密集型的调用很慢(我在看你)

    现在,以blur为例,我有以下方法:

        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
    

    这对我来说似乎很乐观。)

    我现在可以在3秒钟内处理20x20模糊的800x600图像:)

    2 回复  |  直到 15 年前
        1
  •  1
  •   CodesInChaos    15 年前

    你可以像罗斯建议的那样使用bytearray。你可以用 Marshal.Copy 将数据从非托管指针复制到字节数组。您可以使用lockbits/unlockbits访问位图的非托管内存。

    但就我个人而言,我更喜欢一系列有意义的 32 bit color struct . (不能使用system.drawing.color,因为它会变得更大、更慢)。如果要复制到未定义marshal.copy的数组类型,可以像我的 Pixel.LoadFromBitmap 功能。

    您不应该太频繁地分配给许多大数组/分配,因为GC不能很好地处理这个问题。所以您可能需要实现手动池。

        2
  •  1
  •   Ross    15 年前

    您应该直接获取字节数组,而不是使用getpixel。