1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 15:53:03 +04:00

Merge pull request #17570 from HannibalAPE:text_det_recog_demo

[GSoC] High Level API and Samples for Scene Text Detection and Recognition

* APIs and samples for scene text detection and recognition

* update APIs and tutorial for Text Detection and Recognition

* API updates:
(1) put decodeType into struct Voc
(2) optimize the post-processing of DB

* sample update:
(1) add transformation into scene_text_spotting.cpp
(2) modify text_detection.cpp with API update

* update tutorial

* simplify text recognition API
update tutorial

* update impl usage in recognize() and detect()

* dnn: refactoring public API of TextRecognitionModel/TextDetectionModel

* update provided models
update opencv.bib

* dnn: adjust text rectangle angle

* remove points ordering operation in model.cpp

* update gts of DB test in test_model.cpp

* dnn: ensure to keep text rectangle angle

- avoid 90/180 degree turns

* dnn(text): use quadrangle result in TextDetectionModel API

* dnn: update Text Detection API
(1) keep points' order consistent with (bl, tl, tr, br) in unclip
(2) update contourScore with boundingRect
This commit is contained in:
Wenqing Zhang
2020-12-04 02:47:40 +08:00
committed by GitHub
parent 5ecf693774
commit 22d64ae08f
19 changed files with 2339 additions and 181 deletions
+249
View File
@@ -1326,6 +1326,255 @@ CV__DNN_INLINE_NS_BEGIN
float confThreshold = 0.5f, float nmsThreshold = 0.0f);
};
/** @brief This class represents high-level API for text recognition networks.
*
* TextRecognitionModel allows to set params for preprocessing input image.
* TextRecognitionModel creates net from file with trained weights and config,
* sets preprocessing input, runs forward pass and return recognition result.
* For TextRecognitionModel, CRNN-CTC is supported.
*/
class CV_EXPORTS_W_SIMPLE TextRecognitionModel : public Model
{
public:
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first)
TextRecognitionModel();
/**
* @brief Create Text Recognition model from deep learning network
* Call setDecodeType() and setVocabulary() after constructor to initialize the decoding method
* @param[in] network Net object
*/
CV_WRAP TextRecognitionModel(const Net& network);
/**
* @brief Create text recognition model from network represented in one of the supported formats
* Call setDecodeType() and setVocabulary() after constructor to initialize the decoding method
* @param[in] model Binary file contains trained weights
* @param[in] config Text file contains network configuration
*/
CV_WRAP inline
TextRecognitionModel(const std::string& model, const std::string& config = "")
: TextRecognitionModel(readNet(model, config)) { /* nothing */ }
/**
* @brief Set the decoding method of translating the network output into string
* @param[in] decodeType The decoding method of translating the network output into string: {'CTC-greedy': greedy decoding for the output of CTC-based methods}
*/
CV_WRAP
TextRecognitionModel& setDecodeType(const std::string& decodeType);
/**
* @brief Get the decoding method
* @return the decoding method
*/
CV_WRAP
const std::string& getDecodeType() const;
/**
* @brief Set the vocabulary for recognition.
* @param[in] vocabulary the associated vocabulary of the network.
*/
CV_WRAP
TextRecognitionModel& setVocabulary(const std::vector<std::string>& vocabulary);
/**
* @brief Get the vocabulary for recognition.
* @return vocabulary the associated vocabulary
*/
CV_WRAP
const std::vector<std::string>& getVocabulary() const;
/**
* @brief Given the @p input frame, create input blob, run net and return recognition result
* @param[in] frame The input image
* @return The text recognition result
*/
CV_WRAP
std::string recognize(InputArray frame) const;
/**
* @brief Given the @p input frame, create input blob, run net and return recognition result
* @param[in] frame The input image
* @param[in] roiRects List of text detection regions of interest (cv::Rect, CV_32SC4). ROIs is be cropped as the network inputs
* @param[out] results A set of text recognition results.
*/
CV_WRAP
void recognize(InputArray frame, InputArrayOfArrays roiRects, CV_OUT std::vector<std::string>& results) const;
};
/** @brief Base class for text detection networks
*/
class CV_EXPORTS_W TextDetectionModel : public Model
{
protected:
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first)
TextDetectionModel();
public:
/** @brief Performs detection
*
* Given the input @p frame, prepare network input, run network inference, post-process network output and return result detections.
*
* Each result is quadrangle's 4 points in this order:
* - bottom-left
* - top-left
* - top-right
* - bottom-right
*
* Use cv::getPerspectiveTransform function to retrive image region without perspective transformations.
*
* @note If DL model doesn't support that kind of output then result may be derived from detectTextRectangles() output.
*
* @param[in] frame The input image
* @param[out] detections array with detections' quadrangles (4 points per result)
* @param[out] confidences array with detection confidences
*/
CV_WRAP
void detect(
InputArray frame,
CV_OUT std::vector< std::vector<Point> >& detections,
CV_OUT std::vector<float>& confidences
) const;
/** @overload */
CV_WRAP
void detect(
InputArray frame,
CV_OUT std::vector< std::vector<Point> >& detections
) const;
/** @brief Performs detection
*
* Given the input @p frame, prepare network input, run network inference, post-process network output and return result detections.
*
* Each result is rotated rectangle.
*
* @note Result may be inaccurate in case of strong perspective transformations.
*
* @param[in] frame the input image
* @param[out] detections array with detections' RotationRect results
* @param[out] confidences array with detection confidences
*/
CV_WRAP
void detectTextRectangles(
InputArray frame,
CV_OUT std::vector<cv::RotatedRect>& detections,
CV_OUT std::vector<float>& confidences
) const;
/** @overload */
CV_WRAP
void detectTextRectangles(
InputArray frame,
CV_OUT std::vector<cv::RotatedRect>& detections
) const;
};
/** @brief This class represents high-level API for text detection DL networks compatible with EAST model.
*
* Configurable parameters:
* - (float) confThreshold - used to filter boxes by confidences, default: 0.5f
* - (float) nmsThreshold - used in non maximum suppression, default: 0.0f
*/
class CV_EXPORTS_W_SIMPLE TextDetectionModel_EAST : public TextDetectionModel
{
public:
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first)
TextDetectionModel_EAST();
/**
* @brief Create text detection algorithm from deep learning network
* @param[in] network Net object
*/
CV_WRAP TextDetectionModel_EAST(const Net& network);
/**
* @brief Create text detection model from network represented in one of the supported formats.
* An order of @p model and @p config arguments does not matter.
* @param[in] model Binary file contains trained weights.
* @param[in] config Text file contains network configuration.
*/
CV_WRAP inline
TextDetectionModel_EAST(const std::string& model, const std::string& config = "")
: TextDetectionModel_EAST(readNet(model, config)) { /* nothing */ }
/**
* @brief Set the detection confidence threshold
* @param[in] confThreshold A threshold used to filter boxes by confidences
*/
CV_WRAP
TextDetectionModel_EAST& setConfidenceThreshold(float confThreshold);
/**
* @brief Get the detection confidence threshold
*/
CV_WRAP
float getConfidenceThreshold() const;
/**
* @brief Set the detection NMS filter threshold
* @param[in] nmsThreshold A threshold used in non maximum suppression
*/
CV_WRAP
TextDetectionModel_EAST& setNMSThreshold(float nmsThreshold);
/**
* @brief Get the detection confidence threshold
*/
CV_WRAP
float getNMSThreshold() const;
};
/** @brief This class represents high-level API for text detection DL networks compatible with DB model.
*
* Related publications: @cite liao2020real
* Paper: https://arxiv.org/abs/1911.08947
* For more information about the hyper-parameters setting, please refer to https://github.com/MhLiao/DB
*
* Configurable parameters:
* - (float) binaryThreshold - The threshold of the binary map. It is usually set to 0.3.
* - (float) polygonThreshold - The threshold of text polygons. It is usually set to 0.5, 0.6, and 0.7. Default is 0.5f
* - (double) unclipRatio - The unclip ratio of the detected text region, which determines the output size. It is usually set to 2.0.
* - (int) maxCandidates - The max number of the output results.
*/
class CV_EXPORTS_W_SIMPLE TextDetectionModel_DB : public TextDetectionModel
{
public:
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first)
TextDetectionModel_DB();
/**
* @brief Create text detection algorithm from deep learning network.
* @param[in] network Net object.
*/
CV_WRAP TextDetectionModel_DB(const Net& network);
/**
* @brief Create text detection model from network represented in one of the supported formats.
* An order of @p model and @p config arguments does not matter.
* @param[in] model Binary file contains trained weights.
* @param[in] config Text file contains network configuration.
*/
CV_WRAP inline
TextDetectionModel_DB(const std::string& model, const std::string& config = "")
: TextDetectionModel_DB(readNet(model, config)) { /* nothing */ }
CV_WRAP TextDetectionModel_DB& setBinaryThreshold(float binaryThreshold);
CV_WRAP float getBinaryThreshold() const;
CV_WRAP TextDetectionModel_DB& setPolygonThreshold(float polygonThreshold);
CV_WRAP float getPolygonThreshold() const;
CV_WRAP TextDetectionModel_DB& setUnclipRatio(double unclipRatio);
CV_WRAP double getUnclipRatio() const;
CV_WRAP TextDetectionModel_DB& setMaxCandidates(int maxCandidates);
CV_WRAP int getMaxCandidates() const;
};
//! @}
CV__DNN_INLINE_NS_END
}
+778 -2
View File
@@ -4,7 +4,6 @@
#include "precomp.hpp"
#include <algorithm>
#include <iostream>
#include <utility>
#include <iterator>
@@ -37,9 +36,10 @@ public:
virtual void setPreferableBackend(Backend backendId) { net.setPreferableBackend(backendId); }
virtual void setPreferableTarget(Target targetId) { net.setPreferableTarget(targetId); }
/*virtual*/
virtual
void initNet(const Net& network)
{
CV_TRACE_FUNCTION();
net = network;
outNames = net.getUnconnectedOutLayersNames();
@@ -91,6 +91,7 @@ public:
/*virtual*/
void processFrame(InputArray frame, OutputArrayOfArrays outs)
{
CV_TRACE_FUNCTION();
if (size.empty())
CV_Error(Error::StsBadSize, "Input size not specified");
@@ -103,6 +104,7 @@ public:
Mat imInfo(Matx13f(size.height, size.width, 1.6f));
net.setInput(imInfo, "im_info");
}
net.forward(outs, outNames);
}
};
@@ -545,4 +547,778 @@ void DetectionModel::detect(InputArray frame, CV_OUT std::vector<int>& classIds,
CV_Error(Error::StsNotImplemented, "Unknown output layer type: \"" + lastLayer->type + "\"");
}
struct TextRecognitionModel_Impl : public Model::Impl
{
std::string decodeType;
std::vector<std::string> vocabulary;
TextRecognitionModel_Impl()
{
CV_TRACE_FUNCTION();
}
TextRecognitionModel_Impl(const Net& network)
{
CV_TRACE_FUNCTION();
initNet(network);
}
inline
void setVocabulary(const std::vector<std::string>& inputVoc)
{
vocabulary = inputVoc;
}
inline
void setDecodeType(const std::string& type)
{
decodeType = type;
}
virtual
std::string decode(const Mat& prediction)
{
CV_TRACE_FUNCTION();
CV_Assert(!prediction.empty());
if (decodeType.empty())
CV_Error(Error::StsBadArg, "TextRecognitionModel: decodeType is not specified");
if (vocabulary.empty())
CV_Error(Error::StsBadArg, "TextRecognitionModel: vocabulary is not specified");
std::string decodeSeq;
if (decodeType == "CTC-greedy")
{
CV_CheckEQ(prediction.dims, 3, "");
CV_CheckType(prediction.type(), CV_32FC1, "");
const int vocLength = (int)(vocabulary.size());
CV_CheckLE(prediction.size[1], vocLength, "");
bool ctcFlag = true;
int lastLoc = 0;
for (int i = 0; i < prediction.size[0]; i++)
{
const float* pred = prediction.ptr<float>(i);
int maxLoc = 0;
float maxScore = pred[0];
for (int j = 1; j < vocLength + 1; j++)
{
float score = pred[j];
if (maxScore < score)
{
maxScore = score;
maxLoc = j;
}
}
if (maxLoc > 0)
{
std::string currentChar = vocabulary.at(maxLoc - 1);
if (maxLoc != lastLoc || ctcFlag)
{
lastLoc = maxLoc;
decodeSeq += currentChar;
ctcFlag = false;
}
}
else
{
ctcFlag = true;
}
}
} else if (decodeType.length() == 0) {
CV_Error(Error::StsBadArg, "Please set decodeType");
} else {
CV_Error_(Error::StsBadArg, ("Unsupported decodeType: %s", decodeType.c_str()));
}
return decodeSeq;
}
virtual
std::string recognize(InputArray frame)
{
CV_TRACE_FUNCTION();
std::vector<Mat> outs;
processFrame(frame, outs);
CV_CheckEQ(outs.size(), (size_t)1, "");
return decode(outs[0]);
}
virtual
void recognize(InputArray frame, InputArrayOfArrays roiRects, CV_OUT std::vector<std::string>& results)
{
CV_TRACE_FUNCTION();
results.clear();
if (roiRects.empty())
{
auto s = recognize(frame);
results.push_back(s);
return;
}
std::vector<Rect> rects;
roiRects.copyTo(rects);
// Predict for each RoI
Mat input = frame.getMat();
for (size_t i = 0; i < rects.size(); i++)
{
Rect roiRect = rects[i];
Mat roi = input(roiRect);
auto s = recognize(roi);
results.push_back(s);
}
}
static inline
TextRecognitionModel_Impl& from(const std::shared_ptr<Model::Impl>& ptr)
{
CV_Assert(ptr);
return *((TextRecognitionModel_Impl*)ptr.get());
}
};
TextRecognitionModel::TextRecognitionModel()
{
impl = std::static_pointer_cast<Model::Impl>(makePtr<TextRecognitionModel_Impl>());
}
TextRecognitionModel::TextRecognitionModel(const Net& network)
{
impl = std::static_pointer_cast<Model::Impl>(std::make_shared<TextRecognitionModel_Impl>(network));
}
TextRecognitionModel& TextRecognitionModel::setDecodeType(const std::string& decodeType)
{
TextRecognitionModel_Impl::from(impl).setDecodeType(decodeType);
return *this;
}
const std::string& TextRecognitionModel::getDecodeType() const
{
return TextRecognitionModel_Impl::from(impl).decodeType;
}
TextRecognitionModel& TextRecognitionModel::setVocabulary(const std::vector<std::string>& inputVoc)
{
TextRecognitionModel_Impl::from(impl).setVocabulary(inputVoc);
return *this;
}
const std::vector<std::string>& TextRecognitionModel::getVocabulary() const
{
return TextRecognitionModel_Impl::from(impl).vocabulary;
}
std::string TextRecognitionModel::recognize(InputArray frame) const
{
return TextRecognitionModel_Impl::from(impl).recognize(frame);
}
void TextRecognitionModel::recognize(InputArray frame, InputArrayOfArrays roiRects, CV_OUT std::vector<std::string>& results) const
{
TextRecognitionModel_Impl::from(impl).recognize(frame, roiRects, results);
}
///////////////////////////////////////// Text Detection /////////////////////////////////////////
struct TextDetectionModel_Impl : public Model::Impl
{
TextDetectionModel_Impl() {}
TextDetectionModel_Impl(const Net& network)
{
CV_TRACE_FUNCTION();
initNet(network);
}
virtual
std::vector< std::vector<Point2f> > detect(InputArray frame, CV_OUT std::vector<float>& confidences)
{
CV_TRACE_FUNCTION();
std::vector<RotatedRect> rects = detectTextRectangles(frame, confidences);
std::vector< std::vector<Point2f> > results;
for (const RotatedRect& rect : rects)
{
Point2f vertices[4] = {};
rect.points(vertices);
std::vector<Point2f> result = { vertices[0], vertices[1], vertices[2], vertices[3] };
results.emplace_back(result);
}
return results;
}
virtual
std::vector< std::vector<Point2f> > detect(InputArray frame)
{
CV_TRACE_FUNCTION();
std::vector<float> confidences;
return detect(frame, confidences);
}
virtual
std::vector<RotatedRect> detectTextRectangles(InputArray frame, CV_OUT std::vector<float>& confidences)
{
CV_Error(Error::StsNotImplemented, "");
}
virtual
std::vector<cv::RotatedRect> detectTextRectangles(InputArray frame)
{
CV_TRACE_FUNCTION();
std::vector<float> confidences;
return detectTextRectangles(frame, confidences);
}
static inline
TextDetectionModel_Impl& from(const std::shared_ptr<Model::Impl>& ptr)
{
CV_Assert(ptr);
return *((TextDetectionModel_Impl*)ptr.get());
}
};
TextDetectionModel::TextDetectionModel()
: Model()
{
// nothing
}
static
void to32s(
const std::vector< std::vector<Point2f> >& detections_f,
CV_OUT std::vector< std::vector<Point> >& detections
)
{
detections.resize(detections_f.size());
for (size_t i = 0; i < detections_f.size(); i++)
{
const auto& contour_f = detections_f[i];
std::vector<Point> contour(contour_f.size());
for (size_t j = 0; j < contour_f.size(); j++)
{
contour[j].x = cvRound(contour_f[j].x);
contour[j].y = cvRound(contour_f[j].y);
}
swap(detections[i], contour);
}
}
void TextDetectionModel::detect(
InputArray frame,
CV_OUT std::vector< std::vector<Point> >& detections,
CV_OUT std::vector<float>& confidences
) const
{
std::vector< std::vector<Point2f> > detections_f = TextDetectionModel_Impl::from(impl).detect(frame, confidences);
to32s(detections_f, detections);
return;
}
void TextDetectionModel::detect(
InputArray frame,
CV_OUT std::vector< std::vector<Point> >& detections
) const
{
std::vector< std::vector<Point2f> > detections_f = TextDetectionModel_Impl::from(impl).detect(frame);
to32s(detections_f, detections);
return;
}
void TextDetectionModel::detectTextRectangles(
InputArray frame,
CV_OUT std::vector<cv::RotatedRect>& detections,
CV_OUT std::vector<float>& confidences
) const
{
detections = TextDetectionModel_Impl::from(impl).detectTextRectangles(frame, confidences);
return;
}
void TextDetectionModel::detectTextRectangles(
InputArray frame,
CV_OUT std::vector<cv::RotatedRect>& detections
) const
{
detections = TextDetectionModel_Impl::from(impl).detectTextRectangles(frame);
return;
}
struct TextDetectionModel_EAST_Impl : public TextDetectionModel_Impl
{
float confThreshold;
float nmsThreshold;
TextDetectionModel_EAST_Impl()
: confThreshold(0.5f)
, nmsThreshold(0.0f)
{
CV_TRACE_FUNCTION();
}
TextDetectionModel_EAST_Impl(const Net& network)
: TextDetectionModel_EAST_Impl()
{
CV_TRACE_FUNCTION();
initNet(network);
}
void setConfidenceThreshold(float confThreshold_) { confThreshold = confThreshold_; }
float getConfidenceThreshold() const { return confThreshold; }
void setNMSThreshold(float nmsThreshold_) { nmsThreshold = nmsThreshold_; }
float getNMSThreshold() const { return nmsThreshold; }
// TODO: According to article EAST supports quadrangles output: https://arxiv.org/pdf/1704.03155.pdf
#if 0
virtual
std::vector< std::vector<Point2f> > detect(InputArray frame, CV_OUT std::vector<float>& confidences) CV_OVERRIDE
#endif
virtual
std::vector<cv::RotatedRect> detectTextRectangles(InputArray frame, CV_OUT std::vector<float>& confidences) CV_OVERRIDE
{
CV_TRACE_FUNCTION();
std::vector<cv::RotatedRect> results;
std::vector<Mat> outs;
processFrame(frame, outs);
CV_CheckEQ(outs.size(), (size_t)2, "");
Mat geometry = outs[0];
Mat scoreMap = outs[1];
CV_CheckEQ(scoreMap.dims, 4, "");
CV_CheckEQ(geometry.dims, 4, "");
CV_CheckEQ(scoreMap.size[0], 1, "");
CV_CheckEQ(geometry.size[0], 1, "");
CV_CheckEQ(scoreMap.size[1], 1, "");
CV_CheckEQ(geometry.size[1], 5, "");
CV_CheckEQ(scoreMap.size[2], geometry.size[2], "");
CV_CheckEQ(scoreMap.size[3], geometry.size[3], "");
CV_CheckType(scoreMap.type(), CV_32FC1, "");
CV_CheckType(geometry.type(), CV_32FC1, "");
std::vector<RotatedRect> boxes;
std::vector<float> scores;
const int height = scoreMap.size[2];
const int width = scoreMap.size[3];
for (int y = 0; y < height; ++y)
{
const float* scoresData = scoreMap.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 < confThreshold)
continue;
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;
boxes.push_back(RotatedRect(0.5f * (p1 + p3), Size2f(w, h), -angle * 180.0f / (float)CV_PI));
scores.push_back(score);
}
}
// Apply non-maximum suppression procedure.
std::vector<int> indices;
NMSBoxes(boxes, scores, confThreshold, nmsThreshold, indices);
confidences.clear();
confidences.reserve(indices.size());
// Re-scale
Point2f ratio((float)frame.cols() / size.width, (float)frame.rows() / size.height);
bool isUniformRatio = std::fabs(ratio.x - ratio.y) <= 0.01f;
for (uint i = 0; i < indices.size(); i++)
{
auto idx = indices[i];
auto conf = scores[idx];
confidences.push_back(conf);
RotatedRect& box0 = boxes[idx];
if (isUniformRatio)
{
RotatedRect box = box0;
box.center.x *= ratio.x;
box.center.y *= ratio.y;
box.size.width *= ratio.x;
box.size.height *= ratio.y;
results.emplace_back(box);
}
else
{
Point2f vertices[4] = {};
box0.points(vertices);
for (int j = 0; j < 4; j++)
{
vertices[j].x *= ratio.x;
vertices[j].y *= ratio.y;
}
RotatedRect box = minAreaRect(Mat(4, 1, CV_32FC2, (void*)vertices));
// minArea() rect is not normalized, it may return rectangles rotated by +90/-90
float angle_diff = std::fabs(box.angle - box0.angle);
while (angle_diff >= (90 + 45))
{
box.angle += (box.angle < box0.angle) ? 180 : -180;
angle_diff = std::fabs(box.angle - box0.angle);
}
if (angle_diff > 45) // avoid ~90 degree turns
{
std::swap(box.size.width, box.size.height);
if (box.angle < box0.angle)
box.angle += 90;
else if (box.angle > box0.angle)
box.angle -= 90;
}
// CV_DbgAssert(std::fabs(box.angle - box0.angle) <= 45);
results.emplace_back(box);
}
}
return results;
}
static inline
TextDetectionModel_EAST_Impl& from(const std::shared_ptr<Model::Impl>& ptr)
{
CV_Assert(ptr);
return *((TextDetectionModel_EAST_Impl*)ptr.get());
}
};
TextDetectionModel_EAST::TextDetectionModel_EAST()
: TextDetectionModel()
{
impl = std::static_pointer_cast<Model::Impl>(makePtr<TextDetectionModel_EAST_Impl>());
}
TextDetectionModel_EAST::TextDetectionModel_EAST(const Net& network)
: TextDetectionModel()
{
impl = std::static_pointer_cast<Model::Impl>(makePtr<TextDetectionModel_EAST_Impl>(network));
}
TextDetectionModel_EAST& TextDetectionModel_EAST::setConfidenceThreshold(float confThreshold)
{
TextDetectionModel_EAST_Impl::from(impl).setConfidenceThreshold(confThreshold);
return *this;
}
float TextDetectionModel_EAST::getConfidenceThreshold() const
{
return TextDetectionModel_EAST_Impl::from(impl).getConfidenceThreshold();
}
TextDetectionModel_EAST& TextDetectionModel_EAST::setNMSThreshold(float nmsThreshold)
{
TextDetectionModel_EAST_Impl::from(impl).setNMSThreshold(nmsThreshold);
return *this;
}
float TextDetectionModel_EAST::getNMSThreshold() const
{
return TextDetectionModel_EAST_Impl::from(impl).getNMSThreshold();
}
struct TextDetectionModel_DB_Impl : public TextDetectionModel_Impl
{
float binaryThreshold;
float polygonThreshold;
double unclipRatio;
int maxCandidates;
TextDetectionModel_DB_Impl()
: binaryThreshold(0.3f)
, polygonThreshold(0.5f)
, unclipRatio(2.0f)
, maxCandidates(0)
{
CV_TRACE_FUNCTION();
}
TextDetectionModel_DB_Impl(const Net& network)
: TextDetectionModel_DB_Impl()
{
CV_TRACE_FUNCTION();
initNet(network);
}
void setBinaryThreshold(float binaryThreshold_) { binaryThreshold = binaryThreshold_; }
float getBinaryThreshold() const { return binaryThreshold; }
void setPolygonThreshold(float polygonThreshold_) { polygonThreshold = polygonThreshold_; }
float getPolygonThreshold() const { return polygonThreshold; }
void setUnclipRatio(double unclipRatio_) { unclipRatio = unclipRatio_; }
double getUnclipRatio() const { return unclipRatio; }
void setMaxCandidates(int maxCandidates_) { maxCandidates = maxCandidates_; }
int getMaxCandidates() const { return maxCandidates; }
virtual
std::vector<cv::RotatedRect> detectTextRectangles(InputArray frame, CV_OUT std::vector<float>& confidences) CV_OVERRIDE
{
CV_TRACE_FUNCTION();
std::vector< std::vector<Point2f> > contours = detect(frame, confidences);
std::vector<cv::RotatedRect> results; results.reserve(contours.size());
for (size_t i = 0; i < contours.size(); i++)
{
auto& contour = contours[i];
RotatedRect box = minAreaRect(contour);
// minArea() rect is not normalized, it may return rectangles with angle=-90 or height < width
const float angle_threshold = 60; // do not expect vertical text, TODO detection algo property
bool swap_size = false;
if (box.size.width < box.size.height) // horizontal-wide text area is expected
swap_size = true;
else if (std::fabs(box.angle) >= angle_threshold) // don't work with vertical rectangles
swap_size = true;
if (swap_size)
{
std::swap(box.size.width, box.size.height);
if (box.angle < 0)
box.angle += 90;
else if (box.angle > 0)
box.angle -= 90;
}
results.push_back(box);
}
return results;
}
std::vector< std::vector<Point2f> > detect(InputArray frame, CV_OUT std::vector<float>& confidences) CV_OVERRIDE
{
CV_TRACE_FUNCTION();
std::vector< std::vector<Point2f> > results;
std::vector<Mat> outs;
processFrame(frame, outs);
CV_Assert(outs.size() == 1);
Mat binary = outs[0];
// Threshold
Mat bitmap;
threshold(binary, bitmap, binaryThreshold, 255, THRESH_BINARY);
// Scale ratio
float scaleHeight = (float)(frame.rows()) / (float)(binary.size[0]);
float scaleWidth = (float)(frame.cols()) / (float)(binary.size[1]);
// Find contours
std::vector< std::vector<Point> > contours;
bitmap.convertTo(bitmap, CV_8UC1);
findContours(bitmap, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
// Candidate number limitation
size_t numCandidate = std::min(contours.size(), (size_t)(maxCandidates > 0 ? maxCandidates : INT_MAX));
for (size_t i = 0; i < numCandidate; i++)
{
std::vector<Point>& contour = contours[i];
// Calculate text contour score
if (contourScore(binary, contour) < polygonThreshold)
continue;
// Rescale
std::vector<Point> contourScaled; contourScaled.reserve(contour.size());
for (size_t j = 0; j < contour.size(); j++)
{
contourScaled.push_back(Point(int(contour[j].x * scaleWidth),
int(contour[j].y * scaleHeight)));
}
// Unclip
RotatedRect box = minAreaRect(contourScaled);
// minArea() rect is not normalized, it may return rectangles with angle=-90 or height < width
const float angle_threshold = 60; // do not expect vertical text, TODO detection algo property
bool swap_size = false;
if (box.size.width < box.size.height) // horizontal-wide text area is expected
swap_size = true;
else if (std::fabs(box.angle) >= angle_threshold) // don't work with vertical rectangles
swap_size = true;
if (swap_size)
{
std::swap(box.size.width, box.size.height);
if (box.angle < 0)
box.angle += 90;
else if (box.angle > 0)
box.angle -= 90;
}
Point2f vertex[4];
box.points(vertex); // order: bl, tl, tr, br
std::vector<Point2f> approx;
for (int j = 0; j < 4; j++)
approx.emplace_back(vertex[j]);
std::vector<Point2f> polygon;
unclip(approx, polygon, unclipRatio);
results.push_back(polygon);
}
confidences = std::vector<float>(contours.size(), 1.0f);
return results;
}
// According to https://github.com/MhLiao/DB/blob/master/structure/representers/seg_detector_representer.py (2020-10)
static double contourScore(const Mat& binary, const std::vector<Point>& contour)
{
Rect rect = boundingRect(contour);
int xmin = std::max(rect.x, 0);
int xmax = std::min(rect.x + rect.width, binary.cols - 1);
int ymin = std::max(rect.y, 0);
int ymax = std::min(rect.y + rect.height, binary.rows - 1);
Mat binROI = binary(Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1));
Mat mask = Mat::zeros(ymax - ymin + 1, xmax - xmin + 1, CV_8U);
std::vector<Point> roiContour;
for (size_t i = 0; i < contour.size(); i++) {
Point pt = Point(contour[i].x - xmin, contour[i].y - ymin);
roiContour.push_back(pt);
}
std::vector<std::vector<Point>> roiContours = {roiContour};
fillPoly(mask, roiContours, Scalar(1));
double score = cv::mean(binROI, mask).val[0];
return score;
}
// According to https://github.com/MhLiao/DB/blob/master/structure/representers/seg_detector_representer.py (2020-10)
static void unclip(const std::vector<Point2f>& inPoly, std::vector<Point2f> &outPoly, const double unclipRatio)
{
double area = contourArea(inPoly);
double length = arcLength(inPoly, true);
double distance = area * unclipRatio / length;
size_t numPoints = inPoly.size();
std::vector<std::vector<Point2f>> newLines;
for (size_t i = 0; i < numPoints; i++) {
std::vector<Point2f> newLine;
Point pt1 = inPoly[i];
Point pt2 = inPoly[(i - 1) % numPoints];
Point vec = pt1 - pt2;
float unclipDis = (float)(distance / norm(vec));
Point2f rotateVec = Point2f(vec.y * unclipDis, -vec.x * unclipDis);
newLine.push_back(Point2f(pt1.x + rotateVec.x, pt1.y + rotateVec.y));
newLine.push_back(Point2f(pt2.x + rotateVec.x, pt2.y + rotateVec.y));
newLines.push_back(newLine);
}
size_t numLines = newLines.size();
for (size_t i = 0; i < numLines; i++) {
Point2f a = newLines[i][0];
Point2f b = newLines[i][1];
Point2f c = newLines[(i + 1) % numLines][0];
Point2f d = newLines[(i + 1) % numLines][1];
Point2f pt;
Point2f v1 = b - a;
Point2f v2 = d - c;
double cosAngle = (v1.x * v2.x + v1.y * v2.y) / (norm(v1) * norm(v2));
if( fabs(cosAngle) > 0.7 ) {
pt.x = (b.x + c.x) * 0.5;
pt.y = (b.y + c.y) * 0.5;
} else {
double denom = a.x * (double)(d.y - c.y) + b.x * (double)(c.y - d.y) +
d.x * (double)(b.y - a.y) + c.x * (double)(a.y - b.y);
double num = a.x * (double)(d.y - c.y) + c.x * (double)(a.y - d.y) + d.x * (double)(c.y - a.y);
double s = num / denom;
pt.x = a.x + s*(b.x - a.x);
pt.y = a.y + s*(b.y - a.y);
}
outPoly.push_back(pt);
}
}
static inline
TextDetectionModel_DB_Impl& from(const std::shared_ptr<Model::Impl>& ptr)
{
CV_Assert(ptr);
return *((TextDetectionModel_DB_Impl*)ptr.get());
}
};
TextDetectionModel_DB::TextDetectionModel_DB()
: TextDetectionModel()
{
impl = std::static_pointer_cast<Model::Impl>(makePtr<TextDetectionModel_DB_Impl>());
}
TextDetectionModel_DB::TextDetectionModel_DB(const Net& network)
: TextDetectionModel()
{
impl = std::static_pointer_cast<Model::Impl>(makePtr<TextDetectionModel_DB_Impl>(network));
}
TextDetectionModel_DB& TextDetectionModel_DB::setBinaryThreshold(float binaryThreshold)
{
TextDetectionModel_DB_Impl::from(impl).setBinaryThreshold(binaryThreshold);
return *this;
}
float TextDetectionModel_DB::getBinaryThreshold() const
{
return TextDetectionModel_DB_Impl::from(impl).getBinaryThreshold();
}
TextDetectionModel_DB& TextDetectionModel_DB::setPolygonThreshold(float polygonThreshold)
{
TextDetectionModel_DB_Impl::from(impl).setPolygonThreshold(polygonThreshold);
return *this;
}
float TextDetectionModel_DB::getPolygonThreshold() const
{
return TextDetectionModel_DB_Impl::from(impl).getPolygonThreshold();
}
TextDetectionModel_DB& TextDetectionModel_DB::setUnclipRatio(double unclipRatio)
{
TextDetectionModel_DB_Impl::from(impl).setUnclipRatio(unclipRatio);
return *this;
}
double TextDetectionModel_DB::getUnclipRatio() const
{
return TextDetectionModel_DB_Impl::from(impl).getUnclipRatio();
}
TextDetectionModel_DB& TextDetectionModel_DB::setMaxCandidates(int maxCandidates)
{
TextDetectionModel_DB_Impl::from(impl).setMaxCandidates(maxCandidates);
return *this;
}
int TextDetectionModel_DB::getMaxCandidates() const
{
return TextDetectionModel_DB_Impl::from(impl).getMaxCandidates();
}
}} // namespace
+8
View File
@@ -113,6 +113,14 @@ void normAssertDetections(
double confThreshold = 0.0, double scores_diff = 1e-5,
double boxes_iou_diff = 1e-4);
// For text detection networks
// Curved text polygon is not supported in the current version.
// (concave polygon is invalid input to intersectConvexConvex)
void normAssertTextDetections(
const std::vector<std::vector<Point>>& gtPolys,
const std::vector<std::vector<Point>>& testPolys,
const char *comment = "", double boxes_iou_diff = 1e-4);
void readFileContent(const std::string& filename, CV_OUT std::vector<char>& content);
#ifdef HAVE_INF_ENGINE
+46
View File
@@ -177,6 +177,52 @@ void normAssertDetections(
testBoxes, comment, confThreshold, scores_diff, boxes_iou_diff);
}
// For text detection networks
// Curved text polygon is not supported in the current version.
// (concave polygon is invalid input to intersectConvexConvex)
void normAssertTextDetections(
const std::vector<std::vector<Point>>& gtPolys,
const std::vector<std::vector<Point>>& testPolys,
const char *comment /*= ""*/, double boxes_iou_diff /*= 1e-4*/)
{
std::vector<bool> matchedRefBoxes(gtPolys.size(), false);
for (uint i = 0; i < testPolys.size(); ++i)
{
const std::vector<Point>& testPoly = testPolys[i];
bool matched = false;
double topIoU = 0;
for (uint j = 0; j < gtPolys.size() && !matched; ++j)
{
if (!matchedRefBoxes[j])
{
std::vector<Point> intersectionPolygon;
float intersectArea = intersectConvexConvex(testPoly, gtPolys[j], intersectionPolygon, true);
double iou = intersectArea / (contourArea(testPoly) + contourArea(gtPolys[j]) - intersectArea);
topIoU = std::max(topIoU, iou);
if (1.0 - iou < boxes_iou_diff)
{
matched = true;
matchedRefBoxes[j] = true;
}
}
}
if (!matched) {
std::cout << cv::format("Unmatched-det:") << testPoly << std::endl;
std::cout << "Highest IoU: " << topIoU << std::endl;
}
EXPECT_TRUE(matched) << comment;
}
// Check unmatched groundtruth.
for (uint i = 0; i < gtPolys.size(); ++i)
{
if (!matchedRefBoxes[i]) {
std::cout << cv::format("Unmatched-gt:") << gtPolys[i] << std::endl;
}
EXPECT_TRUE(matchedRefBoxes[i]);
}
}
void readFileContent(const std::string& filename, CV_OUT std::vector<char>& content)
{
const std::ios::openmode mode = std::ios::in | std::ios::binary;
+220
View File
@@ -113,6 +113,155 @@ public:
model.segment(frame, mask);
normAssert(mask, exp, "", norm, norm);
}
void testTextRecognitionModel(const std::string& weights, const std::string& cfg,
const std::string& imgPath, const std::string& seq,
const std::string& decodeType, const std::vector<std::string>& vocabulary,
const Size& size = {-1, -1}, Scalar mean = Scalar(),
double scale = 1.0, bool swapRB = false, bool crop = false)
{
checkBackend();
Mat frame = imread(imgPath, IMREAD_GRAYSCALE);
TextRecognitionModel model(weights, cfg);
model.setDecodeType(decodeType)
.setVocabulary(vocabulary)
.setInputSize(size).setInputMean(mean).setInputScale(scale)
.setInputSwapRB(swapRB).setInputCrop(crop);
model.setPreferableBackend(backend);
model.setPreferableTarget(target);
std::string result = model.recognize(frame);
EXPECT_EQ(result, seq) << "Full frame: " << imgPath;
std::vector<Rect> rois;
rois.push_back(Rect(0, 0, frame.cols, frame.rows));
rois.push_back(Rect(0, 0, frame.cols, frame.rows)); // twice
std::vector<std::string> results;
model.recognize(frame, rois, results);
EXPECT_EQ((size_t)2u, results.size()) << "ROI: " << imgPath;
EXPECT_EQ(results[0], seq) << "ROI[0]: " << imgPath;
EXPECT_EQ(results[1], seq) << "ROI[1]: " << imgPath;
}
void testTextDetectionModelByDB(const std::string& weights, const std::string& cfg,
const std::string& imgPath, const std::vector<std::vector<Point>>& gt,
float binThresh, float polyThresh,
uint maxCandidates, double unclipRatio,
const Size& size = {-1, -1}, Scalar mean = Scalar(),
double scale = 1.0, bool swapRB = false, bool crop = false)
{
checkBackend();
Mat frame = imread(imgPath);
TextDetectionModel_DB model(weights, cfg);
model.setBinaryThreshold(binThresh)
.setPolygonThreshold(polyThresh)
.setUnclipRatio(unclipRatio)
.setMaxCandidates(maxCandidates)
.setInputSize(size).setInputMean(mean).setInputScale(scale)
.setInputSwapRB(swapRB).setInputCrop(crop);
model.setPreferableBackend(backend);
model.setPreferableTarget(target);
// 1. Check common TextDetectionModel API through RotatedRect
std::vector<cv::RotatedRect> results;
model.detectTextRectangles(frame, results);
EXPECT_GT(results.size(), (size_t)0);
std::vector< std::vector<Point> > contours;
for (size_t i = 0; i < results.size(); i++)
{
const RotatedRect& box = results[i];
Mat contour;
boxPoints(box, contour);
std::vector<Point> contour2i(4);
for (int i = 0; i < 4; i++)
{
contour2i[i].x = cvRound(contour.at<float>(i, 0));
contour2i[i].y = cvRound(contour.at<float>(i, 1));
}
contours.push_back(contour2i);
}
#if 0 // test debug
Mat result = frame.clone();
drawContours(result, contours, -1, Scalar(0, 0, 255), 1);
imshow("result", result); // imwrite("result.png", result);
waitKey(0);
#endif
normAssertTextDetections(gt, contours, "", 0.05f);
// 2. Check quadrangle-based API
// std::vector< std::vector<Point> > contours;
model.detect(frame, contours);
#if 0 // test debug
Mat result = frame.clone();
drawContours(result, contours, -1, Scalar(0, 0, 255), 1);
imshow("result_contours", result); // imwrite("result_contours.png", result);
waitKey(0);
#endif
normAssertTextDetections(gt, contours, "", 0.05f);
}
void testTextDetectionModelByEAST(const std::string& weights, const std::string& cfg,
const std::string& imgPath, const std::vector<RotatedRect>& gt,
float confThresh, float nmsThresh,
const Size& size = {-1, -1}, Scalar mean = Scalar(),
double scale = 1.0, bool swapRB = false, bool crop = false)
{
const double EPS_PIXELS = 3;
checkBackend();
Mat frame = imread(imgPath);
TextDetectionModel_EAST model(weights, cfg);
model.setConfidenceThreshold(confThresh)
.setNMSThreshold(nmsThresh)
.setInputSize(size).setInputMean(mean).setInputScale(scale)
.setInputSwapRB(swapRB).setInputCrop(crop);
model.setPreferableBackend(backend);
model.setPreferableTarget(target);
std::vector<cv::RotatedRect> results;
model.detectTextRectangles(frame, results);
EXPECT_EQ(results.size(), (size_t)1);
for (size_t i = 0; i < results.size(); i++)
{
const RotatedRect& box = results[i];
#if 0 // test debug
Mat contour;
boxPoints(box, contour);
std::vector<Point> contour2i(4);
for (int i = 0; i < 4; i++)
{
contour2i[i].x = cvRound(contour.at<float>(i, 0));
contour2i[i].y = cvRound(contour.at<float>(i, 1));
}
std::vector< std::vector<Point> > contours;
contours.push_back(contour2i);
Mat result = frame.clone();
drawContours(result, contours, -1, Scalar(0, 0, 255), 1);
imshow("result", result); //imwrite("result.png", result);
waitKey(0);
#endif
const RotatedRect& gtBox = gt[i];
EXPECT_NEAR(box.center.x, gtBox.center.x, EPS_PIXELS);
EXPECT_NEAR(box.center.y, gtBox.center.y, EPS_PIXELS);
EXPECT_NEAR(box.size.width, gtBox.size.width, EPS_PIXELS);
EXPECT_NEAR(box.size.height, gtBox.size.height, EPS_PIXELS);
EXPECT_NEAR(box.angle, gtBox.angle, 1);
}
}
};
TEST_P(Test_Model, Classify)
@@ -446,6 +595,77 @@ TEST_P(Test_Model, Segmentation)
testSegmentationModel(weights_file, config_file, inp, exp, norm, size, mean, scale, swapRB);
}
TEST_P(Test_Model, TextRecognition)
{
if (target == DNN_TARGET_OPENCL_FP16)
applyTestTag(CV_TEST_TAG_DNN_SKIP_OPENCL_FP16);
std::string imgPath = _tf("text_rec_test.png");
std::string weightPath = _tf("onnx/models/crnn.onnx", false);
std::string seq = "welcome";
Size size{100, 32};
double scale = 1.0 / 127.5;
Scalar mean = Scalar(127.5);
std::string decodeType = "CTC-greedy";
std::vector<std::string> vocabulary = {"0","1","2","3","4","5","6","7","8","9",
"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"};
testTextRecognitionModel(weightPath, "", imgPath, seq, decodeType, vocabulary, size, mean, scale);
}
TEST_P(Test_Model, TextDetectionByDB)
{
if (target == DNN_TARGET_OPENCL_FP16)
applyTestTag(CV_TEST_TAG_DNN_SKIP_OPENCL_FP16);
std::string imgPath = _tf("text_det_test1.png");
std::string weightPath = _tf("onnx/models/DB_TD500_resnet50.onnx", false);
// GroundTruth
std::vector<std::vector<Point>> gt = {
{ Point(142, 193), Point(136, 164), Point(213, 150), Point(219, 178) },
{ Point(136, 165), Point(122, 114), Point(319, 71), Point(330, 122) }
};
Size size{736, 736};
double scale = 1.0 / 255.0;
Scalar mean = Scalar(122.67891434, 116.66876762, 104.00698793);
float binThresh = 0.3;
float polyThresh = 0.5;
uint maxCandidates = 200;
double unclipRatio = 2.0;
testTextDetectionModelByDB(weightPath, "", imgPath, gt, binThresh, polyThresh, maxCandidates, unclipRatio, size, mean, scale);
}
TEST_P(Test_Model, TextDetectionByEAST)
{
if (target == DNN_TARGET_OPENCL_FP16)
applyTestTag(CV_TEST_TAG_DNN_SKIP_OPENCL_FP16);
std::string imgPath = _tf("text_det_test2.jpg");
std::string weightPath = _tf("frozen_east_text_detection.pb", false);
// GroundTruth
std::vector<RotatedRect> gt = {
RotatedRect(Point2f(657.55f, 409.5f), Size2f(316.84f, 62.45f), -4.79)
};
// Model parameters
Size size{320, 320};
double scale = 1.0;
Scalar mean = Scalar(123.68, 116.78, 103.94);
bool swapRB = true;
// Detection algorithm parameters
float confThresh = 0.5;
float nmsThresh = 0.4;
testTextDetectionModelByEAST(weightPath, "", imgPath, gt, confThresh, nmsThresh, size, mean, scale, swapRB);
}
INSTANTIATE_TEST_CASE_P(/**/, Test_Model, dnnBackendsAndTargets());
}} // namespace