基线:全量源码首提(D1 版本控制落地,含第一轮优化 B1-B4/A1/A4/缩放修复)
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
// ============================================================
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user