samples/dnn/text_detection.cpp#

  1/*
  2    Text detection model (EAST): https://github.com/argman/EAST
  3    Download link for EAST model: https://www.dropbox.com/s/r2ingd0l3zt8hxs/frozen_east_text_detection.tar.gz?dl=1
  4
  5    DB detector model:
  6    https://drive.google.com/uc?export=download&id=17_ABp79PlFt9yPCxSaarVc_DKTmrSGGf
  7
  8    CRNN Text recognition model sourced from: https://github.com/meijieru/crnn.pytorch
  9    How to convert from .pb to .onnx:
 10    Using classes from: https://github.com/meijieru/crnn.pytorch/blob/master/models/crnn.py
 11
 12    Additional converted ONNX text recognition models available for direct download:
 13    Download link: https://drive.google.com/drive/folders/1cTbQ3nuZG-EKWak6emD_s8_hHXWz7lAr?usp=sharing
 14    These models are taken from: https://github.com/clovaai/deep-text-recognition-benchmark
 15
 16    Importing and using the CRNN model in PyTorch:
 17    import torch
 18    from models.crnn import CRNN
 19
 20    model = CRNN(32, 1, 37, 256)
 21    model.load_state_dict(torch.load('crnn.pth'))
 22    dummy_input = torch.randn(1, 1, 32, 100)
 23    torch.onnx.export(model, dummy_input, "crnn.onnx", verbose=True)
 24
 25    Usage: ./example_dnn_text_detection DB
 26*/
 27#include <iostream>
 28#include <fstream>
 29
 30#include <opencv2/geometry.hpp>
 31#include <opencv2/imgproc.hpp>
 32#include <opencv2/highgui.hpp>
 33#include <opencv2/dnn.hpp>
 34
 35#include "common.hpp"
 36
 37using namespace cv;
 38using namespace std;
 39using namespace cv::dnn;
 40
 41const string about = "Use this script for Text Detection and Recognition using OpenCV. \n\n"
 42        "Firstly, download required models using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to point to the directory where models are downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.\n"
 43        "To run:\n"
 44        "\t Example: ./example_dnn_text_detection modelName(i.e. DB or East) --ocr_model=<path to VGG_CTC.onnx>\n\n"
 45        "Detection model path can also be specified using --model argument. \n\n"
 46        "Download ocr model using: python download_models.py OCR \n\n";
 47
 48// Command-line keys to parse the input arguments
 49string keys =
 50    "{ help  h                        |                     | Print help message. }"
 51    "{ input i                        |      right.jpg      | Path to an input image. }"
 52    "{ @alias                         |                     | An alias name of model to extract preprocessing parameters from models.yml file. }"
 53    "{ zoo                            |  ../dnn/models.yml  | An optional path to file with preprocessing parameters }"
 54    "{ ocr_model                      |                     | Path to a binary .onnx model for recognition. }"
 55    "{ model                          |                     | Path to detection model file. }"
 56    "{ thr                            |        0.5          | Confidence threshold for EAST detector. }"
 57    "{ nms                            |        0.4          | Non-maximum suppression threshold for EAST detector. }"
 58    "{ binaryThreshold bt             |        0.3          | Confidence threshold for the binary map in DB detector. }"
 59    "{ polygonThreshold pt            |        0.5          | Confidence threshold for polygons in DB detector. }"
 60    "{ maxCandidate max               |        200          | Max candidates for polygons in DB detector. }"
 61    "{ unclipRatio ratio              |        2.0          | Unclip ratio for DB detector. }"
 62    "{ vocabularyPath vp              |   alphabet_36.txt   | Path to vocabulary file. }";
 63
 64// Function prototype for the four-point perspective transform
 65static void fourPointsTransform(const Mat& frame, const Point2f vertices[], Mat& result);
 66static void processFrame(
 67    const Mat& frame,
 68    const vector<vector<Point>>& detResults,
 69    const std::string& ocr_model,
 70    bool imreadRGB,
 71    Mat& board,
 72    FontFace& fontFace,
 73    int fontSize,
 74    int fontWeight,
 75    const vector<std::string>& vocabulary
 76);
 77
 78int main(int argc, char** argv) {
 79    // Setting up command-line parser with the specified keys
 80    CommandLineParser parser(argc, argv, keys);
 81
 82    if (!parser.has("@alias") || parser.has("help"))
 83    {
 84        cout << about << endl;
 85        parser.printMessage();
 86        return -1;
 87    }
 88    const string modelName = parser.get<String>("@alias");
 89    const string zooFile = findFile(parser.get<String>("zoo"));
 90
 91    keys += genPreprocArguments(modelName, zooFile, "");
 92    keys += genPreprocArguments(modelName, zooFile, "ocr_");
 93    parser = CommandLineParser(argc, argv, keys);
 94    parser.about(about);
 95
 96    // Parsing command-line arguments
 97
 98    String sha1 = parser.get<String>("sha1");
 99    String ocr_sha1 = parser.get<String>("ocr_sha1");
100    String detModelPath = findModel(parser.get<String>("model"), sha1);
101    String ocr = findModel(parser.get<String>("ocr_model"), ocr_sha1);
102    int height = parser.get<int>("height");
103    int width = parser.get<int>("width");
104    bool imreadRGB = parser.get<bool>("rgb");
105    String vocPath = parser.get<String>("vocabularyPath");
106    float binThresh = parser.get<float>("binaryThreshold");
107    float polyThresh = parser.get<float>("polygonThreshold");
108    double unclipRatio = parser.get<double>("unclipRatio");
109    uint maxCandidates = parser.get<uint>("maxCandidate");
110    float confThreshold = parser.get<float>("thr");
111    float nmsThreshold = parser.get<float>("nms");
112    Scalar mean = parser.get<Scalar>("mean");
113
114    // Ensuring the provided arguments are valid
115    if (!parser.check()) {
116        parser.printErrors();
117        return 1;
118    }
119
120    // Asserting detection model path is provided
121    CV_Assert(!detModelPath.empty());
122
123    vector<vector<Point>> detResults;
124    // Reading the input image
125    Mat frame = imread(samples::findFile(parser.get<String>("input")));
126    Mat board(frame.size(), frame.type(), Scalar(255, 255, 255));
127    int stdSize = 20;
128    int stdWeight = 400;
129    int stdImgSize = 512;
130    int imgWidth = min(frame.rows, frame.cols);
131    int size = (stdSize*imgWidth)/stdImgSize;
132    int weight = (stdWeight*imgWidth)/stdImgSize;
133    FontFace fontFace("sans");
134
135    // Initializing and configuring the text detection model based on the provided config
136    if (modelName == "East") {
137        // EAST Detector initialization
138        TextDetectionModel_EAST detector(detModelPath);
139        detector.setConfidenceThreshold(confThreshold)
140                .setNMSThreshold(nmsThreshold);
141        // Setting input parameters specific to EAST model
142        detector.setInputParams(1.0, Size(width, height), mean, true);
143        // Performing text detection
144        detector.detect(frame, detResults);
145    }
146    else if (modelName == "DB") {
147        // DB Detector initialization
148        TextDetectionModel_DB detector(detModelPath);
149        detector.setBinaryThreshold(binThresh)
150                .setPolygonThreshold(polyThresh)
151                .setUnclipRatio(unclipRatio)
152                .setMaxCandidates(maxCandidates);
153        // Setting input parameters specific to DB model
154        detector.setInputParams(1.0 / 255.0, Size(width, height), mean);
155        // Performing text detection
156        detector.detect(frame, detResults);
157    }
158    else {
159        cout << "[ERROR]: Unsupported file config for the detector model. Valid values: east/db" << endl;
160        return 1;
161    }
162
163    // Reading and storing vocabulary for text recognition
164    CV_Assert(!vocPath.empty());
165    ifstream vocFile;
166    vocFile.open(samples::findFile(vocPath));
167    CV_Assert(vocFile.is_open());
168    std::string vocLine;
169    vector<std::string> vocabulary;
170    while (getline(vocFile, vocLine)) {
171        vocabulary.push_back(vocLine);
172    }
173
174    processFrame(frame, detResults, ocr, imreadRGB, board, fontFace, size, weight, vocabulary);
175    return 0;
176}
177
178// Performs a perspective transform for a four-point region
179static void fourPointsTransform(const Mat& frame, const Point2f vertices[], Mat& result) {
180    const Size outputSize = Size(100, 32);
181    // Defining target vertices for the perspective transform
182    Point2f targetVertices[4] = {
183        Point(0, outputSize.height - 1),
184        Point(0, 0),
185        Point(outputSize.width - 1, 0),
186        Point(outputSize.width - 1, outputSize.height - 1)
187    };
188    // Computing the perspective transform matrix
189    Mat rotationMatrix = getPerspectiveTransform(vertices, targetVertices);
190    // Applying the perspective transform to the region
191    warpPerspective(frame, result, rotationMatrix, outputSize);
192}
193
194void processFrame(
195    const Mat& frame,
196    const vector<vector<Point>>& detResults,
197    const std::string& ocr_model,
198    bool imreadRGB,
199    Mat& board,
200    FontFace& fontFace,
201    int fontSize,
202    int fontWeight,
203    const vector<std::string>& vocabulary
204) {
205    if (detResults.size() > 0) {
206        // Text Recognition
207        Mat recInput;
208        if (!imreadRGB) {
209            cvtColor(frame, recInput, cv::COLOR_BGR2GRAY);
210        } else {
211            recInput = frame;
212        }
213
214        vector<vector<Point>> contours;
215        for (uint i = 0; i < detResults.size(); i++) {
216            const auto& quadrangle = detResults[i];
217            CV_CheckEQ(quadrangle.size(), (size_t)4, "");
218
219            contours.emplace_back(quadrangle);
220
221            vector<Point2f> quadrangle_2f;
222            for (int j = 0; j < 4; j++)
223                quadrangle_2f.emplace_back(detResults[i][j]);
224
225            // Cropping the detected text region using a four-point transform
226            Mat cropped;
227            fourPointsTransform(recInput, &quadrangle_2f[0], cropped);
228
229            if(!ocr_model.empty()){
230                TextRecognitionModel recognizer(ocr_model);
231                recognizer.setVocabulary(vocabulary);
232                recognizer.setDecodeType("CTC-greedy");
233
234                // Setting input parameters for the recognition model
235                double recScale = 1.0 / 127.5;
236                Scalar recMean = Scalar(127.5);
237                Size recInputSize = Size(100, 32);
238                recognizer.setInputParams(recScale, recInputSize, recMean);
239                // Recognizing text from the cropped image
240                string recognitionResult = recognizer.recognize(cropped);
241                cout << i << ": '" << recognitionResult << "'" << endl;
242
243                // Displaying the recognized text on the image
244                putText(board, recognitionResult, Point(detResults[i][1].x, detResults[i][0].y), Scalar(0, 0, 0), fontFace, fontSize, fontWeight);
245            }
246            else{
247                cout << "[WARN] Please pass the path to the ocr model using --ocr_model to get the recognised text." << endl;
248            }
249        }
250        // Drawing detected text regions on the image
251        polylines(board, contours, true, Scalar(200, 255, 200), 1);
252        polylines(frame, contours, true, Scalar(0, 255, 0), 1);
253    } else {
254        cout << "No Text Detected." << endl;
255    }
256
257    // Displaying the final image with detected and recognized text
258    Mat stacked;
259    hconcat(frame, board, stacked);
260    imshow("Text Detection and Recognition", stacked);
261    waitKey(0);
262}