Algoritmo per rilevare gli angoli del foglio di carta nella foto


97

Qual è il modo migliore per rilevare gli angoli di una fattura / ricevuta / foglio di carta in una foto? Questo deve essere utilizzato per la successiva correzione della prospettiva, prima dell'OCR.

Il mio approccio attuale è stato:

RGB> Grigio> Rilevamento Canny Edge con soglia> Dilata (1)> Rimuovi piccoli oggetti (6)> Cancella oggetti boarder> scegli blog più grandi in base all'area convessa. > [rilevamento angoli - Non implementato]

Non posso fare a meno di pensare che ci debba essere un approccio statistico / "intelligente" più robusto per gestire questo tipo di segmentazione. Non ho molti esempi di formazione, ma probabilmente potrei mettere insieme 100 immagini.

Contesto più ampio:

Sto usando matlab per prototipare e sto pianificando di implementare il sistema in OpenCV e Tesserect-OCR. Questo è il primo di una serie di problemi di elaborazione delle immagini che devo risolvere per questa specifica applicazione. Quindi sto cercando di lanciare la mia soluzione e familiarizzare con gli algoritmi di elaborazione delle immagini.

Ecco alcune immagini di esempio che vorrei che l'algoritmo gestisse: Se desideri accettare la sfida, le immagini di grandi dimensioni si trovano su http://madteckhead.com/tmp

caso 1
(fonte: madteckhead.com )

caso 2
(fonte: madteckhead.com )

caso 3
(fonte: madteckhead.com )

caso 4
(fonte: madteckhead.com )

Nel migliore dei casi questo dà:

caso 1 - astuto
(fonte: madteckhead.com )

caso 1 - post canny
(fonte: madteckhead.com )

caso 1 - blog più grande
(fonte: madteckhead.com )

Tuttavia fallisce facilmente in altri casi:

caso 2 - astuto
(fonte: madteckhead.com )

caso 2 - post canny
(fonte: madteckhead.com )

caso 2 - blog più grande
(fonte: madteckhead.com )

Grazie in anticipo per tutte le fantastiche idee! Amo così!

EDIT: Hough Transform Progress

D: Quale algoritmo raggrupperebbe le linee hough per trovare gli angoli? Seguendo i consigli delle risposte sono stato in grado di utilizzare la trasformazione di Hough, selezionare le linee e filtrarle. Il mio approccio attuale è piuttosto rozzo. Ho ipotizzato che la fattura sarà sempre meno di 15 gradi fuori allineamento con l'immagine. Finisco con risultati ragionevoli per le linee se questo è il caso (vedi sotto). Ma non sono del tutto sicuro di un algoritmo adatto per raggruppare le linee (o votare) da estrapolare per gli angoli. Le linee di Hough non sono continue. E nelle immagini rumorose possono esserci linee parallele, quindi sono richieste metriche di forma o distanza dall'origine della linea. Qualche idea?

caso 1 caso 2 caso 3 caso 4
(fonte: madteckhead.com )


1
Sì, l'ho fatto funzionare in circa il 95% dei casi. Da allora ho dovuto accantonare il codice a causa della mancanza di tempo. Pubblicherò un seguito a un certo punto, sentiti libero di commissionarmi se hai bisogno di aiuto urgente. Ci scusiamo per la mancanza di un buon seguito. Mi piacerebbe tornare a lavorare su questa funzione.
Nathan Keller

Nathan, potresti per favore pubblicare un seguito su come hai finito per farlo? Sono rimasto fermo nello stesso punto riconoscendo gli angoli / il contorno esterno del foglio di carta. Mi imbatto negli stessi identici problemi che hai fatto tu, quindi sarei molto interessato a una soluzione.
tim

6
Tutte le immagini in questo post ora sono 404.
ChrisF

Risposte:


28

Sono un amico di Martin che ci stava lavorando all'inizio di quest'anno. Questo è stato il mio primo progetto di codifica in assoluto, e un po 'si è concluso in un po' di fretta, quindi il codice ha bisogno di qualche errore ... decodifica ... Darò alcuni suggerimenti da quello che ti ho già visto fare, e poi ordina il mio codice nel mio giorno libero domani.

Primo suggerimento, OpenCVe pythonsono fantastici, passa da loro il prima possibile. : D

