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

news2025/6/2 21:59:19

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

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

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2392888.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【连接器专题】SD卡座规格书审查需要审哪些方面?

在审查SD卡座规格书时,我们需要考虑哪些方面? 首先在拿到一份SD卡座的详细规格书时,一般供应商给到的规格书中包括了一些基础信息、产品图纸信息、技术参数信息,同时有些供应商会给出产品可靠性测试报告。因此我们会从这几个要素去看规格书。 基础信息 基础信息一般会给变更…

eBest智能价格引擎系统 助力屈臣氏饮料落地「价格大脑」+「智慧通路」数字基建​

从价格策略到终端执行&#xff0c;数字化正在重构饮料行业竞争壁垒&#xff01; 近日&#xff0c;eBest为屈臣氏饮料提供的智能价格引擎系统已正式上线并投入运营。同时&#xff0c;基于eBest SFA方案且与屈臣氏饮料业务场景深度耦合的Smart Field Operation智慧通路项目正式启…

Kettle 远程mysql 表导入到 hadoop hive

kettle 远程mysql 表导入到 hadoop hive &#xff08;教学用 &#xff09; 文章目录 kettle 远程mysql 表导入到 hadoop hive创建 对象 执行 SQL 语句 -mysql 导出 CSV格式CSV 文件远程上传到 HDFS运行 SSH 命令远程登录 run SSH 并执行 hadoop fs -put 建表和加载数据总结 创…

完整解析 Linux Kdump Crash Kernel 工作原理和实操步骤

完整解析 Linux Kdump Crash Kernel 工作原理和实操步骤 一、前言 在使用 Linux 操作系统进行内核开发或者系统维护时&#xff0c;内核 panic 是最常见的系统崩溃环节。如果想要在内核崩溃后立即分析环境和输出内核内存 dump&#xff0c;Kdump crashkernel 是最接近完美的解…

菜鸟之路Day36一一Web开发综合案例(部门管理)

菜鸟之路Day36一一Web开发综合案例&#xff08;部门管理&#xff09; 作者&#xff1a;blue 时间&#xff1a;2025.5.28 文章目录 菜鸟之路Day36一一Web开发综合案例&#xff08;部门管理&#xff09;一.环境搭建二.开发规范三.部门管理3.1查询3.2删除3.3新增3.3修改根据id来…

第 1 章:学习起步

1. React Native前置知识要求 在开始学习React Native之前&#xff0c;有一些前置知识你需要了解。不过别担心&#xff0c;我会带你逐步掌握这些内容&#xff0c;让你顺利入门。 1.1. JavaScript是必须掌握的 学习React Native&#xff0c;JavaScript是基础。你需要了解Java…

SQL查询——大厂面试真题

前言 本文总结了SQLite数据库的核心操作要点&#xff1a;1. 基础语法&#xff1a;SQL语句不区分大小写&#xff0c;多语句需用分号分隔&#xff0c;支持多种注释方式2. 表操作&#xff1a;包括创建表&#xff08;定义主键、非空约束等&#xff09;、插入/更新/删除数据、添加/…

Linux-pcie ranges介绍

参考链接&#xff1a;https://elinux.org/Device_Tree_Usage#PCI_Host_Bridge pcie bar高低端BAR起始地址介绍 pcie设备树节点 / {compatible "rockchip,rk3588";interrupt-parent <&gic>;#address-cells <2>;#size-cells <2>;pcie3x4: p…

⭐ Unity AVProVideo插件自带播放器 脚本重构 实现视频激活重置功能

一、功能概述 本笔记记录直接修改插件自带的场景播放其中 原始的 MediaPlayerUI 脚本,实现激活时自动重置播放器的功能。 我用的插件版本是 AVPro Video - Ultra Edition 2.7.3 修改后的脚本将具备以下特性: 激活 GameObject 时自动重置播放位置到开头 可配置是否在重置后自…

详解K8s API Server 如何处理请求的?

详解K8s API Server 如何处理请求的? Kubernetes(K8s)是一个强大的容器编排系统,而API Server(kube-apiserver) 是它的核心组件之一。 如果把 K8s 比作一个国家,API Server 就是政府机构,所有资源的创建、修改、删除都要经过它审批! 🎯 API Server 的作用 📌 A…

