跳到主要内容

如何在 Windows、Mac 和 Linux 上运行 OpenThai 2.0 OCR Text 与 OCR Markdown

· 11 分钟阅读
Kobkrit Viriyayudhakorn
CEO @ iApp Technology

今天我们发布了两个可免费下载的泰文 OCR 模型:OpenThai 2.0 OCR Text 和 OpenThai 2.0 OCR Markdown。本指南介绍如何在 Windows、macOS 或 Linux 上运行它们,从安装 Python 到把 PDF 转成文本或 Markdown。文档始终留在你的电脑上。

选择哪个模型​

OpenThai 2.0 OCR TextOpenThai 2.0 OCR Markdown
输出按阅读顺序排列的页面文本,每行带位置框和置信度Markdown,表格以 HTML 输出(含 rowspan 与 colspan),每个区块带位置框和类别
下载大小4 MB2.5 GB
运行环境任何 CPU,无需 GPUNVIDIA GPU(最快)、Apple 芯片的 Mac,或 CPU
速度在 M3 Max 的 Mac 上约 0.5 秒一页单张 NVIDIA H100 上 1.6 秒一页;M3 Max 的 Mac 上 15 至 27 秒一页
许可Apache-2.0OpenMDW-1.1

需要文字本身时用 OCR Text,例如搜索、从扫描件中取出文字或交给聊天机器人。版面重要时用 OCR Markdown,尤其是表格,数字必须留在原来的行和列中。

Mac 上的数据是为本指南在 M3 Max 的 MacBook Pro 上测得的,测试页为一页泰国政府公文和一页表格;H100 的数据来自模型卡。

不写代码:Chinda Bot​

我们的免费桌面助手 Chinda Bot 已内置 OCR Text。用回形针按钮附加 PDF,或把它拖进对话,每一页都在你的电脑上读取;点击文件名即可查看并复制读出的文字。Chinda Bot 支持 Apple 芯片的 Mac、Windows 电脑(Intel 或 AMD,以及 Windows on Arm)和 Linux arm64。

Chinda Bot 只输出文本行。若要让表格保持表格,请使用下文的 OCR Markdown。

开始之前:安装 Python​

两个模型都需要 Python 3.10 或更高版本。本指南在 macOS 上用 Python 3.13、在 Ubuntu 24.04 上用 Python 3.12 测试。

从 python.org 下载 Python 3.13 并运行安装程序,在第一页勾选 Add python.exe to PATH。然后打开命令提示符(按 Windows 键并输入 cmd)。下文所有 Windows 命令都在命令提示符中运行。

OpenThai 2.0 OCR Text:任何电脑都能用​

1. 新建文件夹和 Python 环境​

mkdir thai-ocr
cd thai-ocr
py -m venv .venv
.venv\Scripts\activate

这个环境把安装的软件包与电脑的其他部分隔开。每次打开新窗口时,再运行最后一行激活它。

2. 安装软件包并下载模型​

所有系统都用相同的两条命令:

pip install onnxruntime opencv-python-headless pyclipper numpy pillow pypdfium2 huggingface_hub
hf download iapp/OpenThai2.0-OCR-Text --local-dir OpenThai2.0-OCR-Text

模型是 1.8 MB 和 2.4 MB 的两个文件,连同读取代码一起放在 OpenThai2.0-OCR-Text 文件夹中。

Windows on Arm(RTX Spark 等 Arm 电脑):opencv-python-headless 和 pyclipper 两个软件包还没有 Windows on Arm 版本,因此 pip install 在这类电脑上会失败。可以从 python.org 安装 x64 版 Python,由 Windows 以模拟方式运行,速度较慢;或者使用在 Windows on Arm 上原生运行 OCR Text 的 Chinda Bot。

3. 保存脚本​

把以下代码保存为 thai-ocr 文件夹中的 ocr_text.py:

ocr_text.py
"""Read Thai and English text from pictures and PDFs with OpenThai 2.0 OCR Text.

Usage: python ocr_text.py document.pdf [more files ...]
Prints the text and saves it next to each file as <file>.txt.
"""
import sys
import time
from pathlib import Path

MODEL_DIR = Path(__file__).resolve().parent / "OpenThai2.0-OCR-Text"
sys.path.insert(0, str(MODEL_DIR))
from runtime.line_engine import LineOCR # noqa: E402


def pages(path):
"""Each page of a PDF as a picture (drawn at 200 dpi), or the picture itself."""
if path.suffix.lower() == ".pdf":
import pypdfium2 as pdfium
pdf = pdfium.PdfDocument(str(path))
for i in range(len(pdf)):
yield pdf[i].render(scale=200 / 72).to_pil()
else:
yield str(path)


ocr = LineOCR(str(MODEL_DIR), threads=2)
for name in sys.argv[1:]:
path = Path(name)
texts = []
for number, page in enumerate(pages(path), start=1):
start = time.perf_counter()
result = ocr.read(page)
seconds = time.perf_counter() - start
print(f"{path.name} page {number}: {len(result['lines'])} lines in {seconds:.2f} s", file=sys.stderr)
texts.append(result["text"])
text = "\n\n".join(texts)
path.with_name(path.name + ".txt").write_text(text, encoding="utf-8")
print(text)

