144 lines
6.3 KiB
C#
144 lines
6.3 KiB
C#
// ============================================================
|
||
// 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 成套报价软件
|
||
{
|
||
/// <summary>Umi-OCR 本地引擎客户端(单实例)。</summary>
|
||
internal sealed class UmiOcrClient
|
||
{
|
||
private readonly JavaScriptSerializer _json = new JavaScriptSerializer();
|
||
private string _baseUrl = "http://127.0.0.1:1224";
|
||
|
||
/// <summary>状态提示,接 OcrClient.StatusChanged。</summary>
|
||
public event Action<string> StatusChanged;
|
||
|
||
public bool IsConfigured
|
||
{
|
||
get { return true; } // 本地引擎无需 Key
|
||
}
|
||
|
||
/// <summary>服务地址由 OcrClient 从 baidu_ocr.json 的 umi_url 注入(热更新)。</summary>
|
||
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('/');
|
||
}
|
||
|
||
/// <summary>识别一块位图 → 文本行列表(带位置)。JSON POST 到本机 Umi-OCR。</summary>
|
||
public Task<List<OcrItem>> RecognizeAsync(Bitmap crop, int timeoutMs)
|
||
{
|
||
if (crop == null || crop.Width <= 0 || crop.Height <= 0)
|
||
return Task.FromResult(new List<OcrItem>());
|
||
|
||
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<OcrItem> 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<string, object>
|
||
{
|
||
{ "base64", b64 },
|
||
// ★ ocr.cls=true:纠正文本方向(PaddleOCR 引擎方向分类,即截图OCR界面的"纠正文本方向";
|
||
// 界面设置不会带给 HTTP 调用,必须显式传——否则旋转90°的横排文字识别必乱)
|
||
{ "options", new Dictionary<string, object> { { "data.format", "dict" }, { "ocr.cls", true } } }
|
||
});
|
||
|
||
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<Dictionary<string, object>>(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<OcrItem>(); // 无文本,不算错误
|
||
if (code != 100)
|
||
throw new Exception("Umi-OCR 错误 code=" + code + ": " +
|
||
Convert.ToString(resp.ContainsKey("data") ? resp["data"] : ""));
|
||
|
||
var items = new List<OcrItem>();
|
||
var arr = resp.ContainsKey("data") ? resp["data"] as System.Collections.ArrayList : null;
|
||
if (arr != null)
|
||
{
|
||
foreach (Dictionary<string, object> 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);
|
||
}
|
||
}
|
||
}
|