// ============================================================ // UmiOcrClient:Umi-OCR 本地 OCR 引擎(hiroi-sora/Umi-OCR,离线免费) // · 前提:用户在本机运行 Umi-OCR 主程序,全局设置里开启 HTTP 服务(默认开启,端口 1224) // · POST {BaseUrl}/api/ocr application/json: // { "base64": "<纯base64无前缀>", "options": { "data.format": "dict" } } // · 响应: code=100 成功;data=[{ text, score, box:[[x,y]×4 左上/右上/右下/左下], end }] // code=101 无文本(data 为原因字符串);其余 code 失败 // · 地址存 baidu_ocr.json 的 umi_url(OCR设置窗口写,默认 http://127.0.0.1:1224) // · 本地引擎无需 API Key,IsConfigured 恒 true // ============================================================ using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Net; using System.Text; using System.Threading.Tasks; using System.Web.Script.Serialization; namespace pdfbt { /// Umi-OCR 本地引擎客户端(单实例)。 internal sealed class UmiOcrClient { private readonly JavaScriptSerializer _json = new JavaScriptSerializer(); private string _baseUrl = "http://127.0.0.1:1224"; /// 状态提示,接 OcrClient.StatusChanged。 public event Action StatusChanged; public bool IsConfigured { get { return true; } // 本地引擎无需 Key } /// 服务地址由 OcrClient 从 baidu_ocr.json 的 umi_url 注入(热更新)。 public void SetBaseUrl(string url) { url = (url == null ? "" : url.Trim()); if (url.Length == 0) url = "http://127.0.0.1:1224"; if (!url.StartsWith("http")) url = "http://" + url; _baseUrl = url.TrimEnd('/'); } /// 识别一块位图 → 文本行列表(带位置)。JSON POST 到本机 Umi-OCR。 public Task> RecognizeAsync(Bitmap crop, int timeoutMs) { if (crop == null || crop.Width <= 0 || crop.Height <= 0) return Task.FromResult(new List()); return Task.Factory.StartNew(() => { var sw = System.Diagnostics.Stopwatch.StartNew(); var result = RequestAndParse(crop, timeoutMs); Notify("Umi-OCR·" + result.Count + "条 " + sw.ElapsedMilliseconds + "ms"); return result; }); } private List RequestAndParse(Bitmap crop, int timeoutMs) { // PNG base64(无 data: 前缀);PNG 保坐标精度,本地服务无 4MB 限制 string b64; using (var ms = new MemoryStream()) { crop.Save(ms, ImageFormat.Png); b64 = Convert.ToBase64String(ms.ToArray()); } string req = _json.Serialize(new Dictionary { { "base64", b64 }, { "options", new Dictionary { { "data.format", "dict" } } } }); string body; using (var wc = new WebClient()) { wc.Encoding = Encoding.UTF8; wc.Headers["Content-Type"] = "application/json"; wc.Proxy = null; // 本地地址不走系统代理(配了代理会把 127.0.0.1 也代理走) body = wc.UploadString(_baseUrl + "/api/ocr", "POST", req); } var resp = _json.Deserialize>(body); if (resp == null || !resp.ContainsKey("code")) throw new Exception("Umi-OCR 返回解析失败"); int code; int.TryParse(Convert.ToString(resp["code"]), out code); if (code == 101) return new List(); // 无文本,不算错误 if (code != 100) throw new Exception("Umi-OCR 错误 code=" + code + ": " + Convert.ToString(resp.ContainsKey("data") ? resp["data"] : "")); var items = new List(); var arr = resp.ContainsKey("data") ? resp["data"] as System.Collections.ArrayList : null; if (arr != null) { foreach (Dictionary w in arr) { if (w == null) continue; var boxArr = w.ContainsKey("box") ? w["box"] as System.Collections.ArrayList : null; if (boxArr == null || boxArr.Count < 4) continue; var pts = new PointF[4]; for (int i = 0; i < 4; i++) { var xy = boxArr[i] as System.Collections.ArrayList; float x, y; float.TryParse(Convert.ToString(xy != null && xy.Count > 0 ? xy[0] : "0"), out x); float.TryParse(Convert.ToString(xy != null && xy.Count > 1 ? xy[1] : "0"), out y); pts[i] = new PointF(x, y); } float score = 0.95f; if (w.ContainsKey("score")) { float sv; if (float.TryParse(Convert.ToString(w["score"]), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out sv)) score = sv; } items.Add(new OcrItem { Text = Convert.ToString(w.ContainsKey("text") ? w["text"] : ""), Box = pts, // box 即发送图像素坐标(顺时针四点),与 OcrItem 语义一致 Score = score }); } } return items; } private void Notify(string msg) { var h = StatusChanged; if (h != null) h(msg); } } }