Tools,

tesserocr vs pytesseract, tested on 55 scans

3.2x

less CPU with tesserocr on small images

Get the code

Same text, less work per image

Both ran Tesseract 5.5.4. From a file path, both wrote the tesseract command's text.

55 of 55 book scans, the same text

13,858 of 14,693 words found, both

CPU seconds, median of 3 rounds
Job pytesseract tesserocr
259 line images 18.9 s 5.9 s
55 full pages 61.6 s 58.1 s

pytesseract starts the tesseract program for every image, and the program loads the model each time. That cost 50 to 64 ms of CPU an image.

The code: open the API once

pytesseract needs the tesseract program installed:

import pytesseract

text = pytesseract.image_to_string("scan.jpg", config="--dpi 300")

tesserocr keeps Tesseract loaded. Make one API and reuse it for every image:

from tesserocr import PyTessBaseAPI

with PyTessBaseAPI(path="/opt/homebrew/share/tessdata/") as api:
    api.SetVariable("user_defined_dpi", "300")
    for name in ["scan1.jpg", "scan2.jpg"]:
        api.SetImageFile(name)
        print(api.GetUTF8Text())

path is the folder with your models. tesseract --list-langs prints it.

Both re-save a Pillow JPEG

Pass Image.open("scan.jpg") and both libraries save it again as a JPEG before Tesseract reads it. That copy loses detail.

6 of 55 pages kept the command's text

13,854 words found, 4 fewer

Pass the path instead. If you already hold the image, call it a PNG, so the copy is lossless:

img = Image.open("scan.jpg")
img.format = "PNG"

With that line, both matched the command on every page.

pip install tesserocr has no models

The wheel I got (2.12.0, Mac arm64, Python 3.13) carries its own Tesseract 5.5.3 and no language files. The first API call fails:

RuntimeError: Failed to init API, possibly an invalid tessdata path: ./

Pass path as above. Or build it against the Tesseract you already have, and it finds the models itself:

pip install --no-binary tesserocr tesserocr
How I tested it
  • Pages: 55 proofread Wikisource scans from 11 printed books, JPEG, 1,920 pixels wide, no dpi in the file. The same set as Tesseract in Docker.
  • Score: true words found in order, with the scorer of the tessdata test.
  • Apple Silicon Mac, Homebrew Tesseract 5.5.4, tessdata_fast English, Python 3.13, pytesseract 0.3.13, Pillow 13.0.0, and tesserocr 2.12.0 built from source.
  • Time is CPU time of the process and its children, so other jobs on the Mac did not count. The 3 rounds stayed within 4%.
  • Line images: 259 lines cut from 10 scans, saved as PNG, read with psm 7. Both wrote the same text on every line.
  • The wheel's own 5.5.3 wrote the same text as 5.5.4 on every page, in 54.9 s. I did not find out why it ran faster.
What I did not test
  • Threads. I read one image at a time.
  • Windows and Linux installs, and wall time on an idle Mac.
  • Other languages, tessdata_best, and photos.
Sources

Two ways to skip the work

Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.

Try the Actor

Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.

Get one page free

Questions people ask

Which one should I use?

pytesseract for a few pages: it needs only pip and the tesseract program. tesserocr for many small images or video frames, with one API kept open.

Does the JPEG trap hit numpy or OpenCV images?

Not numpy arrays. An array has no file format, so both save it without loss. As arrays, all 55 scans matched the command. I tested RGB arrays from Pillow, not OpenCV's BGR.

Can tesserocr give me line or word boxes?

Yes. api.GetComponentImages(RIL.TEXTLINE, True) returns each line with its box, and RIL.WORD gives words. I cut the 259 line images with it. pytesseract has image_to_data.