InviolusOCR: tiny Turkish line OCR for microcontrollers

A compact convolutional CTC recognizer for one cropped line of printed Turkish text: 560,954 parameters and a 598 KB full-int8 TensorFlow Lite file that keeps almost all of the float model's accuracy. A half-width tiny variant is 171 KB.

Keywords: Turkish OCR, Türkçe OCR, tiny OCR, text recognition, TFLite, int8, TinyML, ESP32, microcontroller.

Türkçe özet: Mikrodenetleyiciler için tek satır Türkçe metin okuyan, 561 bin parametreli, int8 TFLite dosyası 598 KB olan küçük bir OCR modeli. int8 hâli, ayrılmış test satırlarının %77,3'ünü, gerçek sahne metni (TS-TR) kırpıntılarının %56,2'sini ve basılı belge satırlarının (donut) %46,5'ini harfi harfine doğru okuyor. 171 KB'lık küçük sürüm daha düşük doğrulukta çalışıyor (%64,4 / %32,6 / %20,1). Ticari olmayan kullanım içindir (CC BY-NC-SA 4.0).

This replaces inviolus/erdem-ocr-tr-tinyml (2026-10-09), whose int8 file lost most of its accuracy.

Files

File What it is
inviolusocr.keras Float model, input (batch, 32, 160, 1), output (batch, 40, 90) softmax
inviolusocr_int8.tflite Full-int8 export, input [1, 32, 160, 1] int8 (scale 0.007843, zero point -1), output [1, 40, 90] int8 (scale 0.00390625, zero point -128)
inviolusocr_tiny.keras, inviolusocr_tiny_int8.tflite Half-width variant, 144,330 parameters, same input, output and alphabet
alphabet.json Character set used to decode the output
benchmark.json, benchmark_T.json Scores for this model and the earlier versions; scores for tiny

Model

  • Input: one horizontal text line, grayscale, resized to 160×32, scaled to [-1, 1] (pixel / 127.5 - 1).
  • Architecture: 4 conv + batch-norm + pool blocks, a dense projection, then four residual dilated 1-D convolutions (dilation 1, 2, 4, 1) instead of an LSTM, and a dense softmax. Only ops that quantize cleanly to int8.
  • Output: 40 timesteps × 90 classes. Class 0 is padding, 1..88 are the characters of alphabet.json in order, 89 is the CTC blank.
  • Character set: digits, ASCII letters, ç ğ ı İ ö ş ü Ç Ğ Ö Ş Ü, space and .,:;!?-_/()'%. Up to 24 characters per line.
  • Not included: text detection. The line must already be cropped.

Usage

import json
import numpy as np
import tensorflow as tf

alphabet = json.load(open("alphabet.json", encoding="utf-8"))["alphabet"]
BLANK = len(alphabet) + 1

def preprocess(path):
    image = tf.io.decode_image(tf.io.read_file(path), channels=1, expand_animations=False)
    image = tf.image.resize(image, [32, 160])
    return (tf.cast(image, tf.float32) / 127.5 - 1.0).numpy()

def decode(ids):
    text, previous = [], None
    for i in ids:
        if i != previous and 0 < i < BLANK:
            text.append(alphabet[i - 1])
        previous = i
    return "".join(text)

# int8 TFLite (the file meant for devices)
interpreter = tf.lite.Interpreter(model_path="inviolusocr_int8.tflite")
interpreter.allocate_tensors()
inp, out = interpreter.get_input_details()[0], interpreter.get_output_details()[0]
scale, zero = inp["quantization"]
x = np.clip(np.round(preprocess("line.png") / scale + zero), -128, 127).astype(np.int8)
interpreter.set_tensor(inp["index"], x[np.newaxis])
interpreter.invoke()
print(decode(interpreter.get_tensor(out["index"])[0].argmax(-1)))

Results

Exact-line accuracy (the whole line correct), all models scored on the same rows: 12,093 held-out lines (synthetic, Zenodo, TS-TR, esengul3, STS-TR) and the 2,078-crop official TS-TR test split (real scene text, never trained on), plus 2,563 printed-document rows from the donut 200k test list.

Exact-line accuracy of the int8 files of the main and tiny models

Model, int8 file Params Size Held-out TS-TR Donut test
erdem-ocr 2026-10-09 (LSTM) 631,226 665 KB 32.9% 33.5% not measured
InviolusOCR 2026-10-31 (previous version here) 560,954 598 KB 78.4% 58.2% 9.3%
InviolusOCR 2026-11-01 (main) 560,954 598 KB 77.3% 56.2% 46.5%
InviolusOCR tiny 2026-11-02 144,330 171 KB 64.4% 32.6% 20.1%

Character error rate of the int8 files: main 10.0% held-out, 18.8% TS-TR, 12.6% donut; tiny 14.1%, 31.3%, 24.1%.

On-device measurement (ESP32-P4)

Measured on 2026-11-06 on one ESP32-P4 board (rev v1.3, 360 MHz, 32 MB flash, 32 MB PSRAM) with ESP-IDF 5.3.5 and esp-tflite-micro 1.4.2. Each row is the mean of 20 runs after 2 warm-up runs; the published files were measured byte for byte. One inference is one 160x32 grayscale line.

Model File Arena used Latency, arena in internal SRAM Latency, arena in PSRAM (200 MHz)
InviolusOCR tiny 171 KB 172 KB 413 ms 417 ms
InviolusOCR D 598 KB 339 KB 1365 ms 1387 ms

The weights stay in flash; the arena only holds the activations. Both models fit in internal SRAM.

What this does and does not show:

  • Random int8 input: this is speed and memory only. The output was not compared with the PC interpreter, so correctness on the device is unchecked.
  • Only the model inference is timed, not image decoding, resizing, CTC decoding.
  • It is an ESP32-P4, not an ESP32-S3. The numbers do not carry over to the S3, which has other cores.
  • Whether ESP-NN's accelerated kernels are active for the P4 was not checked, so the speed may improve.
  • One board. Source and raw output: benchmarks/esp32-p4.

Limitations

  • Printed text only; no handwriting, curved text or full pages.
  • Real scene text is still the weak spot (56% exact).
  • Licence plates are not read (2% exact on 250 plate crops in a first, unchecked test).
  • Speed and memory are measured on one ESP32-P4 only (see above); no ESP32-S3 or ESP32-C6 measurement yet.
  • Text detection and line cropping must be done before this model runs.

Training data

About 106,000 training crops. The main model was continued from earlier weights; tiny was trained from scratch for 260 minutes on CPU.

License

Because of the non-commercial (TS-TR, STS-TR) and share-alike (esengul3, donut) terms of the training data, this model is released under CC BY-NC-SA 4.0: non-commercial use only, credit this model and the datasets above, and share adaptations under the same license.

Training code (MIT): ErdemWilkinson/InviolusOCR.

Downloads last month
109
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support