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

OpenCv + Qt5.12.2 文字识别

OpenCv + Qt5.12.2 文字检测与文本识别

前言

​ 好久没有进行一些相关的更新的了,去年一共更新了四篇,最近一直在做音视频相关的直播服务,又是重新学习积攒经验的一个过程。去年疫情也比较严重,等到解封,又一直很忙,最近又算有了一些时间,所以想着可以做一些更新了,又拿起了 OpenCV,做一些相关更新了。其实代码相关的工作,在上一篇 OpenCV-摄像头相关的完成之后已经做完了,只是一直没有写相关博客,这次先给做完。

简介

​ 文本检测与文本识别都是基于原生OpenCV的扩张模块来实现的,基本流程是按照 OpenCV 文字检测与识别模块来实现的,只不过是我做了一些关于Ot与OpenCV的集成工作做成了项目。大致工作流程为:图片选择功能选择图片保存

​ 相关的文档我在内外网搜索后发现大致几篇一样的文档,来源不可考,大致都贴出来:

OpenCV 文字檢測與識別模塊 - 台部落 / OpenCV 文字检测与识别模块 - CSDN

OPENCV 文字检测与识别模块 - 灰信网

文档基本相同,CSDN与灰信网完全相同,台部落是资源路径不同,台部落是原始模型资源路径,CSDN与灰信网的路径相同是一个网盘。但是台部落与CSDN博主是同一个名字。那就是灰信网。

资源路径

编译相关的已经在前两篇文档已经描述过了,路径如下: OpenCv4.4.0+Qt5.12.2+OpenCv-Contrib-4.4.0。

那就描述一下本期需要用到的一些资源:

文字检测

资源文件描述如下: textDetector.hpp 文档中 37-39行。详细内容如下:

/** @brief TextDetectorCNN class provides the functionallity of text bounding box detection.This class is representing to find bounding boxes of text words given an input image.This class uses OpenCV dnn module to load pre-trained model described in @cite LiaoSBWL17.The original repository with the modified SSD Caffe version: https://github.com/MhLiao/TextBoxes.Model can be downloaded from [DropBox](https://www.dropbox.com/s/g8pjzv2de9gty8g/TextBoxes_icdar13.caffemodel?dl=0).Modified .prototxt file with the model description can be found in `opencv_contrib/modules/text/samples/textbox.prototxt`.*/

textbox.prototxt - 本地文档模块目录中,按照路径查找即可。

TextBoxes_icdar13.caffemodel - TextBoxes_icdar13.caffemodel

文字识别

所需要的资源如下:见相关网页描述: OpenCV.org, text_recognition_cnn.cpp,不过也只是贴出了相关路径而已,原始博客中提到的关于

    cout << "   Demo of text recognition CNN for text detection." << endl<< "   Max Jaderberg et al.: Reading Text in the Wild with Convolutional Neural Networks, IJCV 2015"<<endl<<endl<< "   Usage: " << progFname << " <output_file> <input_image>" << endl<< "   Caffe Model files (textbox.prototxt, TextBoxes_icdar13.caffemodel)"<<endl<< "     must be in the current directory. See the documentation of text::TextDetectorCNN class to get download links." << endl<< "   Obtaining text recognition Caffe Model files in linux shell:" << endl<< "   wget http://nicolaou.homouniversalis.org/assets/vgg_text/dictnet_vgg.caffemodel" << endl<< "   wget http://nicolaou.homouniversalis.org/assets/vgg_text/dictnet_vgg_deploy.prototxt" << endl<< "   wget http://nicolaou.homouniversalis.org/assets/vgg_text/dictnet_vgg_labels.txt" <<endl << endl;

相关路径已经失效。

vgg_text,是一些快照文件,只有两个比较小的文件资源,模型module已经是没有的了。最后还是使用CSDN博主的资源,利用百度网盘下载了,折磨人。

其他涉及到资源文件,基本都在模块的文件路径下:

trained_classifierNM1.xml
trained_classifierNM2.xml
OCRHMM_transitions_table.xml
OCRHMM_knn_model_data.xml.gz
trained_classifier_erGrouping.xml

路径如下:

opencv_contrib-4.4.0\modules\text\samples

其他的一些图片资源也可以在当前目录下找到。

