Windows打开端口:
控制面板 > 系统和安全 > 防火墙> 高级设置 → 入站规则 → 右侧选择 → 新建规则 → 端口 → 协议类型 TCP→ 端口
using System;
using System.Drawing;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Linq;
using PaddleOCRSharp;
using HttpMultipartParser;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Drawing.Imaging;
using OpenCvSharp;
using System.Net.Http;
class Program
{
static void Main(string[] args)
{
//string remoteUrl = "http://10.50.7.210:9011/profile/upload/2025/05/21/image_20250521093746A004.jpg";
// curl "http://localhost:8082/image-ocr?templateCode=abc123&path=E://3.png"
// 打开端口:控制面板 > 系统和安全 > 防火墙> 高级设置 → 入站规则 → 右侧选择 → 新建规则 → 端口 → 协议类型 TCP→ 端口
string baseUrl = "http://127.0.0.1:8082/image-ocr/";
//string baseUrl = "http://*:8082/image-ocr/";
var server = new OCRHttpServer(baseUrl);
Console.CancelKeyPress += (sender, e) =>
{
e.Cancel = true;
server.Stop();
};
server.Start();
Console.WriteLine("Press CTRL+C to stop the server...");
Console.WriteLine("curl \"http://localhost:8082/image-ocr?templateCode=n&path=imagePath\"");
while (true)
{
Thread.Sleep(100);
}
}
}
class OCRHttpServer
{
private readonly HttpListener _listener;
private readonly string _baseUrl;
private PaddleOCREngine engine;
private PaddleStructureEngine structengine;
public OCRHttpServer(string baseUrl)
{
_baseUrl = baseUrl;
_listener = new HttpListener();
_listener.Prefixes.Add(baseUrl);
}
public void OCRModel_Load()
{
string outpath = Path.Combine(Environment.CurrentDirectory, "out");
if (!Directory.Exists(outpath))
{ Directory.CreateDirectory(outpath); }
自带轻量版中英文模型V3模型
//OCRModelConfig config = null;
//服务器中英文模型
//OCRModelConfig config = new OCRModelConfig();
//string root = System.IO.Path.GetDirectoryName(typeof(OCRModelConfig).Assembly.Location);
//string modelPathroot = root + @"\inferenceserver";
//config.det_infer = modelPathroot + @"\ch_ppocr_server_v2.0_det_infer";
//config.cls_infer = modelPathroot + @"\ch_ppocr_mobile_v2.0_cls_infer";
//config.rec_infer = modelPathroot + @"\ch_ppocr_server_v2.0_rec_infer";
//config.keys = modelPathroot + @"\ppocr_keys.txt";
//英文和数字模型V3
OCRModelConfig config = new OCRModelConfig();
string root = System.IO.Path.GetDirectoryName(typeof(OCRModelConfig).Assembly.Location);
string modelPathroot = root + @"\en_v3";
config.det_infer = modelPathroot + @"\en_PP-OCRv3_det_infer";
config.cls_infer = modelPathroot + @"\ch_ppocr_mobile_v2.0_cls_infer";
config.rec_infer = modelPathroot + @"\en_PP-OCRv3_rec_infer";
config.keys = modelPathroot + @"\en_dict.txt";
//OCR参数
OCRParameter oCRParameter = new OCRParameter();
oCRParameter.cpu_math_library_num_threads = 10;//预测并发线程数
oCRParameter.enable_mkldnn = true;//web部署该值建议设置为0,否则出错,内存如果使用很大,建议该值也设置为0.
oCRParameter.cls = false; //是否执行文字方向分类;默认false
oCRParameter.det = true;//是否开启方向检测,用于检测识别180旋转
oCRParameter.use_angle_cls = false;//是否开启方向检测,用于检测识别180旋转
oCRParameter.det_db_score_mode = true;//是否使用多段线,即文字区域是用多段线还是用矩形,
//初始化OCR引擎
engine = new PaddleOCREngine(config, oCRParameter);
Console.Clear();
//模型配置,使用默认值
StructureModelConfig structureModelConfig = null;
//表格识别参数配置,使用默认值
StructureParameter structureParameter = new StructureParameter();
structengine = new PaddleStructureEngine(structureModelConfig, structureParameter);
Console.Clear();
}
public void Start()
{
_listener.Start();
Console.WriteLine($"Server started at {_baseUrl}");
OCRModel_Load();
ThreadPool.QueueUserWorkItem((o) =>
{
try
{
while (_listener.IsListening)
{
ThreadPool.QueueUserWorkItem((contextState) =>
{
var context = (HttpListenerContext)contextState;
try
{
HandleRequest(context);
}
catch (Exception ex)
{
Console.WriteLine($"Error handling request: {ex.Message}");
SendErrorResponse(context, 500, "Internal Server Error");
}
finally
{
context.Response.Close();
}
}, _listener.GetContext());
}
}
catch (Exception ex)
{
Console.WriteLine($"Server error: {ex.Message}");
}
});
}
public void Stop()
{
_listener.Stop();
_listener.Close();
Console.WriteLine("Server stopped");
}
private void HandleRequest(HttpListenerContext context)
{
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
if (request.HttpMethod == "POST")
{
HandlePostRequest(request, response);
}
else if (request.HttpMethod == "GET")
{
HandleGetRequest(request, response);
}
else
{
SendError(response, "Unsupported HTTP method");
}
response.OutputStream.Close();
}
private string HandleImageOCRRequest(string imagePath)
{
string jsonResponse = string.Empty;
try
{
if (string.IsNullOrEmpty(imagePath))
{
// 返回成功响应
var response = new
{
Status = "Error",
Message = "",
ReceivedAt = DateTime.Now
};
jsonResponse = JsonSerializer.Serialize(response);
return jsonResponse;
}
jsonResponse = ProgressImage(imagePath);
return jsonResponse;
}
catch (Exception ex)
{
Console.WriteLine($"Error processing string: {ex}");
var response = new
{
Status = "Error",
Message = "",
ReceivedAt = DateTime.Now
};
jsonResponse = JsonSerializer.Serialize(response);
return jsonResponse;
}
}
//用OpenCV实现分块检测
private string ProgressImage(string imagePath)
{
string jsonResponse = string.Empty;
string message = string.Empty;
using (Mat src = Cv2.ImRead(imagePath, ImreadModes.Color))
{
if (src.Empty())
throw new Exception("无法加载图像");
Mat gray = new Mat();
Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);
//图片边缘裁掉多少像素的边缘
int gap = 5;
int height = src.Rows;
int width = src.Cols;
// 创建掩膜(矩形区域)
Mat mask = new Mat(height, width, MatType.CV_8UC1, Scalar.All(0));
Rect rectROI = new Rect(gap, gap, width - gap * 2, height - gap * 2);
Mat roiMask = new Mat(mask, rectROI);
roiMask.SetTo(Scalar.All(255));
// 阈值分割
Mat thresh = new Mat();
Cv2.Threshold(gray, thresh, 254, 255, ThresholdTypes.Binary);
// 与掩膜进行 AND 操作
Mat maskedThresh = new Mat();
Cv2.BitwiseAnd(thresh, mask, maskedThresh);
// 填充孔洞
Mat filled = new Mat();
maskedThresh.CopyTo(filled);
// 创建FloodFill所需的mask(比原图大2像素)
Mat floodFillMask = new Mat(filled.Rows + 2, filled.Cols + 2, MatType.CV_8UC1, Scalar.All(0));
// 执行floodfill从边界开始填充背景
Cv2.FloodFill(filled, floodFillMask, new OpenCvSharp.Point(0, 0), new Scalar(255),
out Rect rect,
new Scalar(), new Scalar(),
FloodFillFlags.Link8);
// 反转图像以获取填充后的对象
Cv2.BitwiseNot(filled, filled);
// 查找轮廓(相当于连接区域)
OpenCvSharp.Point[][] contours;
HierarchyIndex[] hierarchy;
Cv2.FindContours(filled, out contours, out hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
Console.WriteLine(imagePath);
// 遍历每个轮廓
for (int i = 0; i < contours.Length; i++)
{
Rect boundingRect = Cv2.BoundingRect(contours[i]);
// 裁剪图像
Mat cropped = new Mat(src, boundingRect);
// 保存裁剪图像到临时路径
string tempImagePath = Path.Combine("E:/File/", $"{i + 1}.png");
Cv2.ImWrite(tempImagePath, cropped);
// 转换为 byte[]
byte[] imageBytes = cropped.ToBytes();
// OCR 识别
OCRResult ocrResult = engine.DetectText(imageBytes); // 假设 engine 是已初始化的 OCR 引擎
string outMessage = "";
string printMessage = "";
foreach (var item in ocrResult.TextBlocks)
{
string input = item.ToString(); // 根据实际结构调整
var idMatch = Regex.Match(input, @"^([^,]+)");
string text = idMatch.Success ? idMatch.Groups[1].Value.Trim() : "";
var coordinatesMatch = Regex.Match(input, @"\[(\([^)]*\)(?:,\([^)]*\))*)\]");
string coordsStr = coordinatesMatch.Success ? coordinatesMatch.Groups[1].Value.Trim() : "";
outMessage += text + ":" + coordsStr + ";";
printMessage += text + " ";
}
message += $"Rectangle{i + 1}:{{{outMessage}}}";
Console.WriteLine($"Rectangle {i+1}");
Console.WriteLine($"OCR Result: {printMessage}");
}
}
// 14. 返回 JSON 结果
var response = new
{
Status = "Success",
Message = message,
ReceivedAt = DateTime.Now
};
jsonResponse = JsonSerializer.Serialize(response);
return jsonResponse;
}
// 处理 GET 请求,解析 query string 中的 templateCode 和 path
private void HandleGetRequest(HttpListenerRequest request, HttpListenerResponse response)
{
// 使用 HttpUtility.ParseQueryString 来解析查询字符串
Uri url = request.Url;
if (url == null)
{
SendError(response, "Invalid request URL");
return;
}
NameValueCollection queryParams = System.Web.HttpUtility.ParseQueryString(url.Query);
string templateCode = queryParams["templateCode"];
string path = queryParams["path"];
if (string.IsNullOrEmpty(templateCode) || string.IsNullOrEmpty(path))
{
SendError(response, "Missing required parameters: templateCode and path");
return;
}
string responseBody = "";
responseBody = HandleImageOCRRequest(path);
byte[] buffer = Encoding.UTF8.GetBytes(responseBody);
response.ContentType = "text/plain";
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
}
// 处理 POST multipart/form-data 文件上传
private void HandlePostRequest(HttpListenerRequest request, HttpListenerResponse response)
{
string boundary = request.ContentType?.Split('=')[1];
if (string.IsNullOrEmpty(boundary))
{
SendError(response, "Invalid Content-Type");
return;
}
using (Stream input = request.InputStream)
{
Encoding encoding = Encoding.UTF8;
string formData = ReadMultipartFormData(input, encoding, boundary);
string responseBody = $"Received POST:\nFile Content:\n{formData}";
byte[] buffer = encoding.GetBytes(responseBody);
response.ContentType = "text/plain";
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
}
}
// 发送错误信息
private void SendError(HttpListenerResponse response, string message)
{
byte[] buffer = Encoding.UTF8.GetBytes(message);
response.StatusCode = (int)HttpStatusCode.BadRequest;
response.ContentType = "text/plain";
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
}
// 读取 multipart/form-data 内容
private string ReadMultipartFormData(Stream inputStream, Encoding encoding, string boundary)
{
StreamReader reader = new StreamReader(inputStream, encoding);
string boundaryLine;
StringBuilder result = new StringBuilder();
while ((boundaryLine = reader.ReadLine()) != null)
{
if (boundaryLine.Contains(boundary)) continue;
// 跳过 headers
string line;
while (!(string.IsNullOrEmpty(line = reader.ReadLine()))) { }
// Read content until next boundary
string content;
while ((content = reader.ReadLine()) != null && !content.Contains(boundary))
{
result.AppendLine(content);
}
break; // 只处理第一个 part
}
return result.ToString().Trim();
}
private void SendResponse(HttpListenerContext context, string responseString)
{
try
{
byte[] buffer = Encoding.UTF8.GetBytes(responseString);
context.Response.ContentLength64 = buffer.Length;
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
}
catch (Exception)
{
}
}
private void SendErrorResponse(HttpListenerContext context, int statusCode, string message)
{
context.Response.StatusCode = statusCode;
SendResponse(context, message);
}
}
起服务后:
测试: