/ Модель

Характеристики

Лицензия
Apache 2.0
Архитектура
vision-encoder-decoder · dense
Базовая модель
deit-small-patch16-224
Опубликована
7 окт 2026

ko-hand-ocr

Описание изображенийИзображение → текст

Компактная модель localdeel для распознавания корейских и английских рукописных строк по изображениям.

50скачиваний за 30 дней0лайковвеса обновлены12 окт 2026

Бенчмарки

Как модель справилась с задачами пользователей ailist и что о ней публикуют открытые лидерборды

Тесты пользователейпока нет

Никто ещё не опубликовал тест этой модели. Соберите бенчмарк из своих задач — результат появится здесь.

  1. Добавьте модель в бенчмарк

    Вместе с другими моделями категории «Распознавание текста» — сравнение идёт внутри одной категории.

  2. Опишите свои задачи

    Напишите запросы, при необходимости приложите файлы и эталонные ответы.

  3. Запустите и опубликуйте

    Модели ответят на каждую задачу, судья оценит ответы. Опубликованный результат появится на этой странице.

Публичные оценки

Из открытых лидербордов. Сравнивайте результаты одного теста при одинаковых условиях — отсутствие оценки не значит, что модель хуже.

В подключённых источниках результатов для этой модели пока нет.

Что умеет модель

Описание составлено по карточке модели и метаданным Hugging Face

ko-hand-ocr — компактная модель распознавания рукописных строк на корейском языке и английском, созданная разработчиком localdeel. Это дообученная архитектура Vision Encoder-Decoder на основе facebook/deit-small-patch16-224 в стиле TrOCR; модель содержит около 41 млн параметров и обучалась с нуля на синтетических изображениях без рукописного датасета. Она принимает изображение уже выделенной строки и возвращает распознанный текст. Для чтения целых страниц или фотографий требуется отдельная нарезка изображения на строки.

Модель рассчитана на быстрое пакетное распознавание рукописного текста, в том числе через сопровождающий Python-пакет, сервер API и MCP-инструмент. Автор сообщает о сравнительных испытаниях на отложенных шрифтах и фотографиях, но результаты относятся к конкретным данным и конфигурации теста. Модель не предназначена для печатных документов, таблиц или текста в сценах; для этих сценариев автор ожидает более низкое качество. Веса доступны по лицензии Apache-2.0.

Сценарии применения

  • распознавание корейского рукописного текста в изображениях строк
  • чтение рукописных строк со смесью корейского и английского
  • пакетное распознавание предварительно вырезанных строк
  • извлечение рукописных значений из форм после нарезки на строки

Ограничения

Модель распознаёт отдельные изображения строк, поэтому целые страницы и фотографии нужно предварительно нарезать; печатный текст, таблицы и текст в сценах не являются её целевыми задачами. Для работы нужны сопровождающий пакет ko-hand-ocr и его словарь символов; напрямую загрузить эту архитектуру в LM Studio, Ollama или vLLM нельзя.

Карточка модели

README от автора на Hugging Face, как он его опубликовал

Открыть на Hugging Face

ko-hand-ocr

한글·영문이 섞인 손글씨 한 줄을 읽는다. 손글씨 데이터셋 없이, 합성 그림만으로 처음부터 학습한 41M 모델. Apache-2.0 — 가중치도 같다.

GitHub · PyPI · 만든 과정 (PDF) · 한국어 · English

ko-hand-ocr 성적판

맨 위 표는 공개된 다른 OCR 여섯(ko-trocr · PaddleOCR PP-OCRv5 · EasyOCR · Tesseract 5 · PaddleOCR-VL-1.6 · Qwen3-VL-2B)과 같은 시험지·같은 PC·같은 순간에 견준 것이다 — 누구를 어떻게 돌렸는지는 아래. 그 아래는 우리 두 모델의 항목별 성적이고, tools/verify.py 로 글씨체마다 810~963줄씩 잰 값이다. 단일 모델은 41M 모델 하나로 읽는다. 앙상블은 모델 3개가 같이 읽고 서로 닮은 답을 고른다 — 더 정확하고 일곱 배 느리다. 초록 줄은 사진을 통째로 읽는 우리 VL 모델(0.8B)이다 — LM Studio·Ollama 에 그대로 올라간다(아래).

2026-10-08 에 시험지를 고쳤다. 동해독도·기랑해랑 시험지에 그 글꼴이 못 그리는 글자 (빈칸·□)가 든 줄이 섞여 있어서 다 맞혀도 97.5% 였다. 그 줄을 빼고 모두 다시 쟀다(나머지 네 벌은 그림까지 같다). 그래서 글씨체 숫자는 그 전 숫자보다 높게 나온다 — 모델이 바뀐 것이 아니다. 사진 숫자는 그대로다.

2026-10-08 에 시험지를 고쳤다. 동해독도·기랑해랑 시험지에 그 글꼴이 못 그리는 글자 (빈칸·□)가 든 줄이 섞여 있어서 다 맞혀도 97.5% 였다. 그 줄을 빼고 모두 다시 쟀다(나머지 네 벌은 그림까지 같다). 그래서 이 README 의 글씨체 숫자는 그 전 숫자보다 높게 나온다 — 모델이 바뀐 것이 아니다. 사진 숫자는 그대로다.

받기와 쓰기

이 저장소의 가중치는 GitHub v0.4.1 릴리스의 zip 과 같은 무게를 float16 으로 저장한 것이다(2026-10-12, 받는 크기가 반). 읽을 때는 설정대로 float32 로 올라간다. 시험 글씨체·두루 글씨체·사진 칸에서 단일 모델은 743줄 모두 답과 믿음값이 그대로였고, 앙상블은 731줄 가운데 한 줄이 다른 틀린 답으로 바뀌었다(정답 2025.12.06 을 2025.12.26 대신 2027.12.06 으로) — 점수는 소수 셋째 자리까지 같다. zip 과 sha256 이 같은 float32 원본은 이 저장소의 태그 v0.4.1 에 있다. 아래 「빠르기와 덩치」 표는 그 전에 잰 값이다 — 같은 날 ko-hand-ocr-vl 도 어휘를 잘라 853M -> 660M 이 됐다(답은 같다).

자리무엇크기
/ (맨 위)단일 모델 — 41M, 디코더 8층. 대부분 이것으로 충분하다78MB
ensemble/앙상블 — 모델 3개(36M + 41M + 41M)가 같이 읽고 서로 닮은 답을 고른다. 바닥을 올리고 7배 느리다225MB

무게만으로는 안 읽힌다. 자모 어휘와 줄 자르기가 kohandocr 꾸러미에 있으니 먼저 설치한다. transformers 의 pipeline 으로는 못 연다.

pip install ko-hand-ocr
from huggingface_hub import snapshot_download
from kohandocr.reader import Reader

# 단일 모델 (78MB)
folder = snapshot_download("localdeel/ko-hand-ocr", ignore_patterns=["ensemble/*", "assets/*"])
reader = Reader(folder, device="cpu")

# 사진 한 장을 통째로. 줄 자르기까지 해 준다.
reader.read_photo(open("scan.jpg", "rb").read())
# -> ['보안점검표', '부서 : 포테토뭉부서', '이름 : 감자밭']

