In molte PMI italiane i documenti «vivi» non sono ancora tutti digitali. Arrivano fatture di fornitori in PDF generato, DDT stampati e riscannati, bolle di consegna, moduli compilati a mano, estratti conto, pratiche assicurative. Il gestionale o l’ERP, però, vuole campi: numero documento, data, partita IVA, imponibile, aliquota, codice articolo, quantità.

L’OCR da solo non risolve il problema. Trasforma un’immagine in testo; quello che serve al back-office è estrarre campi affidabili, con una confidenza misurabile e un punto dove una persona può correggere i casi dubbi. In questo articolo raccontiamo come impostiamo una pipeline realistica: preprocessing, riconoscimento, estrazione, validazione e integrazione — con un occhio a privacy, layout italiani e limiti di strumenti open source come Tesseract.

Il problema non è «leggere», è «strutturare»

Una fattura PDF generata dal gestionale del fornitore spesso contiene già testo selezionabile: lì l’OCR non serve, basta un estrattore di testo (o un parser basato su layout). Una scansione a 150 dpi con pieghe, timbri e tabelle storti è un altro mondo.

In ambito PMI italiano i documenti tipici sono:

  • fatture e note di credito (anche dopo l’obbligo di fattura elettronica restano molte copie PDF «di cortesia» e documenti esteri);
  • DDT e bolle con tabelle di righe;
  • moduli e pratiche a campi fissi (key-value);
  • documenti misti: logo, intestazione, tabella, pie’ di pagina con IBAN e note.

L’obiettivo operativo non è «avere il testo completo», ma ottenere un JSON (o un Excel) con i campi che alimentano contabilità, magazzino o archiviazione. È il percorso che descriviamo anche sulla pagina dedicata all’OCR documenti per aziende: upload → estrazione e normalizzazione → validazione con log → output verso gestionale.

Architettura a due passi (e un terzo umano)

Una pipeline che regge in produzione separa tre responsabilità.

1. OCR → testo (e, se possibile, layout).

Il motore restituisce stringhe, spesso con bounding box (coordinate della parola o della riga). Qui contano qualità della scansione, lingua (ita + eventuali eng) e preprocessing.

2. Estrazione campi → struttura.

Dal testo (o dal layout) si ricavano i campi di business: con regole (regex, ancore tipo «Partita IVA», «Totale documento»), con modelli addestrati su layout ripetitivi, o con un LLM a cui si passa il testo OCR e uno schema JSON. Le regole sono prevedibili e facili da auditare; i modelli e i LLM gestiscono meglio layout variabili, ma vanno tenuti sotto controllo con esempi e soglie.

3. Revisione umana sui casi sotto soglia.

Ogni campo (o ogni documento) dovrebbe avere una confidenza. Sotto una soglia — o se fallisce un controllo di validazione — il documento finisce in coda di revisione. Solo i casi dubbi costano tempo a una persona; il resto passa in automatico.

Senza il passo 3, «automazione al 100%» diventa spesso «errori silenziosi al 5%», che in contabilità pesano più del lavoro risparmiato.

Preprocessing: dove si guadagna (o si perde) accuratezza

Prima di chiamare Tesseract o un altro motore, vale la pena sistemare l’immagine:

  • Risoluzione: mirare ad almeno ~300 dpi sul testo piccolo; sotto i 200 dpi i caratteri sottili (partite IVA, decimali) degradano in fretta.
  • Deskew: raddrizzare la pagina riduce errori su colonne e tabelle.
  • Binarizzazione / contrasto: utile su scansioni grigie o con sfondo colorato; attenzione a non «mangiare» linee di tabella o timbri che a volte aiutano a capire il layout.
  • Ritaglio e pagina: elaborare una pagina alla volta; rimuovere bordi neri dello scanner.
  • PDF multi-pagina: convertire in immagini (es. con pdf2image / Poppler) e processare in sequenza, tenendo l’ordine.

Esempio minimale in Python (immagine già preparata o pagina da PDF):

from pathlib import Path import re import pytesseract from PIL import Image, ImageOps, ImageFilter def prepara(path: Path) -> Image.Image: img = Image.open(path).convert("L") # scala di grigi  img = ImageOps.autocontrast(img) # leggero sharpen; il deskew serio richiede OpenCV o simili  img = img.filter(ImageFilter.SHARPEN) return img def ocr_ita(img: Image.Image) -> str: # --psm 6: blocco di testo uniforme; da cambiare per moduli/tabelle  return pytesseract.image_to_string(img, lang="ita", config="--psm 6") PIVA_RE = re.compile(r"\b(?:IT)?\s*(\d{11})\b") DATA_RE = re.compile(r"\b(\d{1,2}[/.\-]\d{1,2}[/.\-]\d{2,4})\b") def estrai_candidati(testo: str) -> dict: pive = PIVA_RE.findall(testo) date = DATA_RE.findall(testo) return { "partite_iva": pive, "date": date, "testo_len": len(testo), } 

Questo snip è un punto di partenza, non un motore di fatture. In produzione aggiungerete: confidenza per parola (image_to_data), normalizzazione date italiane, checksum della partita IVA, matching anagrafica fornitori, gestione multi-pagina.

Limiti di Tesseract su layout e tabelle

Tesseract resta un ottimo motore open source per testo continuo e per molti documenti semplici. Sui layout aziendali italiani mostra limiti noti:

  • tabelle: le celle vicine si «attaccano»; quantità e prezzi finiscono sulla riga sbagliata;
  • multi-colonna e intestazioni a blocchi: l’ordine di lettura può mescolare colonne;
  • caratteri confusi: O/0, l/1, S/5, soprattutto a bassa risoluzione;
  • scrittura a mano: fuori scope, salvo casi molto controllati o motori diversi.

