Skip to content

Ottenere la posizione di tutto il testo presente nell'immagine utilizzando opencv

Matías, parte di questa grande squadra, ci ha fatto il favore di creare questo scritto poiché ha una perfetta padronanza dell'argomento.

Soluzione:

Ecco un potenziale approccio che utilizza operazioni morfologiche per filtrare i contorni non testuali. L'idea è:

  1. Ottenere un'immagine binaria. Caricare l'immagine, in scala di grigi, quindi
    Soglia di Otsu

  2. Rimuovere le linee orizzontali e verticali. Creare kernel orizzontali e verticali utilizzando cv2.getStructuringElement quindi rimuovere le linee con cv2.drawContours

  3. Rimuove le linee diagonali, gli oggetti circolari e i contorni curvi. Filtrare utilizzando l'area del contorno cv2.contourArea
    e l'approssimazione del contorno cv2.approxPolyDP
    per isolare i contorni non di testo

  4. Estrazione di ROI di testo e OCR. Individuare i contorni e filtrare le ROI, quindi eseguire l'OCR con
    Pytesseract.


Rimozione delle linee orizzontali evidenziate in verde

enter image description here

Linee verticali rimosse

enter image description here

Rimossi contorni assortiti non di testo (linee diagonali, oggetti circolari e curve)

enter image description here

Rilevava le regioni di testo

enter image description here

import cv2
import numpy as np
import pytesseract

pytesseract.pytesseract.tesseract_cmd = r"C:Program FilesTesseract-OCRtesseract.exe"

# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
clean = thresh.copy()

# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,30))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

cnts = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    # Remove diagonal lines
    area = cv2.contourArea(c)
    if area < 100:
        cv2.drawContours(clean, [c], -1, 0, 3)
    # Remove circle objects
    elif area > 1000:
        cv2.drawContours(clean, [c], -1, 0, -1)
    # Remove curve stuff
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    x,y,w,h = cv2.boundingRect(c)
    if len(approx) == 4:
        cv2.rectangle(clean, (x, y), (x + w, y + h), 0, -1)

open_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
opening = cv2.morphologyEx(clean, cv2.MORPH_OPEN, open_kernel, iterations=2)
close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,2))
close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, close_kernel, iterations=4)
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    area = cv2.contourArea(c)
    if area > 500:
        ROI = image[y:y+h, x:x+w]
        ROI = cv2.GaussianBlur(ROI, (3,3), 0)
        data = pytesseract.image_to_string(ROI, lang='eng',config='--psm 6')
        if data.isalnum():
            cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
            print(data)

cv2.imwrite('image.png', image)
cv2.imwrite('clean.png', clean)
cv2.imwrite('close.png', close)
cv2.imwrite('opening.png', opening)
cv2.waitKey()

Bene, ecco un'altra possibile soluzione. So che lavorate con Python - io lavoro con il C++. Vi darò alcune idee e spero che, se lo desiderate, possiate implementare questa risposta.

L'idea principale è quella di non usare la pre-elaborazione (almeno non nella fase iniziale) e concentrarsi invece su ogni carattere di destinazione, ottenendo qualche proprietà e filtro ogni blob in base a queste proprietà.

Sto cercando di non usare la pre-elaborazione perché: 1) i filtri e le fasi morfologiche potrebbero degradare la qualità dei blob e 2) i vostri blob target sembrano presentare alcune caratteristiche che potremmo sfruttare, soprattutto: rapporto d'aspetto e area.

Guardate, i numeri e le lettere sembrano tutti più alti che larghi... inoltre, sembrano variare all'interno di un certo valore di area. Ad esempio, si desidera scartare gli oggetti "troppo larghi" oppure "troppo grande".

L'idea è di filtrare tutto ciò che non rientra nei valori precalcolati. Ho esaminato i caratteri (numeri e lettere) e ho ottenuto valori di area minima e massima e un rapporto minimo di aspetto (qui, il rapporto tra altezza e larghezza).

Lavoriamo sull'algoritmo. Si inizia leggendo l'immagine e ridimensionandola a metà delle dimensioni. L'immagine è troppo grande. Convertire in scala di grigi e ottenere un'immagine binaria tramite otsu, ecco lo pseudo-codice:

//Read input:
inputImage = imread( "diagram.png" );

//Resize Image;
resizeScale = 0.5;

inputResized = imresize( inputImage, resizeScale );

