Algorithmus zum Erkennen von Ecken von Papierblättern im Foto


97

Was ist der beste Weg, um die Ecken einer Rechnung / Quittung / eines Blattes Papier auf einem Foto zu erkennen? Dies ist für die nachfolgende perspektivische Korrektur vor der OCR zu verwenden.

Mein aktueller Ansatz war:

RGB> Grau> Canny Edge Detection mit Schwellenwert> Dilatieren (1)> Kleine Objekte entfernen (6)> Grenzobjekte löschen> Größeres Blog basierend auf konvexem Bereich auswählen. > [Eckenerkennung - Nicht implementiert]

Ich kann nicht anders, als zu glauben, dass es einen robusteren „intelligenten“ / statistischen Ansatz geben muss, um diese Art der Segmentierung zu handhaben. Ich habe nicht viele Trainingsbeispiele, aber ich könnte wahrscheinlich 100 Bilder zusammen bekommen.

Breiteren Kontext:

Ich verwende Matlab als Prototyp und plane, das System in OpenCV und Tesserect-OCR zu implementieren. Dies ist das erste einer Reihe von Bildverarbeitungsproblemen, die ich für diese spezielle Anwendung lösen muss. Daher möchte ich meine eigene Lösung entwickeln und mich erneut mit Bildverarbeitungsalgorithmen vertraut machen.

Hier sind einige Beispielbilder, die der Algorithmus verarbeiten soll: Wenn Sie die Herausforderung annehmen möchten, finden Sie die großen Bilder unter http://madteckhead.com/tmp

Fall 1
(Quelle: madteckhead.com )

Fall 2
(Quelle: madteckhead.com )

Fall 3
(Quelle: madteckhead.com )

Fall 4
(Quelle: madteckhead.com )

Im besten Fall ergibt dies:

Fall 1 - schlau
(Quelle: madteckhead.com )

Fall 1 - Post Canny
(Quelle: madteckhead.com )

Fall 1 - größter Blog
(Quelle: madteckhead.com )

In anderen Fällen schlägt es jedoch leicht fehl:

Fall 2 - schlau
(Quelle: madteckhead.com )

Fall 2 - Post Canny
(Quelle: madteckhead.com )

Fall 2 - größter Blog
(Quelle: madteckhead.com )

Vielen Dank im Voraus für all die tollen Ideen! Ich liebe so!

EDIT: Hough Transform Fortschritt

F: Welcher Algorithmus würde die Hough-Linien gruppieren, um Ecken zu finden? Nach den Ratschlägen der Antworten konnte ich die Hough-Transformation verwenden, Linien auswählen und filtern. Mein aktueller Ansatz ist ziemlich grob. Ich bin davon ausgegangen, dass die Rechnung immer weniger als 15 Grad vom Bild entfernt ist. Wenn dies der Fall ist, erhalte ich vernünftige Ergebnisse für Zeilen (siehe unten). Ich bin mir jedoch nicht ganz sicher, welcher Algorithmus geeignet ist, um die Linien zu gruppieren (oder abzustimmen), um sie für die Ecken zu extrapolieren. Die Hough-Linien sind nicht durchgehend. Und in den verrauschten Bildern können parallele Linien vorhanden sein, sodass eine gewisse Form oder Entfernung von den Linienursprungsmetriken erforderlich ist. Irgendwelche Ideen?

Fall 1 Fall 2 Fall 3 Fall 4
(Quelle: madteckhead.com )


1
Ja, ich habe es in etwa 95% der Fälle zum Laufen gebracht. Ich musste den Code seitdem aus Zeitgründen zurückstellen. Ich werde irgendwann ein Follow-up veröffentlichen. Wenn Sie dringend Hilfe benötigen, können Sie mich gerne beauftragen. Entschuldigung für das Fehlen einer guten Nachverfolgung. Ich würde gerne wieder an dieser Funktion arbeiten.
Nathan Keller

