前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >PaddleOCR C++(三)---动态库返回识别结果及矩形位置

PaddleOCR C++(三)---动态库返回识别结果及矩形位置

作者头像
Vaccae
发布于 2021-07-30 08:08:18
发布于 2021-07-30 08:08:18
2.2K00
代码可运行
举报
文章被收录于专栏:微卡智享微卡智享
运行总次数:0
代码可运行

PaddleOCR C++学习笔记(二)》尝试做图像的分割,结果都效果不明显,所以这篇我们从OCR识别这里来处理,将返回的识别字符和对应的识别矩形框都显示出来,用于区分识别的效果。

实现效果

上面的就是实现的效果图,从上面可以看出,识别的位置及识别的字符串也都在原图中绘制出来了,知道了对应的位置,比返回一串整体的字符串要效果好不少。

相应的里面也可以看出,识别的效果还是有待加强,几张图中,做过透视变换后的这一张图识别的效果是最好的,所有的数字都识别了出来。

而同样的做了透视变换,下面这两张:

上面这个可以看出,定位文本时数字1只截取了其中一部分,所以识别时被认成T了,而定位的文本框中也有重复的,像23这一个框定位了一次,结果又把234678这个框定位了一下,并且只识别出来278。

而上面这个图中,定位出来识别的效果还不错,除了数字5识别为5.0,只要识别出来的都对,但是同样,数字10,13,15,11还有3都没有检测出来。

其实这也看出来,如果真的是想识别效果好,还是需要自己训练模型,这个OCR应该是对文本的效果更好。

当然本篇的重点其实还是对PaddleOCR的动态为封装,实现外部调用好返回的是字符串加对应位置的列表,接下来就是正篇开始。

代码实现

微卡智享

PaddleOCR动态库部分修改

01

定义结构体

要返回对应的数组列表,首先就是要在动态库中定义名为OCRTextRect结构体,位置定义在了自己新建的ocr_export.h里。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct OCRTextRect {
public:
  char* OCRText;  //识别的信息
  int ptx, pty;       //Rect的起始坐标
  int width, height;  //Rect的宽和高

  OCRTextRect() : OCRText(""), ptx(0), pty(0), width(0), height(0)
  {
  }
};

结构体中定义了返回的字符串char*,然后加上了矩形Rect的起始坐标点X,Y,剩下的就是宽和高的长度。

这里要强调一个重点,为什么会用结构体?在动态库中,千万不要使用STL库的东西,容易发生内存的重分配问题,原因STL库全都是基于模板的,模板是在编译器生成的。这也就是说同一份STL代码在不同动态库中有各自的实现,如果只是方法多了一份自然就没问题,但是部分STL容器里面存有一些静态变量,因此多个实现会导致多份静态变量,然后导致某些方法的调用出现差别,最终导致内存操作异常而崩溃。

因此像STL库中std::vector,std::string这些都不能使用。

02

增加动态库外部调用函数

增加了一个PaddleOCRTextRect外部调用的函数。

ocr_export.cpp中的实现方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
DLLEXPORT int PaddleOCRTextRect(cv::Mat& img, OCRTextRect* resptr)
{
  std::vector<std::pair<std::string, cv::Rect>> str_res;
  std::string tmpstr;

  if (!img.data) {
    return 0;
  }
  PaddleOCR::OCRConfig config = readOCRConfig();
  //打印config参数
  config.PrintConfigInfo();

  //图像检测文本
  PaddleOCR::DBDetector det(config.det_model_dir, config.use_gpu, config.gpu_id,
    config.gpu_mem, config.cpu_math_library_num_threads,
    config.use_mkldnn, config.max_side_len, config.det_db_thresh,
    config.det_db_box_thresh, config.det_db_unclip_ratio,
    config.use_polygon_score, config.visualize,
    config.use_tensorrt, config.use_fp16);

  PaddleOCR::Classifier* cls = nullptr;
  if (config.use_angle_cls == true) {
    cls = new PaddleOCR::Classifier(config.cls_model_dir, config.use_gpu, config.gpu_id,
      config.gpu_mem, config.cpu_math_library_num_threads,
      config.use_mkldnn, config.cls_thresh,
      config.use_tensorrt, config.use_fp16);
  }

  PaddleOCR::CRNNRecognizer rec(config.rec_model_dir, config.use_gpu, config.gpu_id,
    config.gpu_mem, config.cpu_math_library_num_threads,
    config.use_mkldnn, config.char_list_file,
    config.use_tensorrt, config.use_fp16);

  //检测文本框
  std::vector<std::vector<std::vector<int>>> boxes;
  det.Run(img, boxes);
  //OCR识别
  str_res = rec.RunOCR(boxes, img, cls);

  try
  {
    for (int i = 0; i < str_res.size(); ++i) {
      char* reschar = new char[str_res[i].first.length() + 1];
      str_res[i].first.copy(reschar, std::string::npos);
      resptr[i].OCRText = reschar;
      resptr[i].ptx = str_res[i].second.x;
      resptr[i].pty = str_res[i].second.y;
      resptr[i].width = str_res[i].second.width;
      resptr[i].height = str_res[i].second.height;

      //std::cout << "cout:" << str_res[i].first << std::endl;
    }
  }
  catch (const std::exception& ex)
  {
    std::cout << ex.what() << std::endl;
  }

  return str_res.size();
}

方法中返回的int是具体识别的数组中的个数,在外部调用时可以用这个来判断,因为传入参数中OCRTextRect的指针,需要外部调用前先分配的数组的大小,所以外面的定义数组大小可能会定义更大,返回的int可以知道具体是识别了多少个矩形框。

