当前位置: 首页 > news >正文

macOS原生OCR:用Vision框架快速实现屏幕文字识别提取

在 macOS 上做 OCR,最容易想到的方案有两种:把图片上传到云端识别接口,或者在本地安装 Tesseract。而这个发布在 Hacker News Show HN 板块的项目给出了第三种做法:完全依赖 macOS 自带的原生 OCR 能力,把屏幕上看到的一段文字原样复现成可编辑文本,也就是项目名 Monkey see, monkey do 表达的意思——看到什么,就把什么复刻出来。这个思路的吸引力在于:不依赖网络,不把截图送出设备,不需要额外安装识别引擎,而且可以直接通过系统能力抓取任意窗口内容。

实际开发中,这种“看到但不能复制”的场景非常多,比如只读文档、视频字幕、老旧软件面板、禁止复制网页。与其手打,不如用一个几十行的 Swift 脚本完成从屏幕抓图到文字输出的完整链路。下面从 Vision 框架的核心概念讲起,逐步实现一个可运行的 macOS 屏幕 OCR 工具,并覆盖权限、精度、性能和扩展方向。跑通后,你会得到两个命令行工具:一个输入图片路径输出文本,另一个输入窗口编号输出该窗口内的文本。

1. 为什么说 macOS 原生 OCR 是常被忽略的现成能力

1.1 这个项目要解决的场景:可见但不可选

Monkey see, monkey do 这类工具的核心使用场景很明确:屏幕上明明显示着一行文字,但系统不让你选中、复制,或者根本没有对应的文本层。最常见的场景有几类:

  • 只读 PDF 和扫描文档,文字以图片形式存在,无法直接拷贝。
  • 视频里的字幕、会议录屏上的提示信息,想记录却只能暂停截图。
  • 老旧企业软件界面,文案写死在资源里,既不能复制也没有文本导出。
  • 网页开发者故意禁用文本选择,或者内容本身是图片验证码、图表。

这些场景如果手动重打一遍,效率低还容易出错。OCR 工具的作用就是让“眼睛看到的”直接变成“剪贴板里的”。项目名里的 monkey see,指截获屏幕内容;monkey do,指把识别出的文本交还给用户使用。所以这不是一个复杂的识别算法项目,而是一个典型的系统能力集成项目。

1.2 原生 OCR 的技术基础:Vision 框架

macOS 从 10.15 开始,在系统自带的 Vision 框架中加入了基于深度学习的文本识别能力,核心 API 是VNRecognizeTextRequest。OCR 的全称是 Optical Character Recognition,即光学字符识别,目标是从图像中提取可编辑的字符序列。Vision 的实现运行在设备本地,识别过程不需要联网,识别结果按文字行返回,每一行包含对应的文本框坐标和候选字符串。

到 macOS 13 之后,Vision 的文本识别准确率进一步提升,新的识别 revision 对中文、英文混排、表格数字都有更好的处理。从开发者角度看,macOS 的“实况文本”功能本质上也是基于这一套识别能力构建的。也就是说,系统已经内置了高质量 OCR 引擎,开发者的工作不是重新发明识别算法,而是把系统能力接入自己的业务场景。

这里有一个容易误解的地方:原生 OCR 不等于“随便拿一张模糊图片就能完美识别”。它擅长的是常见界面文本、截图、文档扫描件;对于复杂背景、艺术字体、低分辨率图片,仍然需要配合裁剪、放大和参数调整。

1.3 它和 Tesseract、云 OCR 的差异

选择 OCR 方案时,不能只看准确率一个维度。下面这张表可以帮助判断什么时候该用原生 Vision:

维度Vision 原生 OCRTesseract云 OCR
网络依赖需要联网
图片是否离开设备不会不会会上传
中文/多语言支持系统内置,按系统版本支持需要手动下载语言包一般支持较全
安装成本系统自带,无需安装需要 brew 或源码编译需要 SDK 和密钥
调用成本无按量费用通常按次计费
集成方式Swift/ObjC APIC/C++/Python/RESTHTTP API
适合场景macOS 工具、快捷脚本、隐私敏感场景跨平台离线批处理高精度多语种、复杂图像

选择原生 OCR 的最大理由不是“准确率一定最高”,而是集成成本最低、隐私边界最清晰。云 OCR 在复杂图像上准确率通常更高,但每次调用都要把截图传出去;对于这种抓取本地屏幕文字的小工具,隐私问题往往比精度更致命。Tesseract 的优点是跨平台,但中文识别需要额外语言包,且对 UI 字号较小的截图效果通常不如系统方案。

