基线:全量源码首提(D1 版本控制落地,含第一轮优化 B1-B4/A1/A4/缩放修复)

This commit is contained in:
dqb-dev
2026-10-01 22:15:22 +08:00
commit 1e65792b09
499 changed files with 204579 additions and 0 deletions
+141
View File
@@ -0,0 +1,141 @@
// ============================================================
// 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
{
/// <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 },
{ "options", new Dictionary<string, object> { { "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<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);
}
}
}