/ Модель

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

Контекст
128 000 токенов
Параметры
508 млн
Лицензия
Apache 2.0
Архитектура
qwen2_5_vl · dense
Библиотека
transformers
Опубликована
14 авг 2026

NaviDC-OCR

Генерация текстаТекст → текст10 профессий

Компактная Vision-Language-модель для извлечения текста и структуры из цифровых документов и их фотографий.

16 611скачиваний за 30 дней30лайковвеса обновлены3 сен 2026

Бенчмарки

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

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

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

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

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

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

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

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

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

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

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

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

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

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

NaviDC-OCR — специализированная компактная мультимодальная модель StarDoc-AI для разбора цифровых документов и фотографий документов. Она построена на архитектуре Qwen2.5-VL и предназначена для извлечения текста и структуры, включая таблицы, формулы и порядок чтения. Метаданные указывают 507,9 млн параметров и контекст до 128 000 токенов; README приводит оценку размера 1,2 млрд параметров, поэтому сведения о размере расходятся.

Модель принимает изображения документов и формирует текстовый результат разбора. В README заявлена устойчивость к геометрическим искажениям фотографий без предварительного выравнивания, а также приведены результаты на нескольких бенчмарках. Карточка позиционирует её как лёгкую модель для практического развёртывания; фактическое качество может зависеть от типа документа и условий съёмки. Поддерживаемые языки по метаданным — китайский, английский и японский. Лицензия — Apache-2.0.

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

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

Ограничения

В README размер модели указан как около 1,2 млрд параметров, тогда как метаданные сообщают 507,9 млн. Результаты приведены на бенчмарках; качество на конкретных документах может отличаться.

Для каких профессий подходит10

Разделы каталога профессий, в задачах которых используется эта модель

Все профессии

Также подходит для

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

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

Открыть на Hugging Face

License GitHub Papers with Code: SOTA on OmniDocBench v1.6

🔥 News

  • 2026/09/01 - We noticed that EMNLP 2026 is hosting the Dr.DocBench Challenge, a document parsing competition. We evaluated NaviDC-OCR with its native weights, achieving better results than MinerU 2.5 Pro and PaddleOCR-VL 1.6. Detailed results are shown below dr.docbench-challenge. We welcome the use of NaviDC‑OCR for competitions. Going forward, we will continue to deliver competitive parsing models for the community.
  • 2026/08/29 — Thanks to Nandraj for the GGUF conversion and llama.cpp support! 🔗 NaviDC-OCR-GGUF
  • 2026/08/17 — NaviDC-OCR model weights and technical report have been released.

📖 Introduction

NaviDC-OCR is a lightweight (~1.2B parameters), open-source Vision-Language Model designed specifically for document parsing.

Unlike existing methods that mainly target either digital documents or camera-captured documents, NaviDC-OCR unifies both scenarios within a single framework.

Compared with previous document parsing models, NaviDC-OCR introduces

  • Multi-node Consensus Voting (MCV) for automatic pseudo-label generation
  • Geometry-aware document modeling for camera-captured documents
  • Curvature-Guided Douglas-Peucker Sampling (CGDP)
  • Image-to-image self-verification for automatic data refinement
  • Progressive four-stage training pipeline
  • Content-Structure Decoupled Learning for tables and formulas

These techniques enable NaviDC-OCR to achieve state-of-the-art performance on both digital and camera-captured document benchmarks while remaining lightweight enough for practical deployment.

📊 Experimental Results

NaviDC-OCR achieves state-of-the-art performance on multiple public document parsing benchmarks.

Layout Visualization of Distorted Documents

To evaluate the model's ability to understand complex document deformations, we conduct a visual evaluation on the public dewarping datasets DocUNet and DIR300, with representative results shown in Figure. NaviDC-OCR directly performs layout and content parsing on distorted documents without dewarping preprocessing or a dedicated rectification model, demonstrating robust parsing under complex geometric deformations.


