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 :)
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
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
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 –