C# 结合PaddleOCRSharp搭建Http网络服务

发布于:2025-05-31 ⋅ 阅读:(24) ⋅ 点赞:(0)

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);
    }
}

起服务后:
在这里插入图片描述
测试:

在这里插入图片描述

在这里插入图片描述