尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

在 .NET MAUI 里接入 PP-OCRv6 文本检测实现查找并点击

在 .NET MAUI 里接入 PP-OCRv6 文本检测实现查找并点击
📅 发布时间:2026/7/23 9:52:18

目录

1. 背景

2. 接入过程

2.1 Python先行,快速验证

2.2 迁移到 MAUI

2.3 画框的学问

2.4 坑:四点排序

3. 查找并点击

4. 最后


这篇文章介绍了如何在 .NET MAUI 中接入 PP-OCRv6 完整的 OCR 能力,实现文本检测与识别。解决 FlowBot 开发中更实际的问题,怎么先找到文字按钮在哪,再把它点下去。

1. 背景

在之前的文章 在 .NET MAUI 里接入 PP-OCRv6 离线文字识别,我们已经把 PP-OCRv6 的识别模型接进了 FlowBot,已经可以对一块截图区域做离线识别。这个能力本身是成立的,用来做“文字条件判断”也没问题。

但真到自动化场景里,很快会遇到一个更麻烦的问题:很多时候我们并不知道文字的精确位置,只知道屏幕上大概会出现“下载”“跳过”“立即领取”这类词,希望程序自己找到它,然后再去点击。

这时候如果还只靠 rec 模型,就行不通了。因为识别模型本质上是拿一张已经裁好的单行文字图去读内容,它不是拿来在整张手机截图里“找字”的。直接把整屏或者一个大区域喂给 rec,那必然是不靠谱的。

所以后面的能力必须补上文本检测,也就是 det。让模型先回答“字在哪”,再继续交给识别模型回答“字是什么”,最后才能把“查找并点击”这件事真正做成。

完整接入OCR

当然,最完整的还需要 cls 模型来判断文字方向,但在 FlowBot 里,我暂时没有接 cls。原因很简单:手机界面上的文字基本都是横排,旋转的情况极少,所以先不接 cls 也不会影响大部分场景。

2. 接入过程

从业务上看,这次接入的目标其实很明确:

  1. 对当前屏幕截图,或者一个可选搜索区域截图,运行 det 模型。

  2. 得到一批文本候选四点框。

  3. 对每个四点框做透视裁剪,裁出更接近单行文字的图块。

  4. 把裁剪结果交给 rec 模型识别。

  5. 用目标关键词去匹配识别结果。

  6. 匹配成功后,点击这个四点框的几何中心点。

对应到 FlowBot 里的最终能力,就是“查找并点击”步骤在 OCR 模式下的执行流程。

这里有个关键认识:det 和 rec 不是两个平行功能,而是一条串起来的流水线。det 负责定位,rec 负责理解,少一个都不太行。

2.1 Python先行,快速验证

我这次还是先走了一遍 Python 验证。原因和上一篇一样:先把模型和后处理链路单独验证清楚,再往 MAUI 里搬,排查起来会轻松很多。

当然,这次做的收益也很明显,直接发现了处理上的问题,刚开始的第一版测试发现整体的识别率不高,位置虽然对,但是识别内容乱码,置信度也很低。经过排查,发现是裁剪出来的图片被旋转了180度喂给 rec,导致识别结果完全不对。

最终经过调试和排错,最终做成了一个完整的run_ppocr.py,默认走small模型组,也可以切到tiny。模型文件是这样组织的:

script/PP-OCRv6_small_det_onnx.onnx script/pp_ocrv6_small_rec.onnx script/PP-OCRv6_small_rec_inference.yml

命令行用法也尽量保持简单:

python script/run_ppocr.py script/test.png python script/run_ppocr.py script/test.png --compare-output script/run_ppocr_compare.png python script/run_ppocr.py script/test.png --json-output script/run_ppocr_result.json

这个脚本里,det 输入和 rec 输入是两套预处理:

  • det 会先按最长边限制缩放,再补齐到 32 的倍数。

  • rec 则是固定高度48,宽度按比例自适应。

det 预处理大致如下:

def preprocess_det(image: Image.Image, limit_side_len: int) -> tuple[np.ndarray, tuple[int, int]]: rgb_image = image.convert("RGB") original_size = rgb_image.size resized_width, resized_height = resize_to_multiple_of_32(rgb_image.width, rgb_image.height, limit_side_len) resized = rgb_image.resize((resized_width, resized_height), Image.Resampling.BICUBIC) array = np.asarray(resized).astype("float32") / 255.0 mean = np.array([0.485, 0.456, 0.406], dtype="float32") std = np.array([0.229, 0.224, 0.225], dtype="float32") array = (array - mean) / std array = np.transpose(array, (2, 0, 1)) return np.expand_dims(array, axis=0).astype("float32"), original_size

