Skip to content

feat: support ncnn ocr#142

Open
Aliothmoon wants to merge 2 commits into
mainfrom
feat/ncnn-build-conversion
Open

feat: support ncnn ocr#142
Aliothmoon wants to merge 2 commits into
mainfrom
feat/ncnn-build-conversion

Conversation

@Aliothmoon

@Aliothmoon Aliothmoon commented Jun 15, 2026

Copy link
Copy Markdown
Owner
  • setup_maa_core.py 解压官方 android tarball 后,把 PaddleOCR/PaddleCharOCR/global 的 inference.onnx 转成 ncnn 模型

  • rec: onnxsim 固定 [1,3,48,320] + pnnx fp16=0;det: pnnx fp16=0 保 fp32),再删掉 OCR onnx(保留 resource/onnx 的 OnnxSessions

Sourcery 总结

在构建阶段将已部署的 OCR ONNX 模型转换为用于 Android 的 NCNN 格式,并将其集成到 MAA 核心的安装流程和 CI 构建流程中。

新特性:

  • 引入脚本,用于就地将已部署的 PP-OCR ONNX 模型转换为适用于 Android OCR 的 NCNN 格式,并支持可选缓存和 fp16 rec 权重。

增强:

  • 扩展 MAA 核心安装脚本,使其在部署资源后可选地执行 OCR ONNX 到 NCNN 的转换,并提供跳过转换、保留 ONNX 文件以及控制 rec fp16 的参数选项。

构建:

  • 新增脚本依赖文件,固定 OCR 模型转换所需的 ONNX、onnxsim 和 pnnx 版本。

CI:

  • 更新 Android 开发版和发布版构建工作流,在下载并部署 MAA Core 之前安装 OCR 转换所需的 Python 依赖。
Original summary in English

Summary by Sourcery

Add build-time conversion of deployed OCR ONNX models to NCNN for Android and wire it into the MAA core setup and CI build workflows.

New Features:

  • Introduce a script to convert deployed PP-OCR ONNX models to NCNN format in place for Android OCR use, with optional caching and fp16 rec weights.

Enhancements:

  • Extend the MAA core setup script to optionally run OCR ONNX-to-NCNN conversion after deploying resources, with flags to skip conversion, retain ONNX files, and control rec fp16.

Build:

  • Add a scripts requirements file pinning ONNX, onnxsim, and pnnx versions required for OCR model conversion.

CI:

  • Update dev and release Android build workflows to install the OCR conversion Python dependencies before downloading and deploying MAA Core.

setup_maa_core.py 解压官方 android tarball 后,调用新增的 convert_ocr_ncnn.py 就地把 PaddleOCR/PaddleCharOCR/global 的 inference.onnx 转成 ncnn(rec: onnxsim 固定 [1,3,48,320] + pnnx fp16=0;det: pnnx fp16=0 保 fp32),再删掉 OCR onnx(保留 resource/onnx 的 OnnxSessions)。
@sourcery-ai

sourcery-ai Bot commented Jun 15, 2026

Copy link
Copy Markdown

审阅者指南

在构建阶段新增一条流水线,将已部署的 PP-OCR inference.onnx 模型转换为适用于 Android OCR 的 ncnn 格式,并支持缓存及 CLI 参数开关,同时将其集成到 setup_maa_core 和 GitHub Android 构建工作流中。

Android 构建中 OCR onnx 到 ncnn 转换的时序图

sequenceDiagram
    participant GitHubActions
    participant setup_maa_core
    participant convert_ocr_ncnn
    participant pnnx
    participant onnxsim

    GitHubActions->>setup_maa_core: main(--abi, --skip-ncnn, --keep-onnx, --rec-fp16)
    alt not args.skip_ncnn
        setup_maa_core->>convert_ocr_ncnn: convert_tree(resource_dir, cache_dir, keep_onnx, rec_fp16)
        loop each inference.onnx
            convert_ocr_ncnn->>convert_ocr_ncnn: convert_one(onnx_path, kind, cache_dir, rec_fp16)
            alt cache_miss
                convert_ocr_ncnn->>convert_ocr_ncnn: _convert_into_cache(onnx_path, kind, rec_fp16, cache_param, cache_bin)
                opt kind == rec
                    convert_ocr_ncnn->>onnxsim: _run([python, -m, onnxsim, onnx_path, rec_sim.onnx, --overwrite-input-shape])
                end
                convert_ocr_ncnn->>pnnx: _run([pnnx, work_onnx.name, inputshape, fp16])
            else cache_hit
                convert_ocr_ncnn-->>convert_ocr_ncnn: reuse cached .param/.bin
            end
        end
        convert_ocr_ncnn-->>setup_maa_core: stats
    else args.skip_ncnn
        setup_maa_core-->>GitHubActions: [SKIP] ncnn conversion
    end
Loading

文件级变更

变更 详情 文件
引入可复用的 OCR ONNX→ncnn 转换脚本,并将其接入 Android 资源流水线
  • 新增 scripts/convert_ocr_ncnn.py,实现将 det/rec inference.onnx 模型通过 onnxsim+pnnx 转换为 det.ncnn/rec.ncnn,使用固定输入尺寸并支持 fp16 控制
  • 实现基于内容寻址的 ncnn 转换结果缓存,使用 onnx SHA、模型类型、fp16 标志及配方版本作为键
  • 提供独立转换的 CLI 接口,支持配置资源根目录、缓存目录、是否保留 ONNX 文件,以及是否以 fp16 存储 rec 权重
scripts/convert_ocr_ncnn.py
将 OCR ncnn 转换集成到 Maa core 安装脚本中,并支持可配置行为
  • setup_maa_core.py 中导入新的转换模块
  • 新增 CLI 参数,用于跳过 ncnn 转换、保留原始 OCR ONNX 文件,以及为 rec 模型启用 fp16 权重
  • 在部署 MaaResource 之后,使用 Android 资源和缓存路径调用 convert_ocr_ncnn.convert_tree,并打印已转换/命中缓存/被删除/被跳过模型的汇总信息,或打印跳过信息
scripts/setup_maa_core.py
确保 CI Android 构建具备执行 OCR 转换所需的依赖
  • 在 dev 和 release Android GitHub 工作流中,在下载并部署 MAA Core 之前新增一步安装 scripts/requirements.txt
  • 新建固定版本的 scripts/requirements.txt,列出已在精度上验证的 onnx、onnxsim 和 pnnx 版本
.github/workflows/build-dev.yml
.github/workflows/build-release.yml
scripts/requirements.txt

技巧与命令

与 Sourcery 交互

  • 触发新审查: 在 Pull Request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub Issue: 通过回复某条审查评论来让 Sourcery 从该评论创建一个 Issue。你也可以在审查评论下回复 @sourcery-ai issue 来从中创建 Issue。
  • 生成 Pull Request 标题: 在 Pull Request 标题中任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在 Pull Request 中评论 @sourcery-ai title 以(重新)生成标题。
  • 生成 Pull Request 摘要: 在 Pull Request 描述中任意位置写上 @sourcery-ai summary,即可在对应位置生成 PR 摘要。你也可以在 Pull Request 中评论 @sourcery-ai summary 以在任意时间(重新)生成摘要。
  • 生成审阅者指南: 在 Pull Request 中评论 @sourcery-ai guide,即可在任意时间(重新)生成审阅者指南。
  • 批量解决所有 Sourcery 评论: 在 Pull Request 中评论 @sourcery-ai resolve,即可将所有 Sourcery 评论标记为已解决。如果你已经处理完所有评论且不想再看到它们,这会很有用。
  • 批量驳回所有 Sourcery 审查: 在 Pull Request 中评论 @sourcery-ai dismiss,即可驳回所有现有的 Sourcery 审查。特别适合你想从头开始新一轮审查的情况——别忘了再次评论 @sourcery-ai review 来触发新审查!

自定义使用体验

打开你的 控制面板 来:

  • 启用或禁用审查功能,例如 Sourcery 自动生成的 Pull Request 摘要、审阅者指南等。
  • 更改审查语言。
  • 添加、删除或编辑自定义审查指令。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

Adds a build-time pipeline to convert deployed PP-OCR inference.onnx models into ncnn format for Android OCR, with caching and CLI flags, and wires it into setup_maa_core and GitHub Android build workflows.

Sequence diagram for OCR onnx to ncnn conversion in Android build

sequenceDiagram
    participant GitHubActions
    participant setup_maa_core
    participant convert_ocr_ncnn
    participant pnnx
    participant onnxsim

    GitHubActions->>setup_maa_core: main(--abi, --skip-ncnn, --keep-onnx, --rec-fp16)
    alt not args.skip_ncnn
        setup_maa_core->>convert_ocr_ncnn: convert_tree(resource_dir, cache_dir, keep_onnx, rec_fp16)
        loop each inference.onnx
            convert_ocr_ncnn->>convert_ocr_ncnn: convert_one(onnx_path, kind, cache_dir, rec_fp16)
            alt cache_miss
                convert_ocr_ncnn->>convert_ocr_ncnn: _convert_into_cache(onnx_path, kind, rec_fp16, cache_param, cache_bin)
                opt kind == rec
                    convert_ocr_ncnn->>onnxsim: _run([python, -m, onnxsim, onnx_path, rec_sim.onnx, --overwrite-input-shape])
                end
                convert_ocr_ncnn->>pnnx: _run([pnnx, work_onnx.name, inputshape, fp16])
            else cache_hit
                convert_ocr_ncnn-->>convert_ocr_ncnn: reuse cached .param/.bin
            end
        end
        convert_ocr_ncnn-->>setup_maa_core: stats
    else args.skip_ncnn
        setup_maa_core-->>GitHubActions: [SKIP] ncnn conversion
    end
Loading

File-Level Changes

Change Details Files
Introduce a reusable OCR ONNX→ncnn conversion script and wire it into the Android resource pipeline
  • Add scripts/convert_ocr_ncnn.py implementing conversion of det/rec inference.onnx models to det.ncnn/rec.ncnn using onnxsim+pnnx with fixed input shapes and fp16 control
  • Implement content-addressed cache of converted ncnn artifacts keyed by onnx SHA, model kind, fp16 flag, and recipe version
  • Provide a CLI for standalone conversion with options for resource root, cache directory, keeping ONNX files, and storing rec weights in fp16
scripts/convert_ocr_ncnn.py
Integrate OCR ncnn conversion into Maa core setup script with configurable behavior
  • Import the new conversion module in setup_maa_core.py
  • Add CLI flags to skip ncnn conversion, keep original OCR ONNX files, and enable fp16 weights for rec models
  • After deploying MaaResource, call convert_ocr_ncnn.convert_tree with Android assets and cache paths, printing a summary of converted/cached/removed/skipped models or a skip message
scripts/setup_maa_core.py
Ensure CI Android builds have the dependencies needed for OCR conversion
  • Add a step to install scripts/requirements.txt before downloading and deploying MAA Core in dev and release Android GitHub workflows
  • Create a pinned scripts/requirements.txt listing onnx, onnxsim, and pnnx versions validated for accuracy
.github/workflows/build-dev.yml
.github/workflows/build-release.yml
scripts/requirements.txt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个安全问题、1 个其他问题,并留下了一些整体性的反馈:

安全问题

  • 检测到未使用静态字符串的 subprocess.run 调用。如果这一数据可能被恶意行为者控制,就可能存在命令注入风险。请审查该调用的使用场景,确保其不会被外部资源控制。你可以考虑使用 shlex.escape()。(link)

总体评论

  • _pnnx_outputs 中,binp 路径用两种不同方式计算了两次,第一次的值被覆盖了;建议简化为单一且明确正确的构造方式,以避免困惑和潜在的路径相关 bug。
  • _find_pnnx() 会在每次转换时通过 _convert_into_cache 间接调用;你可以只解析一次 pnnx 并将路径向下传递,以避免在大型目录树中多次执行 shutil.which 查找。
给 AI 代理的提示
Please address the comments from this code review:

## Overall Comments
- In `_pnnx_outputs`, the `binp` path is computed twice with two different approaches and the first value is overwritten; consider simplifying this to a single, clearly correct construction to avoid confusion and potential path bugs.
- `_find_pnnx()` is invoked indirectly for every conversion via `_convert_into_cache`; you could resolve `pnnx` once and pass the path down to avoid repeated `shutil.which` lookups in large trees.

## Individual Comments

### Comment 1
<location path="scripts/convert_ocr_ncnn.py" line_range="86-94" />
<code_context>
+
+
+def _pnnx_outputs(workdir: Path) -> tuple[Path, Path]:
+    params = list(workdir.glob("*.ncnn.param"))
+    if len(params) != 1:
+        raise SystemExit(f"expected exactly one *.ncnn.param in {workdir}, found {len(params)}")
+    param = params[0]
+    binp = param.with_suffix("").with_suffix(".bin")  # foo.ncnn.param -> foo.ncnn.bin
+    binp = param.parent / (param.name[: -len(".param")] + ".bin")
+    if not binp.exists():
+        raise SystemExit(f"pnnx .bin not found next to {param}")
+    return param, binp
+
+
</code_context>
<issue_to_address>
**nitpick:** Avoid redundant and slightly confusing `.bin` path construction in `_pnnx_outputs`.

Inside `_pnnx_outputs`, `binp` is assigned twice and only the second value is used. Please remove the unused first assignment and stick to a single, clear construction for the `.bin` filename (e.g. derived from `param.stem`), so there’s no dead code or ambiguity about the naming logic.
</issue_to_address>

### Comment 2
<location path="scripts/convert_ocr_ncnn.py" line_range="58-65" />
<code_context>
    res = subprocess.run(
        [str(c) for c in cmd],
        cwd=str(cwd) if cwd else None,
        capture_output=True,
        text=True,
        encoding="utf-8",
        errors="replace",
    )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

Sourcery 对开源项目永久免费——如果你觉得我们的评审有帮助,请考虑分享给更多人 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续评审。
Original comment in English

Hey - I've found 1 security issue, 1 other issue, and left some high level feedback:

Security issues:

  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)

