mirror of
https://github.com/opencv/opencv.git
synced 2026-07-31 08:13:04 +04:00
EAST: An Efficient and Accurate Scene Text Detector (https://arxiv.org/abs/1704.03155v2)
This commit is contained in:
@@ -1,35 +1,8 @@
|
||||
#ifndef __OPENCV_SAMPLES_DNN_CUSTOM_LAYERS__
|
||||
#define __OPENCV_SAMPLES_DNN_CUSTOM_LAYERS__
|
||||
|
||||
#include <opencv2/dnn.hpp>
|
||||
|
||||
//! [A custom layer interface]
|
||||
class MyLayer : public cv::dnn::Layer
|
||||
{
|
||||
public:
|
||||
//! [MyLayer::MyLayer]
|
||||
MyLayer(const cv::dnn::LayerParams ¶ms);
|
||||
//! [MyLayer::MyLayer]
|
||||
|
||||
//! [MyLayer::create]
|
||||
static cv::Ptr<cv::dnn::Layer> create(cv::dnn::LayerParams& params);
|
||||
//! [MyLayer::create]
|
||||
|
||||
//! [MyLayer::getMemoryShapes]
|
||||
virtual bool getMemoryShapes(const std::vector<std::vector<int> > &inputs,
|
||||
const int requiredOutputs,
|
||||
std::vector<std::vector<int> > &outputs,
|
||||
std::vector<std::vector<int> > &internals) const CV_OVERRIDE;
|
||||
//! [MyLayer::getMemoryShapes]
|
||||
|
||||
//! [MyLayer::forward]
|
||||
virtual void forward(std::vector<cv::Mat*> &inputs, std::vector<cv::Mat> &outputs, std::vector<cv::Mat> &internals) CV_OVERRIDE;
|
||||
//! [MyLayer::forward]
|
||||
|
||||
//! [MyLayer::finalize]
|
||||
virtual void finalize(const std::vector<cv::Mat*> &inputs, std::vector<cv::Mat> &outputs) CV_OVERRIDE;
|
||||
//! [MyLayer::finalize]
|
||||
|
||||
virtual void forward(cv::InputArrayOfArrays inputs, cv::OutputArrayOfArrays outputs, cv::OutputArrayOfArrays internals) CV_OVERRIDE;
|
||||
};
|
||||
//! [A custom layer interface]
|
||||
#include <opencv2/dnn/shape_utils.hpp> // getPlane
|
||||
|
||||
//! [InterpLayer]
|
||||
class InterpLayer : public cv::dnn::Layer
|
||||
@@ -113,15 +86,33 @@ private:
|
||||
//! [InterpLayer]
|
||||
|
||||
//! [ResizeBilinearLayer]
|
||||
class ResizeBilinearLayer : public cv::dnn::Layer
|
||||
class ResizeBilinearLayer CV_FINAL : public cv::dnn::Layer
|
||||
{
|
||||
public:
|
||||
ResizeBilinearLayer(const cv::dnn::LayerParams ¶ms) : Layer(params)
|
||||
{
|
||||
CV_Assert(!params.get<bool>("align_corners", false));
|
||||
CV_Assert(blobs.size() == 1, blobs[0].type() == CV_32SC1);
|
||||
outHeight = blobs[0].at<int>(0, 0);
|
||||
outWidth = blobs[0].at<int>(0, 1);
|
||||
CV_Assert(!blobs.empty());
|
||||
|
||||
for (size_t i = 0; i < blobs.size(); ++i)
|
||||
CV_Assert(blobs[i].type() == CV_32SC1);
|
||||
|
||||
// There are two cases of input blob: a single blob which contains output
|
||||
// shape and two blobs with scaling factors.
|
||||
if (blobs.size() == 1)
|
||||
{
|
||||
CV_Assert(blobs[0].total() == 2);
|
||||
outHeight = blobs[0].at<int>(0, 0);
|
||||
outWidth = blobs[0].at<int>(0, 1);
|
||||
factorHeight = factorWidth = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Assert(blobs.size() == 2, blobs[0].total() == 1, blobs[1].total() == 1);
|
||||
factorHeight = blobs[0].at<int>(0, 0);
|
||||
factorWidth = blobs[1].at<int>(0, 0);
|
||||
outHeight = outWidth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static cv::Ptr<cv::dnn::Layer> create(cv::dnn::LayerParams& params)
|
||||
@@ -130,25 +121,32 @@ public:
|
||||
}
|
||||
|
||||
virtual bool getMemoryShapes(const std::vector<std::vector<int> > &inputs,
|
||||
const int requiredOutputs,
|
||||
const int,
|
||||
std::vector<std::vector<int> > &outputs,
|
||||
std::vector<std::vector<int> > &internals) const CV_OVERRIDE
|
||||
std::vector<std::vector<int> > &) const CV_OVERRIDE
|
||||
{
|
||||
CV_UNUSED(requiredOutputs); CV_UNUSED(internals);
|
||||
std::vector<int> outShape(4);
|
||||
outShape[0] = inputs[0][0]; // batch size
|
||||
outShape[1] = inputs[0][1]; // number of channels
|
||||
outShape[2] = outHeight;
|
||||
outShape[3] = outWidth;
|
||||
outShape[2] = outHeight != 0 ? outHeight : (inputs[0][2] * factorHeight);
|
||||
outShape[3] = outWidth != 0 ? outWidth : (inputs[0][3] * factorWidth);
|
||||
outputs.assign(1, outShape);
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void finalize(const std::vector<cv::Mat*>&, std::vector<cv::Mat> &outputs) CV_OVERRIDE
|
||||
{
|
||||
if (!outWidth && !outHeight)
|
||||
{
|
||||
outHeight = outputs[0].size[2];
|
||||
outWidth = outputs[0].size[3];
|
||||
}
|
||||
}
|
||||
|
||||
// This implementation is based on a reference implementation from
|
||||
// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h
|
||||
virtual void forward(std::vector<cv::Mat*> &inputs, std::vector<cv::Mat> &outputs, std::vector<cv::Mat> &internals) CV_OVERRIDE
|
||||
virtual void forward(std::vector<cv::Mat*> &inputs, std::vector<cv::Mat> &outputs, std::vector<cv::Mat> &) CV_OVERRIDE
|
||||
{
|
||||
CV_UNUSED(internals);
|
||||
cv::Mat& inp = *inputs[0];
|
||||
cv::Mat& out = outputs[0];
|
||||
const float* inpData = (float*)inp.data;
|
||||
@@ -195,19 +193,54 @@ private:
|
||||
return x + size[3] * (y + size[2] * (c + size[1] * b));
|
||||
}
|
||||
|
||||
int outWidth, outHeight;
|
||||
int outWidth, outHeight, factorWidth, factorHeight;
|
||||
};
|
||||
//! [ResizeBilinearLayer]
|
||||
|
||||
//! [Register a custom layer]
|
||||
#include <opencv2/dnn/layer.details.hpp> // CV_DNN_REGISTER_LAYER_CLASS macro
|
||||
//
|
||||
// The folowing code is used only to generate tutorials documentation.
|
||||
//
|
||||
|
||||
int main(int argc, char** argv)
|
||||
//! [A custom layer interface]
|
||||
class MyLayer : public cv::dnn::Layer
|
||||
{
|
||||
CV_DNN_REGISTER_LAYER_CLASS(MyType, MyLayer);
|
||||
public:
|
||||
//! [MyLayer::MyLayer]
|
||||
MyLayer(const cv::dnn::LayerParams ¶ms);
|
||||
//! [MyLayer::MyLayer]
|
||||
|
||||
//! [MyLayer::create]
|
||||
static cv::Ptr<cv::dnn::Layer> create(cv::dnn::LayerParams& params);
|
||||
//! [MyLayer::create]
|
||||
|
||||
//! [MyLayer::getMemoryShapes]
|
||||
virtual bool getMemoryShapes(const std::vector<std::vector<int> > &inputs,
|
||||
const int requiredOutputs,
|
||||
std::vector<std::vector<int> > &outputs,
|
||||
std::vector<std::vector<int> > &internals) const CV_OVERRIDE;
|
||||
//! [MyLayer::getMemoryShapes]
|
||||
|
||||
//! [MyLayer::forward]
|
||||
virtual void forward(std::vector<cv::Mat*> &inputs, std::vector<cv::Mat> &outputs, std::vector<cv::Mat> &internals) CV_OVERRIDE;
|
||||
//! [MyLayer::forward]
|
||||
|
||||
//! [MyLayer::finalize]
|
||||
virtual void finalize(const std::vector<cv::Mat*> &inputs, std::vector<cv::Mat> &outputs) CV_OVERRIDE;
|
||||
//! [MyLayer::finalize]
|
||||
|
||||
virtual void forward(cv::InputArrayOfArrays inputs, cv::OutputArrayOfArrays outputs, cv::OutputArrayOfArrays internals) CV_OVERRIDE;
|
||||
};
|
||||
//! [A custom layer interface]
|
||||
|
||||
//! [Register a custom layer]
|
||||
#include <opencv2/dnn/layer.details.hpp> // CV_DNN_REGISTER_LAYER_CLASS
|
||||
|
||||
static inline void loadNet()
|
||||
{
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Interp, InterpLayer);
|
||||
// ...
|
||||
//! [Register a custom layer]
|
||||
CV_UNUSED(argc); CV_UNUSED(argv);
|
||||
|
||||
//! [Register InterpLayer]
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Interp, InterpLayer);
|
||||
cv::dnn::Net caffeNet = cv::dnn::readNet("/path/to/config.prototxt", "/path/to/weights.caffemodel");
|
||||
@@ -217,16 +250,8 @@ int main(int argc, char** argv)
|
||||
CV_DNN_REGISTER_LAYER_CLASS(ResizeBilinear, ResizeBilinearLayer);
|
||||
cv::dnn::Net tfNet = cv::dnn::readNet("/path/to/graph.pb");
|
||||
//! [Register ResizeBilinearLayer]
|
||||
|
||||
if (false) loadNet(); // To prevent unused function warning.
|
||||
}
|
||||
|
||||
cv::Ptr<cv::dnn::Layer> MyLayer::create(cv::dnn::LayerParams& params)
|
||||
{
|
||||
return cv::Ptr<cv::dnn::Layer>(new MyLayer(params));
|
||||
}
|
||||
MyLayer::MyLayer(const cv::dnn::LayerParams&) {}
|
||||
bool MyLayer::getMemoryShapes(const std::vector<std::vector<int> >&, const int,
|
||||
std::vector<std::vector<int> >&,
|
||||
std::vector<std::vector<int> >&) const { return false; }
|
||||
void MyLayer::forward(std::vector<cv::Mat*>&, std::vector<cv::Mat>&, std::vector<cv::Mat>&) {}
|
||||
void MyLayer::finalize(const std::vector<cv::Mat*>&, std::vector<cv::Mat>&) {}
|
||||
void MyLayer::forward(cv::InputArrayOfArrays, cv::OutputArrayOfArrays, cv::OutputArrayOfArrays) {}
|
||||
#endif // __OPENCV_SAMPLES_DNN_CUSTOM_LAYERS__
|
||||
@@ -0,0 +1,169 @@
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <opencv2/highgui.hpp>
|
||||
#include <opencv2/dnn.hpp>
|
||||
|
||||
#include "custom_layers.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 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 decode(const Mat& scores, const Mat& geometry, float scoreThresh,
|
||||
std::vector<RotatedRect>& detections, std::vector<float>& confidences);
|
||||
|
||||
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");
|
||||
CV_Assert(parser.has("model"));
|
||||
String model = parser.get<String>("model");
|
||||
|
||||
// Register a custom layer.
|
||||
CV_DNN_REGISTER_LAYER_CLASS(ResizeBilinear, ResizeBilinearLayer);
|
||||
|
||||
// Load network.
|
||||
Net net = readNet(model);
|
||||
|
||||
// Open a video file or an image file or a camera stream.
|
||||
VideoCapture cap;
|
||||
if (parser.has("input"))
|
||||
cap.open(parser.get<String>("input"));
|
||||
else
|
||||
cap.open(0);
|
||||
|
||||
static const std::string kWinName = "EAST: An Efficient and Accurate Scene Text Detector";
|
||||
namedWindow(kWinName, WINDOW_NORMAL);
|
||||
|
||||
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;
|
||||
while (waitKey(1) < 0)
|
||||
{
|
||||
cap >> frame;
|
||||
if (frame.empty())
|
||||
{
|
||||
waitKey();
|
||||
break;
|
||||
}
|
||||
|
||||
blobFromImage(frame, blob, 1.0, Size(inpWidth, inpHeight), Scalar(123.68, 116.78, 103.94), true, false);
|
||||
net.setInput(blob);
|
||||
net.forward(outs, outNames);
|
||||
|
||||
Mat scores = outs[0];
|
||||
Mat geometry = outs[1];
|
||||
|
||||
// Decode predicted bounding boxes.
|
||||
std::vector<RotatedRect> boxes;
|
||||
std::vector<float> confidences;
|
||||
decode(scores, geometry, confThreshold, boxes, confidences);
|
||||
|
||||
// Apply non-maximum suppression procedure.
|
||||
std::vector<int> indices;
|
||||
NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);
|
||||
|
||||
// Render detections.
|
||||
Point2f ratio((float)frame.cols / inpWidth, (float)frame.rows / inpHeight);
|
||||
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;
|
||||
}
|
||||
for (int j = 0; j < 4; ++j)
|
||||
line(frame, vertices[j], vertices[(j + 1) % 4], Scalar(0, 255, 0), 1);
|
||||
}
|
||||
|
||||
// Put efficiency information.
|
||||
std::vector<double> layersTimes;
|
||||
double freq = getTickFrequency() / 1000;
|
||||
double t = net.getPerfProfile(layersTimes) / freq;
|
||||
std::string label = format("Inference time: %.2f ms", t);
|
||||
putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0));
|
||||
|
||||
imshow(kWinName, frame);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void decode(const Mat& scores, const Mat& geometry, float scoreThresh,
|
||||
std::vector<RotatedRect>& detections, std::vector<float>& confidences)
|
||||
{
|
||||
detections.clear();
|
||||
CV_Assert(scores.dims == 4, geometry.dims == 4, scores.size[0] == 1,
|
||||
geometry.size[0] == 1, scores.size[1] == 1, geometry.size[1] == 5,
|
||||
scores.size[2] == geometry.size[2], scores.size[3] == geometry.size[3]);
|
||||
|
||||
const int height = scores.size[2];
|
||||
const int width = scores.size[3];
|
||||
const int planeSize = height * width;
|
||||
|
||||
float* scoresData = (float*)scores.data;
|
||||
float* geometryData = (float*)geometry.data;
|
||||
float* x0_data = geometryData;
|
||||
float* x1_data = geometryData + planeSize;
|
||||
float* x2_data = geometryData + planeSize * 2;
|
||||
float* x3_data = geometryData + planeSize * 3;
|
||||
float* anglesData = geometryData + planeSize * 4;
|
||||
for (int y = 0; y < height; ++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);
|
||||
}
|
||||
scoresData += width;
|
||||
x0_data += width;
|
||||
x1_data += width;
|
||||
x2_data += width;
|
||||
x3_data += width;
|
||||
anglesData += width;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user