【OpenVinoSharp】基于C#和OpenVINO部署PaddleOCR模型

news2024/10/1 19:19:47

基于C#和OpenVINO部署PaddleOCR模型

  • 1.OpenVINO
  • 2.PaddleOCR
    • PP-OCRv2
    • PP-OCRv3
  • 3.模型下载与转换
    • 3.1 Detection model
    • 3.2 Direction classifier
    • 3.3 Recognition model
  • 4.OpenVinoSharp安装
    • 4.1 获取OpenVinoSharp源码
    • 4.2 配置OpenVINO依赖项
  • 5. PaddleOCR
    • 5. 1模型推理C#实现
      • (1) 模型推理基类
      • (2)文本区域识别类
      • (3) 文字方向判断类
      • (4)文字内容识别
    • 5.2 文字识别结果类
    • 5.3 PaddleOCR类
    • 5.3 其他类
  • 6. 文本识别过程与结果

  基于OpenVINO模型推理库,在C#语言下,调用封装的OpenVINO动态链接库,部署推理PaddleOCR中的文字识别模型;实现了在C#平台使用PaddleOCR文字识别模型识别文字。

  OpenVinoSharp源码在GitHub和Gitee上已开源,可以直接通过Git获取所有源码

  在Github上克隆下载:

git clone https://github.com/guojin-yan/OpenVinoSharp_deploy_PaddleOCR.git

  在Gitee上克隆下载:

git clone https://gitee.com/guojin-yan/OpenVinoSharp_deploy_PaddleOCR.git

飞桨AI Studio 项目链接
基于C#和OpenVINO部署PaddleOCR模型 - 飞桨AI Studio

1.OpenVINO

  OpenVINO™是英特尔基于自身现有的硬件平台开发的一种可以加快高性能计算机视觉和深度学习视觉应用开发速度工具套件,用于快速开发应用程序和解决方案,以解决各种任务(包括人类视觉模拟、自动语音识别、自然语言处理和推荐系统等)。

image-20230422095309885

  该工具套件基于最新一代的人工神经网络,包括卷积神经网络 (CNN)、递归网络和基于注意力的网络,可扩展跨英特尔® 硬件的计算机视觉和非视觉工作负载,从而最大限度地提高性能。它通过从边缘到云部署的高性能、人工智能和深度学习推理来为应用程序加速,并且允许直接异构执行。极大的提高计算机视觉、自动语音识别、自然语言处理和其他常见任务中的深度学习性能;使用使用流行的框架(如TensorFlow,PyTorch等)训练的模型;减少资源需求,并在从边缘到云的一系列英特尔®平台上高效部署;支持在Windows与Linux系统,且官方支持编程语言为Python与C++语言。

  官方发行的OpenVINO™未提供C#编程语言接口,因此在使用时无法实现在C#中利用OpenVINO™进行模型部署。在之前所做工作中,利用动态链接库功能,调用官方依赖库,实现在C#中部署深度学习模型,推出了OpenVinoSharp。在本文项目中,使用了OpenVinoSharp作为模型推理工具,加速PaddleOCR模型推理。