这个均值和方差跟上一篇 rec 的0.5 / 0.5归一化不一样。也就是说,det 和 rec 虽然都属于 PP-OCRv6,但前处理不能偷懒混用。

Python测试

2.2 迁移到 MAUI

模型文件本身接进 MAUI 其实不难,和上一篇类似,仍然是放到Resources/Raw,第一次使用时复制到 cache 目录,再用InferenceSession加载。

这次新增的是检测模型:

src/FlowBot.App/Resources/Raw/det_inference.onnx

Android 运行时里我额外加了一个PpOcrDetector,和PpOcrRecognizer分开管理:

private const string PpOcrDetModelAssetName = "det_inference.onnx"; private const int PpOcrDetLimitSideLen = 960; private const float PpOcrDetThreshold = 0.2f; private const int PpOcrDetMinArea = 16; private const float PpOcrDetUnclipRatio = 1.4f; private static readonly Lazy<PpOcrDetector> PpOcrDetectorInstance = new(CreatePpOcrDetector); private static readonly Lazy<PpOcrRecognizer> PpOcrRecognizerInstance = new(CreatePpOcrRecognizer);

真正麻烦的部分不在session.Run,而在 det 输出以后怎么把分数图变成可用的文字框。

我这里没有直接照搬 PaddleOCR 的全部 C++/Python 后处理,而是做了一套更适合当前项目、但行为足够接近的版本:

  1. 对输出分数图按阈值二值化。

  2. 提取连通域。

  3. 过滤太小的区域。

  4. 提取边界点。

  5. 做凸包。

  6. 按unclip思路外扩多边形。

  7. 求最小外接四边形。

二值化这一步很直接:

for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { mask[y, x] = output[0, 0, y, x] >= PpOcrDetThreshold; } }

后面我没有直接输出轴对齐矩形,而是尽量保留为四点框。原因也很简单:手机界面上的文字不总是完全水平,哪怕只是轻微倾斜,四点框也比普通矩形更接近真实文字区域。

2.3 画框的学问

如果只是想“画个框看起来差不多”,普通矩形当然更省事。

但一旦后面要把这个框拿去做 rec,矩形的问题就会马上暴露出来:

  • 框太大,会把背景、图标、边缘噪声一起裁进去。

  • 框方向不对,会让本来横着的字被切歪。

  • 多行或斜排文本,会让识别模型输入分布明显变差。

FlowBot 这次接 det,不是为了做一个“能看到框”的展示功能,而是为了支撑后续点击和判断。所以框的几何质量,直接影响识别质量。

也正因为这样,后处理最后落成了四点框,而不是简单left/top/right/bottom。

2.4 坑:四点排序

这次调试里,最容易让人误判的一点是:有时候 det 看起来已经框住了目标文字,但 rec 结果还是一塌糊涂。

一开始很容易怀疑是这些问题:

  • det-thresh太低或太高。

  • unclip-ratio不合适。

  • 检测框不够贴边。

  • 模型本身对手机截图不稳定。

但我后面把中间裁剪图导出来一看,才发现真正的问题根本不在阈值,而在透视裁剪的点顺序。

如果四点框的顺序错了,透视变换出来的文字图会被镜像、翻折,或者局部扭曲。rec 模型拿到这种输入,当然不可能正常识别。

最终稳定下来的排序逻辑是:

private static PointF[] OrderQuad(IReadOnlyList<PointF> points) { if (points.Count != 4) { throw new InvalidOperationException($"期望 4 个点,实际为 {points.Count} 个。"); } var topLeft = points.OrderBy(point => point.X + point.Y).First(); var bottomRight = points.OrderByDescending(point => point.X + point.Y).First(); var topRight = points.OrderByDescending(point => point.X - point.Y).First(); var bottomLeft = points.OrderBy(point => point.X - point.Y).First(); return [topLeft, topRight, bottomRight, bottomLeft]; }

这个顺序看起来只是个小细节,但它实际上决定了后面整条 OCR 流水线是不是成立。

