Element in Bitmap finden

  • C++

Es gibt 10 Antworten in diesem Thema. Der letzte Beitrag () ist von LaMiy.

    Element in Bitmap finden

    Mein Vorhaben ist es in einer Bitmap Formen zu finden. (Welche spielt dabei keine Rolle)
    Dafür habe ich mir eine Methode geschrieben, die folgendes macht.
    1. Solange einen Pixel weitergehen, bis ein dunkler Pixel gefunden wurde. (Helligkeit wird hier überprüft)
    2. Dieser Pixel ist offenbar der Anfang einer Form
    3. Also gehe ich alle Pixel ab, die mit dieser Form zusammenhängen. (Ich gehe nach links, rechts, oben, unten, diagonal links ...)
    4. Wenn der neue Pixel auch dunkel ist, dann gehe ich wieder in alle Richtungen
    5. Bei diesem Vorgang merke ich mir alle besuchten Pixel, sodass ich sie nicht nochmal besuche
    6. Jeder dunkle Pixel wird in einem Array markiert

    Vergleichen kann man dieses Vorhaben mit einer Breitensuche, oder auch Tiefensuche.
    Nun zum eigentlichen Problem.
    In C# funktioniert der Code einwandfrei. (Breitensuche mit einer Queue und Tiefensuche mit einer Rekursion)
    In C++ funktioniert es in der Breitensuche zwar auch, aber es ist extrem langsam und ich weiß nicht woran das liegt.
    Hat jemand eine Idee woran das liegen könnte?

    Hier die Codeausschnitte und ein paar Bilder einer Leistungsanalyse.
    C++ Code

    C-Quellcode

    1. // Sucht nach Textelementen
    2. void Map::Search()
    3. {
    4. // Aktuelle Position
    5. int cp = 0;
    6. // Breite und Höhe
    7. int width = this->ptrBmpData->Width;
    8. int height = this->ptrBmpData->Height;
    9. // Neues Array erstellen (0 => nicht besucht 1=> besucht)
    10. std::vector<bool> generalMask(width * height, false);
    11. // Neuen Zeiger erstellen, der auf den ersten Pixel zeigt (alternativ kann auch Buffer benutzt werden)
    12. UINT * scan = (UINT*) ptrBmpData->Scan0;
    13. // Über alle Farbwerte gehen
    14. for (int cp = 0; cp < (width * height); cp ++)
    15. {
    16. // X und Y-Position berechnen
    17. int x = cp % width;
    18. int y = cp / width;
    19. // Wenn der aktuelle Pixel noch nicht besucht wurde und die Helligkeit stimmt
    20. if (!generalMask[cp] == true && this->GetBrightness(*scan) <= this->maxBrightness)
    21. {
    22. // Element, ausgehend vom gefundenen Punkt suchen
    23. StructureElement elem = this->GetElement(generalMask, x , y , width , height);
    24. }
    25. // Als besucht markieren
    26. generalMask[cp] = true;
    27. // Zum nächsten Wert
    28. scan++;
    29. }
    30. }
    31. // Methode um die Position eines Buchstabens zu finden
    32. StructureElement Map::GetElement(std::vector<bool>& generalMask, int originX, int originY, int width, int height)
    33. {
    34. // Maske für die Pixel dieses Buchstabens
    35. //TODO: Eventuell kleiner machen?
    36. std::vector<bool> maskResult(width * height);
    37. // Rechteck für die Ausmaße
    38. Rect rect(width + 1, height + 1, -1, -1);
    39. // Methode aufrufen und Daten in die Variablen laden
    40. GetElementHelper(generalMask,
    41. maskResult,
    42. originX,
    43. originY,
    44. width,
    45. height,
    46. rect);
    47. cout << originX << "/" << originY << "\n";
    48. //Strukturelement zurückgeben
    49. return StructureElement(maskResult,
    50. originX,
    51. originY,
    52. rect.X - rect.Width,
    53. rect.Y - rect.Height);
    54. }
    55. // Rekursive Methode für das Suchen den Positionen und Außmaße
    56. void Map::GetElementHelper(std::vector<bool>& generalMask, std::vector<bool>& bResult, int originX, int originY, int width, int height, Rect bounds)
    57. {
    58. // Neue Queue erstellen. In ihr werden alle noch zu besuchenden Pixel gespeichert
    59. queue<Vector2>* qVectors = new queue<Vector2>;
    60. qVectors->push(Vector2((float)originX, (float)originY));
    61. // Solange etwas in der Qeue ist
    62. while (!qVectors->empty())
    63. {
    64. // Nächsten Wert aus der Queue holen
    65. Vector2 vActual = qVectors->front();
    66. qVectors->pop();
    67. // Wurde der Pixel noch nicht besucht und ist er unterhalb des gewünschten Helligkeitswerts
    68. if (vActual.GetX() < width && vActual.GetY() < height &&
    69. this->GetBrightness(this->Buffer[(int)(vActual.GetY() * width + vActual.GetX())]) <= this->maxBrightness &&
    70. generalMask[(int) (vActual.GetY() * width + vActual.GetX())] == false)
    71. {
    72. // Festlegen der Größe
    73. if (vActual.GetX() < bounds.X)
    74. bounds.X = (int) vActual.GetX();
    75. if (vActual.GetY() < bounds.Y)
    76. bounds.Y = (int) vActual.GetY();
    77. if (vActual.GetX() > bounds.Width)
    78. bounds.Width = (int) vActual.GetX();
    79. if (vActual.GetY() > bounds.Height)
    80. bounds.Height = (int) vActual.GetY();
    81. // Als besucht markieren
    82. generalMask[(int) (vActual.GetY() * width + vActual.GetX())] = true;
    83. // Als Textpixel markieren
    84. bResult[(int) (vActual.GetY() * width + vActual.GetX())] = true;
    85. // Oben und unten
    86. if (originY > 0)
    87. qVectors->push(Vector2(vActual.GetX(), vActual.GetY() - 1));
    88. if (originY + 1 < height)
    89. qVectors->push(Vector2(vActual.GetX(), vActual.GetY() + 1));
    90. if (originX > 0)
    91. {
    92. // Linke Seite
    93. qVectors->push(Vector2(vActual.GetX() - 1, vActual.GetY()));
    94. // Diagonal links
    95. if (originY > 0)
    96. qVectors->push(Vector2(vActual.GetX() - 1, vActual.GetY() - 1));
    97. if (originY + 1 < height)
    98. qVectors->push(Vector2(vActual.GetX() - 1, vActual.GetY() + 1));
    99. }
    100. if (originX + 1 < width)
    101. {
    102. // Rechte Seite
    103. qVectors->push(Vector2(vActual.GetX() + 1, vActual.GetY()));
    104. // Diagonal right
    105. if (originY > 0)
    106. qVectors->push(Vector2(vActual.GetX() + 1, vActual.GetY() - 1));
    107. if (originY + 1 < height)
    108. qVectors->push(Vector2(vActual.GetX() + 1, vActual.GetY() + 1));
    109. }
    110. }
    111. }
    112. delete qVectors;
    113. qVectors = 0;
    114. }
    C#

    C-Quellcode

    1. public IEnumerable<StructureElement> Search()
    2. {
    3. // Dimension
    4. int width = this._bitmapData.Width;
    5. int height = this._bitmapData.Height;
    6. // Output list
    7. ICollection<StructureElement> elements = new LinkedList<StructureElement>();
    8. // Actual uint position
    9. int cp = 0;
    10. // Generel visited positions
    11. this.generalMask = new BitArray(width * height);
    12. // Pointer at the fist uint
    13. uint* scp = (uint*)_bitmapData.Scan0;
    14. // Create the first coordinate
    15. BitmapCoordinate crd = new BitmapCoordinate(generalMask, scp, width);
    16. // Stick the array
    17. fixed (uint* cb = this.Buffer)
    18. // Step through each pixel
    19. for (uint* cbp = cb, cbdest = cb + this.Buffer.Length; cbp < cbdest; cbp++)
    20. {
    21. // Get x and y from position
    22. int x = cp % width;
    23. int y = cp / width;
    24. //nur nicht bereits besuchte und von weiß abweichende Pixel als Struktur-Anfang feststellen
    25. if (generalMask[cp] == false && this.GetBrightness(*cbp) <= this.maxBrightness)
    26. elements.Add(GetElement(crd, x, y, width, height)); // Die Struktur innerhalb der Bitmap der Liste aller Strukturen hinzufügen
    27. generalMask.Set(cp, true);
    28. cp++; // Increment cp
    29. }
    30. return elements;
    31. }
    32. private StructureElement GetElement(BitmapCoordinate helper, int originX, int originY, int width, int height)
    33. {
    34. // Das Element ausgehend von (originX, originY) suchen
    35. BitArray bResult = new BitArray(width * height);
    36. // Resultrectangle
    37. Rect rct = new Rect() { X = width + 1, Y = height + 1, Right = -1, Bottom = -1 };
    38. // Methode mit den aktuell besuchten Feldern und einem Feld für die Ergebnisse aufrufen
    39. GetElementHelper(helper, bResult, originX, originY, width, height, rct);
    40. //Strukturelement zurückgeben
    41. return new StructureElement(bResult, _bitmap, originX, originY, new Rectangle(rct.X, rct.Y, rct.Right - rct.X, rct.Bottom - rct.Y));
    42. }
    43. private void GetElementHelper(BitmapCoordinate helper, BitArray bResult, int originX, int originY, int width, int height, Rect bounds)
    44. {
    45. // Neue Queue erstellen. In ihr werden alle noch zu besuchenden Pixel gespeichert
    46. Queue<Vector2> qVectors = new Queue<Vector2>();
    47. qVectors.Enqueue(new Vector2(originX, originY));
    48. // Solange etwas in der Qeue ist
    49. while (qVectors.Count > 0)
    50. {
    51. // Nächsten Wert aus der Queue holen
    52. Vector2 vActual = qVectors.Dequeue();
    53. // Wurde der Pixel noch nicht besucht und ist er unterhalb des gewünschten Helligkeitswerts
    54. if (vActual.X < width && vActual.Y < height &&
    55. this.GetBrightness(this.Buffer[(int)(vActual.Y * width + vActual.X)]) <= this.maxBrightness &&
    56. generalMask[(int)(vActual.Y * width + vActual.X)] == false)
    57. {
    58. // Festlegen der Größe
    59. if (vActual.X < bounds.X)
    60. bounds.X = (int)vActual.X;
    61. if (vActual.Y < bounds.Y)
    62. bounds.Y = (int)vActual.Y;
    63. if (vActual.X > bounds.Right)
    64. bounds.Right = (int)vActual.X;
    65. if (vActual.Y > bounds.Bottom)
    66. bounds.Bottom = (int)vActual.Y;
    67. // Als besucht markieren
    68. generalMask[(int)(vActual.Y * width + vActual.X)] = true;
    69. // Als Textpixel markieren
    70. bResult[(int)(vActual.Y * width + vActual.X)] = true;
    71. // Oben und unten
    72. if (originY > 0)
    73. qVectors.Enqueue(new Vector2(vActual.X, vActual.Y - 1));
    74. if (originY + 1 < height)
    75. qVectors.Enqueue(new Vector2(vActual.X, vActual.Y + 1));
    76. if (originX > 0)
    77. {
    78. // Linke Seite
    79. qVectors.Enqueue(new Vector2(vActual.X - 1, vActual.Y));
    80. // Diagonal links
    81. if (originY > 0)
    82. qVectors.Enqueue(new Vector2(vActual.X - 1, vActual.Y - 1));
    83. if (originY + 1 < height)
    84. qVectors.Enqueue(new Vector2(vActual.X - 1, vActual.Y + 1));
    85. }
    86. if (originX + 1 < width)
    87. {
    88. // Rechte Seite
    89. qVectors.Enqueue(new Vector2(vActual.X + 1, vActual.Y));
    90. // Diagonal right
    91. if (originY > 0)
    92. qVectors.Enqueue(new Vector2(vActual.X + 1, vActual.Y - 1));
    93. if (originY + 1 < height)
    94. qVectors.Enqueue(new Vector2(vActual.X + 1, vActual.Y + 1));
    95. }
    96. }
    97. }
    98. }

    Codezusatz:
    Vector2 : Structure, die einfach einen X und einen Y Wert speichert (vergleichbar mit Point)
    Buffer : Farbwerte der Bitmap als UINT
    GetBrightness() : Methode zum Auslesen des Helligkeitswerts
    StructureElement: Klasse, die einfach Informationen über eine Form festhalten soll
    BitmapCoordinate: Klasse, die einfach Informationen über die Bitmap, sowie die "generalMask", also die Maske aller besuchten Pixel festhalten soll.

    Was ich mir vorstellen könnte ist, dass das BitArray so viel schneller als vector<bool> ist, mir wurde jedoch gesagt, dass vector<bool> bis auf's letzte optimiert ist.
    Ich hoffe dass jemand mir helfen kann und dass ich das Problem verständlich geschildert habe.
    Bilder
    • process.png

      109,17 kB, 1.502×462, 169 mal angesehen
    • time.png

      15,18 kB, 1.046×302, 160 mal angesehen
    Hi
    auf vector<bool> kann man verzichten, indem man direkt auf einem Array operiert. Erzeuge dazu ein Array mit x * y / 32 32-Bit (oder x * y / 64 64-Bit) Ganzzahlen und greife auf das (x, y)-te Element über p = (y * width + x) / 32 (bzw. 64) zu und überprüfe anschließend, ob p & (1 << (y * width + x) % 32) != 0 ist.

    Gruß
    ~blaze~
    Ja, in C++ gibts aber auch noch ne list<>. Das gilt aber auch für vector<>, bis auf eine Ausnahme - der vector<bool>. Ein Array braucht pro bool ein Byte, da man minimal nur 1 Byte im Speicher ablegen kann. Ein vector<bool> braucht für jedes Element nur 1 Bit. Es wird ein Byte allokiert und dann solang mit Bits befüllt bis ein neues gebraucht wird, ich denk mal so regelt das BitArray auch.

    Ich hab schon etwas an dem Code rumgetestet und bin der Meinung, dass es an der Rekursion liegt. Diese ist auch sehr langsam, nur ich hab keine Ahnung wieso das dann in .Net läuft. Also müsste irgendwo n Fehler drin stecken.
    Ich habe es mal so getestet.

    C-Quellcode

    1. int width = 2, height = 2;
    2. unsigned int* mask = 0;
    3. mask = new unsigned int[width * height];
    4. mask[(1 * width + 0) / 32] = 1;
    5. int x = 0, y = 1;
    6. unsigned int tmp = mask[(y * width + x) / 32];
    7. if (tmp & (1 << (y * width + x) % 32) != 0)
    8. cout << "Passt";

    Ich versuche das mal zu implementieren und melde dann, ob es spürbar schneller ist.

    LaMiy schrieb:

    Ich versuche das mal zu implementieren und melde dann, ob es spürbar schneller ist.

    Es ist deutlich schneller. Es wird noch schneller, wenn du so wenig Aufrufe von new machst wie es geht. new ist teuer!
    Siehe auch: de.wikipedia.org/wiki/RAII, obwohl das für deinen Fall wohl nicht geht da der Stack zu klein ist. Aber eigentlich brauchst du new nur ein mal, imho.
    To make foobar2000 a real random music player, I figured out the only way to achieve this is to use Windows Media Player.

    At some point in time, you recognize that knowing more does not necessarily make you more happy.
    @LaMiy:: Das sieht ja sehr nach echter Bildverarbeitung aus.
    Vielleicht übersdenkst Du Dein Konzept noch mal völlig neu.
    Mach Dir eine Reihe von Elementar-Operatoren, z.B. (für SW-Bilder)
    Medianfilter (Ausreißer eliminieren)
    Gradient
    (intelligente) Diskriminierung
    Laplacefilter (Kanten finden)
    ...
    desweiteren geeignete Anzeigeoperationen, Histogramm-Korrektur, Falschfarbenbild usw.
    ---
    Und wenn Du ein Bild bearbeitest, wird der bearbeitete Bildinhalt in einen neuen Bildspeicher geschrieben bzw. danach wieder zurückkopiert.
    Und mach Dir gleich einen Undo-Operator mit.
    So kannst Du Dein Bild "experimentell" prozessieren - nach jedem Schritt anzeigen lassen und sehen, was nun kommen könnte
    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:
    Diese Art von Filtern brauche ich wahrscheinlich erst später.
    (aber auch dann nur im kleinen)
    Die Idee mit dem "Undo" Operator finde ich zu Testzwecken sehr gut, wie genau stellst du dir das vor? Einfach "Unlocken" und wieder Locken?

    @all
    Habe mich mit @blaze: über seine Variante unterhalten.
    Soweit sollte es passen, aber es fliegt 'n StackOverflow.
    Jemand eine Idee?
    Code

    C-Quellcode

    1. // Sucht nach Textelementen
    2. void Map::Search()
    3. {
    4. // Aktuelle Position
    5. int cp = 0;
    6. // Breite und Höhe
    7. int width = this->ptrBmpData->Width;
    8. int height = this->ptrBmpData->Height;
    9. // Neues Array erstellen (0 => nicht besucht 1=> besucht)
    10. unsigned int* mask = 0;
    11. mask = new unsigned int[width * height];
    12. ZeroMemory(mask, width * height);
    13. // Neuen Zeiger erstellen, der auf den ersten Pixel zeigt (alternativ kann auch Buffer benutzt werden)
    14. UINT * scan = (UINT*)ptrBmpData->Scan0;
    15. // Über alle Farbwerte gehen
    16. for (int cp = 0; cp < (width * height); cp++)
    17. {
    18. // X und Y-Position berechnen
    19. int x = cp % width;
    20. int y = cp / width;
    21. int p = mask[(y * width + x) / 32];
    22. int pp = 1 << ((y * width + x) % 32);
    23. int a = p & pp;
    24. int yy = a;
    25. // Wenn der aktuelle Pixel noch nicht besucht wurde und die Helligkeit stimmt
    26. if (a == 0 && this->GetBrightness(*scan) <= this->maxBrightness)
    27. {
    28. // Element, ausgehend vom gefundenen Punkt suchen
    29. StructureElement elem = this->GetElement(mask, x, y, width, height);
    30. }
    31. // Als besucht markieren
    32. mask[(y * width + x) / 32] |= (1 << ((width * y + x) % 32));
    33. scan ++;
    34. }
    35. }
    36. // Methode um die Position eines Buchstabens zu finden
    37. StructureElement Map::GetElement(unsigned int* mask, int originX, int originY, int width, int height)
    38. {
    39. // Maske für die Pixel dieses Buchstabens
    40. //TODO: Eventuell kleiner machen?
    41. unsigned int* maskResult = 0;
    42. maskResult = new unsigned int[width * height];
    43. ZeroMemory(maskResult, width * height);
    44. // Rechteck für die Ausmaße
    45. Rect rect(width + 1, height + 1, -1, -1);
    46. // Methode aufrufen und Daten in die Variablen laden
    47. GetElementHelper(mask,
    48. maskResult,
    49. originX,
    50. originY,
    51. width,
    52. height,
    53. rect);
    54. cout << originX << "/" << originY << "\n";
    55. //Strukturelement zurückgeben
    56. return StructureElement(maskResult,
    57. originX,
    58. originY,
    59. rect.X - rect.Width,
    60. rect.Y - rect.Height);
    61. }
    62. // Rekursive Methode für das Suchen den Positionen und Außmaße
    63. void Map::GetElementHelper(unsigned int* generalMask, unsigned int* bResult, int originX, int originY, int width, int height, Rect bounds)
    64. {
    65. int p = generalMask[(originY * width + originX) / 32];
    66. int pp = 1 << ((originY * width + originX) % 32);
    67. int a = p & pp;
    68. float brightness = this->GetBrightness(this->Buffer[(originY * width + originX)]);
    69. // Wurde der Pixel noch nicht besucht und ist er unterhalb des gewünschten Helligkeitswerts
    70. if (originX < width && originY < height && originY > 0 && originX > 0 &&
    71. brightness <= this->maxBrightness &&
    72. a == 0)
    73. {
    74. // Allocation of the vars
    75. if (originX < bounds.X)
    76. bounds.X = originX;
    77. if (originY < bounds.Y)
    78. bounds.Y = originY;
    79. if (originX > bounds.Width)
    80. bounds.Width = originX;
    81. if (originY > bounds.Height)
    82. bounds.Height = originY;
    83. // Set as visited
    84. int xx = generalMask[(originY * width + originX) / 32];
    85. generalMask[(originY * width + originX) / 32] |= 1 << ((originY * width + originX) % 32);
    86. int xxx = generalMask[(originY * width + originX) / 32];
    87. // Mark as not white pixel
    88. bResult[(originY * width + originX) / 32] |= 1 << ((originY * width + originX) % 32);
    89. // Up and down
    90. if (originY > 0)
    91. GetElementHelper(generalMask, bResult, originX, originY - 1, width, height, bounds);
    92. if (originY + 1 < height)
    93. GetElementHelper(generalMask, bResult, originX, originY + 1, width, height, bounds);
    94. if (originX > 0)
    95. {
    96. // Continue at the left side
    97. GetElementHelper(generalMask, bResult, originX - 1, originY, width, height, bounds);
    98. // Diagonal left
    99. if (originY > 0)
    100. GetElementHelper(generalMask, bResult, originX - 1, originY - 1, width, height, bounds);
    101. if (originY + 1 < height)
    102. GetElementHelper(generalMask, bResult, originX - 1, originY + 1, width, height, bounds);
    103. }
    104. if (originX + 1 < width)
    105. {
    106. // Continue at the right side
    107. GetElementHelper(generalMask, bResult, originX + 1, originY, width, height, bounds);
    108. // Diagonal right
    109. if (originY > 0)
    110. GetElementHelper(generalMask, bResult, originX + 1, originY - 1, width, height, bounds);
    111. if (originY + 1 < height)
    112. GetElementHelper(generalMask, bResult, originX + 1, originY + 1, width, height, bounds);
    113. }
    114. }
    115. }

    LaMiy schrieb:

    wie genau stellst du dir das vor?
    Du arbeitest auf einem Array. Mach vor dem Anwenden des Operators eine Kopie vom Feld. Die kannst Du in einer List(Of ) | vector<> speichern und ggf. restaurieren.
    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!
    So ich habe es nun hiermit gelöst. stackoverflow.com/questions/12…-and-assignment-operators
    Das ist im Prinzip die Methode, die blaze vorgeschlagen hat.
    Klappt sehr gut, und im direkten Vergleich bekam ich mit der Methode vector<bool> 'nen StackOverflow, während die verlinkte Klasse funktioniert hat.
    (Kann eventuell sein, dass ich vector<bool> falsch benutzt habe :D )