尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

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 的文本识别链路可以拆成四步准备图像。把图片文件转成CGImage或者直接使用屏幕抓取的CGImage。创建请求。创建VNRecognizeTextRequest实例设置识别精度、语言等参数。交给处理器。用VNImageRequestHandler(cgImage:options:)承载图片调用perform([request])。取回结果。从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或.accurateusesLanguageCorrectiontrue是否用语言模型修正拼写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 窗口时需要在系统设置中授权可以用下面的代码查询当前系统支持的识别语言列表和可用 revisionimport 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,hscreencapture -x -o -R 200,200,1000,600 /tmp/region.png这种方式优点是零代码接入缺点是每次都要落盘一次图片而且无法精确指定某个窗口。如果要做成 Mac App或者想对一个窗口
返回列表