Dr.DocBench Challenge

模型overall ↑Text edit ↓formula cdm ↑Table teds ↑order edit ↓
Specialized VLMs
Navidc‑OCR67.960.19030.0264.970.398
Mineru 2.5 pro62.260.34020.0467.750.356
OvisOCR259.250.38830.0061.590.3791
PaddleOCRvl 1.655.110.43640.2151.340.412

OmniDocBench v1.6

Model TypeMethodsParamOverall ↑Text Edit ↓Formula CDM ↑Table TEDS ↑Table TEDS-S ↑Read Order Edit ↓
Specialized VLMsNaviDC-OCR1.2B96.870.02796.3697.0598.520.122
OvisOCR20.8B96.580.02597.5394.7697.160.111
PaddleOCR-VL-1.60.9B96.330.03397.4994.7697.110.127
MinerU2.5-Pro1.2B95.750.03697.4593.4295.920.120
GLM-OCR0.9B95.220.04497.1892.8395.390.133
PaddleOCR-VL-1.50.9B94.870.03896.6991.6794.370.130
HunyuanOCR-1.51B94.740.03397.4994.7697.110.127
PaddleOCR-VL0.9B94.110.04095.7090.6593.740.135
Youtu-Parsing2.5B93.680.04493.4592.0295.000.116
Logics-Parsing-v24B93.270.04195.4788.4291.980.137
FireRed-OCR2B93.200.03795.2788.0491.060.131
MinerU2.51.2B92.980.04595.5987.8891.470.130
OpenDoc-0.1B0.1B90.640.04992.9383.8887.450.140
dots.ocr3B90.500.04889.1287.1890.580.138
DeepSeek-OCR 23B90.170.05091.5983.8987.750.144
HunyuanOCR1B89.870.08987.4491.0193.230.171
Dolphin-v23B89.340.06990.5384.4087.440.150
OCRVerse4B88.440.06389.1482.4486.270.163
MonkeyOCR-pro-3B3B88.430.07488.3384.3588.620.189
General VLMsOvis2.6-30B-A3B30B93.620.03594.9389.4492.400.135
Gemini 3 Pro--92.850.06495.8389.1592.960.165
Gemini 3 Flash--92.580.06695.0389.2993.510.173
Qwen3-VL-235B235B89.780.06392.5383.0786.750.166
GPT-5.2--86.520.11488.0082.9587.930.193
InternVL3.5-241B241B83.610.13089.5274.3579.780.215

Wild_OmniDocBench

Model TypeMethodsParamOverall ↑Text Edit ↓Formula CDM ↑Table TEDS ↑Table TEDS-S ↑Read Order Edit ↓
Decoupled VLMsNaviDC-OCR1.2B88.530.117388.2689.0592.140.2011
PaddleOCR-VL-1.60.9B87.360.136988.4285.7690.140.2057
MinerU2.5-Pro1.2B87.330.136290.1585.4690.120.2013
GLM-OCR0.9B85.080.151489.0981.3185.900.2228
PaddleOCR-VL-1.50.9B84.640.146186.7281.8086.520.2138
End-to-End VLMsOvisOCR20.8B87.910.12990.3785.1389.110.2021
dots.ocr3B81.840.148385.075.3280.200.2200
HunyuanOCR-1.51B77.620.197985.1267.5470.670.2750
Logics-Parsing-v24B77.100.402991.480.1987.160.2355

PureDocBench