//Convert to grayscale;
inputGray = rgb2gray( inputResized );

//Get binary image via otsu:
binaryImage = imbinarize( inputGray, "Otsu" );

Bene. Lavoreremo con questa immagine. È necessario esaminare ogni blob bianco e applicare un "filtro proprietà". Sto usando componenti collegati con statistiche per eseguire un ciclo su ogni blob e ottenerne l'area e il rapporto d'aspetto; in C++ questo viene fatto come segue:

//Prepare the output matrices:
cv::Mat outputLabels, stats, centroids;
int connectivity = 8;

//Run the binary image through connected components:
int numberofComponents = cv::connectedComponentsWithStats( binaryImage, outputLabels, stats, centroids, connectivity );

//Prepare a vector of colors – color the filtered blobs in black
std::vector colors(numberofComponents+1);
colors[0] = cv::Vec3b( 0, 0, 0 ); // Element 0 is the background, which remains black.

//loop through the detected blobs:
for( int i = 1; i <= numberofComponents; i++ ) {

    //get area:
    auto blobArea = stats.at(i, cv::CC_STAT_AREA);

    //get height, width and compute aspect ratio:
    auto blobWidth = stats.at(i, cv::CC_STAT_WIDTH);
    auto blobHeight = stats.at(i, cv::CC_STAT_HEIGHT);
    float blobAspectRatio = (float)blobHeight/(float)blobWidth;

    //Filter your blobs…

};

Ora applicheremo il filtro delle proprietà. Si tratta solo di un confronto con le soglie precalcolate. Ho usato i seguenti valori:

Minimum Area: 40  Maximum Area:400
MinimumAspectRatio:  1

All'interno del vostro for confronta le proprietà attuali del blob con questi valori. Se i test sono positivi, si "dipinge" il blob di nero. Continuando all'interno del ciclo for :

    //Filter your blobs…

    //Test the current properties against the thresholds:
    bool areaTest =  (blobArea > maxArea)||(blobArea < minArea);
    bool aspectRatioTest = !(blobAspectRatio > minAspectRatio); //notice we are looking for TALL elements!

    //Paint the blob black:
    if( areaTest || aspectRatioTest ){
        //filtered blobs are colored in black:
        colors[i] = cv::Vec3b( 0, 0, 0 );
    }else{
        //unfiltered blobs are colored in white:
        colors[i] = cv::Vec3b( 255, 255, 255 );
    }

Dopo il ciclo, si costruisce l'immagine filtrata:

cv::Mat filteredMat = cv::Mat::zeros( binaryImage.size(), CV_8UC3 );
for( int y = 0; y < filteredMat.rows; y++ ){
    for( int x = 0; x < filteredMat.cols; x++ )
    {
        int label = outputLabels.at(y, x);
        filteredMat.at(y, x) = colors[label];
    }
}

E... questo è praticamente tutto. Sono stati filtrati tutti gli elementi che non sono simili a ciò che si sta cercando. Eseguendo l'algoritmo si ottiene questo risultato:

enter image description here

Ho inoltre trovato le Bounding Box dei blob per visualizzare meglio i risultati:

enter image description here

Come si vede, alcuni elementi non vengono individuati. È possibile affinare il "filtro delle proprietà" per identificare meglio i caratteri che si stanno cercando. Una soluzione più profonda, che implica un po' di apprendimento automatico, richiede la costruzione di un "vettore di caratteristiche ideali", l'estrazione di caratteristiche dai blob e il confronto di entrambi i vettori tramite una misura di somiglianza. È anche possibile applicare alcuni post-per migliorare i risultati...

Comunque, amico, il tuo problema non è banale né facilmente scalabile, e io ti sto solo dando delle idee. Speriamo che tu riesca a implementare la tua soluzione.

Un metodo è quello di utilizzare la finestra scorrevole (è costoso).

Determinare la dimensione dei caratteri nell'immagine (tutti i caratteri sono della stessa dimensione come si vede nell'immagine) e impostare la dimensione della finestra. Provare tesseract per il rilevamento (l'immagine di input richiede una pre-elaborazione). Se una finestra rileva caratteri consecutivi, memorizzare le coordinate della finestra. Unire le coordinate e ottenere la regione dei caratteri.

Commenti e valutazioni

Ricorda che ti diamo la possibilità di dire se hai risposto alla tua domanda appena in tempo.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.