4. 读取文档​

python ocr_text.py document.pdf

脚本会输出文本并保存为 document.pdf.txt。PDF 按页读取,图片(PNG、JPG 等常见格式)直接读取;也可以一次传入多个文件。在较新的电脑上每页约需半秒。

两点说明:

  • 扫描 PDF 和带文字层的 PDF 都可以,因为每一页都先绘制成图片再读取。
  • 处理上千页时,把 threads=2 改为 threads=1,按 CPU 核心数各运行一份脚本,分别处理一部分文件。单个进程中,线程数超过约两个核心后就不再变快。模型卡在 8 核服务器上测得约每分钟 950 页。

OpenThai 2.0 OCR Markdown​

该模型有 2.5 GB,最适合在 GPU 上运行。我们在 Linux 上用 NVIDIA H100 GPU 运行它,并在 M3 Max 的 Mac 上测试了本指南。选择你的系统:

有 NVIDIA GPU: 使用 WSL 2。它在 Windows 中运行 Ubuntu,并通过已安装的 NVIDIA 驱动让 Ubuntu 使用 GPU。以管理员身份打开 PowerShell 并运行:

wsl --install -d Ubuntu-24.04

按提示重启后,从开始菜单打开 Ubuntu,在其中按照本页 Linux 的步骤操作。我们尚未亲自在 Windows 上测试 OCR Markdown。

没有 NVIDIA GPU: 使用下文的 OCR Markdown API,或用 Chinda Bot 读取纯文本。

把以下代码保存为 ocr_markdown.py,放在包含 OpenThai2.0-OCR-Markdown 的文件夹中:

ocr_markdown.py
"""Read pictures and PDFs into Markdown with OpenThai 2.0 OCR Markdown.

python ocr_markdown.py document.pdf
runs the model on this computer: an NVIDIA GPU, a Mac's GPU, or the CPU
python ocr_markdown.py document.pdf --server http://localhost:8000/v1
sends the pages to a vLLM server instead (the fastest way)

Saves the Markdown next to each file as <file>.md. Tables come out as HTML.
"""
import argparse
import sys
import time
from pathlib import Path

from PIL import Image
from nemotron_parse.inference import DocumentParser, GenerationError, GenerationSettings, TransformersBackend

MODEL_DIR = Path(__file__).resolve().parent / "OpenThai2.0-OCR-Markdown"


def pages(path):
"""Each page of a PDF as a picture (drawn at 200 dpi), or the picture itself."""
if path.suffix.lower() == ".pdf":
import pypdfium2 as pdfium
pdf = pdfium.PdfDocument(str(path))
for i in range(len(pdf)):
yield pdf[i].render(scale=200 / 72).to_pil()
else:
yield Image.open(path)


def local_parser():
import torch
from transformers import AutoModel, AutoProcessor

if torch.cuda.is_available():
device, dtype = "cuda", torch.bfloat16
elif torch.backends.mps.is_available():
device, dtype = "mps", torch.float32
else:
device, dtype = "cpu", torch.float32
model = AutoModel.from_pretrained(MODEL_DIR, trust_remote_code=True, dtype=dtype).to(device).eval()
processor = AutoProcessor.from_pretrained(MODEL_DIR, trust_remote_code=True)

class Backend(TransformersBackend):
# a vLLM server reads the prompt after a new line; send the same tokens here
def __call__(self, image, *, prompt, **kwargs):
return super().__call__(image, prompt="\n" + prompt, **kwargs)

print(f"Running the model on {device}", file=sys.stderr)
# read greedily (temperature 0); with sampling, a Mac's GPU garbled pages in our test
return DocumentParser(backend=Backend(model, processor), fullpage=GenerationSettings(temperature=0))


args = argparse.ArgumentParser()
args.add_argument("files", nargs="+")
args.add_argument("--server", help="a vLLM server, for example http://localhost:8000/v1")
args = args.parse_args()

parser = DocumentParser(base_url=args.server, model="nemotron-parse") if args.server else local_parser()
with parser:
for name in args.files:
path = Path(name)
parts = []
for number, page in enumerate(pages(path), start=1):
start = time.perf_counter()
try:
parts.append(parser.parse(page, mode="single")["markdown"])
except GenerationError:
parts.append(f"<!-- page {number} could not be read completely -->")
print(f"{path.name} page {number}: {time.perf_counter() - start:.1f} s", file=sys.stderr)
markdown = "\n\n".join(parts)
path.with_name(path.name + ".md").write_text(markdown, encoding="utf-8")
print(markdown)

脚本会写出 document.pdf.md,每个表格是一个 HTML <table>,Markdown 阅读器和大多数语言模型都能直接读取。若某页因超出长度上限被截断,脚本会在文件中注明,然后继续读下一页。

没有 GPU?使用 API​

两个模型也提供托管 API,无需安装:

链接​

如果某份泰文文档读得不好,欢迎把不含个人信息的样本发送至 sale@iapp.co.th,这些页面能帮助我们改进下一个版本。