Algorithme pour détecter les coins de la feuille de papier sur la photo


98

Quelle est la meilleure façon de détecter les coins d'une facture / d'un reçu / d'une feuille de papier sur une photo? Ceci doit être utilisé pour la correction de perspective ultérieure, avant l'OCR.

Mon approche actuelle a été:

RVB> Gris> Détection de Canny Edge avec seuillage> Dilater (1)> Supprimer les petits objets (6)> Effacer les objets de la frontière> choisir un grand blog basé sur la zone convexe. > [détection de coin - Non implémenté]

Je ne peux m'empêcher de penser qu'il doit y avoir une approche «intelligente» / statistique plus robuste pour gérer ce type de segmentation. Je n'ai pas beaucoup d'exemples de formation, mais je pourrais probablement rassembler 100 images.

Contexte plus large:

J'utilise matlab pour prototyper et je prévois d'implémenter le système dans OpenCV et Tesserect-OCR. C'est le premier d'un certain nombre de problèmes de traitement d'image que je dois résoudre pour cette application spécifique. Je cherche donc à lancer ma propre solution et à me re-familiariser avec les algorithmes de traitement d'image.

Voici un exemple d'image que j'aimerais que l'algorithme gère: Si vous souhaitez relever le défi, les grandes images sont à http://madteckhead.com/tmp

cas 1
(source: madteckhead.com )

cas 2
(source: madteckhead.com )

cas 3
(source: madteckhead.com )

cas 4
(source: madteckhead.com )

Dans le meilleur des cas, cela donne:

cas 1 - rusé
(source: madteckhead.com )

cas 1 - post canny
(source: madteckhead.com )

cas 1 - le plus grand blog
(source: madteckhead.com )

Cependant il échoue facilement sur d'autres cas:

cas 2 - rusé
(source: madteckhead.com )

cas 2 - post canny
(source: madteckhead.com )

cas 2 - le plus grand blog
(source: madteckhead.com )

Merci d'avance pour toutes les bonnes idées! J'aime tellement!

EDIT: Progrès de la transformation de Hough

Q: Quel algorithme regrouperait les lignes hough pour trouver des coins? En suivant les conseils des réponses, j'ai pu utiliser la transformation de Hough, choisir des lignes et les filtrer. Mon approche actuelle est plutôt grossière. J'ai fait l'hypothèse que la facture sera toujours à moins de 15 degrés hors d'alignement avec l'image. Je me retrouve avec des résultats raisonnables pour les lignes si tel est le cas (voir ci-dessous). Mais je ne suis pas tout à fait sûr d'un algorithme approprié pour regrouper les lignes (ou voter) à extrapoler pour les coins. Les lignes de Hough ne sont pas continues. Et dans les images bruyantes, il peut y avoir des lignes parallèles, donc une certaine forme ou une certaine distance par rapport aux métriques d'origine de la ligne sont nécessaires. Des idées?

cas 1 cas 2 cas 3 cas 4
(source: madteckhead.com )


1
Oui, je l'ai fait fonctionner dans environ 95% des cas. Depuis, j'ai dû mettre le code de côté en raison du manque de temps. Je posterai un suivi à un moment donné, n'hésitez pas à me commander si vous avez besoin d'une aide urgente. Désolé pour le manque de bon suivi. J'adorerais revenir travailler sur cette fonctionnalité.
Nathan Keller

Nathan, pourriez-vous s'il vous plaît poster un suivi sur la façon dont vous avez fini par le faire? Je suis resté au même point en reconnaissant les coins / contour extérieur d'une feuille de papier. Je rencontre exactement les mêmes problèmes que vous, donc je serais très intéressé par une solution.
tim

6
Toutes les images de cet article maintenant 404.
ChrisF

Réponses:


28

Je suis l'ami de Martin qui travaillait là-dessus plus tôt cette année. C'était mon tout premier projet de codage, et s'est un peu terminé dans un peu de précipitation, donc le code a besoin d'errr ... décodage ... je vais donner quelques conseils de ce que je vous ai déjà vu faire, puis trier mon code le jour de mon congé de demain.

Premier conseil, OpenCVet vous pythonêtes génial, passez à eux dès que possible. :RÉ

