Tools,

EasyOCR vs Tesseract vs PaddleOCR vs RapidOCR, tested

EasyOCR does not beat Tesseract on clean printed pages. On 127 of them it came last, with 12,623 of 14,693 book words and 1,855 of 2,418 invoice numbers. Tesseract read 13,858 and 2,337. PaddleOCR beat Tesseract on both, with 14,076 words and every number. RapidOCR read 2,415 numbers in 0.6 seconds a page, but slightly fewer words, 13,744 against 13,858. Tesseract still uses the least memory and disk, but a tilt of more than about 2 degrees, or a shadow, breaks it unless you fix the image first. PaddleOCR and RapidOCR did not need that. If you want one default, use RapidOCR. All invoices and all damage here are made by code, so test your own pages too.

Try it on your PDF, $0.006 a page

That button is our own hosted tool, and it runs Tesseract, with the limits below. More on that.

Which one to use

Results on 8 kinds of page

Each cell is words right / numbers right. Words are out of 14,693 on 55 real printed book scans. Numbers are out of 2,418 on 72 made-up invoices.

Your page Tesseract Tesseract, image fixed first EasyOCR RapidOCR PaddleOCR
Clean scan 13,858 / 2,337 No fix needed 12,623 / 1,855 13,744 / 2,415 14,076 / 2,418
Tilted 7 degrees 3,655 / 13 13,741 / 2,324 8,788 / 1,647 13,766 / 2,414 14,030 / 2,418
Deep shadow, 35% light 6,769 / 839 13,841 / 2,370 12,615 / 1,838 13,873 / 2,415 14,144 / 2,418
Grainy scan 13,817 / 2,079 13,857 / 2,366 12,634 / 1,762 13,885 / 2,412 14,008 / 2,418
Phone style mix 6,666 / 813 13,847 / 2,351 8,577 / 1,737 13,886 / 2,414 14,122 / 2,418
White text on black 13,861 / 2,345 No fix needed 12,599 / 1,945 13,934 / 2,415 14,076 / 2,418
Low resolution, about 100 dpi 13,763 / 2,225 Not tested here 11,517 / 1,281 14,002 / 2,404 14,089 / 2,418
Very low resolution, about 75 dpi 13,208 / 1,307 Not tested here 9,239 / 822 13,996 / 2,370 14,019 / 2,411

These are the same pages and the same scoring as my image preprocessing test. The two low resolution rows reuse the pages of my DPI test. A word counts when it appears in order with exact case. A number counts when the right row holds the exact text. The third column is Tesseract after the deskew and divide steps from that page. Tesseract ran with psm 3 and --dpi 300, or the true dpi on low resolution pages. EasyOCR and RapidOCR ran on their default settings. PaddleOCR ran with three image helpers turned off, as the code below shows. I sorted the boxes of all three into lines first (see below).

What each one costs

Engine Seconds a page, default threads Seconds a page, one core Peak memory Disk Last release
Tesseract 5.5.3 0.6 0.6 75 MB 40 MB Oct 28, 2026 (5.5.4)
EasyOCR 1.7.2 5.8 7.7 26.1 GB 1.0 GB Sep 24, 2024
RapidOCR 3.10.0 0.6 2.5 2.8 GB 290 MB Oct 8, 2026
PaddleOCR 3.7.0 20.5 Not run 18.5 GB 984 MB Jun 11, 2026

I ran 22 pages one at a time, on the CPU of a Mac with 18 cores and 128 GB of memory, with other jobs running beside mine. Disk is the install plus the model files. Peak memory is what one process reached across those 22 pages, on a machine with memory to spare. I did not test how low it can go on a small server. PaddleOCR used about one core on its default threads. I did not try its thread settings.

The first run of EasyOCR and PaddleOCR also downloads model files. RapidOCR's come inside the pip package. EasyOCR on this Mac's GPU took 1.4 seconds a page, so a GPU helps it a lot. That is still more than twice the time of RapidOCR on CPU.

Why EasyOCR lost

Here is where the 563 numbers it missed on clean invoices went. A dollar sign read as S is a plain misread. So is a cut number: in 180 of the 182, the front part is in no box to its left. A lost cell or row can be my line sort as much as EasyOCR.

