Tools,

keras-ocr, tested on scanned book pages

1,334

of 1,955 words, exact

Get the fix

Run keras-ocr in 2026

15 min for 10 pages, Mac CPU, 2 threads

119 MB of weights, first run

In a new Python 3.12 venv, in a folder with scan.pdf:

pdfimages -j scan.pdf p
pip install keras-ocr tensorflow tf-keras
python kocr.py out.txt p-*.jpg
kocr.py
# keras-ocr 0.9.3 on page images, text in reading order.
# Run: python kocr.py out.txt p-000.jpg p-001.jpg
import os
import sys

# keras-ocr needs Keras 2: pip install tf-keras
os.environ.setdefault("TF_USE_LEGACY_KERAS", "1")
import numpy as np

# imgaug 0.4.0 reads np.sctypes, which NumPy 2 removed
if not hasattr(np, "sctypes"):
    np.sctypes = {
        "float": [np.float16, np.float32, np.float64],
        "int": [np.int8, np.int16, np.int32, np.int64],
        "uint": [np.uint8, np.uint16, np.uint32, np.uint64],
        "complex": [np.complex64, np.complex128],
        "others": [bool, object, bytes, str, np.void],
    }
import keras_ocr


def lines(preds):
    """keras-ocr returns words and boxes in no order. Make lines, top to bottom."""
    words = []
    for w, b in preds:
        ys = b[:, 1]
        words.append((ys.mean(), b[:, 0].min(), np.ptp(ys), w))
    rows = []
    for y, x, h, w in sorted(words):
        if rows and y - rows[-1][0] < h / 2:
            rows[-1][1].append((x, w))
        else:
            rows.append([y, [(x, w)]])
    return "\n".join(" ".join(w for _, w in sorted(r)) for _, r in rows)


if __name__ == "__main__":
    pipeline = keras_ocr.pipeline.Pipeline()
    quiet = {"verbose": 0}  # no Keras progress bars
    pages = [pipeline.recognize([p], quiet, quiet)[0] for p in sys.argv[2:]]
    with open(sys.argv[1], "w") as f:
        f.write("\n".join(lines(p) for p in pages) + "\n")

pdfimages comes with poppler. It saves each scanned page as a JPEG, with no change.

Two errors after pip install

pip gives you TensorFlow 2.21.0, Keras 3 and NumPy 2. keras-ocr 0.9.3 came out in 2023. The plain import stopped:

AttributeError: `np.sctypes` was removed in the NumPy 2.0 release. Access dtypes explicitly instead.

imgaug 0.4.0, which keras-ocr imports, still reads np.sctypes. The few lines at the top of kocr.py put it back. Then the model build stopped:

ValueError: Unrecognized keyword arguments passed to Dense: {'weights': [array(...

Keras 3 refuses that argument. tf-keras and TF_USE_LEGACY_KERAS=1 give keras-ocr the Keras 2 it was written for.

Why not pip install "numpy<2"?

It breaks TensorFlow 2.21.0 instead. pip warned that 5 installed packages need NumPy 2, and the import stopped with AttributeError: module 'numpy' has no attribute 'long'.

keras-ocr vs Tesseract

Same 10 pages, 1,955 words
Engine Exact Case and punctuation ignored
keras-ocr 0.9.3 1,334 1,795
Tesseract 5.5.4 1,877 1,947

keras-ocr reads only a to z and 0 to 9. The exact count takes every capital letter and comma as a miss, so the second column is the fair one. Tesseract still found more, in 36 seconds against 15 minutes.

On the same pages, PaddleOCR found 1,919 exact words and docTR found 1,869.

Commas turn into letters

The first line of page 1, and what keras-ocr read:

"Keep your head, Hatton," commanded
ct keep your head hattons commanded

The opening quote became "ct", and the comma after "Hatton" became an s. keras-ocr also returns words and boxes in no order. The lines() function in kocr.py puts them in lines, top to bottom. I tested it on one column of text only.

Full-size pages: 1 more word, slower

Pages 1 and 2, 381 words
Pipeline Exact Time a page
Default, 2,048 px tall 277 84 s
scale=1, max_size=3079 278 128 s

The default shrinks each page before it looks. Full size found 1 more exact word, and it needed 6.9 GB of memory against 3.9 GB.

The first run downloads 119 MB

Pipeline() downloads two weight files from the keras-ocr GitHub releases into ~/.keras-ocr. With no network and no saved files, it stopped:

urllib.error.URLError: <urlopen error [Errno 8] nodename nor servname provided, or not known>

With the files saved, a run on page 1 with no network worked. To keep them somewhere else, set KERAS_OCR_CACHE_DIR.

MIT, and quiet since 2023

keras-ocr is MIT. The detector weights come from CRAFT by Clova AI, and the recognizer from kurapan/CRNN. Both repositories are MIT.

The last release, 0.9.3, is from November 2023. Expect more breaks as TensorFlow moves on.

Need text with nothing to install?

Our Actor takes PDF, JPG and PNG files and returns Markdown, from one web request. On the same 10 pages, inside a PDF, it found 1,874 exact words, with capitals and punctuation.

curl -s -X POST "https://api.apify.com/v2/acts/brasspage~pdf-ocr-to-markdown/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"documentUrls": ["https://api.apify.com/v2/key-value-stores/sTnmNrGoSsi7tLQvs/records/anythingllm-mixed-11-pages.pdf"]}' \
  | jq -r '.[].markdown' > book.md

It costs $0.006 a page.

How I tested it
  • Pages: the 10 scans of OCR in n8n, proofread Wikisource book pages, 1,955 true words. Each page is one JPEG, 1,920 x 3,079 pixels.
  • keras-ocr 0.9.3, TensorFlow 2.21.0, tf-keras 2.21.0, NumPy 2.5.4, Python 3.12.12, on an Apple Silicon Mac. TensorFlow used the CPU, with 2 threads, at lowest priority.
  • Time: the slowest of 3 runs, with other jobs on the Mac. The fastest took 9 minutes. All 3 found the same words.
  • Tesseract 5.5.4 from Homebrew, English, default settings, on the same JPEGs.
  • Score: true words found in order, with the scorer of the tessdata test. For the second column, both texts were lowercased, and every character that is not a letter or a digit was removed.
  • No network: macOS sandbox-exec with all network access denied.
  • The Actor: one real run, build 0.1.65, on November 12. Its file holds the same 10 pages plus a typed cover page.
What I did not test
  • A GPU (tensorflow-metal), and all CPU cores.
  • Training or fine-tuning the recognizer.
  • Two-column pages, photos, and handwriting.
  • Windows, Linux, and Python 3.13.
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

Does keras-ocr work with TensorFlow 2.16 or newer?

Yes, with two fixes: install tf-keras and set TF_USE_LEGACY_KERAS=1, and give imgaug the np.sctypes it reads. With both, keras-ocr 0.9.3 ran on TensorFlow 2.21.0 and NumPy 2.5.4.

Is keras-ocr better than Tesseract?

Not on scanned book pages. On 10 pages, keras-ocr found 1,334 of 1,955 exact words and Tesseract 5.5.4 found 1,877. With case and punctuation ignored: 1,795 and 1,947.

Can keras-ocr read capital letters and punctuation?

Not with its default recognizer. It reads only a to z and 0 to 9, so the text comes back in lowercase, and a comma can turn into a letter.