Acquisire testo da immagini con Python – Parte 4 – Esportiamo i dati in CSV

Nell’ultimo post di questa serie abbiamo scoperto come estrarre testo da specifiche parti di un’immagine. Ora, per proseguire nel progetto che ci siamo dati in origine (vedi qui) dobbiamo fare in modo che il dato letto sia correttamente salvato in un file CSV che poi importeremo in seguito. Quindi, ricapitolando: un csv che riporti nella prima colonna il la descrizione del campo come, nome, ruolo e skills e nella seconda colonna il valore di questi campi. Ad esempio nel caso seguente dovremmo partire dall’immagine:

Scheda calciatore

Per ottenere un csv che possa più o meno essere come il seguente:

Esportazione desiderata

Per falro anzitutto creo una funzione Python che mi data un’immagine e le dimensioni in cui è contenuta mi estragga il testo, così evito di dovre riscrivere tutte le volte il codice per estrarle, in più passo come parametro un booleano che, all’occorrenza, mi può anche far vedere l’immagine ritagliata prima di estrarre il testo.

# Function to extract data from a portion of screenshot
def extract_portion_for_csv(x,y,w,h,image,showimg):
    roi = image[y:y+h,x:x+w]
    gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
    if showimg:
        plt.imshow(gray_roi, cmap='gray')
        plt.show()
    export = pytesseract.image_to_string(gray_roi)
    return export

Ora che abbiamo una funzione che estrae il testo da specifiche parti dell’immagine si tratta solo di estrarre ognuna di esse definendo punto per punto dove recuperare il dato. Questa è la parte più noiosa in cui, testo per testo dobbiamo recuperare le coordinate. Per meglio organizzare le cose definisco 2 aree: quella in alto che contiene i dettagli anagrafici del profilo ed una sotto che contiene la parte di skills. Questo frammento estrae la parte anagrafica:

def extract_playerdetails_generic(img, showimg, matrix):
    #Date
    txt = extract_portion_for_csv(1225, 135, 148, 27, img, showimg)
    new_row = ["Date", txt]
    matrix.append(new_row)
    #Name 
    txt = extract_portion_for_csv(110, 140, 1240, 100, img, showimg)
    new_row = ["Name", txt]
    matrix.append(new_row)
    #Role
    txt = extract_portion_for_csv(350, 326, 900, 70, img, showimg)
    new_row = ["Role", txt]
    matrix.append(new_row)
    #Birth
    txt = extract_portion_for_csv(350, 400, 900, 70, img, showimg)
    new_row = ["Birth", txt]
    matrix.append(new_row)

E questo frammento invece estrae la parte di skills:

def extract_playerdetails_skills(img,showimg,matrix):
    txt = ""
    ycursor, i =474, 1
    #Left Side Skills
    while i<12:
        #Skill Desc
        #txt += extract_portion_for_csv(150, ycursor, 250, 30, img, showimg) + ","
        txta = extract_portion_for_csv(150, ycursor, 250, 30, img, showimg)
        #Skill Value
        #txt += extract_portion_for_csv(650, ycursor, 50, 30, img, showimg) + "\n"
        txtb = extract_portion_for_csv(650, ycursor, 50, 30, img, showimg)
        new_row = [txta, txtb]
        matrix.append(new_row)
        i=i+1
        ycursor += 34
    #Right Side Skills
    xoffset = 600
    ycursor, i =474, 1
    while i<9:
        #Skill Desc
        #txt += extract_portion_for_csv(150+xoffset, ycursor, 250, 30, img, showimg) + ","
        txta = extract_portion_for_csv(150+xoffset, ycursor, 250, 30, img, showimg)
        #Skill Value
        #txt += extract_portion_for_csv(650+xoffset, ycursor, 50, 30, img, showimg) + "\n"
        txtb = extract_portion_for_csv(650+xoffset, ycursor, 50, 30, img, showimg)
        new_row = [txta, txtb]
        matrix.append(new_row)
        i=i+1
        ycursor += 34
    #Current form
    #txt += "Current form," + extract_portion_for_csv(475+xoffset, 34*8 + 474, 250, 30, img, showimg) + "\n"
    txt = extract_portion_for_csv(475+xoffset, 34*8 + 474, 250, 30, img, showimg)
    new_row = ["Current form", txt]
    matrix.append(new_row)
    #Morale
    #txt += "Morale," + extract_portion_for_csv(475+xoffset, 34*9 + 474, 250, 30, img, showimg) + "\n"
    txt = extract_portion_for_csv(475+xoffset, 34*9 + 474, 250, 30, img, showimg)
    new_row = ["Morale", txt]
    matrix.append(new_row)
    #Physical Condition
    #¶txt += "Physical Condition," + extract_portion_for_csv(250+xoffset, 34*10 + 474, 400, 30, img, showimg) + "\n"
    txt = extract_portion_for_csv(250+xoffset, 34*10 + 474, 400, 30, img, showimg)
    new_row = ["Physical Condition", txt]
    matrix.append(new_row)