Nathan, könntest du bitte ein Follow-up darüber posten, wie du dazu gekommen bist? Ich bin an der gleichen Stelle geblieben und habe Ecken / Außenkonturen des Blattes erkannt. Ich habe genau die gleichen Probleme wie Sie, daher wäre ich sehr an einer Lösung interessiert.
Tim

6
Alle Bilder in diesem Beitrag jetzt 404.
ChrisF

Antworten:


28

Ich bin Martins Freund, der Anfang dieses Jahres daran gearbeitet hat. Dies war mein erstes Codierungsprojekt und endete ein bisschen in Eile, daher muss der Code etwas fehlerhaft ... dekodiert werden ... Ich werde ein paar Tipps von dem geben, was ich bereits gesehen habe, und dann sortiere meinen Code an meinem freien Tag morgen.

Erster Tipp, OpenCVund pythonsind fantastisch, gehen Sie so schnell wie möglich zu ihnen. : D.

Anstatt kleine Objekte und / oder Geräusche zu entfernen, senken Sie die schlauen Fesseln, damit mehr Kanten akzeptiert werden, und finden Sie dann die größte geschlossene Kontur (bei OpenCV-Verwendung findcontour()mit einigen einfachen Parametern, glaube ich CV_RETR_LIST). Es könnte immer noch schwierig sein, wenn es auf einem weißen Blatt Papier ist, aber es lieferte definitiv die besten Ergebnisse.

Houghline2()Versuchen Sie für die Transformation mit dem CV_HOUGH_STANDARDim Gegensatz zu dem CV_HOUGH_PROBABILISTIC, es gibt Rho- und Theta- Werte, die die Linie in Polarkoordinaten definieren, und dann können Sie die Linien innerhalb einer bestimmten Toleranz zu diesen gruppieren.

Meine Gruppierung diente als Nachschlagetabelle. Für jede Zeile, die von der Hough-Transformation ausgegeben wurde, ergab sich ein Rho-Theta-Paar. Wenn diese Werte innerhalb von beispielsweise 5% eines Wertepaars in der Tabelle lagen, wurden sie verworfen. Wenn sie außerhalb dieser 5% lagen, wurde der Tabelle ein neuer Eintrag hinzugefügt.

Sie können dann viel einfacher parallele Linien oder Abstände zwischen Linien analysieren.

Hoffe das hilft.


Hallo Daniel, danke, dass du mitgemacht hast. Ich mag dich nähern. Es ist eigentlich die Route, mit der ich im Moment gute Ergebnisse erziele. Es gab sogar ein OpenCV-Beispiel, das die Rechtecke erkannte. Ich musste nur etwas nach den Ergebnissen filtern. Wie Sie sagten, ist das Weiß auf Weiß mit dieser Methode schwer zu erkennen. Aber es war ein einfacher und kostengünstigerer Ansatz als der Hough. Ich habe den Hough-Ansatz tatsächlich aus meinem Algo herausgelassen und eine Poly-Approximation durchgeführt. Schauen Sie sich das Beispiel mit den Quadraten in opencv an. Ich würde gerne Ihre Umsetzung der Hough-Abstimmung sehen. Vielen Dank im Voraus, Nathan
Nathan Keller

Ich hatte Probleme mit diesem Ansatz, ich werde eine Lösung veröffentlichen, wenn ich etwas Besseres für zukünftige Referenz entwickeln kann
Anshuman Kumar

@AnshumanKumar Ich brauche wirklich Hilfe bei dieser Frage. Kannst du mir bitte helfen? stackoverflow.com/questions/61216402/…
Carlos Diego

19