03

ocr_rec.cpp的修改

前面文章说了ocr_rec.cpp里面是识别的方法,里面通过RunOCR函数进入,其中GetRotateCropImage的函数,用于处理生成的boxes的矩形点,然后截图这里面的图形进行OCR识别的。

在不动原来的GetRotateCropImage函数方法,我们再重写一个GetRotateCropImage,加入一个cv::Rect的参数用于生成截取的矩形。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
cv::Mat CRNNRecognizer::GetRotateCropImage(const cv::Mat& srcimage, std::vector<std::vector<int>> box, cv::Rect& rect)
{
    cv::Mat image;
    srcimage.copyTo(image);
    std::vector<std::vector<int>> points = box;

    int x_collect[4] = { box[0][0], box[1][0], box[2][0], box[3][0] };
    int y_collect[4] = { box[0][1], box[1][1], box[2][1], box[3][1] };
    int left = int(*std::min_element(x_collect, x_collect + 4));
    int right = int(*std::max_element(x_collect, x_collect + 4));
    int top = int(*std::min_element(y_collect, y_collect + 4));
    int bottom = int(*std::max_element(y_collect, y_collect + 4));

    cv::Mat img_crop;
    rect = cv::Rect(left, top, right - left, bottom - top);
    image(rect).copyTo(img_crop);

    for (int i = 0; i < points.size(); i++) {
        points[i][0] -= left;
        points[i][1] -= top;
    }

    int img_crop_width = int(sqrt(pow(points[0][0] - points[1][0], 2) +
        pow(points[0][1] - points[1][1], 2)));
    int img_crop_height = int(sqrt(pow(points[0][0] - points[3][0], 2) +
        pow(points[0][1] - points[3][1], 2)));

    cv::Point2f pts_std[4];
    pts_std[0] = cv::Point2f(0., 0.);
    pts_std[1] = cv::Point2f(img_crop_width, 0.);
    pts_std[2] = cv::Point2f(img_crop_width, img_crop_height);
    pts_std[3] = cv::Point2f(0.f, img_crop_height);

    cv::Point2f pointsf[4];
    pointsf[0] = cv::Point2f(points[0][0], points[0][1]);
    pointsf[1] = cv::Point2f(points[1][0], points[1][1]);
    pointsf[2] = cv::Point2f(points[2][0], points[2][1]);
    pointsf[3] = cv::Point2f(points[3][0], points[3][1]);

    cv::Mat M = cv::getPerspectiveTransform(pointsf, pts_std);

    cv::Mat dst_img;
    cv::warpPerspective(img_crop, dst_img, M,
        cv::Size(img_crop_width, img_crop_height),
        cv::BORDER_REPLICATE);

    if (float(dst_img.rows) >= float(dst_img.cols) * 1.5) {
        cv::Mat srcCopy = cv::Mat(dst_img.rows, dst_img.cols, dst_img.depth());
        cv::transpose(dst_img, srcCopy);
        cv::flip(srcCopy, srcCopy, 0);
        return srcCopy;
    }
    else {
        return dst_img;
    }
}

同样的RunOCR方法原来是void没有返回函数的,这里面我们我们也重写了这个方法返回为std::vector<std::pair<std::string, cv::Rect>>,用于最终处理存放到结构体中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
std::vector<std::pair<std::string, cv::Rect>> CRNNRecognizer::RunOCR(std::vector<std::vector<std::vector<int>>> boxes, cv::Mat& img, Classifier* cls)
{
    cv::Mat srcimg;
    img.copyTo(srcimg);
    cv::Mat crop_img;
    cv::Mat resize_img;

    std::cout << "The predicted text is :" << std::endl;
    int index = 0;
    std::vector<std::pair<std::string, cv::Rect>> vtsresstr;
    std::vector<std::string> str_res;
    cv::Rect tmprect;
    for (int i = 0; i < boxes.size(); i++) {
        crop_img = GetRotateCropImage(srcimg, boxes[i], tmprect);

        if (cls != nullptr) {
            crop_img = cls->Run(crop_img);
        }

        float wh_ratio = float(crop_img.cols) / float(crop_img.rows);

        this->resize_op_.Run(crop_img, resize_img, wh_ratio, this->use_tensorrt_);

        this->normalize_op_.Run(&resize_img, this->mean_, this->scale_,
            this->is_scale_);

        std::vector<float> input(1 * 3 * resize_img.rows * resize_img.cols, 0.0f);

        this->permute_op_.Run(&resize_img, input.data());

        // Inference.
        auto input_names = this->predictor_->GetInputNames();
        auto input_t = this->predictor_->GetInputHandle(input_names[0]);
        input_t->Reshape({ 1, 3, resize_img.rows, resize_img.cols });
        input_t->CopyFromCpu(input.data());
        this->predictor_->Run();

        std::vector<float> predict_batch;
        auto output_names = this->predictor_->GetOutputNames();
        auto output_t = this->predictor_->GetOutputHandle(output_names[0]);
        auto predict_shape = output_t->shape();

        int out_num = std::accumulate(predict_shape.begin(), predict_shape.end(), 1,
            std::multiplies<int>());
        predict_batch.resize(out_num);

        output_t->CopyToCpu(predict_batch.data());

        // ctc decode
        int argmax_idx;
        int last_index = 0;
        float score = 0.f;
        int count = 0;
        float max_value = 0.0f;

        for (int n = 0; n < predict_shape[1]; n++) {
            argmax_idx =
                int(Utility::argmax(&predict_batch[n * predict_shape[2]],
                    &predict_batch[(n + 1) * predict_shape[2]]));
            max_value =
                float(*std::max_element(&predict_batch[n * predict_shape[2]],
                    &predict_batch[(n + 1) * predict_shape[2]]));

            if (argmax_idx > 0 && (!(n > 0 && argmax_idx == last_index))) {
                score += max_value;
                count += 1;
                str_res.push_back(label_list_[argmax_idx]);
            }
            last_index = argmax_idx;
        }
        score /= count;
        cv::String tmpstr;
        //for (int i = 0; i < str_res.size(); i++) {
        //    tmpstr += str_res[i];
        //    std::cout << tmpstr;
        //}
        for (int i = index; i < str_res.size(); i++) {
            tmpstr += str_res[i];
            std::cout << tmpstr;
        }
        index = str_res.size();
        std::cout << "\tscore: " << score << std::endl;
        std::pair<std::string, cv::Rect> tmppair;
        tmppair.first = tmpstr;
        tmppair.second = tmprect;
        vtsresstr.push_back(tmppair);
    }
    return vtsresstr;
}

