EasyOCR to gotowa do użycia biblioteka OCR od JaidedAI dla Pythona: pip install easyocr, dwie linijki kodu i tekst „wtopiony” w obraz wraca jako stringi. Projekt ma licencję Apache-2.0, obsługuje ponad 80 języków i działa jako pipeline dwóch modeli PyTorch — detektora CRAFT, który zaznacza wszystko, co uzna za tekst, a potem rozpoznawacza odczytującego znaki wewnątrz każdego pola. Wytrenowane wagi pobierają się automatycznie przy pierwszym uruchomieniu. W praktyce to hostowana lokalnie alternatywa dla Tesseract i PaddleOCR, a nie płatne OCR API rozliczane per strona.
Podstawowe API jest krótkie: Reader(['en']), a potem readtext(). Wdrożeniowo to już większy ciężar — około 2 GB samego PyTorch w tym środowisku i mniej więcej gigabajt szczytowego zużycia pamięci resident w świeżym procesie. Wygenerowałem 36 angielskich plików PNG, uruchomiłem easyocr 1.7.2 na CPU i oceniłem odczyty znak po znaku względem wygenerowanego ground truth. Największe spadki CER wynikały z rozmiaru i orientacji; dashboard ujawnił też problemy z wykrywaniem krótkich tokenów i błędy w rozpoznawaniu znaku dolara.
Najbardziej wyraźna porażka dotyczy parametru, który wszyscy polecają. rotation_info ma na issue trackerze opinię tego rozwiązania dla obróconych obrazów, więc dostał trzy ortogonalnie obrócone kopie tego samego zdania. Przy 270° zrobił dokładnie to, co obiecuje, obniżając character error rate z 0,83 do 0,10. Przy 180° zadziałał tylko częściowo: 0,85 do 0,67, a jedna fraza zniknęła. Przy 90° poszedł w przeciwną stronę, z 0,81 do 0,92, a rozpoznawacz zaczął zwracać tekst lustrzany. Ten sam parametr, ta sama lista kątów, trzy różne wyniki — więc samo włączenie tej opcji nie oznacza, że „rotacja jest obsługiwana”. Te same 36 fixture’ów pokazały też gwałtowny próg dla rozmiaru czcionki, systematyczne mylenie znaku dolara oraz jedną moją prognozę, która okazała się po prostu błędna.
Dwa modele w jednym płaszczu
EasyOCR nie jest jednym modelem. To pipeline złożony z dwóch etapów, a to, który z nich zawiódł, zmienia sposób debugowania.
Pierwszy etap to CRAFT, czyli detektor. Jego jedyne zadanie to lokalizacja — ustalenie, gdzie na obrazie w ogóle jest tekst, i zwrócenie ramek. Nie odczytuje żadnego znaku. Drugi etap to CRNN, czyli rozpoznawacz — ekstrakcja cech ResNet, potem BiLSTM, a następnie greedy decoding CTC — który czyta znaki wewnątrz każdej ramki. Oba działają na PyTorch. Na CPU rozpoznawacz domyślnie uruchamia się z dynamiczną kwantyzacją do int8, dlatego jest szybszy i lżejszy, niż sugerowałaby liczba parametrów.

