当前位置: 首页 > news >正文

文字识别(OCR)专题——基于NCNN轻量级PaddleOCRv4模型C++推理

前言

PaddleOCR 提供了基于深度学习的文本检测、识别和方向检测等功能。其主要推荐的 PP-OCR 算法在国内外的企业开发者中得到广泛应用。在短短的几年时间里,PP-OCR 的累计 Star 数已经超过了32.2k,常常出现在 GitHub Trending 和 Paperswithcode 的日榜和月榜第一位,被认为是当前OCR领域最热门的仓库之一。

PaddleOCR 最初主打的 PP-OCR 系列模型在去年五月份推出了 v3 版本。最近,飞桨 AI 套件团队对 PP-OCRv3 进行了全面改进,推出了重大更新版本 PP-OCRv4。这个新版本预计带来了更先进的技术、更高的性能和更广泛的适用性,将进一步推动OCR技术在各个领域的应用。

PP-OCRv4在速度可比情况下,中文场景端到端 Hmean 指标相比于 PP-OCRv3提升4.25%,效果大幅提升。具体指标如下表所示:
在这里插入图片描述
测试环境:CPU 型号为 Intel Gold 6148,CPU 预测时使用 OpenVINO。

除了更新中文模型,本次升级也优化了英文数字模型,在自有评估集上文本识别准确率提升6%,如下表所示:
在这里插入图片描述
同时,也对已支持的80余种语言识别模型进行了升级更新,在有评估集的四种语系识别准确率平均提升5%以上,如下表所示:
在这里插入图片描述

一、模型转换

1.模型下载

从https://github.com/PaddlePaddle/PaddleOCR/tree/release/2.7 下载要用到的模型,要下载的模型有文本检测模型、文字方向模型、文字识别模型,我这里只下下载了文本检测与文字识别的模型。
在这里插入图片描述
下载好的模型nference.pdiparams为模型参数文件,inference.pdmodel为模型结构文件,这两个文件在转换onnx的时候都要用到。

2.模型转成onnx

使用paddle2ONNX进行模型转换,git地址:https://github.com/paddlepaddle/paddle2onnx, 下载源码然后编译转换,也可以使用在线转换的方法,如果嫌麻烦,最好使用在线的转换方法,在线地址:https://www.paddlepaddle.org.cn/paddle/visualdl/modelconverter/x2paddle
在这里插入图片描述

3. onnx转ncnn模型

这里为了之后在移动部署做准备,选择使用NCNN做最终的模型推理,NCNN封装了很高效的API接品,可以方便地在移动设备和嵌入式系统上进行神经网络的部署和推理。适用于移动设备和嵌入式设备。它被设计用于在各种硬件平台上高效地运行神经网络推断(inference)。NCNN主要特点包括:

  1. 轻量级和高效性: NCNN被设计为一个轻量级框架,具有高度优化的推断性能。它的设计目标是在移动设备和嵌入式设备上实现高效的神经网络推理。

  2. 跨平台支持: NCNN支持多种硬件平台,包括CPU、GPU、DSP等,并且可以在各种操作系统上运行,如Windows、Android、iOS、Linux等。

  3. 优化和硬件加速: NCNN对各种硬件进行了优化,并利用硬件加速特性提高了神经网络推断的性能。

  4. 丰富的模型支持: NCNN支持各种常见的深度学习模型,如AlexNet、VGG、ResNet、MobileNet等,并且兼容一些深度学习框架导出的模型,Caffe、TensorFlow、ONNX等。

可以从https://github.com/Tencent/ncnn 获取源码进行编译,也可以下载官方编译好的lib进行转换,还可以使用在线接口进行转换。在线接地址:https://convertmodel.com/。
在这里插入图片描述

转出来的模型后缀是.param和.bin文件。

二、文本检测

文本检测是旨在从图像或视频中准确地检测和定位文本的位置和边界框,OCR系统中的一个重要组成部分,它为后续的文本识别提供了定位和定界的信息。

  1. 预处理:对输入的图像进行预处理,可能包括图像增强、去噪、尺寸标准化等操作,以便更好地适应文本检测算法。

  2. 文本区域检测:使用特定的算法或模型来检测图像中可能包含文本的区域。常见的方法包括基于区域的方法(如基于区域的CNN(R-CNN)系列)、基于锚点的方法(如SSD和YOLO)、以及基于注意力机制的方法(如EAST、TextBoxes++等)。

  3. 后处理:在获取文本区域的初始预测结果后,可以进行后处理步骤来提高检测的准确性和稳定性。这可能包括非极大值抑制(NMS)来消除重叠的边界框、边框回归以精细调整边界框的位置等。

文本检测类:

#ifndef __OCR_DBNET_H__
#define __OCR_DBNET_H__#include "base_struct.h"
#include <ncnn/net.h>
#include <vector>
#include <ncnn/cpu.h>namespace NCNNOCR
{class DbNet{public:DbNet();~DbNet() {};int read_model(std::string param_path = "data/det.param",std::string bin_path = "data/det.bin", bool use_gpu = true);bool detect(cv::Mat& src, std::vector<TextBox>& results, int _target_size = 1024);private:ncnn::Net net;const float meanValues[3] = { 0.485 * 255, 0.456 * 255, 0.406 * 255 };const float normValues[3] = { 1.0 / 0.229 / 255.0, 1.0 / 0.224 / 255.0,1.0 / 0.225 / 255.0 };float boxThresh = 0.3f;float boxScoreThresh = 0.5f;float unClipRatio = 2.0f;int target_size;};
}#endif //__OCR_DBNET_H__

类实现:

#include "db_net.h"
#include "tools.h"namespace NCNNOCR
{int DbNet::read_model(std::string param_path, std::string bin_path, bool use_gpu){ncnn::set_cpu_powersave(2);ncnn::set_omp_num_threads(ncnn::get_big_cpu_count());net.opt = ncnn::Option();#if NCNN_VULKANnet.opt.use_vulkan_compute = use_gpu;
#endifnet.opt.lightmode = true;net.opt.num_threads = ncnn::get_big_cpu_count();int rp = net.load_param(param_path.c_str());int rb = net.load_model(bin_path.c_str());if (rp == 0 || rb == 0){return false;}return true;}std::vector<TextBox> inline findRsBoxes(const cv::Mat& fMapMat,const cv::Mat& norfMapMat,const float boxScoreThresh,const float unClipRatio){const float minArea = 3;std::vector<TextBox> rsBoxes;rsBoxes.clear();std::vector<std::vector<cv::Point>> contours;cv::findContours(norfMapMat, contours, cv::RETR_LIST,cv::CHAIN_APPROX_SIMPLE);for (int i = 0; i < contours.size(); ++i) {float minSideLen, perimeter;std::vector<cv::Point> minBox =getMinBoxes(contours[i], minSideLen, perimeter);if (minSideLen < minArea)continue;float score = boxScoreFast(fMapMat, contours[i]);if (score < boxScoreThresh)continue;//---use clipper start---std::vector<cv::Point> clipBox = unClip(minBox, perimeter, unClipRatio);std::vector<cv::Point> clipMinBox =getMinBoxes(clipBox, minSideLen, perimeter);//---use clipper end---if (minSideLen < minArea + 2)continue;for (int j = 0; j < clipMinBox.size(); ++j) {clipMinBox[j].x = (clipMinBox[j].x / 1.0);clipMinBox[j].x =(std::min)((std::max)(clipMinBox[j].x, 0), norfMapMat.cols);clipMinBox[j].y = (clipMinBox[j].y / 1.0);clipMinBox[j].y =(std::min)((std::max)(clipMinBox[j].y, 0), norfMapMat.rows);}rsBoxes.emplace_back(TextBox{ clipMinBox, score });}reverse(rsBoxes.begin(), rsBoxes.end());return rsBoxes;}bool DbNet::detect(cv::Mat& src, std::vector<TextBox>& results_, int _target_size){target_size = _target_size;int width = src.cols;int height = src.rows;int w = width;int h = height;float scale = 1.f;const int resizeMode = 0; // min = 0, max = 1if (resizeMode == 1) {if (w < h) {scale = (float)target_size / w;w = target_size;h = h * scale;}else {scale = (float)target_size / h;h = target_size;w = w * scale;}}else if (resizeMode == 0) {if (w > h) {scale = (float)target_size / w;w = target_size;h = h * scale;}else {scale = (float)target_size / h;w = w * scale;h = target_size;}}ncnn::Extractor extractor = net.create_extractor();ncnn::Mat out;cv::Size in_pad_size;int wpad = (w + 31) / 32 * 32 - w;int hpad = (h + 31) / 32 * 32 - h;ncnn::Mat in_pad_;ncnn::Mat input = ncnn::Mat::from_pixels_resize(src.data, ncnn::Mat::PIXEL_RGB, width, height, w, h);// pad to target_size rectanglencnn::copy_make_border(input, in_pad_, hpad / 2, hpad - hpad / 2, wpad / 2,wpad - wpad / 2, ncnn::BORDER_CONSTANT, 0.f);in_pad_.substract_mean_normalize(meanValues, normValues);in_pad_size = cv::Size(in_pad_.w, in_pad_.h);extractor.input("x", in_pad_);extractor.extract("sigmoid_0.tmp_0", out);//    ncnn::Mat flattened_out = out.reshape(out.w * out.h * out.c);//-----boxThresh-----cv::Mat fMapMat(in_pad_size.height, in_pad_size.width, CV_32FC1, (float*)out.data);cv::Mat norfMapMat;norfMapMat = fMapMat > boxThresh;cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));cv::dilate(norfMapMat, norfMapMat, element, cv::Point(-1, -1), 1);std::vector<TextBox> results =findRsBoxes(fMapMat, norfMapMat, boxScoreThresh,unClipRatio);for (int i = 0; i < results.size(); i++) {for (int j = 0; j < results[i].boxPoint.size(); j++) {float x = float(results[i].boxPoint[j].x - (wpad / 2)) / scale;float y = float(results[i].boxPoint[j].y - (hpad / 2)) / scale;x = std::max(std::min(x, (float)(width - 1)), 0.f);y = std::max(std::min(y, (float)(height - 1)), 0.f);results[i].boxPoint[j].x = (int)x;results[i].boxPoint[j].y = (int)y;}if (abs(results[i].boxPoint[0].x - results[i].boxPoint[1].x) <= 3) {continue;}if (abs(results[i].boxPoint[0].y - results[i].boxPoint[3].y) <= 3) {continue;}results_.push_back(results[i]);}return true;}DbNet::DbNet(){}
}

检测结果:
在这里插入图片描述
在这里插入图片描述

三、文字识别

1. OCR

文字识别是将印刷或手写文本转换为可文本,被广泛应用于各种领域,包括数字化档案管理、自动化数据录入、图像搜索、身份验证、自动车牌识别、票据处理、手写文字识别。

类声明:

#ifndef __OCR_CRNNNET_H__
#define __OCR_CRNNNET_H__#include "base_struct.h"
#include <ncnn/net.h>
#include <opencv2/opencv.hpp>
#include <vector>
#include <ncnn/cpu.h>
#include <fstream>namespace NCNNOCR
{class CrnnNet{public:CrnnNet();~CrnnNet() {};int read_model(std::string param_path = "data/ch_recv4.ncnn.param",std::string bin_path = "data/ch_recv4.ncnn.bin",std::string key_path = "data/dict_chi_sim.txt", bool use_gpu = false);int read_keys(std::string key_path);bool detect(cv::Mat& src, TextLine& result);bool detect(std::vector<cv::Mat>& src, std::vector<TextLine>& results);private:TextLine scoreToTextLine(const std::vector<float>& outputData, int h, int w);private:ncnn::Net net;const int dstHeight = 48;const int dstWidth = 320;const float meanValues[3] = { 127.5, 127.5, 127.5 };const float normValues[3] = { 1.0 / 127.5, 1.0 / 127.5, 1.0 / 127.5 };std::vector<std::string> keys;};
}#endif //__OCR_DBNET_H__

类实现:

#include "crnn_net.h"namespace NCNNOCR
{template<class ForwardIterator>inline static size_t argmax(ForwardIterator first, ForwardIterator last){return std::distance(first, std::max_element(first, last));}int CrnnNet::read_model(std::string param_path, std::string bin_path,std::string key_path, bool use_gpu){ncnn::set_cpu_powersave(2);ncnn::set_omp_num_threads(ncnn::get_big_cpu_count());net.opt = ncnn::Option();#if NCNN_VULKANnet.opt.use_vulkan_compute = use_gpu;
#endifnet.opt.num_threads = ncnn::get_big_cpu_count();int rp = net.load_param(param_path.c_str());int rb = net.load_model(bin_path.c_str());int rk = read_keys(key_path);if (rp == 0 || rb == 0 || rk == 0){return false;}return true;}int CrnnNet::read_keys(std::string key_path){std::ifstream in(key_path.c_str());std::string line;if (in){while (getline(in, line)){// line中不包括每行的换行符keys.push_back(line);}}else{printf("The keys.txt file was not found\n");}keys.insert(keys.begin(), "#");keys.emplace_back(" ");return keys.size();};TextLine CrnnNet::scoreToTextLine(const std::vector<float>& outputData, int h, int w){int keySize = keys.size();std::string strRes;std::vector<float> scores;int lastIndex = -1;int maxIndex;float maxValue;for (int i = 0; i < h; i++){maxIndex = 0;maxValue = -1000.f;maxIndex = int(argmax(outputData.begin() + i * w, outputData.begin() + i * w + w));maxValue = float(*std::max_element(outputData.begin() + i * w, outputData.begin() + i * w + w)); // / partition;if (maxIndex > 0 && maxIndex < keySize && (!(maxIndex == lastIndex))){/* std::cout << maxIndex << std::endl;*/scores.emplace_back(maxValue);//std::cout << keys[maxIndex] << std::endl;strRes.append(keys[maxIndex]);}lastIndex = maxIndex;}return { strRes, scores };}bool CrnnNet::detect(cv::Mat& src, TextLine& result){int resized_w = 0;float ratio = src.cols / float(src.rows);resized_w = ceil(dstHeight * ratio);cv::Size tmp = cv::Size(resized_w, dstHeight);ncnn::Mat input = ncnn::Mat::from_pixels_resize(src.data, ncnn::Mat::PIXEL_BGR2RGB,src.cols, src.rows, tmp.width, tmp.height);input.substract_mean_normalize(meanValues, normValues);ncnn::Extractor extractor = net.create_extractor();extractor.input("in0", input);ncnn::Mat out;extractor.extract("out0", out);float* floatArray = (float*)out.data;std::vector<float> outputData(floatArray, floatArray + out.h * out.w);result = scoreToTextLine(outputData, out.h, out.w);return true;}bool CrnnNet::detect(std::vector<cv::Mat>& src,std::vector<TextLine>& results){int sizeLen = src.size();// results.resize(sizeLen);for (size_t i = 0; i < sizeLen; i++){TextLine textline;if (detect(src[i], textline)){results.emplace_back(textline);}else{return false;}}return true;}CrnnNet::CrnnNet(){}
}

2.在图像画中文

识别后,想要比对识别的结果,可以把文字画到当前图像,但OpenCV没有提供画中文的方法,甩以要自己写一个画中文的方法:

#include "put_text.h"void get_string_size(HDC hDC, const char* str, int* w, int* h)
{SIZE size;GetTextExtentPoint32A(hDC, str, strlen(str), &size);if (w != 0) *w = size.cx;if (h != 0) *h = size.cy;
}void put_text_ch(Mat &dst, const char* str, Point org, Scalar color, int fontSize, const char* fn, bool italic, bool underline)
{CV_Assert(dst.data != 0 && (dst.channels() == 1 || dst.channels() == 3));int x, y, r, b;if (org.x > dst.cols || org.y > dst.rows) return;x = org.x < 0 ? -org.x : 0;y = org.y < 0 ? -org.y : 0;LOGFONTA lf;lf.lfHeight = -fontSize;lf.lfWidth = 0;lf.lfEscapement = 0;lf.lfOrientation = 0;lf.lfWeight = 5;lf.lfItalic = italic;   //斜体lf.lfUnderline = underline; //下划线lf.lfStrikeOut = 0;lf.lfCharSet = DEFAULT_CHARSET;lf.lfOutPrecision = 0;lf.lfClipPrecision = 0;lf.lfQuality = PROOF_QUALITY;lf.lfPitchAndFamily = 0;strcpy_s(lf.lfFaceName, fn);HFONT hf = CreateFontIndirectA(&lf);HDC hDC = CreateCompatibleDC(0);HFONT hOldFont = (HFONT)SelectObject(hDC, hf);int strBaseW = 0, strBaseH = 0;int singleRow = 0;char buf[1 << 12];strcpy_s(buf, str);char *bufT[1 << 12];  // 这个用于分隔字符串后剩余的字符,可能会超出。//处理多行{int nnh = 0;int cw, ch;const char* ln = strtok_s(buf, "\n", bufT);while (ln != 0){get_string_size(hDC, ln, &cw, &ch);strBaseW = max(strBaseW, cw);strBaseH = max(strBaseH, ch);ln = strtok_s(0, "\n", bufT);nnh++;}singleRow = strBaseH;strBaseH *= nnh;}if (org.x + strBaseW < 0 || org.y + strBaseH < 0){SelectObject(hDC, hOldFont);DeleteObject(hf);DeleteObject(hDC);return;}r = org.x + strBaseW > dst.cols ? dst.cols - org.x - 1 : strBaseW - 1;b = org.y + strBaseH > dst.rows ? dst.rows - org.y - 1 : strBaseH - 1;org.x = org.x < 0 ? 0 : org.x;org.y = org.y < 0 ? 0 : org.y;BITMAPINFO bmp = { 0 };BITMAPINFOHEADER& bih = bmp.bmiHeader;int strDrawLineStep = strBaseW * 3 % 4 == 0 ? strBaseW * 3 : (strBaseW * 3 + 4 - ((strBaseW * 3) % 4));bih.biSize = sizeof(BITMAPINFOHEADER);bih.biWidth = strBaseW;bih.biHeight = strBaseH;bih.biPlanes = 1;bih.biBitCount = 24;bih.biCompression = BI_RGB;bih.biSizeImage = strBaseH * strDrawLineStep;bih.biClrUsed = 0;bih.biClrImportant = 0;void* pDibData = 0;HBITMAP hBmp = CreateDIBSection(hDC, &bmp, DIB_RGB_COLORS, &pDibData, 0, 0);CV_Assert(pDibData != 0);HBITMAP hOldBmp = (HBITMAP)SelectObject(hDC, hBmp);//color.val[2], color.val[1], color.val[0]SetTextColor(hDC, RGB(255, 255, 255));SetBkColor(hDC, 0);//SetStretchBltMode(hDC, COLORONCOLOR);strcpy_s(buf, str);const char* ln = strtok_s(buf, "\n", bufT);int outTextY = 0;while (ln != 0){TextOutA(hDC, 0, outTextY, ln, strlen(ln));outTextY += singleRow;ln = strtok_s(0, "\n", bufT);}uchar* dstData = (uchar*)dst.data;int dstStep = dst.step / sizeof(dstData[0]);unsigned char* pImg = (unsigned char*)dst.data + org.x * dst.channels() + org.y * dstStep;unsigned char* pStr = (unsigned char*)pDibData + x * 3;for (int tty = y; tty <= b; ++tty){unsigned char* subImg = pImg + (tty - y) * dstStep;unsigned char* subStr = pStr + (strBaseH - tty - 1) * strDrawLineStep;for (int ttx = x; ttx <= r; ++ttx){for (int n = 0; n < dst.channels(); ++n) {double vtxt = subStr[n] / 255.0;int cvv = vtxt * color.val[n] + (1 - vtxt) * subImg[n];subImg[n] = cvv > 255 ? 255 : (cvv < 0 ? 0 : cvv);}subStr += 3;subImg += dst.channels();}}SelectObject(hDC, hOldBmp);SelectObject(hDC, hOldFont);DeleteObject(hf);DeleteObject(hBmp);DeleteDC(hDC);
}

3.字符转换

识别的字符属于UTF8,在windows下,要转成ASCII才能正常显示不乱码,在C++中,可以使用标准库中的一些函数来处理字符编码的转换,但需要注意UTF-8和ASCII字符编码之间的差异。因为UTF-8是一种更广泛支持字符的编码方式,所以在进行转换时,需要确保要转换的文本仅包含ASCII字符。

#include "EncodeConversion.h"
#include <Windows.h>//utf8 转 Unicode
extern std::wstring Utf8ToUnicode(const std::string& utf8string)
{int widesize = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, NULL, 0);if (widesize == ERROR_NO_UNICODE_TRANSLATION){throw std::exception("Invalid UTF-8 sequence.");}if (widesize == 0){throw std::exception("Error in conversion.");}std::vector<wchar_t> resultstring(widesize);int convresult = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, &resultstring[0], widesize);if (convresult != widesize){throw std::exception("La falla!");}return std::wstring(&resultstring[0]);
}//unicode 转为 ascii
extern std::string WideByteToAcsi(std::wstring& wstrcode)
{int asciisize = ::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, NULL, 0, NULL, NULL);if (asciisize == ERROR_NO_UNICODE_TRANSLATION){throw std::exception("Invalid UTF-8 sequence.");}if (asciisize == 0){throw std::exception("Error in conversion.");}std::vector<char> resultstring(asciisize);int convresult = ::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, &resultstring[0], asciisize, NULL, NULL);if (convresult != asciisize){throw std::exception("La falla!");}return std::string(&resultstring[0]);
}//utf-8 转 ascii
extern std::string UTF8ToASCII(std::string& strUtf8Code)
{std::string strRet("");//先把 utf8 转为 unicodestd::wstring wstr = Utf8ToUnicode(strUtf8Code);//最后把 unicode 转为 asciistrRet = WideByteToAcsi(wstr);return strRet;
}//ascii 转 Unicode
extern std::wstring AcsiToWideByte(std::string& strascii)
{int widesize = MultiByteToWideChar(CP_ACP, 0, (char*)strascii.c_str(), -1, NULL, 0);if (widesize == ERROR_NO_UNICODE_TRANSLATION){throw std::exception("Invalid UTF-8 sequence.");}if (widesize == 0){throw std::exception("Error in conversion.");}std::vector<wchar_t> resultstring(widesize);int convresult = MultiByteToWideChar(CP_ACP, 0, (char*)strascii.c_str(), -1, &resultstring[0], widesize);if (convresult != widesize){throw std::exception("La falla!");}return std::wstring(&resultstring[0]);
}//Unicode 转 Utf8
extern std::string UnicodeToUtf8(const std::wstring& widestring)
{int utf8size = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, NULL, 0, NULL, NULL);if (utf8size == 0){throw std::exception("Error in conversion.");}std::vector<char> resultstring(utf8size);int convresult = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, &resultstring[0], utf8size, NULL, NULL);if (convresult != utf8size){throw std::exception("La falla!");}return std::string(&resultstring[0]);
}//ascii 转 Utf8
extern std::string ASCIIToUTF8(std::string& strAsciiCode)
{std::string strRet("");//先把 ascii 转为 unicodestd::wstring wstr = AcsiToWideByte(strAsciiCode);//最后把 unicode 转为 utf8strRet = UnicodeToUtf8(wstr);return strRet;
}