Invece di rimuovere piccoli oggetti e / o rumore, abbassa i vincoli astuti, in modo che accetti più spigoli, e poi trova il contorno chiuso più grande (in OpenCV uso findcontour()con alcuni semplici parametri, penso di aver usato CV_RETR_LIST). potrebbe ancora lottare quando è su un pezzo di carta bianco, ma stava sicuramente fornendo i migliori risultati.

Per la Houghline2()trasformazione, prova con il CV_HOUGH_STANDARDcontrario di CV_HOUGH_PROBABILISTIC, darà i valori rho e theta , definendo la linea in coordinate polari, quindi puoi raggruppare le linee entro una certa tolleranza a quelle.

Il mio raggruppamento ha funzionato come una tabella di ricerca, per ogni linea emessa dalla trasformazione hough darebbe una coppia rho e theta. Se questi valori erano entro, diciamo, il 5% di una coppia di valori nella tabella, venivano scartati, se erano fuori da quel 5%, veniva aggiunta una nuova voce alla tabella.

È quindi possibile eseguire l'analisi delle linee parallele o della distanza tra le linee molto più facilmente.

Spero che questo ti aiuti.


Ciao Daniel, grazie per essere stato coinvolto. Mi piace che ti avvicini. in realtà è la via con cui sto ottenendo buoni risultati al momento. C'era anche un esempio OpenCV che rilevava i rettangoli. Dovevo solo filtrare i risultati. come dicevi il bianco su bianco è difficile da rilevare con questo metodo. Ma era un approccio semplice e meno costoso rispetto al hough. In realtà ho lasciato l'approccio hough dal mio algoritmo e ho fatto una poli approssimazione, dai un'occhiata all'esempio dei quadrati in opencv. Mi piacerebbe vedere la tua implementazione del voto hough. Grazie in anticipo, Nathan
Nathan Keller

Stavo avendo problemi con questo approccio,
posterò

@AnshumanKumar ho davvero bisogno di aiuto con questa domanda, puoi aiutarmi, per favore? stackoverflow.com/questions/61216402/…
Carlos Diego,

19

