Enterprise Document Text Extractor: OpenCV & EasyOCR Pipeline for EDMS Searchability
How I built an image preprocessing system using OpenCV and OCR (EasyOCR/Tesseract) to extract raw text content from physical documents for corporate document search.
Executive Summary
During my internship bootcamp at Berijalan Techno Center, I had the opportunity to contribute to the development of the company’s EDMS (Enterprise Document Management System) project, commercially known as Documend (https://berijalan.co.id/products/documend). One of the major functional challenges of the document management platform at the time was that uploaded physical documents (in the form of scanned photos or PDFs) were static, and their text contents were unsearchable. Users could only search files based on filenames or basic metadata.
Since there was no internal OCR API available at the time, I took the initiative to design and build a standalone Enterprise Document Text Extractor prototype pipeline using Python, OpenCV, and OCR engines (EasyOCR & Tesseract). This pipeline aimed to align, enhance the contrast of faded documents, extract text content asynchronously, and index the output for full-text search. Although this custom implementation was not integrated into the production EDMS product and remains my personal project, it demonstrates my deep understanding of computer vision and image data engineering.
Text Extraction Pipeline Architecture
The text extraction pipeline is designed to handle variations in physical documents uploaded by users under poor lighting conditions or skewed camera angles. The data flows sequentially:
graph TD
User[Physical Document / Faded Image] -->|Upload| Web[Upload Gateway]
subgraph Preprocessing [OpenCV Preprocessing Layer]
Web -->|1. Grayscale| Gray[Grayscale Conversion]
Gray -->|2. Deskew| Rotate[Deskewing via minAreaRect]
Rotate -->|3. Contrast Enhancement| CLAHE[CLAHE Adaptation]
CLAHE -->|4. Denoise| Blur[Bilateral Filter]
end
subgraph Extraction [OCR Inference Layer]
Blur -->|EasyOCR / Deep Learning| Easy[EasyOCR Engine]
Blur -->|Legacy Tesseract| Tess[Tesseract OCR Engine]
end
Easy -->|Output Text| Search[(Full-Text Search Index / EDMS)]
Tess -->|Output Text| Search
style Preprocessing fill:#f9f,stroke:#333,stroke-width:1px
style Extraction fill:#bfb,stroke:#333,stroke-width:1px
Image Engineering with OpenCV
Photos of documents that are dim, shadowed, or tilted are often completely unreadable by standard OCR engines. To optimize text readability, I implemented three main image preprocessing techniques:
- Deskewing (Rotation Correction):
Uses
cv2.minAreaRectto calculate the text skew angle on detected text regions, then rotates the image back to a straight alignment using an affinity transformation matrix (cv2.warpAffine) to correct text rotation. - CLAHE (Contrast Limited Adaptive Histogram Equalization): A local histogram equalization technique to adaptively enhance text contrast. CLAHE is highly effective for faded ID cards or receipts with uneven lighting or shadows.
- Bilateral Filtering: Smooths background noise (such as paper texture or document patterns) without dulling the edge sharpness of text characters. This is crucial because standard binarization often fragments letters if noise is too heavy.
Here is the Python implementation I designed for the document preprocessing stage:
import cv2
import numpy as np
def deskew_image(image):
"""
Applies skew correction (deskewing) using minAreaRect
to automatically detect text/document orientation.
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.bitwise_not(gray)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
coords = np.column_stack(np.where(thresh > 0))
if len(coords) == 0:
return image, 0.0
rect = cv2.minAreaRect(coords)
angle = rect[-1]
# Normalize rotation angle
if angle < -45:
angle = -(90 + angle)
else:
angle = -angle
if abs(angle) > 45:
return image, 0.0
(h, w) = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
return rotated, angle
def preprocess_clahe_denoised(img_gray):
"""
Enhances local contrast using CLAHE and suppresses background noise
using a Bilateral Filter without degrading character edge sharpness.
"""
# Apply CLAHE to enhance faded characters
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
enhanced = clahe.apply(img_gray)
# Bilateral filter to selectively reduce background noise
denoised = cv2.bilateralFilter(enhanced, 9, 75, 75)
return denoised
OCR Engine Comparison (EasyOCR vs Tesseract)
In evaluating text extraction performance for Indonesian and English on physical documents, I compared two OCR engine approaches:
- EasyOCR (Deep Learning based on PyTorch):
- Pros: Highly robust at recognizing irregular characters, non-standard fonts, and low-contrast images after CLAHE processing.
- Cons: Requires higher computing resources (CPU/GPU) compared to Tesseract.
- Tesseract OCR (Traditional Pattern Matching):
- Pros: Fast and efficient for clean, structured documents (such as scanner-scanned PDFs).
- Cons: Highly sensitive to background noise and document skew.
From test results on 100+ faded/skewed document samples:
- Without Preprocessing: Tesseract often produced empty text or gibberish symbols.
- With OpenCV Preprocessing Pipeline: The volume of correctly extracted characters by EasyOCR increased by up to 45% on shadowed document photos. Tesseract also showed significant legibility improvements once the rotation was deskewed.
Impact & Key Takeaways
- Self-Built EDMS Searchability: Proved the feasibility of full-text search in EDMS using OCR-indexed text without relying on external paid APIs.
- Image Engineering Competency: Strengthened my understanding of color space manipulation, image affinity transformations, local contrast histograms, and PyTorch-based deep learning library integrations in data engineering workflows.