Skip to content

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 pytesseractpytesseract bridges 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 tesseract on Mac, or apt install tesseract-ocrapt install tesseract-ocr on 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:

setup.py
import pytesseract
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
setup.py
import pytesseract
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"

Create the project

  1. Create a folder named ocr-appocr-app.
  2. Inside it, create basic_ocr_with_tesseract.pybasic_ocr_with_tesseract.py.
  3. Install the Python packages and the Tesseract engine.

Write the code

basic_ocr_with_tesseract.pySource
basic_ocr_with_tesseract.py
"""
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.py
"""
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

command
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.
command
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

basic_ocr_with_tesseract.py
from PIL import Image
import pytesseract
 
image = Image.open(file_path)
extracted_text = pytesseract.image_to_string(image)
basic_ocr_with_tesseract.py
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

basic_ocr_with_tesseract.py
file_path = filedialog.askopenfilename(
    filetypes=[("Image Files", "*.png;*.jpg;*.jpeg;*.bmp;*.tiff")])
if file_path:
    self.extract_text(file_path)
basic_ocr_with_tesseract.py
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

basic_ocr_with_tesseract.py
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}")
basic_ocr_with_tesseract.py
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:

preprocess.py
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"))
preprocess.py
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.py
# --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.py
# --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

Picking 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:

boxes.py
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])
boxes.py
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):

batch.py
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)
batch.py
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

ProblemCauseFix
TesseractNotFoundErrorTesseractNotFoundErrorEngine not installed / not on PATHInstall Tesseract; set tesseract_cmdtesseract_cmd
Gibberish outputNoisy, low-contrast, or skewed imagePreprocess: grayscale + threshold + denoise
Misses a single line / receiptWrong layout assumptionSet --psm 7--psm 7 (single line) or 66 (block)
Wrong languageDefault is EnglishInstall language data; pass lang="deu"lang="deu" etc.
Tiny text unreadResolution too lowUpscale the image before OCR
Results stack in the boxDidn’t clear the Text widgetdelete(1.0, END)delete(1.0, END) before inserting

Variations to Try

  1. Preprocessing pipeline — grayscale, threshold, deskew (the big win).
  2. Multi-language — add lang=lang= and the language packs.
  3. Batch folder — OCR every image in a directory.
  4. Searchable PDF — image → PDF with a hidden text layer.
  5. Receipt parser — extract totals/dates with regex on the output.
  6. Live capture — OCR from a webcam frame.
  7. Translate — pipe extracted text into a translation API.
  8. 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--psm modes 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 coffee

Was this page helpful?

Let us know how we did