Eine Studentengruppe an meiner Universität hat kürzlich eine iPhone-App (und eine Python-OpenCV-App) vorgeführt, die genau dafür geschrieben wurde. Soweit ich mich erinnere, waren die Schritte ungefähr so:

  • Medianfilter zum vollständigen Entfernen des Textes auf dem Papier (dies war handgeschriebener Text auf weißem Papier mit ziemlich guter Beleuchtung und funktioniert möglicherweise nicht mit gedrucktem Text, es hat sehr gut funktioniert). Der Grund war, dass es die Eckenerkennung viel einfacher macht.
  • Hough Transform für Linien
  • Suchen Sie die Peaks im Hough Transform-Akkumulatorraum und zeichnen Sie jede Linie über das gesamte Bild.
  • Analysieren Sie die Linien und entfernen Sie alle Linien, die sehr nahe beieinander liegen und sich in einem ähnlichen Winkel befinden (gruppieren Sie die Linien zu einer Linie). Dies ist notwendig, da die Hough-Transformation nicht perfekt ist, da sie in einem diskreten Probenraum arbeitet.
  • Suchen Sie Linienpaare, die ungefähr parallel sind und andere Paare schneiden, um zu sehen, welche Linien Quads bilden.

Dies schien ziemlich gut zu funktionieren und sie konnten ein Foto von einem Stück Papier oder Buch aufnehmen, die Eckenerkennung durchführen und dann das Dokument im Bild in fast Echtzeit auf eine flache Ebene abbilden (es gab eine einzige OpenCV-Funktion, die ausgeführt werden musste die Zuordnung). Es gab keine OCR, als ich sah, dass es funktionierte.


Danke für die tollen Ideen Martin. Ich habe Ihren Rat befolgt und den Hough-Transformationsansatz implementiert. (Siehe Ergebnisse oben). Ich habe Mühe, einen robusten Algorithmus zu bestimmen, der die Linien extrapoliert, um die Schnittpunkte zu finden. Es gibt nicht viele Zeilen und einige Fehlalarme. Haben Sie einen Rat, wie ich Zeilen am besten zusammenführen und verwerfen kann? Wenn Ihre Schüler interessiert sind, ermutigen Sie sie bitte, Kontakt aufzunehmen. Ich würde gerne ihre Erfahrungen mit der Ausführung der Algorithmen auf einer mobilen Plattform hören. (Das ist mein nächstes Ziel). Vielen Dank für Ihre Ideen.
Nathan Keller

1
Es sieht so aus, als ob der HT für Linien in allen außer Ihrem zweiten Bild gut funktioniert hat. Definieren Sie jedoch eine Schwellentoleranz für Ihre Start- und Endwerte im Akkumulator? Der HT definiert nicht wirklich Start- und Endpositionen, sondern die m- und c-Werte in y = mx + c. Siehe hier - beachten Sie, dass hier eher Polarkoordinaten im Akkumulator als kartesische verwendet werden. Auf diese Weise können Sie Linien nach c und dann nach m gruppieren, um sie auszudünnen. Wenn Sie sich vorstellen, dass sich die Linien über das gesamte Bild erstrecken, finden Sie nützlichere Schnittpunkte.
Martin Foot

@MartinFoot Ich brauche wirklich Hilfe bei dieser Frage. Kannst du mir bitte helfen? stackoverflow.com/questions/61216402/…
Carlos Diego

16

Folgendes habe ich mir nach einigem Experimentieren ausgedacht:

import cv, cv2, numpy as np
import sys

def get_new(old):
    new = np.ones(old.shape, np.uint8)
    cv2.bitwise_not(new,new)
    return new

