Zhang-Suen Thinning Algorithmus

  • C#
  • .NET (FX) 4.5–4.8

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

    Zhang-Suen Thinning Algorithmus

    Ich bin im Moment dabei einen Algorithmus zur Skelettierung in C# zu implementieren.
    Dabei soll der Zhang-Suen Thinning Algorithmus ganz gut sein.
    Hier habe ich eine Java-Implementierung gefunden und sie versucht zu übersetzen. nayefreza.wordpress.com/2013/0…ithm-java-implementation/
    Der unten stehende Code ist dabei rausgekommen.

    Klappt auch sehr gut, aber ist extrem langsam. (ca. 15sek. bei 1000x300 Pixeln)
    Ich habe schon versucht den Algorithmus abschnittweise arbeiten zu lassen. (Nicht über das ganze Bild, sondern nur über relevante Abschnitte)
    Da kam aber ein sehr komisches Ergebnis raus.

    Meine Frage ist ob jemand eine Idee hat wie man das ganze beschleunigen kann?
    (Paralell.For habe ich schon probiert)
    Könnte man vielleicht Zeiger einsetzten?

    Oder kennt jemand eine andere Methode die vielleicht schneller ist?
    Grüße :)

    Implementierung
    Spoiler anzeigen

    C#-Quellcode

    1. private float maxBrightness = 0.9f;
    2. private const uint WHITE = 4294967295;
    3. public uint[] Thinning(uint[] buffer, int originX, int originY, int xWidth, int xHeight, int width, int height)
    4. {
    5. int a, b;
    6. List<Point> lpointsToChange = new List<Point>();
    7. bool hasChange;
    8. do
    9. {
    10. hasChange = false;
    11. for (int y = originY; y < originY + xWidth; y ++)
    12. {
    13. for (int x = originX; x + 1 < originX + xWidth; x++)
    14. {
    15. a = getA(buffer, y, x, width, height);
    16. b = getB(buffer, y, x, width, height);
    17. if (y > 0 && y + 1 < height && x > 0 && x + 1 < width)
    18. {
    19. int p1 = GetValueOfColor(buffer[y * width + x]);
    20. int p2 = GetValueOfColor(buffer[(y - 1) * width + x]);
    21. int p3 = GetValueOfColor(buffer[(y - 1) * width + x + 1]);
    22. int p4 = GetValueOfColor(buffer[(y) * width + x + 1]);
    23. int p5 = GetValueOfColor(buffer[(y + 1) * width + x + 1]);
    24. int p6 = GetValueOfColor(buffer[(y + 1) * width + x]);
    25. int p7 = GetValueOfColor(buffer[(y + 1) * width + x - 1]);
    26. int p8 = GetValueOfColor(buffer[(y) * width + x - 1]);
    27. int p9 = GetValueOfColor(buffer[(y - 1) * width + x - 1]);
    28. if (p1 == 1 && 2 <= b && b <= 6 && a == 1
    29. && (p2 * p4 * p6 == 0)
    30. && (p4 * p6 * p8 == 0))
    31. {
    32. lpointsToChange.Add(new Point(x, y));
    33. hasChange = true;
    34. }
    35. }
    36. }
    37. }
    38. foreach (Point p in lpointsToChange)
    39. buffer[p.Y * width + p.X] = WHITE;
    40. lpointsToChange.Clear();
    41. for (int y = originY; y < originY + xWidth; y++)
    42. {
    43. for (int x = originY; x + 1 < originX + xWidth; x++)
    44. {
    45. a = getA(buffer, y, x, width, height);
    46. b = getB(buffer, y, x, width, height);
    47. if (y > 0 && y + 1 < height && x > 0 && x + 1 < width)
    48. {
    49. int p1 = GetValueOfColor(buffer[y * width + x]);
    50. int p2 = GetValueOfColor(buffer[(y - 1) * width + x]);
    51. int p3 = GetValueOfColor(buffer[(y - 1) * width + x + 1]);
    52. int p4 = GetValueOfColor(buffer[(y) * width + x + 1]);
    53. int p5 = GetValueOfColor(buffer[(y + 1) * width + x + 1]);
    54. int p6 = GetValueOfColor(buffer[(y + 1) * width + x]);
    55. int p7 = GetValueOfColor(buffer[(y + 1) * width + x - 1]);
    56. int p8 = GetValueOfColor(buffer[(y) * width + x - 1]);
    57. int p9 = GetValueOfColor(buffer[(y - 1) * width + x - 1]);
    58. if (p1 == 1 && 2 <= b && b <= 6 && a == 1
    59. && (p2 * p4 * p8 == 0)
    60. && (p2 * p6 * p8 == 0))
    61. {
    62. lpointsToChange.Add(new Point(x, y));
    63. hasChange = true;
    64. }
    65. }
    66. }
    67. }
    68. foreach (Point p in lpointsToChange)
    69. buffer[p.Y * width + p.X] = WHITE;
    70. lpointsToChange.Clear();
    71. } while (hasChange);
    72. return buffer;
    73. }
    74. private int GetValueOfColor(uint value)
    75. {
    76. return (value.GetBrightness() <= maxBrightness) ? 1 : 0;
    77. }
    78. private int getA(uint[] buffer, int y, int x, int width, int height)
    79. {
    80. // 1 = black 0 = white
    81. // helligkeitswerte 1-weiß 0-schwarz
    82. if (y > 0 && y + 1 < height && x > 0 && x + 1 < width)
    83. {
    84. int p2 = GetValueOfColor(buffer[(y - 1) * width + x]);
    85. int p3 = GetValueOfColor(buffer[(y - 1) * width + x + 1]);
    86. int p4 = GetValueOfColor(buffer[(y) * width + x + 1]);
    87. int p5 = GetValueOfColor(buffer[(y + 1) * width + x + 1]);
    88. int p6 = GetValueOfColor(buffer[(y + 1) * width + x]);
    89. int p7 = GetValueOfColor(buffer[(y + 1) * width + x - 1]);
    90. int p8 = GetValueOfColor(buffer[(y) * width + x - 1]);
    91. int p9 = GetValueOfColor(buffer[(y - 1) * width + x - 1]);
    92. int count = 0;
    93. //p2 p3
    94. if (p2 == 0 && p3 == 1) count++;
    95. //p3 p4
    96. if (p3 == 0 && p4 == 1) count++;
    97. //p4 p5
    98. if (p4 == 0 && p5 == 1) count++;
    99. //p5 p6
    100. if (p5 == 0 && p6 == 1) count++;
    101. //p6 p7
    102. if (p6 == 0 && p7 == 1) count++;
    103. //p7 p8
    104. if (p7 == 0 && p8 == 1) count++;
    105. //p8 p9
    106. if (p8 == 0 && p9 == 1) count++;
    107. //p9 p2
    108. if (p9 == 0 && p2 == 1) count++;
    109. return count;
    110. }
    111. else
    112. return 0;
    113. }
    114. private int getB(uint[] buffer, int y, int x, int width, int height)
    115. {
    116. if (y > 0 && y + 1 < height && x > 0 && x + 1 < width)
    117. {
    118. int p2 = GetValueOfColor(buffer[(y - 1) * width + x]);
    119. int p3 = GetValueOfColor(buffer[(y - 1) * width + x + 1]);
    120. int p4 = GetValueOfColor(buffer[(y) * width + x + 1]);
    121. int p5 = GetValueOfColor(buffer[(y + 1) * width + x + 1]);
    122. int p6 = GetValueOfColor(buffer[(y + 1) * width + x]);
    123. int p7 = GetValueOfColor(buffer[(y + 1) * width + x - 1]);
    124. int p8 = GetValueOfColor(buffer[(y) * width + x - 1]);
    125. int p9 = GetValueOfColor(buffer[(y - 1) * width + x - 1]);
    126. return p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9;
    127. }
    128. else
    129. return 0;
    130. }


    Angaben zum Code:
    Gearbeitet wird mit einem uint-Array, welches die Farbwerte jeden Pixel beinhaltet.
    Dieses wird per LockBits erzeugt. (Die Zeit bis die Bitmap "gelockt" ist sind in den 15. Sekunden nicht enthalten)

    Anhang: Wie es später mal aussehen soll. (Nur damit man sich etwas drunter vorstellen kann)
    Bilder
    • wieesseinsoll.png

      6,24 kB, 930×316, 141 mal angesehen
    @LaMiy Mach die Schleifen for (int y = originY; y < originY + xWidth; y ++) mit Parallel.For().
    Summiere in getA und getB die Werte gleich auf.
    Was macht GetBrightness()?

    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!

    LaMiy schrieb:

    Paralell.For hatte ich schon versucht.
    Das bringt eigentlich immer was, die Frage ist, wie gut Du den Innenteil entkopelst.
    Was ist mit GetBrightness()?
    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!

    LaMiy schrieb:

    Was genau meinst du damit?
    Du musst eine Prozedur schreiben, die allein mit dem y-Wert als Parameter die innere Schleife abarbeitet. Gugst Du hier.
    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!
    Statt

    C#-Quellcode

    1. for (int y = a; y < b; y++)
    2. {
    3. for (int x = a; x < b; x++)
    4. {
    5. do(x, y);
    6. }
    7. }
    machst Du so was in der Art:

    C#-Quellcode

    1. Parallel.For (a, b, ACTION(do2));
    2. // ...
    3. do2(int y)
    4. {
    5. for (int x = a; x < b; x++)
    6. {
    7. do(x, y);
    8. }
    9. }


    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!
    ich versteh den Algo nicht, aber ich würde mir denken, da kann man etwas effizienteres schaffen, einfach vom Algo her, ohne unsafe code oder parallel.

    Nämlich die Flächen iwie nach dem Turtle-Prinzip bestimmen.
    Also nicht von oben nach unten und von rechts nach links abgrasen, sondern mit einer Turtle den Rand der Fläche entlanglaufen, und immer ein Pixel wegnehmen, bis nur noch eine Linie bleibt.

    Aber das ist jetzt nur ein Konzept, und die Ausarbeitung wird sicher enorm anspruchsvoll.

    also mal das Turtle-Prinzip angugge kannst du hier: [Sammelthread] Knobel-Aufgaben, knifflige Algorithmen, elegante Lösungen

    LaMiy schrieb:


    C#-Quellcode

    1. for (int y = originY; y < originY + xWidth; y++)
    2. {
    3. for (int x = originY; x + 1 < originX + xWidth; x++)
    4. {
    5. a = getA(buffer, y, x, width, height);
    6. b = getB(buffer, y, x, width, height);
    7. // hier her
    8. if (y > 0 && y + 1 < height && x > 0 && x + 1 < width)
    9. {
    10. int p1 = GetValueOfColor(buffer[y * width + x]);
    11. int p2 = GetValueOfColor(buffer[(y - 1) * width + x]);
    12. int p3 = GetValueOfColor(buffer[(y - 1) * width + x + 1]);
    13. int p4 = GetValueOfColor(buffer[(y) * width + x + 1]);
    14. int p5 = GetValueOfColor(buffer[(y + 1) * width + x + 1]);
    15. int p6 = GetValueOfColor(buffer[(y + 1) * width + x]);
    16. int p7 = GetValueOfColor(buffer[(y + 1) * width + x - 1]);
    17. int p8 = GetValueOfColor(buffer[(y) * width + x - 1]);
    18. int p9 = GetValueOfColor(buffer[(y - 1) * width + x - 1]);
    19. if (p1 == 1 && 2 <= b && b <= 6 && a == 1 // von hier nach oben
    20. && (p2 * p4 * p8 == 0)
    21. && (p2 * p6 * p8 == 0))
    22. {
    23. lpointsToChange.Add(new Point(x, y));
    24. hasChange = true;
    25. }
    26. }
    27. }
    28. }

    Wenn Du die Bedingung (2 <= b && b <= 6 && a == 1) unmittelbar nach Berechnung von a und b abtestest, muss im Negativ-Fall der ganze innere Kram gar nicht erst berechnet werden. :thumbsup:
    Und
    Berechne keine Zwischenwerte, die Du nicht brauchst: p3, p5, p7, p7.
    Kommentiere sie aus, da bleiben sie der Vollständigkeit halber sichtbar.

    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!