代码

头文件:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <iostream>
#include <fstream>
#include <vector>
#include <opencv2/opencv.hpp>
#include <opencv2/text.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/dnn.hpp>
#include <opencv2/features2d.hpp>class ParallelExtracCSER: public cv::ParallelLoopBody
{
private:std::vector<cv::Mat> &channels;std::vector<std::vector<cv::text::ERStat>> &regions;std::vector<cv::Ptr<cv::text::ERFilter>> erFiter_1;std::vector<cv::Ptr<cv::text::ERFilter>> erFiter_2;
public:ParallelExtracCSER(std::vector<cv::Mat> &_channels, std::vector<std::vector<cv::text::ERStat>> &_regions,std::vector<cv::Ptr<cv::text::ERFilter>> _erFiter_1, std::vector<cv::Ptr<cv::text::ERFilter>> _erFiter_2): channels(_channels), regions(_regions), erFiter_1(_erFiter_1), erFiter_2(_erFiter_2){}virtual void operator()( const cv::Range &r) const CV_OVERRIDE{for(int c = r.start; c < r.end; c++){erFiter_1[c]->run(channels[c], regions[c]);erFiter_2[c]->run(channels[c], regions[c]);}}ParallelExtracCSER & operator=(const ParallelExtracCSER &a);
};template  <class T>
class ParallelOCR: public cv::ParallelLoopBody
{
private:std::vector<cv::Mat> &detections;std::vector<std::string> &outputs;std::vector<std::vector<cv::Rect> > &boxes;std::vector<std::vector<std::string> > &words;std::vector<std::vector<float> > &confidences;std::vector<cv::Ptr<T> > &ocrs;
public:ParallelOCR(std::vector<cv::Mat> &_detections, std::vector<std::string> &_outputs, std::vector< std::vector<cv::Rect> > &_boxes,std::vector< std::vector<std::string> > &_words, std::vector< std::vector<float> > &_confidences,std::vector< cv::Ptr<T> > &_ocrs):detections(_detections),outputs(_outputs),boxes(_boxes),words(_words),confidences(_confidences),ocrs(_ocrs){}virtual void operator()(const cv::Range &r) const CV_OVERRIDE{for(int c=r.start; c < r.end; c++){ocrs[c%ocrs.size()]->run(detections[c], outputs[c], &boxes[c], &words[c], &confidences[c], cv::text::OCR_LEVEL_WORD);}}ParallelOCR & operator=(const ParallelOCR &a);
};namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = nullptr);~MainWindow();private:Ui::MainWindow *ui;void WindowInit();std::string sourcePath;void showImage(cv::Mat &image);bool fileExists(const std::string &filename);void textboxDraw(cv::Mat src, std::vector<cv::Rect> &groups, std::vector<float> &probs, std::vector<int> &indexes);bool isRepetitive(const std::string &s);void erDraw(std::vector<cv::Mat> &channels, std::vector<std::vector<cv::text::ERStat>> &regions, std::vector<cv::Vec2i> group, cv::Mat segmentation);public slots:void slot_importImage();void slot_saveImage();void slot_textDetector();void slot_textRecognizer();
};#endif // MAINWINDOW_H

MainWindow类是主要的Ctrl模块,其他两个类 ParallelExtracCSERParallelOCR属于业务类了,主要功能模块实现相关的。

函数实现

槽函数

主要对应四个主要功能,图片导入,图片保存,文本检测,文本识别

1. slot_importImage()
void MainWindow::slot_importImage()
{QString imagePath = QFileDialog::getOpenFileName(this,"选择图片","./","*png *jpg *jpeg");QImage image;if(image.load(imagePath))qDebug() << "导入图片成功" << imagePath;sourcePath = QDir::toNativeSeparators(imagePath).toStdString();qDebug() << "图片路径:" << QDir::toNativeSeparators(imagePath);int imageWidth = image.width();int imageHeight = image.height();if(imageWidth > 640){imageHeight = (640*10 / imageWidth) * imageHeight /10;imageWidth = 640;}if(imageHeight > 480){imageWidth = (480*10 / imageHeight) * imageWidth /10;imageHeight = 480;}image = image.scaled(imageWidth, imageHeight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);this->resize(imageWidth*2+2,imageHeight);ui->label_source->setPixmap(QPixmap::fromImage(image));
}