Ad una prima analisi possono sembrare complessi ma in realtà non lo sono: sono semplicemente abbastanza ripetitivi. Per ognuno dei testi che dobbiamo estrarre facciamo in modo di specificare le coordinate e mettiamo tutti i testi all’interno di una matrice così da utilizzarla poi nella scrittura del file csv tramite questo frammento:

def write_to_csv (csv_folder, csv_filename, matrixtowrite):
    with open(os.path.join(csv_folder, csv_filename), "w", newline="") as csvfile:
        csv_writer = csv.writer(csvfile)
        # Write data to CSV file
        csv_writer.writerows(matrixtowrite)

Come si può notare riempiamo una variabile matrix, una matrice che poi utilizzero per scrivere il file stesso. Il file generato contiene l’estrazione completa, confrontandola con la desiderata notiamo delle differenze:

Desiderata a sinistra, risultato dell’estrazione a destra

Come si può notare il risultato non è male ma lontano dall’essere perfetto: ci sono molti caratteri speciali che sporcano la lettura come “_”, “*”, “=”. Probabilmente le parti meno riconoscibili sono i trattini “-” e i numeri che hanno un colore del font minore meno pronunciato come quello in Marking 3. Il sistema sembra fare fatica a lavorare dove c’è un contrasto basso. Se guardiamo infatti la figura possiamo notare che tutte le parti che non sono state riconosciute sembrano essere meno evidenti delle altre.

Immagine originale con le parti meno chiare evidenziate in rosso

E’ chiaro che un file così non può essere importato per essere acquisito. L’ideale è capire come migliorare la qualità dell’estrazione, specie per quei caratteri che hanno un basso contrasto. Proviamo a fare una domanda specifica a ChatGPT:

Come posso aumentare l’accuratezza quando il contrasto è basso?

La risposta purtroppo non preannuncia nulla di buono: sembra non sia così semplice . Nel prossimo post analizzeremo le proposte di ChatGPT e proveremo a capire se è possibile migliorare il risultato.

Acquisire testo da immagini con Python – Parte 3 – Lavoriamo un po’ più di fino

Nello scorso post abbiamo visto come estrarre i testi da una schermata. Purtroppo nel caso analizzato abbiamo molti dati dispersi in vari punti e questo ci ha fornito un estratto difficilmente elaborabile.

Schermata Giocatore

Ciò che gioca a nostro favore in realtà è che il formato del dato è quello per tutte le schermate, ciò che cambierà sarà certamente il nome del calciatore, le info anagrafiche ed i valori delle skills. Fortunatamente la struttura ed il posizionamento sono praticamente identici. In soldoni: sappiamo precisamente dove andare a reperire le informazioni, quindi se ci fosse un modo per restringere il campo potremmo estrarre i dati un po’ alla volta selezionando solo ciò che ci serve.

In rosso alcuni esempi di dati da estrarre

E’ chiaro che sarebbe ideale trovare un modo per estrarre solo le aeree in rosso. Ci sarà? Chiediamo a ChatGPT 🙂

