vendor: OpenCV 5.0.0 snapshot at 755e50675d97db9b7d449d8bd6b09888646f6c6e

This commit is contained in:
Gitea Mirror Bot
2026-08-22 00:11:13 +08:00
commit 12022378a3
3872 changed files with 2513409 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+791
View File
@@ -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
+106
View File
@@ -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::
+288
View File
@@ -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);
}
}
}
+50
View File
@@ -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
+98
View File
@@ -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
+863
View File
@@ -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