2.slot_saveImage()

void MainWindow::slot_saveImage()
{if(currentActive.isEmpty() || sourcePath.empty()){qDebug() << "currentActive is " << currentActive.isEmpty() << " sourcePath: " << sourcePath.empty();return;}QString source_path_name = QString::fromStdString(sourcePath);size_t pos = sourcePath.find('.');if(pos == std::string::npos){qDebug() << QString::fromStdString(sourcePath) << " iamget format is error";return;}QStringList sourcePaths = source_path_name.split('.');QString saveName = sourcePaths.at(0) + "_" + currentActive + "." + sourcePaths.at(1);if(ui->label_result->pixmap()->save(saveName, sourcePaths.at(1).toStdString().c_str())){qDebug() << saveName << " save success.";}else{qDebug() << saveName << " save fail.";}
}

3.slot_textDetector()

void MainWindow::slot_textDetector()
{const std::string modelArch = "textbox.prototxt" ;const std::string moddelWeights = "TextBoxes_icdar13.caffemodel";if(!fileExists(modelArch) || !fileExists(moddelWeights)){qDebug() << "Model files not found in the current directory. Aborting!";return;}if(sourcePath.empty()){qDebug() << "图片路径无效,请检查图片是否存在!";return;}cv::Mat image = cv::imread(sourcePath, cv::IMREAD_COLOR);if(image.empty()){qDebug() << "image is empty" << sourcePath.c_str();return;}qDebug() << "Starting Text Box Demo";cv::Ptr<cv::text::TextDetectorCNN> textSpotter = cv::text::TextDetectorCNN::create(modelArch, moddelWeights);std::vector<cv::Rect> bbox;std::vector<float> outProbabillities;textSpotter->detect(image, bbox, outProbabillities);std::vector<int> indexes;cv::dnn::NMSBoxes(bbox, outProbabillities, 0.4f, 0.5f, indexes);cv::Mat imageCopy = image.clone();
//    float threshold = 0.5;
//    for(int i = 0; i < bbox.size(); i++)
//    {
//        if(outProbabillities[i] > threshold)
//        {
//            cv::Rect rect = bbox[i];
//            cv::rectangle(imageCopy,rect,cv::Scalar(255,0,0),2);
//        }
//    }textboxDraw(imageCopy, bbox, outProbabillities, indexes);showImage(imageCopy);imageCopy = image.clone();cv::Ptr<cv::text::OCRHolisticWordRecognizer> wordSpotter =cv::text::OCRHolisticWordRecognizer::create("dictnet_vgg_deploy.prototxt", "dictnet_vgg.caffemodel", "dictnet_vgg_labels.txt");for(size_t i = 0; i < indexes.size(); i++){cv::Mat wordImg;cv::cvtColor(image(bbox[indexes[i]]),wordImg, cv::COLOR_BGR2GRAY);std::string word;std::vector<float> confs;wordSpotter->run(wordImg, word, nullptr, nullptr, &confs);cv::Rect currrentBox = bbox[indexes[i]];rectangle(imageCopy, currrentBox, cv::Scalar( 0, 255, 255 ), 2, cv::LINE_AA);int baseLine = 0;cv::Size labelSize = cv::getTextSize(word, cv::FONT_HERSHEY_PLAIN, 1, 1, &baseLine);int yLeftBottom = std::max(currrentBox.y, labelSize.height);rectangle(imageCopy, cv::Point(currrentBox.x, yLeftBottom - labelSize.height),cv::Point(currrentBox.x +labelSize.width, yLeftBottom + baseLine), cv::Scalar( 255, 255, 255 ), cv::FILLED);putText(imageCopy, word, cv::Point(currrentBox.x , yLeftBottom), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar( 0,0,0 ), 1, cv::LINE_AA);}showImage(imageCopy);
}

4.slot_textRecognizer()


void MainWindow::slot_textRecognizer()
{if(sourcePath.empty()){qDebug() << "图片路径无效,请检查图片是否存在!";return;}cv::Mat image = cv::imread(sourcePath, cv::IMREAD_COLOR);if(image.empty()){qDebug() << "image is empty" << sourcePath.c_str();return;}bool downsize = false;int RegionType = 1;int GroupingAlgorithm = 0;int Recongnition = 0;cv::String regionTypeString[2] = {"ERStats","MSER"};cv::String GroupingAlgorithmsStr[2] = {"exhaustive_search", "multioriented"};cv::String recognitionsStr[2] = {"Tesseract", "NM_chain_features + KNN"};std::vector<cv::Mat> channels;std::vector<std::vector<cv::text::ERStat>> regions(2);cv::Mat gray,outImage;// Create ERFilter objects with the 1st and 2nd stage default classifiers// since er algorithm is not reentrant we need one filter for channelstd::vector< cv::Ptr<cv::text::ERFilter> > erFilters1;std::vector< cv::Ptr<cv::text::ERFilter> > erFilters2;if(!fileExists("trained_classifierNM1.xml") || !fileExists("trained_classifierNM2.xml")|| !fileExists("OCRHMM_transitions_table.xml") || !fileExists("OCRHMM_knn_model_data.xml.gz") || !fileExists("trained_classifier_erGrouping.xml")){qDebug() << " trained_classifierNM1.xml file not found!";return;}for(int i = 0; i<2; i++ ){cv::Ptr<cv::text::ERFilter> erFilter1 = createERFilterNM1(cv::text::loadClassifierNM1("trained_classifierNM1.xml"), 8, 0.00015f, 0.13f, 0.2f, true, 0.1f);cv::Ptr<cv::text::ERFilter> erFilter2 = createERFilterNM2(cv::text::loadClassifierNM2("trained_classifierNM2.xml"), 0.5);erFilters1.push_back(erFilter1);erFilters2.push_back(erFilter2);}int numOcrs = 10;std::vector<cv::Ptr<cv::text::OCRTesseract>> ocrs;for(int o = 0; o < numOcrs; o++){ocrs.push_back(cv::text::OCRTesseract::create());}cv::Mat transitionP;std::string filename = "OCRHMM_transitions_table.xml";cv::FileStorage fs(filename, cv::FileStorage::READ);fs["transition_probabilities"] >> transitionP;fs.release();cv::Mat emissionP = cv::Mat::eye(62, 62, CV_64FC1);std::string voc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";std::vector< cv::Ptr<cv::text::OCRHMMDecoder>> decoders;for(int o = 0; o <numOcrs; o++){decoders.push_back(cv::text::OCRHMMDecoder::create(cv::text::loadOCRHMMClassifierNM("OCRHMM_knn_model_data.xml.gz"),voc, transitionP, emissionP));}double tAll = (double)cv::getTickCount();if(downsize)cv::resize(image,image,cv::Size(image.size().width,image.size().height),0,0,cv::INTER_LINEAR_EXACT);cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);channels.clear();channels.push_back(gray);channels.push_back(255 - gray);regions[0].clear();regions[1].clear();switch (RegionType) {case 0:cv::parallel_for_(cv::Range(0, (int)channels.size()), ParallelExtracCSER(channels, regions, erFilters1, erFilters2));break;case 1:{std::vector<std::vector<cv::Point>> contours;std::vector<cv::Rect> bboxes;cv::Ptr<cv::MSER> mesr = cv::MSER::create(21, (int)(0.00002*gray.cols*gray.rows), (int)(0.05*gray.cols * gray.rows), 1, 0.7);mesr->detectRegions(gray, contours, bboxes);if(contours.size() > 0)MSERsToERStats(gray, contours, regions);}break;}std::vector< std::vector<cv::Vec2i>> nmRegionGroups;std::vector<cv::Rect> nmBoxes;switch (GroupingAlgorithm) {case 0:cv::text::erGrouping(image, channels, regions, nmRegionGroups, nmBoxes, cv::text::ERGROUPING_ORIENTATION_HORIZ);break;case 1:cv::text::erGrouping(image, channels, regions, nmRegionGroups, nmBoxes, cv::text::ERGROUPING_ORIENTATION_ANY, "trained_classifier_erGrouping.xml", 0.5);break;}/*Text Recognition (OCR)*/int bottom_bar_height = outImage.rows/7 ;cv::copyMakeBorder(image, outImage, 0, bottom_bar_height, 0, 0, cv::BORDER_CONSTANT, cv::Scalar(150, 150, 150));float scale_font = (float)(bottom_bar_height /85.0);std::vector<std::string> words_detection;float min_confidence1 = 0.f, min_confidence2 = 0.f;if (Recongnition == 0){min_confidence1 = 51.f;min_confidence2 = 60.f;}std::vector<cv::Mat> detections;for (int i=0; i<(int)nmBoxes.size(); i++){rectangle(outImage, nmBoxes[i].tl(), nmBoxes[i].br(), cv::Scalar(255,255,0),3);cv::Mat group_img = cv::Mat::zeros(image.rows+2, image.cols+2, CV_8UC1);erDraw(channels, regions, nmRegionGroups[i], group_img);group_img(nmBoxes[i]).copyTo(group_img);copyMakeBorder(group_img,group_img,15,15,15,15,cv::BORDER_CONSTANT,cv::Scalar(0));detections.push_back(group_img);}std::vector<std::string> outputs((int)detections.size());std::vector< std::vector<cv::Rect> > boxes((int)detections.size());std::vector< std::vector<std::string> > words((int)detections.size());std::vector< std::vector<float> > confidences((int)detections.size());// parallel process detections in batches of ocrs.size() (== num_ocrs)for (int i=0; i<(int)detections.size(); i=i+(int)numOcrs){cv::Range r;if (i+(int)numOcrs <= (int)detections.size())r = cv::Range(i,i+(int)numOcrs);elser = cv::Range(i,(int)detections.size());switch(Recongnition){case 0: // TesseractqDebug() << "+++++";cv::parallel_for_(r, ParallelOCR<cv::text::OCRTesseract>(detections, outputs, boxes, words, confidences, ocrs));qDebug() << "---";break;case 1: // NM_chain_features + KNNcv::parallel_for_(r, ParallelOCR<cv::text::OCRHMMDecoder>(detections, outputs, boxes, words, confidences, decoders));break;}}for(auto &it : outputs){qDebug() << QString::fromStdString(it);}for (int i=0; i<(int)detections.size(); i++){outputs[i].erase(remove(outputs[i].begin(), outputs[i].end(), '\n'), outputs[i].end());//cout << "OCR output = \"" << outputs[i] << "\" length = " << outputs[i].size() << endl;if (outputs[i].size() < 3)continue;for (int j=0; j<(int)boxes[i].size(); j++){boxes[i][j].x += nmBoxes[i].x-15;boxes[i][j].y += nmBoxes[i].y-15;//cout << "  word = " << words[j] << "\t confidence = " << confidences[j] << endl;if ((words[i][j].size() < 2) || (confidences[i][j] < min_confidence1) ||((words[i][j].size()==2) && (words[i][j][0] == words[i][j][1])) ||((words[i][j].size()< 4) && (confidences[i][j] < min_confidence2)) ||isRepetitive(words[i][j]))continue;words_detection.push_back(words[i][j]);rectangle(outImage, boxes[i][j].tl(), boxes[i][j].br(), cv::Scalar(255,0,255),3);cv::Size word_size = getTextSize(words[i][j], cv::FONT_HERSHEY_SIMPLEX, (double)scale_font, (int)(3*scale_font), nullptr);cv::rectangle(outImage, boxes[i][j].tl()-cv::Point(3,word_size.height+3), boxes[i][j].tl()+cv::Point(word_size.width,0), cv::Scalar(255,0,255),-1);cv::putText(outImage, words[i][j], boxes[i][j].tl()-cv::Point(1,1), cv::FONT_HERSHEY_SIMPLEX, scale_font, cv::Scalar(255,255,255),(int)(3*scale_font));}}tAll = ((double)cv::getTickCount() - tAll)*1000/cv::getTickFrequency();int text_thickness = 1+(outImage.rows/500);std::string fps_info = cv::format("%2.1f Fps. %dx%d", (float)(1000 / tAll), image.cols, image.rows);cv::putText(outImage, fps_info, cv::Point( 10,outImage.rows-5 ), cv::FONT_HERSHEY_DUPLEX, scale_font, cv::Scalar(255,0,0), text_thickness);cv::putText(outImage, regionTypeString[RegionType], cv::Point((int)(outImage.cols*0.5), outImage.rows - (int)(bottom_bar_height/ 1.5)), cv::FONT_HERSHEY_DUPLEX, scale_font, cv::Scalar(255,0,0), text_thickness);cv::putText(outImage, GroupingAlgorithmsStr[GroupingAlgorithm], cv::Point((int)(outImage.cols*0.5),outImage.rows-((int)(bottom_bar_height /3)+4) ), cv::FONT_HERSHEY_DUPLEX, scale_font, cv::Scalar(255,0,0), text_thickness);cv::putText(outImage, regionTypeString[Recongnition], cv::Point((int)(outImage.cols*0.5),outImage.rows-5 ), cv::FONT_HERSHEY_DUPLEX, scale_font, cv::Scalar(255,0,0), text_thickness);showImage(outImage);
}

