OpenCV  4.5.0
Open Source Computer Vision
samples/dnn/text_detection.cpp
/*
Text detection model: https://github.com/argman/EAST
Download link: https://www.dropbox.com/s/r2ingd0l3zt8hxs/frozen_east_text_detection.tar.gz?dl=1
CRNN Text recognition model taken from here: https://github.com/meijieru/crnn.pytorch
How to convert from pb to onnx:
Using classes from here: https://github.com/meijieru/crnn.pytorch/blob/master/models/crnn.py
More converted onnx text recognition models can be downloaded directly here:
Download link: https://drive.google.com/drive/folders/1cTbQ3nuZG-EKWak6emD_s8_hHXWz7lAr?usp=sharing
And these models taken from here:https://github.com/clovaai/deep-text-recognition-benchmark
import torch
from models.crnn import CRNN
model = CRNN(32, 1, 37, 256)
model.load_state_dict(torch.load('crnn.pth'))
dummy_input = torch.randn(1, 1, 32, 100)
torch.onnx.export(model, dummy_input, "crnn.onnx", verbose=True)
*/
#include <opencv2/dnn.hpp>
using namespace cv;
using namespace cv::dnn;
const char* keys =
"{ help h | | Print help message. }"
"{ input i | | Path to input image or video file. Skip this argument to capture frames from a camera.}"
"{ model m | | Path to a binary .pb file contains trained detector network.}"
"{ ocr | | Path to a binary .pb or .onnx file contains trained recognition network.}"
"{ width | 320 | Preprocess input image by resizing to a specific width. It should be multiple by 32. }"
"{ height | 320 | Preprocess input image by resizing to a specific height. It should be multiple by 32. }"
"{ thr | 0.5 | Confidence threshold. }"
"{ nms | 0.4 | Non-maximum suppression threshold. }";
void decodeBoundingBoxes(const Mat& scores, const Mat& geometry, float scoreThresh,
std::vector<RotatedRect>& detections, std::vector<float>& confidences);
void fourPointsTransform(const Mat& frame, Point2f vertices[4], Mat& result);
void decodeText(const Mat& scores, std::string& text);
int main(int argc, char** argv)
{
// Parse command line arguments.
CommandLineParser parser(argc, argv, keys);
parser.about("Use this script to run TensorFlow implementation (https://github.com/argman/EAST) of "
"EAST: An Efficient and Accurate Scene Text Detector (https://arxiv.org/abs/1704.03155v2)");
if (argc == 1 || parser.has("help"))
{
parser.printMessage();
return 0;
}
float confThreshold = parser.get<float>("thr");
float nmsThreshold = parser.get<float>("nms");
int inpWidth = parser.get<int>("width");
int inpHeight = parser.get<int>("height");
String modelDecoder = parser.get<String>("model");
String modelRecognition = parser.get<String>("ocr");
if (!parser.check())
{
parser.printErrors();
return 1;
}
CV_Assert(!modelDecoder.empty());
// Load networks.
Net detector = readNet(modelDecoder);
Net recognizer;
if (!modelRecognition.empty())
recognizer = readNet(modelRecognition);
// Open a video file or an image file or a camera stream.
bool openSuccess = parser.has("input") ? cap.open(parser.get<String>("input")) : cap.open(0);
CV_Assert(openSuccess);
static const std::string kWinName = "EAST: An Efficient and Accurate Scene Text Detector";
std::vector<Mat> outs;
std::vector<String> outNames(2);
outNames[0] = "feature_fusion/Conv_7/Sigmoid";
outNames[1] = "feature_fusion/concat_3";
Mat frame, blob;
TickMeter tickMeter;
while (waitKey(1) < 0)
{
cap >> frame;
if (frame.empty())
{
break;
}
blobFromImage(frame, blob, 1.0, Size(inpWidth, inpHeight), Scalar(123.68, 116.78, 103.94), true, false);
detector.setInput(blob);
tickMeter.start();
detector.forward(outs, outNames);
tickMeter.stop();
Mat scores = outs[0];
Mat geometry = outs[1];
// Decode predicted bounding boxes.
std::vector<RotatedRect> boxes;
std::vector<float> confidences;
decodeBoundingBoxes(scores, geometry, confThreshold, boxes, confidences);
// Apply non-maximum suppression procedure.
std::vector<int> indices;
NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);
Point2f ratio((float)frame.cols / inpWidth, (float)frame.rows / inpHeight);
// Render text.
for (size_t i = 0; i < indices.size(); ++i)
{
RotatedRect& box = boxes[indices[i]];
Point2f vertices[4];
box.points(vertices);
for (int j = 0; j < 4; ++j)
{
vertices[j].x *= ratio.x;
vertices[j].y *= ratio.y;
}
if (!modelRecognition.empty())
{
Mat cropped;
fourPointsTransform(frame, vertices, cropped);
cvtColor(cropped, cropped, cv::COLOR_BGR2GRAY);
Mat blobCrop = blobFromImage(cropped, 1.0/127.5, Size(), Scalar::all(127.5));
recognizer.setInput(blobCrop);
tickMeter.start();
Mat result = recognizer.forward();
tickMeter.stop();
std::string wordRecognized = "";
decodeText(result, wordRecognized);
putText(frame, wordRecognized, vertices[1], FONT_HERSHEY_SIMPLEX, 1.5, Scalar(0, 0, 255));
}
for (int j = 0; j < 4; ++j)
line(frame, vertices[j], vertices[(j + 1) % 4], Scalar(0, 255, 0), 1);
}
// Put efficiency information.
std::string label = format("Inference time: %.2f ms", tickMeter.getTimeMilli());
putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0));
imshow(kWinName, frame);
tickMeter.reset();
}
return 0;
}
void decodeBoundingBoxes(const Mat& scores, const Mat& geometry, float scoreThresh,
std::vector<RotatedRect>& detections, std::vector<float>& confidences)
{
detections.clear();
CV_Assert(scores.dims == 4); CV_Assert(geometry.dims == 4); CV_Assert(scores.size[0] == 1);
CV_Assert(geometry.size[0] == 1); CV_Assert(scores.size[1] == 1); CV_Assert(geometry.size[1] == 5);
CV_Assert(scores.size[2] == geometry.size[2]); CV_Assert(scores.size[3] == geometry.size[3]);
const int height = scores.size[2];
const int width = scores.size[3];
for (int y = 0; y < height; ++y)
{
const float* scoresData = scores.ptr<float>(0, 0, y);
const float* x0_data = geometry.ptr<float>(0, 0, y);
const float* x1_data = geometry.ptr<float>(0, 1, y);
const float* x2_data = geometry.ptr<float>(0, 2, y);
const float* x3_data = geometry.ptr<float>(0, 3, y);
const float* anglesData = geometry.ptr<float>(0, 4, y);
for (int x = 0; x < width; ++x)
{
float score = scoresData[x];
if (score < scoreThresh)
continue;
// Decode a prediction.
// Multiple by 4 because feature maps are 4 time less than input image.
float offsetX = x * 4.0f, offsetY = y * 4.0f;
float angle = anglesData[x];
float cosA = std::cos(angle);
float sinA = std::sin(angle);
float h = x0_data[x] + x2_data[x];
float w = x1_data[x] + x3_data[x];
Point2f offset(offsetX + cosA * x1_data[x] + sinA * x2_data[x],
offsetY - sinA * x1_data[x] + cosA * x2_data[x]);
Point2f p1 = Point2f(-sinA * h, -cosA * h) + offset;
Point2f p3 = Point2f(-cosA * w, sinA * w) + offset;
RotatedRect r(0.5f * (p1 + p3), Size2f(w, h), -angle * 180.0f / (float)CV_PI);
detections.push_back(r);
confidences.push_back(score);
}
}
}
void fourPointsTransform(const Mat& frame, Point2f vertices[4], Mat& result)
{
const Size outputSize = Size(100, 32);
Point2f targetVertices[4] = {Point(0, outputSize.height - 1),
Point(0, 0), Point(outputSize.width - 1, 0),
Point(outputSize.width - 1, outputSize.height - 1),
};
Mat rotationMatrix = getPerspectiveTransform(vertices, targetVertices);
warpPerspective(frame, result, rotationMatrix, outputSize);
}
void decodeText(const Mat& scores, std::string& text)
{
static const std::string alphabet = "0123456789abcdefghijklmnopqrstuvwxyz";
Mat scoresMat = scores.reshape(1, scores.size[0]);
std::vector<char> elements;
elements.reserve(scores.size[0]);
for (int rowIndex = 0; rowIndex < scoresMat.rows; ++rowIndex)
{
Point p;
minMaxLoc(scoresMat.row(rowIndex), 0, 0, 0, &p);
if (p.x > 0 && static_cast<size_t>(p.x) <= alphabet.size())
{
elements.push_back(alphabet[p.x - 1]);
}
else
{
elements.push_back('-');
}
}
if (elements.size() > 0 && elements[0] != '-')
text += elements[0];
for (size_t elementIndex = 1; elementIndex < elements.size(); ++elementIndex)
{
if (elementIndex > 0 && elements[elementIndex] != '-' &&
elements[elementIndex - 1] != elements[elementIndex])
{
text += elements[elementIndex];
}
}
}
cv::Mat::reshape
Mat reshape(int cn, int rows=0) const
Changes the shape and/or the number of channels of a 2D matrix without copying the data.
cv::Mat::rows
int rows
the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
Definition: mat.hpp:2087
cv::FONT_HERSHEY_SIMPLEX
@ FONT_HERSHEY_SIMPLEX
normal size sans-serif font
Definition: imgproc.hpp:818
cv::String
std::string String
Definition: cvstd.hpp:150
cv::Point_< float >
cv::Scalar_< double >::all
static Scalar_< double > all(double v0)
returns a scalar with all elements set to v0
cv::TickMeter::stop
void stop()
stops counting ticks.
Definition: utility.hpp:310
cv::cvtColor
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0)
Converts an image from one color space to another.
cv::Size2f
Size_< float > Size2f
Definition: types.hpp:345
cv::sin
softdouble sin(const softdouble &a)
Sine.
cv::dnn::NMSBoxes
void NMSBoxes(const std::vector< Rect > &bboxes, const std::vector< float > &scores, const float score_threshold, const float nms_threshold, std::vector< int > &indices, const float eta=1.f, const int top_k=0)
Performs non maximum suppression given boxes and corresponding scores.
cv::dnn::readNet
Net readNet(const String &model, const String &config="", const String &framework="")
Read deep learning network represented in one of the supported formats.
cv::Point2f
Point_< float > Point2f
Definition: types.hpp:192
cv::Mat::ptr
uchar * ptr(int i0=0)
Returns a pointer to the specified matrix row.
cv::WINDOW_NORMAL
@ WINDOW_NORMAL
the user can resize the window (no constraint) / also use to switch a fullscreen window to a normal s...
Definition: highgui.hpp:183
cv::VideoCapture
Class for video capturing from video files, image sequences or cameras.
Definition: videoio.hpp:625
cv::Mat::dims
int dims
the matrix dimensionality, >= 2
Definition: mat.hpp:2085
cv::waitKey
int waitKey(int delay=0)
Waits for a pressed key.
cv::Point_::y
_Tp y
y coordinate of the point
Definition: types.hpp:187
cv::getPerspectiveTransform
Mat getPerspectiveTransform(InputArray src, InputArray dst, int solveMethod=DECOMP_LU)
Calculates a perspective transform from four pairs of the corresponding points.
cv::Point_::x
_Tp x
x coordinate of the point
Definition: types.hpp:186
cv::TickMeter::reset
void reset()
resets internal values.
Definition: utility.hpp:374
cv::Size_
Template class for specifying the size of an image or rectangle.
Definition: types.hpp:316
dnn.hpp
highgui.hpp
cv::namedWindow
void namedWindow(const String &winname, int flags=WINDOW_AUTOSIZE)
Creates a window.
cv::Size
Size2i Size
Definition: types.hpp:347
cv::Mat::row
Mat row(int y) const
Creates a matrix header for the specified matrix row.
cv::dnn
Definition: all_layers.hpp:47
cv::line
void line(InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
Draws a line segment connecting two points.
cv::Size_::width
_Tp width
the width
Definition: types.hpp:339
cv::TickMeter::start
void start()
starts counting ticks.
Definition: utility.hpp:304
cv::cos
softdouble cos(const softdouble &a)
Cosine.
cv::Mat::size
MatSize size
Definition: mat.hpp:2109
cv::RotatedRect
The class represents rotated (i.e. not up-right) rectangles on a plane.
Definition: types.hpp:504
cv::TickMeter
a Class to measure passing time.
Definition: utility.hpp:295
cv::imshow
void imshow(const String &winname, InputArray mat)
Displays an image in the specified window.
cv::RotatedRect::points
void points(Point2f pts[]) const
cv::dnn::Net::forward
Mat forward(const String &outputName=String())
Runs forward pass to compute output of layer with name outputName.
cv::minMaxLoc
void minMaxLoc(InputArray src, double *minVal, double *maxVal=0, Point *minLoc=0, Point *maxLoc=0, InputArray mask=noArray())
Finds the global minimum and maximum in an array.
cv::Scalar
Scalar_< double > Scalar
Definition: types.hpp:669
cv::warpPerspective
void warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar &borderValue=Scalar())
Applies a perspective transformation to an image.
cv::putText
void putText(InputOutputArray img, const String &text, Point org, int fontFace, double fontScale, Scalar color, int thickness=1, int lineType=LINE_8, bool bottomLeftOrigin=false)
Draws a text string.
cv::Point
Point2i Point
Definition: types.hpp:194
CV_Assert
#define CV_Assert(expr)
Checks a condition at runtime and throws exception if it fails.
Definition: base.hpp:342
cv::Mat
n-dimensional dense array class
Definition: mat.hpp:793
cv::dnn::Net::setInput
void setInput(InputArray blob, const String &name="", double scalefactor=1.0, const Scalar &mean=Scalar())
Sets the new input value for the network.
cv::dnn::blobFromImage
Mat blobFromImage(InputArray image, double scalefactor=1.0, const Size &size=Size(), const Scalar &mean=Scalar(), bool swapRB=false, bool crop=false, int ddepth=CV_32F)
Creates 4-dimensional blob from image. Optionally resizes and crops image from center,...
cv::CommandLineParser
Designed for command line parsing.
Definition: utility.hpp:789
cv::Size_::height
_Tp height
the height
Definition: types.hpp:340
cv::COLOR_BGR2GRAY
@ COLOR_BGR2GRAY
convert between RGB/BGR and grayscale, color conversions
Definition: imgproc.hpp:546
cv
"black box" representation of the file storage associated with a file on disk.
Definition: affine.hpp:52
cv::TickMeter::getTimeMilli
double getTimeMilli() const
returns passed time in milliseconds.
Definition: utility.hpp:333
imgproc.hpp
CV_PI
#define CV_PI
Definition: cvdef.h:354
cv::dnn::Net
This class allows to create and manipulate comprehensive artificial neural networks.
Definition: dnn.hpp:391
cv::VideoCapture::open
virtual bool open(const String &filename, int apiPreference=CAP_ANY)
Opens a video file or a capturing device or an IP video stream for video capturing.