Quando il documento è una tabella densa (DDT con molte righe), spesso conviene un approccio ibrido: OCR a livello di cella dopo aver rilevato la griglia (OpenCV / librerie di table detection), oppure un servizio di document intelligence che restituisce già strutture tabella. La scelta dipende da volume, varietà dei layout e vincoli di privacy.

Cloud API o motore locale?

Non esiste una risposta unica. Criteri che usiamo in pratica:

Fattore Motore locale (es. Tesseract + regole) API cloud di document AI
Privacy / GDPR I file restano in azienda o nel vostro server Serve DPA, valutazione trasferimento dati, eventuale regione UE
Costo a volume Prevedibile (CPU/GPU vostre) Cresce con pagine/mese
Layout variabili Più lavoro di regole/modelli Spesso più robusto out-of-the-box
Latenza / offline Controllabile, anche senza Internet Dipende dalla rete e dal provider
Audit Codice e log sotto il vostro controllo Va definito cosa logga il fornitore

Per documenti con dati personali o fiscali sensibili, molte aziende preferiscono OCR on-premise o su cloud privato europeo proprio per tenere sotto controllo dove finiscono le scansioni. Il GDPR non vieta il cloud: impone base giuridica, misure di sicurezza, accordi con il responsabile del trattamento e, se serve, valutazione d’impatto. La domanda utile in kickoff è: questi file possono lasciare il perimetro aziendale?

Validazione: la differenza tra testo e dato utilizzabile

Dopo l’estrazione, i controlli che evitano disastri contabili sono banali ma indispensabili:

  • partita IVA: 11 cifre e checksum (algoritmo ufficiale);
  • codice fiscale / identificativi esteri quando previsti;
  • date in range plausibile e coerenti tra documento e registrazione;
  • totali: somma righe ≈ imponibile; IVA ≈ imponibile × aliquota (con tolleranza di arrotondamento);
  • anagrafiche: matching fuzzy con anagrafica fornitori già in gestionale;
  • obbligatorietà: se manca un campo critico → revisione, non import silenzioso.
def checksum_piva(piva: str) -> bool: """Checksum partita IVA italiana (11 cifre).""" if not piva.isdigit() or len(piva) != 11: return False s = 0 for i, ch in enumerate(piva[:10]): n = int(ch) if i % 2: # posizioni pari (1-based): raddoppia  n *= 2 if n > 9: n -= 9 s += n return (10 - s % 10) % 10 == int(piva[10]) def valida_fattura(campi: dict, soglia: float = 0.85) -> list[str]: errori = [] if campi.get("confidence", 0) < soglia: errori.append("confidenza_bassa") piva = campi.get("piva_fornitore", "") if not checksum_piva(piva): errori.append("piva_non_valida") try: if abs(float(campi["imponibile"]) * float(campi["aliquota"]) / 100 - float(campi["iva"])) > 0.05: errori.append("iva_incoerente") except (KeyError, ValueError, TypeError): errori.append("importi_mancanti") return errori 

(Il checksum sopra è quello usato per le partite IVA italiane; in produzione usate una funzione collaudata e unit test su casi noti.)

Se il documento è una fattura e l’obiettivo finale è l’invio allo SdI, l’OCR è solo il primo tratto: serve poi mappare i campi nello schema FatturaPA. Per PDF già strutturati o layout fissi spesso si lavora su un convertitore PDF→XML per fattura elettronica; per scansioni il percorso è OCR + estrazione + stessa validazione XSD/semantica vista nelle integrazioni SDI.

Checklist per valutare un progetto OCR in azienda

  1. Obiettivo: quali campi servono davvero a quale processo (contabilità, magazzino, archivio)?
  2. Volumi e varietà: quante pagine/mese? Quanti layout distinti? Quanto cambiano nel tempo?
  3. Origine file: PDF nativi, scan, foto da smartphone, mix?
  4. Qualità campione: chiedete 20–50 documenti reali (anonimizzati se serve) e misurate l’accuratezza campo per campo, non solo «l’OCR ha letto qualcosa».
  5. Privacy: i file possono uscire? Serve log degli accessi, cifratura, retention?
  6. Soglie e coda umana: chi corregge? Con quale interfaccia? Entro quanto tempo?
  7. Integrazione: JSON/CSV verso ERP, API, o data entry assistito se i volumi sono bassi e irregolari.
  8. Manutenzione: chi aggiorna le regole quando un fornitore cambia modello di fattura?
  9. Metriche: precision/recall sui campi critici, % documenti a revisione, tempo medio di correzione.
  10. Exit strategy: formati di export e proprietà dei modelli/regole, così non restate bloccati su un vendor.

Cosa abbiamo imparato

  • OCR ≠ dati strutturati: separate riconoscimento ed estrazione, e misurate la confidenza.
  • Il preprocessing paga più di un cambio di motore su scan mediocri.
  • Tesseract basta per molti testi semplici; tabelle e layout complessi chiedono più disegno (o un motore diverso).
  • Privacy e perimetro vanno decisi in kickoff, non dopo il primo batch in cloud.
  • Validazione e revisione umana sono parte del prodotto, non un optional.
  • Per le fatture, collegare l’estrazione allo schema FatturaPA evita di rifare il lavoro a valle.

Se state impostando una pipeline simile e volete confrontare scelte su tabelle o su soglie di confidenza, scrivetelo nei commenti: sono dettagli su cui si impara soprattutto dai casi reali.

Scritto dal team di S-Data, software house che sviluppa gestionali e integrazioni su misura.