Model TypeModelClean Overall ↑Clean Text ↓Clean Formula ↑Clean Table ↑Digital Degraded Overall ↑Digital Degraded Text ↓Digital Degraded Formula ↑Digital Degraded Table ↑Real Degraded Overall ↑Real Degraded Text ↓Real Degraded Formula ↑Real Degraded Table ↑
Decoupled VLMNaviDC-OCR86.900.11181.0191.0977.470.20672.5980.4570.850.30265.1177.66
DotsMOCR76.270.15166.2377.6573.160.19864.3274.9561.730.31254.3961.97
MinerU2.5-Pro75.870.22265.1484.6871.770.27261.7980.7362.560.37552.7072.47
YouTu-Parsing75.020.23067.3480.7469.660.27061.4474.4960.290.36052.2064.69
PaddleOCR-VL-1.573.010.26663.5382.1266.730.33958.0376.0760.500.39854.0067.33
GLM-OCR68.650.31457.8979.4463.060.38353.2374.2158.310.43350.3467.83
Dolphin-v265.900.34259.8072.1260.240.39352.2067.8644.920.55339.9850.04
MonkeyOCR-pro-3B62.230.34648.4672.8357.400.39745.5766.3246.490.51138.1852.43
End-to-End VLMOvisOCR282.140.14971.2990.1277.770.19267.8784.7166.610.31657.6473.79
FD-RL78.380.19368.2186.2276.330.21467.1683.2267.040.29858.8272.08
Logics-Parsing-v276.350.21367.6782.6773.850.24867.3379.0267.640.30461.6571.64
dots.ocr72.010.24861.3779.5165.950.30756.6771.8655.680.40347.7059.63
Qianfan-OCR57.220.37049.7958.8350.850.43844.4151.9645.060.49439.0845.53
General VLMsQwen3-VL-8B72.440.26165.1078.3572.030.26664.8877.8262.730.34255.5566.81
Kimi K2.672.320.30366.9380.3069.950.32264.6977.3168.020.33562.4475.14
Gemini-3.1-Pro70.040.30665.6375.0869.280.32265.8174.2471.980.30068.6277.26
Qwen3.5-397B-A17B69.120.23365.2665.4068.340.24463.9165.5362.700.28760.7056.12

ICDAR2026 Sci-ImageMiner

#TeamRMSTEDSWeighted
1NaviDC-OCR17.2366.3941.81
2VLMinators17.2964.3140.80
3Ricoh_SRCB16.2361.1238.67
4Vassilis Sioros14.9455.2035.07
5DocMiner12.6753.7233.19
6Qwen3 VL 8B14.0857.8635.97

🚀 Installation

pip install transformers torch pillow

Quick Start

import html
import itertools
import json
import re
from dataclasses import dataclass

from PIL import Image
import torch
from transformers import AutoProcessor, AutoModel

@dataclass
class ContentBlock:
    type: str
    bbox: list[float]
    angle: int | None = None
    content: str | None = None


@dataclass
class TableCell:
    text: str
    start_row_offset_idx: int
    end_row_offset_idx: int
    start_col_offset_idx: int
    end_col_offset_idx: int
    row_span: int = 1
    col_span: int = 1


OTSL_NL = "<nl>"
OTSL_FCEL = "<fcel>"
OTSL_ECEL = "<ecel>"
OTSL_LCEL = "<lcel>"
OTSL_UCEL = "<ucel>"
OTSL_XCEL = "<xcel>"
OTSL_TOKENS = [OTSL_NL, OTSL_FCEL, OTSL_ECEL, OTSL_LCEL, OTSL_UCEL, OTSL_XCEL]


def _otsl_extract_tokens_and_text(text: str):
    pattern = "(" + "|".join(map(re.escape, OTSL_TOKENS)) + ")"
    tokens = re.findall(pattern, text)
    parts = [part for part in re.split(pattern, text) if part.strip()]
    return tokens, parts


def _count_right(rows, row_idx, col_idx, tokens):
    span = 0
    while col_idx < len(rows[row_idx]) and rows[row_idx][col_idx] in tokens:
        span += 1
        col_idx += 1
    return span


def _count_down(rows, row_idx, col_idx, tokens):
    span = 0
    while row_idx < len(rows) and col_idx < len(rows[row_idx]) and rows[row_idx][col_idx] in tokens:
        span += 1
        row_idx += 1
    return span