Ctrl函数

void MainWindow::WindowInit()
{//设置菜单QMenu* file = ui->menuBar->addMenu(QString("文件"));QAction* importImage = file->addAction(QString("选择图片"));QAction* saveImage = file->addAction(QString("保存"));QMenu* funtion = ui->menuBar->addMenu(QString("功能"));QAction* textDetector = funtion->addAction(QString("文字检测"));QAction* textRecognizer = funtion->addAction(QString("文字识别"));//绑定信号与槽函数connect(importImage,&QAction::triggered,this,&MainWindow::slot_importImage);connect(saveImage,&QAction::triggered,this,&MainWindow::slot_saveImage);connect(textDetector,&QAction::triggered,this,&MainWindow::slot_textDetector);connect(textRecognizer,&QAction::triggered,this,&MainWindow::slot_textRecognizer);
}

Qt图片显示函数

做了一个图片显示,附带缩放显示

void MainWindow::showImage(cv::Mat &image)
{cv::Mat outImage;cv::cvtColor(image, outImage, cv::COLOR_BGR2RGB);QImage qImage = QImage((const unsigned char*)(outImage.data),outImage.cols,outImage.rows,outImage.step,QImage::Format_RGB888);int imageWidth = qImage.width();int imageHeight = qImage.height();if(imageWidth > 640){imageHeight = (640*10 / imageWidth) * imageHeight /10;imageWidth = 640;}if(imageHeight > 480){imageWidth = (480*10 / imageHeight) * imageWidth /10;imageHeight = 480;}qImage = qImage.scaled(imageWidth, imageHeight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);ui->label_result->setPixmap(QPixmap::fromImage(qImage));
}