2. 理解 Vision 的识别链路,才知道参数该调哪里

2.1 一次识别请求的处理流程

Vision 的文本识别链路可以拆成四步:

  1. 准备图像。把图片文件转成CGImage,或者直接使用屏幕抓取的CGImage
  2. 创建请求。创建VNRecognizeTextRequest实例,设置识别精度、语言等参数。
  3. 交给处理器。用VNImageRequestHandler(cgImage:options:)承载图片,调用perform([request])
  4. 取回结果。从request.results拿到VNRecognizedTextObservation数组,每个观察对象对应一行文字。

用代码表达就是下面几行:

let request = VNRecognizeTextRequest() let handler = VNImageRequestHandler(cgImage: image, options: [:]) try handler.perform([request]) for case let observation as VNRecognizedTextObservation in request.results ?? [] { if let candidate = observation.topCandidates(1).first { print(candidate.string) } }

这个链路和 Core ML 的用法很像:handler 负责读图,request 负责描述任务,perform 负责执行。理解这一点后,后续所有参数调整都围绕 request 进行,抓图逻辑和识别逻辑可以完全分离。这也是为什么先写图片版脚本、再接入屏幕抓图是更合理的开发顺序。

2.2 请求参数与结果对象速查

VNRecognizeTextRequest的常用参数如下:

参数默认值作用
recognitionLevel.accurate识别精度,可选.fast.accurate
usesLanguageCorrectiontrue是否用语言模型修正拼写
recognitionLanguages空数组识别语言列表,空则按系统语言推断
minimumTextHeight0.0低于该高度比例的文字会被忽略
regionOfInterest全图只识别图像中的指定区域

结果对象VNRecognizedTextObservation包含两个重要成员:

  • boundingBox:识别文字行在图像中的位置,使用归一化坐标。
  • topCandidates(_:):按置信度排序的候选字符串数组。

实际项目中几乎只用topCandidates(1).first?.string取出最可能的字符串。少数场景会用前三个候选做纠错或人工确认,但普通抓字工具不需要。

2.3 坐标系统是最大的认知坑

boundingBox使用 Vision 归一化坐标系:原点 (0,0) 在图像左下角,(1,1) 在右上角。而 macOS 在 Core Graphics 里的常用显示坐标是原点在左上角。如果不做转换,直接把boundingBox画到图片上,会发现识别框整体上下颠倒。

转换公式很简单:

let normalized = observation.boundingBox let topLeftX = normalized.origin.x let topLeftY = 1.0 - normalized.origin.y - normalized.height

如果只是输出纯文本、不画框,这个坐标问题可以暂时忽略。但只要你想在截图上用红框标出识别到的文字,这一步就绕不开。另外,输出顺序也需要靠坐标排序,这一点在后文单独展开。

3. 从屏幕到文本:搭建最小抓字工具

3.1 环境准备:版本和工程形态

先确认本机环境,否则会在识别阶段无谓踩兼容性问题:

依赖最低要求建议
macOS10.15 以上13 以上,中文识别效果更好
Xcode Command Line Tools已安装保持最新
屏幕录制权限抓取其他 App 窗口时需要在系统设置中授权

可以用下面的代码查询当前系统支持的识别语言列表和可用 revision:

import Vision let supported = try VNRecognizeTextRequest.supportedRecognitionLanguages( for: .accurate, revision: VNRecognizeTextRequestRevision3 ) print(supported)

VNRecognizeTextRequestRevision3需要 macOS 13 及以上。低版本系统要把revision参数换成系统默认版本,或者直接调用不带 revision 参数的版本。如果原始项目没有给出明确版本要求,落地前要先确认自己系统的实际支持情况。

注意:swift直接运行脚本时,第一次会先编译,耗时比执行编译好的二进制慢不少,这是正常现象。

3.2 先写一个图片 OCR 脚本

为了让问题边界清晰,先实现输入图片、输出文本的最小脚本ocr.swift