Chiedo a ChatGPT

Notare che ho pure scritto wite invece di write, non volontariamente, è solo un typo, ma vediamo come ci risponde.

Codice Python

Bene, ChatGPT ci espone tutto il codice da utilizzare: viene definita una ROI (region of interest) dell’immagine, viene convertita in scala di grigio e poi infine si estrae il testo così come facevamo anche nel caso precedente. Ok proviamo con un esempio: proviamo ad estrarre il nome del calciatore:

# Function to extract data from screenshots and rename files
def extract_data_and_rename(screenshot_folder):
    for file in os.listdir(screenshot_folder):
        print(file)
        if file.endswith(".png"):
            img_path = os.path.join(screenshot_folder, file)
            img = cv2.imread(img_path)
            x, y, w, h = 110, 140, 1240, 100
            #Define ROI
            roi = img[y:y+h,x:x+w]
            gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
            plt.imshow(gray_roi, cmap='gray')
            plt.show()
            # Implement OCR to extract data from the image
            extracted_data = pytesseract.image_to_string(gray_roi)
            # Extract relevant information from the data
            print(extracted_data)

Come si può notare ho riprodotto fedelmente quanto indicato da ChatGPT, operando qualche accorgimento:

  • Itero tutti files presenti nella cartella
  • per ognuno di essi fisso x,y,w,h in modo da centrare esattamente il quadro dove sta il nome
  • Estraggo il frammento d’immagine con una scala di grigio
  • utilizzo una libreria per farmi vedere il frammento e capire se è realmente corretto
  • infine faccio scrivere a schermo il testo

Il risultato è questo:

Risultato acqusizione

Questo è indubbiamente il risultato che mi serve: qui il testo è stato estratto correttamente e può ora essere utilizzato per qualcosa di più strutturato. Purtroppo la parte più ostica è quella di estrarre delle coordinate corrette in cui trovare il testo che ci serve. Andando per tentativi diventa quasi impossibile, quindi googlando ho scoperto che è possibile attraverso la libreria pyplot visualizzare l’immagine selezionata, di conseguenza andando per tentativi possiamo definire pezzo per pezzo le aeree in cui operare l’estrazione effettiva. A questo punto non ci resta che definire pezzo per pezzo dove prelevare i dati che ci servono, estrarli ed in qualche modo convogliarli in un file di ouput che possa essere utilizzabile per aggregare i dati dei vari giocatori.

Acquisire testo da immagini con Python – Parte 2 – Tesseract e il primo test di acquisizione

Nel precedente post al fine di provare ad acquisire e aggregare dati provenienti da schermate di un video gioco anni novanta abbiamo chiesto a ChatGPT di darci una mano nel compito essendo neofiti totali. La scorsa volta ci siamo fermati all’installazione di Python, ora passiamo a Tesseract.

Tesseract

Se proviamo a far girare il codice che ci ha fornito ChatGPT scopriamo che manca un prerequisito che è Tesseract. Ma cos’è esattamente?

Ecco la risposta sempre di ChatGPT

La risposta di ChatGPT

Bene, Tesseract è un OCR ed è utilizzato per estrarre testi dalle immagini: quello che mi serve. E’ Open-Source, supporta il riconoscimento in varie lingue ed è molto accurato se correttamente “allenato” (interessante). Può essere facilmente utilizzato attraverso API e nello sopecifico per sessere tuilizzato in Python necessita della libra “pytesseract”. Direi che è esattamente quello che mi serve. Per installare Tesseract basta un semplice comando con brew [1]

brew install tesseract

Inifine come suggerito da ChatGPT installo anche il wrapper per Python.

pip install pytesseract

A questo punto possiamo cominciare a lavorare sul codice Python per capire come adattarlo e ricondurlo a quelle che sono le mie necessità.

Primo ciclo di codice