# 이미 줄을 잘라 두었다면 그림(PIL)을 묶음으로 넘긴다.
reader.read([cell_a, cell_b, cell_c])
# 앙상블 (225MB) — ensemble/also/ 가 있으면 Reader 가 알아서 모델 3개로 읽는다
folder = snapshot_download("localdeel/ko-hand-ocr", allow_patterns=["ensemble/*"])
reader = Reader(folder + "/ensemble", device="cpu")

후보 목록 안에서만 고르게 가두는 both(), 읽은 값과 믿음값을 같이 주는 read_trust()(믿음값은 모델끼리 견주어 재므로 앙상블에서만 나온다) 같은 나머지 쓰는 법은 GitHub README 에 있다.

LM Studio · Ollama · vLLM 과 같이 쓰기

모델을 그대로 올리고 싶으면 ko-hand-ocr-vl 을 쓴다. Qwen3.5-0.8B 를 구조 그대로 한글 손글씨로 미세조정한 VL 모델이라 새 코드 없이 올라가고, 사진을 자르지 않고 통째로 읽는다. 설정(욕심 뽑기, 생각 끔)까지 같이 들어 있어서 받자마자 쓴다.

ollama run hf.co/localdeel/ko-hand-ocr-vl-GGUF:Q8_0      # Ollama
lms get https://huggingface.co/localdeel/ko-hand-ocr-vl-GGUF@q8_0   # LM Studio

대신 스무 배 크고(1.6GB) CPU 에서 일곱 배 느리다. 잘라 둔 줄을 빨리 많이 읽을 것이면 이 41M 이 낫다.

이 41M 모델은 그대로는 못 올린다. 셋은 글을 이어 쓰는 LLM 을 돌리는 엔진이다. LM Studio·Ollama 는 llama.cpp 의 GGUF 만, vLLM 은 자기가 아는 구조만 돌린다. 이 모델은 그림 인코더에 교차 주의 디코더를 단 TrOCR 꼴이고 자모 어휘와 줄 자르기가 따로 있어서, GGUF 로 바꿀 수도 그 목록에 넣을 수도 없다. 대신 그 엔진들과 같은 말(API)을 하는 서버와 MCP 도구를 넣었다.

pip install "ko-hand-ocr[mcp]"      # 서버만 쓸 거면 [mcp] 는 빼도 된다

서버 — OpenAI·Ollama 와 같은 말

ko-hand-ocr-serve                   # 허깅페이스에서 단일 모델(78MB)을 받아 127.0.0.1:8765 에 띄운다
ko-hand-ocr-serve --ensemble        # 앙상블(225MB)
부르는 쪽이렇게
openai 클라이언트, vLLM 을 부르던 코드base_url="http://127.0.0.1:8765/v1" 로 바꾼다. 그림은 image_url 에 base64 로
ollama CLIOLLAMA_HOST=127.0.0.1:8765 를 주고 ollama run ko-hand-ocr "C:\scan.jpg" · ollama list
curlcurl --data-binary @scan.jpg http://127.0.0.1:8765/read
import base64
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8765/v1", api_key="none")
picture = "data:image/jpeg;base64," + base64.b64encode(open("scan.jpg", "rb").read()).decode()
reply = client.chat.completions.create(model="ko-hand-ocr", messages=[
    {"role": "user", "content": [{"type": "image_url", "image_url": {"url": picture}}]}])