#!/usr/bin/env swift import Foundation import AppKit import Vision guard CommandLine.arguments.count > 1 else { print("用法: swift ocr.swift <图片路径>") exit(1) } let path = CommandLine.arguments[1] guard let image = NSImage(contentsOfFile: path) else { print("无法读取图片: \(path)") exit(1) } var rect = NSRect(origin: .zero, size: image.size) guard let cgImage = image.cgImage(forProposedRect: &rect, context: nil, hints: nil) else { print("无法转换为 CGImage") exit(1) } let request = VNRecognizeTextRequest() request.recognitionLevel = .accurate request.recognitionLanguages = ["zh-Hans", "en-US"] request.usesLanguageCorrection = true let handler = VNImageRequestHandler(cgImage: cgImage, options: [:]) try handler.perform([request]) var lines: [String] = [] for case let observation as VNRecognizedTextObservation in request.results ?? [] { if let candidate = observation.topCandidates(1).first { lines.append(candidate.string) } } print(lines.joined(separator: "\n"))

这个脚本有几个关键点:

  • NSImage加载图片后再转CGImage,兼容常见图片格式;也可以直接用CGImageSource读取,更贴近底层但代码更长。
  • recognitionLanguages显式设置了["zh-Hans", "en-US"],避免系统语言环境不同导致的识别差异。
  • 输出顺序直接使用request.results的返回顺序,严格场景需要按坐标排序。

3.3 接入屏幕抓图:真正实现“看到什么复刻什么”

上面的脚本只解决了“图片到文字”,真正的 monkey see 还需要拿到屏幕内容。最简单的方式是用系统自带的screencapture命令:

screencapture -x -o /tmp/screen.png swift ocr.swift /tmp/screen.png

-x表示不播放抓图声音,-o表示不包含鼠标指针。这样可以得到全屏截图,再交给 OCR 脚本。如果想只抓一个区域,可以用-R x,y,w,h

screencapture -x -o -R 200,200,1000,600 /tmp/region.png

这种方式优点是零代码接入,缺点是每次都要落盘一次图片,而且无法精确指定某个窗口。如果要做成 Mac App,或者想对一个窗口

http://www.cnnetsun.cn/news/4296466.html

相关文章:

  • 不会写代码也能全栈上线?用 Codex 做出 AI 剧本杀的完整拆解
  • 用Python实现影视预告评论情感分析与可视化实战
  • 零基础AI编程入门:Claude Code与Codex实战指南
  • Python爬虫入门实战:18个案例掌握HTTP请求、数据解析与存储
  • 技术博客选题边界:为什么社会新闻不能写成CSDN教程
  • AI芯片竞争背后:GPU、CUDA与大模型算力生态解析
  • Claude记忆升级实战:跨聊天持久化项目上下文与Claude Code配置
  • STM32未用FLASH区域填充:链接脚本配置与固件校验优化
  • 零基础Python学习路径:从环境配置到爬虫与数据分析实战
  • 深入解析SambaNova RDU:可重构数据流芯片如何革新大模型推理
  • Win10+VS2019编译Curl 7.84.0:从环境配置到项目集成的完整指南
  • Java秋招面试核心考点全梳理:从基础到项目实践
  • 从零搭建弹幕标签点名系统:Python+Redis实现直播间指人游戏
  • SASS2MLIR:将NVIDIA机器码提升到MLIR实现GPU性能优化
  • 从robots.txt到Shelf Protocol:电商数据如何实现商业授权
  • VC6.0股票行情软件核心模块:多线程实时刷新与MFC界面优化
  • 迷你主机如何跑本地大模型?AMD Ryzen AI Max+ 395用统一内存突破显存瓶颈
  • AI画板不靠谱,查错却靠谱:PCB设计检查工具链实战
  • DeepSeek V4 Flash 0731 成本评估:API计费与本地部署全解析
  • Windows平台CMake 3.31.10深度解析:从部署、生成器选择到编码问题解决
  • figma爱丽丝测评:可动塑料小人如何治愈手办冷淡期
  • 从AI价值占比到AI工程化:普通团队的落地路径
  • 从点灯到做项目:32位单片机学习路径与工程化实践
  • 两年经验社招微信五轮面试全流程复盘与经验总结
  • 智能车竞赛新手备赛指南:从零到稳定完赛的完整路线图
  • 从零备战智能车竞赛:规则、硬件与PID调试全流程复盘
  • 轮腿机器人竞赛实战复盘:从机械结构到PID与视觉识别的工程优化
  • CodeBuddy NPC深度评测:从安装部署到团队级AI员工落地
  • 700个智能体并发请求Hugging Face:从限流原理到请求层设计实战
  • 时间步条件Transformer:单模型实现灵活多时效AI天气预报