What happened Numbers Printed Read as
Number cut short at a comma or a dot 182 $1,682.23 682.23
Dollar sign read as a letter 127 $169.13 S169.13
One digit cell not found in its row 126 1 Nothing
Whole row lost 27
Other misreads 101 1 11

On book pages it read a as &, opened quotes as ((, and joined I told into Itold. It gave 47 wrong numbers a confidence of 90% or more on clean pages, so its score will not always warn you.

So I tried its own settings on every 6th page (22 pages, 2,729 words and 399 numbers):

Setting Words right Numbers right Numbers wrong
EasyOCR defaults 2,397 261 117
mag_ratio=2 2,397 261 117
decoder="beamsearch" 2,054 261 120
width_ths=1.0 2,392 257 116
width_ths=2.0 2,383 261 118
link_threshold=0.2 2,401 257 116
low_text=0.3 2,370 261 112
min_size=10 2,397 261 117
Tesseract, for scale 2,587 379 14
RapidOCR, for scale 2,564 399 0
PaddleOCR, for scale 2,623 399 0

No setting fixed it. None raised the numbers, the best one added 4 words, and the beam search decoder lost 343 words. mag_ratio=2 gave the same text as the defaults on all 22 pages, because these pages are already taller than EasyOCR's default canvas_size of 2560 pixels, so both shrink to the same size. min_size=10 changed the text on 2 pages and no score, and I stopped a min_size=5 run because it was too slow. On these 22 pages, RapidOCR and PaddleOCR, as set in the code below, read every number.

Boxes are not text

Tesseract gives you lines. The other three give you boxes. On book pages the order changed at most 15 words for RapidOCR and PaddleOCR. For EasyOCR on phone style pages, my line sort cost 1,905 words: in its own order it read 10,482, and the table shows 8,577. On invoices each cell is its own box, so the rows fall apart: RapidOCR in the order it returned read 20 numbers in the right row. Sorted into lines, the same output read 2,415. If your pages hold tables, you need this:

import math
import statistics


def to_lines(boxes, texts):
    """Sort OCR boxes into lines of text, top to bottom, left to right."""
    tilts = []
    for b in boxes:
        (x0, y0), (x1, y1) = b[0], b[1]
        if abs(x1 - x0) > 3 * abs(b[3][1] - y0):
            tilts.append(math.atan2(y1 - y0, x1 - x0))
    tilt = statistics.median(tilts) if tilts else 0
    cos, sin = math.cos(tilt), math.sin(tilt)

    items = []
    for b, text in zip(boxes, texts):
        x, y = sum(p[0] for p in b) / 4, sum(p[1] for p in b) / 4
        items.append((y * cos - x * sin, x * cos + y * sin, math.dist(b[0], b[3]), text))
    height = statistics.median(i[2] for i in items)

    rows = []
    for item in sorted(items, key=lambda i: i[0]):
        if rows and item[0] - rows[-1][-1][0] < height / 2:
            rows[-1].append(item)
        else:
            rows.append([item])
    return [" ".join(i[3] for i in sorted(r, key=lambda i: i[1])) for r in rows]

It takes out the page tilt first. My first version used the height of each box on the page. On a tilted page that made one long line look many lines tall, so separate lines merged. Use the height along the side of the box, as here. It gave the same lines as my scorer on all 3,317 pages I compared.

The code for each

Each Python block ran as printed, on one of my pages.

Tesseract

brew install tesseract   # or apt-get install tesseract-ocr
pip install pytesseract
import pytesseract

print(pytesseract.image_to_string("page.jpg", config="--dpi 300"))

RapidOCR

pip install rapidocr onnxruntime
from rapidocr import RapidOCR

engine = RapidOCR()
result = engine("page.jpg")
for box, text, conf in zip(result.boxes, result.txts, result.scores):
    print(round(conf, 2), text)

With the helper above, in a file named lines.py:

from rapidocr import RapidOCR

from lines import to_lines

result = RapidOCR()("page.jpg")
print("\n".join(to_lines(result.boxes.tolist(), result.txts)))

PaddleOCR

pip install paddleocr paddlepaddle
from paddleocr import PaddleOCR

ocr = PaddleOCR(
    lang="en",
    use_doc_orientation_classify=False,
    use_doc_unwarping=False,
    use_textline_orientation=False,
)
page = ocr.predict("page.jpg")[0]
for text, conf in zip(page["rec_texts"], page["rec_scores"]):
    print(round(float(conf), 2), text)

Those three flags turn off the page rotation check, the page unwarping and the text line rotation check. It took a model download on first run.

EasyOCR

pip install easyocr
import easyocr

reader = easyocr.Reader(["en"], gpu=False)
for box, text, conf in reader.readtext("page.jpg"):
    print(round(conf, 2), text)

Test it on your own pages

My pages are not your pages. Type out one page into page.txt, and count what an engine finds:

import difflib

from rapidocr import RapidOCR

from lines import to_lines

result = RapidOCR()("page.jpg")
text = " ".join(to_lines(result.boxes.tolist(), result.txts))

truth = open("page.txt").read().split()
match = difflib.SequenceMatcher(None, truth, text.split(), autojunk=False)
found = sum(b.size for b in match.get_matching_blocks())
print(f"{found} of {len(truth)} words")

Do not choose by confidence. Read Tesseract confidence score for why. If you stay with Tesseract, see how to improve Tesseract accuracy and tessdata_best vs fast. For a whole PDF, see PDF to Markdown in Python and scanned PDF to text.

If you do not want to build it

PDF OCR to Markdown is our Apify Actor. It runs Tesseract, not PaddleOCR or RapidOCR. A default run has 1 GB of memory and a quarter of a CPU core, and both of those used more than that here. It renders each scanned page at 300 dpi in gray, turns it upright, deskews up to 5 degrees and rebuilds tables, and on tables with lines it checks the math. It reads digital PDFs from their text layer, with no OCR. You paste links to PDFs or images and get Markdown back, with nothing to install.

I did not run these tests on it, so no number above describes it. It has no shadow step, so if your pages are photos with shadows, try RapidOCR or PaddleOCR first.

What I did not test

Questions people ask

Is EasyOCR better than Tesseract?

Not on clean printed pages. On 127 clean pages Tesseract read 13,858 of 14,693 book words and 2,337 of 2,418 invoice numbers. EasyOCR read 12,623 and 1,855. EasyOCR did hold up better than plain Tesseract on tilted, shadowed and phone style pages: at 7 degrees it kept 1,647 numbers, and Tesseract kept 13. PaddleOCR and RapidOCR beat both there.

Is PaddleOCR better than Tesseract?

On accuracy, yes, on every set I ran. It read all 2,418 numbers on clean pages, and 14,076 words against 13,858. The price is time and memory: 20.5 seconds a page on this Mac against 0.6, and 18.5 GB of memory against 75 MB.

Is EasyOCR faster than Tesseract?

No. On CPU, one page at a time, EasyOCR took 5.8 seconds a page and Tesseract took 0.6. On this Mac's GPU, EasyOCR took 1.4 seconds. RapidOCR took 0.6 on CPU.

What is RapidOCR?

RapidOCR runs PaddleOCR's text models on ONNX Runtime, so you do not install PaddlePaddle. In my run it used the small PP-OCRv6 models, and PaddleOCR used the medium ones. It read a little less here and ran much faster.

Does EasyOCR need a GPU?

No. With gpu=False it ran on CPU at 5.8 seconds a page. It prints a warning that a GPU is much faster. I tested one GPU, the one in this Mac, and it took 1.4 seconds a page.

Can any of these read handwriting?

Better than you might expect, on one neat hand. On 5 pages of one neat manuscript, of 929 words, PaddleOCR found 840, RapidOCR 747, Tesseract 452 and EasyOCR 271. That is one writer on 5 pages, so try your own.

How I tested: Tesseract 5.5.3 with its default English model, EasyOCR 1.7.2 with torch 2.15.0, RapidOCR 3.10.0 with onnxruntime 1.31.1, and PaddleOCR 3.7.0 with paddlepaddle 3.3.1, on Python 3.12 on an arm64 Mac. Book pages are 55 proofread Wikisource scans from 11 books. The 5 handwritten pages are from one neat manuscript in the same set. The invoices are drawn with their own slight tilt and a light blur, to look scanned. Low resolution book pages are shrunk copies. The scripts and their output are in our private repo. Sources, read October 31 and November 1, 2026: EasyOCR, RapidOCR, PaddleOCR, Tesseract, and the release dates on Tesseract's releases page and PyPI for EasyOCR and RapidOCR. We make the Apify Actors. The four engines and Wikisource are projects we do not make.