文字绘制

void MainWindow::textboxDraw(cv::Mat src, std::vector<cv::Rect>& groups, std::vector<float>& probs, std::vector<int>& indexes)
{for (size_t i = 0; i < indexes.size(); i++){if (src.type() == CV_8UC3){cv::Rect currrentBox = groups[indexes[i]];cv::rectangle(src, currrentBox, cv::Scalar( 0, 255, 255 ), 2, cv::LINE_AA);cv::String cvlabel = cv::format("%.2f", probs[indexes[i]]);qDebug() << "text box: " << currrentBox.size().width << " " <<currrentBox.size().height << " confidence: " << probs[indexes[i]] << "\n";int baseLine = 0;cv::Size labelSize = getTextSize(cvlabel, cv::FONT_HERSHEY_PLAIN, 1, 1, &baseLine);int yLeftBottom = std::max(currrentBox.y, labelSize.height);cv::rectangle(src, cv::Point(currrentBox.x, yLeftBottom - labelSize.height),cv::Point(currrentBox.x + labelSize.width, yLeftBottom + baseLine), cv::Scalar( 255, 255, 255 ), cv::FILLED);cv::putText(src, cvlabel, cv::Point(currrentBox.x, yLeftBottom), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar( 0,0,0 ), 1, cv::LINE_AA);}elsecv::rectangle(src, groups[i], cv::Scalar( 255 ), 3, 8 );}
}

## 源码

基本流程如上,相关的函数解释与释义都已经附上,更详细的说明解释,见上述博客内容,就不再做一边赘述了。

源码

相关文章:

OpenCv + Qt5.12.2 文字识别

