pytesseract
pytesseract is a thin Python wrapper around Google's Tesseract OCR engine. It does not contain any OCR code itself: each call shells out to the tesseract binary installed on your system and parses what comes back. Through it you get image_to_string for plain text, image_to_data for a TSV of words with bounding boxes and confidence scores, image_to_boxes for character boxes, image_to_osd for orientation and script detection, and searchable PDF, hOCR, or ALTO XML output. It accepts PIL images, numpy arrays, or file paths, and reads any of the 100+ languages Tesseract has traineddata for.
The path of least resistance to Tesseract from Python and fine for clean scanned documents, but it is a subprocess wrapper around a 20-year-old engine: for photos, handwriting, or volume work, start with a modern OCR library instead.
Use it if
- You are OCRing clean printed or scanned documents and want a permissive Apache-licensed stack that runs on CPU anywhere Tesseract installs
- You need word-level bounding boxes and confidence scores (image_to_data) for downstream layout logic or redaction
- You want searchable PDFs or hOCR output straight from images with one function call
- You need one of Tesseract's 100+ language packs, including scripts the deep learning OCR projects cover poorly
- Your images are photos, screenshots at odd angles, low resolution scans, or handwriting: Tesseract degrades badly there, and easyocr or paddleocr handle natural scene text far better
- You process images at high volume: every call spawns a tesseract subprocess and round-trips temp files, so throughput is poor; tesserocr binds the C++ API directly and keeps the engine warm
- You expect it to handle preprocessing: pytesseract does zero image cleanup, and accuracy depends heavily on binarization, deskewing, and DPI work you must do yourself with OpenCV or Pillow
- You cannot install system binaries on the target machine: without the tesseract executable and its language packs on the host, the package does nothing
Setup reality
pip install pytesseract is the easy half; the OCR engine is a system dependency you install separately (apt install tesseract-ocr on Debian/Ubuntu, brew install tesseract on macOS, a community installer on Windows), plus per-language packs like tesseract-ocr-deu. On Windows you almost always have to point pytesseract.pytesseract.tesseract_cmd at the exe path because it is not on PATH. Docker images must bake the binary in. Pillow is required for image handling, and real quality tuning happens through Tesseract's cryptic --psm and --oem config strings, which are documented in Tesseract's own manual, not here.
Patterns
OCR an image to plain textimage-to-text
from PIL import Image
import pytesseract
text = pytesseract.image_to_string(Image.open("invoice.png"))
print(text)Passing a file path string instead of a PIL image skips pytesseract's conversion, but then the file must be a format Tesseract itself supports.
Point at the tesseract binary (Windows and custom installs)set-binary-path
import pytesseract
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
print(pytesseract.get_tesseract_version())TesseractNotFoundError almost always means this line is missing. get_tesseract_version() is the quickest sanity check that wiring works.
OCR non-English textother-languages
from PIL import Image
import pytesseract
print(pytesseract.get_languages(config="")) # what is installed
text = pytesseract.image_to_string(Image.open("brief.png"), lang="deu")
mixed = pytesseract.image_to_string(Image.open("menu.png"), lang="eng+fra")Each language needs its traineddata pack installed on the system (e.g. apt install tesseract-ocr-deu); lang="eng+fra" runs multiple languages at once but slows recognition.
Tune page segmentation and engine modepsm-oem-config
from PIL import Image
import pytesseract
# psm 6: assume a single uniform block of text; oem 3: default LSTM engine
custom_config = r"--oem 3 --psm 6"
text = pytesseract.image_to_string(Image.open("receipt.png"), config=custom_config)
# single line of text, digits only
digits = pytesseract.image_to_string(
Image.open("meter.png"),
config=r"--psm 7 -c tessedit_char_whitelist=0123456789",
)Wrong psm is the number one cause of garbage output: the default expects a full page, so single lines, sparse text, and receipts each need their own mode.
Get words with boxes and confidence scoreswords-with-confidence
from PIL import Image
import pytesseract
from pytesseract import Output
data = pytesseract.image_to_data(Image.open("form.png"), output_type=Output.DICT)
for i, word in enumerate(data["text"]):
conf = int(data["conf"][i])
if word.strip() and conf > 60:
print(word, conf, (data["left"][i], data["top"][i], data["width"][i], data["height"][i]))conf is -1 for non-word rows (blocks, lines), so filter before trusting it. Filtering below roughly 60 removes most hallucinated fragments.
Get character-level bounding boxescharacter-boxes
from PIL import Image
import pytesseract
img = Image.open("plate.png")
for line in pytesseract.image_to_boxes(img).splitlines():
ch, x1, y1, x2, y2, _ = line.split(" ")
print(ch, x1, y1, x2, y2)Box coordinates use the bottom-left origin (Tesseract convention), not the top-left origin PIL and OpenCV use; flip y before drawing.
Produce a searchable PDFsearchable-pdf
import pytesseract
pdf_bytes = pytesseract.image_to_pdf_or_hocr("scan.png", extension="pdf")
with open("scan-searchable.pdf", "w+b") as f:
f.write(pdf_bytes)The output layers invisible text over the original image. For whole multi-page documents, ocrmypdf wraps this workflow with rotation and cleanup.
Detect page rotation and scriptorientation-detection
from PIL import Image
import pytesseract
osd = pytesseract.image_to_osd(Image.open("sideways.png"))
print(osd) # includes 'Rotate: 90' and 'Script: Latin'
OSD needs the osd traineddata pack installed and fails on images with little text. Rotate the image by the reported angle before running OCR.
Preprocess with OpenCV before OCRopencv-preprocessing
import cv2
import pytesseract
img = cv2.imread("photo.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.resize(gray, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
text = pytesseract.image_to_string(thresh)OpenCV loads BGR while pytesseract assumes RGB, so convert (or grayscale) first. Upscaling small text and Otsu thresholding are the two cheapest accuracy wins.
Stop runaway OCR jobs with a timeouttimeout-handling
import pytesseract
try:
text = pytesseract.image_to_string("huge-scan.tiff", timeout=5)
except RuntimeError:
text = "" # tesseract was terminated after 5 secondsNoisy images can make Tesseract grind for minutes. The timeout kills the subprocess and raises RuntimeError, which you must catch.
Get several output formats in one OCR passmultiple-outputs-one-pass
import pytesseract
text, boxes = pytesseract.run_and_get_multiple_output(
"page.png", extensions=["txt", "box"]
)Runs recognition once instead of once per format; supports mixing txt, pdf, hocr, box, and tsv. Worth it when you need text plus coordinates.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| easyocr | PyPI | You want deep learning OCR with much better accuracy on photos and scene text, pip-installable with optional GPU. |
| paddleocr | PyPI | You need strong layout analysis, table recognition, or top-tier accuracy on Chinese and mixed-language documents. |
| tesserocr | PyPI | You want the same Tesseract engine through direct C++ bindings for real throughput instead of subprocess calls. |