Tech With Galvan
Back to Articles
Development3/24/2025 • 4 min read

Image to Text (OCR) App using Python and Streamlit

Extract text from images with Python, Streamlit, and Tesseract OCR — upload a screenshot or photo and get editable text instantly.

Galvan
Galvan

Founder & Creator

Introduction

Optical Character Recognition sounds like advanced AI, but with Tesseract — Google’s open-source OCR engine — it is a single function call away. This tutorial builds an app where you drop in a screenshot, scanned document, or photo of a page, and get clean, editable, copyable text out. It is the natural companion to the QR scanner: same upload-decode-display skeleton, but for human-readable text.

The one setup quirk: Tesseract is a native binary, not a pip package. Python talks to it through the pytesseract wrapper, so you install the engine separately (links below), then everything else is pure Python.

Features

  • Upload any image — PNG, JPG, or WebP screenshots and photos.
  • Instant extraction — full text with layout preserved line-by-line.
  • Language selection — English plus any Tesseract language packs you have.
  • Confidence scores — see how sure the engine is per word.
  • Copy-ready output — text in a code block with a built-in copy button.

Prerequisites

  • Python 3.8+ — from python.org.
  • Tesseract engine:
    • macOS: brew install tesseract
    • Ubuntu: sudo apt install tesseract-ocr
    • Windows: installer from the UB-Mannheim build
  • Python packages:
pip install streamlit pytesseract pillow

Step 1: Create the Script

Save as ocr_app.py:

import streamlit as st
import pytesseract
from PIL import Image
import io

st.set_page_config(page_title="OCR — Image to Text", page_icon="📝")
st.title("📝 Image to Text (OCR)")

uploaded = st.file_uploader("Upload an image with text", type=["png", "jpg", "jpeg", "webp"])
lang = st.selectbox("Language", ["eng", "hin", "spa", "fra", "deu"])

if uploaded:
    img = Image.open(io.BytesIO(uploaded.getvalue()))
    st.image(img, caption=f"{img.size[0]}×{img.size[1]} px", use_container_width=True)

    if st.button("🔍 Extract Text", type="primary"):
        with st.spinner("Reading the image..."):
            text = pytesseract.image_to_string(img, lang=lang)
            data = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)

        clean = [ln for ln in text.splitlines() if ln.strip()]
        st.subheader(f"Extracted {len(clean)} lines")
        st.code("\n".join(clean), language=None)

        confidences = [int(c) for c in data["conf"] if int(c) > 0]
        if confidences:
            avg = sum(confidences) / len(confidences)
            st.caption(f"Average confidence: {avg:.0f}%")

Step 2: Run the App

streamlit run ocr_app.py

Screenshot a paragraph of text, upload it, and hit Extract — you should get the words back nearly perfectly. Handwriting and stylized fonts are where confidence drops.

How It Works

Tesseract works in stages: binarize the image (text black, background white), find connected regions, group them into lines and words, then classify each glyph against its trained models. pytesseract.image_to_string wraps the whole pipeline and returns plain text; image_to_data returns the same run as a dictionary — including per-word bounding boxes and confidence values — which is where the average-confidence caption comes from.

Image quality dominates results. Sharp, high-contrast, straight-on images score 95%+; skewed phone photos of glossy pages drop fast. The single most effective preprocessing step, when needed, is converting to grayscale and boosting contrast with Pillow — the same transforms covered in the Pillow image processing tutorial.

The language code matters more than people expect: lang="eng" forces English glyph models. Passing "eng+hin" runs both model sets and merges results, which is how multilingual receipts get read correctly.

Common Errors & Fixes

  • TesseractNotFoundError — the engine binary is not on your PATH. On Windows, set pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe" at the top of the script.
  • Garbage output (~ and | everywhere) — the image is too low-contrast or too small. Upscale 2× with img.resize and convert to grayscale before OCR.
  • Selected language returns nothing — the language pack is not installed (brew install tesseract-lang on macOS, or tesseract-ocr-hin on Ubuntu).
  • Empty text from an obviously-texted image — inverted colors (dark mode screenshots). Flip with ImageOps.invert(img.convert("RGB")) before extraction.

Key Concepts

  • Engine vs wrapper — Tesseract is the binary; pytesseract is its Python remote control.
  • image_to_string vs image_to_data — plain text vs words-with-positions-and-confidence.
  • Preprocessing wins — grayscale, contrast, and upscaling beat any parameter tuning.
  • Language packs — glyph models per language, combinable with +.

What to Try Next

  • Draw bounding boxes on the image using image_to_data coordinates — the annotation pattern from the QR scanner applies directly.
  • Add a PDF input tab: rasterize pages with pdf2image, then OCR each — combine with the PDF merger for a full document toolkit.
  • Send extracted text to the sentiment analyzer for an end-to-end pipeline.
  • Add a search-in-image box that highlights matching words from the OCR data.

FAQ

How accurate is Tesseract?

On clean printed text, 95–99% character accuracy. Handwriting, cursive, and low-resolution photos drop sharply — for those, a deep-learning OCR (EasyOCR, PaddleOCR) does better at the cost of heavier installs.

Can it read tables?

It reads the text but flattens the structure. Use image_to_data coordinates to reconstruct columns, or try Tesseract’s TSV output for a starting grid.

Where does my image go?

Nowhere — decoding happens in your process via the local Tesseract binary. Like the PDF merger, the privacy story is the feature.