"""导图图片 OCR:PowerShell WinRT Windows.Media.Ocr(zh-Hans-CN),坐标还原阅读顺序""" import re import subprocess import tempfile from pathlib import Path from .config import OCR_DIR # WinRT StorageFile 无法处理含中文的路径 → 中间文件一律用纯 ASCII 临时目录 ASCII_TMP = Path(tempfile.gettempdir()) / "yawen_ocr" PS_TEMPLATE = r"""$ErrorActionPreference = 'Stop' Add-Type -AssemblyName System.Runtime.WindowsRuntime $null = [Windows.Storage.StorageFile, Windows.Storage, ContentType = WindowsRuntime] $null = [Windows.Media.Ocr.OcrEngine, Windows.Foundation, ContentType = WindowsRuntime] $null = [Windows.Foundation.IAsyncOperation`1, Windows.Foundation, ContentType = WindowsRuntime] $null = [Windows.Graphics.Imaging.BitmapDecoder, Windows.Foundation, ContentType = WindowsRuntime] $asTaskGeneric = ([System.WindowsRuntimeSystemExtensions].GetMethods() | Where-Object { $_.Name -eq 'AsTask' -and $_.GetParameters().Count -eq 1 -and $_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1' })[0] function Await($WinRtTask, $ResultType) { $asTask = $asTaskGeneric.MakeGenericMethod($ResultType) $netTask = $asTask.Invoke($null, @($WinRtTask)) try { $netTask.GetAwaiter().GetResult() } catch { $ex = $_.Exception while ($ex.InnerException) { $ex = $ex.InnerException } Write-Output "AWAIT-ERR: $($ex.GetType().FullName): $($ex.Message)" throw } } $imgPath = '{{IMG}}' $outFile = '{{OUT}}' $file = Await ([Windows.Storage.StorageFile]::GetFileFromPathAsync($imgPath)) ([Windows.Storage.StorageFile]) $stream = Await ($file.OpenAsync([Windows.Storage.FileAccessMode]::Read)) ([Windows.Storage.Streams.IRandomAccessStream]) $decoder = Await ([Windows.Graphics.Imaging.BitmapDecoder]::CreateAsync($stream)) ([Windows.Graphics.Imaging.BitmapDecoder]) $bitmap = Await ($decoder.GetSoftwareBitmapAsync()) ([Windows.Graphics.Imaging.SoftwareBitmap]) $engine = [Windows.Media.Ocr.OcrEngine]::TryCreateFromUserProfileLanguages() if ($null -eq $engine) { $engine = [Windows.Media.Ocr.OcrEngine]::TryCreateFromLanguage([Windows.Globalization.Language]::new('zh-Hans-CN')) } $result = Await ($engine.RecognizeAsync($bitmap)) ([Windows.Media.Ocr.OcrResult]) $sb = New-Object System.Text.StringBuilder foreach ($line in $result.Lines) { $firstWord = $line.Words | Select-Object -First 1 $x = [int]$firstWord.BoundingRect.X $y = [int]$firstWord.BoundingRect.Y $text = $line.Text $null = $sb.AppendLine("[$x,$y] $text") } [System.IO.File]::WriteAllText($outFile, $sb.ToString(), [System.Text.Encoding]::UTF8) Write-Host "DONE lines=$($result.Lines.Count)" """ def _reorder(lines: list[tuple[int, int, str]]) -> str: """按坐标还原导图阅读顺序:行优先(y 容差 60px),行内按 x""" if not lines: return "" lines.sort(key=lambda t: t[1]) rows: list[list[tuple[int, int, str]]] = [] cur_y, cur_row = None, [] for x, y, text in lines: if cur_y is None or abs(y - cur_y) <= 60: cur_row.append((x, y, text)) cur_y = y if cur_y is None else min(cur_y, y) else: rows.append(sorted(cur_row, key=lambda t: t[0])) cur_row, cur_y = [(x, y, text)], y if cur_row: rows.append(sorted(cur_row, key=lambda t: t[0])) return "\n".join(" ".join(t[2] for t in row) for row in rows) def _prepare(img_path: str) -> str: """预处理:EXIF 转正 + 缩放到 OCR 引擎可处理尺寸(≤2000px),存纯 ASCII 路径""" try: from PIL import Image, ImageOps im = Image.open(img_path) im = ImageOps.exif_transpose(im) w, h = im.size if max(w, h) > 2000: r = 2000 / max(w, h) im = im.resize((int(w * r), int(h * r)), Image.LANCZOS) ASCII_TMP.mkdir(parents=True, exist_ok=True) small = ASCII_TMP / f"{Path(img_path).stem}_small.jpg" im.convert("RGB").save(small, quality=92) return str(small) except Exception: return img_path # PIL 失败就用原图 def ocr_image(img_path: str) -> dict: """OCR 一张图片,返回排序后的文本""" target = _prepare(img_path) img = Path(target) OCR_DIR.mkdir(parents=True, exist_ok=True) out = OCR_DIR / f"{img.stem}_ocr.txt" ps = OCR_DIR / f"{img.stem}_ocr.ps1" ps.write_text(PS_TEMPLATE.replace("{{IMG}}", str(img)) .replace("{{OUT}}", str(out)), encoding="utf-8-sig") # BOM:PowerShell 5.1 按 UTF-8 解析脚本 try: r = subprocess.run(["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", str(ps)], capture_output=True, text=True, encoding="gbk", errors="replace", timeout=180) except Exception as e: return {"ok": False, "error": f"OCR 进程失败: {e}"} if not out.exists(): return {"ok": False, "error": (r.stderr or r.stdout or "OCR 无输出")[:300]} raw = out.read_text(encoding="utf-8", errors="ignore") lines = [] for ln in raw.splitlines(): m = re.match(r"\[(\d+),(\d+)\] (.*)", ln) if m: lines.append((int(m.group(1)), int(m.group(2)), m.group(3).strip())) text = _reorder(lines) if not text.strip(): return {"ok": False, "error": "OCR 未识别到文字(图片可能不清晰)"} return {"ok": True, "text": text, "lines": len(lines)}