Tesseract vs. EasyOCR — From Quick Wins to Robust Pipelines
OCR
Tesseract
Author
Fabian
Published
August 21, 2025
I wrote this post because I had to re-learn OCR recently. I kept forgetting the details, configs, and gotchas — so this is my reference to stay sharp. If you’re in the same boat, copy this into a Jupyter notebook or run the code cells as-is.
TL;DR
Use Tesseract when you can preprocess your images into clean, high-contrast, deskewed text blocks. It’s fast, accurate, and transparent.
Use EasyOCR when layout is messy, images are low-quality, or you need a one-function solution that handles a lot of cases out-of-the-box.
The real secret: good preprocessing beats model choice more often than not. Below, I show a minimal example and then a full, production-style preprocessing pipeline you can reuse.
What we’ll build
A minimal OCR demo using both libraries on a synthetic image (so the notebook always runs without external assets).
A robust preprocessing pipeline (OpenCV) that handles skew, noise, line removal, and multi-block pages — then feeds the cleaned image into Tesseract and EasyOCR for comparison.
A tiny evaluation helper so you can sanity-check your results vs. expected text.
Why I’m writing this
I needed OCR again recently and realized I’d forgotten a bunch of the practical bits: - Which --psm to use in Tesseract? - What’s the fastest path to “good enough” when the scan is crooked or low DPI? - When should I reach for EasyOCR instead? This post is my “drop-in” reference — with code I can paste right into a Jupyter notebook.
Setup & prerequisites
Note: Tesseract requires the native binary in addition to the Python wrapper pytesseract.
Python: 3.12.2 | packaged by conda-forge | (main, Feb 16 2024, 20:54:21) [Clang 16.0.6 ]
Platform: macOS-15.5-arm64-arm-64bit
pytesseract: 0.3.13
Quick differences (practical viewpoint)
Tesseract shines with clean prints and when you can control preprocessing. You can tweak --psm (page segmentation) and --oem (engine mode) for accuracy and speed.
EasyOCR is great for messy images and quick starts: it does detection + recognition in one shot and is more forgiving without heavy preprocessing.
If you need the most control and explainability (data pipelines that you can reason about), start with Tesseract + OpenCV. If you want a fast “good enough,” try EasyOCR first.
Minimal --psm cheat sheet (Tesseract)
--psm 6: Assume a single uniform block of text (good for paragraphs).
--psm 11: Sparse text (no particular order) — useful for screenshots / scattered text.
--psm 3: Fully automatic page segmentation (no OSD) — general default when unsure.
Part 1 — Minimal example (synthetic image)
We’ll generate a synthetic text image (so this notebook is self-contained), then run Tesseract and EasyOCR on it.
from PIL import Image, ImageDraw, ImageFont, ImageFilterimport numpy as npimport cv2import matplotlib.pyplot as pltimport pytesseract# --- Generate a synthetic image with text ---W, H =1200, 400img = Image.new("RGB", (W, H), "white")draw = ImageDraw.Draw(img)# Try to grab a font; fall back if not available.def get_font(size=48):# These paths are heuristic; adjust to your system if needed. candidates = ["/System/Library/Fonts/Supplemental/Arial.ttf","/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf","C:/Windows/Fonts/arial.ttf", ]for p in candidates:try:return ImageFont.truetype(p, size=size)exceptException:passreturn ImageFont.load_default()font = get_font(48)text ="Invoice #19845 — Amount Due: $1,284.30\nDue Date: 2025-10-15\nPlease remit to: Zeetō Group"draw.text((50, 80), text, font=font, fill=(0,0,0))# Add mild rotation and noise to simulate a scanimg = img.rotate(-2, expand=True, fillcolor="white")noisy = np.array(img).astype(np.uint8)noise = np.random.normal(0, 6, noisy.shape).astype(np.int16)noisy = np.clip(noisy.astype(np.int16) + noise, 0, 255).astype(np.uint8)noisy_img = Image.fromarray(noisy).filter(ImageFilter.GaussianBlur(0.6))plt.figure(figsize=(8,3))plt.imshow(noisy_img); plt.axis('off'); plt.title('Synthetic scan')plt.show()
Tesseract says:
Invoice #19845 — Amount Due: $1,284.30
Due Date: 2025-10-15
Please remit to: Zeeto Group
# --- Minimal EasyOCR ---import easyocrreader = easyocr.Reader(['en'], gpu=False) # set gpu=True if CUDA availableresults = reader.readtext(np.array(noisy_img))easy_text ="\n".join([r[1] for r in results])print("\nEasyOCR says:\n", easy_text)
Using CPU. Note: This module is much faster with a GPU.
EasyOCR says:
Invoice #19845
Amount Due: $1,284.30
Due Date: 2025-10-15
Please remit to: Zeeto
Group
You should already get decent results. If not, jump to the pipeline below — it will almost always help.
Part 2 — A robust preprocessing pipeline (OpenCV)
Real documents are messy: skewed, low DPI, scans with lines and stamps, or multi-column layouts. Below is a compact pipeline you can adapt anywhere.