General comments:

  • In _pnnx_outputs, the binp path is computed twice with two different approaches and the first value is overwritten; consider simplifying this to a single, clearly correct construction to avoid confusion and potential path bugs.
  • _find_pnnx() is invoked indirectly for every conversion via _convert_into_cache; you could resolve pnnx once and pass the path down to avoid repeated shutil.which lookups in large trees.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_pnnx_outputs`, the `binp` path is computed twice with two different approaches and the first value is overwritten; consider simplifying this to a single, clearly correct construction to avoid confusion and potential path bugs.
- `_find_pnnx()` is invoked indirectly for every conversion via `_convert_into_cache`; you could resolve `pnnx` once and pass the path down to avoid repeated `shutil.which` lookups in large trees.

## Individual Comments

### Comment 1
<location path="scripts/convert_ocr_ncnn.py" line_range="86-94" />
<code_context>
+
+
+def _pnnx_outputs(workdir: Path) -> tuple[Path, Path]:
+    params = list(workdir.glob("*.ncnn.param"))
+    if len(params) != 1:
+        raise SystemExit(f"expected exactly one *.ncnn.param in {workdir}, found {len(params)}")
+    param = params[0]
+    binp = param.with_suffix("").with_suffix(".bin")  # foo.ncnn.param -> foo.ncnn.bin
+    binp = param.parent / (param.name[: -len(".param")] + ".bin")
+    if not binp.exists():
+        raise SystemExit(f"pnnx .bin not found next to {param}")
+    return param, binp
+
+
</code_context>
<issue_to_address>
**nitpick:** Avoid redundant and slightly confusing `.bin` path construction in `_pnnx_outputs`.

Inside `_pnnx_outputs`, `binp` is assigned twice and only the second value is used. Please remove the unused first assignment and stick to a single, clear construction for the `.bin` filename (e.g. derived from `param.stem`), so there’s no dead code or ambiguity about the naming logic.
</issue_to_address>

### Comment 2
<location path="scripts/convert_ocr_ncnn.py" line_range="58-65" />
<code_context>
    res = subprocess.run(
        [str(c) for c in cmd],
        cwd=str(cwd) if cwd else None,
        capture_output=True,
        text=True,
        encoding="utf-8",
        errors="replace",
    )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +86 to +94
params = list(workdir.glob("*.ncnn.param"))
if len(params) != 1:
raise SystemExit(f"expected exactly one *.ncnn.param in {workdir}, found {len(params)}")
param = params[0]
binp = param.with_suffix("").with_suffix(".bin") # foo.ncnn.param -> foo.ncnn.bin
binp = param.parent / (param.name[: -len(".param")] + ".bin")
if not binp.exists():
raise SystemExit(f"pnnx .bin not found next to {param}")
return param, binp

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: 请避免在 _pnnx_outputs 中对 .bin 路径进行冗余且略显困惑的构造。

_pnnx_outputs 内部,binp 被赋值了两次,但只使用了第二次的值。请删除未使用的第一次赋值,并仅保留一种清晰的 .bin 文件名构造方式(例如基于 param.stem),以消除无效代码并避免命名逻辑上的歧义。

Original comment in English

nitpick: Avoid redundant and slightly confusing .bin path construction in _pnnx_outputs.

Inside _pnnx_outputs, binp is assigned twice and only the second value is used. Please remove the unused first assignment and stick to a single, clear construction for the .bin filename (e.g. derived from param.stem), so there’s no dead code or ambiguity about the naming logic.

Comment on lines +58 to +65
res = subprocess.run(
[str(c) for c in cmd],
cwd=str(cwd) if cwd else None,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): 检测到未使用静态字符串的 subprocess.run 调用。如果这一数据可能被恶意行为者控制,就可能存在命令注入风险。请审查该调用的使用场景,确保其不会被外部资源控制。你可以考虑使用 shlex.escape()

来源:opengrep

Original comment in English

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

onnxsim 在 GitHub macOS runner 的 Python 3.14 上 SIGSEGV(exit -11),导致 rec 转换失败。实测 pnnx 仅凭固定 inputshape=[1,3,48,320] 即可正确转 SVTR rec(对 CN/PaddleCharOCR/全部 global 实测 cos=1.0、argmax=100%),无需 onnxsim 预简化。故移除 onnxsim 步骤,rec 与 det 统一为纯 pnnx;requirements 仅保留 pnnx(不再装 onnxsim/onnxruntime)。
@Aliothmoon Aliothmoon force-pushed the feat/ncnn-build-conversion branch from 58de704 to cd344f5 Compare June 15, 2026 17:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant