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:
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user