Apriamo Visual Studio Code e creiamo un file vuoto Test.py e copiamo il codice suggerito nel post precedente quindi lanciamo l’esecuzione dal menu Run > Start Debugging. Questo è il risultato:

Primo lancio

L’esecuzione va in errore e la modalità debug di Visual Studio Code ci aiuta evidenziando dove sta il problema: certo devo fornire un path corretto dove prelevare gli screenshots. Al netto di questo errore comunque il setup sembra corretto possiamo quindi dedicarci alla parte più divertene: vale a dire scrivere il codice. Anzitutto faccio un po’ di pulizia: rimuovo la parte che fa la categorizzazione perchè al momento non so ancora come poterla implementare e lo stesso faccio con la funzione che scrive il csv. Infine fornisco il path dove ho già preparato alcuni screenshots da cui estrarre il testo che mi serve. Il main dopo questo restyling è molto minimale:

# Main function to execute the workflow
def main():
    # Path to the folder containing screenshots
    screenshot_folder = ""/Users/xxxx/ScreenCapture""

    # Extract data from screenshots and rename files
    extract_data_and_rename(screenshot_folder)

Infine mi dedico alla funzione principale extract_data_and_rename che chiaramente itera i files nella cartella e tramite pytesseract estrae il testo dell’immagine. Al momento però mi limito a fare un print del dato estratto:

# Function to extract data from screenshots and rename files
def extract_data_and_rename(screenshot_folder):
    for file in os.listdir(screenshot_folder):
        if file.endswith(".png"):
            img_path = os.path.join(screenshot_folder, file)
            img = cv2.imread(img_path)
            # Implement OCR to extract data from the image
            extracted_data = pytesseract.image_to_string(img)
            # Extract relevant information from the data
            print(extracted_data)

Ok ci siamo se lo lanciamo teoricamente dovrebbe iterare tutti i files png presenti nella cartella e scrivere il contenuto estratto da ognuno di essi a schermo. Per questa prima prova uso una sola immagine:

Immagine sorgente

e questo è ciò che il sistema è stato in grado di interpretare:

Testi estratta dallo screenshot

Beh, diciamo che come primo test è già qualcosa però è evidente che alcuni testi sono stati correttamente interpretati mentre altri vanno rivisti. C’è parecchio da lavorare!

[1] https://pyimagesearch.com/2021/08/16/installing-tesseract-pytesseract-and-python-ocr-packages-on-your-system/

Acquisire testo da immagini con Python – Parte 1 – Cominciamo dalle basi

Qualche giorno fa in questo post [1] parlavo dei temi relativi all’AI e le sue differenze con la pura automazione. Come esempio pratico vorrei provare ad acquisire in automatico i dati provenienti da alcune immagini e convogliare queste informazioni in un excel. Dovete sapere che io sono un grande amante dei giochi degli anni ottanta e novanta. E voi direte che centra questo? Uno dei giochi sul quale ho perso letteralmente le notti quando ero poco più che un teenager era Championship Manager (Scudetto nell’edizione italiana). E’ stato il primo gioco a fornire una simulazione di ottimo livello del manager calcistico. Sono passati gli anni, sarà l’età avanzata, sarà del sano romanticismo, ma ancora questo gioco riesce a toccare corde cui i giochi strafighi di oggi difficilmente riescono a sfiorare. Se non lo conoscete vi invito a fare un giro su questo sito [2] dove potete addirittura scaricare il gioco (nella versione 97/98) e con dosbox [3] un simulatore di DOS potete persino giocarci. Ebbene una delle cose che ho sempre desiderato fare è avere uno scarico dei dati di giocatori e partite per poterli incrociare avere delle statistiche da cui possibilmente evincere trends ed informazioni utili a schierare le formazioni migliori.

Una schermata di esempio è quella delle skills di un singolo calciatore in cui ci sono info anagrafiche e capacità tecniche

Scheda calciatore

