
開發 macOS 應用時把屏幕上的文字“抓”下來復制到剪貼板是一個很常見的自動化需求。之前我一直用第三方 OCR 服務或者 Tesseract但配置麻煩、識別中文效果也不理想。后來發現 macOS 系統本身自帶了 OCR 能力通過 Vision 框架就能調用不需要聯網也不依賴任何第三方 SDK。看到有人分享了一個叫 “Monkey see, monkey do” 的小項目思路就是“眼睛看到什么就能復制什么”本質上就是屏幕截取 原生 OCR。這篇文章我就把這條鏈路完整拆解一遍帶你從零實現一個 macOS 原生 OCR 文字復制工具代碼可以直接跑。本文適合 macOS 開發者、自動化腳本愛好者也適合想了解 Vision 框架文本識別的大前端和 AI 方向讀者。學完你可以掌握如何調用 macOS 原生 OCR 識別圖片如何截取屏幕區域如何把識別結果寫入剪貼板并了解權限配置、性能優化和常見坑點。1. 背景macOS 上實現 OCR 有哪些方案1.1 什么是 OCROCROptical Character Recognition光學字符識別的目的是從圖像中提取文字。比如一張截圖、一張掃描件、甚至攝像頭拍到的畫面只要里面有文字OCR 就能把它們變成可編輯、可復制的文本。在 macOS 上做文字識別常見方案有方案類型優缺點Tesseract開源本地引擎免費但需要安裝語言包中文識別精度一般百度 OCR / 騰訊 OCR云端 API精度高但要聯網、有調用配額涉及隱私macOS Vision系統原生框架免費、離線、集成度高支持中文無需額外依賴從工程角度看如果你的工具只在 macOS 上跑且不想把用戶截圖上傳到云端Vision 框架是最合適的選擇。它內置在系統里性能和識別率都經過蘋果優化尤其是 macOS 12 之后對中文、日文、韓文的支持已經比較完善。1.2 “Monkey see, monkey do” 想表達什么“Monkey see, monkey do” 是一個英文俗語意思是“看到什么就模仿什么”。放到 OCR 工具里就是一個很形象的產品邏輯屏幕上出現什么文字工具就把它識別并復現出來。你可以想象這樣的使用場景看視頻時發現字幕里有想要保存的句子直接一框選就復制閱讀加密 PDF 或圖片型 PDF 時無法選中文字用 OCR 工具提取軟件界面上的報錯信息無法復制直接截屏識別開會時想把幻燈片里的文字快速做成筆記。這些需求都可以用 macOS 原生能力實現不需要購買專業軟件。1.3 為什么推薦使用系統原生 OCR使用 Vision 框架的好處很明顯離線可用不依賴網絡不把數據上傳到第三方服務器免費無限量本地計算沒有 API 配額和費用隱私安全截圖留在本機適合處理敏感文檔中文支持好系統語言包已經內置開箱即用與 macOS 系統深度集成可以配合快捷鍵、Automator、Shortcuts 使用。如果你的應用只面向 macOS優先用系統能力往往是性價比最高的方案。2. 環境準備與版本說明在開始寫代碼之前先確認你的開發環境和運行環境。2.1 操作系統版本Vision 框架從 macOS 10.15 開始引入文本識別能力但早期的VNRecognizeTextRequest僅支持英文。如果你需要識別中文建議使用 macOS 12 Monterey 及之后版本此時 Live Text 已內置識別質量和語言豐富度提升明顯。本文示例基于 macOS 13/14 實測理論兼容 macOS 12。如果你的系統是 macOS 10.15 或 11代碼大部分可用但中文識別可能受限。2.2 開發工具Xcode可以使用 Xcode 14 或更高版本命令行工具也可以單獨安裝Swift示例代碼基于 Swift 5Command Line Tools如果你不想建完整 Xcode 工程直接用swift命令編譯運行也可以。檢查 Command Line Tools 是否安裝xcode-select --install查看 Swift 版本swift --version2.3 運行權限如果你的 OCR 工具只識別圖片文件不需要額外權限。但如果你要實現屏幕實時取詞需要給終端或 App 授權“屏幕錄制”權限系統設置 → 隱私與安全性 → 屏幕錄制把你運行工具所在的終端 App比如 Terminal、iTerm勾選上。如果你要把工具打包成獨立 App還需在 App 的Info.plist中聲明keyNSScreenCaptureUsageDescription/key string需要截取屏幕內容以進行 OCR 文字識別/string如果是命令行工具沒有 Info.plist但系統仍會在第一次調用截屏 API 時彈出授權提示。2.4 示例項目結構為了讓示例盡量簡單我們不建 Xcode 工程直接用 Swift 腳本。項目結構如下monkey-see/ ├── main.swift ├── Package.swift # 可選如果使用 SwiftPM └── images/ └── sample.png你可以把main.swift當成獨立命令行工具運行。3. 核心原理Vision 框架如何做文字識別Vision 是蘋果提供的計算機視覺框架。它并不直接對外暴露 UI而是通過 request-handler 模式工作。3.1 核心概念VNRecognizeTextRequest文本識別請求對象負責配置識別參數VNImageRequestHandler圖像請求處理器負責把圖片數據交給 Vision 分析VNRecognizedTextObservation識別結果包含文字內容和坐標位置。一個完整的識別流程是把圖片轉換為CGImage創建VNRecognizeTextRequest創建VNImageRequestHandler并執行請求遍歷VNRecognizedTextObservation提取文本。3.2 識別精度設置recognitionLevel有兩個選項.fast速度快適合實時截屏.accurate精度高適合從圖片中提取文字。對于截圖場景建議先用.accurate提高準確率如果性能不夠再改為.fast。3.3 語言支持通過recognitionLanguages可以指定識別語言。常用語言代碼zh-Hans簡體中文en-US英文ja-JP日文ko-KR韓文。如果希望同時識別中英文可以傳入多個語言request.recognitionLanguages [zh-Hans, en-US]注意并非所有語言在所有系統版本上都可用建議做一次可用語言查詢。3.4 坐標系統Vision 的坐標系統與 UIKit/AppKit 不同。Vision 使用歸一化坐標原點在圖片左下角y 軸向上。如果你需要把文字框畫在截圖原圖上需要做坐標轉換。3.5 核心代碼示例下面是一個最小可用的 Swift 函數用來識別一張 PNG 圖片中的所有文字import Vision import AppKit func recognizeText(from image: NSImage) - String { guard let cgImage image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return } var resultText let request VNRecognizeTextRequest { request, error in guard let observations request.results as? [VNRecognizedTextObservation] else { return } for observation in observations { if let candidate observation.topCandidates(1).first { resultText candidate.string \n } } } request.recognitionLevel .accurate request.recognitionLanguages [zh-Hans, en-US] request.usesLanguageCorrection true let handler VNImageRequestHandler(cgImage: cgImage, options: [:]) try? handler.perform([request]) return resultText }這段代碼中topCandidates(1)返回置信度最高的一個候選文本usesLanguageCorrection會利用語言模型修正識別結果默認開啟try? handler.perform忽略錯誤實際項目中建議用do-catch處理。4. 完整實戰從屏幕截取到復制文字接下來我們實現一個完整的命令行工具用戶啟動程序后截取當前屏幕自動識別屏幕上的所有文字并復制到剪貼板。4.1 創建 Swift 腳本先創建一個文件夾mkdir monkey-see cd monkey-see touch main.swift用文本編輯器打開main.swift。4.2 獲取屏幕截圖macOS 截取主屏幕可以使用 Core Graphics 的CGDisplayCreateImage。這個 API 會返回一個CGImage可以直接交給 Vision 處理。import CoreGraphics func captureScreen() - CGImage? { let screenRect CGMainDisplayBounds() guard let image CGDisplayCreateImage(CGMainDisplayID()) else { return nil } return image }CGMainDisplayID()返回主顯示器 IDCGDisplayCreateImage會生成該顯示器的圖像。如果你想截取某個區域可以在生成圖片后使用cropping(to:)裁剪。4.3 對截圖進行 OCR 識別我們復用上一節的recognizeText函數但輸入變成CGImage避免 NSImage 轉換開銷import Vision func recognizeText(from cgImage: CGImage) - String { var resultText let request VNRecognizeTextRequest { request, error in guard error nil else { print(識別錯誤: \(error!.localizedDescription)) return } guard let observations request.results as? [VNRecognizedTextObservation] else { return } for observation in observations { if let candidate observation.topCandidates(1).first { resultText candidate.string \n } } } request.recognitionLevel .accurate request.recognitionLanguages [zh-Hans, en-US] request.usesLanguageCorrection true let handler VNImageRequestHandler(cgImage: cgImage, options: [:]) do { try handler.perform([request]) } catch { print(執行失敗: \(error.localizedDescription)) } return resultText }4.4 寫入剪貼板macOS 里寫入剪貼板最簡單的方式是使用NSPasteboardimport AppKit func copyToPasteboard(_ text: String) { let pasteboard NSPasteboard.general pasteboard.clearContents() pasteboard.setString(text, forType: .string) }注意命令行工具使用 AppKit 需要保證有圖形會話普通終端運行沒有問題。4.5 主程序組裝在main.swift里把這三步串起來import Foundation import AppKit import Vision import CoreGraphics // ... 上面定義的函數 ... main struct MonkeySee { static func main() { print(正在截取屏幕...) guard let screenImage captureScreen() else { print(截屏失敗) exit(1) } print(正在進行 OCR 識別...) let text recognizeText(from: screenImage) guard !text.isEmpty else { print(沒有識別到文字) exit(0) } copyToPasteboard(text) print(識別完成已復制以下內容到剪貼板) print(-----------------------------) print(text) } }如果你不想用main也可以直接把頂層代碼寫在main.swift中。4.6 編譯運行由于 Swift 命令行工具可以直接從源文件編譯執行swiftc -o monkey-see main.swift -framework Vision -framework AppKit -framework CoreGraphics然后運行./monkey-see首次運行可能會彈出“終端想要截取屏幕圖像”的授權提示點擊允許后需要重新運行。4.7 測試效果準備一張包含中英文文字的圖片或者打開任意網頁運行工具后正在截取屏幕... 正在進行 OCR 識別... 識別完成已復制以下內容到剪貼板 ----------------------------- 這個網站使用 Cookie 來增強您的瀏覽體驗。 Hello, welcome to my blog!打開文本編輯器粘貼就能看到完整內容。5. 進階實現矩形區域 OCR 和快捷鍵喚起整屏識別雖然簡單但實際使用時往往只需要識別某一塊區域。比如彈窗中的錯誤信息、視頻字幕、表格中的某一列。下面我們增加區域選擇能力。5.1 使用screencapture命令配合區域截圖最簡單的方式是調用系統自帶的screencapture命令行工具。它可以讓你手動框選區域。screencapture -i /tmp/ocr_selection.png如果希望截取后光標變成十字準線并允許用戶拖動選擇區域使用-i交互模式。截圖成功后生成的 PNG 文件再交給 OCR 識別。5.2 Swift 調用screencapture在 Swift 中可以用Process執行系統命令import Foundation func captureSelection() - URL? { let url URL(fileURLWithPath: /tmp/ocr_selection.png) let process Process() process.executableURL URL(fileURLWithPath: /usr/sbin/screencapture) process.arguments [-i, url.path] do { try process.run() process.waitUntilExit() if FileManager.default.fileExists(atPath: url.path) { return url } } catch { print(截屏失敗: \(error)) } return nil }注意screencapture在較新的 macOS 中路徑通常是/usr/sbin/screencapture也可能位于/sbin建議用xcrun --find screencapture查找。5.3 從文件加載圖片并識別拿到截圖文件后用 NSImage 加載if let url captureSelection() { guard let image NSImage(contentsOf: url), let cgImage image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { print(無法加載截圖) exit(1) } let text recognizeText(from: cgImage) copyToPasteboard(text) print(text) }5.4 綁定快捷鍵如果你希望按下某個快捷鍵就啟動“區域 OCR”流程可以使用全局熱鍵庫比如Carbon 的RegisterEventHotKey第三方庫HotKeySwiftPMmacOS 自帶 Shortcuts 快捷指令。用 Carbon 實現全局快捷鍵比較繁瑣這里給出思路。更推薦使用HotKey這個開源庫它封裝了事件監聽使用起來更現代。import HotKey let hotKey HotKey(key: .space, modifiers: [.control]) hotKey.keyDownHandler { // 執行區域截圖和 OCR startRegionCaptureAndRecognize() }注意第三方庫需要額外集成如果是個人學習完全可以用 Automator 或者 BetterTouchTool 去綁定快捷鍵。5.5 權限補充使用screencapture -i時系統同樣會要求屏幕錄制權限。如果出現黑屏或無法截圖請檢查權限設置。6. 常見問題與排查思路6.1 問題對照表問題現象常見原因解決思路截屏返回空圖沒有屏幕錄制權限系統設置中勾選終端的屏幕錄制權限識別不到任何文字圖片中沒有文字 / 識別語言未配置換測試圖片添加語言參數中文識別亂碼recognitionLanguages只設置了en-US添加zh-Hans坐標錯位Vision 坐標系與 AppKit 不一致轉換坐標y 1 - normalizedY運行效率低使用.accurate且圖片過大調整為.fast或縮小圖片尺寸命令行工具無法使用 AppKit未導入AppKit框架編譯時加-framework AppKit彈出安全提示系統首次請求屏幕錄制權限點擊允許后重啟終端6.2 識別不了中文怎么辦首先確認系統版本。macOS 12 以下對中文支持有限建議升級系統。其次在請求中明確指定中文request.recognitionLanguages [zh-Hans]如果仍無效用下面代碼查詢當前可用的識別語言let supported try? VNRecognizeTextRequest.supportedRecognitionLanguages(for: .accurate, revision: VNRecognizeTextRequestRevision3) print(supported ?? [])輸出結果會列出所有支持的語言代碼。6.3 截屏權限出現“系統數據占用過大”或無法截取這和 OCR 本身無關但很多用戶會遇到。如果你的 macOS 系統提示“系統數據占用過大”可以檢查“屏幕錄制”權限列表是否有殘留的已卸載 App。清理方法重置終端權限或重啟系統。如果鼠標光標在screencapture -i交互模式下不顯示可能是全屏截圖權限未開啟或者正在使用虛擬機。虛擬機環境下屏幕捕獲行為與真實 macOS 不同請參考虛擬機的顯示驅動配置。6.4 OCR 識別結果順序不對Vision 返回的觀察結果并不是絕對按閱讀順序排列的。解決辦法是手動排序let sortedObservations observations.sorted { a, b in if abs(a.boundingBox.midY - b.boundingBox.midY) 0.01 { return a.boundingBox.midY b.boundingBox.midY } else { return a.boundingBox.minX b.boundingBox.minX } }由于 Vision 坐標原點在左下角midY越大表示位置越靠上。按從下到上不這里我們是要按閱讀順序先按 y 降序上到下同一行內按 x 升序左到右。6.5 編譯時找不到 Vision 框架確保命令行編譯時添加了-framework Visionswiftc -o monkey-see main.swift -framework Vision -framework AppKit -framework CoreGraphics如果你使用 SwiftPM則需要在Package.swift中鏈接系統框架。7. 最佳實踐與工程建議7.1 合理選擇識別精度如果你的應用需要實時處理視頻幀建議使用.fast并在每幀之間做節流。對于靜態截圖.accurate更合適。7.2 提高識別準確率識別前對圖片做二值化或增強對比度將圖片縮放至合適大小過大或過小都會影響效果對旋轉的文字設置recognitionLevel .accurate但仍有限度對結果做后處理例如去掉多余空格、修正標點。7.3 隱私與安全不要把截屏圖片明文保存在磁盤上用完即刪如果網絡傳輸 OCR 結果務必加密如果你的 App 會處理敏感數據在隱私政策中明確說明在用戶授權前不要觸發截圖否則可能被系統拒絕。7.4 處理多語言建議根據用戶系統語言動態設置識別語言而不是寫死。可以通過Locale.preferredLanguages獲取當前語言映射到 Vision 的語言代碼。7.5 內存管理Vision 處理大圖時會占用較多內存。在命令行工具中識別完成后及時釋放對象。如果是 iOS/macOS App建議將識別任務放到后臺隊列DispatchQueue.global(qos: .userInitiated).async { let text recognizeText(from: cgImage) DispatchQueue.main.async { self.textView.string text } }7.6 日志與錯誤處理生產工具一定要有完整的錯誤日志。可以把每次識別耗時、識別語言、候選文字數量記錄下來方便后續調優。7.7 打包成 App如果你把工具分發給同事使用可以打包成.app并在Info.plist聲明權限。使用osascript或 Automator 也能快速做成“服務”菜單項無需 Xcode。7.8 與最新系統兼容macOS Sequoia 發布后很多用戶遇到“若要打開此 App你需要從 macOS 恢復啟動并將安全策略更改為完整安全”的問題。這本質上是自定義內核擴展或未簽名 App 的安全策略設置。對普通 OCR 工具來說建議使用 Developer ID 簽名避免用戶被安全策略卡住。7.9 性能優化方向如果你要做“全局 OCR 搜索”這類工具可以考慮監聽屏幕變化增量識別使用VNImageRequestHandler的regionOfInterest只識別變化區域使用 GPU 加速但 Vision 默認已經使用系統加速能力建立文字緩存避免重復識別。8. 總結與下一步學習方向通過本文的實戰你已經掌握了 macOS 原生 OCR 的核心鏈路使用VNRecognizeTextRequest識別圖片文字使用CGDisplayCreateImage或screencapture截取屏幕使用NSPasteboard寫入剪貼板處理屏幕錄制權限通過識別語言參數解決中文支持問題。這套方案非常適合做個人效率工具比如“截圖 OCR”“屏幕取詞”“離線文字提取”。如果想繼續深入可以嘗試在 iOS 上使用同樣的 Vision API 實現拍照取詞結合 Core ML 做自定義文字分類利用 Live Text 的底層能力做更聰明的 OCR 應用把 Swift 代碼封裝成命令行工具通過 Python 調用形成更靈活的自動化腳本。關于 Python 調用可以借助PyObjC框架但需要安裝對應 macOS 版本支持的 pyobjc-framework-Vision 包。環境配置時要注意 Python 版本與 PyObjC 的兼容性建議在虛擬環境中安裝。最后提醒一點屏幕錄制權限在開發調試階段經常被忽略一旦發現截屏全黑優先檢查“隱私與安全性”里的授權列表。調試完成后把權限限定在最小范圍保障用戶隱私。如果你也打算做一個類似的“看到什么就復制什么”的小工具不妨先從今天的示例改造起。加上一個快捷鍵再配合區域截圖就是一個相當順手的日常效率工具了。