Kann man einen GraphicsPath anklicken, um fehlerhafte Linien zu entfernen?

  • VB.NET
  • .NET 4.5

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

    @RodFromGermany
    Wenn Du die Länge einer Diagonale berechnest, solltest Du die Komponenten unter der Wurzel nicht vorher auf Short casten.

    Habe ich auch schon gesehen, danke dir trotzdem ! Ich hatte das erst gemacht, weil doch tatsächlich f(y + 1, x)) - f(y - 1, x) einen Überlauf verursacht hat der hintere war größer als der vordere, sodass etwas Negatives herauskommt, was in Byte nicht geht. Jetzt ist eh alles in Double. (und asynchron und heller)

    VB.NET-Quellcode

    1. Private Async Sub Button_Kantenerkennung_professionell_Click(sender As Object, e As EventArgs) Handles Button_Kantenerkennung_professionell.Click
    2. Await Task.Run(Sub() Kantenerkennung_professionell())
    3. End Sub
    4. Private Sub Kantenerkennung_professionell()
    5. 'Das Originalbild wird in ein Graustufenbild umgewandelt. Ich nutze die Emgu.CV-Sachen, da ich das am einfachsten finde und ich dann keine selbstgeschriebenen Matrizen hernehmen muss.
    6. Using bgr_img As Emgu.CV.Image(Of Emgu.CV.Structure.Bgr, Byte) = Picture1.ToImage(Of Emgu.CV.Structure.Bgr, Byte)
    7. Using imgGray As Emgu.CV.Image(Of Emgu.CV.Structure.Gray, Byte) = bgr_img.Convert(Of Emgu.CV.Structure.Gray, Byte)()
    8. If PictureBox1.Image IsNot Nothing Then
    9. Me.Invoke(Sub() PictureBox1.Image = Nothing)
    10. End If
    11. Me.Invoke(Sub() PictureBox1.Image = imgGray.ToBitmap())
    12. 'in ein 2D-Array kopieren
    13. Dim f(imgGray.Height - 1, imgGray.Width - 1) As Byte
    14. For y As Integer = 0 To imgGray.Height - 1 Step 1
    15. For x As Integer = 0 To imgGray.Width - 1 Step 1
    16. f(y, x) = imgGray.Data(y, x, 0)
    17. Next
    18. Next
    19. 'Höhe an der Stelle x, y soll der Farbwert an der Stelle x, y sein.
    20. 'Es erstreckt sich ein Gebirge zwischen 0 und 255.
    21. 'Das Graustufenbild wird also dreidimensional.
    22. '„Kanten“ sind die Übergänge – Stellen, an denen der Anstieg möglichst groß ist.
    23. 'Es wird nun der Gradient berechnet:
    24. 'Dim Gradient_x(imgGray.Data.Length - 1) As Double
    25. 'Dim Gradient_y(imgGray.Data.Length - 1) As Double
    26. Dim Betrag_des_Gradienten(imgGray.Data.Length - 1) As Double
    27. Dim i As Integer = 0
    28. For y As Integer = 1 To imgGray.Height - 2 Step 1
    29. For x As Integer = 1 To imgGray.Width - 2 Step 1
    30. 'Gradient_x(i) = (CDbl(f(y, x + 1)) - CDbl(f(y, x - 1))) / 2.0
    31. 'Gradient_y(i) = (CDbl(f(y + 1, x)) - CDbl(f(y - 1, x))) / 2.0
    32. 'Pythagoras
    33. Betrag_des_Gradienten(i) = Math.Sqrt(Math.Pow((CDbl(f(y, x + 1)) - CDbl(f(y, x - 1))) / 2.0, 2) + Math.Pow((CDbl(f(y + 1, x)) - CDbl(f(y - 1, x))) / 2.0, 2))
    34. i += 1
    35. Next
    36. Next
    37. i = 0
    38. Dim Kantenbild As New Bitmap(imgGray.Width, imgGray.Height, Imaging.PixelFormat.Format32bppArgb)
    39. Dim _Graphics As Graphics = Graphics.FromImage(Kantenbild)
    40. Dim Max As Double = 0.0
    41. For a As Integer = 0 To Betrag_des_Gradienten.Length - 1 Step 1 'maximalen Wert aller Werte finden.
    42. If Betrag_des_Gradienten(a) > Max Then
    43. Max = Betrag_des_Gradienten(a)
    44. End If
    45. Next
    46. Dim Faktor As Double = 255.0 / Max
    47. For y As Integer = 0 To imgGray.Height - 2 Step 1
    48. For x As Integer = 1 To imgGray.Width - 2 Step 1
    49. Kantenbild.SetPixel(x, y, Color.FromArgb(CInt(Math.Round(Betrag_des_Gradienten(i) * Faktor, 0)), CInt(Math.Round(Betrag_des_Gradienten(i) * Faktor, 0)), CInt(Math.Round(Betrag_des_Gradienten(i) * Faktor, 0))))
    50. i += 1
    51. Next
    52. Next
    53. Me.Invoke(Sub() PictureBox1.Image = Kantenbild)
    54. Kantenbild0 = Kantenbild
    55. End Using
    56. End Using
    57. End Sub
    Grundsätzlich kannst du diese Operationen ganzzahlig ausführen. Ist meines Erachtens nicht so gut zwischen double und int hin -und her zu casten. Du kannst statt pow(x, 2), einfach x * x schreiben (und nicht wie ich erwähnt hatte << 1, das wäre tatsächlich * 2), Wurzel brauchst du da sowieso nicht, weil die Wurzelfunktion stetig im Definitionsbereich ist und damit Ordnungen beibehält, dass heißt wenn x^2 kleiner ist als y^2, dann ist betragsmäßig x auch kleiner als y. Dein Kantenbild wird sich also nicht verändern, du brauchst nur halt einen zusätzlichen Proportionalitätsfaktor um den Wert in einen gewünschten Bereich zu drücken - oder du arbeitest von vornherein mit Farbwerten die jeweils zwischen 0 und 1 liegen - aber ob diese ganzen Optimierungen jetzt was grundlegend ändert.. wenn's funktioniert ist es erstmal ja gut.
    Und Gott alleine weiß alles am allerbesten und besser.
    Ich muss mich nochmal an euch wenden. Ich möchte, wie in Post1 beschrieben, einen Bereich auswählen; und die Erkennung soll in dem Rechteck suchen und einen Körper umranden. Aufgrund der Kantenerkennung funktioniert das besser, aber nicht 100%ig perfekt. Ich habe mir 2 Hilfen eingebaut. Einmal eine Schwarzweißschwelle („Wie starkes Weiß (0 bis 255) muss ich sehen?“), und eine Einstellung, wie lang eine Strecke zwischen 2 Punkten maximal sein darf. Je nach Foto sind natürlich die Einstellungen leicht verschieden. Man kann jedoch immer sagen: Zu streng ist Mist, zu locker ist Mist. Irgendwo dazwischen ist das Optimum, jedoch muss das Wort Optimum nicht ‘gut’ heißen. Das Ergebnis-Bild ist im Anhang.

    Was kann man noch machen? Am liebsten wäre mir, und daher der Titelname, dass ich den GraphicsPath manuell korrigieren kann. Aber ich finde nichts Gescheites bei sta**********. Ich habe bisher nur gefunden, wie man komplett selbst zeichnet und dann habe ich dort einen Löschvorgang eingebaut. Das habe ich mal hochgeladen. Allerdings bekomme ich es nicht hin, beides zu tun. Any help is appreciated


    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Data;
    5. using System.Drawing;
    6. using System.Drawing.Drawing2D;
    7. using System.Linq;
    8. using System.Text;
    9. using System.Threading.Tasks;
    10. using System.Windows.Forms;
    11. namespace selbst_zeichnen
    12. {
    13. public partial class Form1 : Form
    14. {
    15. private GraphicsPath _drawingPath = new GraphicsPath();
    16. private Point lastMouseLocation;
    17. private bool drawing = false;
    18. public Form1()
    19. {
    20. InitializeComponent();
    21. }
    22. private void Form1_Load(object sender, EventArgs e)
    23. {
    24. }
    25. private void Form1_MouseDown(object sender, MouseEventArgs e)
    26. {
    27. if (e.Button == MouseButtons.Right)
    28. {
    29. _drawingPath = new GraphicsPath();
    30. Invalidate();
    31. }
    32. }
    33. private void Form1_MouseMove(object sender, MouseEventArgs e)
    34. {
    35. if (e.Button == MouseButtons.Left)
    36. {
    37. drawing = true;
    38. _drawingPath.AddLine(lastMouseLocation, e.Location);
    39. Invalidate();
    40. }
    41. if (e.Button == MouseButtons.None && drawing)
    42. {
    43. drawing = false;
    44. _drawingPath.StartFigure();
    45. }
    46. lastMouseLocation = e.Location;
    47. }
    48. private void Form1_Paint(object sender, PaintEventArgs e)
    49. {
    50. e.Graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;
    51. e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
    52. e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
    53. e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    54. using (SolidBrush b = new SolidBrush(Color.Blue))
    55. using (Pen p = new Pen(b, 51))
    56. {
    57. e.Graphics.DrawPath(p, _drawingPath);
    58. }
    59. using (SolidBrush b = new SolidBrush(Color.LightGreen))
    60. using (Pen p = new Pen(b, 50))
    61. {
    62. e.Graphics.DrawPath(p, _drawingPath);
    63. }
    64. }
    65. }
    66. }
    Bilder
    • Screenshot 2021-01-15 210349 - Kopie.png

      1,16 MB, 1.615×858, 17 mal angesehen
    @φConst Ja, gerne. Ich sage es vorsichtshalber: es geht mir nicht um Elektronik, sondern ich brauchte einfach irgendein Bild, wo größere und kleinere geschlossene Teile drin sind – das Holstentor kann man schlecht umrahmen; ist auch nicht das Ziel.
    Du kannst allerdings jedes beliebige Foto hernehmen. :)
    Bilder
    • 20210111_141648 - Kopie.jpg

      5,45 MB, 4.032×3.024, 17 mal angesehen
    Exemplarische Vorgehensweise:
    Image-Segmentation anwenden, ich habe mal eine Segmentierung basierend auf sogenannte Graph-Cuts gewählt (in MatLab geladen, und dann entsprechend Vorder -und Hintergrund markiert), Resultat:


    Nun kann ich einen Treshold festlegen - alles gelb-markierte wird betont, alles andere auf 0 gesetzt, Resultat:


    Darauf die Kantendetektion anwenden, und dann einfach mit dem Urbild addieren - schon hast du eine präzise, MODIFIZIERBARE, Kantendetektion implementiert.
    Dein Beispiel wäre:


    Näheres:


    Was du per se vorhast, ist nicht zu unterschätzen. Das geht tief in die Materie, wenn du das präziser haben willst, und individuell die Kurven setzen ist auch kein Kompromiss, weil du dadurch Akkuratesse und Präzision verlieren könntest.
    Und Gott alleine weiß alles am allerbesten und besser.

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

    Danke für die Idee. Das Video schaue ich mir morgen an.

    individuell die Kurven setzen ist auch kein Kompromiss, weil du dadurch Akkuratesse und Präzision verlieren könntest.
    Ja, ich überlege seit 2 Tagen hin und her zwischen
    1) „Programm soll möglichst gut erkennen, damit ich keine anderen Programme brauche und weil ich sonst Präzision verliere.“
    2) „Das ist schwer zu implementieren, ich muss individuell Kurven setzen.“


    Daher finde ich deine Vorgehensweise geeignet.

    Bartosz schrieb:

    „Wie starkes Weiß (0 bis 255) muss ich sehen?“
    Diskretisiere das Graubild, alle Werte kleiner als x auf 0, alle anderen auf 255.
    Dann finde Deine Kanten,mit dem oben beschriebenen Filter.
    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!
    Zum Abschluss möchte ich das, was innerhalb des GraphicsPaths ist, auf ein neues Bild übertragen, siehe Schema. Ich habe bisher keine Möglichkeit gefunden, dies in ein neues Bild zu übertragen; daher dachte ich mir, ich schreibe mir eine Funktion, welche prüft, ob das derzeitige x, y innerhalb oder außerhalb des Paths liegt. Wenn außerhalb: transparent, sonst wie im Originalbild. Wie kann in nun diese Liste durchgehen, wenn es mehrere gleiche y oder x gibt?
    Ich habe schon mal den Code für ein neues Bild basierend auf dem Rechteck erstellen.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Using Extracted As Bitmap = New Bitmap(rect.Width, rect.Height, Imaging.PixelFormat.Format32bppArgb)
    2. Using Grp As Graphics = Graphics.FromImage(Extracted)
    3. Grp.DrawImage(Form1.Picture1, 0, 0, rect, GraphicsUnit.Pixel)
    4. End Using
    5. If System.IO.Directory.Exists("C:\Users\xy\Desktop") Then
    6. Extracted.Save("C:\Users\xy\Desktop\1.png", Imaging.ImageFormat.Png)
    7. End If
    8. End Using
    Bilder
    • IMG_20210118_0001 2 - Kopie.jpg

      516,4 kB, 3.508×1.660, 13 mal angesehen