Practical OCR: Boosting Accuracy from 40% to 90% with Pytesseract and OpenCV

Python tutorial - IT technology blog
Python tutorial - IT technology blog

Why You Should Never Feed Raw Images Directly to OCR Libraries

About six months ago, I took on a challenging task: digitizing a massive archive of old invoices for my company. My first thought was to use Google Vision API or AWS Textract for a quick fix. However, data security concerns and costs forced me to pivot toward an open-source solution: Tesseract OCR.

Reality wasn’t as smooth as the online tutorials suggested. If you just pip install pytesseract and feed it a phone photo, the output is often a mess of gibberish. OCR (Optical Character Recognition) is like teaching a child to read in the dark. To get it right, you need to turn on the lights, clean the glasses, and point to each line. That’s where OpenCV comes in as a crucial preprocessing filter.

The following workflow is distilled from processing over 50,000 real-world records. It helped boost accuracy from 40% (raw images) to over 90% with just a few basic image processing steps.

Setting Up the Right “Toolbox”

A common mistake is installing the Python library but forgetting to install the Tesseract Engine on the operating system. Pytesseract is merely a wrapper that allows Python to communicate with the underlying Tesseract software, much like the concepts explored in mastering subprocess in Python to execute external tools.

1. Installing Tesseract Engine

For Ubuntu/Debian, run the following command:

sudo apt update
sudo apt install tesseract-ocr tesseract-ocr-vie

For Windows, download the installer (.exe) from UB Mannheim’s GitHub. Be sure to note the installation path, usually C:\Program Files\Tesseract-OCR, and consider modernizing your Python file handling code to manage these paths effectively.

2. Supporting Libraries

It’s best to use a virtual environment (venv) to keep your project clean:

pip install pytesseract opencv-python numpy Pillow

Preprocessing: The Key to Boosting Accuracy

My hard-earned lesson: Input image quality accounts for 80% of the success rate. Instead of feeding the raw image directly, perform these three “cleaning” steps using OpenCV.

Step 1: Grayscale and Denoising

Color images contain too much noise that can be distracting. Converting to grayscale helps the algorithm focus on the contrast between the text and the background.

import cv2
import pytesseract

# Configure path for Windows
# pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

def preprocess_basic(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # Denoise using Median Blur to smooth the background area
    return cv2.medianBlur(gray, 3)

Step 2: Thresholding

This step converts the image to pure black and white (Binary). I prefer using Otsu’s Thresholding. It automatically calculates the optimal threshold, which is especially effective when invoice images have shadows or uneven lighting.

def apply_threshold(image):
    return cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

Step 3: Configuring PSM and OEM Parameters

Tesseract provides different reading modes via the PSM (Page Segmentation Mode) parameter. Choosing the wrong PSM is a leading cause of line skipping or missing characters.

  • PSM 6: Assume a single uniform block of text (great for invoices and administrative documents).
  • PSM 3: Fully automatic page segmentation (useful for multi-column newspaper pages).
  • OEM 3: Default mode, combining the modern LSTM engine.

Complete code to extract text:

def extract_invoice_text(image_path):
    img = cv2.imread(image_path)
    processed_img = apply_threshold(preprocess_basic(img))

    # Config: Vietnamese + Text block mode
    config = r'--oem 3 --psm 6 -l vie'
    return pytesseract.image_to_string(processed_img, config=config)

print(extract_invoice_text('sample_invoice.png'))

Real-world Operating Experience (Scaling Up)

When deploying scripts to process tens of thousands of images daily, such as automating file monitoring with Python Watchdog to detect new documents, you will encounter challenges that don’t appear in local environments.

1. Deskewing

Hand-captured photos are often tilted. Even a 5-10 degree tilt can cause Tesseract to misread lines. I often use the cv2.minAreaRect function to find the tilt angle and rotate the image horizontally before OCR.

2. Don’t Trust the Machine 100% (Confidence Score)

In a production environment, never save OCR results directly to the database without verification. Pytesseract can return a confidence score (0-100%) for each word. For financial projects, I set a threshold of 80%. If any word has lower confidence, the system flags it for human review (Human-in-the-loop).

# Get detailed data for quality control
data = pytesseract.image_to_data(processed_img, output_type=pytesseract.Output.DICT)
for i, text in enumerate(data['text']):
    conf = int(data['conf'][i])
    if conf > 0: # Skip whitespace
        print(f"Word: {text} | Confidence: {conf}%")

3. Performance Optimization

Pytesseract spawns a new process every time a function is called. If you run a loop of 1,000 images, the CPU will be overloaded. It is crucial to understand when to use multiprocessing vs. threading to optimize these heavy workloads.

Consider using concurrent.futures for multi-threading, using Dramatiq and Redis for background tasks, or switching to EasyOCR if you have a GPU and need faster processing speeds.

Final Thoughts

OCR is not a “plug-and-play” miracle. Instead of searching for a universal library, focus on cleaning your input data. A sharp, noise-free, horizontal black-and-white image will help Tesseract achieve maximum performance. If your project requires higher complexity, such as handwriting recognition, consider custom Deep Learning models or paid solutions.

Share: