草庐IT

OpenCv + Qt5.12.2 文字识别

何其不顾四月天 2023-10-21 原文

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_OBJECT

public:
    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 channel
    std::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);
        else
            r = cv::Range(i,(int)detections.size());

        switch(Recongnition)
        {
        case 0: // Tesseract
            qDebug() << "+++++";
            cv::parallel_for_(r, ParallelOCR<cv::text::OCRTesseract>(detections, outputs, boxes, words, confidences, ocrs));
            qDebug() << "---";
            break;
        case 1: // NM_chain_features + KNN
            cv::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);
        }
        else
            cv::rectangle(src, groups[i], cv::Scalar( 255 ), 3, 8 );
    }
}

## 源码

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

源码

有关OpenCv + Qt5.12.2 文字识别的更多相关文章

  1. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

  2. ruby - 字符串文字中的转义状态作为 `String#tr` 的参数 - 2

    对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一

  3. 报告回顾丨模型进化狂飙,DetectGPT能否识别最新模型生成结果? - 2

    导读语言模型给我们的生产生活带来了极大便利,但同时不少人也利用他们从事作弊工作。如何规避这些难辨真伪的文字所产生的负面影响也成为一大难题。在3月9日智源Live第33期活动「DetectGPT:判断文本是否为机器生成的工具」中,主讲人Eric为我们讲解了DetectGPT工作背后的思路——一种基于概率曲率检测的用于检测模型生成文本的工具,它可以帮助我们更好地分辨文章的来源和可信度,对保护信息真实、防止欺诈等方面具有重要意义。本次报告主要围绕其功能,实现和效果等展开。(文末点击“阅读原文”,查看活动回放。)Ericmitchell斯坦福大学计算机系四年级博士生,由ChelseaFinn和Chri

  4. Vscode+Cmake配置并运行opencv环境(Windows和Ubuntu大同小异) - 2

    之前在培训新生的时候,windows环境下配置opencv环境一直教的都是网上主流的vsstudio配置属性表,但是这个似乎对新生来说难度略高(虽然个人觉得完全是他们自己的问题),加之暑假之后对cmake实在是爱不释手,且这样配置确实十分简单(其实都不需要配置),故斗胆妄言vscode下配置CV之法。其实极为简单,图比较多所以很长。如果你看此文还配不好,你应该思考一下是不是自己的问题。闲话少说,直接开始。0.CMkae简介有的人到大二了都不知道cmake是什么,我不说是谁。CMake是一个开源免费并且跨平台的构建工具,可以用简单的语句来描述所有平台的编译过程。它能够根据当前所在平台输出对应的m

  5. [Vuforia]二.3D物体识别 - 2

    之前说过10之后的版本没有3dScan了,所以还是9.8的版本或者之前更早的版本。 3d物体扫描需要先下载扫描的APK进行扫面。首先要在手机上装一个扫描程序,扫描现实中的三维物体,然后上传高通官网,在下载成UnityPackage类型让Unity能够使用这个扫描程序可以从高通官网上进行下载,是一个安卓程序。点到Tools往下滑,找到VuforiaObjectScanner下载后解压数据线连接手机,将apk文件拷入手机安装然后刚才解压文件中的Media文件夹打开,两个PDF图打印第一张A4-ObjectScanningTarget.pdf,主要是用来辅助扫描的。好了,接下来就是扫描三维物体。将瓶

  6. Qt Designer的简单使用 - 2

    在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q

  7. ruby-on-rails - 在 heroku 的 .fonts 文件夹中包含自定义字体,似乎无法识别它们 - 2

    Heroku支持人员告诉我,为了在我的Web应用程序中使用自定义字体(未安装在系统中,您可以在bash控制台中使用fc-list查看已安装的字体)我必须部署一个包含所有字体的.fonts文件夹里面的字体。问题是我不知道该怎么做。我的意思是,我不知道文件名是否必须遵循heroku的任何特殊模式,或者我必须在我的代码中做一些事情来考虑这种字体,或者如果我将它包含在文件夹中它是自动的......事实是,我尝试以不同的方式更改字体的文件名,但根本没有使用该字体。为了提供更多详细信息,我们使用字体的过程是将PDF转换为图像,更具体地说,使用rghostgem。并且最终图像根本不使用自定义字体。在

  8. ruby-on-rails - 没有这样的文件或目录 - 用 Mini Magick 识别 - 2

    在我让另一个人重做我的前端UI之前,我的Rails应用程序运行平稳。我已经尝试解决此错误3天了。这是错误:Nosuchfileordirectory-identifyExtractedsource(aroundline#59):575859606162@post=Post.find(params[:id])authorize@postif@post.update_attributes(post_params)flash[:notice]="Postwasupdated."redirect_to[@topic,@post]else{"utf8"=>"✓","_method"=>"patc

  9. ruby - 字符串文字前面的 * 在 ruby​​ 中有什么作用? - 2

    这段代码似乎创建了一个范围从a到z的数组,但我不明白*的作用。有人可以解释一下吗?[*"a".."z"] 最佳答案 它叫做splatoperator.SplattinganLvalueAmaximumofonelvaluemaybesplattedinwhichcaseitisassignedanArrayconsistingoftheremainingrvaluesthatlackcorrespondinglvalues.Iftherightmostlvalueissplattedthenitconsumesallrvaluesw

  10. ruby - 使用 ruby​​ 识别阵列上的运行 - 2

    如果我们有一个数组array=[1,1,0,0,2,3,0,0,0,3,3,3]我们如何识别给定数字的运行(具有相同值的连续数字的数量)?例如:run_pattern_for(array,0)->2run_pattern_for(array,3)->1run_pattern_for(array,1)->1run_pattern_for(array,2)->0没有2的运行,因为没有连续出现2。3有一个运行,因为只有一个幻影以树为连续数字。 最佳答案 尝试:classArraydefcount_runs(element)chunk{|n

随机推荐