这样最终PaddleOCRTextRect外部调用里面就可以给OCRTextRect结构体数组进行赋值了。

调用程序修改

01

定义结构体

和动态库里面一样,在调用动态库的程序里面也要先定义OCRTextRect的结构体。

02

加入调用函数

加入typedef定义动态库的调用函数,并写一个外部调用的方法。

03

其实的修改

再增加两个函数,实现将返回的OCRTextRect结构体数组转换为vector容器,插入的过程按照从上到下,从左到右的顺序进行排序,所以又写了一个二分查找的算法。

完整的PaddleOCRAPI

PaddleOCRApi.h

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#pragma once
//通过调用windowsAPI 来加载和卸载DLL  
#include <Windows.h>  
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <locale>
#include <codecvt>
#include "..\..\Utils\CvUtils.h"

struct OCRTextRect {
public:
  char* OCRText;  //识别的信息
  int ptx, pty;       //Rect的起始坐标
  int width, height;  //Rect的宽和高

  OCRTextRect() {
    OCRText = nullptr;
    ptx = 0;
    pty = 0;
    width = 0;
    height = 0;
  }
};

class PaddleOcrApi
{
private:
  typedef char*(*DllFun)(cv::Mat&);

  typedef int (*DllFunOCRTextRect)(cv::Mat&, OCRTextRect*);

  //二分查找
  static int binarySearch(std::vector<std::pair<std::string, cv::Rect>>& vtsrect, const OCRTextRect rect);

public:
  static std::string GetPaddleOCRText(cv::Mat& src);

  static std::string GetPaddleOCRTextRect(cv::Mat& src, std::vector<std::pair<std::string, cv::Rect>>& vtsocr);

  //排序OCRTextRect转为vector容器
  static std::vector<std::pair<std::string, cv::Rect>> SortRectPair(const OCRTextRect* vtsrect, const int count);
  //透视变换获取图像
  static cv::Mat GetPerspectiveMat(cv::Mat& src, int iterations = 1);

  //分割数据华容道图像
  static std::vector<cv::Mat> GetNumMat(cv::Mat& src);

  // string的编码方式为utf8,则采用:
  static std::string wstr2utf8str(const std::wstring& str);
  static std::wstring utf8str2wstr(const std::string& str);

  // string的编码方式为除utf8外的其它编码方式,可采用:
  static std::string wstr2str(const std::wstring& str, const std::string& locale);
  static std::wstring str2wstr(const std::string& str, const std::string& locale);

};

PaddleOCRAPI.cpp

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include "PaddleOcrApi.h"

//二分查找定位当前插入序号
int PaddleOcrApi::binarySearch(std::vector<std::pair<std::string, cv::Rect>>& vtsrect, const OCRTextRect rect)
{
  int left = 0;
  int right = vtsrect.size() - 1;
  int res = 0;

  std::pair<std::string, cv::Rect> lastitem("", cv::Rect());

  while (left <= right) {
    int mid = left + (right - left) / 2;
    //获取中位值
    std::pair<std::string, cv::Rect> item(vtsrect[mid].first,vtsrect[mid].second);

    //判断最后值是否相等
    if (item.first == lastitem.first && item.second.x == lastitem.second.x
      && item.second.y == lastitem.second.y) {
      res = mid;
      break;
    }
    else if (rect.pty+rect.height > item.second.y + item.second.height / 2) {
      lastitem.first = item.first;
      lastitem.second = item.second;
      left = mid + 1;
    }
    else if (rect.ptx < item.second.x) {
      lastitem.first = item.first;
      lastitem.second = item.second;
      right = mid - 1;
    }
    else if (rect.ptx >= item.second.x) {
      lastitem.first = item.first;
      lastitem.second = item.second;
      left = mid + 1;
    }
  }

  return res;
}

std::string PaddleOcrApi::GetPaddleOCRText(cv::Mat& src)
{
  std::string resstr;
  DllFun funName;
  HINSTANCE hdll;

  try
  {
    hdll = LoadLibrary(L"PaddleOCRExport.dll");
    if (hdll == NULL)
    {
      resstr = "加载不到PaddleOCRExport.dll动态库!";
      FreeLibrary(hdll);
      return resstr;
    }

    funName = (DllFun)GetProcAddress(hdll, "PaddleOCRText");
    if (funName == NULL)
    {
      resstr = "找不到PaddleOCRText函数!";
      FreeLibrary(hdll);
      return resstr;
    }

    resstr = funName(src);
    // 将utf-8的string转换为wstring
    std::wstring wtxt = utf8str2wstr(resstr);
    // 再将wstring转换为gbk的string
    resstr = wstr2str(wtxt, "Chinese");

    FreeLibrary(hdll);
  }
  catch (const std::exception& ex)
  {
    resstr = ex.what();
    return "Error:" + resstr;
    FreeLibrary(hdll);
  }

  return resstr;
}

std::string PaddleOcrApi::GetPaddleOCRTextRect(cv::Mat& src, std::vector<std::pair<std::string, cv::Rect>>& vtsocr)
{
  std::string resstr;
  DllFunOCRTextRect funName;
  HINSTANCE hdll;

  try
  {
    hdll = LoadLibrary(L"PaddleOCRExport.dll");
    if (hdll == NULL)
    {
      resstr = "加载不到PaddleOCRExport.dll动态库!";
      FreeLibrary(hdll);
      return resstr;
    }

    funName = (DllFunOCRTextRect)GetProcAddress(hdll, "PaddleOCRTextRect");
    if (funName == NULL)
    {
      resstr = "找不到PaddleOCRText函数!";
      FreeLibrary(hdll);
      return resstr;
    }

    OCRTextRect vts[100];

    int count = funName(src, vts);

    std::cout << "size:" << std::to_string(count) << std::endl;
    
    for (int i = 0; i< count; ++i) {
      std::cout << vts[i].OCRText<< std::endl;
      std::cout << "Rect:x=" << std::to_string(vts[i].ptx);
      std::cout << " y=" << std::to_string(vts[i].pty);
      std::cout << " width=" << std::to_string(vts[i].width);
      std::cout << " height=" << std::to_string(vts[i].height) << std::endl;

      OCRTextRect tmprect = vts[i];
        // 将utf-8的string转换为wstring
      std::wstring wtxt = utf8str2wstr(tmprect.OCRText);
      // 再将wstring转换为gbk的string
      std::string tmpstr = wstr2str(wtxt, "Chinese");

      // 通过二分查找排序插入到vtsocr的容器中
      int index = binarySearch(vtsocr, vts[i]);
      vtsocr.insert(vtsocr.begin() + index, std::pair<std::string, cv::Rect>(tmpstr,
        cv::Rect(tmprect.ptx, tmprect.pty, tmprect.width, tmprect.height)));
    }
    resstr = "OK";

    FreeLibrary(hdll);
  }
  catch (const std::exception& ex)
  {
    resstr = ex.what();
    return "Error:" + resstr;
    FreeLibrary(hdll);
  }
  return resstr;
}

//排序OCRTextRect
std::vector<std::pair<std::string, cv::Rect>> PaddleOcrApi::SortRectPair(const OCRTextRect* vtsrect, const int count)
{
  std::vector<std::pair<std::string, cv::Rect>> resvts;

  return std::vector<std::pair<std::string, cv::Rect>>();
}

cv::Mat PaddleOcrApi::GetPerspectiveMat(cv::Mat& src, int iterations)
{
  cv::Mat tmpsrc, cannysrc, resultMat;
  src.copyTo(tmpsrc);

  //高斯滤波
  cv::GaussianBlur(tmpsrc, tmpsrc, cv::Size(5, 5), 0.5, 0.5);

  int srcArea = tmpsrc.size().area();
  float maxArea = 0;
  int maxAreaidx = -1;

  std::vector<cv::Mat> channels;
  cv::Mat B_src, G_src, R_src, dstmat;
  cv::split(tmpsrc, channels);

  int minthreshold = 120, maxthreshold = 200;

  //B进行Canny
  //大津法求阈值
  CvUtils::GetMatMinMaxThreshold(channels[0], minthreshold, maxthreshold, 1);
  std::cout << "OTSUmin:" << minthreshold << "  OTSUmax:" << maxthreshold << std::endl;
  //Canny边缘提取
  cv::Canny(channels[0], B_src, minthreshold, maxthreshold);

  //大津法求阈值
  CvUtils::GetMatMinMaxThreshold(channels[1], minthreshold, maxthreshold, 1);
  std::cout << "OTSUmin:" << minthreshold << "  OTSUmax:" << maxthreshold << std::endl;
  //Canny边缘提取
  Canny(channels[1], G_src, minthreshold, maxthreshold);

  //大津法求阈值
  CvUtils::GetMatMinMaxThreshold(channels[2], minthreshold, maxthreshold, 1);
  std::cout << "OTSUmin:" << minthreshold << "  OTSUmax:" << maxthreshold << std::endl;
  //Canny边缘提取
  Canny(channels[2], R_src, minthreshold, maxthreshold);


  bitwise_or(B_src, G_src, dstmat);
  bitwise_or(R_src, dstmat, dstmat);
  //CvUtils::SetShowWindow(dstmat, "dstmat", 700, 20);
  //imshow("dstmat", dstmat);


  std::vector<std::vector<cv::Point>> contours;
  std::vector<cv::Vec4i> hierarchy;
  findContours(dstmat, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);

  cv::Mat dstcontour = cv::Mat::zeros(cannysrc.size(), CV_8SC3);
  cv::Mat tmpcontour;
  dstcontour.copyTo(tmpcontour);

  //定义拟合后的多边形数组
  std::vector<std::vector<cv::Point>> vtshulls(contours.size());

  for (int i = 0; i < contours.size(); ++i) {
    //判断轮廓形状,不是四边形的忽略掉
    double lensval = 0.01 * arcLength(contours[i], true);
    std::vector<cv::Point> convexhull;
    approxPolyDP(cv::Mat(contours[i]), convexhull, lensval, true);

    //拟合的多边形存放到定义的数组中
    vtshulls[i] = convexhull;

    //不是四边形的过滤掉
    if (convexhull.size() != 4) continue;

    //求出最小旋转矩形
    cv::RotatedRect rRect = minAreaRect(contours[i]);
    //更新最小旋转矩形中面积最大的值
    if (rRect.size.height == 0) continue;

    if (rRect.size.area() > maxArea && rRect.size.area() > srcArea * 0.1
      && !CvUtils::CheckRectBorder(src, rRect)) {
      maxArea = rRect.size.area();
      maxAreaidx = i;
    }
  }

  //找到符合条码的最大面积的轮廓进行处理
  if (maxAreaidx >= 0) {
    std::cout << "iterations:" << iterations << "  maxAreaidx:" << maxAreaidx << std::endl;
    //获取最小旋转矩形
    cv::RotatedRect rRect = minAreaRect(contours[maxAreaidx]);
    cv::Point2f vertices[4];
    //重新排序矩形坐标点,按左上,右上,右下,左下顺序
    CvUtils::SortRotatedRectPoints(vertices, rRect);

    std::cout << "Rect:" << vertices[0] << vertices[1] << vertices[2] << vertices[3] << std::endl;

    //根据获得的4个点画线
    for (int k = 0; k < 4; ++k) {
      line(dstcontour, vertices[k], vertices[(k + 1) % 4], cv::Scalar(255, 0, 0));
    }

    //计算四边形的四点坐标
    cv::Point2f rPoints[4];
    CvUtils::GetPointsFromRect(rPoints, vertices, vtshulls[maxAreaidx]);
    for (int k = 0; k < 4; ++k) {
      line(dstcontour, rPoints[k], rPoints[(k + 1) % 4], cv::Scalar(255, 255, 255));
    }


    //采用离最小矩形四个点最近的重新设置范围,将所在区域的点做直线拟合再看看结果
    cv::Point2f newPoints[4];
    CvUtils::GetPointsFromFitline(newPoints, rPoints, vertices);
    for (int k = 0; k < 4; ++k) {
      line(dstcontour, newPoints[k], newPoints[(k + 1) % 4], cv::Scalar(255, 100, 255));
    }


    //根据最小矩形和多边形拟合的最大四个点计算透视变换矩阵    
    cv::Point2f rectPoint[4];
    //计算旋转矩形的宽和高
    float rWidth = CvUtils::CalcPointDistance(vertices[0], vertices[1]);
    float rHeight = CvUtils::CalcPointDistance(vertices[1], vertices[2]);
    //计算透视变换的左上角起始点
    float left = dstcontour.cols;
    float top = dstcontour.rows;
    for (int i = 0; i < 4; ++i) {
      if (left > newPoints[i].x) left = newPoints[i].x;
      if (top > newPoints[i].y) top = newPoints[i].y;
    }

    rectPoint[0] = cv::Point2f(left, top);
    rectPoint[1] = rectPoint[0] + cv::Point2f(rWidth, 0);
    rectPoint[2] = rectPoint[1] + cv::Point2f(0, rHeight);
    rectPoint[3] = rectPoint[0] + cv::Point2f(0, rHeight);


    //计算透视变换矩阵    
    cv::Mat warpmatrix = getPerspectiveTransform(rPoints, rectPoint);
    cv::Mat resultimg;
    //透视变换
    warpPerspective(src, resultimg, warpmatrix, resultimg.size(), cv::INTER_LINEAR);

    /*CvUtils::SetShowWindow(resultimg, "resultimg", 200, 20);
    imshow("resultimg", resultimg);*/

    //载取透视变换后的图像显示出来
    cv::Rect cutrect = cv::Rect(rectPoint[0], rectPoint[2]);
    resultMat = resultimg(cutrect);

    //CvUtils::SetShowWindow(resultMat, "resultMat", 600, 20);
    //cv::imshow("resultMat", resultMat);

    iterations--;
    if (iterations > 0) {
      resultMat = GetPerspectiveMat(resultMat, iterations);
    }
  }
  else {
    src.copyTo(resultMat);
  }
  return resultMat;
}

