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";//英文和数字模型V3OCRModelConfig 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; //是否执行文字方向分类;默认falseoCRParameter.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 和 pathprivate 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;// 跳过 headersstring line;while (!(string.IsNullOrEmpty(line = reader.ReadLine()))) { }// Read content until next boundarystring 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);}
}
起服务后:
测试: