2010-11-08 19 views
0

J'écris une bibliothèque d'effets d'image qui expose la fonctionnalité en utilisant une notation fluide.Optimisation du traitement manuel de l'image (.Net 4)

Certains effets simples sont rapides (bordures, des ombres, etc.), mais certains d'entre

les appels à plus forte intensité de CPU sont lents (flou je vous regarde) Maintenant, en prenant comme exemple le flou, je « ai la méthode suivante:

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 

Je suis conscient que mon code peut être amélioré (tableau pas une liste pour stocker les couleurs, etc.), mais de loin le plus appel de méthode CPU-intensive est de Image.GetPixel - et je préférerais régler ça avant de toucher le reste de mon code.

Actuellement, la répartition est la suivante:

  • Image.GetPixel: 47%
  • Image.SetPixel: 13%
  • Linq moyenne: 11%
  • Divers: 29%

Cela suppose une force de flou de 1, par exemple < = 9 pixels pour chaque pixel défini.

Maintenant avec d'autres langages, j'ai lu des images à partir du disque et ai sauté au pixel approprié en faisant quelque chose comme: (Y*Width+X)*PixelBytes qui a été assez rapide. Existe-t-il un équivalent en .Net (en gardant à l'esprit que mon image peut seulement être en mémoire). Est-ce que GetPixel le fait déjà? Si oui, comment puis-je améliorer ma méthode?

Ai-je manqué un truc évident pour optimiser cela?

Solution:

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 

Et la fonction de manipuler effectivement l'image (je sais que c'est bavard mais il est rapide):

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 

L'augmentation des performances est énorme - Au moins 30 -40x plus rapide. Le plus appel CPU-intensive est maintenant de calculer la position dans le tableau à modifier:

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

Ce qui semble assez optimisé pour moi :)

Je peux maintenant traiter 20x20 brouille en moins de 3 secondes pour un 800x600 image :)

Répondre

1

Vous pouvez utiliser un bytearray comme suggéré par Ross. Vous pouvez utiliser Marshal.Copy pour copier des données d'un pointeur non géré vers un tableau d'octets. Vous accédez à la mémoire non gérée d'un bitmap avec LockBits/UnlockBits.

Mais personnellement, je préfère avoir un tableau d'un 32 bit color struct significatif. (Vous ne pouvez pas utiliser System.Drawing.Color pour cela, c'est beaucoup plus grand et plus lent). Si vous voulez copier vers un type de tableau pour lequel Marshal.Copy n'est pas défini, vous pouvez le faire comme ma fonction Pixel.LoadFromBitmap.

Vous ne devriez pas attribuer beaucoup de grands tableaux/allouer trop souvent, car le GC ne gère pas bien avec cela. Vous devrez donc peut-être implémenter la mise en pool manuelle.

+0

Merci. Je ne sais pas comment obtenir un tableau d'octets à partir d'un System.Drawing.Image - en outre, que recommanderiez-vous re: pooling/"mise en cache"/??? Comme vous pouvez le voir, je réutilise la même liste pour éviter de créer des milliers en mémoire. Pensez-vous qu'un appel préventif à GC.Collect() pourrait être bénéfique à la fin de cette méthode? – Basic

+0

Ajout d'un peu sur la copie. Je ne pense pas que ce soit une bonne idée d'utiliser GC.Collect ici. Pour la mise en commun, vous aurez besoin d'un pool global qui contient WeakReferences pour pixelarrays dont vous n'avez plus besoin et de les réutiliser quand vous en avez besoin. – CodesInChaos

+0

Cet article montre l'utilisation de LockBits et des appels graphiques pour convertir une image 8bpp en 1bpp. Le code source est assez facile à suivre. http://www.codeproject.com/KB/GDI-plus/BitonalImageConverter.aspx –

1

Vous devriez obtenir le tableau d'octets directement, sans utiliser le GetPixel.

+0

Pouvez-vous donner un exemple de comment obtenir un tableau d'octets pour un System.Drawing.Image? – Basic

+0

Non, je ne suis pas familier avec .net mais il est courant en C++ par exemple lors du traitement d'image. L'appel à getPixel est cher. Vous obtenez un pointeur sur les octets de l'image, appliquez un traitement sur le pixel courant et déplacez le pointeur sur le pixel suivant. Ce lien peut être utile: http://www.codeproject.com/KB/recipes/ImageConverter.aspx – Ross