Nella fattispecie mi piacerebbe quindi avere in un unico excel le skills per colonna e i giocatori per riga magari indicando anche la data in cui questi dati sono stati raccolti con l’idea di poter monitorare un giocatore anche in giorni diversi nel caso le sue skills migliorino o peggiorino. Detto questo non ho la minima idea di come procedere, quindi chiedo a ChatGPT se mi può dare una mano:

Definizione del requisito

Chiedo scusa per l’inglese maccheronico, ma penso si capisca abbastanza bene. Ed ecco la risposta:

Prima parte della risposta

Bene, diciamo che già la lista delle attività pone sul tavolo alcuni strumenti come Python, OpenCV… In più mi porta cnhe un esempio di codice Python da utilizzare:

import os
import cv2
import pytesseract
import csv
from datetime import datetime

# Function to categorize screenshots based on format
def categorize_screenshots(screenshot_folder):
    # Implement your logic to categorize screenshots
    pass

# Function to extract data from screenshots and rename files
def extract_data_and_rename(screenshot_folder):
    for file in os.listdir(screenshot_folder):
        if file.endswith(".png"):
            img_path = os.path.join(screenshot_folder, file)
            img = cv2.imread(img_path)
            # Implement OCR to extract data from the image
            extracted_data = pytesseract.image_to_string(img)
            # Extract relevant information from the data
            # Rename the file using extracted data and current date
            new_filename = f"{extracted_data}_{datetime.now().strftime('%Y-%m-%d')}.png"
            os.rename(img_path, os.path.join(screenshot_folder, new_filename))

# Function to fill CSV files with data from screenshots
def fill_csv_files(screenshot_folder, csv_folder):
    for file in os.listdir(screenshot_folder):
        if file.endswith(".png"):
            img_path = os.path.join(screenshot_folder, file)
            img = cv2.imread(img_path)
            extracted_data = pytesseract.image_to_string(img)
            # Write extracted data to CSV file
            csv_filename = file.replace(".png", ".csv")
            with open(os.path.join(csv_folder, csv_filename), "w", newline="") as csvfile:
                csv_writer = csv.writer(csvfile)
                # Write data to CSV file
                csv_writer.writerow(extracted_data.split("\n"))

# Main function to execute the workflow
def main():
    # Path to the folder containing screenshots
    screenshot_folder = "path/to/screenshots"
    # Path to the folder where CSV files will be stored
    csv_folder = "path/to/csv_files"

    # Categorize screenshots
    categorize_screenshots(screenshot_folder)

    # Extract data from screenshots and rename files
    extract_data_and_rename(screenshot_folder)

    # Fill CSV files with data from screenshots
    fill_csv_files(screenshot_folder, csv_folder)

if __name__ == "__main__":
    main()

Fantastico: non avevo idea da dove cominicare e ChatGPT mi ha dato delle indicazioni preziose per cominiciare ad approfondire i temi.

Python

Non conosco questo linguaggio se non per sentito dire: non ho mai scritto mezza riga di codice, ma mi sembra di poter dire, dall’esempio che riporta ChatGPT, non sia poi così complesso. Leggendo su Wikipedia [4] tra l’altro scopro che deve il nome ai Monthy Python, già questa la dice lunga. Installarlo non è complesso basta seguire gli step indicati nelle varie guide online (tipo questa [5]). Anzitutto verifico che non sia già presente con questo comando a terminale:

python --version

Nel mio caso il risultato è quello che vedete (io ho installato la versione 3)

Bash

Nel caso non lo abbiate installato potete seguire la guida utilizzando brew

brew install python

Ora che Python è finalmente installato possiamo aprire Visual Studio code e utilizzando il codice che ChatGPT ci ha fornito creiamo un file .py di test da eseguire.

Nel prossimo post vedremo le librerie da utilizzare in Python così come ce le ha suggerite ChatGPT.

[1] https://www.beren.it/2024/02/08/automazione-o-intelligenza-artificale/

[2] https://www.fmscout.com/a-cm9798-v2-project.html

[3] https://www.dosbox.com

[4] https://it.wikipedia.org/wiki/Python

[5] https://www.jcchouinard.com/install-python-on-macos/