OpenCv Qt5.12.2 文字检测与文本识别 前言 ​ 好久没有进行一些相关的更新的了&#xff0c;去年一共更新了四篇&#xff0c;最近一直在做音视频相关的直播服务&#xff0c;又是重新学习积攒经验的一个过程。去年疫情也比较严重&#xff0c;等到解封&#xff0c;又一直很忙&a…...

网络作业1【计算机网络】

网络作业1【计算机网络】前言推荐网络作业1一. 单选题&#xff08;共7题&#xff0c;58.1分&#xff09;二. 多选题&#xff08;共1题&#xff0c;8.3分&#xff09;三. 判断题&#xff08;共4题&#xff0c;33.6分&#xff09;最后前言 2023-3-13 20:11:42 以下内容源自《计…...

常见背包问题

一.前言若你想学习或正在学习动态规划&#xff0c;背包问题一定是你需要了解的一种题型&#xff0c;并且大多数人最初都是从背包问题入坑进而打开动态规划这一大门。背包问题分为多种&#xff0c;你可以先掌握最常见的主要是三类&#xff1a;01背包、完全背包、多重背包二.分析…...

【python】python编译器以及安装

✅作者简介&#xff1a;一名在读大二学生&#xff0c;希望大家多多支持 &#x1f525;系列专栏&#xff1a;python &#x1f4ac;个人主页&#xff1a;小园园子的CSDN博客 python编译器以及安装一、编译器与解释器详细内容Python解释器种类Python的运行机制二、python环境搭建p…...

Effective C++快速复习

Effective C快速复习 习惯 C 01 视 C 为一个语言联邦&#xff1a;C、Object-Oriented C、Template C、STL 02 尽量以 const, enum, inline 替换 #define&#xff1a;其实是尽量以编译器替换预处理器比较好&#xff0c;因为 #define 只是简单的字符串匹配替换&#xff0c;编译…...

【华为OD机试真题JAVA】绘图机器的绘图问题

标题:绘图机器的绘图问题| 时间限制:1秒 | 内存限制:262144K | 语言限制:不限 绘图机器的绘图笔初始位置在原点(0,0) 机器启动后按照以下规则来进行绘制直线 1. 尝试沿着横线坐标正向绘制直线 直到给定的终点E 2. 期间可以通过指令在纵坐标轴方向进行偏移 off…...

GPT-4最震撼我的一点

昨天我看了一遍OpenAI发的视频和论文&#xff0c;最震撼我的并不是根据手绘草图生成HTML页面代码&#xff0c;因为草图太简单&#xff0c;对于复杂的有交互的界面&#xff0c;还不知道它的能力究竟如何&#xff0c;能不能生成准确的、清晰的代码&#xff0c;我再实验一下再给大…...

LeetCode-复制带随机指针的链表

题目描述&#xff1a; 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的…...

如何在Unity中实现AStar寻路算法及地图编辑器

文章目录AStar算法简介实现Node节点节点间的估价算法核心邻节点的搜索方式地图编辑器简介实现绘制地图网格障碍/可行走区域地图数据存储AStar算法 简介 Unity中提供了NavMesh导航寻路的AI功能&#xff0c;如果项目不涉及服务端它应该能满足大部分需求&#xff0c;但如果涉及服…...

线性代数之矩阵

一、思维导图二、矩阵及其运算1、矩阵的定义注&#xff1a;零矩阵&#xff1a;元素均为0 的矩阵&#xff0c;通常记作0m*n称为矩阵的类型。满足阶梯形矩阵 行简化的阶梯形矩阵即满足如下条件的矩阵&#xff1a; (1)阶梯形; (2)非零首元所在列其余元素均为0 &#xff1b; (3) 非…...

【个人首测】百度文心一言 VS ChatGPT GPT-4

昨天我写了一篇文章GPT-4牛是牛&#xff0c;但这几天先别急,文中我测试了用GPT-4回答ChatGPT 3.5 和 Notion AI的问题&#xff0c;大家期待的图片输入也没有出现。 昨天下午百度发布了文心一言&#xff0c;对标ChatGPT&#xff0c;录屏无实机演示让百度股价暴跌。但是晚上百度就…...

基于STM32的ADC采样及各式滤波实现(HAL库,含VOFA+教程)