Un gruppo di studenti della mia università ha recentemente dimostrato un'app per iPhone (e un'app OpenCV python) che avevano scritto per fare esattamente questo. Come ricordo, i passaggi erano qualcosa del genere:

  • Filtro mediano per rimuovere completamente il testo sulla carta (questo era testo scritto a mano su carta bianca con un'illuminazione abbastanza buona e potrebbe non funzionare con il testo stampato, ha funzionato molto bene). Il motivo era che rende il rilevamento degli angoli molto più semplice.
  • Trasformazione di Hough per le linee
  • Trova i picchi nello spazio dell'accumulatore Trasformata di Hough e traccia ogni linea sull'intera immagine.
  • Analizza le linee e rimuovi quelle che sono molto vicine tra loro e hanno un angolo simile (raggruppa le linee in una sola). Ciò è necessario perché la trasformazione di Hough non è perfetta poiché funziona in uno spazio campione discreto.
  • Trova coppie di linee che sono approssimativamente parallele e che intersecano altre coppie per vedere quali linee formano quad.

Sembrava funzionare abbastanza bene e sono stati in grado di scattare una foto di un pezzo di carta o di un libro, eseguire il rilevamento degli angoli e quindi mappare il documento nell'immagine su un piano piano quasi in tempo reale (c'era un'unica funzione OpenCV da eseguire la mappatura). Non c'era l'OCR quando l'ho visto funzionare.


Grazie per le grandi idee Martin. Ho seguito il tuo consiglio e ho implementato l'approccio della trasformazione di Hough. (Vedi i risultati sopra). Sto lottando per determinare un algoritmo robusto che estrapoli le linee per trovare le intersezioni. Non ci sono molte righe e pochi falsi positivi. Hai qualche consiglio su come posso unire e scartare al meglio le righe? Se i tuoi studenti sono interessati, incoraggiali a mettersi in contatto. Mi piacerebbe conoscere la loro esperienza nel far funzionare gli algoritmi su una piattaforma mobile. (Questo è il mio prossimo obiettivo). Molte grazie per le tue idee.
Nathan Keller

1
Sembra che l'HT per le linee abbia funzionato bene in tutte tranne che nella seconda immagine, ma stai definendo una tolleranza di soglia per i valori di inizio e fine nell'accumulatore? L'HT non definisce realmente le posizioni di inizio e fine, piuttosto i valori di m e c in y = mx + c. Vedi qui - nota che questo utilizza coordinate polari nell'accumulatore piuttosto che cartesiane. In questo modo puoi raggruppare le linee per ce poi per m per assottigliarle e immaginando le linee che si estendono su tutta l'immagine troverai intersezioni più utili.
Martin Foot

@MartinFoot ho davvero bisogno di aiuto con questa domanda, potete aiutarmi, per favore? stackoverflow.com/questions/61216402/…
Carlos Diego,

16

Ecco cosa mi è venuto in mente dopo un po 'di sperimentazione:

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

Non perfetto, ma almeno funziona per tutti i campioni:

1 2 3 4


4
Sto lavorando a un progetto simile. Corro sopra il codice e mi dà l'errore "Nessun modulo denominato cv". Ho installato la versione Open CV 2.4 e l'importazione di cv2 funziona perfettamente per me.
Navneet Singh

Saresti così gentile da aggiornare questo codice in modo che funzioni? pastebin.com/PMH5Y0M8 mi dà solo una pagina nera.
7erm

Hai qualche idea su come trasformare il seguente codice in 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, ho davvero bisogno di aiuto con questa domanda, puoi aiutarmi, per favore? stackoverflow.com/questions/61216402/…
Carlos Diego,

9

Invece di iniziare dal rilevamento dei bordi, è possibile utilizzare il rilevamento degli angoli.

Marvin Framework fornisce un'implementazione dell'algoritmo Moravec per questo scopo. Potresti trovare gli angoli delle carte come punto di partenza. Di seguito l'output dell'algoritmo di Moravec:

inserisci qui la descrizione dell'immagine


4

Inoltre puoi usare MSER (regioni estremali massime stabili) sul risultato dell'operatore Sobel per trovare le regioni stabili dell'immagine. Per ogni regione restituita da MSER è possibile applicare scafo convesso e poli approssimazione per ottenere alcuni come questo:

Ma questo tipo di rilevamento è utile per il rilevamento dal vivo più di una singola immagine che non sempre restituisce il miglior risultato.

risultato


1
Puoi condividere alcuni dettagli in più per questo forse un codice, grazie mille in anticipo
Monty

Ricevo un errore in cv2.CHAIN_APPROX_SIMPLE che dice troppi valori da decomprimere. Qualche idea? Sto usando un'immagine 1024 * 1024 come campione
Praveen

1
Grazie a tutti, appena capito il cambio di sintassi nella corrente ramo Opencv answers.opencv.org/question/40329/...
Praveen

MSER non è pensato per estrarre i BLOB? L'ho provato e rileva solo la maggior parte del testo
Anshuman Kumar

3

Dopo il rilevamento dei bordi, utilizzare la trasformazione di Hough. Quindi, metti quei punti in una SVM (macchina vettoriale di supporto) con le loro etichette, se gli esempi hanno linee morbide, SVM non avrà alcuna difficoltà a dividere le parti necessarie dell'esempio e altre parti. Il mio consiglio su SVM, metti un parametro come connettività e lunghezza. Cioè, se i punti sono collegati e lunghi, è probabile che siano una linea della ricevuta. Quindi, puoi eliminare tutti gli altri punti.


Ciao Ares, grazie per le tue idee! Ho implementato la trasformazione di Hough (vedi sopra). Non riesco a trovare un modo robusto per trovare gli angoli dati i falsi positivi e le linee non continue. Hai altre idee? È passato un po 'di tempo da quando ho esaminato le tecniche SVM. È questo un approccio supervisionato? Non ho dati di allenamento, ma potrei generarne alcuni. Sarei interessato ad esplorare l'approccio poiché sono ansioso di saperne di più su SVM. Puoi consigliare eventuali risorse. Cordiali saluti. Nathan
Nathan Keller

3

Qui hai il codice di @Vanuan usando 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);

Dov'è la definizione della variabile di linee? Deve essere std :: vector <cv :: Vec4i> righe;
Can Ürek

@ CanÜrek Hai ragione. std::vector<cv::Vec4i> lines;è dichiarato in un ambito globale nel mio progetto.
GBF_Gabriel

1
  1. Converti in spazio di laboratorio

  2. Usa kmeans segmento 2 cluster

  3. Quindi utilizzare contorni o hough su uno dei cluster (interno)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.