2.PaddleOCR

  PP-OCR是PaddleOCR自研的实用的超轻量OCR系统。在实现前沿算法的基础上,考虑精度与速度的平衡,进行模型瘦身深度优化,使其尽可能满足产业落地需求。PP-OCR是一个两阶段的OCR系统,其中文本检测算法选用DB,文本识别算法选用[RNN,并在检测和识别模块之间添加文本方向分类器,以应对不同方向的文本识别。

  PP-OCR系统在持续迭代优化,目前已发布PP-OCR、PP-OCRv2和PP-OCRv3三个版本。PP-OCR从骨干网络选择和调整、预测头部的设计、数据增强、学习率变换策略、正则化参数选择、预训练模型使用以及模型自动裁剪量化8个方面,采用19个有效策略,对各个模块的模型进行效果调优和瘦身(如绿框所示),最终得到整体大小为3.5M的超轻量中英文OCR和2.8M的英文数字OCR。

PP-OCRv2

  PP-OCRv2在PP-OCR的基础上,进一步在5个方面重点优化,检测模型采用CML协同互学习知识蒸馏策略和CopyPaste数据增广策略;识别模型采用LCNet轻量级骨干网络、UDML 改进知识蒸馏策略和Enhanced CTC loss损失函数改进(如上图红框所示),进一步在推理速度和预测效果上取得明显提升。

PP-OCRv3

  PP-OCRv3在PP-OCRv2的基础上,针对检测模型和识别模型,进行了共计9个方面的升级:

  • PP-OCRv3检测模型对PP-OCRv2中的CML协同互学习文本检测蒸馏策略进行了升级,分别针对教师模型和学生模型进行进一步效果优化。其中,在对教师模型优化时,提出了大感受野的PAN结构LK-PAN和引入了DML蒸馏策略;在对学生模型优化时,提出了残差注意力机制的FPN结构RSE-FPN。
  • PP-OCRv3的识别模块是基于文本识别算法SVTR优化。SVTR不再采用RNN结构,通过引入Transformers结构更加有效地挖掘文本行图像的上下文信息,从而提升文本识别能力。PP-OCRv3通过轻量级文本识别网络SVTR_LCNet、Attention损失指导CTC损失训练策略、挖掘文字上下文信息的数据增广策略TextConAug、TextRotNet自监督预训练模型、UDML联合互学习策略、UIM无标注数据挖掘方案,6个方面进行模型加速和效果提升。

ppocrv3_framework

3.模型下载与转换

  本文中用到的所有模型均在PaddleOCR中下载,本文中给出了详细获取流程,丙夜在代码厂库中提供了本文中所用到的所有模型。

3.1 Detection model

(1)PaddlePaddle模型下载方式:

  命令行直接输入以下代码,或者浏览器输入后面的网址即可。

wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar

  下载好后将其解压到文件夹中,便可以获得Paddle格式的推理模型。

(2)转换为ONNX格式:

  该方式需要安装paddle2onnx和onnxruntime模块。在命令行中输入以下指令进行转换:

paddle2onnx --model_dir ./ch_PP-OCRv3_det_infer --model_filename inference.pdmodel --params_filename inference.pdiparams --save_file ./inference/det_onnx/model.onnx --opset_version 10 --input_shape_dict="{'x':[-1,3,640,640]}" --enable_onnx_checker True

  其中在指定模型输入大小时,-1代表不指定,可以模型推理部署时在进行指定。运行上述指令后在./inference/det_onnx/路径下可以找到该模型

(3)转换为IR格式

  利用OpenVINOTM模型优化器,可以实现将ONNX模型转为IR格式。

  在OpenVINOTM环境下,切换到模型优化器文件夹,直接使用下面指令便可以进行转换。

mo –input_model det_onnx/model.onnx

  经过上述指令模型转换后,可以在当前文件夹下找到转换后的三个文件。

3.2 Direction classifier

(1)PaddlePaddle模型下载方式:

  命令行直接输入以下代码,或者浏览器输入后面的网址即可。

wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar

  下载好后将其解压到文件夹中,便可以获得Paddle格式的推理模型。

(2)转换为ONNX格式:

  该方式需要安装paddle2onnx和onnxruntime模块。在命令行中输入以下指令进行转换:

paddle2onnx --model_dir ./ch_ppocr_mobile_v2.0_cls_infer --model_filename inference.pdmodel --params_filename inference.pdiparams --save_file ./inference/cls_onnx/model.onnx --opset_version 10 --input_shape_dict="{'x':[-1,3,640,640]}" --enable_onnx_checker True

  其中在指定模型输入大小时,-1代表不指定,可以模型推理部署时在进行指定。运行上述指令后在./inference/cls_onnx/路径下可以找到该模型

(3)转换为IR格式

  利用OpenVINOTM模型优化器,可以实现将ONNX模型转为IR格式。

  在OpenVINOTM环境下,切换到模型优化器文件夹,直接使用下面指令便可以进行转换。

mo –input_model det_onnx/model.onnx

  经过上述指令模型转换后,可以在当前文件夹下找到转换后的三个文件。

3.3 Recognition model

(1)PaddlePaddle模型下载方式:

  命令行直接输入以下代码,或者浏览器输入后面的网址即可。

wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar

  下载好后将其解压到文件夹中,便可以获得Paddle格式的推理模型。

(2)转换为ONNX格式:

  该方式需要安装paddle2onnx和onnxruntime模块。在命令行中输入以下指令进行转换:

paddle2onnx --model_dir ./ch_PP-OCRv3_rec_infer --model_filename inference.pdmodel --params_filename inference.pdiparams --save_file ./inference/rec_onnx/model.onnx --opset_version 10 --input_shape_dict="{'x':[-1,3,40,6625]}" --enable_onnx_checker True

  其中在指定模型输入大小时,-1代表不指定,可以模型推理部署时在进行指定。运行上述指令后在./inference/rec_onnx/路径下可以找到该模型

(3)转换为IR格式

  利用OpenVINOTM模型优化器,可以实现将ONNX模型转为IR格式。

  在OpenVINOTM环境下,切换到模型优化器文件夹,直接使用下面指令便可以进行转换。

mo –input_model det_onnx/model.onnx

  经过上述指令模型转换后,可以在当前文件夹下找到转换后的三个文件。

4.OpenVinoSharp安装

4.1 获取OpenVinoSharp源码

  OpenVinoSharp源码在GitHub和Gitee上已开源,可以直接通过Git获取所有源码

  在Github上克隆下载:

git clone https://github.com/guojin-yan/OpenVinoSharp.git

  在Gitee上克隆下载:

git clone https://gitee.com/guojin-yan/OpenVinoSharp.git

4.2 配置OpenVINO依赖项

  OpenVinoSharp项目主要依赖OpenVINO和OpenCV两个依赖项,使用者可以根据自己安装情况配置,下文是笔者安装配置:

包含目录

OpenCV
E:\OpenCV Source\opencv-4.5.5\build\include
E:\OpenCV Source\opencv-4.5.5\build\include\opencv2
OpenVINO
C:\Program Files (x86)\Intel\openvino_2022.1.0.643\runtime\include
C:\Program Files (x86)\Intel\openvino_2022.1.0.643\runtime\include\ie

库目录

OpenCV
E:\OpenCV Source\opencv-4.5.5\build\x64\vc15\lib
OpenVINO
C:\Program Files (x86)\Intel\openvino_2022.1.0.643\runtime\lib\intel64\Release

附加依赖项

OpenCV
opencv_world455.lib
OpenVINO
openvino.lib

5. PaddleOCR

  PaddleOCR文字识别分为三个阶段:文本区域识别->文字方向判断与转换->文本内容识别。

image-20230422134443619

5. 1模型推理C#实现

  PaddleOCR官方提供了C++与Python接口,未提供C#接口,在本文中,主要实现在C#使用OpenVINO™部署工具部署PaddleOCR全系列模型,因此需要C#中复现所有的PaddleOCR模型推理方法,对此,在本项目中构建了PaddleOCR 文字识别所需的文本推理类。

  PaddleOCR基本的文本识别包括文本区域识别、文本方向判断以及文字内容识别。对与代码中的详细内容此处不做详细介绍。

(1) 模型推理基类

public class Predictor
{
    protected Core m_core;
    protected EnumDataType m_type = 0;
    protected string m_input_name;
    protected string m_output_name;

    protected float[] m_mean = new float[3];
    protected float[] m_scale = new float[3];

    protected float[] infer(Mat img, int result_length)
    {
        byte[] image_data_det = img.ImEncode(".bmp");
        ulong image_size_det = Convert.ToUInt64(image_data_det.Length);
        // 将图片数据加载到模型
        m_core.load_input_data(m_input_name, image_data_det, image_size_det, (int)m_type, m_mean, m_scale);
        // 模型推理
        m_core.infer();
        float[] result = m_core.read_infer_result<float>(m_output_name, result_length);
        return result;
    }
}

(2)文本区域识别类

public class OcrDet : Predictor
{
    private float m_det_db_thresh = 0.3f;
    private float m_det_db_box_thresh = 0.5f;
    private string m_det_db_score_mode = "slow";
    private float m_det_db_unclip_ratio = 2.0f;
    string m_limit_type = "max";
    int m_limit_side_len = 960;


    private PostProcessor m_post_processor = new PostProcessor();
    private PreProcess m_preprocess = new PreProcess();

    public OcrDet(string det_model, string device, string input_name, string output_name,
        ulong[] input_size_det, EnumDataType type, double det_db_thresh = 0.3, double det_db_box_thresh = 0.5)
    {
        m_core = new Core(det_model, device);
        m_type = type;
        m_input_name = input_name;
        m_output_name = output_name;
        m_det_db_thresh = (float)det_db_thresh;
        m_det_db_box_thresh = (float)det_db_box_thresh;
        // 设置模型节点形状
        m_core.set_input_sharp(m_input_name, input_size_det);
    }

    public List<List<List<int>>> predict(Mat image)
    {
        float ratio_h;
        float ratio_w;
        Mat resize_img = m_preprocess.ResizeImgType0(image, m_limit_type, m_limit_side_len, out ratio_h, out ratio_w);

        ratio_h = (float)(640.0 / image.Cols);
        ratio_w = (float)(640.0 / image.Rows);

        int result_det_length = 640 * 640;
        float[] result_det = infer(resize_img, result_det_length);

        // 将模型输出转为byte格式
        byte[] result_det_byte = new byte[result_det_length];
        for (int i = 0; i < result_det_length; i++)
        {
            result_det_byte[i] = (byte)(result_det[i] * 255);
        }
        // 重构结果图像
        Mat cbuf_map = new Mat(640, 640, MatType.CV_8UC1, result_det_byte);
        Mat pred_map = new Mat(640, 640, MatType.CV_32F, result_det);

        double threshold = m_det_db_thresh * 255;
        double maxvalue = 255;
        // 图像阈值处理
        Mat bit_map = new Mat();
        Cv2.Threshold(cbuf_map, bit_map, threshold, maxvalue, ThresholdTypes.Binary);
        //Cv2.ImShow("bit_map", bit_map);

        List<List<List<int>>> boxes = m_post_processor.BoxesFromBitmap(pred_map, bit_map, m_det_db_box_thresh, m_det_db_unclip_ratio,
            m_det_db_score_mode);
        boxes = m_post_processor.FilterTagDetRes(boxes, ratio_h, ratio_w, image);
        return boxes;
    }
}

(3) 文字方向判断类

public class OcrCls : Predictor
{
    public float m_cls_thresh = 0.9f;
    private PreProcess m_preprocess = new PreProcess();


    public OcrCls(string cls_model, string device, string input_name, string output_name,
        ulong[] input_size_det, EnumDataType type, double cls_thresh = 0.9)
    {
        m_core = new Core(cls_model, device);
        m_type = type;
        m_input_name = input_name;
        m_output_name = output_name;
        m_cls_thresh = (float)cls_thresh;
        // 设置模型节点形状
        m_core.set_input_sharp(m_input_name, input_size_det);

        m_mean = new float[3] { 0.5f * 255, 0.5f * 255, 0.5f * 255 };
        m_scale = new float[3] { 0.5f * 255, 0.5f * 255, 0.5f * 255 };
    }

    public void predict(List<Mat> img_list, List<int> lables, List<float> scores)
    {
        int img_num = img_list.Count;
        List<int> cls_image_shape = new List<int> { 3, 48, 192 };
        for (int n = 0; n < img_num; n++)
        {
            Mat resize_img = m_preprocess.ClsResizeImg(img_list[n], cls_image_shape);

            if (resize_img.Cols < cls_image_shape[2])
            {
                Cv2.CopyMakeBorder(resize_img, resize_img, 0, 0, 0, cls_image_shape[2] - resize_img.Cols,
                    BorderTypes.Constant, new Scalar(0, 0, 0));
            }
            int result_cls_length = 2;
            float[] result_cls = infer(resize_img, result_cls_length);
            int lable = result_cls[0] > result_cls[1] ? 0 : 1;
            lables.Add(lable);
            scores.Add(result_cls[lable]);
            //Console.WriteLine("({0}, {1})", result_cls[0], result_cls[1]);

        }
    }
}

(4)文字内容识别

public class OcrRec : Predictor
{
    private int[] m_rec_image_shape;
    List<string> m_label_list;
    private PostProcessor m_post_processor = new PostProcessor();
    private PreProcess m_preprocess = new PreProcess();

    public OcrRec(string det_model, string device, string input_name, string output_name,
        ulong[] input_size_rec, EnumDataType type, string label_path)
    {
        m_core = new Core(det_model, device);
        m_type = type;
        m_input_name = input_name;
        m_output_name = output_name;
        m_label_list = Utility.ReadDict(label_path);
        m_label_list.Insert(0, "#");
        m_label_list.Add(" ");
        m_rec_image_shape = new int[] { (int)input_size_rec[1], (int)input_size_rec[2], (int)input_size_rec[3] };
        // 设置模型节点形状
        m_core.set_input_sharp(m_input_name, input_size_rec);

        m_mean = new float[3] { 0.5f * 255, 0.5f * 255, 0.5f * 255 };
        m_scale = new float[3] { 0.5f * 255, 0.5f * 255, 0.5f * 255 };
    }

    public void predict(List<Mat> img_list, List<string> rec_texts, List<float> rec_text_scores)
    {
        int img_num = img_list.Count;
        List<float> width_list = new List<float>();
        for (int i = 0; i < img_num; i++)
        {
            width_list.Add((float)(img_list[i].Cols) / img_list[i].Rows);
        }
        List<int> indices = Utility.argsort(width_list);
        for (int n = 0; n < img_num; n++)
        {
            int imgH = m_rec_image_shape[1];
            int imgW = m_rec_image_shape[2];
            float max_wh_ratio = imgW * 1.0f / imgH;
            int h = img_list[n].Rows;
            int w = img_list[n].Cols;
            float wh_ratio = w * 1.0f / h;
            max_wh_ratio = Math.Max(max_wh_ratio, wh_ratio);
            Mat resize_img = m_preprocess.CrnnResizeImg(img_list[n].Clone(), max_wh_ratio, m_rec_image_shape);
            int result_cls_length = 40 * 6625;
            float[] result_cls = infer(resize_img, result_cls_length);
            string str_res = "";
            int argmax_idx;
            int last_index = 0;
            float score = 0.0f;
            int count = 0;
            float max_value = 0.0f;
            for (int r = 0; r < 40; r++)
            {
                float[] temp = new float[6625];
                for (int j = 0; j < 6625; j++)
                {
                    temp[j] = result_cls[r * 6625 + j];
                }
                argmax_idx = Utility.argmax(temp, out max_value);
                if (argmax_idx > 0 && (!(n > 0 && argmax_idx == last_index)))
                {
                    score += max_value;
                    count += 1;
                    str_res += m_label_list[argmax_idx];
                }
                last_index = argmax_idx;
            }
            score /= count;
            rec_texts.Add(str_res);
            rec_text_scores.Add(score);
        }
    }
}

5.2 文字识别结果类

  为了更好的存放文字识别结果,构建了结果类:

public class OCRPredictResult
{
    public List<List<int>> box = new List<List<int>>();
    public string text = "";
    public float score = -1.0f;
    public float cls_score = -1.0f;
    public int cls_label = -1;
}

5.3 PaddleOCR类

  根据PaddleOCR文字识别的三个阶段,此处根据是实现的模型推理C#实现的方法,完整的实现文本识别,此处构建了PaddleOCR推理类:

public class PaddleOCR
{
    OcrDet ocrDet;
    OcrCls ocrCls;
    OcrRec ocrRec;
    public PaddleOCR(Dictionary<string, string> model_path){}
    public List<OCRPredictResult> predict_det(Mat image){}
    public void predict_cls(List<Mat> img_list, List<OCRPredictResult> ocr_results){}
    public void predict_rec(List<Mat> img_list, List<OCRPredictResult> ocr_results){}
    public List<OCRPredictResult> predict(Mat image){}
}

predict_det:文本区域识别

  通过调用OcrDet模型推理类,识别待预测图片中包含文字的区域,并通过文字区域提取算法,提取完整的文本区域。

public List<OCRPredictResult> predict_det(Mat image)
{
    List<OCRPredictResult> ocr_results = new List<OCRPredictResult>();
    // 文字区域识别
    List<List<List<int>>> boxes = ocrDet.predict(image);

    for (int i = 0; i < boxes.Count; i++)
    {
        OCRPredictResult res = new OCRPredictResult();
        res.box = boxes[i];
        ocr_results.Add(res);
    }
    ocr_results = ocr_results.OrderBy(t => t.box[0][1]).ThenBy(t => t.box[0][0]).ToList();
    return ocr_results;
}

predict_cls:文本方向判断

  通过调用OcrCls模型推理类,识别裁剪后的文字区域方向,主要是实现将反向的文字识别出来并作转换。

public void predict_cls(List<Mat> img_list, List<OCRPredictResult> ocr_results)
{
    List<int> lables = new List<int>();
    List<float> scores = new List<float>();
    ocrCls.predict(img_list, lables, scores);
    // output cls results
    for (int i = 0; i < lables.Count; i++)
    {
        ocr_results[i].cls_label = lables[i];
        ocr_results[i].cls_score = scores[i];
    }
}

predict_rec:文字内容识别

  通过调用OcrRec模型推理类,识别裁剪后的文字区域中的文字,并根据文字结果字典,获取识别后的文字内容。

public void predict_rec(List<Mat> img_list, List<OCRPredictResult> ocr_results)
{
    List<string> rec_texts = new List<string>();
    List<float> rec_text_scores = new List<float>();
    ocrRec.predict(img_list, rec_texts, rec_text_scores);
    for (int i = 0; i < rec_texts.Count; i++)
    {
        ocr_results[i].text = rec_texts[i];
        ocr_results[i].score = rec_text_scores[i];
    }
}

predict:文字识别

  分别调用前面封装的predict_rec()predict_cls()、**predict_det()**方法,实现文字识别的整体流程。

public List<OCRPredictResult> predict(Mat image)
{
    DateTime start = DateTime.Now;
    // 文本区域识别
    List<OCRPredictResult> ocr_results = predict_det(image);
    // crop image
    List<Mat> img_list = new List<Mat>();
    for (int j = 0; j < ocr_results.Count; j++)
    {
        Mat crop_img = Utility.GetRotateCropImage(image, ocr_results[j].box);
        img_list.Add(crop_img);
        //Cv2.ImShow("resize_img", crop_img);
        //Cv2.WaitKey(0);
    }
    // 文字方向判断
    predict_cls(img_list, ocr_results);
    for (int i = 0; i < img_list.Count; i++)
    {
        if (ocr_results[i].cls_label % 2 == 1 &&
            ocr_results[i].cls_score > ocrCls.m_cls_thresh) { }
        else
        {
            Cv2.Rotate(img_list[i], img_list[i], RotateFlags.Rotate180);
        }
    }

    predict_rec(img_list, ocr_results);
    DateTime end = DateTime.Now;
    TimeSpan timeSpan = end - start;
    Console.WriteLine("图片预测时间:{0} ms", timeSpan.TotalMilliseconds);

    return ocr_results;
}

5.3 其他类

该项目还包含其他文本以及图片处理类,由于内容较多,此处不做赘述,可以通过查看项目源码获取。

6. 文本识别过程与结果

在前面我们已经对文本识别方法进行了封装,最后在使用时,我们只需要指定预测模型与测试图片,调用PaddleOCR类即可实现。

public void test()
{
    string det_path = @".\..\..\..\..\..\model\ppocr_model_v3\det_onnx\model.onnx";
    string cls_path = @".\..\..\..\..\..\model\\ppocr_model_v3\cls_onnx\model.onnx";
    string rec_path = @".\..\..\..\..\..\model\\ppocr_model_v3\rec_onnx\model.onnx";
    // 模型路径
    Dictionary<string, string> model_path = new Dictionary<string, string>();
    model_path.Add("det_model", det_path);
    model_path.Add("cls_model", cls_path);
    model_path.Add("rec_model", rec_path);

    PaddleOCR paddleOCR = new PaddleOCR(model_path);

    string image_path = @".\..\..\..\..\..\image\demo_1.jpg";
    Mat image = Cv2.ImRead(image_path);
    List<OCRPredictResult> ocr_results = paddleOCR.predict(image);
    Utility.print_result(ocr_results);
    string save_path = Path.GetDirectoryName(image_path) + "\\" + Path.GetFileNameWithoutExtension(image_path) + "_reault.jpg";
    Utility.VisualizeBboxes(image.Clone(), ocr_results, save_path);
}

下图是文本区域识别与处理后的文本区域,可以看出经过模型识别与轮廓处理,已经可以实现文本区域完全获取。

demo_1_reault

根据识别结果,识别文本方向并最后识别文字内容。

image-20230422143911528

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

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

相关文章

Kali 安装中文输入法(超详细)

1.进入管理员下的控制台。 2. 输入密码后点击“授权”。 3.在控制台内输入下面的命令。 apt install fcitx 4.敲击回车后会开始安装&#xff0c;这里输入“y”。 5.回车后会继续进行安装&#xff0c;安装完成后会自动停止。 6.输入下面的命令来安装google输入法。 apt-get in…

【Python】【进阶篇】22、Django模板加载与响应

目录 22、Django模板加载与响应1. 什么是模板1) 模板的配置2) 修改settings配置文件 2. 模板的加载与响应方式3. render方法详解 22、Django模板加载与响应 在前文章节《Django模板系统》中&#xff0c;我们对 Django 的模板系统有了初步的认识&#xff0c;在本章我们将重点讲…