三、整体测试

#include <iostream>
#include "crnn_net.h"
#include "db_net.h"
#include "tools.h"
#include "put_text.h"
#include "EncodeConversion.h"int main() 
{NCNNOCR::DbNet det_net;NCNNOCR::CrnnNet rec_net;rec_net.read_model();det_net.read_model();cv::Mat img = cv::imread("235.jpg");if (img.empty()){std::cout << "empty" << std::endl;return 0;}cv::Mat drawImg = img.clone();std::vector< NCNNOCR::TextBox> boxResult;std::vector< NCNNOCR::TextLine> recResult;det_net.detect(img, boxResult,2560);recResult.resize(boxResult.size());for (size_t i = 0; i < boxResult.size(); i++) {cv::Mat partImg = NCNNOCR::getRotateCropImage(img, boxResult[i].boxPoint);rec_net.detect(partImg, recResult[i]);cv::polylines(drawImg, boxResult[i].boxPoint, true, cv::Scalar(0,0,255),4);std::string text = UTF8ToASCII(recResult.at(i).text);std::cout << text << std::endl;if (text.empty()){continue;}put_text_ch(drawImg, text.c_str(), boxResult[i].boxPoint[0], cv::Scalar(0, 0, 255), 80);}cv::namedWindow("result", 0);cv::imshow("result", drawImg);cv::waitKey();return 0;
}