if __name__ == '__main__':
    orig = cv2.imread(sys.argv[1])

    # these constants are carefully picked
    MORPH = 9
    CANNY = 84
    HOUGH = 25

    img = cv2.cvtColor(orig, cv2.COLOR_BGR2GRAY)
    cv2.GaussianBlur(img, (3,3), 0, img)


    # this is to recognize white on white
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(MORPH,MORPH))
    dilated = cv2.dilate(img, kernel)

    edges = cv2.Canny(dilated, 0, CANNY, apertureSize=3)

    lines = cv2.HoughLinesP(edges, 1,  3.14/180, HOUGH)
    for line in lines[0]:
         cv2.line(edges, (line[0], line[1]), (line[2], line[3]),
                         (255,0,0), 2, 8)

    # finding contours
    contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL,
                                   cv.CV_CHAIN_APPROX_TC89_KCOS)
    contours = filter(lambda cont: cv2.arcLength(cont, False) > 100, contours)
    contours = filter(lambda cont: cv2.contourArea(cont) > 10000, contours)

    # simplify contours down to polygons
    rects = []
    for cont in contours:
        rect = cv2.approxPolyDP(cont, 40, True).copy().reshape(-1, 2)
        rects.append(rect)

    # that's basically it
    cv2.drawContours(orig, rects,-1,(0,255,0),1)

    # show only contours
    new = get_new(img)
    cv2.drawContours(new, rects,-1,(0,255,0),1)
    cv2.GaussianBlur(new, (9,9), 0, new)
    new = cv2.Canny(new, 0, CANNY, apertureSize=3)

    cv2.namedWindow('result', cv2.WINDOW_NORMAL)
    cv2.imshow('result', orig)
    cv2.waitKey(0)
    cv2.imshow('result', dilated)
    cv2.waitKey(0)
    cv2.imshow('result', edges)
    cv2.waitKey(0)
    cv2.imshow('result', new)
    cv2.waitKey(0)

    cv2.destroyAllWindows()

Nicht perfekt, funktioniert aber zumindest für alle Proben:

1 2 3 4


4
Ich arbeite an einem ähnlichen Projekt. Ich laufe über dem Code und es gibt mir den Fehler "Kein Modul namens cv". Ich habe die Open CV 2.4-Version installiert und der Import von cv2 funktioniert perfekt für mich.
Navneet Singh

Wären Sie so freundlich, diesen Code zu aktualisieren, damit er funktioniert? pastebin.com/PMH5Y0M8 gibt mir nur eine schwarze Seite.
the7erm

Haben Sie eine Idee, wie Sie den folgenden Code in Java umwandeln können: for line in lines[0]: cv2.line(edges, (line[0], line[1]), (line[2], line[3]), (255,0,0), 2, 8) # finding contours contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL, cv.CV_CHAIN_APPROX_TC89_KCOS) contours = filter(lambda cont: cv2.arcLength(cont, False) > 100, contours) contours = filter(lambda cont: cv2.contourArea(cont) > 10000, contours)
Aurelianr

Vanuan, ich brauche wirklich Hilfe bei dieser Frage. Können Sie mir bitte helfen? stackoverflow.com/questions/61216402/…
Carlos Diego

9

Anstatt mit der Kantenerkennung zu beginnen, können Sie die Eckenerkennung verwenden.

Marvin Framework bietet zu diesem Zweck eine Implementierung des Moravec-Algorithmus. Sie könnten die Ecken der Papiere als Ausgangspunkt finden. Unterhalb der Ausgabe des Moravec-Algorithmus:

Geben Sie hier die Bildbeschreibung ein


4

Sie können auch MSER ( Maximal stabile Extremalregionen) über dem Sobel-Operatorergebnis verwenden, um die stabilen Regionen des Bildes zu finden. Für jede von MSER zurückgegebene Region können Sie eine konvexe Hülle und eine Poly-Approximation anwenden, um Folgendes zu erhalten:

Diese Art der Erkennung ist jedoch nützlich für die Live-Erkennung von mehr als einem einzelnen Bild, das nicht immer das beste Ergebnis liefert.

Ergebnis


1
Können Sie einige weitere Details für diesen Code mitteilen, vielleicht ein paar im Voraus
Monty

In cv2.CHAIN_APPROX_SIMPLE wird eine Fehlermeldung angezeigt, die besagt, dass zu viele Werte zum Entpacken vorhanden sind. Irgendeine Idee? Ich verwende ein 1024 * 1024-Bild als Beispiel
Praveen