def _otsl_parse_texts(parts, tokens):
    rows = [list(row) for is_nl, row in itertools.groupby(tokens, lambda token: token == OTSL_NL) if not is_nl]
    if not rows:
        return [], []

    max_cols = max(len(row) for row in rows)
    for row in rows:
        row.extend([OTSL_ECEL] * (max_cols - len(row)))

    cells = []
    row_idx = 0
    col_idx = 0
    for idx, part in enumerate(parts):
        if part in (OTSL_FCEL, OTSL_ECEL):
            cell_text = ""
            right_offset = 1
            if part != OTSL_ECEL and idx + 1 < len(parts) and parts[idx + 1] not in OTSL_TOKENS:
                cell_text = parts[idx + 1].strip()
                right_offset = 2

            next_right = parts[idx + right_offset] if idx + right_offset < len(parts) else ""
            next_bottom = rows[row_idx + 1][col_idx] if row_idx + 1 < len(rows) and col_idx < len(rows[row_idx + 1]) else ""
            col_span = 1 + (_count_right(rows, row_idx, col_idx + 1, {OTSL_LCEL, OTSL_XCEL}) if next_right in {OTSL_LCEL, OTSL_XCEL} else 0)
            row_span = 1 + (_count_down(rows, row_idx + 1, col_idx, {OTSL_UCEL, OTSL_XCEL}) if next_bottom in {OTSL_UCEL, OTSL_XCEL} else 0)
            cells.append(TableCell(
                text=cell_text,
                row_span=row_span,
                col_span=col_span,
                start_row_offset_idx=row_idx,
                end_row_offset_idx=row_idx + row_span,
                start_col_offset_idx=col_idx,
                end_col_offset_idx=col_idx + col_span,
            ))
        if part in (OTSL_FCEL, OTSL_ECEL, OTSL_LCEL, OTSL_UCEL, OTSL_XCEL):
            col_idx += 1
        elif part == OTSL_NL:
            row_idx += 1
            col_idx = 0
    return cells, rows


def convert_otsl_to_html(otsl_content: str) -> str:
    if otsl_content.startswith("<table") and otsl_content.endswith("</table>"):
        return otsl_content

    tokens, parts = _otsl_extract_tokens_and_text(otsl_content)
    cells, rows = _otsl_parse_texts(parts, tokens)
    if not cells or not rows:
        return ""

    grid = [[None for _ in range(len(rows[0]))] for _ in range(len(rows))]
    for cell in cells:
        for row_idx in range(cell.start_row_offset_idx, min(cell.end_row_offset_idx, len(rows))):
            for col_idx in range(cell.start_col_offset_idx, min(cell.end_col_offset_idx, len(rows[0]))):
                grid[row_idx][col_idx] = cell

    html_rows = []
    for row_idx, row in enumerate(grid):
        html_rows.append("<tr>")
        for col_idx, cell in enumerate(row):
            if cell is None or cell.start_row_offset_idx != row_idx or cell.start_col_offset_idx != col_idx:
                continue
            attrs = ""
            if cell.row_span > 1:
                attrs += f' rowspan="{cell.row_span}"'
            if cell.col_span > 1:
                attrs += f' colspan="{cell.col_span}"'
            html_rows.append(f"<td{attrs}>{html.escape(cell.text.strip())}</td>")
        html_rows.append("</tr>")
    return "<table>" + "".join(html_rows) + "</table>"


def post_process(blocks: list[ContentBlock]) -> list[ContentBlock]:
    for block in blocks:
        if block.type == "table" and block.content:
            block.content = convert_otsl_to_html(block.content)
        elif block.type == "equation" and block.content:
            content = block.content.strip()
            content = content.removeprefix("\\[").removesuffix("\\]").strip()
            if not (content.startswith("$") and content.endswith("$")):
                content = f"$${content}$$"
            block.content = content
    return [block for block in blocks if block.type != "equation_block"]