Au lieu de supprimer les petits objets et / ou le bruit, abaissez les contraintes astucieuses, de sorte qu'il accepte plus d'arêtes, puis trouvez le plus grand contour fermé (dans OpenCV, utilisez findcontour()des paramètres simples, je pense que j'ai utilisé CV_RETR_LIST). pourrait encore avoir du mal quand c'est sur un morceau de papier blanc, mais fournissait certainement les meilleurs résultats.

Pour la Houghline2()transformation, essayez avec le CV_HOUGH_STANDARDplutôt que le CV_HOUGH_PROBABILISTIC, cela donnera des valeurs rho et thêta , définissant la ligne en coordonnées polaires, puis vous pourrez regrouper les lignes dans une certaine tolérance à celles-ci.

Mon regroupement fonctionnait comme une table de recherche, pour chaque ligne sortie de la transformation hough, cela donnerait une paire rho et thêta. Si ces valeurs se situaient dans, disons 5% d'une paire de valeurs du tableau, elles étaient ignorées, si elles étaient en dehors de ces 5%, une nouvelle entrée était ajoutée au tableau.

Vous pouvez alors faire l'analyse des lignes parallèles ou de la distance entre les lignes beaucoup plus facilement.

J'espère que cela t'aides.


Salut Daniel, merci de votre implication. J'aime ton approche. c'est en fait l'itinéraire avec lequel j'obtiens de bons résultats pour le moment. Il y avait même un exemple OpenCV qui a détecté les rectangles. Juste eu à faire un peu de filtrage sur les résultats. comme vous le disiez, le blanc sur blanc est difficile à détecter avec cette méthode. Mais c'était une approche simple et moins coûteuse que la hough. En fait, j'ai laissé l'approche hough de mon algorithme et j'ai fait une approximation poly, jetez un œil à l'exemple des carrés dans opencv. J'aimerais voir votre mise en œuvre du vote hough. Merci d'avance, Nathan
Nathan Keller

J'avais des problèmes avec cette approche, je publierai une solution si je peux concevoir quelque chose de mieux pour référence future
Anshuman Kumar

@AnshumanKumar j'ai vraiment besoin d'aide avec cette question, pouvez-vous m'aider, s'il vous plaît? stackoverflow.com/questions/61216402/…
Carlos Diego

19

Un groupe d'étudiants de mon université a récemment démontré une application iPhone (et une application Python OpenCV) qu'ils avaient écrite pour faire exactement cela. Si je me souviens bien, les étapes ressemblaient à ceci:

  • Filtre médian pour supprimer complètement le texte sur le papier (il s'agissait d'un texte manuscrit sur papier blanc avec un assez bon éclairage et peut ne pas fonctionner avec du texte imprimé, cela a très bien fonctionné). La raison en était que cela rend la détection des coins beaucoup plus facile.
  • Hough Transform pour les lignes
  • Trouvez les pics dans l'espace de l'accumulateur Hough Transform et tracez chaque ligne sur toute l'image.
  • Analysez les lignes et supprimez celles qui sont très proches les unes des autres et à un angle similaire (regroupez les lignes en une seule). Ceci est nécessaire car la transformation de Hough n'est pas parfaite car elle fonctionne dans un espace d'échantillonnage discret.
  • Trouvez des paires de lignes à peu près parallèles et qui coupent d'autres paires pour voir quelles lignes forment des quadrilatères.

Cela semblait assez bien fonctionner et ils ont pu prendre une photo d'un morceau de papier ou d'un livre, effectuer la détection des coins, puis mapper le document de l'image sur un plan plat presque en temps réel (il y avait une seule fonction OpenCV à exécuter la cartographie). Il n'y avait pas d'OCR quand je l'ai vu fonctionner.


Merci pour les bonnes idées Martin. J'ai suivi vos conseils et mis en œuvre l'approche de transformation de Hough. (Voir les résultats ci-dessus). J'ai du mal à déterminer un algorithme robuste qui extrapolera les lignes pour trouver les intersections. Il n'y a pas beaucoup de lignes et quelques faux positifs. Avez-vous des conseils sur la meilleure façon de fusionner et de supprimer des lignes? Si vos étudiants sont intéressés, encouragez-les à entrer en contact. J'aimerais entendre leurs expériences pour faire fonctionner les algorithmes sur une plate-forme mobile. (C'est mon prochain objectif). Merci beaucoup pour vos idées.
Nathan Keller le