微调数据处理

1. 数据爬取 我们将爬取的1G文件都保存到all_m_files目录下 查看原始数据文件数量&#xff1a; find /root/all_m_files -type f | wc -l 2. 数据预处理 仅保留UTF-8 格式文件&#xff0c;且所有保留的代码文件长度必须大于20行 import os import pandas as pddef try_read…

解决开发者技能差距:AI 在提升效率与技能培养中的作用

企业在开发者人才方面正面临双重挑战。一方面&#xff0c;IDC 预测&#xff0c;到2025年&#xff0c;全球全职开发者将短缺400万人&#xff1b;另一方面&#xff0c;一些行业巨头已暂停开发者招聘&#xff0c;转而倚重人工智能&#xff08;AI&#xff09;来满足开发需求。这不禁…

XCTF-web-easyphp

解析 第一个条件&#xff08; k e y 1 &#xff09;&#xff1a; i s s e t ( key1&#xff09;&#xff1a;isset( key1&#xff09;&#xff1a;isset(a) && intval(KaTeX parse error: Expected EOF, got & at position 14: a) > 6000000 &̲& strl…

Transformer 通关秘籍11:Word2Vec 及工具的使用

将文字文本转换为词向量(word embedding&#xff09;的过程中&#xff0c;一个非常著名的算法模型应该就是 Word2Vec 了。 相信大家或多或少都听说过&#xff0c;本节就来简单介绍一下 Word2Vec 。 什么是 Word2Vec &#xff1f; Word2Vec 可以非常有效的创建词嵌入向量&…

【DAY34】GPU训练及类的call方法

内容来自浙大疏锦行python打卡训练营 浙大疏锦行 知识点&#xff1a; CPU性能的查看&#xff1a;看架构代际、核心数、线程数GPU性能的查看&#xff1a;看显存、看级别、看架构代际GPU训练的方法&#xff1a;数据和模型移动到GPU device上类的call方法&#xff1a;为什么定义前…

Flutte ListView 列表组件

目录 1、垂直列表 1.1 实现用户中心的垂直列表 2、垂直图文列表 2.1 动态配置列表 2.2 for循环生成一个动态列表 2.3 ListView.builder配置列表 列表布局是我们项目开发中最常用的一种布局方式。Flutter中我们可以通过ListView来定义列表项&#xff0c;支持垂直和水平方向展示…

muduo库的初步认识和基本使用,创建一个简单查询单词服务系统

小编在学习完muduo库之后&#xff0c;觉得对于初学者&#xff0c;muduo库还是有点不好理解&#xff0c;所以在此&#xff0c;小编来告诉大家muduo库的初步认识和基本使用&#xff0c;让初学者也可以更快的上手和使用muduo库。 Muduo由陈硕大佬开发&#xff0c;是⼀个基于 非阻塞…

【HTML/CSS面经】

HTML/CSS面经 HTML1. script标签中的async和defer的区别2. H5新特性&#xff08;1 标签语义化&#xff08;2 表单功能增强&#xff08;3 音频和视频标签&#xff08;4 canvas和svg绘画&#xff08;5 地理位置获取&#xff08;6 元素拖动API&#xff08;7 Web Worker&#xff08…

git查看commit属于那个tag

1. 快速确认commit原始分支及合入tag # git describe 213b4b3bbef2771f7a1b8166f6e6989442ca67c8 查看commit合入tag # git describe 213b4b3bbef2771f7a1b8166f6e6989442ca67c8 --all 查看commit原始分支 2.查看分支与master关系 # git show --all 0.5.67_0006 --stat 以缩…

mysql-mysql源码本地调试

前言 先进行mysql源码本地编译&#xff1a;mysql源码本地编译 1.本地调试 这里以macbook为例 1.使用vscode打开mysql源码 2.创建basedir目录、数据目录、配置文件目录、配置文件 cd /Users/test/ mkdir mysqldir //创建数据目录和配置目录 cd mysqldir mkdir conf data …