Basic OCR with Tesseract
Abstract
Optical Character Recognition turns a picture of text into editable text — the tech behind scanning receipts, digitizing books, and reading license plates. This project wraps Google’s open-source Tesseract engine in a Tkinter GUI: upload an image, and the extracted text appears in a scrollable box. It’s only a few lines, because pytesseractpytesseract does the heavy lifting. The real lesson is everything around the OCR call — because raw scans rarely read cleanly, you’ll learn the image preprocessing (grayscale, threshold, denoise) that turns garbage output into accurate text, plus page-segmentation modes, batch processing, and searchable-PDF export.
You will leave understanding:
- How
pytesseractpytesseractbridges Python and the Tesseract engine. - Why preprocessing is 80% of real-world OCR accuracy.
- What page-segmentation modes (
--psm--psm) do and when to change them. - How to scale from one image to a batch pipeline.
Prerequisites
- Python 3.6 or above.
- The Tesseract engine itself (separate from the Python package): install via the Tesseract installer on Windows,
brew install tesseractbrew install tesseracton Mac, orapt install tesseract-ocrapt install tesseract-ocron Linux. pip install pytesseract pillow opencv-pythonpip install pytesseract pillow opencv-python.- Tkinter (bundled with Python).
Getting Started
Point Python at Tesseract (Windows)
If pytesseractpytesseract can’t find the engine, set the path explicitly:
import pytesseract
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"import pytesseract
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"Create the project
- Create a folder named
ocr-appocr-app. - Inside it, create
basic_ocr_with_tesseract.pybasic_ocr_with_tesseract.py. - Install the Python packages and the Tesseract engine.
Write the code
basic_ocr_with_tesseract.py
Source"""
Basic OCR with Tesseract
A Python application that uses Tesseract OCR to extract text from images. Features include:
- GUI for uploading images
- Displaying extracted text
"""
import pytesseract
from PIL import Image
from tkinter import Tk, Label, Button, filedialog, Text, Scrollbar, END
class BasicOCR:
def __init__(self, root):
self.root = root
self.root.title("Basic OCR with Tesseract")
self.label = Label(root, text="Upload an image to extract text:")
self.label.pack(pady=10)
self.upload_button = Button(root, text="Upload Image", command=self.upload_image)
self.upload_button.pack(pady=5)
self.text_area = Text(root, wrap="word", width=60, height=20)
self.text_area.pack(pady=10)
self.scrollbar = Scrollbar(root, command=self.text_area.yview)
self.scrollbar.pack(side="right", fill="y")
self.text_area.config(yscrollcommand=self.scrollbar.set)
def upload_image(self):
"""Open a file dialog to upload an image."""
file_path = filedialog.askopenfilename(
filetypes=[("Image Files", "*.png;*.jpg;*.jpeg;*.bmp;*.tiff")]
)
if file_path:
self.extract_text(file_path)
def extract_text(self, file_path):
"""Extract text from the uploaded image using Tesseract OCR."""
try:
image = Image.open(file_path)
extracted_text = pytesseract.image_to_string(image)
self.text_area.delete(1.0, END)
self.text_area.insert(END, extracted_text)
except Exception as e:
self.text_area.delete(1.0, END)
self.text_area.insert(END, f"Error: {e}")
def main():
root = Tk()
app = BasicOCR(root)
root.mainloop()
if __name__ == "__main__":
main()
"""
Basic OCR with Tesseract
A Python application that uses Tesseract OCR to extract text from images. Features include:
- GUI for uploading images
- Displaying extracted text
"""
import pytesseract
from PIL import Image
from tkinter import Tk, Label, Button, filedialog, Text, Scrollbar, END
class BasicOCR:
def __init__(self, root):
self.root = root
self.root.title("Basic OCR with Tesseract")
self.label = Label(root, text="Upload an image to extract text:")
self.label.pack(pady=10)
self.upload_button = Button(root, text="Upload Image", command=self.upload_image)
self.upload_button.pack(pady=5)
self.text_area = Text(root, wrap="word", width=60, height=20)
self.text_area.pack(pady=10)
self.scrollbar = Scrollbar(root, command=self.text_area.yview)
self.scrollbar.pack(side="right", fill="y")
self.text_area.config(yscrollcommand=self.scrollbar.set)
def upload_image(self):
"""Open a file dialog to upload an image."""
file_path = filedialog.askopenfilename(
filetypes=[("Image Files", "*.png;*.jpg;*.jpeg;*.bmp;*.tiff")]
)
if file_path:
self.extract_text(file_path)
def extract_text(self, file_path):
"""Extract text from the uploaded image using Tesseract OCR."""
try:
image = Image.open(file_path)
extracted_text = pytesseract.image_to_string(image)
self.text_area.delete(1.0, END)
self.text_area.insert(END, extracted_text)
except Exception as e:
self.text_area.delete(1.0, END)
self.text_area.insert(END, f"Error: {e}")
def main():
root = Tk()
app = BasicOCR(root)
root.mainloop()
if __name__ == "__main__":
main()
Run it
C:\Users\Your Name\ocr-app> python basic_ocr_with_tesseract.py
# Upload Image -> pick a clear photo/scan of text -> text appears in the box.C:\Users\Your Name\ocr-app> python basic_ocr_with_tesseract.py
# Upload Image -> pick a clear photo/scan of text -> text appears in the box.Step-by-Step Explanation
1. The OCR call
from PIL import Image
import pytesseract
image = Image.open(file_path)
extracted_text = pytesseract.image_to_string(image)from PIL import Image
import pytesseract
image = Image.open(file_path)
extracted_text = pytesseract.image_to_string(image)That’s the entire recognition step. Image.openImage.open loads the file with Pillow; image_to_stringimage_to_string hands it to Tesseract and returns the recognized text. Everything else is UI and error handling.
2. The file dialog
file_path = filedialog.askopenfilename(
filetypes=[("Image Files", "*.png;*.jpg;*.jpeg;*.bmp;*.tiff")])
if file_path:
self.extract_text(file_path)file_path = filedialog.askopenfilename(
filetypes=[("Image Files", "*.png;*.jpg;*.jpeg;*.bmp;*.tiff")])
if file_path:
self.extract_text(file_path)Restricting filetypesfiletypes keeps users from selecting files Tesseract can’t read. The if file_pathif file_path guard handles the user pressing Cancel.
3. Showing the result safely
try:
...
self.text_area.delete(1.0, END)
self.text_area.insert(END, extracted_text)
except Exception as e:
self.text_area.delete(1.0, END)
self.text_area.insert(END, f"Error: {e}")try:
...
self.text_area.delete(1.0, END)
self.text_area.insert(END, extracted_text)
except Exception as e:
self.text_area.delete(1.0, END)
self.text_area.insert(END, f"Error: {e}")Clearing before inserting prevents results from stacking. Wrapping in try/excepttry/except turns a missing-engine or corrupt-file error into a visible message instead of a crash.
The Secret to Good OCR: Preprocessing
Tesseract is only as good as the image you feed it. Clean it up with OpenCV first — this single step often doubles accuracy:
import cv2
def clean(path):
img = cv2.imread(path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 1. drop color
gray = cv2.medianBlur(gray, 3) # 2. remove speckle noise
# 3. binarize: pure black text on white, adaptive to lighting
thresh = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 31, 2)
return thresh
text = pytesseract.image_to_string(clean("scan.jpg"))import cv2
def clean(path):
img = cv2.imread(path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 1. drop color
gray = cv2.medianBlur(gray, 3) # 2. remove speckle noise
# 3. binarize: pure black text on white, adaptive to lighting
thresh = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 31, 2)
return thresh
text = pytesseract.image_to_string(clean("scan.jpg"))Grayscale → denoise → threshold (black/white) is the standard recipe. For skewed scans, add deskewing; for tiny text, upscale the image first.
Page-Segmentation Modes
Tesseract assumes a full page by default. Tell it the layout for better results:
# --psm 6: assume a single uniform block of text (great for paragraphs)
pytesseract.image_to_string(img, config="--psm 6")
# --psm 7: treat the image as a single line (receipts, labels)
pytesseract.image_to_string(img, config="--psm 7")
# --psm 10: a single character# --psm 6: assume a single uniform block of text (great for paragraphs)
pytesseract.image_to_string(img, config="--psm 6")
# --psm 7: treat the image as a single line (receipts, labels)
pytesseract.image_to_string(img, config="--psm 7")
# --psm 10: a single characterPicking the right --psm--psm for the content is one of the biggest accuracy levers.
Get Word Positions (Bounding Boxes)
Beyond plain text, Tesseract can tell you where each word is — useful for highlighting or forms:
data = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)
for i, word in enumerate(data["text"]):
if word.strip():
x, y, w, h = (data[k][i] for k in ("left", "top", "width", "height"))
print(word, (x, y, w, h), "conf:", data["conf"][i])data = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)
for i, word in enumerate(data["text"]):
if word.strip():
x, y, w, h = (data[k][i] for k in ("left", "top", "width", "height"))
print(word, (x, y, w, h), "conf:", data["conf"][i])Batch Mode and Searchable PDFs
Process a folder, or produce a PDF with an invisible text layer (so it’s searchable):
from pathlib import Path
for img in Path("scans").glob("*.png"):
Path(img.with_suffix(".txt")).write_text(
pytesseract.image_to_string(Image.open(img)))
# Searchable PDF:
pdf = pytesseract.image_to_pdf_or_hocr("scan.png", extension="pdf")
Path("scan.pdf").write_bytes(pdf)from pathlib import Path
for img in Path("scans").glob("*.png"):
Path(img.with_suffix(".txt")).write_text(
pytesseract.image_to_string(Image.open(img)))
# Searchable PDF:
pdf = pytesseract.image_to_pdf_or_hocr("scan.png", extension="pdf")
Path("scan.pdf").write_bytes(pdf)Common Mistakes
| Problem | Cause | Fix |
|---|---|---|
TesseractNotFoundErrorTesseractNotFoundError | Engine not installed / not on PATH | Install Tesseract; set tesseract_cmdtesseract_cmd |
| Gibberish output | Noisy, low-contrast, or skewed image | Preprocess: grayscale + threshold + denoise |
| Misses a single line / receipt | Wrong layout assumption | Set --psm 7--psm 7 (single line) or 66 (block) |
| Wrong language | Default is English | Install language data; pass lang="deu"lang="deu" etc. |
| Tiny text unread | Resolution too low | Upscale the image before OCR |
| Results stack in the box | Didn’t clear the Text widget | delete(1.0, END)delete(1.0, END) before inserting |
Variations to Try
- Preprocessing pipeline — grayscale, threshold, deskew (the big win).
- Multi-language — add
lang=lang=and the language packs. - Batch folder — OCR every image in a directory.
- Searchable PDF — image → PDF with a hidden text layer.
- Receipt parser — extract totals/dates with regex on the output.
- Live capture — OCR from a webcam frame.
- Translate — pipe extracted text into a translation API.
- Confidence highlighting — color words by Tesseract’s confidence score.
Real-World Applications
- Document digitization — turning paper archives into searchable text.
- Data entry automation — invoices, receipts, forms.
- Accessibility — reading text aloud for the visually impaired.
- License-plate / sign reading — logistics and automotive.
Educational Value
- OCR fundamentals — what the engine does and its inputs.
- Image preprocessing — the unglamorous step that drives accuracy.
- Configuration — page-segmentation modes and languages.
- Pipelines — single image → batch → structured output.
Next Steps
- Add an OpenCV preprocessing step before OCR.
- Experiment with
--psm--psmmodes and languages. - Build batch processing and searchable-PDF export.
- Parse structured docs (receipts/forms) with regex on the output.
Conclusion
You built an OCR app that extracts text from images in a few lines — and learned that the real skill isn’t the image_to_stringimage_to_string call, it’s the preprocessing and configuration that make it accurate. Grayscale, threshold, the right --psm--psm, and batch pipelines are what separate a demo from the engine behind every scanner app. Full source on GitHub. Explore more vision projects on Python Central Hub.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