在这里插入图片描述

相关文章:

文字识别(OCR)专题——基于NCNN轻量级PaddleOCRv4模型C++推理

前言 PaddleOCR 提供了基于深度学习的文本检测、识别和方向检测等功能。其主要推荐的 PP-OCR 算法在国内外的企业开发者中得到广泛应用。在短短的几年时间里&#xff0c;PP-OCR 的累计 Star 数已经超过了32.2k&#xff0c;常常出现在 GitHub Trending 和 Paperswithcode 的日榜…...

❀My学习Linux命令小记录(14)❀

目录 ❀My学习Linux命令小记录&#xff08;14&#xff09;❀ 56.man指令 57.whatis指令 58.info指令 59.--help指令 60.uname指令 ❀My学习Linux命令小记录&#xff08;14&#xff09;❀ 56.man指令 功能说明&#xff1a;查看Linux中的指令帮助。 &#xff08;ps.man命…...

SqlServer存储过程中使用in

第一步&#xff1a;创建测试存储过程&#xff1a; CREATE PROCEDURE [dbo].[test] deptCode varchar(MAX)AS BEGINSELECT * from DEPT_INFO_A where DEPT_CODE in (deptCode)END 此存储过程只是一个简单的查询 第二步测试&#xff1a; 传入的 deptCode为&#xff1a;101200…...