Praktyczny wniosek: EasyOCR ma dwa zupełnie różne tryby awarii i wymagają one innych napraw. Jeśli detektor nigdy nie narysuje ramki, żadna regulacja rozpoznawacza nie pomoże — znaków po prostu nie było w pipeline. Jeśli ramka istnieje, ale string jest błędny, problem leży po stronie rozpoznawania i preprocessing może uratować sytuację. Prawie każdy wątek typu „EasyOCR pominął mój tekst”, jaki widziałem, miesza te dwa przypadki.
Stan projektu, sprawdzony 27 lipca 2026: 29 825 gwiazdek, 528 otwartych issue, Apache-2.0 oraz v1.7.2 z września 2024, a ostatni push do mastera był w grudniu 2025. Te daty nie dowodzą ani stabilności architektury, ani zdrowia utrzymania projektu. Przed wdrożeniem warto sprawdzić zgodność z własnym stosem Python/PyTorch, świeże odpowiedzi maintainerów i zgłoszenia dotyczące własnego typu danych.
OCR często kojarzy się z obchodzeniem CAPTCHA, ale to nie jest ten przypadek użycia. Nie testowano tu niczego pod kątem łamania zabezpieczeń antybotowych ani nie ma tu żadnej zachęty do takich działań. Zakres obejmuje odczyt tekstu z obrazów i zrzutów ekranu, do których masz prawo dostępu.
Co mierzyłem i czego te liczby nie obejmują
Zestaw testowy to 36 PNG-ów wygenerowanych przeze mnie: 35 obrazów z jedną linią tekstu, obejmujących siedem fontów, osiem rozmiarów, siedem poziomów kontrastu, siedem kątów pochylenia, trzy ortogonalne rotacje i trzy tła — plus jeden syntetyczny zrzut dashboardu z 19 osobno opisanymi elementami. Każdy obraz powstał z tego samego ciągu (Sphinx of black quartz, judge my vow. 1234567890 — 48 znaków, wielkie i małe litery, cyfry, interpunkcja) w tym samym przebiegu, który zapisywał etykietę ground truth, więc obraz i etykieta fizycznie nie mogły się rozjechać.
Dokładność mierzę jako character error rate (CER): odległość Levenshteina liczona w znakach podzielona przez długość ground truth. CER 0 oznacza idealny odczyt. CER 0,10 oznacza, że mniej więcej jeden znak na dziesięć jest błędny. Główny wynik podaję jako CER z rozróżnianiem wielkości liter, a obok wersję bez rozróżniania wielkości liter, bo okazuje się, że właśnie na kapitalizacji skupia się większość „błędu”.
Ważniejsze od samych liczb są granice testu:
- Tylko angielski. Rozpoznawacz
english_g2. EasyOCR reklamuje ponad 80 języków; ja testowałem jeden. To nic nie mówi o skryptach nielacinskich, czyli dokładnie tych, dla których istnieją publikowane porównania akademickie OCR. - Tylko syntetyka. Renderowany tekst, nie zdjęcia. Bez szumu z aparatu, bez artefaktów JPEG, bez problemów z oświetleniem i perspektywą.
- Bez pisma odręcznego. Sam projekt zaznacza, że handwriting nie jest jeszcze obsługiwany.
- Tylko CPU. macOS arm64,
gpu=False. Na maszynie było dostępne MPS, ale EasyOCR używa CPU wszędzie tam, gdzie nie ma CUDA. Wydajności GPU nie mierzyłem, więc nie pojawia się tu żaden wynik GPU. - Jedna maszyna, jedna wersja. easyocr 1.7.2, torch 2.13.0, Python 3.12.
Czyli: to są kontrolowane krzywe jednowymiarowe pokazujące dokładnie, gdzie spada jakość, na czystym, renderowanym tekście łacińskim. To nie jest wynik dla realnych danych i nie powinien go zastępować.
Dowody są możliwe do sprawdzenia, a nie zamknięte w notebooku. Generator fixture’ów i dokładne stringi są w tests/build_fixtures.py oraz tests/fixtures/ground_truth.json; rozpoznawanie, timing i zbieranie zasobów są w tests/run_easyocr.py; a tests/metrics.py liczy raportowane błędy z surowego wyniku. Zapisane rekordy rozpoznawania i metryki zbiorcze znajdują się w artifacts/raw/. Ponowne uruchomienie tego łańcucha pomaga sprawdzić tę maszynę i tę wersję. Nadal nie odpowiada jednak na pytanie, jak EasyOCR zachowa się na zdjęciach z telefonu, w innych językach, układach czy pipeline’ach preprocessingu, więc wdrożenie produkcyjne powinno dodać reprezentatywne dane wejściowe, zamiast traktować te fixture’y jak certyfikację.
Warto wskazać jeszcze jedną pułapkę harnessu, bo prawie dała mi błędny nagłówek. Pierwszy przebieg metryk raportował CER 0,375 na czarnym Arialu na białym tle, co byłoby fatalne dla najłatwiejszego możliwego wejścia. Problem nie leżał w EasyOCR. Detektor rozdzielił jedną widoczną linię na ramkę słów i ramkę cyfr, a mój naiwny sort po y, potem po x sklejał cyfry najpierw. Po naprawie na grupowanie świadome linii (łączenie ramek po pionowym nakładaniu, a potem czytanie od lewej do prawej) czyste fonty zeszły do około 0,04–0,10. Jeśli budujesz własną ewaluację OCR, ta pułapka też na ciebie czeka.
Setup: instalacja jest mała, zależności już nie

