Methoden effizienter gestalten

  • VB.NET

Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von wrack solutionist.

    Methoden effizienter gestalten

    Hallo,

    ich habe zwei Methoden, welche viel zu lange rechnen, leider sehe ich nirgends Potenzial sie schneller zu gestalten, vielleicht seht ihr Ansätze. Die Methoden sollen ein 16 bit S/W Bild in ein 8 Bit Bild umwandeln.

    VB.NET-Quellcode

    1. Private Function Fits2Byte(ByVal arImage As System.Array, _
    2. ByVal trackGammaValue As Integer, ByVal trackMinimumImageValue As Integer, _
    3. ByVal trackMaximumImageValue As Integer) As Byte(,)
    4. Trace.WriteLine("$ Fits2Byte")
    5. Trace.WriteLine("$ Fits2Byte Start:" & Now.ToString)
    6. 'black(level)
    7. Dim intBlackLevel As Integer = trackMinimumImageValue
    8. 'Scale, white - black
    9. Dim intScale As Integer = trackMaximumImageValue - intBlackLevel
    10. Dim intRowsImage As Integer = arImage.GetLength(1)
    11. Dim intColumnImage As Integer = arImage.GetLength(0)
    12. Dim bytearImage(,) As Byte
    13. 'Helligkeitswerte in 16Bit
    14. Dim intOriginalLumValue As Integer
    15. Dim j As Integer
    16. 'Gammastufen()
    17. Dim byteGamma As Byte() = New Byte(255) {}
    18. Dim dblGamma As Double = CDbl(trackGammaValue)
    19. ReDim bytearImage(intColumnImage, intRowsImage)
    20. 'Helligkeiten
    21. Dim byteLum(65535) As Int32
    22. For i As Integer = 0 To 255
    23. byteGamma(i) = CByte((Math.Pow(CDbl(i) / 256.0R, dblGamma) * 256.0R)) 'Gammakurve erstellen
    24. Next
    25. Trace.WriteLine("$ Lum16ToLum8")
    26. Trace.WriteLine("$ Lum16ToLum8 Start:" & Now.ToString)
    27. For i = 0 To 65535
    28. byteLum(i) = byteGamma(Lum16ToLum8(i, intBlackLevel, intScale)) 'Einteilung der Helligkeiten von 16 Bit in 8 Bit
    29. Next
    30. Trace.WriteLine("$ Lum16ToLum8 Ende:" & Now.ToString)
    31. ' Bis hierher gehts schnell!
    32. 'Ab hier bis zum Ende benötigt die Methode gute 2sec!
    33. For i = 0 To intRowsImage - 1
    34. For j = 0 To intColumnImage - 1
    35. intOriginalLumValue = Convert.ToInt32(arImage.GetValue(j, i))
    36. 'convert 16 bit signed to 16 bit unsigned
    37. If intOriginalLumValue < 0 Then
    38. intOriginalLumValue += 65535
    39. End If
    40. bytearImage(j, i) = byteLum(intOriginalLumValue)
    41. Next
    42. Next
    43. Trace.WriteLine("$ Fits2Byte Ende:" & Now.ToString)
    44. Return bytearImage
    45. End Function


    Diezweite Methode ist folgende, diese benötigt gute 4sec.

    VB.NET-Quellcode

    1. Try
    2. Dim imgPtr As IntPtr = bmpdata.Scan0 'Zeiger auf Bild
    3. Dim tmpImage As Byte
    4. Dim Pixel As Integer = 0
    5. Dim offs As Integer
    6. For intRows As Integer = 0 To bytearImage.GetUpperBound(1) - 1
    7. For intColumn As Integer = 0 To bytearImage.GetUpperBound(0) - 1
    8. Try
    9. offs = (intRows * bmpImage.Width + intColumn) * bytesPerPixel
    10. 'offs = (intRows + intColumn * bmpImage.Width) * bytesPerPixel
    11. tmpImage = bytearImage(intColumn, intRows)
    12. System.Runtime.InteropServices.Marshal.WriteByte(bmpdata.Scan0, offs, tmpImage)
    13. System.Runtime.InteropServices.Marshal.WriteByte(bmpdata.Scan0, offs + 1, tmpImage)
    14. System.Runtime.InteropServices.Marshal.WriteByte(bmpdata.Scan0, offs + 2, tmpImage)
    15. Catch ex As Exception
    16. MessageBox.Show("$ Byte2Image: Fehler in Reihe:" & intRows.ToString & " Spalte: " & intColumn.ToString & vbNewLine & _
    17. " tmpImagevalue: " & tmpImage.ToString & " Offset: " & offs.ToString & vbNewLine & _
    18. ex.Message)
    19. Trace.WriteLine("$ Byte2Image: Fehler in Reihe:" & intRows.ToString & " Spalte: " & intColumn.ToString & vbNewLine & _
    20. " tmpImagevalue: " & tmpImage.ToString & " Offset: " & offs.ToString & vbNewLine & _
    21. ex.Message)
    22. Exit For
    23. End Try
    24. Next
    25. Next
    26. bmpImage.UnlockBits(bmpdata) 'zurückschreiben des Bildes
    27. imgOriginalImage = bmpImage 'Rückgabe in globale Variable
    28. Catch ex As Exception
    29. Trace.WriteLine("$ Byte2Image: " & ex.Message)
    30. MessageBox.Show("$ Byte2Image: " & ex.Message)
    31. End Try
    Wie groß sind i und j? Also Ausmaße des Bildes! Denn sonst kann man ja schlecht rumprobieren ;)

    EDIT:
    Ich vermute "Marshal" ist verhältnissmäßig "teuer". Was wenn man erstmal ein temp. byte array erzeugt (größe = width * height) und dann per Marshal.Copy alles in einem Rutsch kopiert?

    EDIT2: Warum verwendest du in der ersten Methode ein System.Array als Parameter und nicht ein "normales" ByteArray. Ich denke mal, dass "GetValue" langsamer ist als einfach Wert(x,y) zu lesen. Denn beim generischen Array sind die Dimensionen ja zur Kompilierzeit nicht bekannt und GetValue ist dann eine Methode, die alle möglichen Fälle abdeckt (Array mit 1,2,3,...n Dimensionen)!

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „picoflop“ ()

    Hallo picoflop,

    die Cam hat 6 Mpix.

    Kannst du mir deinen Vorschlag in EDIT 1 kurz erklären?

    Zu EDIT 2.: Aus der Kamera bekomme ich ein Array vom type system. array

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „wrack solutionist“ ()

    Momentan wird die Marshal.Write() Funktion pro Pixel 3 mal aufgerufen.
    Ersetze doch mal den Marshal Kram durch ein einfaches Array schreiben.
    Also zb:

    dim tmpArray(w * h * 3 - 1) as byte.
    tmparray(introws * width + intcolumns + 0)=tmpimage
    tmparray(.. + 1)=tmpimage
    tmparray(.. + 2)=tmpimage

    und ganz am Schluss dann (wenn die beiden schleifen durch sind):
    System.Runtime.InteropServices.Marshal.Copy(tmparray, 0, scan0, tmparray.Length)

    "Marshal" dient ja dazu, zwischen managed und unmanaged zu vermitteln. Das ist verhältnissmäßig aufwändig und deswegen wäre es vmtl besser, das möglichst selten zu machen.


    Zu EDIT 2.: Aus der Kamera bekomme ich ein Array vom type system. array

    Schau mal hier:
    addressof.com/blog/archive/2003/05/08/181.aspx

    Evtl hilft das beim "umkopieren"?

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „picoflop“ ()

    push .... ;)

    Ich hab mal rumgespielt:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Runtime.InteropServices
    2. Public Class ImageTest
    3. Private pseudoimage As System.Array
    4. Private b(3000, 2000) As Integer
    5. Public Sub CreateImage()
    6. pseudoimage = b
    7. End Sub
    8. Public Sub Bench()
    9. Dim stp As Stopwatch
    10. stp = Stopwatch.StartNew
    11. For i As Integer = 0 To 2999
    12. For j As Integer = 0 To 1999
    13. pseudoimage.GetValue(i, j)
    14. Next
    15. Next
    16. stp.Stop()
    17. Debug.Print("Time: " & stp.ElapsedMilliseconds)
    18. ' copy to byte
    19. stp = Stopwatch.StartNew
    20. Dim bytes() As Byte
    21. Dim handle As GCHandle = GCHandle.Alloc(pseudoimage, GCHandleType.Pinned)
    22. Dim ptr As IntPtr = handle.AddrOfPinnedObject()
    23. ReDim bytes(pseudoimage.Length * 4)
    24. Marshal.Copy(ptr, bytes, 0, bytes.Length)
    25. handle.Free()
    26. Dim a As Integer
    27. For i As Integer = 0 To (3000 * 2000 * 4) - 1 Step 4
    28. a = bytes(i) Or bytes(i + 1) << 8 Or bytes(i + 2) << 16 Or bytes(i + 3) << 24
    29. Next
    30. stp.Stop()
    31. Debug.Print("Time: " & stp.ElapsedMilliseconds)
    32. End Sub
    33. End Class


    Der erste Teil dauert 3500 ms, der zweite ... 159 !
    Man sollte also auf jeden Fall das generische "System.Array" erstmal nach Byte() umkopieren, da der Zugriff dann extremst schneller ist!
    Hallo Picoflop,

    danke der Tipp mit Marshal habe ich mal umgesetzt! Der war gold wert! Nun liegt das ganze bei unter einer 1/100 sec :thumbsup: ! Leider bekomme ich nun aber ein anderes Bild? Woran liegt das, eigenltich sollten beide Varianten die gleiche Lösung bringen!.
    Hier mal ein Auszug ausm Code:

    VB.NET-Quellcode

    1. Dim bmpImage = New Bitmap(Width / bx, Height / by, PixelFormat.Format24bppRgb)
    2. 'Datenstream für Bild
    3. Dim bmpdata As BitmapData = bmpImage.LockBits(New Rectangle(sx, sy, Width, Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb)
    4. Dim bytesPerPixel As Byte = Bitmap.GetPixelFormatSize(bmpdata.PixelFormat) / 8
    5. Try
    6. Dim imgPtr As IntPtr = bmpdata.Scan0 'Zeiger auf Bild
    7. Dim tmpImage As Byte
    8. Dim Pixel As Integer = 0
    9. Dim offs As Integer
    10. Dim tmpArray(Width * Height * 3 - 1) As Byte
    11. For intRows As Integer = 0 To bytearImage.GetUpperBound(1) - 1
    12. For intColumn As Integer = 0 To bytearImage.GetUpperBound(0) - 1
    13. Try
    14. tmpArray(intRows * Width + intColumn + 0) = bytearImage(intColumn, intRows)
    15. tmpArray(intRows * Width + intColumn + 1) = bytearImage(intColumn, intRows)
    16. tmpArray(intRows * Width + intColumn + 2) = bytearImage(intColumn, intRows)
    17. Catch ex As Exception
    18. End Try
    19. Next
    20. Next
    21. System.Runtime.InteropServices.Marshal.Copy(tmpArray, 0, bmpdata.Scan0, tmpArray.Length)
    22. bmpImage.UnlockBits(bmpdata) 'zurückschreiben des Bildes
    23. imgOriginalImage = bmpImage 'Rückgabe in globale Variable




    Die von dir vorgeschlagene Umwandlung eines system.array in ein Byte, beinhaltet leider nicht die Einstellung des Schwarz/-Weißpunktes und des Gammas. So wie ich das verstehe, wird hier eine linerare Umwandlung betrieben.

    VG

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „wrack solutionist“ ()

    wrack solutionist schrieb:

    Leider bekomme ich nun aber ein anderes Bild?

    Gute Frage. Was heißt "anders"? Mal zwei (kleine!) Bilder als Vergleich hochladen?

    EDIT: Idee: Die Scanlines sind evtl auf 4 Byte Grenzen ausgerichtet, so dass jede neue Zeile immer auf einer 4 Byte Grenze anfängt (evtl auch ne 8er oder 16 Grenze?). Wenn ein Bild mit 3 Byte/Pixel NICHT auf einer 4 Byte Grenze endet, dann werden die letzten Bytes nicht verwendet. Ich denke mal das sieht dann auch wie ein Bild das in sich diagonal verschoben ist (hatte so'n Problem mal aufm Palm). Dann müsste man das Temp-Array halt entsprechend größer machen und diese "Füllbytes" berücksichtigen.

    wrack solutionist schrieb:

    Die von dir vorgeschlagene Umwandlung eines system.array in ein Byte, beinhaltet leider nicht die Einstellung des Schwarz/-Weißpunktes und des Gammas. So wie ich das verstehe, wird hier eine linerare Umwandlung betrieben.

    Geht erstmal nur darum, die langwierigen .GetValue Aufrufe rauszubekommen. Im nächsten Schritt musst du dann natürlich wieder deine Gamma-Anpassung machen. Ist etwas schwieriger, weil du ja keinen Integer mehr hast, sondern den aus Bytes zusammensetzen muss, aber wie oben zu sehen ist das trotzdem DEUTLICH schneller. Wirst du etwas fummeln und ausprobieren müssen, aber dafür dürfte da auch nochg mal der Faktor (!) 10-20 drin sein, vermute ich.


    Nachtrag:
    Nun liegt das ganze bei unter einer 1/100 sec :thumbsup: !

    Das mal bitte alle lesen, die immer behaupten, VB.Net wäre LAAAAAANGSAM. Um welchen Faktor haben wir das jetzt gerade beschleunigt? 1000? 2000?
    VB.Net ist NICHT langsam ... WENN man schnellen Code schreibt!
    (Ist nicht als Angriff gegen dich - WraSol - zu verstehen. Nur mal so als allgemeine Anmerkung, dass man halt mit VB auch performanten Code schreiben kann)

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „picoflop“ ()

    Hallo picoflop,

    Hier ist mal der vergleich das obere Bild ist nach der alten Methode das untere nach der Neuen. Im oberen Bild sind die Teststerne zu sehen, im unteren nicht!. EDIT: Dein TIPP mit dem Acanlines war richtig! Nun passt diese Sache! Vielen herzlichen Dank!

    Eine weitere Frage hätte ich zur byte-Umwandlung, so wie ich das sehe, werden ja alle Werte zwischen 0 und 65535 linear zwischen 0 und 255 interpoliert. Wie muss der Code ausschauen, dass er nur zwischen zwei Grenzen diese Interpolatiion macht?
    Des Weiteren hätte ich eine Frage zum Gamma:Zitat Picoflop "Im nächsten Schritt musst du dann natürlich wieder deine Gamma-Anpassung machen. Ist etwas schwieriger, weil du ja keinen Integer mehr hast, sondern den aus Bytes zusammensetzen muss,"
    Verliere ich nicht durch die linerare Interpolation zwischen 0 und 65535, jene Auflösung die Nötig ist um eine nichtlineare Stauchung durchführen zu können?
    Und zum Schluss hätte ich noch eine Frage zur Umwandlung ins Byte, funktioniert diese Methode auch mit 2D- ByteArrays?

    Danke
    Bilder
    • VGl.jpg

      16,18 kB, 960×720, 119 mal angesehen

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „wrack solutionist“ ()

    Erstmal zum "Umwandeln":
    Da hast du, glaube ich, noch nicht ganz verstanden worum es geht.

    Nehmen wir an, du hast ein 2x2 Array von 16 Bit-Werten. Dann sieht das im Speicher ungefähr so aus (HEX-Darstellung!):
    0,0:FEDC 1,0:BA98
    1,0:7654 2,0:3210

    Wenn man das jetzt in ein eindimensionales Bytearray kopiert (NICHT "umwandelt"!) sieht es so aus:
    0:FE 1: DC 2:BA 3:98 4:76 5:54 6:32 7:10

    Um also einen 16 Bit-Wert zu erhalten, machst du entweder
    a = blup.GetValue(0,0) -> FEDC
    oder
    a = b(0)*256 + b(1) bzw = b(0)<<8 OR b(1)
    Obwohl "komplexer" ist die zweite Variante deutlich schneller!

    Berücksichtigt werden muss dabei ggfs Big vs LittleEndian:
    CPU:FEDC, LittleEndianMemory: DCFE, BigEndianMemory: FEDC
    Da x86 LittleEndian ist, muss man also ggfs b(0) + b(1)*256 rechnen. Das kann man aber anhand von ein paar Testwerten ausrechnen.

    Ebenfalls muss man schauen, wie genau die Werte im 2x2 Array gespeichert sind. Also ob zuerst die Zeile und dann Spalte oder erst Spalte dann Zeile.
    0,0:FEDC 1,0:BA98 1,0:7654 2,0:3210 ->
    FE DC BA 98 76 54 32 10
    oder
    FE DC 76 54 BA 98 32 10
    Auch das kann man mit dem Debugger und ein paar Vergleichswerten schnell rausfinden.



    Zum Bild:

    VB.NET-Quellcode

    1. Dim bmpImage = New Bitmap(Width / bx, Height / by, PixelFormat.Format24bppRgb)

    Warum /bx und /by? später arbeitest du doch dann wieder mit Width und Height OHNE diese zu teilen.


    BTW: auch wenn das sicher ne tolle Übung ist, wäre es nicht besser AForge.Net zu verwenden? Da gibt's ja extra nen Gamma- und nen Grayscale-Filter, die ziemlich viele Einstellmöglichkeiten haben.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „picoflop“ ()

    Hallo Picoflop,

    puh, da hast du mir mal was vor den Kopf gestoßen. Nun verstehe nur noch Bahnhof. Ich hab mal zwei methoden geschrieben, was habe ich da nicht verstanden? Die AForge.net sache schaue ich mir an!

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Dim Width As Integer = CamCamera.CameraXSize
    2. Dim Height As Integer = CamCamera.CameraYSize
    3. 'Bild
    4. Dim bmpImage = New Bitmap(Width, Height, PixelFormat.Format24bppRgb)
    5. 'Datenstream für Bild
    6. Dim bmpdata As BitmapData = bmpImage.LockBits(New Rectangle(0, 0, Width, Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb)
    7. Dim bytesPerPixel As Byte = Bitmap.GetPixelFormatSize(bmpdata.PixelFormat) / 8
    8. Dim tmpArray(Width * Height * bytesPerPixel - 1) As Byte
    9. 'umwandeln des System.Array in ein ByteArray
    10. Dim tmparImage() As Byte
    11. Dim handle As GCHandle = GCHandle.Alloc(arImage, GCHandleType.Pinned)
    12. Dim ptr As IntPtr = handle.AddrOfPinnedObject()
    13. ReDim tmparImage(arImage.Length * bytesPerPixel - 1)
    14. Marshal.Copy(ptr, tmparImage, 0, tmparImage.Length)
    15. handle.Free()
    16. For i As Integer = 0 To (Width * Height * bytesPerPixel) - 1 Step bytesPerPixel
    17. tmpArray(i) = tmparImage(i) Or tmparImage(i + 1) << 8 Or tmparImage(i + 2) << 16 Or tmparImage(i + 3) << 24
    18. Next
    19. System.Runtime.InteropServices.Marshal.Copy(tmpArray, 0, bmpdata.Scan0, tmpArray.Length)
    20. bmpImage.UnlockBits(bmpdata) 'zurückschreiben des Bildes
    21. imgOriginalImage = bmpImage 'Rückgabe in globale Variable



    Spoiler anzeigen

    VB.NET-Quellcode

    1. Dim tmparImage() As Byte
    2. Dim handle As GCHandle = GCHandle.Alloc(arImage, GCHandleType.Pinned)
    3. Dim ptr As IntPtr = handle.AddrOfPinnedObject()
    4. ReDim tmparImage(arImage.Length * 3 - 1)
    5. Marshal.Copy(ptr, tmparImage, 0, tmparImage.Length)
    6. handle.Free()
    7. Dim imagestream As New IO.MemoryStream(tmparImage)
    8. Try
    9. imgOriginalImage = Image.FromStream(imagestream)
    10. Catch
    11. End Try