Text Type Classifier (Handwritten vs. Printed) β ResNet-18
A lightweight binary image classifier that distinguishes handwritten from printed text on cropped text-line images. Built as a preprocessing step for an HTR (Handwritten Text Recognition) pipeline, where scans mix typewritten/printed content with handwriting, and each type needs to be routed to a different recognition model.
Trained on text-line crops in modern Russian, pre-reform (old orthography) Russian, Belarusian, English, Polish, German, and other European languages.
Developed as part of the Zhnivo initiative β a project building genealogical databases from Belarusian archival records.
Training Data
~73,000 text-line crops from scanned archival documents:
| Split | Handwritten | Printed | Total |
|---|---|---|---|
| Train | 25,665 | 36,277 | 61,942 |
| Validation | 4,544 | 6,506 | 11,050 |
Model Details
- Architecture: ResNet-18 (torchvision), final fully-connected layer replaced with a 2-class head
- Task: Binary image classification
- Classes:
0 β handwritten,1 β printed - Input: RGB text-line crop, resized to 128Γ512 (HΓW), normalized with ImageNet mean/std
- Framework: PyTorch / torchvision
- File:
resnet18_text_classifier_final.pth(state dict)
Intended Use
- Dataset preparation for OCR/HTR training β automatically splitting large collections of text-line crops into handwritten and printed subsets when building training datasets for TrOCR, Tesseract, PaddleOCR, and similar recognition systems. This removes the need for manual sorting and keeps each recognizer's training data clean of the other text type.
- Inference-time routing β sorting text-line crops (e.g., produced by a text-detection stage) into handwritten and printed streams so each can be sent to a specialized recognizer.
- Corpus analysis β estimating the share of handwritten vs. printed content in scanned document collections.
Trained on line-level crops from scanned archival documents; works best on horizontal text-line images rather than full pages or isolated characters.
Usage
import torch
import torch.nn as nn
from torchvision import models, transforms
from PIL import Image
CLASS_MAP = {0: "handwritten", 1: "printed"}
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = models.resnet18(weights=None)
model.fc = nn.Linear(model.fc.in_features, 2)
model.load_state_dict(torch.load("resnet18_text_classifier_final.pth", map_location=device))
model.to(device).eval()
transform = transforms.Compose([
transforms.Resize((128, 512)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
img = Image.open("line_crop.jpg").convert("RGB")
with torch.no_grad():
logits = model(transform(img).unsqueeze(0).to(device))
pred = logits.argmax(1).item()
print(CLASS_MAP[pred])
Limitations
- Trained on European languages in Cyrillic and Latin scripts; accuracy may drop on other scripts (e.g., CJK, Arabic) or low-quality photos.
- Expects text-line crops; full-page images or non-text regions are out of scope.
- Binary output only β mixed lines (printed form with handwritten fill-in within one crop) are assigned to a single class.
Files
| File | Description |
|---|---|
resnet18_text_classifier_final.pth |
Final model weights (state dict) |
best_model.pth |
Best checkpoint by validation loss |
class_indices.json |
Class-to-index mapping |
loss_history.png |
Training loss curves |
License
MIT β free to use, modify, and redistribute, including commercially. The ResNet-18 architecture and torchvision implementation are BSD-3-Clause licensed (PyTorch/torchvision), which is compatible with this release.