keras-ocr, tested on scanned book pages
1,334
of 1,955 words, exact
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
| 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
| 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.
Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First 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.
Related guides
- Pix2Text OCR: the install fix, tested on scans and math
- Ghostscript OCR: commands, tested vs Tesseract and OCRmyPDF
- docTR OCR on a scanned PDF: Mac setup and results, tested
- PaddleOCR on a Mac: CPU setup and results, tested
- EasyOCR vs Tesseract vs PaddleOCR vs RapidOCR, tested
- Scribe.js OCR, tested on scanned PDFs in Node