Marathi, spoken by more than 80 million people, uses the Devanagari script with several languagespecific modifications. Automatic recognition of Marathi characters is essential for digitizing printed material, enabling realtime translation, and building assistive technologies for visually impaired users. In the past decade, deep learningparticularly convolutional neural networks (CNNs)has become the dominant approach for optical character recognition (OCR). This page explains the problem domain, data preparation, model design, training strategies, and evaluation techniques that are most effective for Marathi character recognition.
The Marathi alphabet consists of:
Because of these combinatorial possibilities, a single word can contain dozens of distinct glyphs. A robust recognizer must therefore handle:
Publicly available sources include:
python-pil or opencv).def preprocess(img): # 1. Convert to grayscale gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. Binarise with Otsu threshold _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 3. Remove small noise kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3)) clean = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) # 4. Deskew coords = np.column_stack(np.where(clean > 0)) angle = cv2.minAreaRect(coords)[-1] if angle < -45: angle = -(90 + angle) else: angle = -angle (h, w) = clean.shape M = cv2.getRotationMatrix2D((w//2, h//2), angle, 1.0) deskewed = cv2.warpAffine(clean, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE) # 5. Resize to 32x32 while keeping aspect ratio resized = cv2.resize(deskewed, (32, 32), interpolation=cv2.INTER_AREA) return resized The above routine produces a uniform 3232 binary image suitable for feeding into small CNNs.
For isolated character classification, a shallow CNN often outperforms deeper models because the input resolution is low and the number of classes (~60) is manageable.
model = tf.keras.Sequential([ tf.keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=(32,32,1)), tf.keras.layers.MaxPooling2D(2,2), tf.keras.layers.Conv2D(64, (3,3), activation='relu'), tf.keras.layers.MaxPooling2D(2,2), tf.keras.layers.Conv2D(128, (3,3), activation='relu'), tf.keras.layers.Flatten(), tf.keras.layers.Dense(256, activation='relu'), tf.keras.layers.Dropout(0.5), tf.keras.layers.Dense(num_classes, activation='softmax')]) Key design choices:
BatchNormalization after each convolution can improve convergence.If larger datasets are unavailable, pretrained models such as MobileNetV2 or EfficientNetB0 can be finetuned on 3232 grayscale images (after upsampling to the required input size). The advantage is faster convergence and better generalisation, especially when dealing with complex ligatures.
Besides overall accuracy, the following metrics are useful for a script with many similar shapes:
Pure visual recognition rarely achieves >95% accuracy on realworld documents because many glyphs look alike. Integrating a language model (LM) dramatically improves results:
Mobile or embedded platforms (Android, Raspberry Pi) benefit from model quantisation (int8) and pruning. TensorFlow Lite or ONNX Runtime enables sub100ms inference for a single character.
When processing full pages, it is common to split the pipeline:
import tensorflow as tf, cv2, numpy as np, json, glob# Load class mapwith open('marathi_map.json') as f: class_map = json.load(f)# Build model (use the baseline architecture)model = build_cnn(num_classes=len(class_map))model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])# Prepare data generatorstrain_datagen = tf.keras.preprocessing.image.ImageDataGenerator( rescale=1./255, rotation_range=15, width_shift_range=0.1, height_shift_range=0.1, shear_range=0.15, zoom_range=0.1, horizontal_flip=False)train_gen = train_datagen.flow_from_directory( 'data/train', target_size=(32,32), color_mode='grayscale', batch_size=64, class_mode='categorical')val_datagen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255)val_gen = val_datagen.flow_from_directory( 'data/val', target_size=(32,32), color_mode='grayscale', batch_size=64, class_mode='categorical')# Traincallbacks = [ tf.keras.callbacks.EarlyStopping(patience=8, restore_best_weights=True), tf.keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=3)]model.fit(train_gen, epochs=50, validation_data=val_gen, callbacks=callbacks)# Inference on a new imagedef recognise_character(img_path): img = cv2.imread(img_path) proc = preprocess(img) / 255.0 proc = np.expand_dims(proc, axis=[0,-1]) # shape (1,32,32,1) probs = model.predict(proc)[0] top3 = probs.argsort()[-3:][::-1] predictions = [(class_map[str(i)], probs[i]) for i in top3] return predictionsprint(recognise_character('samples/sample1.png')) This script demonstrates loading a classtoUnicode map, training with augmentation, and performing inference on a single character image.
By combining a welldesigned CNN, robust data augmentation, and contextual language modelling, it is possible to achieve highaccuracy Marathi character recognition suitable for both academic research and productiongrade OCR services.
