Tesseract OCR in Python
with pytesseract.
A complete, tested guide to pytesseract — installation, the core API, image preprocessing, PDFs, confidence scores, PSM/OEM configuration, performance and the errors people actually hit.
Quickstart (three lines)
Install the engine first, then run this. The full setup follows below.
import pytesseract
from PIL import Image
text = pytesseract.image_to_string(Image.open("image.png"))
print(text)Install pytesseract
pytesseract is only a wrapper — it calls the Tesseract binary, so the engine must be installed first.
pip install pytesseract pillow
# optional, for preprocessing:
pip install opencv-python numpy
# optional, for PDF input:
pip install pdf2imageIf the engine is not on your PATH (common on Windows), point pytesseract at it explicitly:
import pytesseract
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"New to the engine? See the installation guide for Windows, macOS, Linux, Docker and WSL.
Core API
pytesseract exposes one function per output type. Pick the one that matches what you need.
| Function | Returns | Use when |
|---|---|---|
image_to_string | Plain text | You only need the recognised text |
image_to_data | Word boxes + confidence | Highlighting text, filtering low-confidence words |
image_to_boxes | Per-character boxes | Character-level geometry |
image_to_pdf_or_hocr | Searchable PDF or hOCR | Building searchable PDFs or web overlays |
image_to_osd | Orientation and script detection | Detecting rotation or script before OCR |
image_to_alto_xml | ALTO XML | Archival / library pipelines |
import pytesseract
from PIL import Image
from pytesseract import Output
img = Image.open("image.png")
text = pytesseract.image_to_string(img) # plain text
data = pytesseract.image_to_data(img, output_type=Output.DICT) # boxes + conf
osd = pytesseract.image_to_osd(img) # orientation / script
pdf = pytesseract.image_to_pdf_or_hocr(img, extension="pdf") # searchable PDFConfiguration: PSM, OEM, languages and whitelisting
Pass options as a space-separated string to config=.
# Single block of text, LSTM engine, English
text = pytesseract.image_to_string(img, lang="eng", config="--oem 3 --psm 6")
# Digits only (receipt totals, meter readings)
text = pytesseract.image_to_string(img, config="--psm 7 -c tessedit_char_whitelist=0123456789.")
# Mixed languages
text = pytesseract.image_to_string(img, lang="eng+fra")| PSM | Mode | Best for |
|---|---|---|
| 3 | Fully automatic (default) | Standard documents |
| 4 | Single column of variable sizes | Articles, single-column pages |
| 6 | Single uniform block of text | Paragraphs, labels |
| 7 | Single text line | One-line captions, single fields |
| 8 | Single word | Individual words |
| 11 | Sparse text | Receipts, scattered UI text |
| 13 | Raw line | Single line without Tesseract hacks |
OEM modes: --oem 1 (LSTM only, default in v5), --oem 0 (legacy), --oem 3 (default/auto). Full reference in the documentation.
Image preprocessing
Clean input is the single biggest factor in accuracy. These are the steps that matter most.
import cv2, numpy as np, pytesseract
from PIL import Image
image = cv2.imread("receipt.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Upscale small text to ~300 DPI equivalent
gray = cv2.resize(gray, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
# Denoise, then Otsu threshold to pure black/white
blur = cv2.GaussianBlur(gray, (5, 5), 0)
_, binary = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
text = pytesseract.image_to_string(Image.fromarray(binary))- Grayscale — remove colour channels before thresholding.
- Upscale — small text below ~20 px tall recognises badly; scale it up.
- Threshold — Otsu for even lighting, adaptive for uneven lighting.
- Denoise — a light median/Gaussian blur removes speckle that reads as punctuation.
- Deskew — correct rotation with
cv2.minAreaRectbefore OCR. - Remove borders — crop dark scan edges that confuse layout analysis.
Word positions and confidence scores
Use image_to_data to get per-word boxes and a 0–100 confidence score, then filter noise.
from pytesseract import Output
data = pytesseract.image_to_data(img, output_type=Output.DICT)
for text, conf in zip(data["text"], data["conf"]):
if text.strip() and float(conf) > 60: # drop low-confidence noise
print(round(float(conf)), text)OCR a PDF in Python
Tesseract takes images, so rasterise PDF pages first with pdf2image (which needs the poppler tools).
import io, pytesseract
from pdf2image import convert_from_path
from pypdf import PdfWriter, PdfReader
pages = convert_from_path("scan.pdf", dpi=300) # needs poppler
writer = PdfWriter()
for page in pages:
pdf_bytes = pytesseract.image_to_pdf_or_hocr(page, extension="pdf")
writer.add_page(PdfReader(io.BytesIO(pdf_bytes)).pages[0])
with open("scan_searchable.pdf", "wb") as f:
writer.write(f)On Windows, pass the poppler bin folder via poppler_path=. For a deeper workflow (OCRmyPDF, layout fidelity, large files), see the documentation on output formats.
Performance
pytesseract shells out to the CLI for every call, so throughput depends on how you structure it.
| Approach | When | Trade-off |
|---|---|---|
pytesseract per call | Scripts and one-off jobs | Spawns a process each call — slow in loops |
| Batch into multi-page TIFF | Many pages, same settings | One process for many pages |
multiprocessing | Many independent images | Uses all CPU cores; more memory |
tesserocr | High-volume, in-process | Native binding, more setup |
Error catalog
The errors below account for most pytesseract problems.
| Error | Cause | Fix |
|---|---|---|
TesseractNotFoundError | Binary not installed or not on PATH | Install the engine or set pytesseract.pytesseract.tesseract_cmd. |
(-215:Assertion failed) src.type() == CV_8UC1 | Thresholding a 3-channel image | Convert with cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) first. |
Error opening data file ...fra.traineddata | Language pack missing | Install the language package or set TESSDATA_PREFIX. |
| Empty output | Low DPI, wrong PSM, or no text | Upscale, try --psm 6/7/11, preprocess. |
| Very slow in a loop | One process per call | Batch, use multiprocessing, or tesserocr. |
Test and measure accuracy
Measure character and word error rates instead of eyeballing output.
import pytesseract, jiwer
from PIL import Image
hypothesis = pytesseract.image_to_string(Image.open("sample.png"))
reference = open("sample.gt.txt").read()
print("CER:", jiwer.cer(reference, hypothesis))
print("WER:", jiwer.wer(reference, hypothesis))Keep a small set of representative images with ground-truth text as regression fixtures, and re-run when you change preprocessing or config.
Python FAQ
How do I use Tesseract OCR in Python?
Install the Tesseract engine first, then pip install pytesseract pillow. Open the image with Pillow and call pytesseract.image_to_string(img).
Why do I get TesseractNotFoundError?
pytesseract cannot find the tesseract binary. Install the engine, put it on the PATH, or set pytesseract.pytesseract.tesseract_cmd to the full path.
How do I improve accuracy?
Upscale to ~300 DPI, grayscale, threshold, denoise, deskew, and set the right --psm. Use the correct lang= and, for fixed fields, a character whitelist.
How do I OCR a PDF?
Use pdf2image (with poppler) to convert pages to images, run pytesseract on each, and merge with image_to_pdf_or_hocr(..., extension="pdf").
How do I get confidence scores?
Use image_to_data(..., output_type=Output.DICT) and filter on the conf list.
Is pytesseract slow?
Each call spawns a process. Batch, use multiprocessing, or switch to the in-process tesserocr binding for high throughput.
Next steps
Go deeper on the engine and the ecosystem.