pip install easyocr
To wszystko, i uczciwie mówiąc, niewiele więcej jest tu małego. Sam pakiet jest prosty; ciężar to PyTorch, około 2 GB. Potem przy pierwszym wywołaniu readtext(), EasyOCR po cichu pobiera wagi do ~/.EasyOCR/model/ — łącznie 93,7 MiB, podzielone na 79,30 MiB dla detektora CRAFT (craft_mlt_25k.pth) i 14,44 MiB dla angielskiego rozpoznawacza (english_g2.pth).
W samouczkach nikt tego nie podkreśla, więc: pierwszy run wymaga dostępu do sieci i zatrzyma się na pobieraniu, a każde kontenerowe wdrożenie albo pakuje te wagi do obrazu, albo płaci za pobranie przy cold start. Po zbuforowaniu wszystko działa offline.
Zimna inicjalizacja Reader() — modele z dysku do RAM-u plus pass kwantyzacji do int8 — zajmowała 1,3–1,7 sekundy w kolejnych uruchomieniach. Potem:
import easyocr
reader = easyocr.Reader(['en'], gpu=False)
result = reader.readtext('screenshot.png')
I działa. Dwie linijki, nic do konfiguracji, brak polowania na checkpointy. „Easy” w nazwie jest tu zasłużone — tarcie wynika wyłącznie z ciężaru zależności, nie z API.
Czysty tekst: prawie idealne znaki, ale nie idealna kapitalizacja
Siedem systemowych fontów, czarny tekst na białym tle, 32 px, za każdym razem ten sam ciąg:
| Font | CER (z rozróżnianiem wielkości liter) | CER (bez rozróżniania wielkości liter) |
|---|---|---|
| Georgia | 0.0625 | 0.0000 |
| Times | 0.0417 | 0.0417 |
| Comic Sans | 0.0417 | 0.0208 |
| Arial | 0.0833 | 0.0208 |
| Verdana | 0.0833 | 0.0208 |
| Impact | 0.0833 | 0.0208 |
| Courier | 0.1042 | 0.0417 |
| Średnia | 0.0714 | 0.0238 |
Średni CER wynosi 0,071, a po normalizacji wielkości liter spada do 0,024. To jest sedno wyniku. EasyOCR nie gubi znaków na czystym tekście łacińskim — trafia w kształt, ale myli wielkość liter.
Konkretnie: słowo vow w wersji małej wraca jako VOW w sześciu z siedmiu fontów (Comic Sans zadowala się Vow). Impact dorzuca jeszcze of → Of. Drugi powtarzalny błąd to interpunkcja: kropka na końcu zdania wraca jako : albo _ w kilku fontach. Georgia daje idealny odczyt, jeśli przestaniesz przejmować się kapitalizacją.
To naprawdę użyteczna informacja. Jeśli downstream to fuzzy matching, wyszukiwanie po słowach kluczowych albo przekazywanie tekstu do modelu językowego, zamiana wielkości liter kosztuje niewiele. Jeśli downstream to dokładne porównanie stringa z kluczem w bazie, kosztuje wszystko. Znormalizuj case przed porównaniem, a połowa pozornego błędu EasyOCR znika.
To, że Courier wypada najgorzej (0,1042), też ma sens: fonty monospace przestrzennie rozpychają znaki bardziej niż zwykle, a to trudniejsze dla dekodera CTC, który uczył się typowych odstępów między literami.
Bariera rozmiaru jest dokładnie tam, gdzie mówią dokumenty

