Amharic OCR & Document Intelligence
1. Morphological and Geometric Challenges
The Ethiopic writing system (an abugida or alphasyllabary) encodes consonants and vowels into unified syllographs across seven standard orders:
Order: 1st (ä) 2nd (u) 3rd (i) 4th (a) 5th (e) 6th (ə) 7th (o)
Syllable: በ ቡ ቢ ባ ቤ ብ ቦ
IPA: [bɐ] [bu] [bi] [ba] [be] [bɨ] [bo]
Large Inventory, Small Stroke Differences
An Ethiopic OCR model must distinguish over 300 syllabic glyphs, plus numerical ciphers (፩ to ፼) and punctuation delimiters (፡, ።, ፣, ፤).
Vowel changes appear as small variations on the base consonant:
- Foot appendages: Adding a small right-hand horizontal or vertical foot (e.g.,
በ→ቡin the 2nd order,ለ→ሉ). - Shortening/Extension of limbs: In the 3rd order, shortening the right limb and attaching a horizontal cross-stroke (e.g.,
በ→ቢ). - Base stroke modifications: Rounding, slanting, or modifying loops (e.g.,
በ→ቤin the 5th order,በ→ቦin the 7th order).
Confusion Set A (Intra-character order shifts):
በ (1st: [bɐ]) vs. ቤ (5th: [be]) vs. ቦ (7th: [bo])
ነ (1st: [nɐ]) vs. ኔ (5th: [ne]) vs. ኖ (7th: [no])
Confusion Set B (Inter-character structural twins):
ሀ (hä) vs. በ (bä) vs. ከ (kä)
አ (ʔä) vs. ዐ (ʕä) vs. የ (yä)
ገ (gä) vs. ጉ (gu) vs. ጎ (go)
In low-resolution scans, paper bleed, ink spread, and photocopy degradation can erase or connect these strokes.
2. Document Degradation
- Vellum Parchment (Branna) Manuscripts: Hand-scribed with carbon-black and cinnabar-red inks on sheepskin or goatskin. Non-uniform ruling, warping, ink oxidation, and historical Ge’ez abbreviations (seraye) challenge line segmentation.
- 20th Century Letterpress and Typewriters (Negarit Gazeta, 1940s–1980s): Mechanical wear caused ink spread and tilt. Glyphs often touch or fuse, which breaks heuristic character segmentation.
- Mimeographed & Thermal Invoices: Tax invoices, land registries, and clinic logs printed on thermal or thin paper with uneven toner density.
3. Architectures
Analytical Segmentation
Early Amharic OCR systems (Worku, Cowell, Assabie) used horizontal and vertical projection profiles to slice images into lines, words, and character bounding boxes. Multi-limb characters overlap vertically and letters touch, so segmentation errors compounded downstream.
CRNN (CNN + BiLSTM + CTC)
Connectionist Temporal Classification (CTC) removed the need for character segmentation. The model scans full text lines:
- A Convolutional Network (ResNet / VGG) extracts visual feature maps.
- A Bidirectional LSTM models horizontal sequence dependencies.
- A CTC Loss Layer computes the alignment between visual frames and the target text sequence.
Vision-Language Transformers (TrOCR)
Microsoft’s TrOCR combines a pre-trained Vision Transformer (ViT) image encoder with a language model decoder (e.g., RoBERTa or BART).
flowchart LR
A["Raw Document Image<br/>(Text Line / Crop)"] --> B["Image Preprocessing<br/>(Binarization & Dewarp)"]
B --> C["Vision Transformer (ViT)<br/>Patch Embeddings (16x16)"]
C --> D["Cross-Attention Layer"]
D --> E["Autoregressive Text Decoder<br/>(Ethiopic Subword BPE)"]
E --> F["Predicted Unicode Text<br/>'ኢትዮጵያ በታሪክ...'"]
The ViT processes image patches without horizontal slicing, and the decoder’s language model prior can use context to reconstruct smudged characters.
4. Implementation: Preprocessing and Tesseract
import cv2
import numpy as np
import pytesseract
class EthiopicDocumentOCR:
def __init__(self):
# Use Tesseract 5 LSTM engine with Amharic language pack
self.tess_config = r'--oem 1 --psm 6 -l amh'
def preprocess_document(self, image_path: str) -> np.ndarray:
"""Removes paper stains, corrects illumination, and binarizes degraded documents."""
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# Step 1: Remove illumination gradients via morphological closing
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))
background = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
normalized = cv2.divide(img, background, scale=255)
# Step 2: Otsu's adaptive thresholding with Gaussian blur
blurred = cv2.GaussianBlur(normalized, (3, 3), 0)
_, binarized = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
return binarized
def recognize_text(self, processed_img: np.ndarray) -> str:
return pytesseract.image_to_string(processed_img, config=self.tess_config)
# Demonstration
ocr_engine = EthiopicDocumentOCR()
preprocessed = ocr_engine.preprocess_document("legal_gazette_sample.png")
extracted_text = ocr_engine.recognize_text(preprocessed)
print("Extracted Amharic Text:\n", extracted_text)