std::vector<cv::Mat> PaddleOcrApi::GetNumMat(cv::Mat& src)
{
  std::vector<cv::Mat> vts;
  cv::Mat tmpsrc, tmpgray, threshsrc;
  src.copyTo(tmpsrc);

  //使用拉普拉斯算子实现图像对比度提高
  cv::Mat Laplancekernel = (cv::Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);
  cv::Mat imgLaplance, resimg;
  cv::filter2D(tmpsrc, imgLaplance, CV_32F, Laplancekernel);
  tmpsrc.convertTo(resimg, CV_32F);
  resimg = resimg - imgLaplance;
  resimg.convertTo(tmpsrc, CV_8UC3);
  CvUtils::SetShowWindow(tmpsrc, "resimg", 700, 20);
  cv::imshow("resimg", tmpsrc);


  cv::cvtColor(tmpsrc, tmpgray, cv::COLOR_BGR2GRAY);

  //二值化
  cv::threshold(tmpgray, threshsrc, 0, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);

  CvUtils::SetShowWindow(threshsrc, "threshsrc", 700, 20);
  cv::imshow("threshsrc", threshsrc);

  cv::Mat dst;
  cv::distanceTransform(threshsrc, dst, cv::DIST_L1, 3, 5);
  CvUtils::SetShowWindow(dst, "dst1", 700, 20);
  cv::imshow("dst1", dst);

  cv::normalize(dst, dst, 0, 1, cv::NORM_MINMAX);
  CvUtils::SetShowWindow(dst, "dst2", 500, 20);
  cv::imshow("dst2", dst);

  cv::threshold(dst, dst, 0.1, 1, cv::THRESH_BINARY);
  CvUtils::SetShowWindow(dst, "dst3", 500, 20);
  cv::imshow("dst3", dst);


  //std::vector<cv::Vec4f> lines;
  //cv::HoughLinesP(dst_8u, lines, 1, CV_PI / 180.0, 200, 50, 40);

  //cv::Scalar color = cv::Scalar(0, 0, 255);
  //for (int i = 0; i < lines.size(); ++i) {
  //  cv::Vec4f line = lines[i];
  //  cv::putText(tmpsrc, std::to_string(i), cv::Point(line[0], line[1]), 1, 1, color);
  //  cv::line(tmpsrc, cv::Point(line[0], line[1]), cv::Point(line[2], line[3]), color);
  //}
  //CvUtils::SetShowWindow(tmpsrc, "tmpsrc", 300, 20);
  //cv::imshow("tmpsrc", tmpsrc);

  //开运算
  cv::Mat morph1, morph2, morphcalc;
  cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 1));
  cv::morphologyEx(dst, morph1, cv::MORPH_CLOSE, kernel, cv::Point(-1, -1), 1);
  CvUtils::SetShowWindow(morph1, "morph1", 500, 20);
  cv::imshow("morph1", morph1);

  //cv::morphologyEx(threshsrc, morph2, cv::MORPH_TOPHAT, kernel);
  //CvUtils::SetShowWindow(morph2, "morph2", 500, 20);
  //cv::imshow("morph2", morph2);

  //morphcalc = threshsrc - morph2;
  //CvUtils::SetShowWindow(morphcalc, "morphcalc", 500, 20);
  //cv::imshow("morphcalc", morphcalc);


  cv::Mat dst_8u;
  morph1.convertTo(dst_8u, CV_8U);
  CvUtils::SetShowWindow(dst_8u, "dst_8u", 300, 20);
  cv::imshow("dst_8u", dst_8u);
  std::vector<std::vector<cv::Point>> contours;
  std::vector<cv::Vec4i> hierarchy;
  findContours(dst_8u, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
  ////定义拟合后的多边形数组
  std::vector<std::vector<cv::Point>> vtshulls;

  //for (int i = 0; i < contours.size(); ++i) {
  //  //cv::drawContours(tmpsrc, contours, i, cv::Scalar(0, 0, 255));
  //  //判断轮廓形状,不是四边形的忽略掉
  //  double lensval = 0.01 * arcLength(contours[i], true);
  //  std::vector<cv::Point> convexhull;
  //  approxPolyDP(cv::Mat(contours[i]), convexhull, lensval, true);

  //  //不是四边形的过滤掉
  //  if (convexhull.size() != 4) continue;
  //  vtshulls.push_back(convexhull);
  //}

  std::cout << "contourssize:" << contours.size() << std::endl;
  cv::Mat dstimg = cv::Mat::zeros(src.size(), CV_8UC1);
  for (int i = 0; i < contours.size(); ++i) {
    cv::drawContours(dstimg, contours, static_cast<int>(i), cv::Scalar::all(255), -1);
  }

  CvUtils::SetShowWindow(dstimg, "dstimg", 300, 20);
  cv::imshow("dstimg", dstimg);


  return vts;
}

std::string PaddleOcrApi::wstr2utf8str(const std::wstring& str)
{
  static std::wstring_convert<std::codecvt_utf8<wchar_t> > strCnv;
  return strCnv.to_bytes(str);
}

std::wstring PaddleOcrApi::utf8str2wstr(const std::string& str)
{
  static std::wstring_convert< std::codecvt_utf8<wchar_t> > strCnv;
  return strCnv.from_bytes(str);
}

std::string PaddleOcrApi::wstr2str(const std::wstring& str, const std::string& locale)
{
  typedef std::codecvt_byname<wchar_t, char, std::mbstate_t> F;
  static std::wstring_convert<F> strCnv(new F(locale));
  return strCnv.to_bytes(str);
}

std::wstring PaddleOcrApi::str2wstr(const std::string& str, const std::string& locale)
{
  typedef std::codecvt_byname<wchar_t, char, std::mbstate_t> F;
  static std::wstring_convert<F> strCnv(new F(locale));
  return strCnv.from_bytes(str);
}

04

main主程序中的调用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
      std::vector<std::pair<std::string, cv::Rect>> vtsocrs;
      PaddleOcrApi::GetPaddleOCRTextRect(resultMat, vtsocrs);

      //输出识别文字
      //if (!resultMat.empty()) {
      //  putText::putTextZH(resultMat, resstr.data(), cv::Point(20, 20), cv::Scalar(0, 0, 255), 1);
      //  cv::putText(resultMat, resstr, cv::Point(20, 50), 1, 1, cv::Scalar(0, 0, 255));
      //}
      std::cout << "输出:" << std::endl;
      for (int i = 0; i < vtsocrs.size(); ++i) {
        int B = cv::theRNG().uniform(0, 255);
        int G = cv::theRNG().uniform(0, 255);
        int R = cv::theRNG().uniform(0, 255);

        cv::Rect tmprect = vtsocrs[i].second;
        std::string tmptext = "N" + std::to_string(i) + ":" + vtsocrs[i].first;
        cv::Point pt = cv::Point(tmprect.x,  tmprect.y);
        cv::rectangle(resultMat, tmprect, cv::Scalar(B, G, R));
        cv::putText(resultMat, tmptext, pt, 1, 1.2, cv::Scalar(B, G, R));

        std::cout << tmptext << std::endl;
      }


      CvUtils::SetShowWindow(resultMat, "cutMat", 600, 20);
      cv::imshow("cutMat", resultMat);