mongodb v4.4.19 分片精进实操,吐血分享

第一步&#xff1a;创建文件夹&#xff0c;data1111存放config server,data2222/data3333存放shard 第二步&#xff1a;启动服务 2.1 启动config server 2.2 启动shard --port 设置端口 --dbpath 设置数据库路径 --logappend 使用追加的方式写日志 --replS…

ReentrantLock原理剖析

前言 本文主要讲解底层逻辑&#xff0c;基本不会贴代码&#xff0c;目的是让大家能够真正的知晓原理&#xff0c;对照着逻辑去理解代码看代码也会很快就能看懂。 在讲ReentrantLock原理之前&#xff0c;我们先回顾下ReentrantLock的基本用法。ReentrantLock是一个锁编程api&am…

Compiler Lab2- 自制极简编译器

笔者实现的这个超级迷你版编译器&#xff08;词法分析、语法分析、生成中间代码&#xff08;cpp&#xff09;&#xff09;仅支持四则运算&#xff0c;功能真的是非常非常简单&#xff0c;不过其中的流程&#xff08;词法分析 -> 语法分析 -> 中间代码生成&#xff09;还是…

各SQL引擎的SQL转换过程对比

SQL引擎 参考文档:高级语言的解析过程—解析树 从 MySQL、Oracle、TiDB、CK,到 Hive、HBase、Spark,从关系型数据库到大数据计算引擎,他们大都可以借助 SQL 引擎,实现 “接受一条 sql 语句然后返回查询结果” 的功能。 他们核心的执行逻辑都是一样的,大致可以通过下面…