readtext() ma udokumentowany parametr min_size=10, który odrzuca wykryte ramki krótsze niż 10 pikseli. Większość osób przeskakuje nad tym w dokumentacji. Dla każdego, kto wyciąga tekst ze zrzutów ekranu albo PDF-ów, to najważniejsza liczba w API. Oto, co się dzieje przy zmianie wysokości renderowanych glifów:
| Renderowane px | CER | Co się stało |
|---|---|---|
| 8 | 0.7708 | Załamanie — ramki wpadają pod filtr min_size i są odrzucane; zostają tylko fragmenty |
| 10 | 0.1458 | Degradacja — dokładnie przy progu detektor dzieli linię na 3 ramki |
| 12 | 0.0417 | Odzyskanie |
| 16 | 0.0000 | Idealny odczyt |
| 20 | 0.0208 | Czysto |
| 28 | 0.0208 | Czysto |
| 40 | 0.0625 | Czysto (wraca zamiana case) |
| 64 | 0.0625 | Czysto (case-flip) |
Skok z 0,04 do 0,77 między 12 px a 8 px nie jest stopniową degradacją. To filtr robi dokładnie to, do czego został zaprojektowany, a efekt jest taki, że tekst poniżej mniej więcej 10 px staje się dla domyślnego EasyOCR praktycznie niewidoczny.
Najlepszy zakres to 12–28 px, a pełne CER 0 pojawia się przy 16 px. Powyżej 40 px CER lekko rośnie — nie dlatego, że znaki są trudniejsze, ale dlatego, że wraca zamiana vow → VOW. Duży tekst nie jest trudniejszy do odczytania; po prostu nie korzysta już z tego spacingu, który sprawił, że 16 px wypadło idealnie.
Dla każdego, kto wyciąga tekst ze screenshotów: sprawdź wysokość renderowanych glifów, zanim obwinisz model. Dashboard przechwycony w 1× na ekranie HiDPI albo strona PDF zrasteryzowana do 72 DPI bardzo często daje tekst poniżej 10 px. Zrób capture w 2× albo przeskaluj obraz przed OCR, a ominiesz całą klasę zgłoszeń typu „EasyOCR zignorował pół strony”. Jeśli żadna z tych opcji nie wchodzi w grę, obniż min_size — ale licz się z szumem, bo ten filtr istnieje właśnie po to, by tłumić śmieciowe detekcje.
Rotacja: 10° tolerancji i naprawa, która nie jest symetryczna
Najpierw skos. Małe kąty, Arial 32 px, ustawienia domyślne versus rotation_info=[90,180,270]:
| Kąt skosu | CER (domyślnie) | CER (z rotation_info) |
|---|---|---|
| 0° | 0.0833 | 0.0833 |
| 5° | 0.0417 | 0.0417 |
| 10° | 0.0208 | 0.0833 |
| 15° | 0.2917 | 0.3750 |
| 20° | 0.7500 | 0.7708 |
| 30° | 0.8958 | 0.8750 |
| 45° | 0.8958 | 0.8750 |
Domyślny EasyOCR radzi sobie ze skosem do około 10° bez większego problemu (CER ≤ 0,083), przy 15° zaczyna się chwiać, a przy 20° praktycznie odpada. rotation_info nie pomaga na skos, co ma sens, jeśli wiemy, co ten parametr robi — próbuje ponownie tylko dla podanych kątów, a 15° skosu nie jest ani 90, ani 180, ani 270. Przy 10° nawet trochę pogarsza wynik (0,021 → 0,083), bo zły kąt może wygrać głosowanie confidence.
Prawdziwie dziwne rzeczy zaczynają się przy obrotach ortogonalnych:
| Obrót | CER (domyślnie) | CER (z rotation_info) | Odzyskane? |
|---|---|---|---|
| 90° | 0.8125 | 0.9167 | Nie — gorzej |
| 180° | 0.8542 | 0.6667 | Częściowo |
| 270° | 0.8333 | 0.1042 | Tak |
Ten sam parametr. Ta sama lista kątów. Trzy różne wyniki.
Przy 270° rotation_info robi dokładnie to, co obiecuje wątek issue: CER spada z 0,83 do 0,10, czyli do poziomu faktycznie używalnego. Przy 180° działa tylko częściowo — CER poprawia się do 0,67, ale fraza my vow. znika całkowicie. Przy 90° sytuacja się odwraca, z 0,81 do 0,92, a surowy wynik wyjaśnia dlaczego: rozpoznawacz zwraca tekst lustrzany. VOW wraca jako MOA. quartz wraca jako zuuenb. Odczytaj to w lustrze, a będzie poprawnie — fajny trik, ale zupełnie bezużyteczny w pipeline danych.
Sprawdziłem to na surowych predykcjach, a nie na metrykach zagregowanych, bo pierwsze podejrzenie brzmiało „join order wszystko pomieszał”. To nie był artefakt łączenia — tak właśnie zwracał EasyOCR.
Mechanizm to hipoteza, nie pomiar; nie wykonywałem eksperymentu z konwencją rotacji. Pillow renderuje dodatnie kąty przeciwnie do ruchu wskazówek zegara, więc tylko obraz wyrenderowany przy 270° przypadkiem pokrywa się z orientacją retry, którą rozpoznawacz obsługuje dobrze, a przypadek 90° ma najlepszy scoring na odwróconej orientacji. Niezależnie od mechanizmu wniosek operacyjny się nie zmienia:
Ten fixture pokazuje, że nie można zakładać symetrycznego działania rotation_info względem różnych orientacji. Zweryfikuj rotacje, które faktycznie pojawiają się w twoich danych; normalizacja orientacji na wejściu jest kandydatem na mitigację, a nie wymogiem udowodnionym trzema renderami.
Prognoza, w której się pomyliłem
Na wejściu zakładałem, że słaby kontrast będzie piętą achillesową EasyOCR. Blady szary tekst na białym tle to klasyczna historia porażki OCR, a do tego istnieje opisana ścieżka ratunkowa: contrast_ths=0.1 z adjust_contrast=0.5, która ponownie uruchamia niskokontrastowe ramki na wzmocnionej kopii i wybiera bardziej pewny wynik.
Ta funkcja nigdy się nie włączyła, bo nie była potrzebna.
| Szarość foregroundu | Kontrast Webera | CER (domyślnie) | CER (adjust_contrast=1.0) |
|---|---|---|---|
| 0 (czarny) | 1.000 | 0.0833 | 0.0833 |
| 64 | 0.749 | 0.0833 | 0.0833 |
| 110 | 0.569 | 0.0833 | 0.0833 |
| 150 | 0.412 | 0.1042 | 0.1042 |
| 180 | 0.294 | 0.0625 | 0.0625 |
| 200 | 0.216 | 0.0417 | 0.0417 |
| 220 | 0.137 | 0.0417 | 0.0417 |
CER nie wychodzi z czystego pasma aż do Weber 0,14 — szary 220 na białym, tak blady, że musiałem się wpatrywać w fixture, żeby upewnić się, że tekst w ogóle tam jest. A kolumna z podbiciem kontrastu jest identyczna jak domyślna na każdym kroku, bo domyślna już działała dobrze.
Tła opowiedziały tę samą historię. Czarny tekst przez cały czas:
| Tło | CER |
|---|---|
| Jednolity jasnoniebieski panel | 0.083 |
| Pionowy gradient | 0.021 |
| Szum Gaussowski (μ200, σ22) | 0.000 |
Idealny odczyt na najbardziej zaszumionym fixture w całym zestawie.
Zakres jest wąski: to niski kontrast w postaci jednolitego koloru, bez fotografii, szumu sensora i kompresji JPEG. W tym zestawie największe problemy powodowała geometria i krótkie tokeny; testowane warianty koloru i syntetycznego szumu nie.
Realny scenariusz: wyciąganie liczb ze zrzutu dashboardu
To jest przypadek, do którego najczęściej sprowadza się OCR w Pythonie. Ktoś wysyła zrzut ekranu z wewnętrznego dashboardu albo uruchamiasz Python scraping pipeline na analitycznej stronie pełnej wykresów, gdzie liczby istnieją tylko jako renderowane piksele, i chcesz dostać wartości jako dane.
Wyrenderowałem okno „Sales Dashboard” — ciemny nagłówek z tytułem i okrągłą plakietką awatara, trzy panele KPI, trzy przyciski, tabelę 2×3 — i opisałem wszystkie 19 elementów tekstowych ich dokładnymi stringami oraz ramkami pikseli, a potem dopasowałem wynik EasyOCR do nich na podstawie nakładania ramek.
Recall detekcji: 16 z 19. Trzy pominięcia:
- jednoliterowa plakietka „A”
- komórka tabeli „Q1”
- komórka tabeli „Q2”
A „Q3” zostało wykryte. Ten sam font, ten sam rozmiar, ta sama kolumna — detektor zachował jeden token dwuznakowy, a dwa inne odrzucił. Detekcja była niespójna nawet dla wizualnie bardzo podobnych komórek. Ponieważ w tych uruchomieniach wyniki były deterministyczne, nie ma tu dowodu na losowe zachowanie typu „rzut monetą”. Pokrewny problem jakości screenshotów jest śledzony w #460.
Na 16 elementach, które jednak znalazł, tekst był niemal idealny: średni CER 0,027, a 13 z 16 odczytów było dokładnych. Tytuły, etykiety, przyciski („Save”, „Cancel”, „Export CSV”), nagłówki kolumn oraz liczby z przecinkami wróciły z CER 0. 1,284 został odczytany poprawnie, łącznie z przecinkiem.
Trzy niedoskonałe odczyty to ten sam błąd. Kwoty dolarowe:
| Ground truth | Odczyt EasyOCR |
|---|---|
$57,912 | S57,912 |
$18,330 | S18,330 |
$25,178 | S25,178 |
$12,004 | $12,004 (poprawnie) |
Trzy z czterech znaków dolara stały się wielką literą S. Wizualnie to jeszcze do obrony — ale oznacza, że każde pole walutowe w ekstrakcji jest o jeden znak od śmieci, a naiwne float() wywali się na wszystkich.
Jeśli w pipeline screenshotów masz krótkie etykiety albo waluty, sprawdź potencjalne mitigacje, takie jak upscaling, padding crops, ograniczone oczekiwania względem pól lub post-processing wrażliwy na symbole. Żadnej z tych metod tu nie benchmarkowano, a regex zamieniający wiodące S może uszkodzić poprawne wartości. Korekty stosuj tylko tam, gdzie schemat i reguły walidacji czynią je bezpiecznymi.
Koszt uruchomienia
Liczby z tej samej maszyny (macOS arm64, CPU, jeden host, pomiar przy możliwym równoległym obciążeniu — traktuj to jako kształt, nie uniwersalny benchmark):
| Metryka | Wartość |
|---|---|
| Wagi modelu na dysku | 93,7 MiB (79,30 detektor + 14,44 rozpoznawacz) |
| Szczytowy resident memory, świeży proces CPU | 984,5 MiB |
Zimna inicjalizacja Reader() | 1,3–1,7 s |
| Gorąca latencja, jedna czysta linia 48 znaków (p50) | ~0,062 s (p25–p75: 0,059–0,067 s, n=20) |
detail=0 vs detail=1 | prawie identycznie (mediana 0,062 vs 0,063 s) |
Największy koszt to nie 94 MB wag — to około gigabajta pamięci resident na proces do tego ~2 GB instalacji torch. To właśnie ta liczba decyduje, czy rozwiązanie mieści się w kontenerze, i to o niej nikt nie mówi.
Szybkość jest dobra dla łatwego przypadku. Warm sub-0,1 s dla czystej jednej linii na CPU jest całkowicie używalne. Ale to jest łatwy przypadek: jedna krótka, mocno kontrastowa linia. Powtarzające się skargi „EasyOCR działa na CPU kilkadziesiąt sekund” dotyczą dużych dokumentów wieloregionowych w pełnym rozmiarze canvasu; tego nie odtwarzałem — to inny workload, i tylko do niego się odwołuję.
Mały mit do obalenia: detail=0 nie przyspiesza EasyOCR. Po prostu usuwa z wyniku ramki i confidence score. Obliczenia i tak już zostały wykonane. Mediany różnią się o milisekundę, czyli o szum.
Plusy i minusy
Plusy
- Odczyt znaków na czystym renderowanym tekście łacińskim jest praktycznie idealny — średni CER 0,071 z rozróżnianiem wielkości liter, 0,024 po normalizacji case, a pełny CER 0 możliwy przy 16 px.
- Naprawdę dwulinijkowe API.
Reader(['en']), potemreadtext(), bez konfiguracji i bez wybierania modelu. - Znacznie bardziej odporny na kontrast, niż sugeruje folkowa opinia: brak załamania aż do Weber 0,14 na czystym tekście, a tła szumne/gradientowe/kolorowe nie pogorszyły wyniku (fixture z szumem gaussowskim był odczytany perfekcyjnie).
- Niemal idealny na elementach screenshotów, które wykryje: średni CER 0,027, 13 z 16 dokładnych, w tym liczby z przecinkami.
- Deterministyczny. Każda liczba dokładności tutaj była bajtowo identyczna w dwóch niezależnych uruchomieniach procesu; zmieniał się tylko timing.
- Apache-2.0 i self-hosted, bez opłat licencyjnych za użycie; koszty dotyczą tylko obliczeń, pamięci, storage i kolejek.
Minusy
- Gwałtowne załamanie poniżej udokumentowanego progu
min_size=10— CER 0,77 przy 8 px. Mały tekst UI jest domyślnie niewidoczny. - Tolerancja na skos kończy się około 10° i rozpada się przy 20°.
rotation_infonie jest symetryczną naprawą: 270° odzyskuje, 180° częściowo, 90° pogarsza wynik i zwraca tekst lustrzany.- Detektor gubi krótkie, izolowane tokeny — jednoliterową plakietkę i dwa pola dwuznakowe, podczas gdy sąsiednie pola przechodzą.
- Systematyczne mylenie
$zSw wartościach walutowych (3 z 4). - Około 1 GB pamięci resident na proces, plus około 2 GB zależności torch.
- Najnowsze wydanie jest z września 2024; projekt jest raczej stabilny niż intensywnie rozwijany.
Kto powinien tego używać, a kto powinien odpuścić
Warto ocenić EasyOCR wtedy, gdy wejście jest czyste, proste, renderowane i ma sensowny rozmiar — screenshoty, zrzuty UI, zrasteryzowane PDF-y lub generowane raporty — a potrzebujesz hostowanego lokalnie pipeline’u Pythona bez opłat za użycie. Syntetyczne angielskie wyniki CPU dotyczą właśnie tej ścieżki; zdjęcia, pismo odręczne i inne skrypty trzeba testować osobno.
Odejdź od tego rozwiązania, jeśli którykolwiek z tych opisów pasuje do twoich danych. Zdjęcia — moje liczby dotyczą syntetycznie renderowanego tekstu i nic nie mówią o szumie z aparatu, perspektywie czy świetle. Pismo odręczne — sam projekt tego nie obiecuje. Skrypty nielacińskie — EasyOCR obsługuje ponad 80 języków, ale ja testowałem jeden, a punktem odniesienia są tu publikowane porównania akademickie, nie syntetyczny przebieg po angielskim. Wejścia o dowolnej rotacji — chyba że najpierw robisz własną korekcję orientacji. Wdrożenia z ograniczoną pamięcią — gigabajt na worker szybko się sumuje.
Zanim wybierzesz OCR, sprawdź DOM i odpowiedzi sieciowe. Jeśli potrzebne wartości już istnieją jako strukturalny tekst, pobranie ich ze źródła pozwala uniknąć błędów detekcji i rozpoznawania OCR. OCR ma sens tam, gdzie piksele są jedyną dostępną reprezentacją.
Alternatywy i miejsce naszego stosu
EasyOCR ma licencję Apache-2.0 i działa lokalnie, bez opłat za użycie, ale z realnym kosztem obliczeniowym i operacyjnym. PaddleOCR, Tesseract i vision-language models nie były przeze mnie uruchamiane w tym benchmarku, więc nie wyciągam tu wniosku head-to-head.
Ciekawsze pytanie niż OCR kontra OCR brzmi: czy OCR w ogóle jest ci potrzebny?
Większość zadań polegających na wyciąganiu tekstu ze screenshotów to obejście sytuacji, w której strona WWW była trudna do scrapowania — tabela renderowana przez JavaScript, dashboard za logowaniem, serwis, który stawiał opór. Zrobienie screena i przepuszczenie go przez OCR wydaje się drogą najmniejszego oporu, ale wyrzucasz w ten sposób dobrze uporządkowany tekst, a potem płacisz „podatek od znaku dolara”, żeby odzyskać jego gorszą wersję.
Notatka autora: Thunderbit to nasza zarządzana opcja do ekstrakcji z stron WWW. Nie była uruchamiana na tych fixture’ach obrazowych. Kluczowa granica to reprezentacja źródła: używaj ekstrakcji z DOM/sieci, gdy dane strukturalne istnieją, a OCR oceniaj wtedy, gdy źródłem są wyłącznie piksele.
Powiązane materiały z tego samego test benchu: pełne porównanie open-source scraperów, recenzja Crawl4AI oraz szersze spojrzenie na ekstrakcję wspieraną przez AI dla stron opornych na selektory.
Wypróbuj Thunderbit do ekstrakcji danych z sieci
Werdykt
Czy warto używać EasyOCR? Tak, jeśli obrazy są proste, pionowe, glify mają co najmniej 12 pikseli wysokości i czytasz alfabet łaciński. W tych warunkach działa bardzo dobrze — średni CER 0,071 na czystym tekście, 0,024 po normalizacji case, idealny odczyt przy 16 px i lepsza odporność na kontrast, niż sugeruje reputacja projektu. API to naprawdę dwie linijki, a wynik jest deterministyczny, co przy debugowaniu pipeline’u ma większe znaczenie, niż ludzie zwykle przyznają.
Poza tym zakresem zawodzi w konkretny, dający się nauczyć sposób. Tekst poniżej 10 px znika w filtrze min_size. Skos powyżej 20° rozwala odczyt. rotation_info naprawia jedną orientację ortogonalną, drugą naprawia tylko częściowo, a trzecią pogarsza, zwracając tekst lustrzany. Pojedyncze litery i tokeny dwuznakowe wypadają z detektora, podczas gdy sąsiednie pola przechodzą. Znaki dolara zamieniają się w literę S.
Błędy na fixture’ach wynikały z obu etapów: brakujących lub źle obróconych ramek po stronie geometrii oraz mylenia znaku dolara po stronie rozpoznawania. Upscaling, normalizację orientacji, padding cropów i naprawę symboli zgodną ze schematem traktuj jako kandydatów do weryfikacji, a nie uniwersalnie bezpieczne poprawki.
Tylko nie testuj tego tak jak ja prawie to zrobiłem — z uszkodzonym harness ewaluacyjnym i liczbą, której nie rozumiesz. Wygeneruj własne fixture’y, dokładnie poznaj ground truth i znajdź własną barierę.
Wypróbuj Thunderbit do ekstrakcji danych z sieci Get Started Free
FAQ
Czy EasyOCR jest wystarczająco dokładny do produkcyjnej ekstrakcji tekstu ze screenshotów?
Na syntetycznym dashboardzie dopasowane elementy miały średni CER 0,027, a 13 z 16 było odczytanych dokładnie. Trzy z 19 elementów nie zostały wykryte, a trzy z czterech znaków dolara zamieniły się w S. Czy to jest akceptowalne — i czy pomaga upscaling albo naprawa zgodna ze schematem — trzeba sprawdzić na docelowych układach.
Jaki jest minimalny rozmiar fontu, który EasyOCR potrafi odczytać?
W praktyce około 12 pikseli wysokości renderowanego glifu. Parametr readtext() min_size=10 odrzuca wykryte ramki krótsze niż 10 px, a efekt jest gwałtownym progiem, nie łagodnym spadkiem: CER wynosił 0,77 przy 8 px, 0,15 przy 10 px, 0,04 przy 12 px i 0 przy 16 px. Najlepszy zakres w moim przebiegu to 12–28 px. Jeśli źródłem jest screenshot z HiDPI przechwycony w 1× albo PDF zrasteryzowany do 72 DPI, lepiej przeskalować obraz przed OCR niż obniżać min_size, bo ten filtr istnieje po to, by odrzucać śmieciowe detekcje.
Czy rotation_info naprawia obrócone obrazy w EasyOCR?
Nie niezawodnie i nie symetrycznie. Przy rotation_info=[90,180,270] dla trzech ortogonalnie obróconych kopii tego samego zdania obraz 270° został odzyskany poprawnie (CER 0,83 → 0,10), obraz 180° tylko częściowo (0,85 → 0,67, z pominiętą frazą), a obraz 90° zrobił się gorszy (0,81 → 0,92), zwracając tekst lustrzany, np. VOW → MOA. Parametr nic też nie daje przy małym skosie, bo próbuje tylko wskazanych kątów. Najpierw popraw orientację, dopiero potem wywołuj EasyOCR.
Ile pamięci i miejsca na dysku potrzebuje EasyOCR?
Wagi mają 93,7 MiB i przy pierwszym użyciu są pobierane do ~/.EasyOCR/model/ — 79,30 MiB dla detektora i 14,44 MiB dla angielskiego rozpoznawacza. Szczytowe użycie pamięci resident w świeżym procesie CPU wyniosło 984,5 MiB, do tego dochodzi mniej więcej 2 GB instalacji torch. Zimna inicjalizacja czytnika trwała 1,3–1,7 sekundy; jedna czysta linia działała potem z p50 około 0,062 sekundy na tej maszynie. detail=0 zmienia kształt zwrotu, nie zmierzone runtime.
Czy EasyOCR jest darmowy do użytku komercyjnego i czy nadal jest rozwijany? Ma licencję Apache-2.0, więc jest liberalny i przyjazny komercyjnie. Na 27 lipca 2026 repozytorium miało 29 825 gwiazdek i 528 otwartych issue, najnowsze wydanie to v1.7.2 z września 2024, a ostatni push do mastera był w grudniu 2025. Traktuj to jako projekt stabilny, a nie porzucony — architektura od dłuższego czasu się nie zmienia, a aktywność przeniosła się do issue trackera. Zanim zaczniesz na tym budować, samodzielnie sprawdź aktualną licencję i stan wydań.