1
Dank all, nur aus der Änderung der Syntax rechnet im aktuellen OpenCV Zweig answers.opencv.org/question/40329/...
Praveen

Ist MSER nicht dazu gedacht, Blobs zu extrahieren? Ich habe es versucht und es erkennt nur den größten Teil des Textes
Anshuman Kumar

3

Verwenden Sie nach der Kantenerkennung die Hough-Transformation. Fügen Sie diese Punkte dann mit ihren Beschriftungen in eine SVM (Supporting Vector Machine) ein. Wenn die Beispiele glatte Linien aufweisen, hat SVM keine Schwierigkeiten, die erforderlichen Teile des Beispiels und andere Teile zu teilen. Mein Rat zu SVM, setzen Sie einen Parameter wie Konnektivität und Länge. Das heißt, wenn Punkte verbunden und lang sind, sind sie wahrscheinlich eine Empfangszeile. Dann können Sie alle anderen Punkte entfernen.


Hallo Ares, danke für deine Ideen! Ich habe die Hough-Transformation implementiert (siehe oben). Ich kann keinen robusten Weg finden, um die Ecken angesichts der falsch positiven und nicht durchgehenden Linien zu finden. Haben Sie weitere Ideen? Es ist eine Weile her, seit ich mir SVM-Techniken angesehen habe. Ist das ein beaufsichtigter Ansatz? Ich habe keine Trainingsdaten, aber ich könnte einige generieren. Ich würde gerne den Ansatz untersuchen, da ich mehr über SVM erfahren möchte. Können Sie irgendwelche Ressourcen empfehlen? Mit freundlichen Grüßen. Nathan
Nathan Keller

3

Hier haben Sie @ Vanuans Code mit C ++:

cv::cvtColor(mat, mat, CV_BGR2GRAY);
cv::GaussianBlur(mat, mat, cv::Size(3,3), 0);
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Point(9,9));
cv::Mat dilated;
cv::dilate(mat, dilated, kernel);

cv::Mat edges;
cv::Canny(dilated, edges, 84, 3);

std::vector<cv::Vec4i> lines;
lines.clear();
cv::HoughLinesP(edges, lines, 1, CV_PI/180, 25);
std::vector<cv::Vec4i>::iterator it = lines.begin();
for(; it!=lines.end(); ++it) {
    cv::Vec4i l = *it;
    cv::line(edges, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), cv::Scalar(255,0,0), 2, 8);
}
std::vector< std::vector<cv::Point> > contours;
cv::findContours(edges, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_TC89_KCOS);
std::vector< std::vector<cv::Point> > contoursCleaned;
for (int i=0; i < contours.size(); i++) {
    if (cv::arcLength(contours[i], false) > 100)
        contoursCleaned.push_back(contours[i]);
}
std::vector<std::vector<cv::Point> > contoursArea;

for (int i=0; i < contoursCleaned.size(); i++) {
    if (cv::contourArea(contoursCleaned[i]) > 10000){
        contoursArea.push_back(contoursCleaned[i]);
    }
}
std::vector<std::vector<cv::Point> > contoursDraw (contoursCleaned.size());
for (int i=0; i < contoursArea.size(); i++){
    cv::approxPolyDP(Mat(contoursArea[i]), contoursDraw[i], 40, true);
}
Mat drawing = Mat::zeros( mat.size(), CV_8UC3 );
cv::drawContours(drawing, contoursDraw, -1, cv::Scalar(0,255,0),1);

Wo ist die Definition von Linienvariablen? Muss std :: vector <cv :: Vec4i> -Linien sein;
Kann Ürek

@ CanÜrek Du hast recht. std::vector<cv::Vec4i> lines;wird in meinem Projekt global deklariert.
GBF_Gabriel

1
  1. In Laborraum konvertieren

  2. Verwenden Sie kmeans Segment 2 Cluster

  3. Verwenden Sie dann Konturen oder Hough auf einem der Cluster (intenral)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.