1
Il semble que le HT pour les lignes a bien fonctionné dans tout sauf votre deuxième image, mais définissez-vous une tolérance de seuil pour vos valeurs de début et de fin dans l'accumulateur? Le HT ne définit pas vraiment les positions de début et de fin, mais plutôt les valeurs m et c dans y = mx + c. Voir ici - notez que cela utilise des coordonnées polaires dans l'accumulateur plutôt que cartésiennes. De cette façon, vous pouvez regrouper les lignes par c puis par m afin de les éclaircir et en imaginant les lignes s'étendant sur toute l'image, vous trouverez des intersections plus utiles.
Martin Foot

@MartinFoot J'ai vraiment besoin d'aide avec cette question, pouvez-vous m'aider, s'il vous plaît? stackoverflow.com/questions/61216402/…
Carlos Diego

16

Voici ce que j'ai trouvé après un peu d'expérimentation:

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()

Pas parfait, mais fonctionne au moins pour tous les échantillons:

1 2 3 4


4
Je travaille sur un projet similaire. Je cours au-dessus du code et cela me donne l'erreur "Aucun module nommé cv". J'ai installé la version Open CV 2.4 et l'importation cv2 fonctionne parfaitement pour moi.
Navneet Singh

Auriez-vous la gentillesse de mettre à jour ce code pour qu'il fonctionne? pastebin.com/PMH5Y0M8 cela me donne juste une page noire.
the7erm

Avez-vous une idée sur la façon de transformer le code suivant en java: 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 J'ai vraiment besoin d'aide avec cette question, pouvez-vous m'aider, s'il vous plaît? stackoverflow.com/questions/61216402/…
Carlos Diego

9

Au lieu de partir de la détection des bords, vous pouvez utiliser la détection des coins.

Marvin Framework fournit une implémentation de l'algorithme de Moravec à cet effet. Vous pouvez trouver les coins des papiers comme point de départ. Ci-dessous la sortie de l'algorithme de Moravec:

entrez la description de l'image ici


4

Vous pouvez également utiliser MSER (régions extrêmes maximales stables) sur le résultat de l'opérateur Sobel pour trouver les régions stables de l'image. Pour chaque région renvoyée par MSER, vous pouvez appliquer une coque convexe et une approximation poly pour en obtenir comme ceci:

Mais ce type de détection est utile pour la détection en direct de plus d'une seule image qui ne renvoie pas toujours le meilleur résultat.

résultat


1
Pouvez-vous partager plus de détails pour ce code, peut-être un peu, merci beaucoup à l'avance
Monty

Je reçois une erreur dans cv2.CHAIN_APPROX_SIMPLE indiquant trop de valeurs à décompresser. Une idée? J'utilise une image 1024 * 1024 comme échantillon
Praveen

1
Merci à tous, je viens de comprendre le changement de syntaxe dans la branche actuelle d'Opencv answers.opencv.org/question/40329/…
Praveen

MSER n'est-il pas destiné à extraire des blobs? Je l'ai essayé et il ne détecte que la plupart du texte
Anshuman Kumar

3

Après la détection des bords, utilisez la transformation de Hough. Ensuite, mettez ces points dans une SVM (machine à vecteurs de support) avec leurs étiquettes, si les exemples ont des lignes lisses sur eux, SVM n'aura aucune difficulté à diviser les parties nécessaires de l'exemple et les autres parties. Mon conseil sur SVM, mettez un paramètre comme la connectivité et la longueur. Autrement dit, si les points sont connectés et longs, ils sont susceptibles d'être une ligne du reçu. Ensuite, vous pouvez éliminer tous les autres points.


Salut Ares, merci pour vos idées! J'ai implémenté la transformation Hough (voir ci-dessus). Je ne peux pas trouver un moyen robuste de trouver les coins étant donné les faux positifs et les lignes non continues. Avez-vous d'autres idées? Cela fait un moment que je n'ai pas regardé les techniques SVM. Est-ce une approche supervisée? Je n'ai pas de données d'entraînement, mais je pourrais en générer. Je serais intéressé à explorer l'approche car je souhaite en savoir plus sur SVM. Pouvez-vous recommander des ressources. Sincères amitiés. Nathan
Nathan Keller

3

Voici le code de @Vanuan utilisant 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);

Où est la définition des variables de lignes? Doit être des lignes std :: vector <cv :: Vec4i>;
Can Ürek

@ CanÜrek Vous avez raison. std::vector<cv::Vec4i> lines;est déclaré dans une portée globale dans mon projet.
GBF_Gabriel

1
  1. Convertir en espace de laboratoire

  2. Utiliser le cluster kmeans segment 2

  3. Ensuite, utilisez des contours ou hough sur l'un des clusters (intenral)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.