PaddleOCR glue for MoonBit: JPEG/PNG/BMP decode, DBNet boxes, CTC decode, and native ONNX Runtime inference.
中国人民共和国
你好,世界。
MoonBit OCR 2026moon add V1GreenSummer/paddleocr@0.1.1///|
import {
"V1GreenSummer/paddleocr",
}///|
import {
"V1GreenSummer/paddleocr/ocr",
}moon run examples/ctc你好JPEG / PNG / BMP
│ mizchi/image
▼
RGBA
│
├─ 检测:长边 ≤ 960,两边对齐 32,NCHW BGR,ImageNet 归一化
│ moon-ort → DB 概率图
│ 四连通域 → 最小面积矩形 → unclip → 透视裁正
│
└─ 裁正后的横条,高度 48,均值和标准差都是 0.5
moon-ort → CTC 贪心解码
▼
文本行///|
test {
let (width, height) = @paddleocr.limit_side_size(1000, 500, limit=960)
inspect(width, content="960")
inspect(height, content="480")
let charset = @paddleocr.Charset::from_text("你\n好\n")
let logits : Array[Float] = [0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
inspect(
@paddleocr.ctc_greedy(logits, time=2, classes=3, charset~),
content="你好",
)
}| 代码 | wasm | native |
|---|---|---|
| 解码、缩放、归一化、连通域、分行、CTC | 可以单独测 | 可以 |
| engine 里的 ONNX 会话、ocr 单入口包,以及 cmd/ocr | 不编译 | Linux x86_64 CPU |
moon.mod 模块 V1GreenSummer/paddleocr
decode.mbt / rgba.mbt PNG、JPEG、BMP → RGBA
preprocess.mbt 检测 / 识别张量
detect.mbt 概率图 → 框 → 行
ctc.mbt / page.mbt 字典、CTC、Page
error.mbt OcrError
engine/ native:Engine、EngineOptions、Backend 和批处理
ocr/ native 单入口:重导出 Engine 与结果/几何类型
benchmarks/ native 性能基准
examples/ctc/ 不依赖模型的 CTC 最小示例
cmd/ocr/ native 可执行文件| 组件 | 许可证 | 说明 |
|---|---|---|
| mizchi/image 0.4.3 | Apache-2.0 | PNG/JPEG/BMP 解码与缩放 |
| mizchi/zlib 0.4.10 | Apache-2.0 | mizchi/image 的 zlib 依赖 |
| HuLunTunTao/moon-ort 0.1.1 | MIT | ONNX Runtime 的 MoonBit 绑定 |
| moonbitlang/x 0.5.5 | Apache-2.0 | 文件读写 |
| ONNX Runtime 1.30.0 | MIT | 运行时共享库,不随仓库分发 |
| PaddleOCR / RapidOCR 模型 | Apache-2.0 | 上游模型权重,不随仓库分发 |
| 文件 | SHA256 |
|---|---|
| ch_PP-OCRv4_det_infer.onnx | d2a7720d45a54257208b1e13e36a8479894cb74155a5efe29462512d42f49da9 |
| ch_PP-OCRv4_rec_infer.onnx | 48fc40f24f6d2a207a2b1091d3437eb3cc3eb6b676dc3ef9c37384005483683b |
export MOON_ORT_LIBRARY=/path/to/libonnxruntime.so.1.30.0moon run --target native cmd/ocr -- page.png \
--det ch_PP-OCRv4_det_infer.onnx \
--rec ch_PP-OCRv4_rec_infer.onnx \
--dict ppocr_keys_v1.txt \
--boxes| 参数 | 作用 |
|---|---|
| 位置参数 | 待识别的 JPEG、PNG 或 BMP |
| --det --rec --dict | 检测模型、识别模型、字典,三者都要 |
| --library | ONNX Runtime 共享库;缺省时读 MOON_ORT_LIBRARY |
| --limit | 检测长边上限,默认 960 |
| --boxes | 每个框一行:x,y,width,height<TAB>检测分<TAB>识别置信度<TAB>text |
63,59,352,46 0.9941148446743899 0.9998255542346409 中国人民共和国
57,134,280,53 0.9936801729464608 0.9986610412597656 你好,世界。
60,219,444,41 0.989933144244093 0.9717656522989273 MoonBit OCR 2026///|
import {
"V1GreenSummer/paddleocr/ocr",
}let page = @ocr.with_engine(
det=det_path,
rec=rec_path,
dict=dict_path,
engine => {
engine.recognize_file(image_path)
},
)
println(page.render())///|
let settings = @ocr.EngineOptions::standard()
.with_intra_op_threads(4)
.with_rec_batch_size(8)
///|
let engine = @ocr.Engine::load(
det=det_path,
rec=rec_path,
dict=dict_path,
options=settings,
)let engine = @ocr.Engine::load(det=det_path, rec=rec_path, dict=dict_path)
let quads = engine.detect(image) // 只检测
let page = engine.recognize_quads(image, quads) // 改框后识别
let raw = engine.recognize_regions(crops) // 只识别已有裁块
engine.close()| 函数 | 作用 |
|---|---|
| sniff_raster / decode_raster | 识别容器并解成 Rgba |
| Rgba::from_rgba / Rgba::crop / Rgba::warp | 校验、裁剪,或把四边形透视成横条 |
| limit_side_size / prepare_det / prepare_rec | 检测缩放与两路归一化张量 |
| RecognizeOptions::standard / with_* | 检测长边、DB 阈值、min_size、识别宽度和置信度 |
| db_quads / min_area_rect / unclip_quad | 概率图上的旋转矩形 |
| db_regions / db_boxes / map_det_box / group_lines | 一次连通域给出两种框,或轴对齐框与阅读顺序 |
| Charset::from_text / Charset::ppocr / ctc_decode | 字典、CTC 和置信度 |
| Page::text / Page::render / Page::spans / Page::to_json_string | 阅读顺序文本、带框表格、展平 span 或 JSON |
| 函数 | 作用 |
|---|---|
| Engine::detect / Engine::recognize_quads / Engine::recognize_regions | 分步检测、改框后识别、只识别裁块 |
| EngineOptions::standard / with_* | 线程数、图优化、执行模式和识别批大小 |
| Engine::with_scope / with_engine | 成功或报错时自动 close |
| Backend / recognize_with / recognize_quads_with | 用自定义检测/识别函数替换 ONNX 后端,便于测试 |
moon bench --target native --release benchmarks| 项目 | 优化前 | 优化后 | 说明 |
|---|---|---|---|
| db_quads 960×960 | 54.45 ms | 4.00 ms | 只对连通域边界像素建凸包 |
| db_regions 960×960 | 54.47 ms | 3.99 ms | 同一次连通域同时输出两种框 |
| warp 600×50 | 2.24 ms | 1.06 ms | 每个输出像素只投影一次,四通道共用权重 |
| prepare_det 780×300 | 7.32 ms | 6.86 ms | 归一化使用 256 项查找表 |
| ctc_decode 80×100 | 3.06 µs | 3.15 µs | 噪声范围内,未做针对性优化 |
moon test
moon test --target native
moon bench --target native --release benchmarkspub suberror OcrError {
Image(String)
Model(String)
Runtime(String)
Io(String)
Config(String)
Usage(String)
}test {
let charset = @paddleocr.Charset::ppocr("你\n好\n")
debug_inspect(charset.glyph(3), content="Some(\" \")")
}test {
let quad : @paddleocr.DetQuad = {
top_left: { x: 1.0, y: 2.0, },
top_right: { x: 4.0, y: 2.0, },
bottom_right: { x: 4.0, y: 6.0, },
bottom_left: { x: 1.0, y: 6.0, },
score: 0.5,
}
let page = @paddleocr.Page::from_lines([
@paddleocr.TextLine::from_spans([
{
quad,
bounds: { x: 1, y: 2, width: 3, height: 4, score: 0.5, },
text: "你好",
det_score: 0.5,
rec_confidence: 0.9,
},
]),
])
inspect(page.render(), content="你好")
inspect(page.render(boxes=true), content="1,2,3,4\t0.5\t0.9\t你好")
}impl Default for RecognizeOptionstest {
let options = @paddleocr.RecognizeOptions::standard().with_limit(640)
inspect(options.limit_side_len, content="640")
inspect(options.box_thresh, content="0.6")
inspect(options.min_size, content="3")
}fn RecognizeOptions::with_box_thresh(self : RecognizeOptions, box_thresh : Double) -> RecognizeOptionsfn RecognizeOptions::with_det_thresh(self : RecognizeOptions, det_thresh : Double) -> RecognizeOptionsfn RecognizeOptions::with_rec_max_width(self : RecognizeOptions, rec_max_width : Int) -> RecognizeOptionsfn RecognizeOptions::with_rec_thresh(self : RecognizeOptions, rec_thresh : Double) -> RecognizeOptionsfn RecognizeOptions::with_unclip_ratio(self : RecognizeOptions, unclip_ratio : Double) -> RecognizeOptionstest {
let image = @paddleocr.Rgba::from_rgba(1, 1, [
b'\x01', b'\x02', b'\x03', b'\xff',
])
inspect(image.width, content="1")
inspect(image.height, content="1")
}test {
let charset = @paddleocr.Charset::from_text("a\nb\n")
let logits : Array[Float] = [
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
]
inspect(
@paddleocr.ctc_greedy(logits, time=4, classes=3, charset~),
content="ab",
)
}fn db_regions(prob : Array[Float], height~ : Int, width~ : Int, thresh? : Double, box_thresh? : Double, unclip_ratio? : Double, min_size? : Int) -> DetRegions raise OcrErrortest {
let (width, height) = @paddleocr.limit_side_size(10, 20, limit=960)
inspect(width, content="32")
inspect(height, content="32")
}test {
let quad = @paddleocr.min_area_rect([
{ x: 0.0, y: 0.0, },
{ x: 2.0, y: 0.0, },
{ x: 2.0, y: 1.0, },
{ x: 0.0, y: 1.0, },
])
inspect(quad.top_left.x, content="0")
inspect(quad.top_right.x, content="2")
inspect(quad.bottom_left.y, content="1")
}test {
debug_inspect(
@paddleocr.sniff_raster([b'\xff', b'\xd8', b'\xff']),
content="Jpeg",
)
}Install
Download zipPaddleOCR glue for MoonBit: JPEG/PNG/BMP decode, DBNet boxes, CTC decode, and native ONNX Runtime inference.