【五一创作】《嵌入式系统》知识总结7:GPIO寄存器

总述 每组端口具有7个寄存器 • 实现对GPIO端口初始化配置和数据输入输出控制 1. 配置寄存器&#xff1a;GPIOx_CRL、GPIOx_CRH 用来选择引脚功能&#xff0c;例如输入或输出 2. 数据寄存器&#xff1a;GPIOx_IDR、GPIOx_ODR 用来保存引脚输入电平或输出电平 3. 位控寄存器…

数据库管理-第七十一期 五一,休息?(20230503)

数据库管理 2023-05-03 第七十一期 五一&#xff0c;休息&#xff1f;1 备份2 两个DDL3 问题处理4 问题排查总结 第七十一期 五一&#xff0c;休息&#xff1f; 好不容易&#xff0c;熬过万恶的6天班来到了五一假期&#xff0c;想着好好休息&#xff0c;顺便把绝地幸存者给通关…

Java 基础进阶篇(九)—— 常用 API

文章目录 一、Object 类二、Objects 工具类三、Math 类四、System 类五、BigDecimal 类 一、Object 类 一个类要么默认继承了 Object 类&#xff0c;要么间接继承了 Object 类&#xff0c;Object 类是 java 中的祖宗类。Object 类的方法是一切子类都可以直接使用的。 因此&…

