"""屏幕 OCR 识别(RapidOCR,ONNX 推理,跨平台:Windows/Linux/macOS)。 用于"条件判断"步骤的 ocr 选择器:截屏 → 识别文字 → 按关键词匹配 → 可点击。 UI 树里找不到的文字(图片、画布、WebView 渲染的文字)也能识别。 依赖 rapidocr_onnxruntime(中英文模型随包内置,无需下载/联网): pip install rapidocr_onnxruntime 服务器(无显示器)环境建议把 opencv-python 换成 opencv-python-headless。 坐标约定:返回 box 为像素坐标(原点左上),与 u2 的 d.click 一致。 """ import threading import numpy as np from core.logger import get_logger _log = get_logger("core.ocr") _engine = None _engine_lock = threading.Lock() def _get_engine(): global _engine with _engine_lock: if _engine is None: from rapidocr_onnxruntime import RapidOCR _log.info("初始化 RapidOCR 引擎(首次约 1-2 秒加载模型)") _engine = RapidOCR() return _engine def available(): """OCR 是否可用。返回 (ok, hint)。""" try: _get_engine() return True, "" except Exception as e: return False, f"OCR 不可用(需 pip install rapidocr_onnxruntime): {e}" def recognize(image): """识别 PIL Image 上的文字。 返回 [{text, score, box}],box=[x1,y1,x2,y2] 像素坐标(原点左上,与 u2 一致)。 """ engine = _get_engine() # PIL(RGB) → ndarray(BGR),与 OpenCV 约定一致 arr = np.array(image.convert("RGB"))[:, :, ::-1] results = [] try: # 引擎实例并发调用加锁保护(识别本身约 0.2-0.5s,锁开销可忽略) with _engine_lock: result, _ = engine(arr) except Exception as e: _log.warning(f"OCR 识别异常: {e}") return results for item in result or []: box4, text, score = item xs = [p[0] for p in box4] ys = [p[1] for p in box4] results.append({"text": text, "score": float(score), "box": [round(min(xs)), round(min(ys)), round(max(xs)), round(max(ys))]}) return results def find_on_screen(image, keyword): """在截图上查找包含 keyword 的文字。 返回 (found, center_xy, matched_text);center_xy 为**关键词**中心像素坐标 (可点击),未命中返回 (False, None, "")。 OCR 一行常含多段文字(如 "医值得推荐#苏州济世璞真…展开"),若直接点整块 中心会偏离关键词很远。命中块文本较长时,按关键词在文本中的位置比例估算 x (块内文字近似等宽),y 取块中心——中文场景估算偏差小,点击能落准。 """ for r in recognize(image): text = r["text"] if keyword in text: b = r["box"] if len(text) <= 12: # 短文本:整块中心即关键词中心 return True, ((b[0] + b[2]) // 2, (b[1] + b[3]) // 2), text idx = text.find(keyword) ratio = (idx + len(keyword) / 2) / len(text) cx = b[0] + int((b[2] - b[0]) * ratio) cy = (b[1] + b[3]) // 2 return True, (cx, cy), text return False, None, ""