Performance- und Genauigkeitsprobleme bei List(of PointF).Exists(...)

  • VB.NET
  • .NET (FX) 4.5–4.8

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

    Performance- und Genauigkeitsprobleme bei List(of PointF).Exists(...)

    Hallo,
    ich habe sowohl ein Performance- als auch ein Genauigkeitsproblem in der Prozedur.

    Um was geht es

    Ich möchte alle Pixel, die sich innerhalb eines kreisähnlichen Weges befinden, weiß machen; siehe Bild.
    Dafür hole ich mir die xmin, xmax, ymin und ymax aus meiner
    List(of PointF) Manually_drawn_path_on_image.
    Dann iteriere ich von xmin zu xmax und von ymin zu ymax.
    Ich hatte das Problem schon einmal, habe jedoch letzten Endes einen graphicsPath benutzt und konnte dementsprechend GraphicsPath.isVisible nutzen. Einen solchen GraphicsPath nutze ich nicht mehr.

    Anbei der Code. Obwohl ich mit einem Step von 1/20 iteriere, werden trotzdem einige Pixel (oder ganze Linien) ausgelassen. Mittlerweile braucht die Prozedur bei meinem Testbild 8 Minuten, obwohl sie asynchron läuft. Was kann man tun?

    VB.NET-Quellcode

    1. Private Async Sub Button_weissen_Click(sender As Object, e As EventArgs) Handles Button_weissen.Click
    2. If LoadedImage Is Nothing OrElse Manually_drawn_path_on_image.Count = 0 Then
    3. Return
    4. End If
    5. Button_weissen.BackColor = Color.FromArgb(255, 255, 0)
    6. Await Task.Run(Sub() weissen())
    7. Button_weissen.BackColor = Color.FromArgb(0, 207, 0) ' green
    8. If System.IO.File.Exists(Application.StartupPath & "\Piano C2 Wav.wav") Then
    9. My.Computer.Audio.Play(Application.StartupPath & "\Piano C2 Wav.wav", Microsoft.VisualBasic.AudioPlayMode.Background)
    10. End If
    11. End Sub
    12. Private Sub weissen()
    13. Dim xmin As Single = Manually_drawn_path_on_image.Min(Function(p) p.X) - 1.0F
    14. Dim xmax As Single = Manually_drawn_path_on_image.Max(Function(p) p.X) + 1.0F
    15. Dim ymin As Single = Manually_drawn_path_on_image.Min(Function(p) p.Y) - 1.0F
    16. Dim ymax As Single = Manually_drawn_path_on_image.Max(Function(p) p.Y) + 1.0F
    17. Dim gFd As Integer = Average_over_all_colors_in_that_rectangle.ToArgb()
    18. Dim cnt As UInteger = 0UI
    19. For x As Single = xmin To xmax Step (1.0F / 20.0F)
    20. For y As Single = ymin To ymax Step (1.0F / 16.0F)
    21. Dim p As New PointF(x, y)
    22. Dim IsInside As Boolean = Manually_drawn_path_on_image.Exists(
    23. Function(f) _
    24. (CInt(f.Y) = CInt(p.Y) OrElse CInt(f.Y) = CInt(p.Y) + 1I OrElse CInt(f.Y) = CInt(p.Y) - 1I OrElse CInt(f.Y) = CInt(p.Y) - 2I OrElse CInt(f.Y) = CInt(p.Y) + 2I) AndAlso CInt(f.X) <= CInt(p.X)) _
    25. AndAlso Manually_drawn_path_on_image.Exists(
    26. Function(f) _
    27. (CInt(f.Y) = CInt(p.Y) OrElse CInt(f.Y) = CInt(p.Y) + 1I OrElse CInt(f.Y) = CInt(p.Y) - 1I OrElse CInt(f.Y) = CInt(p.Y) - 2I OrElse CInt(f.Y) = CInt(p.Y) + 2I) AndAlso CInt(f.X) >= CInt(p.X))
    28. If Not IsInside Then
    29. Continue For
    30. Else
    31. cnt += 1UI
    32. End If
    33. Dim xi As Integer = CInt(Math.Round(x, 0))
    34. Dim yi As Integer = CInt(Math.Round(y, 0))
    35. Dim aktuelle_Farbe As Integer = LoadedImage.GetPixel(xi, yi).ToArgb()
    36. If Not (CDbl(aktuelle_Farbe) <= CDbl(gFd * (1.0 + ProzentAbweichung / 100.0)) OrElse CDbl(aktuelle_Farbe) >= CDbl(gFd * (1.0 - ProzentAbweichung / 100.0))) Then
    37. LoadedImage.SetPixel(xi, yi, Color.White)
    38. End If
    39. Next
    40. Next
    41. LoadedImage.Save(Environment.GetFolderPath(Environment.SpecialFolder.Desktop) & $"\{Date.Now.ToString("G", Deu).Replace(":"c, "-"c)}.png", Imaging.ImageFormat.Png)
    42. End Sub

    Bilder
    • Screenshot 2021-10-06 184331.png

      32,24 kB, 628×558, 63 mal angesehen

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

    Ich weiß, beantwortet auch die Frage nicht, aber warum überhaupt PointF mit 20stel Pixeln? Ist das echt nötig? Und GetPixel/SetPixel ist im Vergleich zu den Arbeiten mit LockBits deutlich unperformanter.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    @VaporiZed Ich habe viel getestet. Ich habe die Erfahrung gemacht, dass es mit 20stel Pixeln besser läuft. Auch wenn ich zu Int casten muss, weil Single mit Single zu vergleichen nie klappt. Also ja, ich gebe dir Recht, es scheint widersprüchlich, aber es ist ein Tick besser.

    Und zu LockBits: Ich kenn mich damit noch nicht aus.


    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „Bartosz“ ()

    Ok, wie machen wir weiter? Ihr könnt gerne noch Ideen einbringen.


    Edit: Ich habe gerade herausgefunden, dass ich in die Funktion.Exists(..) noch mehr einbauen muss.
    Jetzt bin ich bei

    VB.NET-Quellcode

    1. Dim IsInside As Boolean = Manually_drawn_path_on_image.Exists(
    2. Function(f) _
    3. (CInt(f.Y) = CInt(p.Y) OrElse CInt(f.Y) = CInt(p.Y) + 1I OrElse CInt(f.Y) = CInt(p.Y) - 1I OrElse CInt(f.Y) = CInt(p.Y) - 2I OrElse CInt(f.Y) = CInt(p.Y) + 2I OrElse CInt(f.Y) = CInt(p.Y) + 3I) AndAlso CInt(f.X) <= CInt(p.X)) _
    4. AndAlso Manually_drawn_path_on_image.Exists(
    5. Function(f) _
    6. (CInt(f.Y) = CInt(p.Y) OrElse CInt(f.Y) = CInt(p.Y) + 1I OrElse CInt(f.Y) = CInt(p.Y) - 1I OrElse CInt(f.Y) = CInt(p.Y) - 2I OrElse CInt(f.Y) = CInt(p.Y) + 2I OrElse CInt(f.Y) = CInt(p.Y) + 3I) AndAlso CInt(f.X) >= CInt(p.X))

    also
    OrElse CInt(f.Y) = CInt(p.Y) + 3I hinzugefügt. Nun konnte ich die Steps kleiner machen auf 1/3.

    Aber eigentlich ist das doch nicht richtig?! Man würde erwarten, dass
    CInt(f.Y) = CInt(p.Y) AndAlso CInt(f.X) <= CInt(p.X) reicht... Ich meine, ich schaue pro Zeile auch 2 Pixel nach oben und 3 nach unten. Das is ja komisch

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „Bartosz“ ()

    Kannst du uns einen kompletten, funktionsfähigen Code bereitstellen? Weil der Code aus Post #1 hat einige Variablen die einfach fehlen, da kann man das leider nicht selbst testen.
    Alternativ ein Beispielprojekt bereinigt hier im Forum als Anhang hochladen.
    @Bartosz Also wenn ich mir Deinen unlesbaren und unpflegbaren Code ansehe, muss ich noch mals auf FloodFill verweisen:
    simpledevcode.wordpress.com/20…ll-algorithm-using-c-net/
    Einfacher geht es nun wirklich nicht:
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @'RodFromGermany' Ich möchte zu meiner Verteidigung dazusagen, dass es nicht meine Art ist, so unleserlichen Code zu schreiben. Das ist das Problem. Ungenau → ich schreibe noch mehr in die Exists-Methode um die Pixel zu finden → unleserlich.

    @'KingLM97'
    Manually_drawn_path_on_image ist eine List(of PointF).
    Average_over_all_colors_in_that_rectangle ist eine Color.
    cnt war nur testhalber, denk dir das weg.
    Und dies hier

    VB.NET-Quellcode

    1. If System.IO.File.Exists(Application.StartupPath & "\Piano C2 Wav.wav") Then
    2. My.Computer.Audio.Play(Application.StartupPath & "\Piano C2 Wav.wav", Microsoft.VisualBasic.AudioPlayMode.Background)
    3. End If
    war nur, damit ich in der Zeit eine rauchen gehen kann und benachrichtigt werde. Da die Funktion nicht mehr so lange braucht, kann das auch raus.

    Folgende Situation:
    Ich ziehe mit der Maus eine Linie um einen Bildteil. Diese einzelnen Punkte sind in der
    Manually_drawn_path_on_image. Allerdings, wie der Name schon sagt, auf dem echten Bild. Also umgerechnete Pixelkoordinaten.
    Ich ziehe über einem bestimmen Bildteil mit der Maus ein Rechteck. In einer separaten Funktion wird aus allen Pixeln in diesem Rechteck die Durchschnittsfarbe bestimmt. Das ist
    Average_over_all_colors_in_that_rectangle.

    Da es ein wenig umständlich sein dürfte, dies nachzubauen – was ihr auch nicht sollt um Gottes Willen – fügt einfach ein paar Punkte zu einer List(of PointF) hinzu. Es geht mir nur um die (Un-)Genauigeit von dieser Exists(..)-Funktion. Die läuft an einigen Stellen auf false, wo sie auf true laufen müsste. Siehe hier im alten Thread
    Ich wollte mich noch einmal melden. Ich arbeite nun doch mit einem Drawing2D.GraphicsPath und der dazugehörigen IsVisible(x, y) Funktion. Ich setze diesen Thread auf erledigt.

    Aber das interessiert mich trotzdem allgemein, wie IsVisible(,) wohl intern abläuft – so mittelschnell und trotzdem gute Ergebnisse.