将调用成功后的列表,使用随机颜色显示出来,就实现了文章开头的效果了。

源码中关于动态库里的修改我会上传上来,完整的PaddleOCR的源码各位从PaddleOCR的源码地址中下载即可。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微卡智享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
【韧性架构设计】分布式系统的韧性
由许多协同工作的微服务组成的云原生应用程序架构形成了一个分布式系统。确保分布式系统可用——减少其停机时间——需要提高系统的弹性。弹性是使用提高可用性的策略。弹性策略的示例包括负载平衡、超时和自动重试、截止日期和断路器。
架构师研究会
2022/07/29
5030
【韧性架构设计】分布式系统的韧性
【韧性设计】韧性设计模式:重试、回退、超时、断路器
软件本身并不是目的:它支持您的业务流程并使客户满意。如果软件没有在生产中运行,它就无法产生价值。然而,生产性软件也必须是正确的、可靠的和可用的。
架构师研究会
2022/05/25
1.4K0
【韧性设计】韧性设计模式:重试、回退、超时、断路器
分布式系统的弹性设计
在讨论分布式系统的弹性之前,让我们快速回顾一些基本术语: 弹性Resiliency:任何系统从困难中恢复的能力,(banq注:弹性也就是适应能力)。 分布式系统:一些网络组件通过传递消息来完成一个共同目标。 可用性:任何系统在任何时间点保持正常运行的可能性。 故障与故障:故障Fault是您的系统中是不正确的内部状态。系统中一些常见的故障例子包括: 1.存储层缓慢 2.应用程序中的内存泄露 3.被阻塞的线程 4.依赖性故障 5.在系统中传播坏数据(通常是因为输入数据没有足够的验证) 失败Failure是系统无法执行其预期工作。 失败意味着系统正常运行时间和可用性的损失。故障如果不被封装,会导致在系统中传播,从而导致失败。 当故障Fault转为失败Failure时就意味着系统发生了故障: 弹性就是为了防止故障Fault转化为失败Failure 我们为什么关心系统的弹性? 系统的弹性与其正常运行时间和可用性成正比。系统越有弹性,服务用户的可用性越高。 如果不具有弹性能力,可能会以多种方式影响公司各个方面。 分布式系统的弹性设计很难 我们都明白'可用'至关重要。为了保证可用性,我们需要从零开始建立弹性,以便我们系统中的故障自动恢复。 但是在具有多个分布式系统的复杂微服务架构中建立弹性是很困难的。这些困难是: 1.网络不可靠 2.依赖性总是失败 3.用户行为是不可预测的 虽然构建弹性很难,但并非不可能。遵循一些构建分布式系统的模式可以帮助我们在整个服务中实现较高的正常运行时间。我们将讨论未来的一些模式: 模式[0] = nocode
lyb-geek
2018/09/27
2K0
分布式系统的弹性设计
[微服务架构 ]微服务集成中的3个常见缺陷 - 以及如何避免它们
微服务风靡一时。 他们有一个有趣的价值主张,即在与多个软件开发团队共同开发的同时,将软件快速推向市场。 因此,微服务是在扩展您的开发力量的同时保持高敏捷性和快速的开发速度。
架构师研究会
2019/05/06
1.2K0
[微服务架构 ]微服务集成中的3个常见缺陷 - 以及如何避免它们
【韧性架构】让你的微服务容错的 5 种模式
在本文中,我将介绍微服务中的容错以及如何实现它。如果你在维基百科上查找它,你会发现以下定义:
架构师研究会
2022/06/08
1K0
【韧性架构】让你的微服务容错的 5 种模式
【微服务架构】为故障设计微服务架构
微服务架构可以通过定义明确的服务边界隔离故障。但就像在每个分布式系统中一样,网络、硬件或应用程序级别问题的可能性更高。由于服务依赖关系,任何组件都可能对其消费者暂时不可用。为了最大限度地减少部分中断的影响,我们需要构建可以优雅地响应某些类型的中断的容错服务。
架构师研究会
2022/05/25
5110
【微服务架构】为故障设计微服务架构
【软件架构】支持大规模系统的设计模式和原则
今天,即使是小型初创公司也可能不得不处理数 TB 的数据或构建支持每分钟(甚至一秒钟!)数十万个事件的服务。所谓“规模”,通常是指系统应在短时间内处理的大量请求/数据/事件。
架构师研究会
2022/05/05
6150
【软件架构】支持大规模系统的设计模式和原则
故障驱动的微服务架构设计
此文背景: 之所以发布此文,是有一个直接的原因,就是我们之前在线上遇到了一个使用timeout来判断是否失败的案例,这是真实的,结果就是效果很不好。看了本文中介绍的各种技术和架构模式,让我忽然对之前的这个案例有了一个新的认识,就是“快速失败”不应该依赖于传统的比如timeout这种超时机制来进行,也许使用本文中介绍到的技术(比如:Circuit Breakers)要更加地可靠和受控。 目录 微服务架构的风险 优雅的服务降级 变更管理 健康检查和负载平衡 自愈(Self-healing) 故障转移缓存
ImportSource
2018/04/03
1.4K0
故障驱动的微服务架构设计
深入微服务核心:从架构设计到规模化
《Building Microservices》这本书是吃透微服务的大部头,本文基于全书内容,系统性地阐述了微服务架构的设计原则、实施策略与挑战,从微服务的核心概念出发,延伸到架构设计、服务拆分、集成技术及规模化实践,为开发者提供了构建稳健微服务体系的指导框架。
腾讯云开发者
2025/04/24
2990
深入微服务核心:从架构设计到规模化
业务开发:防御性编程之网络超时与重试机制、幂等机制的关系
网络超时的情况可以分为服务端超时和客户端超时。当api请求超时,客户端并不知道服务端是否成功处理请求,即网络请求超时,服务端业务执行结果可能是成功,也可能是失败。
崔认知
2023/06/19
3930
业务开发:防御性编程之网络超时与重试机制、幂等机制的关系
微服务架构中10个常用的设计模式
从软件开发早期(1960 年代)开始,应对大型软件系统中的复杂性一直是一项令人生畏的任务。多年来为了应对软件系统的复杂性,软件工程师和架构师们做了许多尝试:David Parnas 的模块化和封装 (1972), Edsger W. Dijkstra (1974)的关注点分离以及 SOA(1988)。
架构之家
2022/07/12
9740
微服务架构中10个常用的设计模式
与我一起学习微服务架构设计模式3—微服务架构中的进程间通信
选择合适的进程间通信机制是一个重要的架构决策,它会影响应用的可用性,甚至与事务管理相互影响。
java达人
2019/10/23
1.9K0
万字详解高可用架构设计
系统高可用是一个宏大的命题,从设计思想、架构原则到工程能力、服务管理等等方方面面,每个视角单拆出来都不是一篇文章可以解决的。本文将从大局上全面系统地梳理高可用系统架构,起到一个提纲挈领的作用。
腾讯云开发者
2025/01/07
2.3K0
万字详解高可用架构设计
构建故障恢复系统
作者 | Gandharv Srivastava 译者 | Sambodhi 策划 | marsxxl 1.5 亿,这个数字,是 Capillary 的 Engage+ 产品在新年高峰时段两小时内发送的通信量。即便是这样的小故障,也会影响到我们客户的资本和我们产品的信誉。 故障就像一场大爆炸,它们可以是手榴弹的爆炸,也可以是核弹级别的爆炸,而爆炸造成的破坏取决于爆炸半径。再好的系统,也会有出故障的一天。若不及早发现并加以处置,也会加剧造成更大的破坏。 请注意,这篇文章将着重于微服务设计中的健壮性和
深度学习与Python
2023/03/29
9090
构建故障恢复系统
微服务架构如何避免大规模故障?
点击关注公众号,Java干货及时送达 微服务架构通过一种良好的服务边界划分,能够有效地进行故障隔离。但就像其他分布式系统一样,在网络、硬件或者应用级别上容易出现问题的机率会更高。服务的依赖关系,导致在任何组件暂时不可用的情况下,就它们的消费者而言都是可以接受的。为了能够降低部分服务中断所带来的影响,我们需要构建一个容错服务,来优雅地应对特定类型的服务中断。 本文基于一些在RisingStack的顾问咨询与开发经验,介绍了如何运用一些最常用的技术和架构模型,去构建与维护一个高可用的微服务系统。 如果你不熟
Java技术栈
2022/03/03
4130
设计一个容错的微服务架构
本文介绍了构建和操作高可用性微服务系统的最常见技术和架构模式。如果你不熟悉本文中的模式,那并不一定意味着你做错了。系统设计没有通用解决方案,建立可靠的系统总是会带来额外的成本。
Superbeet
2020/04/17
7170
微服务架构及其最重要的10个设计模式
微服务架构,独享数据库、事件驱动、CQRS、Saga、BFF、API 网关、Strangler、断路器、外部化配置、消费端驱动的契约测试
深度学习与Python
2021/01/06
1.3K0
构建容错软件系统的艺术
我们生活在一个由软件系统驱动的世界。它们已融入我们的日常生活,其持续、可靠的性能不再是奢侈品,而是必需品。企业现在比以往任何时候都更需要确保其系统保持可用性、可靠性和弹性。这种必要性是由满足客户和超越竞争对手的愿望推动的。实现这一目标的秘诀是什么?构建容错软件系统。
用户5166556
2023/10/19
2640
构建容错软件系统的艺术
Istio如何同时实现Hytrix|Ribbon|Zuul|微服务安全的功能?:为微服务引入Istio服务网格(下)
版权说明:本文由高晓雪参照如下文档翻译。魏新宇根据高晓雪的翻译文档,做了适当的注解和文字矫正。 https://developers.redhat.com/download-manager/file/istio_mesh_for_microservices_r1.pdf 本文适合对istio的读者提供泛读参考,对istio理解较深的读者,建议直接阅读英文原文。本系列分上下两篇:上篇为1-3章内容,下篇为4-7章内容。 目录 为微服务引入Istio服务网格 1.介绍 1.1.更快的挑战 1.2.认识I
魏新宇
2018/06/25
2.2K0
重试暂时性故障处理设计-常用的架构设计原则
与远程服务和资源通信的所有应用程序必须对暂时性故障敏感。 对于云中运行的应用程序尤其如此,因为其环境的性质与通过 Internet 建立连接的特点,意味着更容易遇到这种类型的故障。 暂时性故障包括组件和服务瞬间断开网络连接、服务暂时不可用,或者当服务繁忙时出现超时。 这些故障通常可自我纠正,如果在适当的延迟后重复操作,则可能会成功。
jack.yang
2025/04/05
1050
推荐阅读
相关推荐
【韧性架构设计】分布式系统的韧性
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验