当前位置: 首页 > 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;…...

【计算机组成原理】——磁盘性能三要素:容量、寻址与传输的实战解析

1. 磁盘性能三要素&#xff1a;从理论到实战 刚接触计算机组成原理时&#xff0c;我对磁盘性能的理解仅限于"越大越好"。直到有次帮朋友选配NAS存储&#xff0c;面对商家宣传的"7200转高速盘"、"128MB缓存"等参数时&#xff0c;才发现自己完全不…...

高效管理Git仓库:彻底排除node_modules的实用指南

1. 为什么必须排除node_modules文件夹 每次新建Node.js项目时&#xff0c;npm或yarn都会自动生成node_modules目录来存放依赖包。这个文件夹通常包含成千上万个文件&#xff0c;比如一个基础Vue项目就可能超过200MB。我曾见过一个企业级项目的node_modules膨胀到1.2GB&#xff…...

惠普tank 2606,开机报错 ER-08 ,加了碳粉还是报错ER08,黄灯闪烁成像鼓接近寿命期限,别被维修店坑了,这个软件专门维修这个错误,软件运行一下2分钟搞好。

下载地址&#xff1a;链接:https://pan.baidu.com/s/1J7PN4m4fbIzku9DqBFg_nw?pwd0000 提取码:0000 备用下载&#xff1a;下载 惠普tank 2606系列&#xff0c;tank1005系列&#xff0c;打印机提示错误代码 er-08 &#xff0c;加了粉还是报错er08,提示没粉&#xff0c;闪黄灯…...

像素剧本圣殿企业应用:中小型内容工作室剧本量产工作流搭建

像素剧本圣殿企业应用&#xff1a;中小型内容工作室剧本量产工作流搭建 1. 剧本创作新范式 在内容创作行业&#xff0c;剧本开发一直是耗时费力的核心环节。传统编剧流程中&#xff0c;一个完整剧本从构思到成稿往往需要数周甚至数月时间&#xff0c;这对于资源有限的中小型工…...

深度学习模型过拟合的实战诊断与优化策略

1. 过拟合现象的诊断方法 第一次训练神经网络时&#xff0c;我盯着训练准确率冲到99%兴奋不已&#xff0c;结果测试集表现只有65%——这就是典型的过拟合现场。判断模型是否过拟合&#xff0c;就像医生看体检报告&#xff0c;需要多维度交叉验证。 最直观的方法是训练集与验证集…...

Wan2.2-I2V-A14B私有部署实战教程:RTX 4090D一键生成1080P视频

Wan2.2-I2V-A14B私有部署实战教程&#xff1a;RTX 4090D一键生成1080P视频 1. 开篇&#xff1a;为什么选择私有部署 当你需要频繁生成高质量视频内容时&#xff0c;公有云服务往往面临三个痛点&#xff1a;生成排队时间长、隐私数据风险、调用成本高。Wan2.2-I2V-A14B私有部署…...

TFT Overlay:云顶之弈玩家的终极装备合成与羁绊指南

TFT Overlay&#xff1a;云顶之弈玩家的终极装备合成与羁绊指南 【免费下载链接】TFT-Overlay Overlay for Teamfight Tactics 项目地址: https://gitcode.com/gh_mirrors/tf/TFT-Overlay 在云顶之弈的激烈对局中&#xff0c;你是否经常为记不住复杂的装备合成公式而烦恼…...

Open-AutoGLM快速体验:一句话指令让AI自动操作抖音、微信

Open-AutoGLM快速体验&#xff1a;一句话指令让AI自动操作抖音、微信 1. 引言&#xff1a;解放双手的AI手机助手 想象一下这样的场景&#xff1a;你正在做饭&#xff0c;手上沾满面粉&#xff0c;突然需要给朋友发条微信消息。传统方式你需要洗手、解锁手机、打开微信、输入内…...

FireRedASR-AED-L本地化教程:国产统信UOS/麒麟系统全兼容部署方案

FireRedASR-AED-L本地化教程&#xff1a;国产统信UOS/麒麟系统全兼容部署方案 提示&#xff1a;本教程已在统信UOS 20、麒麟V10系统完成实测验证&#xff0c;同样适用于Ubuntu、CentOS等Linux发行版 1. 项目简介&#xff1a;为什么选择这个工具&#xff1f; 如果你正在寻找一个…...

LM358充电器电路设计:从原理到实践

1. LM358芯片基础解析 LM358这颗双运放芯片可以说是电子设计领域的"万金油"了。我第一次接触它是在大学电子竞赛时&#xff0c;老师随手扔给我们几片说&#xff1a;"用这个&#xff0c;不容易烧。"果然&#xff0c;从5V到32V的宽电压范围让它成为新手最友好…...