Obsidian +Obsidian Git插件 + Gitee 自动同步笔记

在Obsidian 关闭安全模式 然后再插件市场里面搜索并下载Obsidian Git 这个插件 注意&#xff1a;这里需要科学上网才能搜索并下载&#xff0c;我看很多博主都没说这件事情 然后再你的Gitee中&#xff0c;新建一个仓库 把这两个勾选上&#xff0c;然后随便选个语言&#xff0…

【JavaEE】应用层UDP协议

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 本篇文章将为大家介绍应用层中UDP协议~~ 在应用层这里&#xff0c;虽然存在一些现有的协议&#xff08;HTTP&#xff09;&#xff0c;但是也有很多情况&#xff0c;需要程序猿自定制协议&a…

Windeployqt 打包,缺少DLL 的原因分析,解决方法

很多同学使用工具windeployqt进行打包发布后&#xff0c;运行exe文件时&#xff0c;还是会出现下图所示的系统错误提示&#xff0c;这种情况就表示相关的DLL 库文件没有被正确打包。可是windeployqt明确显示运行正常啊&#xff0c;难道是QT自家的windeployqt这个工具有bug&…

2024年浙大MBA创客班项目提面如何申请?

2024年浙大MBA创客班提面如何申请&#xff1f;在目前的提前批面试申请过程中不少考生都不太清楚&#xff0c;专注浙大的杭州达立易考教育本期将项目的提前批面试和常规批复试申请基本流程和关键信息整理出来&#xff0c;帮助考生更精准的进行面试准备。 一、浙大MBA创客班项目…