def infer(image: Image.Image, prompt: str) -> str:
    messages = [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": [
            {"type": "image"},
            {"type": "text", "text": prompt},
        ]},
    ]
    chat_prompt = processor.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True,
    )
    inputs = processor(
        text=[chat_prompt],
        images=[image.convert("RGB")],
        padding=True,
        return_tensors="pt",
    ).to(device=model.device, dtype=model.dtype)
    output_ids = model.generate(
        **inputs,
        use_cache=True,
        max_new_tokens=4096,
        do_sample=False,
    )
    output_ids = output_ids.cpu().tolist()[0][len(inputs.input_ids[0]):]
    return processor.batch_decode(
        [output_ids],
        skip_special_tokens=True,
        clean_up_tokenization_spaces=False,
    )[0].strip()



processor = AutoProcessor.from_pretrained("StarDoc-AI/NaviDC-OCR", trust_remote_code=True, use_fast=True)
model = AutoModel.from_pretrained(
    "StarDoc-AI/NaviDC-OCR",
    trust_remote_code=True,
    torch_dtype=torch.bfloat16,
).cuda().eval()

# text
image=Image.open("./assets/text.png").convert("RGB")
raw_text = infer(image, "Please output the text content from the image.")
print(raw_text.strip())

# table
image=Image.open("./assets/table.png").convert("RGB")
raw_otsl = infer(image, "This is the image of a table. Please output the table in OTSL format.")
print(convert_otsl_to_html(raw_otsl))

# formula 
image=Image.open("./assets/formula.png").convert("RGB")
raw_formula = infer(image, "Please write out the expression of the formula in the image using LaTeX format.")
formula_block = ContentBlock("equation", [0.0, 0.0, 1.0, 1.0], content=raw_formula)
formula = post_process([formula_block])[0].content
print(formula)

#code
image=Image.open("./assets/code.png").convert("RGB")
raw_code = infer(image,"The image contains a code snippet, please output the parsing result.")
print(raw_code.strip())

# layout
image=Image.open("./assets/layout.jpg").convert("RGB")
image = image.resize((1036, 1036), Image.Resampling.BICUBIC)
raw_layout = infer(image, "Analyze the image layout.")
print(raw_layout.strip())

# Distorted document layout
layout_image = Image.open("./assets/layout_distorted.jpg").convert("RGB")
layout_image = layout_image.resize((1036, 1036), Image.Resampling.BICUBIC)
raw_layout = infer(layout_image, "\nMulti-point Layout Segmentation Analysis.")
print(raw_layout.strip())

#scientific figure
image=Image.open("./assets/scientific_figure.png").convert("RGB")
raw_scientific_figure = infer(image, "This is a scientific figure. Please extract the table implied by this figure.")
print(convert_otsl_to_html(raw_scientific_figure))

If you would like to perform complete document parsing, please refer to our GitHub repository: https://github.com/caipeng328/NaviDC-OCR.

Citation

@article{navidc_ocr,
  title={NaviDC-OCR: Navigating Document Parsing Across Digital and Camera-Captured Documents},
  author={Cai, Peng and Zou, Zhaofan and Liu, Shifa and Wang, Yikun and Tang, Jiawei and Yang, Kaicheng and Tong, Meng and He, Zhongjiang and Sun, Hao},
  journal={arXiv preprint arXiv:2608.12898},
  year={2026}
}

Community Contributions

Thanks to Nandraj for the GGUF conversion and llama.cpp support! 🔗 NaviDC-OCR-GGUF

Acknowledgements

NaviDC-OCR is built upon

  • MinerU
  • Qwen2.5-VL
  • Qwen3
  • Transformers
  • PyTorch
  • FlashAttention

We sincerely thank these excellent open-source projects.


Contact

If you have any questions, feel free to open an issue or contact us.

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

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

GPU в ailist

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

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

Подбираем GPU…

Класс архитектуры
Qwen2_5_VLForConditionalGeneration
Ревизия весов
710ea2e

0 моделей