前言&#xff1a;本文为手把手教学ADC采样及各式滤波算法的教程&#xff0c;本教程的MCU采用STM32F103ZET6。以HAL库的ADC采样函数为基础进行教学&#xff0c;通过各式常见滤波的实验结果进行分析对比&#xff0c;搭配VOFA工具直观的展示滤波效果。ADC与滤波算法都是嵌入式较为…...

Redis高级篇

文章目录面试题库redis有哪些用法&#xff1f;redis单线程时代性能依然很快的原因&#xff1f;主线程和IO线程怎么协作完成请求处理的BigKey&#xff08;重要&#xff09;什么算是BigKey&#xff1f;怎么发现BigKey&#xff1f;怎么删除bigkey&#xff1f;bigkey生产调优缓存双…...

sess.close()这句话一般是干什么的,在代码中可以不加么?

sess.close()这句话是用于关闭TensorFlow会话对象的方法。 关闭会话对象可以释放资源&#xff0c;避免内存泄漏&#xff0c;以及清除图中的变量和操作。 在代码中是否可以不加这句话&#xff0c;取决于你是如何创建和使用会话对象的。如果你使用了with语句来创建和管理会话对…...

网络舆情监测处置平台,TOOM舆情如何做好舆情风险点及防控措施?

网络舆情监测处置平台是一个综合性的系统&#xff0c;旨在帮助企业、政府或其他组织有效地管理和处置网络舆情。从多个角度来分析该平台&#xff0c;我们可以考虑以下几个方面&#xff1a; 1&#xff0c;技术实现 网络舆情监测处置平台的技术实现是其核心&#xff0c;它通常采…...

百度文心一言对标 ChatGPT,你怎么看?

文心一言 VS ChatGPT接受不完美 期待进步里程碑意义文心一言初体验✔ 文学创作✔ 商业文案创作✔ 数理逻辑推算✔ 中文理解✔ 多模态生成写在最后何为文心&#xff1f;“文”就是我们中华语言文字中的文&#xff0c;“心”是希望该语言模型可以用心的去理解语言&#xff0c;用心…...

阿里笔试2023-3-15

太菜了&#xff0c;记录一下笔试题目&#xff0c;代码有更好解法欢迎分享。 1、满二叉子树的数量。 给定一颗二叉树&#xff0c;试求这课二叉树有多少个节点满足以该节点为根的子树是满二叉树&#xff1f;满二叉树指每一层都达到节点最大值。 第一行输入n表示节点数量&#xff…...

STM32:TIM定时器输出比较(OC)

一、输出比较简介 1、输出比较 OC&#xff08;Output Comapre&#xff09;输出比较输出比较可以通过比较CNT&#xff08;时基单元&#xff09;和CCR&#xff08;捕获单元&#xff09;寄存器值的关系&#xff0c;来对输出电平进行置1、置0或翻转的操作&#xff0c;用于输出一定频…...

HTTPS 加密协议

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录HTTPS"加密" 是什么HTTPS 的工作过程引入证书HTTPS http 安全层 (SSL) SSL 用来加密的协议&#xff0c;也叫 TLS …...

分布式锁和分布式事务

分布式锁 没有图形&#xff0c;只通过大量文字进行说明。分布式锁&#xff1a;redis分布式锁&#xff0c; zk分布式锁&#xff0c; 数据库做分布式锁 redis分布式锁 setnx key value ex 10 原子操作 AB两个线程减库存业务&#xff0c;假设库存是10 A线程获取锁&#xff0c;…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

Vite中定义@软链接

在webpack中可以直接通过符号表示src路径&#xff0c;但是vite中默认不可以。 如何实现&#xff1a; vite中提供了resolve.alias&#xff1a;通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...

Ubuntu系统多网卡多相机IP设置方法

目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机&#xff0c;交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息&#xff0c;系统版本&#xff1a;Ubuntu22.04.5 LTS&#xff1b;内核版本…...

ArcPy扩展模块的使用(3)

管理工程项目 arcpy.mp模块允许用户管理布局、地图、报表、文件夹连接、视图等工程项目。例如&#xff0c;可以更新、修复或替换图层数据源&#xff0c;修改图层的符号系统&#xff0c;甚至自动在线执行共享要托管在组织中的工程项。 以下代码展示了如何更新图层的数据源&…...