本地离线 OCR 工具。识别全部在本机完成,无需联网、无需登录、无统计、无广告。
面向自动化场景:提供三种截屏方式(无障碍 / root / 录屏授权,自动挑选)、本地 HTTP 接口、 以及 MacroDroid / Tasker 插件能力,并内置边缘滑动手势触发器(含跟手滑出菜单)。
| 模块 | 能力 |
|---|---|
| 文字识别 | 三档本地模型:tiny(轻量快速)/ small(高精度)/ rapid(ML Kit 捆绑版,中英) |
| 截屏识别 | 无障碍截图(Android 11+,免 root 免授权)/ root screencap / MediaProjection 授权,按序自动挑选与回退 |
| 图片识别 | 指定图片路径直接识别,支持纯文本 / 含坐标 / 含包围盒三种输出 |
| 本地接口 | http://127.0.0.1:1666,支持 JSON / form / query 三种传参 |
| 自动化插件 | 完整 Tasker / Locale 协议,异步变量回传,MacroDroid 与 Tasker 均可调用 |
| 边缘手势 | 屏幕边缘悬浮手柄,8 方向滑动触发,每方向可独立调触发距离与行为 |
| 滑动菜单 | 滑过手柄跟手弹出一排图标,选中某项才向宏发事件 |
| 手柄黑名单 | 按前台应用隐藏手柄,同一份名单支持黑 / 白两种语义 |
| 防烧屏 | 手柄定时小幅垂直漂移,降低 OLED 残影 |
| 从最近任务隐藏 | 多任务界面不显示卡片,服务与手势照常运行 |
| 日志 | 崩溃自动记录,一键分享,便于反馈问题 |
| 项 | 要求 |
|---|---|
| Android 版本 | 8.0(API 24)及以上 |
| 架构 | arm64-v8a / armeabi-v7a |
| 必需权限 | 所有文件访问(用于读写图片) |
| 可选权限 | 无障碍(免授权截屏、手柄黑名单)、悬浮窗(后台弹截屏授权框)、root(免授权截屏) |
| 存储占用 | 约 46 MB(含本地模型) |
屏蔽系统侧滑返回依赖
setSystemGestureExclusionRects(),需 Android 10(API 29)及以上, 更低版本无此平台能力。
- 从 Releases 下载 APK 安装。
- 首次启动会申请「所有文件访问权限」。
- 打开 App 即自动启动 OCR 服务,无需手动点启动。
- 首次需在后台加载模型(约 1~3 秒),界面顶部会显示服务状态。
服务采用「HTTP 先上线、模型后台加载」:端口会立刻可连(
GET /last秒回), 但模型未就绪时POST /返回503、/last里modelReady为false。 自己写脚本调用时,请以modelReady或503作为「稍后重试」的信号。
界面只有两个页签:OCR设置 / 滑动触发。
「OCR设置」页顶部那张开关卡控制 OCR 服务本身(关掉后接口不再监听、也不开机自启), 它与边缘滑动手势互不影响 —— 只用手势触发宏的人可以把 OCR 服务关掉。 从最近任务划掉 App 时服务不会停止(
android:stopWithTask="false"), 以便外部自动化脚本的调用不因清理后台而中断。如需彻底停止,请在系统设置中「强行停止」。
不传图片路径时,服务自行截屏。三条路径按序自动挑选,前一条失败自动回退,不会因某条临时失效整体不可用:
| 优先级 | 方式 | 授权 | 说明 |
|---|---|---|---|
| ① | 无障碍截图 | 勾选一次,长期有效 | AccessibilityService.takeScreenshot(),需 Android 11+ |
| ② | root 截屏 | 一次 su 授权 |
su -c screencap -p <path>,启动时自动探测 |
| ③ | MediaProjection | 每次重启服务后需重新授权 | 前两者都不可用时的兜底 |
在「OCR设置」页点 无障碍截图,到系统「设置 → 无障碍」里勾选「小孙OCR 截图」。
该服务只保留截图一项能力:onAccessibilityEvent 为空实现,配置里没有声明任何
accessibilityEventTypes,因此收不到屏幕文本、控件树或点击按键事件,不能用于监听用户操作。
- Android 11+ 且不想给 root:用无障碍截图。免 root、免授权、不弹框、不弹确认,适合无人值守。
- 设备有 root:服务启动时自动启用 root 截屏,调用方无需任何授权步骤,也不会出现录屏确认框。
- 两者都没有:需在 App 内点一次「开启截屏授权」,服务重启后需重新授权。
界面「屏幕截屏识别」卡片会显示当前实际走的哪条路径,并提供三个入口按钮。
| 档位 | 来源 | 特点 |
|---|---|---|
tiny |
本地 native(PP-OCRv6) | 最快,模型最小,字典 6904 |
small |
本地 native(PP-OCRv6) | 字典 18708,保留更多标点与符号 |
rapid |
ML Kit(捆绑版) | 中英混排,输出行级包围盒与置信度;模型随 APK 打包,不依赖 Google Play 服务、不联网下载 |
档位是全局状态:App 内切换与外部接口切换共用同一份设置,切一次对所有后续请求生效。
输出格式三选一:
type |
输出 |
|---|---|
1 |
纯文本 |
2 |
JSON 数组(含坐标) |
3 |
详细 JSON(含矩形) |
坐标基于截屏原始像素,可直接用于点击坐标。
在屏幕边缘放一个可拖动的手柄,手指从手柄上划过设定距离即触发(滑过即响应,不必抬手)。 共 8 个方向(上下左右 + 四个斜向),每个方向的触发距离可单独调,也可让某个方向不响应。
设置入口:App 主界面 → 滑动触发 页。
手势引擎本身不执行任何动作,只负责告诉 MacroDroid / Tasker「某个方向的手势成立了」, 具体动作由宏决定。
-
「滑动触发」页 → 打开手势设置,添加手柄(位置、宽度、颜色、可触发方向)
-
在 MacroDroid 里新建宏 → 触发器 → 小孙OCR · 边缘滑动事件
-
在插件配置界面选定方向
-
同一页可选 滑过之后:
跟随手柄—— 按手柄里该方向当前的设定走(默认)直触—— 不弹菜单,抬手立刻发事件菜单—— 弹出图标,选中某项才发事件
这一项改的就是手柄上该方向的设置,两处永远是同一份数据,改哪边都行。
-
展开 Output variables,把菜单变量映射到宏变量
方向与「滑过之后」都是手柄级别、不是宏级别的。 同一方向挂了两个宏, 该方向设成直触则两个宏都是直触。原因很直接:菜单要在向宿主发出查询之前 就弹出来,那个时刻服务还不知道这次滑动属于哪个宏。
| 该方向设为 | 滑过触发距离后 |
|---|---|
| 菜单 | 弹出一排按钮,选中某项才发事件 |
| 直触 | 不弹菜单,抬手立刻发事件,宿主立刻收到方向 |
默认全部为菜单。手柄编辑页的「可触发方向」上方有一行实时汇总,直接列出 「哪些方向滑过会弹菜单」;旁边有「全部设为菜单 / 全部设为直触」两个一键按钮。
弹菜单只取决于三件事:该方向已启用、该方向是菜单模式、菜单总开关开着且有菜单项。 与「该方向有没有配触发器」无关 —— 没配触发器的方向滑过去照样会弹。 不想某个方向弹,把它关掉或切成「直触」即可。
开启后,滑过触发距离不再立即触发宏,而是弹出一排按钮;继续滑到某一项上(跟手高亮), 抬手才选中它。
没选中就不触发:抬手时手指不在任何按钮上,或这批触摸被系统抢走,两种情况都不发通知, 宿主侧完全没反应(不会出现「触发了但变量是空的」)。
配置页:滑动触发 → 滑动菜单。
| 分组 | 可调项 |
|---|---|
| 开关 | 启用 / 关闭 |
| 外观 | 图标大小、显示文字、文字大小、文字间距、浅色圆底 |
| 布局 | 每排数量、逐排数量(可逐排单独覆盖)、图标间距、每排间距 |
| 配色 | 圆底颜色、文字颜色、选中颜色(#RGB / #ARGB / #RRGGBB / #AARRGGBB) |
| 交互 | 热区大小(命中半径 = 图标半径 × 倍率,100%~300%,带感应范围预览) |
| 菜单项 | 添加 / 改名 / 选图标 / 排序 / 删除 |
菜单项三个字段:
| 字段 | 说明 |
|---|---|
| 名字 | 按钮上显示的文字,必填 |
| 动作值 | 回传给宏的 %xsocr_menu_action;留空则与名字相同 |
| 图标 | app:包名 / file:路径 / 资源名;留空则显示名字前两个字 |
排列方式:图标沿 45° 斜线从左下排到右上,每排各自居中;一排排满就在上方另起一排。
指定「在某些应用里不显示手柄」—— 打游戏时不再挡住技能键,看视频时不再盖住画面。
设置入口:滑动触发 → 手柄黑名单。
| 项 | 默认 | 说明 |
|---|---|---|
| 按应用隐藏手柄 | 关 | 总开关。关掉与没有这个功能时完全一样 |
| 名单语义 | 黑名单 | 黑名单 = 名单内不显示;白名单 = 只有名单内显示 |
| 应用名单 | 空 | 同一份列表,两种语义共用,切换语义不必重新录 |
- 打游戏、看视频时嫌手柄碍事 → 用黑名单,把那些应用拉进来
- 平时不想要手柄,只在少数几个应用里用 → 用白名单,把那几个应用设为白名单
前台应用由无障碍服务的窗口状态事件判定,切过去那一刻即生效,无残留。 无障碍未连通时黑名单整体不生效、手柄照常显示 —— 拿不到前台应用名时,宁可让手柄照旧出现, 也不会因为读不到而把用户的手柄误隐藏。黑名单页会显示侦测通道的当前状态与授权入口。
手柄是常驻悬浮窗,OLED 屏长期固定位置会留下残影。开启后手柄每隔一段时间在垂直方向 随机挪动几个像素(默认开,可关)。
| 项 | 默认 | 范围 |
|---|---|---|
| 防止烧屏漂移 | 开 | — |
| 漂移幅度 | 8 dp |
0 ~ 30(0 = 原地不动) |
| 漂移间隔 | 60 秒 |
15 ~ 300 |
漂移只改窗口的垂直位置、不写回用户拖拽的位置;菜单展开时、拖拽中、手指按在手柄上时、 以及息屏状态下都不漂移。
手势导航下系统从屏幕边缘往内判定「返回」,优先级高于应用窗口 —— 手柄贴边时手指刚碰上去
就被系统当成返回吃掉。本应用用平台 API setSystemGestureExclusionRects()(API 29+)申请排除区,
属于通用平台能力,非特定厂商定制。
- 每个手柄有一个「屏蔽本条手柄区域的系统手势」开关(默认开),关掉的手柄不上报排除区。
- Android 10 以下无此能力,属系统限制。此时可把手柄挪到屏幕偏中间的位置避开边缘返回区, 或改用三键导航。
- 单个手柄可调宽度范围
0 ~ 200 dp。
本 App 是一个 Tasker / Locale 兼容插件,MacroDroid 与 Tasker 均可直接调用。
📖 常量表、能力位、协议细节见 MACRODROID_PLUGIN_GUIDE.md。
-
新建宏 → 动作 → Tasker/Locale Plugin
-
在插件列表里选 小孙OCR
-
在配置界面设置:
项 说明 图片来源 截取当前屏幕 / 指定图片路径 模型档位 tiny/small/rapid输出格式 纯文本 / 含坐标 指定文本(可选) 要查找的文字 匹配方式 包含文本 / 匹配文本(完全一致) -
保存(保存时才会把变量清单告知宿主)
-
在动作里展开 Output variables,把需要的变量映射到宏变量
| 变量 | 类型 | 内容 |
|---|---|---|
%xsocr_text |
文本 | 识别出的全部文本(多行用换行或空格拼接) |
%xsocr_match |
文本 | 指定文本是否找到:true / false |
%xsocr_x |
文本(数字) | 命中处的 x 坐标;未找到为 -1 |
%xsocr_y |
文本(数字) | 命中处的 y 坐标;未找到为 -1 |
%xsocr_error |
文本 | 出错信息(失败时才有内容) |
%xsocr_time |
文本 | 耗时(毫秒) |
%xsocr_tier |
文本 | 实际使用的档位 |
触发器路径回传的是另一组变量,手势本身不发识别请求:
| 变量 | 类型 | 内容 |
|---|---|---|
%xsocr_menu_index |
文本(数字) | 滑动菜单选中项序号(从 0 开始) |
%xsocr_menu_text |
文本 | 选中项的名字 |
%xsocr_menu_action |
文本 | 选中项的动作值 |
没选中任何项时,这三个变量都不传。手势方向不单独回传:插件在收到查询时已内部完成 方向匹配,不匹配的这次直接回「不满足」,宏根本不会跑。
| 方式 | 行为 | 例子(查找「确定」) |
|---|---|---|
| 包含文本 | 文本块含这几个字即算命中 | 匹配「确定提交」「请点确定」 |
| 匹配文本 | 整个文本块与它完全一致 | 只匹配「确定」 |
两者均不区分大小写,取第一个命中的文本块。
MacroDroid 在后台触发插件时,App 可能处于后台或已被回收。要让插件能自动弹出截屏授权框, 需要悬浮窗权限(Android 10+ 只允许拥有该权限的应用从后台启动 Activity)。 开启方式:「OCR设置」页 → 屏幕截屏识别卡片 → 悬浮窗权限 按钮。
不开也能用,但需先手动打开一次 App 完成截屏授权;授权在服务存活期间有效, 服务重启后需重新授权。
使用以下任一种方式时,连悬浮窗权限都不需要:
| 方式 | 条件 | 效果 |
|---|---|---|
| 无障碍截图 | Android 11+,勾选一次 | 免 root、免授权、不弹框 |
| root 截屏 | 设备有 root | 免授权、不弹框 |
两者都会让 screenCaptureReady 直接为 true,插件跳过整个授权流程。
- 地址:
http://127.0.0.1:1666
| 路由 | 说明 |
|---|---|
POST / |
识别(path 可选,不传则自动截屏) |
GET /last |
读取上一次识别结果(JSON) |
GET /last?format=text |
同上,返回纯文本 |
GET /ocr?format=text |
一步完成「取图 → 识别 → 返回文本」 |
支持 JSON Body / form 表单 / query 三种方式:
| 字段 | 说明 |
|---|---|
path |
图片绝对路径(需文件权限) |
type |
1 = 纯文本 2 = JSON 数组(含坐标) 3 = 详细 JSON(含矩形) |
tier |
可选:tiny(默认)/ small / rapid。切换后持续生效 |
action |
可选:setTier 表示只切档位、不做识别 |
响应为 JSON 文本:成功 {"status":200,...},失败 {"status":400/404/500/503,"data":"Error: ..."}。
HTTP 状态码与响应体里的 status 保持一致,因此只看状态码的调用方(curl -f、OkHttp 等)
也能正确判错。
唯一例外:
GET /last恒回 HTTP 200 —— 它是探活接口,只要服务在监听就算通; 「还没有识别记录」属于业务状态,写在 body 的status里。不支持 base64 传图。 改用
path后由服务端按需降采样解码,内存可控; base64 会让请求体膨胀 33%,一张 4K 图峰值内存可达 60 MB+,低端机容易 OOM。
# 启动后台服务(供自动化脚本使用)
am start-foreground-service com.xiaosun.ocr/.OCRService
# 传路径识别
curl -X POST http://127.0.0.1:1666 -H "Content-Type: application/json" \
-d '{"path":"/sdcard/test.png","type":1}'
# 不传 path:自动截屏识别
curl -X POST http://127.0.0.1:1666 -H "Content-Type: application/json" \
-d '{"type":3}'
# 只切换模型档位(不识别)
curl -X POST http://127.0.0.1:1666 -H "Content-Type: application/json" \
-d '{"tier":"small","action":"setTier"}'
# 识别的同时切换档位
curl -X POST http://127.0.0.1:1666 -H "Content-Type: application/json" \
-d '{"path":"/sdcard/test.png","type":1,"tier":"small"}'
# 一步完成截屏+识别,直接拿纯文本
curl "http://127.0.0.1:1666/ocr?format=text"
# 读取上一次识别结果
curl "http://127.0.0.1:1666/last?format=text"// Auto.js:传路径
var res = http.post("http://127.0.0.1:1666",
{ "path": "/sdcard/test.png", "type": "1" });
log(res.body.string());「OCR设置」页有一张开关卡:从最近任务隐藏(默认关)。
开启后多任务界面里不再出现本应用的卡片,但服务照跑、手势照用,从桌面图标照常进入。 开关拨动当场生效,无需切出去再进来。
与「划掉 App 服务也不停」不冲突:
stopWithTask="false"管的是服务存活, 这个开关管的是卡片显不显示,两件事可以同时要。
- 识别全程在本机完成,不联网、不上传任何图片或文本。
- 无账号、无登录、无统计上报、无广告。
- 无障碍服务只保留截图能力,不读取屏幕文本、不监听控件与按键事件。
- 应用内日志只写在应用私有目录,仅在你主动点「分享日志」时才会离开设备。
App 自带日志:崩溃自动记录,用户只需点一次「分享日志」。
- 复现一次问题(崩溃无需刻意复现,发生时自动记下)
- 打开 App →「滑动触发」或「OCR设置」页 → 最下面的日志卡片
- 点 分享日志 → 选微信 / QQ / 邮件发出去
(发不出去就点 导出到下载,文件落在
Download/小孙OCR日志_日期时间.txt) - 想先自己看一眼:点 查看日志,弹窗里长按可只复制关键几行
「分享日志」会自动带上设备信息(机型 / 系统版本 / ROM / 屏幕参数)。
日志位于 Android/data/com.xiaosun.ocr/files/logs/xsocr_log.txt,
放在应用外部私有目录:不需要存储权限、卸载时自动清理、可通过 FileProvider 合法分享。
文件超过 512 KB 会轮转为 xsocr_log.old.txt,最多占两份空间。
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-arm64
export ANDROID_HOME=/opt/android-sdk
cd /sdcard/AndroidIDEProjects/ocr
bash ./gradlew assembleDebug --console=plain产物:app/build/outputs/apk/debug/app-debug.apk
/sdcard不支持执行位,请用bash ./gradlew而非./gradlew。- 受限环境中若出现
AAPT2 Daemon startup failed,可改用 Android Studio / AndroidIDE 构建。- 首次构建需联网下载 AGP 与依赖。
- MACRODROID_PLUGIN_GUIDE.md —— 插件协议完整说明
- Releases —— 版本下载