vendor: OpenCV 5.0.0 snapshot at 755e50675d97db9b7d449d8bd6b09888646f6c6e
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,791 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/ml.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
namespace text
|
||||
{
|
||||
|
||||
using namespace std;
|
||||
using namespace cv::ml;
|
||||
|
||||
/* OCR BeamSearch Decoder */
|
||||
|
||||
void OCRBeamSearchDecoder::run(Mat& image, string& output_text, vector<Rect>* component_rects,
|
||||
vector<string>* component_texts, vector<float>* component_confidences,
|
||||
int component_level)
|
||||
{
|
||||
CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) );
|
||||
CV_Assert( (component_level == OCR_LEVEL_TEXTLINE) || (component_level == OCR_LEVEL_WORD) );
|
||||
output_text.clear();
|
||||
if (component_rects != NULL)
|
||||
component_rects->clear();
|
||||
if (component_texts != NULL)
|
||||
component_texts->clear();
|
||||
if (component_confidences != NULL)
|
||||
component_confidences->clear();
|
||||
}
|
||||
void OCRBeamSearchDecoder::run(Mat& image, Mat& mask, string& output_text, vector<Rect>* component_rects,
|
||||
vector<string>* component_texts, vector<float>* component_confidences,
|
||||
int component_level)
|
||||
{
|
||||
CV_Assert(mask.type() == CV_8UC1);
|
||||
CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) );
|
||||
CV_Assert( (component_level == OCR_LEVEL_TEXTLINE) || (component_level == OCR_LEVEL_WORD) );
|
||||
output_text.clear();
|
||||
if (component_rects != NULL)
|
||||
component_rects->clear();
|
||||
if (component_texts != NULL)
|
||||
component_texts->clear();
|
||||
if (component_confidences != NULL)
|
||||
component_confidences->clear();
|
||||
}
|
||||
|
||||
CV_WRAP String OCRBeamSearchDecoder::run(InputArray image, int min_confidence, int component_level)
|
||||
{
|
||||
std::string output1;
|
||||
std::string output2;
|
||||
vector<string> component_texts;
|
||||
vector<float> component_confidences;
|
||||
Mat image_m = image.getMat();
|
||||
run(image_m, output1, NULL, &component_texts, &component_confidences, component_level);
|
||||
for(unsigned int i = 0; i < component_texts.size(); i++)
|
||||
{
|
||||
//cout << "confidence: " << component_confidences[i] << " text:" << component_texts[i] << endl;
|
||||
if(component_confidences[i] > min_confidence)
|
||||
{
|
||||
output2 += component_texts[i];
|
||||
}
|
||||
}
|
||||
return String(output2);
|
||||
}
|
||||
|
||||
CV_WRAP String OCRBeamSearchDecoder::run(InputArray image, InputArray mask, int min_confidence, int component_level)
|
||||
{
|
||||
std::string output1;
|
||||
std::string output2;
|
||||
vector<string> component_texts;
|
||||
vector<float> component_confidences;
|
||||
Mat image_m = image.getMat();
|
||||
Mat mask_m = mask.getMat();
|
||||
run(image_m, mask_m, output1, NULL, &component_texts, &component_confidences, component_level);
|
||||
for(unsigned int i = 0; i < component_texts.size(); i++)
|
||||
{
|
||||
//cout << "confidence: " << component_confidences[i] << " text:" << component_texts[i] << endl;
|
||||
if(component_confidences[i] > min_confidence)
|
||||
{
|
||||
output2 += component_texts[i];
|
||||
}
|
||||
}
|
||||
return String(output2);
|
||||
}
|
||||
|
||||
|
||||
void OCRBeamSearchDecoder::ClassifierCallback::eval( InputArray image, vector< vector<double> >& recognition_probabilities, vector<int>& oversegmentation)
|
||||
{
|
||||
CV_Assert(( image.getMat().type() == CV_8UC3 ) || ( image.getMat().type() == CV_8UC1 ));
|
||||
if (!recognition_probabilities.empty())
|
||||
{
|
||||
for (size_t i=0; i<recognition_probabilities.size(); i++)
|
||||
recognition_probabilities[i].clear();
|
||||
}
|
||||
recognition_probabilities.clear();
|
||||
oversegmentation.clear();
|
||||
}
|
||||
|
||||
struct beamSearch_node {
|
||||
double score;
|
||||
vector<int> segmentation;
|
||||
bool expanded;
|
||||
// TODO calculating score of its child would be much faster if we store the last column
|
||||
// of their "root" path.
|
||||
};
|
||||
|
||||
bool beam_sort_function ( beamSearch_node a, beamSearch_node b );
|
||||
bool beam_sort_function ( beamSearch_node a, beamSearch_node b )
|
||||
{
|
||||
return (a.score > b.score);
|
||||
}
|
||||
|
||||
|
||||
class OCRBeamSearchDecoderImpl CV_FINAL : public OCRBeamSearchDecoder
|
||||
{
|
||||
public:
|
||||
//Default constructor
|
||||
OCRBeamSearchDecoderImpl( Ptr<OCRBeamSearchDecoder::ClassifierCallback> _classifier,
|
||||
const string& _vocabulary,
|
||||
InputArray transition_probabilities_table,
|
||||
InputArray emission_probabilities_table,
|
||||
decoder_mode _mode,
|
||||
int _beam_size)
|
||||
{
|
||||
classifier = _classifier;
|
||||
step_size = classifier->getStepSize();
|
||||
win_size = classifier->getWindowSize();
|
||||
emission_p = emission_probabilities_table.getMat();
|
||||
vocabulary = _vocabulary;
|
||||
mode = _mode;
|
||||
beam_size = _beam_size;
|
||||
transition_probabilities_table.getMat().copyTo(transition_p);
|
||||
for (int i=0; i<transition_p.rows; i++)
|
||||
{
|
||||
for (int j=0; j<transition_p.cols; j++)
|
||||
{
|
||||
if (transition_p.at<double>(i,j) == 0)
|
||||
transition_p.at<double>(i,j) = -DBL_MAX;
|
||||
else
|
||||
transition_p.at<double>(i,j) = log(transition_p.at<double>(i,j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~OCRBeamSearchDecoderImpl() CV_OVERRIDE
|
||||
{
|
||||
}
|
||||
|
||||
void run( Mat& src,
|
||||
Mat& mask,
|
||||
string& out_sequence,
|
||||
vector<Rect>* component_rects,
|
||||
vector<string>* component_texts,
|
||||
vector<float>* component_confidences,
|
||||
int component_level) CV_OVERRIDE
|
||||
{
|
||||
CV_Assert(mask.type() == CV_8UC1);
|
||||
//nothing to do with a mask here
|
||||
run( src, out_sequence, component_rects, component_texts, component_confidences,
|
||||
component_level);
|
||||
}
|
||||
|
||||
void run( Mat& src,
|
||||
string& out_sequence,
|
||||
vector<Rect>* component_rects,
|
||||
vector<string>* component_texts,
|
||||
vector<float>* component_confidences,
|
||||
int component_level) CV_OVERRIDE
|
||||
{
|
||||
|
||||
CV_Assert( (src.type() == CV_8UC1) || (src.type() == CV_8UC3) );
|
||||
CV_Assert( (src.cols > 0) && (src.rows > 0) );
|
||||
CV_Assert( component_level == OCR_LEVEL_WORD );
|
||||
out_sequence.clear();
|
||||
if (component_rects != NULL)
|
||||
component_rects->clear();
|
||||
if (component_texts != NULL)
|
||||
component_texts->clear();
|
||||
if (component_confidences != NULL)
|
||||
component_confidences->clear();
|
||||
|
||||
if(src.type() == CV_8UC3)
|
||||
{
|
||||
cvtColor(src,src,COLOR_RGB2GRAY);
|
||||
}
|
||||
|
||||
|
||||
// TODO if input is a text line (not a word) we may need to split into words here!
|
||||
|
||||
// do sliding window classification along a cropped word image
|
||||
classifier->eval(src, recognition_probabilities, oversegmentation);
|
||||
|
||||
// if the number of oversegmentation points found is less than 2 we can not do nothing!!
|
||||
if (oversegmentation.size() < 2) return;
|
||||
|
||||
|
||||
//NMS of recognitions
|
||||
double last_best_p = 0;
|
||||
int last_best_idx = -1;
|
||||
for (size_t i=0; i<recognition_probabilities.size(); )
|
||||
{
|
||||
double best_p = 0;
|
||||
int best_idx = -1;
|
||||
for (size_t j=0; j<recognition_probabilities[i].size(); j++)
|
||||
{
|
||||
if (recognition_probabilities[i][j] > best_p)
|
||||
{
|
||||
best_p = recognition_probabilities[i][j];
|
||||
best_idx = (int)j;
|
||||
}
|
||||
}
|
||||
|
||||
if ((i>0) && (best_idx == last_best_idx)
|
||||
&& (oversegmentation[i]*step_size < oversegmentation[i-1]*step_size + win_size) )
|
||||
{
|
||||
if (last_best_p > best_p)
|
||||
{
|
||||
//remove i'th elements and do not increment i
|
||||
recognition_probabilities.erase (recognition_probabilities.begin()+i);
|
||||
oversegmentation.erase (oversegmentation.begin()+i);
|
||||
continue;
|
||||
} else {
|
||||
//remove (i-1)'th elements and do not increment i
|
||||
recognition_probabilities.erase (recognition_probabilities.begin()+i-1);
|
||||
oversegmentation.erase (oversegmentation.begin()+i-1);
|
||||
last_best_idx = best_idx;
|
||||
last_best_p = best_p;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
last_best_idx = best_idx;
|
||||
last_best_p = best_p;
|
||||
i++;
|
||||
}
|
||||
|
||||
/*Now we go with the beam search algorithm to optimize the recognition score*/
|
||||
|
||||
//convert probabilities to log probabilities
|
||||
for (size_t i=0; i<recognition_probabilities.size(); i++)
|
||||
{
|
||||
for (size_t j=0; j<recognition_probabilities[i].size(); j++)
|
||||
{
|
||||
if (recognition_probabilities[i][j] == 0)
|
||||
recognition_probabilities[i][j] = -DBL_MAX;
|
||||
else
|
||||
recognition_probabilities[i][j] = log(recognition_probabilities[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
// initialize the beam with all possible character's pairs
|
||||
int generated_chids = 0;
|
||||
for (size_t i=0; i<recognition_probabilities.size()-1; i++)
|
||||
{
|
||||
for (size_t j=i+1; j<recognition_probabilities.size(); j++)
|
||||
{
|
||||
|
||||
beamSearch_node node;
|
||||
node.segmentation.push_back((int)i);
|
||||
node.segmentation.push_back((int)j);
|
||||
node.score = score_segmentation(node.segmentation, out_sequence);
|
||||
vector< vector<int> > childs = generate_childs( node.segmentation );
|
||||
node.expanded = true;
|
||||
|
||||
beam.push_back( node );
|
||||
|
||||
if (!childs.empty())
|
||||
update_beam( childs );
|
||||
|
||||
generated_chids += (int)childs.size();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
while (generated_chids != 0)
|
||||
{
|
||||
generated_chids = 0;
|
||||
|
||||
for (size_t i=0; i<beam.size(); i++)
|
||||
{
|
||||
vector< vector<int> > childs;
|
||||
if (!beam[i].expanded)
|
||||
{
|
||||
childs = generate_childs( beam[i].segmentation );
|
||||
beam[i].expanded = true;
|
||||
}
|
||||
if (!childs.empty())
|
||||
update_beam( childs );
|
||||
generated_chids += (int)childs.size();
|
||||
}
|
||||
}
|
||||
|
||||
// Done! Get the best prediction found into out_sequence
|
||||
double lp = score_segmentation( beam[0].segmentation, out_sequence );
|
||||
|
||||
// fill other (dummy) output parameters
|
||||
if (component_rects != NULL)
|
||||
component_rects->push_back(Rect(0,0,src.cols,src.rows));
|
||||
if (component_texts != NULL)
|
||||
component_texts->push_back(out_sequence);
|
||||
if (component_confidences != NULL)
|
||||
component_confidences->push_back((float)exp(lp));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private:
|
||||
int win_size;
|
||||
int step_size;
|
||||
|
||||
vector< beamSearch_node > beam;
|
||||
vector< vector<double> > recognition_probabilities;
|
||||
vector<int> oversegmentation;
|
||||
|
||||
vector< vector<int> > generate_childs( vector<int> &segmentation )
|
||||
{
|
||||
|
||||
vector< vector<int> > childs;
|
||||
for (size_t i=segmentation[segmentation.size()-1]+1; i<oversegmentation.size(); i++)
|
||||
{
|
||||
int seg_point = (int)i;
|
||||
if (find(segmentation.begin(), segmentation.end(), seg_point) == segmentation.end())
|
||||
{
|
||||
vector<int> child = segmentation;
|
||||
child.push_back(seg_point);
|
||||
childs.push_back(child);
|
||||
}
|
||||
}
|
||||
return childs;
|
||||
}
|
||||
|
||||
void update_beam ( vector< vector<int> > &childs )
|
||||
{
|
||||
string out_sequence;
|
||||
double min_score = -DBL_MAX; //min score value to be part of the beam
|
||||
if ((int)beam.size() >= beam_size)
|
||||
min_score = beam[beam_size-1].score; //last element has the lowest score
|
||||
|
||||
for (size_t i=0; i<childs.size(); i++)
|
||||
{
|
||||
double score = score_segmentation(childs[i], out_sequence);
|
||||
if (score > min_score)
|
||||
{
|
||||
beamSearch_node node;
|
||||
node.score = score;
|
||||
node.segmentation = childs[i];
|
||||
node.expanded = false;
|
||||
beam.push_back(node);
|
||||
sort(beam.begin(),beam.end(),beam_sort_function);
|
||||
if ((int)beam.size() > beam_size)
|
||||
{
|
||||
beam.erase(beam.begin()+beam_size,beam.end());
|
||||
min_score = beam[beam.size()-1].score;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
double score_segmentation( vector<int> &segmentation, string& outstring )
|
||||
{
|
||||
|
||||
// Score Heuristics:
|
||||
// No need to use Viterbi to know a given segmentation is bad
|
||||
// e.g.: in some cases we discard a segmentation because it includes a very large character
|
||||
// in other cases we do it because the overlapping between two chars is too large
|
||||
// TODO Add more heuristics (e.g. penalize large inter-character variance)
|
||||
|
||||
Mat interdist ((int)segmentation.size()-1, 1, CV_32F, 1);
|
||||
for (size_t i=0; i<segmentation.size()-1; i++)
|
||||
{
|
||||
interdist.at<float>((int)i,0) = (float)oversegmentation[segmentation[(int)i+1]]*step_size
|
||||
- (float)oversegmentation[segmentation[(int)i]]*step_size;
|
||||
if ((float)interdist.at<float>((int)i,0)/win_size > 2.25) // TODO explain how did you set this thrs
|
||||
{
|
||||
return -DBL_MAX;
|
||||
}
|
||||
if ((float)interdist.at<float>((int)i,0)/win_size < 0.15) // TODO explain how did you set this thrs
|
||||
{
|
||||
return -DBL_MAX;
|
||||
}
|
||||
}
|
||||
Scalar m, std;
|
||||
meanStdDev(interdist, m, std);
|
||||
//double interdist_std = std[0];
|
||||
|
||||
//TODO Extracting start probs from lexicon (if we have it) may boost accuracy!
|
||||
vector<double> start_p(vocabulary.size());
|
||||
for (int i=0; i<(int)vocabulary.size(); i++)
|
||||
start_p[i] = log(1.0/vocabulary.size());
|
||||
|
||||
|
||||
Mat V = Mat::ones((int)segmentation.size(),(int)vocabulary.size(),CV_64FC1);
|
||||
V = V * -DBL_MAX;
|
||||
vector<string> path(vocabulary.size());
|
||||
|
||||
// Initialize base cases (t == 0)
|
||||
for (int i=0; i<(int)vocabulary.size(); i++)
|
||||
{
|
||||
V.at<double>(0,i) = start_p[i] + recognition_probabilities[segmentation[0]][i];
|
||||
path[i] = vocabulary.at(i);
|
||||
}
|
||||
|
||||
|
||||
// Run Viterbi for t > 0
|
||||
for (int t=1; t<(int)segmentation.size(); t++)
|
||||
{
|
||||
|
||||
vector<string> newpath(vocabulary.size());
|
||||
|
||||
for (int i=0; i<(int)vocabulary.size(); i++)
|
||||
{
|
||||
double max_prob = -DBL_MAX;
|
||||
int best_idx = 0;
|
||||
for (int j=0; j<(int)vocabulary.size(); j++)
|
||||
{
|
||||
double prob = V.at<double>(t-1,j) + transition_p.at<double>(j,i) + recognition_probabilities[segmentation[t]][i];
|
||||
if ( prob > max_prob)
|
||||
{
|
||||
max_prob = prob;
|
||||
best_idx = j;
|
||||
}
|
||||
}
|
||||
|
||||
V.at<double>(t,i) = max_prob;
|
||||
newpath[i] = path[best_idx] + vocabulary.at(i);
|
||||
}
|
||||
|
||||
// Don't need to remember the old paths
|
||||
path.swap(newpath);
|
||||
}
|
||||
|
||||
double max_prob = -DBL_MAX;
|
||||
int best_idx = 0;
|
||||
for (int i=0; i<(int)vocabulary.size(); i++)
|
||||
{
|
||||
double prob = V.at<double>((int)segmentation.size()-1,i);
|
||||
if ( prob > max_prob)
|
||||
{
|
||||
max_prob = prob;
|
||||
best_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
outstring = path[best_idx];
|
||||
return (max_prob / (segmentation.size()-1));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Ptr<OCRBeamSearchDecoder> OCRBeamSearchDecoder::create( Ptr<OCRBeamSearchDecoder::ClassifierCallback> _classifier,
|
||||
const string& _vocabulary,
|
||||
InputArray transition_p,
|
||||
InputArray emission_p,
|
||||
decoder_mode _mode,
|
||||
int _beam_size)
|
||||
{
|
||||
return makePtr<OCRBeamSearchDecoderImpl>(_classifier, _vocabulary, transition_p, emission_p, _mode, _beam_size);
|
||||
}
|
||||
|
||||
Ptr<OCRBeamSearchDecoder> OCRBeamSearchDecoder::create(const String& _filename,
|
||||
const String& _vocabulary,
|
||||
InputArray transition_p,
|
||||
InputArray emission_p,
|
||||
decoder_mode _mode,
|
||||
int _beam_size)
|
||||
{
|
||||
return makePtr<OCRBeamSearchDecoderImpl>(loadOCRBeamSearchClassifierCNN(_filename), _vocabulary, transition_p, emission_p, (decoder_mode)_mode, _beam_size);
|
||||
}
|
||||
|
||||
class OCRBeamSearchClassifierCNN CV_FINAL : public OCRBeamSearchDecoder::ClassifierCallback
|
||||
{
|
||||
public:
|
||||
//constructor
|
||||
OCRBeamSearchClassifierCNN(const std::string& filename);
|
||||
// Destructor
|
||||
~OCRBeamSearchClassifierCNN() CV_OVERRIDE {}
|
||||
|
||||
void eval( InputArray src, vector< vector<double> >& recognition_probabilities, vector<int>& oversegmentation ) CV_OVERRIDE;
|
||||
|
||||
int getWindowSize() {return window_size;}
|
||||
int getStepSize() {return step_size;}
|
||||
void setStepSize(int _step_size) {step_size = _step_size;}
|
||||
|
||||
protected:
|
||||
void normalizeAndZCA(Mat& patches);
|
||||
double eval_feature(Mat& feature, double* prob_estimates);
|
||||
|
||||
private:
|
||||
int window_size; // window size
|
||||
int step_size; // sliding window step
|
||||
int nr_class; // number of classes
|
||||
int nr_feature; // number of features
|
||||
Mat feature_min; // scale range
|
||||
Mat feature_max;
|
||||
Mat weights; // Logistic Regression weights
|
||||
Mat kernels; // CNN kernels
|
||||
Mat M, P; // ZCA Whitening parameters
|
||||
int quad_size;
|
||||
int patch_size;
|
||||
int num_quads; // extract 25 quads (12x12) from each image
|
||||
int num_tiles; // extract 25 patches (8x8) from each quad
|
||||
double alpha; // used in non-linear activation function z = max(0, |D*a| - alpha)
|
||||
};
|
||||
|
||||
OCRBeamSearchClassifierCNN::OCRBeamSearchClassifierCNN (const string& filename)
|
||||
{
|
||||
if (ifstream(filename.c_str()))
|
||||
{
|
||||
FileStorage fs(filename, FileStorage::READ);
|
||||
// Load kernels bank and withenning params
|
||||
fs["kernels"] >> kernels;
|
||||
fs["M"] >> M;
|
||||
fs["P"] >> P;
|
||||
// Load Logistic Regression weights
|
||||
fs["weights"] >> weights;
|
||||
// Load feature scaling ranges
|
||||
fs["feature_min"] >> feature_min;
|
||||
fs["feature_max"] >> feature_max;
|
||||
fs.release();
|
||||
}
|
||||
else
|
||||
CV_Error(Error::StsBadArg, "Default classifier data file not found!");
|
||||
|
||||
nr_feature = weights.rows;
|
||||
nr_class = weights.cols;
|
||||
patch_size = cvRound(sqrt((float)kernels.cols));
|
||||
window_size = 4*patch_size;
|
||||
step_size = 4;
|
||||
quad_size = 12;
|
||||
num_quads = 25;
|
||||
num_tiles = 25;
|
||||
alpha = 0.5; // used in non-linear activation function z = max(0, |D*a| - alpha)
|
||||
}
|
||||
|
||||
void OCRBeamSearchClassifierCNN::eval( InputArray _src, vector< vector<double> >& recognition_probabilities, vector<int>& oversegmentation)
|
||||
{
|
||||
|
||||
CV_Assert(( _src.getMat().type() == CV_8UC3 ) || ( _src.getMat().type() == CV_8UC1 ));
|
||||
if (!recognition_probabilities.empty())
|
||||
{
|
||||
for (size_t i=0; i<recognition_probabilities.size(); i++)
|
||||
recognition_probabilities[i].clear();
|
||||
}
|
||||
recognition_probabilities.clear();
|
||||
oversegmentation.clear();
|
||||
|
||||
|
||||
Mat src = _src.getMat();
|
||||
if(src.type() == CV_8UC3)
|
||||
{
|
||||
cvtColor(src,src,COLOR_RGB2GRAY);
|
||||
}
|
||||
|
||||
resize(src,src,Size(window_size*src.cols/src.rows,window_size),0,0,INTER_LINEAR_EXACT);
|
||||
|
||||
int seg_points = 0;
|
||||
|
||||
Mat quad;
|
||||
Mat tmp;
|
||||
Mat img;
|
||||
|
||||
int sz = src.cols - window_size;
|
||||
int sz_window_quad = window_size - quad_size;
|
||||
int sz_half_quad = (int)(quad_size/2-1);
|
||||
int sz_quad_patch = quad_size - patch_size;
|
||||
// begin sliding window loop foreach detection window
|
||||
for (int x_c = 0; x_c <= sz; x_c += step_size)
|
||||
{
|
||||
|
||||
img = src(Rect(Point(x_c,0),Size(window_size,window_size)));
|
||||
|
||||
vector< vector<double> > data_pool(9);
|
||||
|
||||
|
||||
int quad_id = 1;
|
||||
|
||||
for (int q_x = 0; q_x <= sz_window_quad; q_x += sz_half_quad)
|
||||
{
|
||||
for (int q_y = 0; q_y <= sz_window_quad; q_y += sz_half_quad)
|
||||
{
|
||||
Rect quad_rect = Rect(q_x,q_y,quad_size,quad_size);
|
||||
quad = img(quad_rect);
|
||||
|
||||
//start sliding window (8x8) in each tile and store the patch as row in data_pool
|
||||
for (int w_x = 0; w_x <= sz_quad_patch; w_x++)
|
||||
{
|
||||
for (int w_y = 0; w_y <= sz_quad_patch; w_y++)
|
||||
{
|
||||
quad(Rect(w_x,w_y,patch_size,patch_size)).convertTo(tmp, CV_64F);
|
||||
tmp = tmp.reshape(0,1);
|
||||
normalizeAndZCA(tmp);
|
||||
vector<double> patch;
|
||||
tmp.copyTo(patch);
|
||||
if ((quad_id == 1)||(quad_id == 2)||(quad_id == 6)||(quad_id == 7))
|
||||
data_pool[0].insert(data_pool[0].end(),patch.begin(),patch.end());
|
||||
if ((quad_id == 2)||(quad_id == 7)||(quad_id == 3)||(quad_id == 8)||(quad_id == 4)||(quad_id == 9))
|
||||
data_pool[1].insert(data_pool[1].end(),patch.begin(),patch.end());
|
||||
if ((quad_id == 4)||(quad_id == 9)||(quad_id == 5)||(quad_id == 10))
|
||||
data_pool[2].insert(data_pool[2].end(),patch.begin(),patch.end());
|
||||
if ((quad_id == 6)||(quad_id == 11)||(quad_id == 16)||(quad_id == 7)||(quad_id == 12)||(quad_id == 17))
|
||||
data_pool[3].insert(data_pool[3].end(),patch.begin(),patch.end());
|
||||
if ((quad_id == 7)||(quad_id == 12)||(quad_id == 17)||(quad_id == 8)||(quad_id == 13)||(quad_id == 18)||(quad_id == 9)||(quad_id == 14)||(quad_id == 19))
|
||||
data_pool[4].insert(data_pool[4].end(),patch.begin(),patch.end());
|
||||
if ((quad_id == 9)||(quad_id == 14)||(quad_id == 19)||(quad_id == 10)||(quad_id == 15)||(quad_id == 20))
|
||||
data_pool[5].insert(data_pool[5].end(),patch.begin(),patch.end());
|
||||
if ((quad_id == 16)||(quad_id == 21)||(quad_id == 17)||(quad_id == 22))
|
||||
data_pool[6].insert(data_pool[6].end(),patch.begin(),patch.end());
|
||||
if ((quad_id == 17)||(quad_id == 22)||(quad_id == 18)||(quad_id == 23)||(quad_id == 19)||(quad_id == 24))
|
||||
data_pool[7].insert(data_pool[7].end(),patch.begin(),patch.end());
|
||||
if ((quad_id == 19)||(quad_id == 24)||(quad_id == 20)||(quad_id == 25))
|
||||
data_pool[8].insert(data_pool[8].end(),patch.begin(),patch.end());
|
||||
}
|
||||
}
|
||||
|
||||
quad_id++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//do dot product of each normalized and whitened patch
|
||||
//each pool is averaged and this yields a representation of 9xD
|
||||
Mat feature = Mat::zeros(9,kernels.rows,CV_64FC1);
|
||||
for (int i=0; i<9; i++)
|
||||
{
|
||||
Mat pool = Mat(data_pool[i]);
|
||||
pool = pool.reshape(0,(int)data_pool[i].size()/kernels.cols);
|
||||
for (int p=0; p<pool.rows; p++)
|
||||
{
|
||||
for (int f=0; f<kernels.rows; f++)
|
||||
{
|
||||
feature.row(i).at<double>(0,f) = feature.row(i).at<double>(0,f) + max(0.0,std::abs(pool.row(p).dot(kernels.row(f)))-alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
feature = feature.reshape(0,1);
|
||||
|
||||
|
||||
// data must be normalized within the range obtained during training
|
||||
double lower = -1.0;
|
||||
double upper = 1.0;
|
||||
for (int k=0; k<feature.cols; k++)
|
||||
{
|
||||
feature.at<double>(0,k) = lower + (upper-lower) *
|
||||
(feature.at<double>(0,k)-feature_min.at<double>(0,k))/
|
||||
(feature_max.at<double>(0,k)-feature_min.at<double>(0,k));
|
||||
}
|
||||
|
||||
double *p = new double[nr_class];
|
||||
double predict_label = eval_feature(feature,p);
|
||||
|
||||
if ( (predict_label < 0) || (predict_label > nr_class) )
|
||||
CV_Error(Error::StsOutOfRange, "OCRBeamSearchClassifierCNN::eval Error: unexpected prediction in eval_feature()");
|
||||
|
||||
|
||||
vector<double> recognition_p(p, p+nr_class);
|
||||
recognition_probabilities.push_back(recognition_p);
|
||||
oversegmentation.push_back(seg_points);
|
||||
seg_points++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// normalize for contrast and apply ZCA whitening to a set of image patches
|
||||
void OCRBeamSearchClassifierCNN::normalizeAndZCA(Mat& patches)
|
||||
{
|
||||
|
||||
//Normalize for contrast
|
||||
for (int i=0; i<patches.rows; i++)
|
||||
{
|
||||
Scalar row_mean, row_std;
|
||||
meanStdDev(patches.row(i),row_mean,row_std);
|
||||
row_std[0] = sqrt(pow(row_std[0],2)*patches.cols/(patches.cols-1)+10);
|
||||
patches.row(i) = (patches.row(i) - row_mean[0]) / row_std[0];
|
||||
}
|
||||
|
||||
|
||||
//ZCA whitening
|
||||
if ((M.dims == 0) || (P.dims == 0))
|
||||
{
|
||||
Mat CC;
|
||||
calcCovarMatrix(patches,CC,M,COVAR_NORMAL|COVAR_ROWS|COVAR_SCALE);
|
||||
CC = CC * patches.rows / (patches.rows-1);
|
||||
|
||||
|
||||
Mat e_val,e_vec;
|
||||
eigen(CC.t(),e_val,e_vec);
|
||||
e_vec = e_vec.t();
|
||||
sqrt(1./(e_val + 0.1), e_val);
|
||||
|
||||
|
||||
Mat V = Mat::zeros(e_vec.rows, e_vec.cols, CV_64FC1);
|
||||
Mat D = Mat::eye(e_vec.rows, e_vec.cols, CV_64FC1);
|
||||
|
||||
for (int i=0; i<e_vec.cols; i++)
|
||||
{
|
||||
e_vec.col(e_vec.cols-i-1).copyTo(V.col(i));
|
||||
D.col(i) = D.col(i) * e_val.at<double>(0,e_val.rows-i-1);
|
||||
}
|
||||
|
||||
P = V * D * V.t();
|
||||
}
|
||||
|
||||
for (int i=0; i<patches.rows; i++)
|
||||
patches.row(i) = patches.row(i) - M;
|
||||
|
||||
patches = patches * P;
|
||||
|
||||
}
|
||||
|
||||
double OCRBeamSearchClassifierCNN::eval_feature(Mat& feature, double* prob_estimates)
|
||||
{
|
||||
for(int i=0;i<nr_class;i++)
|
||||
prob_estimates[i] = 0;
|
||||
|
||||
for(int idx=0; idx<nr_feature; idx++)
|
||||
for(int i=0;i<nr_class;i++)
|
||||
prob_estimates[i] += weights.at<float>(idx,i)*feature.at<double>(0,idx); //TODO use vectorized dot product
|
||||
|
||||
int dec_max_idx = 0;
|
||||
for(int i=1;i<nr_class;i++)
|
||||
{
|
||||
if(prob_estimates[i] > prob_estimates[dec_max_idx])
|
||||
dec_max_idx = i;
|
||||
}
|
||||
|
||||
for(int i=0;i<nr_class;i++)
|
||||
prob_estimates[i]=1/(1+exp(-prob_estimates[i]));
|
||||
|
||||
double sum=0;
|
||||
for(int i=0; i<nr_class; i++)
|
||||
sum+=prob_estimates[i];
|
||||
|
||||
for(int i=0; i<nr_class; i++)
|
||||
prob_estimates[i]=prob_estimates[i]/sum;
|
||||
|
||||
return dec_max_idx;
|
||||
}
|
||||
|
||||
Ptr<OCRBeamSearchDecoder::ClassifierCallback> loadOCRBeamSearchClassifierCNN(const String& filename)
|
||||
|
||||
{
|
||||
return makePtr<OCRBeamSearchClassifierCNN>(std::string(filename));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,106 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/dnn.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace cv { namespace text {
|
||||
|
||||
class OCRHolisticWordRecognizerImpl CV_FINAL : public OCRHolisticWordRecognizer
|
||||
{
|
||||
private:
|
||||
dnn::Net net;
|
||||
vector<string> words;
|
||||
|
||||
public:
|
||||
OCRHolisticWordRecognizerImpl(const string &archFilename, const string &weightsFilename, const string &wordsFilename)
|
||||
{
|
||||
net = dnn::readNet(weightsFilename, archFilename);
|
||||
std::ifstream in(wordsFilename.c_str());
|
||||
if (!in)
|
||||
{
|
||||
CV_Error(Error::StsError, "Could not read Labels from file");
|
||||
}
|
||||
std::string line;
|
||||
while (std::getline(in, line))
|
||||
words.push_back(line);
|
||||
CV_Assert(getClassCount() == words.size());
|
||||
}
|
||||
|
||||
void run(Mat& image, std::string& output_text, std::vector<Rect>* component_rects=NULL, std::vector<std::string>* component_texts=NULL, std::vector<float>* component_confidences=NULL, int component_level=0) CV_OVERRIDE
|
||||
{
|
||||
CV_Assert(component_level==OCR_LEVEL_WORD); //Componnents not applicable for word spotting
|
||||
double confidence;
|
||||
output_text = classify(image, confidence);
|
||||
if(component_rects!=NULL){
|
||||
component_rects->resize(1);
|
||||
(*component_rects)[0]=Rect(0,0,image.size().width,image.size().height);
|
||||
}
|
||||
if(component_texts!=NULL){
|
||||
component_texts->resize(1);
|
||||
(*component_texts)[0] = output_text;
|
||||
}
|
||||
if(component_confidences!=NULL){
|
||||
component_confidences->resize(1);
|
||||
(*component_confidences)[0] = float(confidence);
|
||||
}
|
||||
}
|
||||
|
||||
void run(Mat& image, Mat& mask, std::string& output_text, std::vector<Rect>* component_rects=NULL, std::vector<std::string>* component_texts=NULL, std::vector<float>* component_confidences=NULL, int component_level=0) CV_OVERRIDE
|
||||
{
|
||||
//Mask is ignored because the CNN operates on a full image
|
||||
CV_Assert(mask.cols == image.cols && mask.rows == image.rows);
|
||||
this->run(image, output_text, component_rects, component_texts, component_confidences, component_level);
|
||||
}
|
||||
|
||||
protected:
|
||||
Size getPerceptiveField() const
|
||||
{
|
||||
return Size(100, 32);
|
||||
}
|
||||
|
||||
size_t getClassCount()
|
||||
{
|
||||
int id = net.getLayerId("prob");
|
||||
MatShape inputShape;
|
||||
inputShape.push_back(1);
|
||||
inputShape.push_back(1);
|
||||
inputShape.push_back(getPerceptiveField().height);
|
||||
inputShape.push_back(getPerceptiveField().width);
|
||||
vector<MatShape> inShapes, outShapes;
|
||||
net.getLayerShapes(inputShape, CV_32F, id, inShapes, outShapes);
|
||||
CV_Assert(outShapes.size() == 1 && outShapes[0].size() == 4);
|
||||
CV_Assert(outShapes[0][0] == 1 && outShapes[0][2] == 1 && outShapes[0][3] == 1);
|
||||
return outShapes[0][1];
|
||||
}
|
||||
|
||||
string classify(InputArray image, double & conf)
|
||||
{
|
||||
CV_Assert(image.channels() == 1 && image.depth() == CV_8U);
|
||||
Mat resized;
|
||||
resize(image, resized, getPerceptiveField(), 0, 0, INTER_LINEAR_EXACT);
|
||||
Mat blob = dnn::blobFromImage(resized);
|
||||
net.setInput(blob, "data");
|
||||
Mat prob = net.forward("prob");
|
||||
CV_Assert(prob.dims == 4 && !prob.empty() && prob.size[1] == (int)getClassCount());
|
||||
int idx[4] = {0};
|
||||
minMaxIdx(prob, 0, &conf, 0, idx);
|
||||
CV_Assert(0 <= idx[1] && idx[1] < (int)words.size());
|
||||
return words[idx[1]];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Ptr<OCRHolisticWordRecognizer> OCRHolisticWordRecognizer::create(const string &archFilename, const string &weightsFilename, const string &wordsFilename)
|
||||
{
|
||||
return makePtr<OCRHolisticWordRecognizerImpl>(archFilename, weightsFilename, wordsFilename);
|
||||
}
|
||||
|
||||
}} // cv::text::
|
||||
@@ -0,0 +1,288 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/ml.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <queue>
|
||||
|
||||
#ifdef HAVE_TESSERACT
|
||||
#if !defined(USE_STD_NAMESPACE)
|
||||
#define USE_STD_NAMESPACE
|
||||
#endif
|
||||
#include <tesseract/baseapi.h>
|
||||
#include <tesseract/resultiterator.h>
|
||||
#endif
|
||||
|
||||
namespace cv
|
||||
{
|
||||
namespace text
|
||||
{
|
||||
|
||||
using namespace std;
|
||||
|
||||
void OCRTesseract::run(Mat& image, string& output_text, vector<Rect>* component_rects,
|
||||
vector<string>* component_texts, vector<float>* component_confidences,
|
||||
int component_level)
|
||||
{
|
||||
CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) );
|
||||
CV_Assert( (component_level == OCR_LEVEL_TEXTLINE) || (component_level == OCR_LEVEL_WORD) );
|
||||
output_text.clear();
|
||||
if (component_rects != NULL)
|
||||
component_rects->clear();
|
||||
if (component_texts != NULL)
|
||||
component_texts->clear();
|
||||
if (component_confidences != NULL)
|
||||
component_confidences->clear();
|
||||
}
|
||||
|
||||
void OCRTesseract::run(Mat& image, Mat& mask, string& output_text, vector<Rect>* component_rects,
|
||||
vector<string>* component_texts, vector<float>* component_confidences,
|
||||
int component_level)
|
||||
{
|
||||
CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) );
|
||||
CV_Assert( mask.type() == CV_8UC1 );
|
||||
CV_Assert( (component_level == OCR_LEVEL_TEXTLINE) || (component_level == OCR_LEVEL_WORD) );
|
||||
output_text.clear();
|
||||
if (component_rects != NULL)
|
||||
component_rects->clear();
|
||||
if (component_texts != NULL)
|
||||
component_texts->clear();
|
||||
if (component_confidences != NULL)
|
||||
component_confidences->clear();
|
||||
}
|
||||
|
||||
CV_WRAP String OCRTesseract::run(InputArray image, int min_confidence, int component_level)
|
||||
{
|
||||
std::string output1;
|
||||
std::string output2;
|
||||
vector<string> component_texts;
|
||||
vector<float> component_confidences;
|
||||
Mat image_m = image.getMat();
|
||||
run(image_m, output1, NULL, &component_texts, &component_confidences, component_level);
|
||||
for(unsigned int i = 0; i < component_texts.size(); i++)
|
||||
{
|
||||
// cout << "confidence: " << component_confidences[i] << " text:" << component_texts[i] << endl;
|
||||
if(component_confidences[i] > min_confidence)
|
||||
{
|
||||
output2 += component_texts[i];
|
||||
}
|
||||
}
|
||||
return String(output2);
|
||||
}
|
||||
|
||||
CV_WRAP String OCRTesseract::run(InputArray image, InputArray mask, int min_confidence, int component_level)
|
||||
{
|
||||
std::string output1;
|
||||
std::string output2;
|
||||
vector<string> component_texts;
|
||||
vector<float> component_confidences;
|
||||
Mat image_m = image.getMat();
|
||||
Mat mask_m = mask.getMat();
|
||||
run(image_m, mask_m, output1, NULL, &component_texts, &component_confidences, component_level);
|
||||
for(unsigned int i = 0; i < component_texts.size(); i++)
|
||||
{
|
||||
// cout << "confidence: " << component_confidences[i] << " text:" << component_texts[i] << endl;
|
||||
|
||||
if(component_confidences[i] > min_confidence)
|
||||
{
|
||||
output2 += component_texts[i];
|
||||
}
|
||||
}
|
||||
return String(output2);
|
||||
}
|
||||
|
||||
|
||||
class OCRTesseractImpl CV_FINAL : public OCRTesseract
|
||||
{
|
||||
private:
|
||||
#ifdef HAVE_TESSERACT
|
||||
tesseract::TessBaseAPI tess;
|
||||
#endif
|
||||
|
||||
public:
|
||||
//Default constructor
|
||||
OCRTesseractImpl(const char* datapath, const char* language, const char* char_whitelist, int oemode, int psmode)
|
||||
{
|
||||
|
||||
#ifdef HAVE_TESSERACT
|
||||
const char *lang = "eng";
|
||||
if (language != NULL)
|
||||
lang = language;
|
||||
|
||||
if (tess.Init(datapath, lang, (tesseract::OcrEngineMode)oemode))
|
||||
{
|
||||
cout << "OCRTesseract: Could not initialize tesseract." << endl;
|
||||
throw 1;
|
||||
}
|
||||
|
||||
//cout << "OCRTesseract: tesseract version " << tess.Version() << endl;
|
||||
|
||||
tesseract::PageSegMode pagesegmode = (tesseract::PageSegMode)psmode;
|
||||
tess.SetPageSegMode(pagesegmode);
|
||||
|
||||
// tessedit_whitelist default changes from [0-9a-zA-Z] to "".
|
||||
// See https://github.com/opencv/opencv_contrib/issues/3457
|
||||
if(char_whitelist != NULL)
|
||||
tess.SetVariable("tessedit_char_whitelist", char_whitelist);
|
||||
else
|
||||
tess.SetVariable("tessedit_char_whitelist", "");
|
||||
|
||||
tess.SetVariable("save_best_choices", "T");
|
||||
#else
|
||||
cout << "OCRTesseract("<<oemode<<psmode<<"): Tesseract not found." << endl;
|
||||
if (datapath != NULL)
|
||||
cout << " " << datapath << endl;
|
||||
if (language != NULL)
|
||||
cout << " " << language << endl;
|
||||
if (char_whitelist != NULL)
|
||||
cout << " " << char_whitelist << endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
~OCRTesseractImpl() CV_OVERRIDE
|
||||
{
|
||||
#ifdef HAVE_TESSERACT
|
||||
tess.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
void run(Mat& image, string& output, vector<Rect>* component_rects=NULL,
|
||||
vector<string>* component_texts=NULL, vector<float>* component_confidences=NULL,
|
||||
int component_level=0) CV_OVERRIDE
|
||||
{
|
||||
|
||||
CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) );
|
||||
|
||||
#ifdef HAVE_TESSERACT
|
||||
|
||||
if (component_texts != 0)
|
||||
component_texts->clear();
|
||||
if (component_rects != 0)
|
||||
component_rects->clear();
|
||||
if (component_confidences != 0)
|
||||
component_confidences->clear();
|
||||
|
||||
tess.SetImage((uchar*)image.data, image.size().width, image.size().height, image.channels(), image.step1());
|
||||
tess.Recognize(0);
|
||||
char *outText;
|
||||
outText = tess.GetUTF8Text();
|
||||
output = string(outText);
|
||||
if (outText != NULL)
|
||||
delete [] outText;
|
||||
|
||||
if ( (component_rects != NULL) || (component_texts != NULL) || (component_confidences != NULL) )
|
||||
{
|
||||
tesseract::ResultIterator* ri = tess.GetIterator();
|
||||
tesseract::PageIteratorLevel level = tesseract::RIL_WORD;
|
||||
if (component_level == OCR_LEVEL_TEXTLINE)
|
||||
level = tesseract::RIL_TEXTLINE;
|
||||
|
||||
if (ri != 0) {
|
||||
do {
|
||||
const char* word = ri->GetUTF8Text(level);
|
||||
if (word == NULL)
|
||||
continue;
|
||||
float conf = ri->Confidence(level);
|
||||
int x1, y1, x2, y2;
|
||||
ri->BoundingBox(level, &x1, &y1, &x2, &y2);
|
||||
|
||||
if (component_texts != 0)
|
||||
component_texts->push_back(string(word));
|
||||
if (component_rects != 0)
|
||||
component_rects->push_back(Rect(x1,y1,x2-x1,y2-y1));
|
||||
if (component_confidences != 0)
|
||||
component_confidences->push_back(conf);
|
||||
|
||||
delete[] word;
|
||||
} while (ri->Next(level));
|
||||
delete ri;
|
||||
}
|
||||
}
|
||||
|
||||
tess.Clear();
|
||||
|
||||
#else
|
||||
|
||||
cout << "OCRTesseract(" << component_level << image.type() <<"): Tesseract not found." << endl;
|
||||
output.clear();
|
||||
if(component_rects)
|
||||
component_rects->clear();
|
||||
if(component_texts)
|
||||
component_texts->clear();
|
||||
if(component_confidences)
|
||||
component_confidences->clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
void run(Mat& image, Mat& mask, string& output, vector<Rect>* component_rects=NULL,
|
||||
vector<string>* component_texts=NULL, vector<float>* component_confidences=NULL,
|
||||
int component_level=0) CV_OVERRIDE
|
||||
{
|
||||
CV_Assert( mask.type() == CV_8UC1 );
|
||||
CV_Assert( (image.type() == CV_8UC1) || (image.type() == CV_8UC3) );
|
||||
|
||||
run( mask, output, component_rects, component_texts, component_confidences, component_level);
|
||||
}
|
||||
|
||||
void setWhiteList(const String& char_whitelist) CV_OVERRIDE
|
||||
{
|
||||
#ifdef HAVE_TESSERACT
|
||||
tess.SetVariable("tessedit_char_whitelist", char_whitelist.c_str());
|
||||
#else
|
||||
CV_UNUSED(char_whitelist);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
Ptr<OCRTesseract> OCRTesseract::create(const char* datapath, const char* language,
|
||||
const char* char_whitelist, int oem, int psmode)
|
||||
{
|
||||
return makePtr<OCRTesseractImpl>(datapath, language, char_whitelist, oem, psmode);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef __OPENCV_PRECOMP_H__
|
||||
#define __OPENCV_PRECOMP_H__
|
||||
|
||||
#include "opencv2/text.hpp"
|
||||
|
||||
#include "text_config.hpp"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,98 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/dnn.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace cv::dnn;
|
||||
|
||||
namespace cv
|
||||
{
|
||||
namespace text
|
||||
{
|
||||
|
||||
class TextDetectorCNNImpl : public TextDetectorCNN
|
||||
{
|
||||
protected:
|
||||
Net net_;
|
||||
std::vector<Size> sizes_;
|
||||
int inputChannelCount_;
|
||||
|
||||
void getOutputs(const float* buffer,int nbrTextBoxes,int nCol,
|
||||
std::vector<Rect>& Bbox, std::vector<float>& confidence, Size inputShape)
|
||||
{
|
||||
for(int k = 0; k < nbrTextBoxes; k++)
|
||||
{
|
||||
float confidence_ = buffer[k*nCol + 2];
|
||||
if (confidence_ <= FLT_EPSILON) continue;
|
||||
|
||||
float x_min_f = buffer[k*nCol + 3]*inputShape.width;
|
||||
float y_min_f = buffer[k*nCol + 4]*inputShape.height;
|
||||
|
||||
float x_max_f = buffer[k*nCol + 5]*inputShape.width;
|
||||
float y_max_f = buffer[k*nCol + 6]*inputShape.height;
|
||||
|
||||
int x_min = cvRound(std::max(0.f, x_min_f));
|
||||
int y_min = cvRound(std::max(0.f, y_min_f));
|
||||
|
||||
int x_max = std::min(inputShape.width - 1, cvRound(x_max_f));
|
||||
int y_max = std::min(inputShape.height - 1, cvRound(y_max_f));
|
||||
|
||||
if (x_min >= x_max) continue;
|
||||
if (y_min >= y_max) continue;
|
||||
|
||||
int wd = x_max - x_min;
|
||||
int ht = y_max - y_min;
|
||||
|
||||
Bbox.push_back(Rect(x_min, y_min, wd, ht));
|
||||
confidence.push_back(confidence_);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
TextDetectorCNNImpl(const String& modelArchFilename, const String& modelWeightsFilename, std::vector<Size> detectionSizes) :
|
||||
sizes_(detectionSizes)
|
||||
{
|
||||
net_ = readNet(modelWeightsFilename, modelArchFilename);
|
||||
CV_Assert(!net_.empty());
|
||||
inputChannelCount_ = 3;
|
||||
}
|
||||
|
||||
void detect(InputArray inputImage_, std::vector<Rect>& Bbox, std::vector<float>& confidence) CV_OVERRIDE
|
||||
{
|
||||
CV_CheckEQ(inputImage_.channels(), inputChannelCount_, "");
|
||||
Mat inputImage = inputImage_.getMat();
|
||||
Bbox.resize(0);
|
||||
confidence.resize(0);
|
||||
|
||||
for(size_t i = 0; i < sizes_.size(); i++)
|
||||
{
|
||||
Size inputGeometry = sizes_[i];
|
||||
net_.setInput(blobFromImage(inputImage, 1, inputGeometry, Scalar(123, 117, 104), false, false), "data");
|
||||
Mat outputNet = net_.forward();
|
||||
int nbrTextBoxes = outputNet.size[2];
|
||||
int nCol = outputNet.size[3];
|
||||
int outputChannelCount = outputNet.size[1];
|
||||
CV_CheckEQ(outputChannelCount, 1, "");
|
||||
getOutputs((float*)(outputNet.data), nbrTextBoxes, nCol, Bbox, confidence, inputImage.size());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ptr<TextDetectorCNN> TextDetectorCNN::create(const String &modelArchFilename, const String &modelWeightsFilename, std::vector<Size> detectionSizes)
|
||||
{
|
||||
return makePtr<TextDetectorCNNImpl>(modelArchFilename, modelWeightsFilename, detectionSizes);
|
||||
}
|
||||
|
||||
Ptr<TextDetectorCNN> TextDetectorCNN::create(const String &modelArchFilename, const String &modelWeightsFilename)
|
||||
{
|
||||
return create(modelArchFilename, modelWeightsFilename, std::vector<Size>(1, Size(300, 300)));
|
||||
}
|
||||
} //namespace text
|
||||
} //namespace cv
|
||||
@@ -0,0 +1,863 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <limits>
|
||||
#include <stack>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace cv {
|
||||
namespace text {
|
||||
|
||||
namespace {
|
||||
|
||||
struct SWTPoint {
|
||||
int x;
|
||||
int y;
|
||||
float SWT;
|
||||
};
|
||||
|
||||
struct Ray {
|
||||
SWTPoint p;
|
||||
SWTPoint q;
|
||||
std::vector<SWTPoint> points;
|
||||
};
|
||||
|
||||
struct Component {
|
||||
SWTPoint BB_pointP;
|
||||
SWTPoint BB_pointQ;
|
||||
float cx;
|
||||
float cy;
|
||||
float median;
|
||||
float mean;
|
||||
int length, width;
|
||||
std::vector<SWTPoint> points;
|
||||
};
|
||||
|
||||
struct ComponentAttr {
|
||||
float mean, variance, median;
|
||||
int xmin, ymin;
|
||||
int xmax, ymax;
|
||||
float length, width;
|
||||
};
|
||||
|
||||
struct ChannelAverage {
|
||||
float Red, Green, Blue;
|
||||
};
|
||||
|
||||
struct Direction {
|
||||
float x, y;
|
||||
};
|
||||
|
||||
struct ChainedComponent {
|
||||
int chainIndexA;
|
||||
int chainIndexB;
|
||||
std::vector<int> componentIndices;
|
||||
float chainDist;
|
||||
Direction dir;
|
||||
bool merged;
|
||||
};
|
||||
|
||||
const Scalar BLUE (255, 0, 0);
|
||||
const Scalar GREEN(0, 255, 0);
|
||||
const Scalar RED (0, 0, 255);
|
||||
void SWTFirstPass (const Mat& edgeImage, const Mat& gradientX, const Mat& gradientY, bool dark_on_light, Mat & SWTImage, std::vector<Ray> & rays);
|
||||
void SWTSecondPass (Mat & SWTImage, std::vector<Ray> & rays);
|
||||
void normalizeAndScale (const Mat& SWTImage, Mat& output);
|
||||
std::vector<std::vector<SWTPoint>> getComponents (const Mat& SWTImage);
|
||||
ComponentAttr getAttributes(const vector<SWTPoint>& component, const Mat& SWTImage);
|
||||
void renderComponents (const Mat& SWTImage, const std::vector<Component>& components, Mat& output);
|
||||
std::vector<Component> filterComponents(const Mat& SWTImage, const std::vector<std::vector<SWTPoint>>& components, bool skipChecks);
|
||||
void renderComponentBBs (const std::vector<Component>& components, Mat& output);
|
||||
vector<cv::Rect> findValidChains(const Mat& input_image, const Mat& SWTImage, const std::vector<Component>& components, OutputArray output, std::vector<cv::Rect> & chainedTextRegions);
|
||||
vector<cv::Rect> getComponentBBs (const std::vector<Component>& components);
|
||||
bool chainSortDist (const ChainedComponent& Chainl, const ChainedComponent& Chainr);
|
||||
bool chainSortLength (const ChainedComponent& Chainl, const ChainedComponent& Chainr);
|
||||
|
||||
|
||||
// A utility function to add an edge in an
|
||||
// undirected graph.
|
||||
static inline
|
||||
void addEdge(std::vector< std::vector<int> >& adj, int u, int v)
|
||||
{
|
||||
adj[u].push_back(v);
|
||||
adj[v].push_back(u);
|
||||
}
|
||||
|
||||
static
|
||||
void DFSUtil(int v, std::vector<bool> & visited, std::vector< std::vector<int> >& adj, int label, std::vector<int> &component_id)
|
||||
{
|
||||
stack<int> s;
|
||||
s.push(v);
|
||||
while(!s.empty()){
|
||||
v = s.top();
|
||||
s.pop();
|
||||
if(!visited[v])
|
||||
{
|
||||
// Mark the current node as visited and label it as belonging to the current component
|
||||
visited[v] = true;
|
||||
component_id[v] = label;
|
||||
// Recur for all the vertices
|
||||
// adjacent to this vertex
|
||||
for (size_t i = 0; i < adj[v].size(); i++) {
|
||||
int neighbour = adj[v][i];
|
||||
if(!visited[neighbour])
|
||||
{
|
||||
s.push(neighbour);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
int connected_components(std::vector< std::vector<int> >& adj, std::vector<int> &component_id, int num_vertices)
|
||||
{
|
||||
std::vector<bool> visited(num_vertices, false);
|
||||
|
||||
int label = 0;
|
||||
for (int v=0; v<num_vertices; v++)
|
||||
{
|
||||
if (visited[v] == false)
|
||||
{
|
||||
DFSUtil(v, visited, adj, label, component_id);
|
||||
label++;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
void SWTFirstPass(const Mat& edgeImage, const Mat& gradientX, const Mat& gradientY, bool dark_on_light, Mat & SWTImage, std::vector<Ray> & rays)
|
||||
{
|
||||
SWTImage.setTo(Scalar::all(-1));
|
||||
|
||||
for(int row = 0; row < edgeImage.rows; row++ ){
|
||||
for ( int col = 0; col < edgeImage.cols; col++ ){
|
||||
uchar canny = edgeImage.at<uchar>(row, col);
|
||||
if (canny <= 0) continue;
|
||||
|
||||
float dx = gradientX.at<float>(row, col);
|
||||
float dy = gradientY.at<float>(row, col);
|
||||
float mag = sqrt(dx * dx + dy * dy);
|
||||
dx = dx / mag;
|
||||
dy = dy / mag;
|
||||
|
||||
if (dark_on_light){
|
||||
dx = -dx;
|
||||
dy = -dy;
|
||||
}
|
||||
|
||||
Ray ray;
|
||||
SWTPoint p;
|
||||
p.x = col;
|
||||
p.y = row;
|
||||
ray.p = p;
|
||||
std::vector<SWTPoint> points;
|
||||
points.push_back(p);
|
||||
float curPosX = (float) col + (float) 0.5;
|
||||
float curPosY = (float) row + (float) 0.5;
|
||||
int curPixX = col;
|
||||
int curPixY = row;
|
||||
float inc = (float) 0.05;
|
||||
while (true) {
|
||||
curPosX += inc * dx;
|
||||
curPosY += inc * dy;
|
||||
if ((int)(floor(curPosX)) != curPixX || (int)(floor(curPosY)) != curPixY) {
|
||||
curPixX = (int)(floor(curPosX));
|
||||
curPixY = (int)(floor(curPosY));
|
||||
if (curPixX < 0 || (curPixX >= SWTImage.cols) || curPixY < 0 || (curPixY >= SWTImage.rows)) {
|
||||
break;
|
||||
}
|
||||
SWTPoint pt;
|
||||
pt.x = curPixX;
|
||||
pt.y = curPixY;
|
||||
points.push_back(pt);
|
||||
if (edgeImage.at<uchar>(curPixY, curPixX) > 0) {
|
||||
ray.q = pt;
|
||||
float G_xt = gradientX.at<float>(curPixY,curPixX);
|
||||
float G_yt = gradientY.at<float>(curPixY,curPixX);
|
||||
mag = sqrt( (G_xt * G_xt) + (G_yt * G_yt) );
|
||||
G_xt = G_xt / mag;
|
||||
G_yt = G_yt / mag;
|
||||
if (dark_on_light){
|
||||
G_xt = -G_xt;
|
||||
G_yt = -G_yt;
|
||||
}
|
||||
|
||||
if (acos(dx * -G_xt + dy * -G_yt) < CV_PI/2.0 ) {
|
||||
float length = sqrt( ((float)ray.q.x - (float)ray.p.x)*((float)ray.q.x - (float)ray.p.x) + ((float)ray.q.y - (float)ray.p.y)*((float)ray.q.y - (float)ray.p.y));
|
||||
for (std::vector<SWTPoint>::iterator pit = points.begin(); pit != points.end(); pit++) {
|
||||
if (SWTImage.at<float>(pit->y, pit->x) < 0) {
|
||||
SWTImage.at<float>(pit->y, pit->x) = length;
|
||||
} else {
|
||||
SWTImage.at<float>(pit->y, pit->x) = std::min(length, SWTImage.at<float>(pit->y, pit->x));
|
||||
}
|
||||
}
|
||||
ray.points = points;
|
||||
rays.push_back(ray);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static inline
|
||||
bool sortBySWT(const SWTPoint &lhs, const SWTPoint &rhs)
|
||||
{
|
||||
return lhs.SWT < rhs.SWT;
|
||||
}
|
||||
|
||||
|
||||
void SWTSecondPass (Mat & SWTImage, std::vector<Ray> & rays) {
|
||||
for (std::vector<Ray>::iterator rit = rays.begin(); rit != rays.end(); rit++) {
|
||||
for (std::vector<SWTPoint>::iterator pit = rit->points.begin(); pit != rit->points.end(); pit++) {
|
||||
pit->SWT = SWTImage.at<float>(pit->y, pit->x);
|
||||
}
|
||||
std::sort(rit->points.begin(), rit->points.end(), sortBySWT);
|
||||
float median = (rit -> points[rit -> points.size()/2]).SWT;
|
||||
for (std::vector<SWTPoint>::iterator pit = rit->points.begin(); pit != rit->points.end(); pit++) {
|
||||
SWTImage.at<float>(pit->y, pit->x) = std::min(pit->SWT, median);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void normalizeAndScale (const Mat& SWTImage, Mat& output) {
|
||||
CV_CheckTypeEQ(SWTImage.type(), CV_32FC1, "");
|
||||
CV_CheckTypeEQ(output.type(), CV_8UC1, "");
|
||||
|
||||
Mat outputTemp(output.size(), CV_32FC1);
|
||||
|
||||
float maxSWT = 0;
|
||||
float minSWT = (float) FLT_MAX;
|
||||
for(int row = 0; row < SWTImage.rows; row++){
|
||||
for (int col = 0; col < SWTImage.cols; col++){
|
||||
float val = SWTImage.at<float>(row, col);
|
||||
if (val < 0)
|
||||
continue;
|
||||
maxSWT = std::max(val, maxSWT);
|
||||
minSWT = std::min(val, minSWT);
|
||||
}
|
||||
}
|
||||
|
||||
float amplitude = maxSWT - minSWT;
|
||||
for(int row = 0; row < SWTImage.rows; row++){
|
||||
for (int col = 0; col < SWTImage.cols; col++){
|
||||
float val = SWTImage.at<float>(row, col);
|
||||
if (val < 0) {
|
||||
outputTemp.at<float>(row, col) = 1;
|
||||
}
|
||||
else {
|
||||
outputTemp.at<float>(row, col) = (val - minSWT) / amplitude;
|
||||
}
|
||||
}
|
||||
}
|
||||
outputTemp.convertTo(output, CV_8UC1, 255);
|
||||
}
|
||||
|
||||
std::vector<std::vector<SWTPoint>> getComponents (const Mat& SWTImage) {
|
||||
std::unordered_map<int, int> Pix2Node;
|
||||
std::unordered_map<int, SWTPoint> Node2Pix;
|
||||
|
||||
|
||||
int num_vertices = 0;
|
||||
|
||||
for(int row = 0; row < SWTImage.rows; row++){
|
||||
for (int col = 0; col < SWTImage.cols; col++){
|
||||
float val = SWTImage.at<float>(row, col);
|
||||
if (val < 0) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
Pix2Node[row * SWTImage.cols + col] = num_vertices;
|
||||
SWTPoint p;
|
||||
p.x = col;
|
||||
p.y = row;
|
||||
Node2Pix[num_vertices] = p;
|
||||
num_vertices++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector< vector<int> > graph(num_vertices);
|
||||
|
||||
for(int row = 0; row < SWTImage.rows; row++){
|
||||
for (int col = 0; col < SWTImage.cols; col++){
|
||||
float val = SWTImage.at<float>(row, col);
|
||||
if (val < 0) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
int currentNode = Pix2Node[row * SWTImage.cols + col];
|
||||
if (col+1 < SWTImage.cols) {
|
||||
float right = SWTImage.at<float>(row, col+1);
|
||||
if (right > 0 && (val/right <= 3.0 || right/val <= 3.0))
|
||||
addEdge(graph, currentNode, Pix2Node.at(row * SWTImage.cols + col + 1));
|
||||
}
|
||||
if (row+1 < SWTImage.rows) {
|
||||
if (col+1 < SWTImage.cols) {
|
||||
float right_down = SWTImage.at<float>(row+1, col+1);
|
||||
if (right_down > 0 && (val/right_down <= 3.0 || right_down/val <= 3.0))
|
||||
addEdge(graph, currentNode, Pix2Node.at((row+1) * SWTImage.cols + col + 1));
|
||||
}
|
||||
float down = SWTImage.at<float>(row+1, col);
|
||||
if (down > 0 && (val/down <= 3.0 || down/val <= 3.0))
|
||||
addEdge(graph, currentNode, Pix2Node.at((row+1) * SWTImage.cols + col));
|
||||
if (col-1 >= 0) {
|
||||
float left_down = SWTImage.at<float>(row+1, col-1);
|
||||
if (left_down > 0 && (val/left_down <= 3.0 || left_down/val <= 3.0))
|
||||
addEdge(graph, currentNode, Pix2Node.at((row+1) * SWTImage.cols + col - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> component_id(num_vertices);
|
||||
|
||||
int num_comp = connected_components(graph, component_id, num_vertices);
|
||||
|
||||
std::vector<std::vector<SWTPoint> > components;
|
||||
components.reserve(num_comp);
|
||||
|
||||
for (int j = 0; j < num_comp; j++) {
|
||||
std::vector<SWTPoint> tmp;
|
||||
components.push_back(tmp);
|
||||
}
|
||||
for (int j = 0; j < num_vertices; j++) {
|
||||
SWTPoint p = Node2Pix[j];
|
||||
components[component_id[j]].push_back(p);
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
ComponentAttr getAttributes(const vector<SWTPoint>& component, const Mat& SWTImage)
|
||||
{
|
||||
CV_Assert(!component.empty());
|
||||
|
||||
std::vector<float> temp;
|
||||
temp.reserve(component.size());
|
||||
ComponentAttr attributes;
|
||||
attributes.mean = 0;
|
||||
attributes.variance = 0;
|
||||
|
||||
attributes.xmin = 100000;
|
||||
attributes.ymin = 100000;
|
||||
|
||||
attributes.xmax = 0;
|
||||
attributes.ymax = 0;
|
||||
|
||||
float sum = 0;
|
||||
|
||||
for (size_t i = 0; i < component.size(); i++) {
|
||||
const SWTPoint& component_i = component[i];
|
||||
float val = SWTImage.at<float>(component_i.y, component_i.x);
|
||||
sum += val;
|
||||
temp.push_back(val);
|
||||
attributes.xmin = std::min(attributes.xmin, component_i.x);
|
||||
attributes.ymin = std::min(attributes.ymin, component_i.y);
|
||||
attributes.xmax = std::max(attributes.xmax, component_i.x);
|
||||
attributes.ymax = std::max(attributes.ymax, component_i.y);
|
||||
}
|
||||
attributes.mean = sum / ((float)component.size());
|
||||
for (size_t i = 0; i < component.size(); i++) {
|
||||
attributes.variance += (temp[i] - attributes.mean) * (temp[i] - attributes.mean);
|
||||
}
|
||||
|
||||
attributes.variance = attributes.variance / ((float)component.size());
|
||||
std::sort(temp.begin(),temp.end());
|
||||
attributes.median = temp[temp.size()/2];
|
||||
|
||||
attributes.length = (float) (attributes.xmax - attributes.xmin + 1);
|
||||
attributes.width = (float) (attributes.ymax - attributes.ymin + 1);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
void renderComponents (const Mat& SWTImage, const std::vector<Component>& components, Mat& output)
|
||||
{
|
||||
output.setTo(0);
|
||||
|
||||
for (size_t i = 0; i < components.size(); i++) {
|
||||
const Component& component = components[i];
|
||||
for (size_t j = 0; j < component.points.size(); j++)
|
||||
{
|
||||
const SWTPoint& pt = component.points[j];
|
||||
output.at<float>(pt.y, pt.x) = SWTImage.at<float>(pt.y, pt.x);
|
||||
}
|
||||
}
|
||||
for(int row = 0; row < output.rows; row++ ){
|
||||
float* ptr = output.ptr<float>(row);
|
||||
for ( int col = 0; col < output.cols; col++ ){
|
||||
if (*ptr == 0) {
|
||||
*ptr = -1;
|
||||
}
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
float maxVal = 0;
|
||||
float minVal = (float) FLT_MAX;
|
||||
for(int row = 0; row < output.rows; row++ ){
|
||||
const float* ptr = output.ptr<float>(row);
|
||||
for ( int col = 0; col < output.cols; col++ )
|
||||
{
|
||||
float v = ptr[col];
|
||||
if (v != 0)
|
||||
{
|
||||
maxVal = std::max(*ptr, maxVal);
|
||||
minVal = std::min(*ptr, minVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
float difference = maxVal - minVal;
|
||||
for(int row = 0; row < output.rows; row++ ) {
|
||||
float* ptr = output.ptr<float>(row);
|
||||
for (int col = 0; col < output.cols; col++)
|
||||
{
|
||||
float& v = ptr[col];
|
||||
if (v < 1) {
|
||||
v = 1;
|
||||
} else {
|
||||
v = (v - minVal)/difference;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::vector<Component> filterComponents(const Mat& SWTImage, const std::vector<std::vector<SWTPoint>>& components, bool skipChecks)
|
||||
{
|
||||
const int NUM_THETA = 36; // in 180 (CV_PI)
|
||||
|
||||
std::vector<Component> filteredComponents;
|
||||
filteredComponents.reserve(components.size());
|
||||
for (size_t i = 0; i < components.size(); i++)
|
||||
{
|
||||
const vector<SWTPoint>& component = components[i];
|
||||
ComponentAttr attributes = getAttributes(component, SWTImage);
|
||||
if (!skipChecks && attributes.variance > 0.5 * attributes.mean) continue;
|
||||
if (!skipChecks && attributes.width > 300) continue;
|
||||
|
||||
|
||||
float area = attributes.length * attributes.width;
|
||||
|
||||
// compute the rotated bounding box
|
||||
for (int theta_i = 0; theta_i < (NUM_THETA / 2); theta_i++)
|
||||
{
|
||||
float theta = (float)(theta_i * (CV_PI / NUM_THETA));
|
||||
float
|
||||
xmin = 1000000,
|
||||
ymin = 1000000,
|
||||
xmax = 0,
|
||||
ymax = 0;
|
||||
for (size_t j = 0; j < component.size(); j++)
|
||||
{
|
||||
// TODO(optimization) use pre-calculated cos/sin table through [theta_i] indexing
|
||||
float xtemp = component[j].x * cos(theta) + component[j].y * -sin(theta);
|
||||
float ytemp = component[j].x * sin(theta) + component[j].y * cos(theta);
|
||||
xmin = std::min(xtemp,xmin);
|
||||
xmax = std::max(xtemp,xmax);
|
||||
ymin = std::min(ytemp,ymin);
|
||||
ymax = std::max(ytemp,ymax);
|
||||
}
|
||||
float ltemp = xmax - xmin + 1;
|
||||
float wtemp = ymax - ymin + 1;
|
||||
if (ltemp*wtemp < area) {
|
||||
area = ltemp*wtemp;
|
||||
attributes.length = ltemp;
|
||||
attributes.width = wtemp;
|
||||
}
|
||||
}
|
||||
|
||||
if (!skipChecks && (attributes.length/attributes.width < 1./10. || attributes.length/attributes.width > 10.)) continue;
|
||||
|
||||
Component acceptedComponent;
|
||||
acceptedComponent.length = (int) attributes.length;
|
||||
|
||||
acceptedComponent.cx = ((float) (attributes.xmax+attributes.xmin)) / 2;
|
||||
acceptedComponent.cy = ((float) (attributes.ymax+attributes.ymin)) / 2;
|
||||
|
||||
acceptedComponent.BB_pointP.x = attributes.xmin;
|
||||
acceptedComponent.BB_pointP.y = attributes.ymin;
|
||||
|
||||
acceptedComponent.BB_pointQ.x = attributes.xmax;
|
||||
acceptedComponent.BB_pointQ.y = attributes.ymax;
|
||||
|
||||
acceptedComponent.length = attributes.xmax - attributes.xmin + 1;
|
||||
acceptedComponent.width = attributes.ymax - attributes.ymin + 1;
|
||||
|
||||
acceptedComponent.mean = attributes.mean;
|
||||
acceptedComponent.median = attributes.median;
|
||||
|
||||
acceptedComponent.points = component;
|
||||
|
||||
filteredComponents.push_back(acceptedComponent);
|
||||
}
|
||||
if (!skipChecks){
|
||||
std::vector<Component> tempComp;
|
||||
tempComp.reserve(filteredComponents.size());
|
||||
|
||||
for (size_t i = 0; i < filteredComponents.size(); i++) {
|
||||
int count = 0;
|
||||
Component& compi = filteredComponents[i];
|
||||
for (size_t j = 0; j < filteredComponents.size(); j++) {
|
||||
if (i != j) {
|
||||
Component& compj = filteredComponents[j];
|
||||
if (compi.BB_pointP.x <= compj.cx && compi.BB_pointQ.x >= compj.cx &&
|
||||
compi.BB_pointP.y <= compj.cy && compi.BB_pointQ.y >= compj.cy) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count < 2) {
|
||||
tempComp.push_back(compi);
|
||||
}
|
||||
}
|
||||
filteredComponents = tempComp;
|
||||
}
|
||||
|
||||
return filteredComponents;
|
||||
};
|
||||
|
||||
void renderComponentBBs(const std::vector<Component>& components, Mat& output)
|
||||
{
|
||||
for (size_t i = 0; i < components.size(); i++)
|
||||
{
|
||||
const Component& compi = components[i];
|
||||
Scalar c;
|
||||
if (i % 3 == 0) {
|
||||
c = BLUE;
|
||||
}
|
||||
else if (i % 3 == 1) {
|
||||
c = GREEN;
|
||||
}
|
||||
else {
|
||||
c = RED;
|
||||
}
|
||||
rectangle(output, Point(compi.BB_pointP.x, compi.BB_pointP.y), Point(compi.BB_pointQ.x, compi.BB_pointQ.y), c, 2);
|
||||
}
|
||||
}
|
||||
|
||||
vector<cv::Rect> getComponentBBs (const std::vector<Component>& components)
|
||||
{
|
||||
vector<cv::Rect> bbs;
|
||||
for (size_t i = 0; i < components.size(); i++) {
|
||||
const Component& compi = components[i];
|
||||
int wd = compi.BB_pointP.x - compi.BB_pointQ.x;
|
||||
int ht = compi.BB_pointP.y - compi.BB_pointQ.y;
|
||||
if (wd < 0) wd = -wd;
|
||||
if (ht < 0) ht = -ht;
|
||||
|
||||
bbs.push_back(Rect(min(compi.BB_pointP.x, compi.BB_pointQ.x), min(compi.BB_pointP.y, compi.BB_pointQ.y), wd, ht));
|
||||
}
|
||||
return bbs;
|
||||
}
|
||||
|
||||
bool chainSortDist(const ChainedComponent& Chainl, const ChainedComponent& Chainr)
|
||||
{
|
||||
return Chainl.chainDist < Chainr.chainDist;
|
||||
}
|
||||
|
||||
bool chainSortLength(const ChainedComponent& Chainl, const ChainedComponent& Chainr)
|
||||
{
|
||||
return Chainl.componentIndices.size() < Chainr.componentIndices.size();
|
||||
}
|
||||
|
||||
vector<cv::Rect> findValidChains(const Mat& input_image, const Mat& SWTImage, const std::vector<Component>& components, OutputArray output, std::vector<cv::Rect> & chainedTextRegions)
|
||||
{
|
||||
std::vector<ChannelAverage> colorAverages;
|
||||
colorAverages.reserve(components.size());
|
||||
for (size_t i = 0; i < components.size(); i++)
|
||||
{
|
||||
const Component& compi = components[i];
|
||||
CV_Assert(!compi.points.empty());
|
||||
ChannelAverage avgCompi;
|
||||
avgCompi.Red = 0;
|
||||
avgCompi.Green = 0;
|
||||
avgCompi.Blue = 0;
|
||||
for (size_t j = 0; j < compi.points.size(); j++) {
|
||||
int x = compi.points[j].x;
|
||||
int y = compi.points[j].y;
|
||||
avgCompi.Red += (float) input_image.at<uchar>(y, x*3);
|
||||
avgCompi.Green += (float) input_image.at<uchar>(y, x*3+1);
|
||||
avgCompi.Blue += (float) input_image.at<uchar>(y, x*3+2);
|
||||
}
|
||||
avgCompi.Red /= compi.points.size();
|
||||
avgCompi.Green /= compi.points.size();
|
||||
avgCompi.Blue /= compi.points.size();
|
||||
colorAverages.push_back(avgCompi);
|
||||
}
|
||||
std::vector<ChainedComponent> chains;
|
||||
for (size_t i = 0; i < components.size(); i++) {
|
||||
const Component& compi = components[i];
|
||||
for (size_t j = i+1; j < components.size(); j++) {
|
||||
const Component& compj = components[j];
|
||||
if ((compi.median / compj.median <= 2.0 || compj.median / compi.median <= 2.0)
|
||||
&& (compi.width/compj.width <= 2.0 || compj.width/compi.width <= 2.0)) {
|
||||
float dist = (compi.cx - compj.cx) * (compi.cx - compj.cx) +
|
||||
(compi.cy - compj.cy) * (compi.cy - compj.cy);
|
||||
float colorDist = (colorAverages[i].Red - colorAverages[j].Red) * (colorAverages[i].Red - colorAverages[j].Red) +
|
||||
(colorAverages[i].Green - colorAverages[j].Green) * (colorAverages[i].Green - colorAverages[j].Green) +
|
||||
(colorAverages[i].Blue - colorAverages[j].Blue) * (colorAverages[i].Blue - colorAverages[j].Blue);
|
||||
if (dist < 9*(float)(std::max(std::min(compi.length,compi.width),std::min(compj.length,compj.width)))
|
||||
*(float)(std::max(std::min(compi.length,compi.width),std::min(compj.length,compj.width))) && colorDist < 1600) {
|
||||
ChainedComponent chain;
|
||||
chain.chainIndexA = (int)i;
|
||||
chain.chainIndexB = (int)j;
|
||||
vector <int> componentIndices;
|
||||
componentIndices.push_back((int)i);
|
||||
componentIndices.push_back((int)j);
|
||||
chain.componentIndices = componentIndices;
|
||||
chain.chainDist = dist;
|
||||
|
||||
float dx = compi.cx - compj.cx;
|
||||
float dy = compi.cy - compj.cy;
|
||||
float mod = sqrt(dx * dx + dy * dy);
|
||||
dx = dx / mod;
|
||||
dy = dy / mod;
|
||||
|
||||
Direction dir;
|
||||
dir.x = dx;
|
||||
dir.y = dy;
|
||||
chain.dir = dir;
|
||||
chains.push_back(chain);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(chains.begin(), chains.end(), chainSortDist);
|
||||
|
||||
const float alignmentThreshold = (float) CV_PI / 6;
|
||||
const float alignmentThreshold_cos = cos(alignmentThreshold);
|
||||
int merges = 1;
|
||||
while (merges > 0) {
|
||||
for (size_t i = 0; i < chains.size(); i++) {
|
||||
chains[i].merged = false;
|
||||
}
|
||||
merges = 0;
|
||||
std::vector<ChainedComponent> chainsAfterMerging;
|
||||
for (size_t i = 0; i < chains.size(); i++)
|
||||
{
|
||||
ChainedComponent& chains_i = chains[i];
|
||||
for (size_t j = 0; j < chains.size(); j++)
|
||||
{
|
||||
ChainedComponent& chains_j = chains[j];
|
||||
if (i!=j && !chains_i.merged && !chains_j.merged) {
|
||||
if (chains_i.chainIndexA == chains_j.chainIndexA) {
|
||||
if (chains_i.dir.x * -chains_j.dir.x + chains_i.dir.y * -chains_j.dir.y > alignmentThreshold_cos) {
|
||||
chains_i.chainIndexA = chains_j.chainIndexB;
|
||||
for (std::vector<int>::iterator it = chains_j.componentIndices.begin(); it != chains_j.componentIndices.end(); it++) {
|
||||
chains_i.componentIndices.push_back(*it);
|
||||
}
|
||||
float d_x = components[chains_i.chainIndexA].cx - components[chains_i.chainIndexB].cx;
|
||||
float d_y = components[chains_i.chainIndexA].cy - components[chains_i.chainIndexB].cy;
|
||||
chains_i.chainDist = d_x * d_x + d_y * d_y;
|
||||
|
||||
float mag = sqrt(d_x*d_x + d_y*d_y);
|
||||
d_x = d_x / mag;
|
||||
d_y = d_y / mag;
|
||||
Direction dir;
|
||||
dir.x = d_x;
|
||||
dir.y = d_y;
|
||||
chains_i.dir = dir;
|
||||
chains_j.merged = true;
|
||||
merges++;
|
||||
}
|
||||
} else if (chains_i.chainIndexA == chains_j.chainIndexB) {
|
||||
if (chains_i.dir.x * chains_j.dir.x + chains_i.dir.y * chains_j.dir.y > alignmentThreshold_cos) {
|
||||
chains_i.chainIndexA = chains_j.chainIndexA;
|
||||
for (std::vector<int>::iterator it = chains_j.componentIndices.begin(); it != chains_j.componentIndices.end(); it++) {
|
||||
chains_i.componentIndices.push_back(*it);
|
||||
}
|
||||
float d_x = components[chains_i.chainIndexA].cx - components[chains_i.chainIndexB].cx;
|
||||
float d_y = components[chains_i.chainIndexA].cy - components[chains_i.chainIndexB].cy;
|
||||
chains_i.chainDist = d_x * d_x + d_y * d_y;
|
||||
|
||||
float mag = sqrt(d_x*d_x + d_y*d_y);
|
||||
d_x = d_x / mag;
|
||||
d_y = d_y / mag;
|
||||
Direction dir;
|
||||
dir.x = d_x;
|
||||
dir.y = d_y;
|
||||
chains_i.dir = dir;
|
||||
chains_j.merged = true;
|
||||
merges++;
|
||||
}
|
||||
} else if (chains_i.chainIndexB == chains_j.chainIndexA) {
|
||||
if (chains_i.dir.x * chains_j.dir.x + chains_i.dir.y * chains_j.dir.y > alignmentThreshold_cos) {
|
||||
chains_i.chainIndexB = chains_j.chainIndexB;
|
||||
for (std::vector<int>::iterator it = chains_j.componentIndices.begin(); it != chains_j.componentIndices.end(); it++) {
|
||||
chains_i.componentIndices.push_back(*it);
|
||||
}
|
||||
float d_x = components[chains_i.chainIndexA].cx - components[chains_i.chainIndexB].cx;
|
||||
float d_y = components[chains_i.chainIndexA].cy - components[chains_i.chainIndexB].cy;
|
||||
chains_i.chainDist = d_x * d_x + d_y * d_y;
|
||||
|
||||
float mag = sqrt(d_x*d_x + d_y*d_y);
|
||||
d_x = d_x / mag;
|
||||
d_y = d_y / mag;
|
||||
Direction dir;
|
||||
dir.x = d_x;
|
||||
dir.y = d_y;
|
||||
chains_i.dir = dir;
|
||||
chains_j.merged = true;
|
||||
merges++;
|
||||
}
|
||||
} else if (chains_i.chainIndexB == chains_j.chainIndexB) {
|
||||
if (chains_i.dir.x * -chains_j.dir.x + chains_i.dir.y * -chains_j.dir.y > alignmentThreshold_cos) {
|
||||
chains_i.chainIndexB = chains_j.chainIndexA;
|
||||
for (std::vector<int>::iterator it = chains_j.componentIndices.begin(); it != chains_j.componentIndices.end(); it++) {
|
||||
chains_i.componentIndices.push_back(*it);
|
||||
}
|
||||
float d_x = components[chains_i.chainIndexA].cx - components[chains_i.chainIndexB].cx;
|
||||
float d_y = components[chains_i.chainIndexA].cy - components[chains_i.chainIndexB].cy;
|
||||
chains_i.chainDist = d_x * d_x + d_y * d_y;
|
||||
|
||||
float mag = sqrt(d_x*d_x + d_y*d_y);
|
||||
d_x = d_x / mag;
|
||||
d_y = d_y / mag;
|
||||
Direction dir;
|
||||
dir.x = d_x;
|
||||
dir.y = d_y;
|
||||
chains_i.dir = dir;
|
||||
chains_j.merged = true;
|
||||
merges++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
std::vector<ChainedComponent> newchains;
|
||||
for (size_t i = 0; i < chains.size(); i++) {
|
||||
if (!chains[i].merged) {
|
||||
newchains.push_back(chains[i]);
|
||||
}
|
||||
}
|
||||
chains = newchains;
|
||||
std::stable_sort(chains.begin(), chains.end(), chainSortLength);
|
||||
}
|
||||
|
||||
std::vector<ChainedComponent> newchains;
|
||||
std::vector<std::vector<SWTPoint>> componentsPointsVector;
|
||||
vector<Component> finalComponents;
|
||||
finalComponents.reserve(components.size());
|
||||
std::vector<bool> componentIncluded(components.size(), false);
|
||||
for (size_t i = 0; i < chains.size(); i++)
|
||||
{
|
||||
ChainedComponent& chains_i = chains[i];
|
||||
if (chains_i.componentIndices.size() >= 3) {
|
||||
newchains.push_back(chains_i);
|
||||
int xmin,xmax,ymin,ymax;
|
||||
xmin = 1000000;
|
||||
ymin = 1000000;
|
||||
xmax = 0;
|
||||
ymax = 0;
|
||||
for (size_t j = 0; j < chains_i.componentIndices.size(); j++) {
|
||||
int idx = chains_i.componentIndices[j];
|
||||
if (componentIncluded[idx])
|
||||
continue;
|
||||
componentIncluded[idx] = true;
|
||||
const Component& acceptedComponent = components[idx];
|
||||
std::vector<SWTPoint> componentPoints;
|
||||
for (size_t k = 0; k < acceptedComponent.points.size(); k++)
|
||||
{
|
||||
const SWTPoint& pt = acceptedComponent.points[k];
|
||||
componentPoints.push_back(pt);
|
||||
xmin = min(xmin, pt.x);
|
||||
ymin = min(ymin, pt.y);
|
||||
xmax = max(xmax, pt.x);
|
||||
ymax = max(ymax, pt.y);
|
||||
}
|
||||
componentsPointsVector.push_back(componentPoints);
|
||||
}
|
||||
int wd = xmax - xmin;
|
||||
int ht = ymax - ymin;
|
||||
chainedTextRegions.push_back(Rect(xmin, ymin, wd, ht));
|
||||
}
|
||||
}
|
||||
finalComponents = filterComponents(SWTImage, componentsPointsVector, true);
|
||||
chains = newchains;
|
||||
std::stable_sort(chains.begin(), chains.end(), chainSortLength);
|
||||
|
||||
if (output.needed())
|
||||
{
|
||||
Mat outTemp(input_image.size(), CV_32FC1);
|
||||
renderComponents(SWTImage, finalComponents, outTemp);
|
||||
Mat outTemp_8u;
|
||||
outTemp.convertTo(outTemp_8u, CV_8UC1, 255.);
|
||||
cvtColor(outTemp_8u, output, COLOR_GRAY2RGB);
|
||||
Mat output_ = output.getMat();
|
||||
renderComponentBBs(finalComponents, output_);
|
||||
}
|
||||
return getComponentBBs(finalComponents);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void detectTextSWT(InputArray input_, CV_OUT std::vector<cv::Rect>& result, bool dark_on_light, OutputArray & draw /*=noArray()*/, OutputArray & chainBBs /*=noArray()*/)
|
||||
{
|
||||
CV_CheckTypeEQ(input_.type(), CV_8UC3, "");
|
||||
|
||||
Mat input = input_.getMat();
|
||||
|
||||
// Convert to grayscale
|
||||
Mat grayImage;
|
||||
cvtColor(input, grayImage, COLOR_BGR2GRAY);
|
||||
// Create Canny Image
|
||||
double threshold_low = 175;
|
||||
double threshold_high = 320;
|
||||
Mat canny_edge_image;
|
||||
Canny (grayImage, canny_edge_image, threshold_low, threshold_high, 3);
|
||||
|
||||
// Create gradient X, gradient Y
|
||||
Mat gaussianImage;
|
||||
grayImage.convertTo(gaussianImage, CV_32FC1, 1./255.);
|
||||
|
||||
|
||||
Mat gradientX;
|
||||
Mat gradientY;
|
||||
GaussianBlur(gaussianImage, gaussianImage, Size(5, 5), 0);
|
||||
Scharr(gaussianImage, gradientX, -1, 1, 0);
|
||||
Scharr(gaussianImage, gradientY, -1, 0, 1);
|
||||
GaussianBlur(gradientX, gradientX, Size(3, 3), 0);
|
||||
GaussianBlur(gradientY, gradientY, Size(3, 3), 0);
|
||||
|
||||
std::vector<Ray> rays;
|
||||
Mat SWTImage( input.size(), CV_32FC1 );
|
||||
|
||||
SWTFirstPass (canny_edge_image, gradientX, gradientY, dark_on_light, SWTImage, rays );
|
||||
|
||||
SWTSecondPass ( SWTImage, rays );
|
||||
|
||||
Mat normalised_image(input.size(), CV_8UC1);
|
||||
normalizeAndScale(SWTImage, normalised_image);
|
||||
|
||||
// Calculate legally connected components from SWT and gradient image.
|
||||
// return type is a vector of vectors, where each outer vector is a component and
|
||||
// the inner vector contains the (y,x) of each pixel in that component.
|
||||
std::vector<std::vector<SWTPoint> > components = getComponents(SWTImage);
|
||||
std::vector<Component> validComponents = filterComponents(SWTImage, components, false);
|
||||
|
||||
vector<cv::Rect> outTextRegions;
|
||||
|
||||
result = findValidChains(input, SWTImage, validComponents, draw, outTextRegions);
|
||||
|
||||
if (chainBBs.needed()) {
|
||||
_InputArray(outTextRegions).copyTo(chainBBs);
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
Reference in New Issue
Block a user