在这个开发过程中,让我深有体会的是,做 det + rec 集成,千万不要只盯着最终识别文本看。把中间裁剪图导出来,是定位问题最快的方法之一。因为它能很快回答一个更基础的问题:到底是 det 框错了,还是 crop 错了,还是 rec 真不行。

3. 查找并点击

光把 det 模型跑起来,其实还不算完成。

因为对于 FlowBot 这种自动化工具,模型能力最终还是要落到一个具体动作上。

我这次把它落成了一个新的步骤类型:

public enum AutomationStepKind { TextCondition, RepeatUntilText, Swipe, Tap, Delay, Tone, Vibrate, FindAndTap }

同时保留两种文字来源:

public enum TextRecognitionMode { AccessibilityText, PpOcrV6Small }

这样FindAndTap在执行时就可以按识别方式分流:

public Task<FindAndTapResult> FindAndTapAsync( ScreenRegion region, TextRecognitionMode recognitionMode, string? className, string? text, int timeoutMilliseconds, CancellationToken cancellationToken) { return recognitionMode switch { TextRecognitionMode.PpOcrV6Small => FindAndTapWithOcrAsync(region, text, timeoutMilliseconds, cancellationToken), _ => FindAndTapWithAccessibilityAsync(region, className, text, timeoutMilliseconds, cancellationToken) }; }

OCR 模式下的核心循环其实很简单:

  1. 截图。

  2. 先 det。

  3. 每个框做 crop。

  4. 每个 crop 做 rec。

  5. 文本命中就点击。

foreach (var quad in detector.Detect(searchBitmap)) { usingvar quadBitmap = CropQuadBitmap(searchBitmap, quad); var actualText = recognizer.Recognize(quadBitmap); if (!ContainsText(actualText, expectedText)) { continue; } var globalQuad = quad.Select(point => new PointF(point.X + offsetX, point.Y + offsetY)).ToArray(); var centerX = (int)Math.Round(globalQuad.Average(point => point.X)); var centerY = (int)Math.Round(globalQuad.Average(point => point.Y)); await service.TapAsync(centerX, centerY, cancellationToken); returnnew FindAndTapResult(true, $"OCR 命中“{actualText}” 点击 ({centerX},{centerY})"); }

这里点击的不是普通矩形中心,而是四点框几何中心。这样做的好处是,如果检测框本身有一点倾斜或者旋转,点击点仍然会更贴近真实文字区域的中心。

验证测试

4. 最后

通过这两篇文章,我分享了在 .NET MAUI 里接入 PP-OCRv6 的完整过程,从单纯的识别,到检测 + 识别,再到查找并点击。希望能对大家在移动端自动化和 OCR 集成上有所帮助。

这两篇文章的代码和思路,都是在 FlowBot 项目里实践过的,已经在实际场景中验证了可行性。关于 FlowBot 后续整理好后会开源,届时大家可以直接参考和使用。

引入地址

相关新闻

  • Python 3.14无GIL性能实测与多线程优化分析
  • agent-MCP-A2A-ANP销售售后项目
  • AI+隐患排查|金汤令双AI引擎构建智慧隐患治理体系

最新新闻

  • 计算机Python毕设实战-网络音乐资源播放与歌单管理平台 基于 Python Web 的智能音乐娱乐平台【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 抖音小店一件代发需要准备哪些工具? - 抖掌柜
  • 医药AIGC实战:AI疾病筛查技术解析与应用
  • BQ41Z50数据闪存参数详解:Gas Gauging与RA Table配置实战
  • 专业靠谱场景细分的全国法帝诺厂商推荐 - 招财兔数字员工
  • 微信消息撤回机制解析与使用技巧

日新闻

  • 亨得利盐城维修点在哪里?手表维修保养地址指南**公示(2026年7月最新) - 亨得利官方
  • 提升.NET API安全性:Boxed.AspNetCore.Swagger认证授权最佳实践
  • 帝舵佛山**网点地址更新:2026年7月售后热线电话与服务客户指南 - 帝舵中国官方服务中心

周新闻

  • SaaS软件行业GEO实践:AI搜索时代的品牌可见性与获客新路径
  • 什么是PCTFE?医药高端包装的“防潮王牌“材料
  • 【JVM调优实战】16-可视化利器-JConsole-VisualVM-JMC

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号