基于比较排序算法总结

1.数据无法全部放入内存 2.数据以数据流形式

操作系统2(多处理器编程)

一、并发 1.操作系统是最早的并发程序之一 2.并发的基本单位&#xff1a;线程 共享内存的多个执行流 执行流拥有独立的堆栈/寄存器共享全部的内存&#xff08;指针可以互相引用&#xff09; 3.实现原子性 lock(&lk)unlock(&lk) 实现临界区(critical section)之间…

keepalived脑裂现象

Keepealived最常见的问题是会出现脑裂现象&#xff1a; Master一直发送心跳消息给backup主机&#xff0c;如果中间的链路突然断掉&#xff0c;backup主机将无法收到master主机发送过来的心跳消息&#xff08;也就是vrrp报文&#xff09;&#xff0c;backup这时候会立即抢占mas…

Snmputil和Snmputilg工具的下载和基本使用 SNMP协议 Windows系统SNMP服务的安装教程

⬜⬜⬜ ---&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; (*^▽^*)欢迎光临 &#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;---⬜⬜⬜ ✏️write in front✏️ &#x1f4dd;个人主页&#xff1a;陈丹宇jmu &#x1f381;欢迎各位→…

现代CMake高级教程 - 第 7 章:变量与缓存

双笙子佯谬老师的【公开课】现代CMake高级教程课程笔记 第 7 章&#xff1a;变量与缓存 重复执行 cmake -B build 会有什么区别&#xff1f; ❯ cmake -B build -- The C compiler identification is GNU 11.3.0 -- The CXX compiler identification is GNU 11.3.0 -- Detec…

C++:分治算法之输油管道问题

目录 描述 输入 输出 输入样例 输出样例 分析 代码 运行结果 描述 ¢ 某石油公司计划建造一条 由东向西 的主输油管道。该管道要穿过一个有n口油井的油田。从每口油井都要有一条输油管道沿最短路经&#xff08;或南或北&#xff09;与主管道相连。 ¢ 如果给定…

如何区分GPT3.5和4?

切换模型 前两天申请的GPT 4的API调用权限终于申请下来了。 这两天我也是抓紧开发&#xff0c;让自己搭建的国内网站&#xff08;aichatroom.cn&#xff09;可以快速支持上使用GPT 4。 GPT 3.5和GPT4的区别 GPT-3.5 和 GPT-4 分别代表了 OpenAI 发布的两个不同版本的自然语言处…