如何使用macos自带的vision框架进行ocr

mcp 可以做成 免费 不付费

#!/usr/bin/env python3
"""
OCR MCP Server — 基于 macOS Vision 框架的图片文字识别
纯 Python stdlib 实现,零外部依赖。注册到 ~/.claude/settings.json 后永久生效。

注册方式(在 settings.json 的 mcpServers 中添加):
  "ocr": {
    "command": "python3",
    "args": ["buildchain/scripts/ocr_mcp_server.py"]
  }
"""

import json
import sys
import subprocess
import os

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
SWIFT_SCRIPT = os.path.join(SCRIPT_DIR, "ocr_image.swift")

TOOLS = [
    {
        "name": "ocr_image",
        "description": "对图片文件进行 OCR 文字识别,使用 macOS Vision 框架。支持中文、英文等多语言。"
                       "返回图片中的文字内容(纯文本),可过滤低置信度结果。"
                       "适用于:App Store 截图文字提取、钉钉文档截图信息提取、UI 截图文字识别等。",
        "inputSchema": {
            "type": "object",
            "properties": {
                "image_path": {
                    "type": "string",
                    "description": "图片文件的绝对路径,支持 PNG/JPG/HEIC 等格式"
                },
                "min_confidence": {
                    "type": "integer",
                    "description": "最低置信度阈值 (0-100),默认 30。建议中文截图设为 30,精确提取设为 70",
                    "default": 30
                },
                "lang": {
                    "type": "string",
                    "description": "识别语言代码,如 zh-Hans、en-US。默认 zh-Hans+en-US 自动检测",
                    "default": "zh-Hans"
                }
            },
            "required": ["image_path"]
        }
    },
    {
        "name": "ocr_image_json",
        "description": "对图片文件进行 OCR 文字识别,返回结构化 JSON(含每个文字块的置信度、坐标框)。"
                       "适用于:需要定位文字在图中位置的场景(如截图中的表格数据提取)。",
        "inputSchema": {
            "type": "object",
            "properties": {
                "image_path": {
                    "type": "string",
                    "description": "图片文件的绝对路径"
                },
                "min_confidence": {
                    "type": "integer",
                    "description": "最低置信度阈值 (0-100),默认 30",
                    "default": 30
                }
            },
            "required": ["image_path"]
        }
    }
]


def handle_initialize(req_id):
    return {
        "protocolVersion": "2024-11-05",
        "capabilities": {"tools": {}},
        "serverInfo": {
            "name": "ocr-mcp-server",
            "version": "1.0.0"
        }
    }


def handle_tools_list(req_id):
    return {"tools": TOOLS}


def handle_tools_call(req_id, params):
    name = params.get("name", "")
    args = params.get("arguments", {})

    if name == "ocr_image":
        image_path = args.get("image_path", "")
        min_conf = args.get("min_confidence", 30)
        lang = args.get("lang", "zh-Hans")

        result = subprocess.run(
            ["swift", SWIFT_SCRIPT, image_path, "--min", str(min_conf), "--lang", lang],
            capture_output=True, text=True, timeout=60
        )
        text = result.stdout.strip() or result.stderr.strip() or "(未识别到文字)"
        return {"content": [{"type": "text", "text": text}]}

    elif name == "ocr_image_json":
        image_path = args.get("image_path", "")
        min_conf = args.get("min_confidence", 30)

        result = subprocess.run(
            ["swift", SWIFT_SCRIPT, image_path, "--json", "--min", str(min_conf)],
            capture_output=True, text=True, timeout=60
        )
        text = result.stdout.strip() or result.stderr.strip() or "[]"
        return {"content": [{"type": "text", "text": text}]}

    return {"content": [{"type": "text", "text": f"未知工具: {name}"}], "isError": True}