print(reply.choices[0].message.content)     # 줄마다 한 줄
  • 글로 물은 것은 쓰지 않는다. 그림을 줄로 잘라 읽고 줄마다 한 줄씩 돌려줄 뿐이다. 요약·정리는 못 한다 — 그건 채팅 모델 몫이다(아래 MCP).
  • 그림 주소(http://…)는 받으러 가지 않는다. base64 로 실어 보낸다. 아무 데도 나가지 않는다.
  • 기본은 이 PC(127.0.0.1)에서만 열린다. 비밀번호가 없으니 --host 0.0.0.0 은 믿는 망에서만.

LM Studio — MCP 도구로

LM Studio(0.3.17 부터)의 채팅 모델은 바깥 도구를 부를 수 있다(MCP). ~/.lmstudio/mcp.json 에:

{"mcpServers": {"ko-hand-ocr": {"command": "ko-hand-ocr-mcp"}}}

ko-hand-ocr-mcp 가 PATH 에 없으면 전체 경로를 적는다(가상환경의 Scripts\ko-hand-ocr-mcp.exe). 그 뒤 도구를 부를 줄 아는 채팅 모델에게 "C:\scan.jpg 읽고 표로 정리해 줘" 라고 하면, 모델이 read_handwriting 으로 글자를 받아 정리한다. 글자를 알아보는 것은 이 모델, 정리는 채팅 모델이다. 채팅 창에 붙인 그림은 도구에게 오지 않으므로 파일 경로로 준다. 처음 부를 때 모델을 받는다(78MB). 그다음은 사진 한 장에 1초 안팎이다.

확인한 것과 안 한 것. ollama CLI 0.34(list·show·run), 공식 openai 클라이언트(한 번에·흘려 받기), MCP SDK 2.3 손님으로 불러 보았다. LM Studio 채팅 창과 Open WebUI 화면 안에서 끝까지 해 본 것은 아직 아니다.

vLLM 자체에 넣으려면 플러그인으로 모델 구조를 새로 써야 한다. 41M 모델이라 GPU 서버로 얻을 것이 작아서 하지 않았다 — 위 서버가 vLLM 과 같은 OpenAI 주소를 낸다. vLLM 에 모델로 올릴 것이면 위 VL 모델이 Qwen3.5 구조 그대로다(우리는 Windows 라 vLLM 위에서는 직접 돌려 보지 않았다).

한눈에

읽는 것사진 속 손글씨 한 줄 (한글 + 영문)
모델ViT-Small 인코더(ImageNet, Apache-2.0) + 8층 TrOCR 디코더(처음부터 학습)
어휘자모 229개 — 음절 11,172자를 외우지 않는다. 못 본 글자도 쓴다
파라미터41M — ko-trocr(213.7M)의 1/5
가중치78MB 단일 모델 · 225MB 앙상블(모델 3개) (따로 받는다 — GitHub Releases · 허깅페이스)
꾸러미119KB — 코드만
붙이기OpenAI·Ollama 와 같은 말을 하는 서버(ko-hand-ocr-serve) · LM Studio 에 붙는 MCP 도구(ko-hand-ocr-mcp)
속도줄당 0.18초 · 사진 한 장 1.04초 — GPU 없이 CPU 만으로
메모리1.07GB 단일 모델 · 1.51GB 앙상블
성적학습에 안 쓴 글씨체 여섯 벌 평균 96.6% (앙상블 96.6%) · 가장 낮은 벌 92.7% (앙상블 93.1%) · 손글씨 사진 11장 평균 94.9% (앙상블 96.6%) · 17개 가운데 95% 넘김 12개 (앙상블 13개)
남과 견주면같은 시험지에서 공개 OCR 여섯 가운데 가장 잘 읽는 것보다 글씨체 6벌 +18.6%p(ko-trocr), 사진 +15.7%p(ko-trocr) — 단일 모델 기준
VL 판사진을 자르지 않고 통째로 읽는 0.8B 모델 ko-hand-ocr-vl — Qwen3.5-0.8B 를 구조 그대로 미세조정해 LM Studio·Ollama·llama.cpp 에 그대로 올라간다
학습 자료합성 — OFL 글꼴로 그때그때 그린다. 디스크에 남지 않는다
라이선스Apache-2.0, 가중치도 같다 — 자료에서 물려받는 약관이 없다
필요한 것Python 3.10+ · PyTorch 2.5+

되는 것 — 그리고 안 되는 것

된다

  • 사진 한 장을 통째로. 줄을 찾아 자르고 각각 읽는다 (read_photo)
  • 이미 잘린 줄 그림을 묶음으로 (read)
  • 가둬서 읽기 — 후보 목록 안에서만 고르게 하고, 자유롭게 읽은 것과 맞는지 알려 준다 (both)
  • 앙상블로 읽기 — 모델을 여럿 두고 서로 닮은 답을 고른다
  • 아무 데도 보내지 않는다. CPU 에서 완전히 혼자 돈다
  • LM Studio·Ollama·vLLM 쪽 도구와 붙는다 — 같은 말을 하는 서버와 MCP 도구(위 「LM Studio · Ollama · vLLM 과 같이 쓰기」)

안 된다

  • 표 안의 칸을 라벨과 값으로 갈라 주지 않는다. read_photo 는 줄까지만 자른다
  • 세로쓰기, 칸 병합은 다루지 않는다
  • 읽을 것이 없는 칸(전체가 낙서인 것)도 무언가 읽어 낸다

왜 만들었나

우리가 찾아본 가운데 한국어 손글씨를 노리고 공개된 모델은 ddobokki/ko-trocr 하나였는데, 학습 자료가 AI Hub 라 이용 목적·재배포에 제약이 붙는다. 사내 도구에 넣거나 공개 꾸러미로 배포하려면 걸린다. 두루 쓰는 OCR(PaddleOCR·EasyOCR·Tesseract)과 큰 VLM 은 손글씨 한 줄에서 크게 떨어진다 — 같은 시험지에서 그 가운데 가장 잘 읽는 것이 글씨체 6벌 78.2%, 사진 78.5% 다(아래). 그래서 이 일을 하는 모델을 처음부터 다시 만들었다 — 부품마다 출처를 따져 볼 수 있게.

다른 OCR 과 같은 자로 견주면

상대는 여섯이다. 한국어 OCR 로 사람들이 실제로 꺼내 쓰는 것을 갈래마다 세웠다.

상대갈래크기어떻게 돌렸나
ddobokki/ko-trocr한국어 TrOCR (AI Hub 로 배움)214Mtransformers, float32, 빔 5, 길이 상한 16 -> 64 로 풀어 줌
PaddleOCR PP-OCRv5줄 인식기 (korean_PP-OCRv5_mobile_rec)3.3Mpaddleocr 3.7.0 / paddle 3.4.0, TextRecognition
EasyOCR줄 인식기 (korean_g2)4.0Measyocr 1.7.2, Reader(["ko", "en"]).recognize()
Tesseract 5줄 인식기 (LSTM, 파라미터 수는 공개하지 않는다)파일 6MBtesseract 5.5.3, --oem 1 --psm 7 -l kor+eng
PaddleOCR-VL-1.6OCR 전용 VLM906Mtransformers 5.16.1, bfloat16, 물음 「OCR:」(모델 카드)
Qwen3-VL-2B-Instruct두루 쓰는 VLM2.1Btransformers 5.16.1, bfloat16, 한글 물음 — 셋을 대 보고 가장 잘 읽은 것

모두 모델 카드의 사용법 그대로다. 엔진마다 따로 깐 환경에서 돌리고 (tools/rival_worker.py), 시간은 그 엔진이 잰 값만 센다(그림을 넘기는 값은 뺀다).

견줌이 성립하려면 모델만 다르고 나머지는 같아야 한다. 사진은 우리 줄 자르기(kohandocr.page)로 자른 같은 칸을 모두에게 넣었고, 글씨체 시험지는 synth.render 가 그린 날것을 넣었다 — 우리 전처리(64×640 letterbox)를 씌워서 주면 상대만 두 번 찌그러진다. 자·GPU·잰 순간이 모두 같다. 무엇을 어떻게 쟀는지는 tools/vs.py 머리말에 다 적혀 있고, 아래 숫자는 전부 그것이 남긴 runs/VS.json 에서 나왔다.

얼마나 맞히나

실제 손글씨 사진 · 34줄 209글자

자모 닮음글자 오류율(CER)줄 통째로 일치가장 나쁜 장
ko-hand-ocr 앙상블 (118M)96.52%8.61%67.6%90.28%
ko-hand-ocr 단일 모델 (41M)94.24%13.40%61.8%83.03%
ko-hand-ocr-vl (853M)93.42%15.79%55.9%81.99%
ddobokki/ko-trocr (214M)78.50%39.23%26.5%58.56%
PaddleOCR PP-OCRv5 (3.3M)71.75%39.71%8.8%34.81%
Qwen3-VL-2B (2.1B)69.86%38.28%8.8%46.86%
PaddleOCR-VL-1.6 (906M)61.71%44.02%2.9%18.52%
EasyOCR (4.0M)66.88%55.02%2.9%46.19%
Tesseract 5 (6MB)13.21%98.56%0.0%0.00%

안 배운 글씨체 6벌 · 640줄 4448글자

자모 닮음글자 오류율(CER)줄 통째로 일치가장 나쁜 벌
ko-hand-ocr 앙상블 (118M)97.08%6.72%76.7%94.06%
ko-hand-ocr 단일 모델 (41M)96.80%7.17%74.5%93.14%
ko-hand-ocr-vl (853M)97.01%5.82%78.8%94.96%
ddobokki/ko-trocr (214M)78.24%42.31%29.5%65.93%
PaddleOCR PP-OCRv5 (3.3M)72.69%40.54%29.4%52.43%
Qwen3-VL-2B (2.1B)66.56%79.07%20.6%49.77%
PaddleOCR-VL-1.6 (906M)63.71%61.13%19.2%43.58%
EasyOCR (4.0M)56.63%64.32%9.7%35.70%
Tesseract 5 (6MB)17.73%97.35%0.8%6.90%

두루 읽나 — 24벌 · 634줄 3614글자

자모 닮음글자 오류율(CER)줄 통째로 일치가장 나쁜 벌
ko-hand-ocr 앙상블 (118M)97.73%3.21%87.5%89.83%
ko-hand-ocr 단일 모델 (41M)97.26%3.74%87.1%89.14%
ko-hand-ocr-vl (853M)97.38%4.01%85.5%92.24%
ddobokki/ko-trocr (214M)80.45%31.99%42.9%47.56%
PaddleOCR PP-OCRv5 (3.3M)75.29%30.02%40.7%19.38%
Qwen3-VL-2B (2.1B)67.97%63.25%26.8%23.91%
PaddleOCR-VL-1.6 (906M)66.45%45.99%25.2%27.29%
EasyOCR (4.0M)64.58%48.73%19.9%19.08%
Tesseract 5 (6MB)19.73%95.30%2.2%3.07%

글꼴 480벌 전부

글꼴 480벌 전부 · 벌당 12줄 4316줄 22532글자

고르기 6벌6벌두루 24벌24벌학습에 쓴 벌450벌전부480벌95% 넘긴 벌80% 밑인 벌
ko-hand-ocr 앙상블 (118M)99.15%99.00%98.24%98.29%437벌5벌
ko-hand-ocr 단일 모델 (41M)98.49%98.58%98.09%98.11%435벌4벌
ko-hand-ocr-vl (853M)99.26%98.69%97.93%97.98%429벌7벌
ddobokki/ko-trocr (214M)85.29%83.29%82.50%82.58%47벌158벌
PaddleOCR PP-OCRv5 (3.3M)80.07%78.48%78.05%78.09%86벌198벌
Qwen3-VL-2B (2.1B)65.50%67.42%69.34%69.20%52벌286벌
PaddleOCR-VL-1.6 (906M)——————
EasyOCR (4.0M)67.73%68.17%67.18%67.24%9벌319벌
Tesseract 5 (6MB)21.56%19.28%20.55%20.50%0벌480벌
  • 상대 여섯 가운데 가장 잘 읽는 것도 큰 차이로 뒤진다. 안 배운 글씨체 6벌에서 상대 으뜸은 ko-trocr 78.24%, 우리 단일 모델(41M)은 96.80% 다. 두루 24벌에서도 ko-trocr 80.45% 대 97.26%.
  • 사진 열한 장은 아무도 본 적이 없다. 거기가 가장 깨끗한 자리이고, 우리 앙상블 96.52% · 단일 모델 94.24% 대 상대 으뜸 ko-trocr 78.50% 다. 줄을 통째로 맞힌 비율은 67.6% 대 26.5%(상대 가운데 가장 높은 값).
  • 큰 VLM 이라고 손글씨 한 줄을 잘 읽지는 않는다. Qwen3-VL-2B(2.1B)는 6벌 66.56%, PaddleOCR-VL-1.6(906M)은 63.71% 다. 우리보다 스무 배에서 쉰 배 크지만 이 일에 맞춰 배운 모델이 아니다. 문서 전체를 읽는 일이라면 이야기가 다르다(재지 않았다).
  • Tesseract 는 손글씨를 거의 못 읽는다(6벌 17.73%, 사진 13.21%). 인쇄 글자용 엔진이라 예상한 대로다. 기준선으로 둔다.
  • 글꼴을 하나도 안 빼고 480벌을 읽혀도 우리 98.29%(앙상블) · 98.11%(단일 모델) 대 상대 으뜸 ko-trocr 82.58%. 우리가 본 적 없는 24벌(99.00%)이 학습에 쓴 450벌(98.24%)보다 높다 — 외워서 올라간 숫자가 아니라는 뜻이다. PaddleOCR-VL 은 가장 싼 묶음이 줄당 1초 안팎이라 480벌은 재지 않았다(표에서 빈칸).
  • 그래도 무너지는 벌이 있다. 480벌 가운데 우리 단일 모델의 가장 낮은 벌이 47.31%, 앙상블은 63.21% 다. 여섯 벌만 보면 이런 벌이 안 보인다. 벌당 12줄뿐이라 한 벌의 값은 크게 흔들리므로 무리 합계와 분포로 본다.
  • 단일 모델은 사진 바닥이 0.3.0 보다 낮다. 가장 나쁜 장이 hand-06 83.03% 다 — 세 줄짜리 장의 한 줄(전자금융TF서약)이다. 앙상블에서는 다른 모델이 받아 주어 90.28% 그대로다.
  • 사진 통째를 읽는 VL 모델도 같이 쟀다(초록). ko-hand-ocr-vl (0.8B)은 줄을 하나씩 읽혀도 글씨체 6벌 97.01% 로 단일 모델(96.80%)과 비슷하고 가장 낮은 벌은 더 높다(94.96 대 93.14%). 사진 줄 칸은 93.42% 로 조금 낮고, 스무 배 크고 CPU 에서 일곱 배 느리다. 그 모델의 쓰임은 줄을 자르지 않고 사진 통째를 읽는 것과 LM Studio·Ollama 에 그대로 올라가는 것이다. 사진 통째로는 그쪽 설명서에 상대 VL 들과 견준 값이 있다(쪽 단위 94.3% 대 72.7~79.9%).
  • 0.4.0 단일 모델(v83)과의 견줌은 아래 「얼마나 맞히나」에 새 시험지로 다시 잰 값으로 있다.

어디서 갈리나

갈래별 오류율

갈래별 글자 오류율 — 낮을수록 좋다

라틴 섞임60줄서식 라벨58줄숫자 섞임84줄한글 낱말·이름295줄한글 문장143줄
ko-hand-ocr 앙상블 (118M)10.6%6.9%8.9%3.1%6.9%
ko-hand-ocr 단일 모델 (41M)10.6%7.1%8.8%3.2%8.1%
ko-hand-ocr-vl (853M)8.7%7.1%9.5%3.6%4.0%
ddobokki/ko-trocr (214M)55.7%30.7%50.9%24.7%49.5%
PaddleOCR PP-OCRv5 (3.3M)51.2%36.2%44.4%32.4%42.1%
Qwen3-VL-2B (2.1B)58.3%92.9%51.9%90.8%89.7%
PaddleOCR-VL-1.6 (906M)76.6%65.3%46.9%77.2%50.6%
EasyOCR (4.0M)67.5%56.1%65.3%64.1%64.9%
Tesseract 5 (6MB)90.6%100.5%81.4%113.4%95.7%

길이별 오류율

줄 길이별 글자 오류율 — 낮을수록 좋다

1~5자279줄6~10자257줄11~20자98줄21자 이상6줄
ko-hand-ocr 앙상블 (118M)4.1%6.4%7.4%17.6%
ko-hand-ocr 단일 모델 (41M)4.0%7.6%7.2%17.6%
ko-hand-ocr-vl (853M)5.4%5.2%6.7%7.9%
ddobokki/ko-trocr (214M)29.4%36.0%52.3%94.9%
PaddleOCR PP-OCRv5 (3.3M)41.3%33.3%41.3%96.3%
Qwen3-VL-2B (2.1B)95.1%82.7%61.6%83.3%
PaddleOCR-VL-1.6 (906M)95.0%50.4%52.1%57.9%
EasyOCR (4.0M)69.4%59.1%62.6%98.1%
Tesseract 5 (6MB)113.5%97.4%84.8%102.3%
  • 라틴 약어에서 갈린다. 글자 오류율이 우리 10.6%(단일 모델) 대 상대 가운데 가장 나은 PaddleOCR PP-OCRv5 51.2% 다. 서식에 섞이는 영문은 대개 약어(TF, OCR, Codex)인데, 한글과 영문을 한 줄에서 같이 읽는 데서 갈린다.
  • 길수록 갈린다. 21자 이상에서 우리 17.6% 대 상대 가운데 가장 나은 PaddleOCR-VL-1.6 57.9%. ko-trocr 는 인코더가 384×384 정사각형이라 긴 줄을 통째로 눌러 넣는다. 우리는 64×640 이다. 다만 21자 이상은 6줄뿐이라 이 칸 하나로 결론 내면 안 된다 — 방향만 본다. 긴 줄과 문장은 우리 VL 모델이 더 낫다(21자 이상 7.9%, 한글 문장 4.0%).

무엇을 치렀나

빠르기와 덩치

빠르기와 덩치

파라미터받는 크기줄/초 (GPU)줄/초 (CPU)30줄 쪽 (GPU)30줄 쪽 (CPU)꼭대기 VRAM
ko-hand-ocr 앙상블 (118M)118M225MB8.21.054.04초29.1초1772MB
ko-hand-ocr 단일 모델 (41M)41M78MB42.06.761.12초4.8초1052MB
ko-hand-ocr-vl (853M)853M1666MB12.40.952.83초31.9초2601MB
ddobokki/ko-trocr (214M)214M408MB6.20.925.23초32.9초2744MB
PaddleOCR PP-OCRv5 (3.3M)3.3M13MB522.364.010.46초0.9초111MB
Qwen3-VL-2B (2.1B)2.1B4058MB10.50.603.27초50.4초4912MB
PaddleOCR-VL-1.6 (906M)906M1828MB1.40.0822.36초355.8초1831MB
EasyOCR (4.0M)4.0M15MB66.542.470.85초1.1초70MB
Tesseract 5 (6MB)—6MB—35.79—1.2초—
  • 가장 빠른 것은 우리가 아니다. CPU 에서 PaddleOCR PP-OCRv5 64.0줄/초, EasyOCR 42.5줄/초, Tesseract 5 35.8줄/초로 우리 단일 모델(6.8줄/초)보다 빠르다. 대신 6벌에서 PaddleOCR PP-OCRv5 72.7%, EasyOCR 56.6%, Tesseract 5 17.7% 를 읽는다. 맞히는 값과 빠르기를 같이 봐야 한다 — 서식에서 틀린 답은 빈칸보다 나쁘다.
  • 같은 일을 노린 ko-trocr(214M)와 견주면 CPU 에서 7.3배 빠르다(서른 줄 쪽 4.8초 대 32.9초). GPU 없는 사무용 PC 에 넣을 수 있느냐가 여기서 갈린다.
  • CPU 값은 이 노트북의 그때 상태를 크게 탄다. 2026-10-07 하루에 같은 칸·같은 방식으로 단일 모델이 4.6·3.1·2.6줄/초, ko-trocr 와의 배수가 6.3·5.9·4.4배로 움직였고, 이 판(2026-10-09)은 6.8줄/초·7.3배였다. 그래서 아래 「속도와 사양」(bench.py 로 따로 잰 것)의 줄당 0.18초와 숫자가 다르다. 줄/초 자체보다 같은 판 안의 순서를 보고, 배수는 4~7배로 읽는다 — ko-trocr 보다 빠르다는 것은 네 번 모두 같았다.
  • 큰 VLM 은 GPU 가 있어야 쓸 수 있다. CPU 에서 Qwen3-VL-2B 는 0.60줄/초, PaddleOCR-VL-1.6 은 0.08줄/초라 서른 줄 쪽이 50초, 356초다. GPU 에서도 10.5줄/초, 1.4줄/초로 우리 단일 모델(42.0줄/초)보다 느리다. CPU 에서 줄당 2초·12초라 1줄 묶음만 한 번 쟀다. 우리 VL 모델(0.8B)도 같은 처지다 — CPU 0.95줄/초, GPU 12.4줄/초.
  • 묶음 크기는 짐작하지 않고 잰다. 모델마다 가장 싼 묶음으로 읽혔다. 이 카드에 안 들어가는 묶음은 재지 않는다 — ko-trocr 의 32줄 묶음이 그렇고, 자가 적어 놓은 까닭은 「앞 묶음 4642MB(가중치 848MB)에서 활성값을 두 배 하면 8437MB 로 카드 8151MB 를 넘는다」이다. 안 잰 값을 잰 것처럼 적지 않는다.

같지 않은 것 — 어느 쪽에 유리한지까지 적는다

무엇이어떻게누구에게 유리
시험 글꼴여섯·스물네 벌은 우리 학습에서만 뺐다. 상대가 무엇을 보고 배웠는지 우리는 모른다상대
줄 자르기상대에게 우리 것을 빌려주었다(모두 한 줄 그림을 받는다)상대
길이 상한ko-trocr 의 기본은 max_length: 16(글자 단위). VLM 둘과 같이 64 토큰으로 풀어 주었다. Qwen3-VL 이 상한에 닿은 줄은 모두 되풀이로 넘친 줄(11 11 11 …)이었고 긴 정답이 잘린 줄은 없었다(2026-10-07 에 글씨체 670줄을 다시 읽혀 보니 상한에 닿은 24줄이 모두 그랬다)ko-trocr
물음Qwen3-VL 은 정해 둔 OCR 물음이 없어 셋을 대 보고 가장 잘 읽는 것을 주었다Qwen3-VL
정밀도VLM 셋(우리 VL 포함)은 나온 정밀도(bfloat16) 그대로, 나머지는 float32. ko-trocr 는 float16 빠르기도 따로 쟀다—
VLM 의 CPU줄당 몇 초에서 수십 초라 1줄 묶음을 한 번만 쟀다. 8줄로 묶으면 더 빠를 수도 있다ko-hand-ocr
480벌PaddleOCR-VL 은 줄당 1초 안팎이라 480벌 전부는 안 쟀다(빈칸)—
만든 목적줄 인식기 셋은 주로 인쇄·장면 글자, VLM 둘은 문서 전체, ko-trocr 는 AI Hub 의 손글씨·공공행정문서. 손글씨 한 줄 하나만 노리고 만든 것은 우리 쪽이다ko-hand-ocr
모델 수우리 앙상블은 모델 셋(36M 하나 + 41M 둘, 118M). 크기로 견주려면 「단일 모델」 줄을 본다ko-hand-ocr
학습에 쓴 글꼴480벌 가운데 450벌은 우리가 본 글씨체다. 그래서 무리를 갈라 적는다ko-hand-ocr
그리개우리는 시험지와 같은 그리개(synth.render)로 그린 합성 그림으로 배웠다(글꼴은 다르다). 글씨체 시험지의 생김새가 우리에게 낯익다 — 그래서 사진 쪽이 가장 깨끗한 견줌이다ko-hand-ocr

상대들이 나쁜 모델이라는 뜻이 아니다. 인쇄 서류·장면 글자·문서 전체를 읽으라고 만든 모델에 손글씨 한 줄을 들이민 것이고, 그 일에서는 이쪽이 낫다는 것뿐이다. 반대 방향(인쇄 서류 전체, 표, 장면 글자)은 재지 않았고, 재면 우리가 질 가능성이 크다.

잰 자리: NVIDIA GeForce RTX 5060 Laptop GPU · AMD Ryzen 7 260 · torch 2.14.0+cu130 · 2026-10-09 07:25. 2026-10-08 에 시험지를 고친 뒤 열 모델을 한 번에 다시 잰 것이다(VL 설명서에만 싣는 Qwen3.5-0.8B 원본은 이 표에서 뺐다). 사진 쪽은 그 전 판(2026-10-07)과 글자 하나까지 같다 — 사진은 고친 것이 없다. 빠르기는 그때그때 기계 상태를 타므로 같은 순간에 잰 것끼리만 견준다.

얼마나 맞히나

두 가지로 잰다. 둘 다 봐야 한다. 아래는 전부 python tools/verify.py 로 낸 값이다(글씨체 벌마다 810~963줄, 사진 11장).

안 배운 글씨체 (평균)가장 낮은 글씨체손글씨 사진 11장가장 나쁜 장95% 넘긴 항목
단일 모델 (0.4.1)96.6%92.7%94.9%84.2%12 / 17
앙상블 (모델 3개)96.6%93.1%96.6%90.3%13 / 17
0.4.0 단일 모델 (v83)96.4%92.6%95.0%83.0%11 / 17

글씨체는 벌마다 1,000줄을 뽑고, 그 글꼴이 못 그리는 글자가 든 줄을 뺀다 — 963줄, 동해독도 814줄, 기랑해랑 810줄이다. 기울임 줄은 지난 릴리스이고 같은 자로 다시 쟀다 (2026-10-09). 2026-10-08 에 시험지를 고쳐서 그 전에 낸 글씨체 숫자와는 견주지 않는다 — 고친 것은 맨 위에 적었다. 0.3.0 은 새 시험지로 다시 재지 않아서 표에서 뺐다 (그 전 시험지로 잰 견줌은 아래에 있다).

0.4.1 에서 바뀐 것은 단일 모델 하나다. 앙상블(모델 3개)이 다음 글자마다 내는 확률을 단일 모델이 따라 배우게 하고(증류), 그렇게 배운 모델을 0.4.0 의 v83 과 반씩 섞었다. 크기·구조가 그대로라 빠르기도 같다. 17개 가운데 95% 를 넘긴 것이 11 -> 12개(hand-07 94.4 -> 95.8%), 글씨체 여섯 벌이 모두 조금씩 올랐고(가장 낮은 벌 92.6 -> 92.7%), 가장 나쁜 장 hand-06 이 83.0 -> 84.2% 다. 대신 hand-08 이 100 -> 96.7% 로 내려갔다. 크게 나아진 것은 아니다 — 사진 평균은 95.0 대 94.9% 로 같다. 같은 방법을 세기만 바꿔 다시 해 보거나(몫 0.25·1.0), 드롭아웃·무게 지수 평균·SAM 으로 해 본 것은 모두 v83 을 못 넘었다.

0.4.0 은 0.3.0 에서 무엇이 나아졌나(그 전 시험지로 잰 값). 단일 모델(v83)은 글씨체 평균 94.6 -> 95.5%, 가장 낮은 벌 88.5 -> 90.1% 로 올랐다. 대신 사진 바닥이 내려갔다 (hand-06 90.3 -> 83.0%, 세 줄짜리 장의 한 줄). 앙상블은 바닥을 그대로 지키고 사진 평균이 95.8 -> 96.6% 로 올랐다. 17개 항목(글씨체 6 + 사진 11) 가운데 95% 를 넘긴 것이 앙상블 10 -> 12개다.

자모 단위 닮음이고, 사진 쪽은 사진 한 장에 한 표다.

사진 숫자를 그대로 기대하면 안 된다. 만든 사람의 사진 11장 35칸으로 잰 값이라 폭이 ±5%p 다. 남의 손씨에 더 가까운 값은 '안 배운 글씨체' 쪽이다 — 학습에 한 번도 안 쓴 글꼴 여섯 벌로만 시험한 것이라 폭이 절반이다(±2~3%p).

앙상블의 값은 평균이 아니라 바닥에 있다. 글씨체 평균은 이제 같은데 (96.6 대 96.6) 가장 낮은 글씨체가 92.7% 에서 93.1% 로 올라가고, 단일 모델이 84.2% 로 흘린 장(hand-06)이 90.3% 가 된다. 한 모델이 아슬아슬하게 읽는 칸을 다른 모델이 받아 주기 때문이다.

대신 모든 장이 좋아지지는 않는다. 단일 모델이 95.2% 로 넘긴 장이 92.9% 로 내려가기도 한다(hand-10) — 두 모델이 같이 틀리면 맞게 읽은 한 모델이 밀린다. 그리고 일곱 배 느리다. 한 장이라도 사람 손으로 넘어가면 곤란한 자리에는 앙상블을, 많은 양을 빨리 훑어야 하면 단일 모델을 쓴다.

글씨체마다 (벌마다 810~963줄)

글씨체마다의 정확도

합계 하나만 보면 무너지는 글씨체가 가려진다. 실제로 6%p 넘게 벌어진다.

글씨체줄단일 모델 (0.4.1)앙상블0.4.0 단일 모델 (v83)
동해독도 (EastSeaDokdo)81492.7%93.1%92.6%
하이멜로디 (HiMelody)96395.6%95.4%95.4%
나눔펜스크립트 (NanumPenScript)96395.9%95.9%95.7%
감자꽃 (GamjaFlower)96398.0%98.0%97.9%
기랑해랑 (KirangHaerang)81098.1%98.0%98.1%
싱글데이 (SingleDay)96399.0%98.9%98.9%

넘어야 하는 것은 평균이 아니라 가장 낮은 벌이다. 여섯 벌이 모두 90% 를 넘긴 뒤(2026-09-18) 목표를 95% 로 올렸다. 지금은 다섯이 넘고 동해독도가 남았다. 획이 뭉개지고 생략되는 손씨다. 기랑해랑은 시험지를 고치자 94.7 -> 98.1% 가 됐다 — 모델이 나아진 것이 아니라 그 글꼴이 네모(□)로 그리던 줄을 뺀 몫이다.

여섯 벌 다 학습에 한 번도 안 쓴 글꼴이고, 모두 SIL Open Font License 다.

python tools/holdout.py <모델폴더> --per-font --sample sample.png

더 보기

라이선스

Apache-2.0. 가중치도 같다. 학습에 쓴 글꼴의 라이선스는 가중치에 옮아붙지 않는다 — 가중치는 글자 모양의 저작물이 아니라 그림에서 배운 값이다. 근거는 PROVENANCE.md 에 부품별로 적어 두었다.


English

Reads one line of handwritten Korean + English. A 41M model trained from scratch on synthetic data — no handwriting dataset, no inherited terms. Apache-2.0, weights included. Full English README: README.en.md. The green row on the board is our VL model (0.8B), which reads whole photos and loads straight into LM Studio and Ollama.

The test sheets were fixed on 2026-10-08. The EastSeaDokdo and KirangHaerang sheets contained lines with characters those fonts cannot draw, capping them at 97.5%. Those lines are now dropped and everything was re-measured; font numbers are higher than before — the models did not change.

ko-hand-ocr scoreboard

Download and use

The weights here are the same weights as the zips on the GitHub v0.4.1 release, stored as float16 since 2026-10-12 (half the download). They load as float32 as the config says. On the test fonts, held-out fonts and photo line crops the single model gave the same answers and trust values on all 743 lines; the ensemble changed one line of 731 from one wrong reading to another (2025.12.06 read as 2027.12.06 instead of 2025.12.26), with scores equal to three decimals. The float32 originals (same sha256 as the zips) are at this repo's tag v0.4.1. The "speed and size" table below was measured before that; the same day ko-hand-ocr-vl also had its vocabulary cut, 853M -> 660M (same answers).

PathWhatSize
/ (root)Single model — 41M, 8-layer decoder. Enough for most uses78MB
ensemble/Ensemble — three models (36M + 41M + 41M) read together and the answer they agree on wins. Raises the floor, 7x slower225MB

The weights alone do not read anything: the jamo vocabulary and the line cutter live in the kohandocr package, so install it first. The transformers pipeline cannot open this model.

pip install ko-hand-ocr
from huggingface_hub import snapshot_download
from kohandocr.reader import Reader

folder = snapshot_download("localdeel/ko-hand-ocr", ignore_patterns=["ensemble/*", "assets/*"])
reader = Reader(folder, device="cpu")
reader.read_photo(open("scan.jpg", "rb").read())   # a whole photo: cuts lines, reads each

# ensemble
folder = snapshot_download("localdeel/ko-hand-ocr", allow_patterns=["ensemble/*"])
reader = Reader(folder + "/ensemble", device="cpu")

Use it with LM Studio, Ollama and vLLM

To load a model straight into them, use ko-hand-ocr-vl. It is Qwen3.5-0.8B fine-tuned on Korean handwriting with the architecture unchanged, so it loads with no new code and reads a whole photo without line cutting. Its settings (greedy decoding, thinking off) ship with it, so it works as soon as it is downloaded.

ollama run hf.co/localdeel/ko-hand-ocr-vl-GGUF:Q8_0      # Ollama
lms get https://huggingface.co/localdeel/ko-hand-ocr-vl-GGUF@q8_0   # LM Studio

It is twenty times larger (1.6 GB) and seven times slower on CPU. For reading many pre-cut lines quickly, this 41M model is the better fit.

This 41M model cannot be loaded into them as is. All three are engines for LLMs that continue text: LM Studio and Ollama run llama.cpp GGUF files, and vLLM runs the architectures it knows. This model is a TrOCR-style image encoder with a cross-attention decoder, plus its own jamo vocabulary and line cutter, so it neither converts to GGUF nor fits vLLM's list. Instead the package ships a server that speaks the same APIs and an MCP tool.

pip install "ko-hand-ocr[mcp]"      # drop [mcp] if you only need the server

Server — the OpenAI and Ollama APIs

ko-hand-ocr-serve                   # fetches the single model (78MB) from Hugging Face, serves 127.0.0.1:8765
ko-hand-ocr-serve --ensemble        # the ensemble (225MB)
CallerHow
openai client, code written for vLLMset base_url="http://127.0.0.1:8765/v1"; send the image as base64 in image_url
ollama CLIset OLLAMA_HOST=127.0.0.1:8765, then ollama run ko-hand-ocr "C:\scan.jpg" · ollama list
curlcurl --data-binary @scan.jpg http://127.0.0.1:8765/read
import base64
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8765/v1", api_key="none")
picture = "data:image/jpeg;base64," + base64.b64encode(open("scan.jpg", "rb").read()).decode()
reply = client.chat.completions.create(model="ko-hand-ocr", messages=[
    {"role": "user", "content": [{"type": "image_url", "image_url": {"url": picture}}]}])
print(reply.choices[0].message.content)     # one line per handwritten line
  • Text in the prompt is ignored. The image is cut into lines and each line comes back as one line of text. It cannot summarise or tidy up — that is the chat model's job (MCP, below).
  • Image URLs (http://…) are not fetched. Send base64. Nothing leaves the machine.
  • By default it listens on this PC only (127.0.0.1). There is no password, so use --host 0.0.0.0 only on a network you trust.

LM Studio — as an MCP tool

Since 0.3.17, chat models in LM Studio can call outside tools (MCP). In ~/.lmstudio/mcp.json:

{"mcpServers": {"ko-hand-ocr": {"command": "ko-hand-ocr-mcp"}}}

If ko-hand-ocr-mcp is not on PATH, give the full path (Scripts\ko-hand-ocr-mcp.exe in the virtual environment). Then ask a chat model that can call tools to "read C:\scan.jpg and put it in a table": it calls read_handwriting for the text and does the tidying itself. This model recognises the characters; the chat model organises them. An image pasted into the chat window goes to the chat model, not the tool, so pass a file path. The first call downloads the model (78MB); after that a photo takes about a second.

What was checked and what was not. The ollama CLI 0.34 (list, show, run), the official openai client (whole and streamed replies) and an MCP SDK 2.3 client were all run against it. A full run inside the LM Studio chat window or Open WebUI has not been done yet.

Putting it inside vLLM itself would mean writing the architecture as a plugin. For a 41M model a GPU server buys little, so that was not done — the server above exposes the same OpenAI address vLLM does. To serve a model on vLLM itself, the VL model above keeps the Qwen3.5 architecture (we build on Windows, so it has not been run on vLLM ourselves).

At a glance

What it readsOne line of handwritten Korean + English, from a photo
ModelViT-Small encoder (ImageNet, Apache-2.0) + 8-layer TrOCR decoder trained from scratch
Vocabulary229 jamo tokens — not 11,172 syllables, so unseen characters are still writable
Parameters41M — one fifth of ko-trocr (213.7M)
Weights78 MB single model · 225 MB ensemble (3 models) (downloaded separately — GitHub Releases · Hugging Face)
Package119 KB — the code only
Plugs intoa server that speaks the OpenAI and Ollama APIs (ko-hand-ocr-serve) · an MCP tool for LM Studio (ko-hand-ocr-mcp)
Speed0.18 s per line · 1.04 s for a whole photo — CPU only, no GPU
Memory1.07 GB single model · 1.51 GB ensemble
Accuracy96.6% mean on six handwriting fonts never seen in training (96.6% ensemble) · worst font 92.7% (93.1% ensemble) · 11 handwriting photos 94.9% (96.6% ensemble) · 12 of 17 items over 95% (ensemble 13)
Against othersOn the same sheets, ahead of the best of six public OCR models by +18.6 points on the six fonts (ko-trocr) and +15.7 points on the photos (ko-trocr) — single model
VL siblingko-hand-ocr-vl, a 0.8B model that reads whole photos without line cutting — Qwen3.5-0.8B fine-tuned with the architecture unchanged, so it loads straight into LM Studio, Ollama and llama.cpp
Training dataSynthetic — drawn on the fly from OFL fonts. Nothing is stored on disk
LicenseApache-2.0, weights included — no dataset terms inherited
Python3.10+ · PyTorch 2.5+

Against six other OCR models, same sheets, same PC, same moment

RivalKindSizeHow it was run
ddobokki/ko-trocrKorean TrOCR (trained on AI Hub)214Mtransformers, float32, beam 5, length cap raised 16 -> 64
PaddleOCR PP-OCRv5line recogniser (korean_PP-OCRv5_mobile_rec)3.3Mpaddleocr 3.7.0 / paddle 3.4.0, TextRecognition
EasyOCRline recogniser (korean_g2)4.0Measyocr 1.7.2, Reader(["ko", "en"]).recognize()
Tesseract 5line recogniser (LSTM, no parameter count published)6MB filetesseract 5.5.3, --oem 1 --psm 7 -l kor+eng
PaddleOCR-VL-1.6OCR-specific VLM906Mtransformers 5.16.1, bfloat16, prompt "OCR:" (model card)
Qwen3-VL-2B-Instructgeneral VLM2.1Btransformers 5.16.1, bfloat16, Korean prompt — the best of three we tried

Every rival runs exactly as its model card says, each in its own environment. Photos were cut once with our line cutter and the same cells went to everyone; font sheets were handed over raw.

Real handwriting photos · 34 lines, 209 characters

Jamo similarityCharacter error rateExact line matchWorst photo
ko-hand-ocr ensemble (118M)96.52%8.61%67.6%90.28%
ko-hand-ocr single model (41M)94.24%13.40%61.8%83.03%
ko-hand-ocr-vl (853M)93.42%15.79%55.9%81.99%
ddobokki/ko-trocr (214M)78.50%39.23%26.5%58.56%
PaddleOCR PP-OCRv5 (3.3M)71.75%39.71%8.8%34.81%
Qwen3-VL-2B (2.1B)69.86%38.28%8.8%46.86%
PaddleOCR-VL-1.6 (906M)61.71%44.02%2.9%18.52%
EasyOCR (4.0M)66.88%55.02%2.9%46.19%
Tesseract 5 (6MB)13.21%98.56%0.0%0.00%

Six unseen handwriting fonts · 640 lines, 4448 characters

Jamo similarityCharacter error rateExact line matchWorst font
ko-hand-ocr ensemble (118M)97.08%6.72%76.7%94.06%
ko-hand-ocr single model (41M)96.80%7.17%74.5%93.14%
ko-hand-ocr-vl (853M)97.01%5.82%78.8%94.96%
ddobokki/ko-trocr (214M)78.24%42.31%29.5%65.93%
PaddleOCR PP-OCRv5 (3.3M)72.69%40.54%29.4%52.43%
Qwen3-VL-2B (2.1B)66.56%79.07%20.6%49.77%
PaddleOCR-VL-1.6 (906M)63.71%61.13%19.2%43.58%
EasyOCR (4.0M)56.63%64.32%9.7%35.70%
Tesseract 5 (6MB)17.73%97.35%0.8%6.90%

Speed and size

ParametersDownloadlines/s (GPU)lines/s (CPU)30-line page (GPU)30-line page (CPU)Peak VRAM
ko-hand-ocr ensemble (118M)118M225MB8.21.054.04s29.1s1772MB
ko-hand-ocr single model (41M)41M78MB42.06.761.12s4.8s1052MB
ko-hand-ocr-vl (853M)853M1666MB12.40.952.83s31.9s2601MB
ddobokki/ko-trocr (214M)214M408MB6.20.925.23s32.9s2744MB
PaddleOCR PP-OCRv5 (3.3M)3.3M13MB522.364.010.46s0.9s111MB
Qwen3-VL-2B (2.1B)2.1B4058MB10.50.603.27s50.4s4912MB
PaddleOCR-VL-1.6 (906M)906M1828MB1.40.0822.36s355.8s1831MB
EasyOCR (4.0M)4.0M15MB66.542.470.85s1.1s70MB
Tesseract 5 (6MB)—6MB—35.79—1.2s—
  • We are not the fastest. On CPU, PaddleOCR PP-OCRv5 (64.0 lines/s), EasyOCR (42.5 lines/s) and Tesseract 5 (35.8 lines/s) beat our single model (6.8 lines/s). They read PaddleOCR PP-OCRv5 72.7%, EasyOCR 56.6%, Tesseract 5 17.7% of the six fonts. Read accuracy and speed together — on a form a wrong answer is worse than a blank.
  • Against a model of similar purpose, ko-trocr (214M), we are 7.3× faster on CPU (a thirty-line page in 4.8s against 32.9s). Whether it fits on an office PC with no GPU is decided here.
  • CPU figures ride hard on this laptop's state at the time. On 2026-10-07, with the same cells and method, the single model ran at 4.6, 3.1 and 2.6 lines/s and its lead over ko-trocr moved through 6.3×, 5.9× and 4.4×; this run (2026-10-09) gave 6.8 lines/s and 7.3×. That is why the 0.18 s per line under "Speed and footprint" below (bench.py, measured separately) does not match. Read the order within one run rather than lines/s itself, and the lead as 4–7× — being faster than ko-trocr held all four times.
  • The large VLMs need a GPU. On CPU Qwen3-VL-2B does 0.60 lines/s and PaddleOCR-VL-1.6 0.08, so a thirty-line page takes 50s and 356s. Even on GPU they run at 10.5 and 1.4 lines/s, slower than our single model (42.0). On CPU they take 2s and 12s per line, so only the one-line batch was timed, once. Our VL model (0.8B) is in the same position — 0.95 lines/s on CPU, 12.4 on GPU.
  • Batch size is measured, not guessed. Each model reads at its own cheapest batch. A batch that does not fit this card is not timed at all — ko-trocr's 32-line batch is one, and the tool's own reason reads: "앞 묶음 4642MB(가중치 848MB)에서 활성값을 두 배 하면 8437MB 로 카드 8151MB 를 넘는다". A number that was not measured is not written down as if it were.

What is not equal — and which side it favours

WhatHowFavours
Test fontsThe six and the twenty-four were held out of our training only. What the rivals were trained on is unknown to usrivals
Line cuttingOur cutter was lent to every rival (they all receive one line image)rivals
Length capko-trocr ships max_length: 16 (character tokenizer). Like the two VLMs it was given 64 tokens. Every line where Qwen3-VL hit the cap was a runaway repeat (11 11 11 …); no long answer was cut short (re-reading the 670 font lines on 2026-10-07, all 24 that hit the cap were like that)ko-trocr
PromptQwen3-VL has no fixed OCR prompt; three were tried and the one it reads best with was usedQwen3-VL
PrecisionThe three VLMs (ours included) run in their released precision (bfloat16), the rest in float32. ko-trocr's float16 speed was also timed—
VLM on CPUSeconds to tens of seconds per line, so only the one-line batch was timed, once. An 8-line batch might be fasterko-hand-ocr
All 480 fontsPaddleOCR-VL takes about a second per line, so it was not run on all 480 (blank)—
Design intentThe three line recognisers target mainly printed and scene text, the VLMs whole documents, ko-trocr AI Hub's handwriting and administrative documents. Only this model targets a single handwritten lineko-hand-ocr
Number of modelsOur ensemble is three models (one 36M + two 41M, 118M). For a size-matched view read the "single model" rowko-hand-ocr
Training fonts450 of the 480 fonts are ones we have seen. That is why the herds are reported apartko-hand-ocr
RendererWe trained on synthetic images from the same renderer as the font sheets (synth.render, different fonts). The look of the font sheets is familiar to us — which is why the photos are the cleanest comparisonko-hand-ocr

None of this says the rivals are bad models. Models built for printed documents, scene text or whole pages were handed a line of handwriting, and on that job this one does better. The other direction — whole printed documents, tables, scene text — was not measured, and if it were, this model would probably lose.

Measured on: NVIDIA GeForce RTX 5060 Laptop GPU · AMD Ryzen 7 260 · torch 2.14.0+cu130 · 2026-10-09 07:25 — all ten models re-run in one go after the test sheets were fixed on 2026-10-08 (the base Qwen3.5-0.8B, compared only on the VL card, is left out of this table). The photo results match the previous run (2026-10-07) to the character — nothing about the photos changed. Speed rides on the machine's state, so compare only figures taken at the same moment.

Как запустить

На своей GPU в ailist. Цена — по текущим предложениям провайдеров

GPU в ailist

Универсальный рантайм

Можно запустить

Подбираем GPU…

Класс архитектуры
VisionEncoderDecoderModel
Ревизия весов
2637245

0 моделей