Selenium+Unittest+HTMLTestRunner框架更改为Selenium+Pytest+Allure(二)

1 代码框架 整体项目结构如图&#xff1a; Common&#xff1a;公共库 Logs&#xff1a; 日志目录 Page&#xff1a; 页面元素 Report&#xff1a;测试报告 TestCase&#xff1a;测试用例 TestData&#xff1a; 测试数据 2 单模块运行 直接上代码&#xff1a; # -*- coding…...

Kotlin Lambda使用

Kotlin Lambda使用 fun main() /*: Unit*/ {// Lambda会慢慢的难度升级// Kotlin Unit Java void// TODO 下面全部都是函数声明&#xff0c; 既然是函数声明&#xff0c;就不能调用// 函数的声明 用lambda去描述函数的声明val method1 : () -> Unitval method2 : (Int, In…...

华容道问题求解第一部分_思路即方案设计

一、前言 华容道是一种传统的益智游戏&#xff0c;通常由一个长方形木板和若干个方块组成。其中包括一个或多个不同颜色的方块&#xff08;也称为车块&#xff09;和其他大小相同的方块&#xff08;也称为障碍块&#xff09;。游戏的目标是将车块从木板的一个端点移动到另一个…...

测试---UI自动化测试介绍

1、什么是自动化测试 概念&#xff1a;由程序代替人工进行系统校验的过程。--------计算机自己执行&#xff0c;好比手机上安装一个软件软件微信&#xff0c;抖音&#xff0c;微博之类的&#xff0c;在应用商城里面&#xff0c;下载对应app后&#xff0c;手机系统程序会自动安…...

DHCP Host Name

文章目录 前言DHCP OptionOption (12) Host Namednsmasq 前言 打开路由器页面&#xff0c;看到下面连接的设备&#xff0c;有的显示设备名称 Tmall-Genie、ESP-C37CE8&#xff0c;而有的直接显示 MAC 地址 D2:B0:XX:XX:XX:XX。 这个名称是哪里来的呢&#xff1f; 这就是我们今…...

uniapp到底用什么ui框架最合适-关于uni-app的ui库、ui框架、ui组件

文章目录 直接看答案关于uni-app的ui库、ui框架、ui组件组件的概念扩展组件的选择uni ui如何使用uni ui 综上&#xff0c;官方对组件的使用建议是&#xff1a;附录&#xff1a;其他全端兼容ui库参考文章&#xff1a; 直接看答案 如果想自己纯手写&#xff0c;直接用内置组件。…...

Flask 最佳实践(二)

Flask是一个轻量级而灵活的Web框架&#xff0c;提供了足够的自由度让开发者根据项目的需求进行定制。然而&#xff0c;为了在大型项目中保持代码的可维护性和可扩展性&#xff0c;建议采用以下一些建议的最佳实践。 在上一篇博客中&#xff0c;讲述了项目结构、蓝图相关的最佳实…...

【MATLAB源码-第93期】基于matlab的白鲸优化算法(BWO)和鲸鱼优化算法(WOA)机器人栅格路径规划对比。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 白鲸优化算法&#xff08;BWO&#xff09; 白鲸优化算法是受到白鲸捕食和迁徙行为启发的一种算法。其主要特点和步骤包括&#xff1a; 1. 搜索食物&#xff08;全局搜索&#xff09;&#xff1a;算法模仿白鲸寻找食物的行为。…...

nodejs微信小程序+python+PHP在线购票系统的设计与实现-计算机毕业设计推荐

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…...

卷积神经网络训练情感分析

文章目录 1 CNN在自然语言的典型应用2 代码解释3 建议 1 CNN在自然语言的典型应用 卷积的作用在于利用文字的局部特征&#xff0c;一个词的前后几个词必然和这个词本身相关&#xff0c;这组成该词所代表的词群词群进而会对段落文字的意思进行影响&#xff0c;决定这个段落到底…...

github新建项目

参考链接&#xff1a;Github上建立新项目超详细方法过程 在这里新建一个repositories 接下来就选择相关的信息&#xff1a; 然后create a new就行了 接下来需要创建文件&#xff1a;&#xff08;同时通过upload上传文件&#xff09; 每次最多上传100个文件&#xff0c;然后保…...

CRC(循环冗余校验)直接计算和查表法

文章目录 CRC概述CRC名词解释宽度 (WIDTH)多项式 &#xff08;POLY&#xff09;初始值 &#xff08;INIT&#xff09;结果异或值 &#xff08;XOROUT&#xff09;输入数据反转&#xff08;REFIN&#xff09;输出数据反转&#xff08;REFOUT&#xff09; CRC手算过程模二加减&am…...

【算法思考记录】力扣2952. 需要添加的硬币的最小数量【C++,思路挖掘,贪心与证明】

原题链接 文章目录 需要添加的硬币的最小数量&#xff1a;贪心算法实现题目概述示例分析 关键思路分析贪心算法的优化选择证明案例推演与算法实现 C 实现结论 需要添加的硬币的最小数量&#xff1a;贪心算法实现 题目概述 在这个困难难度的算法题中&#xff0c;我们要解决的…...

用友NC JiuQiClientReqDispatch反序列化RCE漏洞复现

0x01 产品简介 用友NC是一款企业级ERP软件。作为一种信息化管理工具,用友NC提供了一系列业务管理模块,包括财务会计、采购管理、销售管理、物料管理、生产计划和人力资源管理等,帮助企业实现数字化转型和高效管理。 0x02 漏洞概述 用友 NC JiuQiClientReqDispatch 接口存在…...

Linux:docker镜像的创建(5)

1.基于已有镜像创建 步骤&#xff1a; 1.将原始镜像加入容器并运行 2.在原始镜像中部署各种服务 3.退出容器 4.使用下面命令将容器生成新的镜像 现在我们在这个容器里做了一些配置&#xff0c;我们要把他做成自己镜像 docker commit -m "centos7_123" -a "tarr…...

数据结构与算法-D2D3线性表之顺序表

线性表&#xff1a;包含若干数据元素的一个线性序列&#xff0c;特征如下&#xff1a; 1&#xff09;对非空表&#xff0c;a0是表头&#xff0c;无前驱&#xff1b; 2&#xff09;an-1是表尾&#xff0c;无后继&#xff1b; 3&#xff09;其他元素仅且仅有一个前驱&#xff0c;…...

01_W5500简介

目录 W5500简介&#xff1a; 芯片特点: 全硬件TCPIP协议栈: 引脚分布&#xff1a; W5500简介&#xff1a; W5500是一款高性价比的以太网芯片&#xff0c;其全球独一无二的全硬件TCPIP协议栈专利技术&#xff0c;解决了嵌入式以太网的接入问题&#xff0c;简单易用&#xff…...

异常 Exception 练习题 (未完成)

异常 Exception 练习题 try-catch异常处理1234 异常1&#xff08;没有自己写&#xff09;234 try-catch异常处理 1 class Exception01 {public static int method() {try {String[] names new String[3];//String[]数组if (names[1].equals("tom")) {//NullPointe…...

Linux系统编程:并发与信号总结

并发 并发是指两个或多个同时独立进行的活动。在计算机系统中&#xff0c;并发指的是同一个系统中多个独立活动同时进行&#xff0c;而非依次进行。 并发在计算机系统中的表现&#xff1a; 一个时间段中有几个程序都处于已启动运行到运行完毕之间&#xff0c;且这几个程序都是…...

Jmeter 接口-加密信息发送(一百九十九)

方式1&#xff1a;使用函数助手 比如MD5加密方式&#xff1a; 如图&#xff0c;需要对${user}进行MD5加密 1、打开函数助手&#xff0c;找到MD5&#xff0c;输入需要加密的值 2、将${__MD5(${user},)}放到请求中 3、查看请求&#xff0c;请求成功 方式2&#xff1a;导入jar包…...

微信小程序nodejs+vue+uniapp视力保养眼镜店连锁预约系统

作为一个视力保养连锁预约的网络系统&#xff0c;数据流量是非常大的&#xff0c;所以系统的设计必须满足使用方便&#xff0c;操作灵活的要求。所以在设计视力保养连锁预约系统应达到以下目标&#xff1a; &#xff08;1&#xff09;界面要美观友好&#xff0c;检索要快捷简易…...

掌握Vue侦听器(watch)的应用

文章目录 &#x1f341;watch 的优缺点&#x1f342;Watch 优点&#x1f342;Watch 缺点 &#x1f341;watch 的用法&#x1f342;对象式 watch&#x1f342;函数式 watch &#x1f341;代码示例&#x1f342;监听基本数据类型&#x1f342;监听复杂数据类型&#xff08;Object…...

SAP-PP:PP顾问管理系统的相关建议

本博客将探讨生产计划领域的控制要点。这将有助于减少仓库库存不准确情况&#xff0c;因为库存不准确会导致实物库存、发货、成本核算和计划方面出现许多效率低下的问题。 在物料主数据关键字段中&#xff0c;必须配置计划交货时间、GR处理时间、内部生产时间、计划交货时间&a…...

Unity资源路径与读取

Unity资源路径有&#xff1a; 1、StreamingAssets&#xff1a;只读&#xff0c;一般用于存放应用程序运行时需要加载的资源文件&#xff0c;可以通过Application.streamingAssetsPath来获取。 2、PersistentDataPath&#xff1a;可读写&#xff0c;一般用于存放应用程序运行时…...

“大+小模型”赋能油气行业高质量发展

近日&#xff0c;中国石油石化科技创新大会暨新技术成果展在北京盛大举行&#xff0c;九章云极DataCanvas公司携油气行业一站式AI综合解决方案重磅亮相&#xff0c;充分展示了公司助推油气行业实现AI规模化应用深厚的AI技术实力和领先的AI应用水准&#xff0c;赢得了行业专家和…...

【win32_004】字符串处理函数

StringCbPrintf 函数 (strsafe.h)&#xff1a;格式化字符串 STRSAFEAPI StringCbPrintf([out] STRSAFE_LPSTR pszDest,//目的缓冲区 LPSTR指针或者数组[in] size_t cbDest,//目的缓冲区大小[in] STRSAFE_LPCSTR pszFormat,//格式 例如&#xff1a; TEXT("%d&…...

如果不小心修改了按钮的名字并且忘记了原名字

出现上述情况&#xff0c;可以右边点击转到代码&#xff0c;注释掉问题行&#xff0c;此页的设计界面就恢复了。...

国外网站发展建设/优化师

/***************************************************************************** #define | enum(enumerator)* 声明&#xff1a;* 今天突然在Linux内核看到枚举和宏&#xff0c;感觉是一样的功能&#xff0c;于是找了一下他们* 之间差异。** …...

国外地推如何开展/推广关键词优化

2004.9.27 Astrophel2005.3.5 Stella v1.12005.5.25 Stella v2.0 ... ...好久没有写技术文章了&#xff0c;更是好久没有敢把自己的文章推到首页.初学的迷茫&#xff0c;入门的欢喜&#xff0c;路上的疲惫&#xff0c;对未来的向往&#xff0c;这些心情&#xff0c;都浓缩在这…...

常州专业网站建设费用/seo优化方式包括

一、前言 该技术博客是关于我在B站自学计算机组成原理知识的笔记&#xff0c;该技术博客是根据 2019 王道考研 计算机组成原理 课程总结而成。该技术博客关于记录视频课程内容&#xff0c;如果对你有帮助&#xff0c;方便大家之后的学习&#xff0c;该系列博客会持续更新至全套…...

唐山seo设计网站/产品seo标题是什么

1.幅频特性、相频特性等属于是&#xff08;&#xff09;。 A.电路性能的测量 B.特性曲线的测量 C.电能量的测量 D.电信号特性的测量 错误 正确答案&#xff1a;左边查询 学生答案&#xff1a;A 2.信号特性的测量指的是对频率、周期、时间、相位、调制系数、失真度等参量的测量。…...

店面建设网站的必要性/推广引流渠道平台

package import 总结 1、如果想将一个类放入包中&#xff0c;在这个类源文件第一句话写package 2、必须保证该类的class文件位于正确目录下 1&#xff09;该类的源码可能会产生影响 △删除或转移到另外的目录 3、另外的类想访问的话&#xff1a; 1&#xff09;写全名 2&#xf…...

洛阳网站建设价格低/宁波建站模板系统

两种科学的碰撞&#xff0c;经常会带起一大片脑洞。而当自然科学与某种人文科学相遇&#xff0c;脑洞的连锁反应格外强烈。比如说&#xff0c;天文学和考古。按理说这俩东西应该谁也不挨着谁。天文学家总是抬头仰望星空&#xff0c;而考古学家始终凝视着大地——这属于颈椎病高…...