Z doświadczenia: większość zespołów traktuje wyjście z OCR jak dane zaufane. Przychodzi JSON, confidence score powyżej 90%, no to jest OK i lecimy z downstream logic. Problem polega na tym, że confidence score informuje jak bardzo model „sam w siebie” wierzy – nie jak bardzo powinieneś mu wierzyć ty. OCR potrafi być niezwykle przekonany do „1,234,56”, podczas gdy na fakturze stoi „1.234,56”. Taka zmyłka rozwala później całą arytmetykę.
Konkret: system księgowy, faktury z kilkunastu krajów. Biznesowa walidacja: netto + VAT = brutto. Sens na papierze, ale w praktyce przechodziły przez to kwoty zupełnie z palca, bo OCR zamieniał separatory dziesiętne lokalnie. Niemiecka faktura z „1.234,56” zamieniała się po OCR na „1,234,56”. Anglosaksoński parser czyta to jako jeden euro dwadzieścia trzy centy. Ratio między netto, VAT i brutto się zgadza – tylko wartości są sto razy za małe.
To nie był pojedynczy przypadek. Systematyczny fuck-up, konkretny model drukarki dostawcy. Przez długi czas nikt nie zauważył, bo walidacja przechodziła, aż do czasu gdy ktoś popatrzył na wykres i stwierdził: „wow, faktury tego kontrahenta są podejrzanie tanie”.
Dlaczego typowa walidacja nie łapie błędów OCR
Cały problem polega na tym, że walidacja biznesowa sprawdza spójność (czyli: matematyka się zgadza), a nie poprawność danych źródłowych. Jeśli OCR spartoli separator dziesiętny i zrobi to konsekwentnie… matematycznie dalej jest OK. Netto 1,23 + VAT 0,28 = brutto 1,51 – tylko że powinno być 1234 + 280 = 1514.
Podobnie z symbolami. OCR zamieni „0” na „O” w numerze faktury – walidacja regexowa tego nie złapie, bo numery zawierają litery. Złączy kolumny tabeli („12 szt” -> „12szt”), pattern się zgadza, logika szczęśliwa. Każda warstwa zakłada, że wejście jest przynajmniej albo poprawne, albo ewidentnie błędne. OCR często robi coś pomiędzy – twory poprawne formalnie, które rozsypią ci system dopiero później.
Najbardziej upierdliwe są błędy character substitution. Tesseract notorycznie:
– „0” (zero) zamienia na „O” (wielkie o) wśród liter,
– „1” na „l” (małe L) w wąskich fontach,
– „8” na „B” w niskiej rozdzielczości,
– „5” na „S” w dziwnych fontach nagłówków.
Jak to wleci w numer faktury? Cicho przejdzie do produkcji, bo nikt nie weryfikuje. Możesz mieć checksum dla NIPu, ale większość numerów faktur czy katalogowych nie ma żadnej sumy kontrolnej.
"Confidence score zwykle kończy swoją pracę dokładnie tam, gdzie zaczyna się odpowiedzialność systemu. Produkcja ma wyjątkowy talent do znajdowania przypadków, których model był najbardziej pewny."
Anatomia błędów OCR w dokumentach finansowych
Finanse mają swój specyficzny zestaw problemów, których nie ma przy plaintext OCR.
Formatowanie liczb
Separatory dziesiętne to tylko początek. W Szwajcarii mamy apostrof jako separator tysięcy: 1″234.56. Większość open source OCR ignoruje apostrof, dostajesz „1234.56”. Część krajów – spacja jako separator: „1 234,56”. Tesseract potrafi wtedy rozbić to na dwa tokeny: „1” i „234,56”.
Kombinacje język/locale nie pomagają. Belgijska faktura: sekcja po francusku (1.234,56), sekcja po angielsku (1,234.56). OCR nie wie o tym, parser wchodzi w tryb loterii.
Tabele i fused wartości
Tabela w fakturze, wąski padding między kolumnami, szczególnie przy starszych PDF-ach? Model potrafi zmergować wszystko w jedno pole, np.
Ilość | Cena | Wartość
12 | 45,00 | 540,00
Po OCR: „1245,00540,00” albo „12 45,00 540,00” – ale żadnego jasnego mappingu do oryginalnych kolumn. Confidence wysoki, bo model widzi cyfry, ale nie ma mowy o granularności bounding boxów.
Niska jakość i artefakty
Faksy przesyłane mailem, skanowane-kopiowane już parę razy? Norma w niektórych branżach. Każdy cykl kompresji podbija poziom syfu, kontrast leci, JPEG dodaje przypadkowe kropki. OCR zaczyna „halucynować” – nie jak LLM, tylko zupełnie serio widzi kropki tam, gdzie była plamka. Confidence dalej powyżej 0.9, bo „wyszło coś, co wygląda jak znak”.
Watermarki to jackpot dla bugów. Półprzezroczyste „KOPIA” rozwala całe bounding boxy, czasem dostajesz totalne śmieci typu: „KBOrutPtoI:A 5 4K0O,0P0I AE”U”.
Dlaczego jeden OCR to za mało
Nie ma silnika OCR, który działa równie dobrze w każdej sytuacji. Tesseract jest spoko przy czystych, prostych skanach, Google Vision radzi sobie z rękopisem, AWS Textract dobrze rozumie dokumenty finansowe i layouty. Każdy ma swój kontekst, gdzie jest najlepszy, i swoje kobyły.
Problem: nie przewidzisz z góry, który akurat ten dokument przeczyta najlepiej. Czasem Tesseract bije Google o głowę, czasem na odwrót. Próbowałem automatycznego predyktora – takie rzeczy praktycznie nie działają przy szerokim miksie layoutów, jakości i typów fontu.
Multi-pass OCR z votingiem ratuje sytuację. Jeden dokument wrzucasz przez kilka modeli, porównujesz wyniki. Dla liczb i dat daje to bardzo dobre efekty:
Tesseract: „1.234,56”
Google: „1.234,56”
AWS: „1,234.56”
Voting -> „1.234,56” (większość)
Przy swobodnym tekście voting jest mniej udany, bo modele generują różne warianty tej samej frazy.
Koszt vs. accuracy
Multi-pass super, do momentu gdy policzysz kasę. Cloudowe OCR: 1.50 USD za 1000 dokumentów. Trzy silniki – trzy razy drożej. Tesseract za darmo, ale wymaga CPU i trochę grzebania.
Przy kilkunastu tysiącach faktur miesięcznie – koszt pomijalny. Milion dokumentów – już robi różnicę, tu opłaca się inwestować w lokalne modele lub własny serwer do Tesseracta.
Finalnie: nie każdy dokument to multi-pass. Pierwszy silnik daje dobre wyniki i walidacja przechodzi, dalej nie ma sensu dublować kosztów. Multi-pass warto robić, jeśli:
– confidence <80% w kluczowych polach,
– walidacja biznesowa nie przechodzi,
– nietypowy layout, niepasujący do szablonu.
To daje 60-70% cięcia kosztów bez dużego strzału w accuracy.
Locale-aware parsing - bez tego się nie obędzie
Najbardziej zdradliwe błędy przy liczbach – nie zamiana cyfr, tylko źle zinterpretowane separatory. OCR wyrzuca ci „1.234,56”. OK, tylko w którym locale? Niemcy: 1234,56, USA: 1.234,56. W skrócie: nie da się ślepo polegać na systemowym locale, bo dokumenty mogą być z dowolnego kraju, mogą być eksportowe z angielskim opisem, język faktury nie gwarantuje formatu liczb.
Próby wykrycia kraju po adresie albo kodzie VAT – fajnie, jeśli adres został dobrze rozpoznany… a to reguła nie jest.
Context detection jako workaround
Gdy parsujesz kwoty VAT, masz szansę odgadnąć locale po proporcji netto/VAT/brutto, bo większość krajów ma stałe stawki VAT. Zrób próbę różnych interpretacji, licz stawkę i porównaj z typowymi wartościami. Przykład heurystyki:
python
def detect_locale(netto_str, vat_str, brutto_str):
for locale_config in [DE, EN, FR, CH]:
netto = parse_number(netto_str, locale_config)
vat = parse_number(vat_str, locale_config)
brutto = parse_number(brutto_str, locale_config)
if abs(netto + vat - brutto) < 0.01:
vat_rate = (vat / netto) * 100
if vat_rate in COMMON_VAT_RATES:
return locale_config
return None
Nie złapie 100% przypadków – korekty faktur, waluty mieszane, negative values komplikują sprawę – ale lepiej mieć heurystykę niż ślepo ufać pojedynczemu parserowi.
Edge case: multi-currency
Faktura z kwotami w kilku walutach – parser widzi dwa formaty liczb i nie wie, czy to bug, czy różne locale. W realu: wymuś rozpoznanie waluty, najlepiej symbol/kod ISO w tym samym bounding boxie co liczba. Jeśli nie możesz powiązać kwoty i waluty z sensem – odsyłasz do ręcznej weryfikacji.
Character-level confidence - praktyczne zastosowania
Większość OCR daje tylko globalny score, a to mało warte, bo średnia maskuje pojedyncze katastrofy. Jeden znak z confidence 0.4, reszta 0.99 – średnia super, a data jest śmieciowa. Przykład:
2023/11/045
[0.99, 0.99, 0.99, 0.99, 0.98, 0.99, 0.99, 0.45, 0.99, 0.99, 0.99]
Character-level confidence przydaje się wtedy gdy człowiek poprawia wyniki w HITL – od razu wiadomo, gdzie szukać babola. Daje się wtedy też targetować reprocessing tylko tych fragmentów – wycinasz pole, robisz denoising/kontrast, puszczasz przez inny silnik.
To lepsze niż masowy reprocessing, bo taniej, krócej, a poprawia najważniejsze miejsca.
Pre-processing - teoria kontra praktyka
Każdy artykuł radzi: preprocessing obligatoryjny. W realu: tylko niektóre techniki realnie coś poprawiają; spora część to strata czasu, a niektóre potrafią „pogorszyć” wyniki.
Deskewing
Przekrzywione skany? Deskewing pomaga, ale tylko powyżej 2 stopni odchylenia. Przy prostych fakturach – zero efektu, a czasem psuje nietypowe layouty. Automatyzacja jest spoko, ale robisz deskewing tylko wtedy, gdy faktycznie obraz jest krzywy.
Denoising
Usuwanie szumu czasami pomaga – zwłaszcza przy faktycznie zjechanych skanach. Ale agresywne denoising potrafi wymazać kropki i przecinki, które przy liczbach są kluczowe. Najpierw quality detection, stopniowany filtering – nie jedna obrabiana wszystkim na raz.
Kontrast
Zwiększenie kontrastu ratuje wyblakłe skany, plus kilka procent do accuracy. Ale przesada zmienia artefakty JPEG w fałszywe separatory. Każda operacja musi być przemyślana.
Rzeczywiste benchmarki
W teorii – preprocessing daje 10-20% poprawy. W praktyce na realnych datach – 1-3%, tylko na tragicznych skanach wychodzi dwucyfrowy upgrade. Jeden klient chciał full preprocessing, test A/B pokazał 1.2% lepiej i 40% wolniej. Skończyliśmy na selective processing dla słabych jakościowo.
"OCR jest zaskakująco skuteczny w przekonywaniu całego pipeline'u, że wszystko jest w porządku. Zwykle dopiero księgowość przypomina, że poprawny JSON i poprawne dane to jednak dwie różne rzeczy."
Post-OCR validation - ostatnia linia obrony
Na końcu dnia walczysz z efektami, które już trafiły do twojego pipeline.
Business validation
Typowe: netto + VAT = brutto. W idealnych warunkach działa, ale kiedy tylko jedna wartość jest odczytana źle, nie masz pojęcia która. Lepiej zrobić walidację po proporcjach VAT – jeśli VAT stanowi 40% brutto, to masz bug nawet jak sumy „zgadzają się” arytmetycznie.
python
def validate_invoice(netto, vat, brutto, vat_rate):
if abs(netto + vat - brutto) > 0.01:
return "FAILED: math doesn"t add up"
expected_vat_ratio = vat_rate / (100 + vat_rate)
actual_vat_ratio = vat / brutto
if abs(expected_vat_ratio - actual_vat_ratio) > 0.01:
return "FAILED: VAT rate inconsistent"
return "OK"
Format + checksum
NIP, IBAN – sprawdzaj checksums. Jeśli suma się nie zgadza, masz pewność że OCR coś pomylił. Szkoda, że większość numerów faktur nie ma żadnej sumy – zostaje tylko podejrzliwy regex.
Cross-field consistency
Termin zapłaty przed datą wystawienia? Dla systemu to valid, dla człowieka – bullshit. Porównuj, flaguj, odrzucaj.
Nazwa kontra NIP – możesz walidować na zewnętrznym API (np. GUS). Spójność danych to silny sygnał.
Statistical outliers
Masz historyczne dane? Szukaj outlierów. Faktury na kwoty 10 razy wyższe niż zwykle – typowy rezultat źle odczytanego separatora. Stare daty? Pomyłka w roku lub miesiącu. Te przypadki tylko flagujesz do priorytetowej kontroli, nie możesz ich automatycznie wyrzucać.
Monitoring OCR w produkcji
W devie masz 95%+ accuracy, test cases przechodzą jak złoto. W produkcji wychodzi na jaw, że jeden vendor ciągle robi strucla, bo używa drukarki z lat 90.
Nie wystarczy ogólny współczynnik accuracy. Musisz rozbijać metryki na dostawców, typy dokumentów, kanały dostarczania.
Wykrywanie wzorców błędów
Z czasem widzisz, które dokumenty i które pola najbardziej się wykrzaczają. Masz dostawcę, gdzie zawsze pole X jest źle. Dokument przychodzi mailem – zawsze gorszej jakości niż upload API. Warto te patterns śledzić i odpowiednio dostosowywać processing: dedykowany template, szczególne reguły pre-processingu, direct HITL dla „problematycznych” źródeł.
Feedback loop z HITL
Każda poprawka w HITL to darmowy set labelowanych danych. Zapisuj „co” człowiek zmienił i „dlaczego” – wtedy masz materiał do fine-tune modelu, do correction layery, albo do optymalizacji preprocessing dla problematycznych fragmentów. W jednym projekcie 6 miesięcy feedbacku pozwoliło zrobić prosty model XGBoost łapiący substytucje znaku (np. O->0), eliminując sporą część korekt HITL.
To działa, ale tylko jeśli realnie zbierasz diffy poprawek – a większość zespołów tego nie robi, klikają save i po temacie.
"Monitoring zaczyna się tam, gdzie kończą się benchmarki. Model może mieć 95% accuracy, ale produkcja i tak znajdzie tego jednego dostawcę, który przez najbliższy rok będzie dostarczał pozostałe 5%."
Kiedy multi-pass, kiedy cloud
Cały pipeline z multi-pass, votingiem i preprocessing ma sens tylko jeśli masz na to powód. Przetwarzasz 1000 faktur o stałym układzie? Lepiej napisz template pod każdego dostawcę, będzie szybciej i dokładniej niż cudować z votingiem.
Multi-pass opłaca się:
– Kiedy masz duży i nieprzewidywalny mix źródeł
– Kiedy nie możesz wypuścić danych do chmury z przyczyn compliance
– Przy dużej skali (wolumeny >100k/miesiąc)
– Kiedy compliance/accuracy goni cię do 95%+ i single model nie daje rady
Chmura (Google Vision, AWS Textract) zawsze będzie lepsza jakościowo niż lokalny Tesseract – o ile możesz je użyć. W jednym projekcie compliance wymusiło 100% lokalne OCR – accuracy skończyło na 87% zero-touch, versus 94% dla cloud API. 7% różnicy to kilkaset dokumentów dziennie do ręcznego przeglądu. Klient się zgodził, bo taki był wymóg, ale przynajmniej świadomie znalazł ten trade-off.
W realu, fakt że model daje ci „confidence >90%” nie znaczy, że dokument jest OK. Realna accuracy zawsze będzie niższa niż confidence. Rozsądna produkcja buduje pełen monitoring: pre/post processing, voting, walidacje, feedback loop z HITL i segmentację po typie dokumentu. Inaczej zawsze wyjdzie ci zonk, i to na najbardziej czułym biznesowo case.
Podsumowanie
Na końcu dnia największym problemem OCR rzadko okazuje się sam model. Znacznie częściej zawodzi założenie, że skoro parser zwrócił poprawny JSON, to problem został rozwiązany. Produkcja bardzo szybko przypomina, że między „da się odczytać” a „można temu zaufać” istnieje przepaść, której nie zasypie ani wysoki confidence score, ani kilka prostych reguł biznesowych.
Dlatego dojrzałe pipeline”y OCR nie opierają się na jednym silniku ani jednej walidacji. Składają się z wielu warstw: preprocessingu, locale-aware parsingu, heurystyk biznesowych, cross-field validation, monitoringu jakości, feedback loopów z HITL i mechanizmów pozwalających zakwestionować wynik modelu wtedy, gdy wygląda podejrzanie. To właśnie te elementy odpowiadają za stabilność systemu po miesiącach działania, a nie kolejny punkt procentowy accuracy w benchmarku.
Z perspektywy utrzymania produkcji OCR przestaje być problemem computer vision, a staje się problemem zarządzania zaufaniem do danych. Im szybciej system nauczy się mówić „nie wiem”, zamiast z pełnym przekonaniem zwracać błędny wynik, tym mniej czasu zespół spędzi na analizowaniu przypadków, które teoretycznie nigdy nie powinny były przejść przez walidację.
Po kilku miesiącach zwykle okazuje się, że najbardziej wartościową metryką nie jest accuracy modelu, ale liczba błędów, które udało się zatrzymać zanim trafiły do systemów finansowych, ERP czy księgowości. Benchmark kończy się wraz z wdrożeniem. Produkcja dopiero wtedy zaczyna wystawiać prawdziwe oceny.
Zobacz powiązane case studies i analizy
Procesy, architektura i workflow powiązane z tematami poruszanymi w tym materiale – od integracji i realtime systems po automatyzacje operacyjne.