def main():
    # 确保 Swift 脚本存在
    if not os.path.exists(SWIFT_SCRIPT):
        print(f"ERROR: Swift OCR 脚本不存在: {SWIFT_SCRIPT}", file=sys.stderr)
        sys.exit(1)

    initialized = False

    for line in sys.stdin:
        line = line.strip()
        if not line:
            continue

        try:
            req = json.loads(line)
        except json.JSONDecodeError:
            continue

        req_id = req.get("id")
        method = req.get("method", "")

        if method == "initialize":
            resp = {"jsonrpc": "2.0", "id": req_id, "result": handle_initialize(req_id)}
            sys.stdout.write(json.dumps(resp) + "\n")
            sys.stdout.flush()
            initialized = True
            continue

        if method == "notifications/initialized":
            continue  # 无需响应

        if not initialized:
            continue

        if method == "tools/list":
            resp = {"jsonrpc": "2.0", "id": req_id, "result": handle_tools_list(req_id)}
        elif method == "tools/call":
            resp = {"jsonrpc": "2.0", "id": req_id, "result": handle_tools_call(req_id, req.get("params", {}))}
        else:
            resp = {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": f"未知方法: {method}"}}

        sys.stdout.write(json.dumps(resp) + "\n")
        sys.stdout.flush()


if __name__ == "__main__":
    main()

#!/usr/bin/env swift
// ==========================================================================
//  ocr_image.swift — macOS Vision 框架 OCR 工具
//  使用 Apple 原生 Vision 框架提取图片中的文字,100% 离线,支持中英文
// ==========================================================================
//  用法:
//    swift buildchain/scripts/ocr_image.swift <图片路径>              # 纯文本输出
//    swift buildchain/scripts/ocr_image.swift <图片路径> --json       # JSON 输出(含置信度+坐标)
//    swift buildchain/scripts/ocr_image.swift <图片路径> --min 70     # 只保留置信度≥70%的结果
//    swift buildchain/scripts/ocr_image.swift <图片路径> --lang en-US # 指定识别语言
// ==========================================================================

import Vision
import AppKit
import Foundation

// ── 解析参数 ──
let args = CommandLine.arguments
guard args.count >= 2 else {
    print("用法: swift ocr_image.swift <图片路径> [--json] [--min 置信度%] [--lang 语言代码]")
    print("示例: swift ocr_image.swift screenshot.png --json --min 70")
    exit(1)
}

let imagePath = args[1]
var outputJSON = false
var minConfidence: Float = 0.3
var languages: [String] = ["zh-Hans", "en-US"]

var i = 2
while i < args.count {
    switch args[i] {
    case "--json":   outputJSON = true
    case "--min":    i += 1; minConfidence = (Float(args[i]) ?? 50) / 100.0
    case "--lang":   i += 1; languages = [args[i]]
    default: break
    }
    i += 1
}

// ── 加载图片 ──
guard let image = NSImage(contentsOfFile: imagePath),
      let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
    let msg = "ERROR: 无法加载图片 \(imagePath)"
    if outputJSON { print(#"{"error":"\#(msg)"}"#) } else { print(msg) }
    exit(1)
}

// ── 执行 OCR ──
let semaphore = DispatchSemaphore(value: 0)
var results: [[String: Any]] = []

let request = VNRecognizeTextRequest { request, error in
    defer { semaphore.signal() }
    if let error = error {
        if outputJSON { print(#"{"error":"\#(error.localizedDescription)"}"#) }
        else { print("ERROR: \(error.localizedDescription)") }
        return
    }
    guard let observations = request.results as? [VNRecognizedTextObservation] else { return }

    for obs in observations {
        guard let top = obs.topCandidates(1).first,
              top.confidence >= minConfidence else { continue }

        let text = top.string.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !text.isEmpty else { continue }

        results.append([
            "text": text,
            "confidence": Int(top.confidence * 100),
            "x": Int(obs.boundingBox.origin.x * 100),
            "y": Int(obs.boundingBox.origin.y * 100),
            "w": Int(obs.boundingBox.size.width * 100),
            "h": Int(obs.boundingBox.size.height * 100)
        ])
    }
}

request.recognitionLevel = .accurate
request.recognitionLanguages = languages
request.usesLanguageCorrection = true

let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
try? handler.perform([request])
semaphore.wait()

// ── 输出 ──
if outputJSON {
    if let jsonData = try? JSONSerialization.data(withJSONObject: results, options: .prettyPrinted),
       let jsonStr = String(data: jsonData, encoding: .utf8) {
        print(jsonStr)
    }
} else {
    for r in results {
        if let text = r["text"] as? String,
           let conf = r["confidence"] as? Int {
            let prefix = conf >= 70 ? "" : "(\(conf)%) "
            print("\(prefix)\(text)")
        }
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容