Skip to content

Commit b1d99df

Browse files
committed
Added a backup adaptive classifier to take over from primary when it fills on a large document
1 parent 78b5e1a commit b1d99df

File tree

5 files changed

+99
-53
lines changed

5 files changed

+99
-53
lines changed

api/baseapi.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -2380,7 +2380,8 @@ void TessBaseAPI::AdaptToCharacter(const char *unichar_repr,
23802380
threshold = tesseract_->matcher_good_threshold;
23812381

23822382
if (blob->outlines)
2383-
tesseract_->AdaptToChar(blob, id, kUnknownFontinfoId, threshold);
2383+
tesseract_->AdaptToChar(blob, id, kUnknownFontinfoId, threshold,
2384+
tesseract_->AdaptedTemplates);
23842385
delete blob;
23852386
}
23862387

ccmain/control.cpp

+14-9
Original file line numberDiff line numberDiff line change
@@ -306,17 +306,22 @@ bool Tesseract::recog_all_words(PAGE_RES* page_res,
306306
page_res_it.restart_page();
307307
// ****************** Pass 1 *******************
308308

309-
// Clear adaptive classifier at the beginning of the page if it is full.
310-
// This is done only at the beginning of the page to ensure that the
311-
// classifier is not reset at an arbitrary point while processing the page,
312-
// which would cripple Passes 2+ if the reset happens towards the end of
313-
// Pass 1 on a page with very difficult text.
314-
// TODO(daria): preemptively clear the classifier if it is almost full.
315-
if (AdaptiveClassifierIsFull()) ResetAdaptiveClassifierInternal();
309+
// If the adaptive classifier is full switch to one we prepared earlier,
310+
// ie on the previous page. If the current adaptive classifier is non-empty,
311+
// prepare a backup starting at this page, in case it fills up. Do all this
312+
// independently for each language.
313+
if (AdaptiveClassifierIsFull()) {
314+
SwitchAdaptiveClassifier();
315+
} else if (!AdaptiveClassifierIsEmpty()) {
316+
StartBackupAdaptiveClassifier();
317+
}
316318
// Now check the sub-langs as well.
317319
for (int i = 0; i < sub_langs_.size(); ++i) {
318-
if (sub_langs_[i]->AdaptiveClassifierIsFull())
319-
sub_langs_[i]->ResetAdaptiveClassifierInternal();
320+
if (sub_langs_[i]->AdaptiveClassifierIsFull()) {
321+
sub_langs_[i]->SwitchAdaptiveClassifier();
322+
} else if (!sub_langs_[i]->AdaptiveClassifierIsEmpty()) {
323+
sub_langs_[i]->StartBackupAdaptiveClassifier();
324+
}
320325
}
321326
// Set up all words ready for recognition, so that if parallelism is on
322327
// all the input and output classes are ready to run the classifier.

classify/adaptmatch.cpp

+69-37
Original file line numberDiff line numberDiff line change
@@ -24,36 +24,36 @@
2424
#endif
2525

2626
#include <ctype.h>
27-
#include "shapeclassifier.h"
2827
#include "ambigs.h"
2928
#include "blobclass.h"
3029
#include "blobs.h"
31-
#include "helpers.h"
32-
#include "normfeat.h"
33-
#include "mfoutline.h"
34-
#include "picofeat.h"
35-
#include "float2int.h"
36-
#include "outfeat.h"
30+
#include "callcpp.h"
31+
#include "classify.h"
32+
#include "const.h"
33+
#include "dict.h"
34+
#include "efio.h"
3735
#include "emalloc.h"
36+
#include "featdefs.h"
37+
#include "float2int.h"
38+
#include "genericvector.h"
39+
#include "globals.h"
40+
#include "helpers.h"
3841
#include "intfx.h"
39-
#include "efio.h"
40-
#include "normmatch.h"
41-
#include "ndminx.h"
4242
#include "intproto.h"
43-
#include "const.h"
44-
#include "globals.h"
45-
#include "werd.h"
46-
#include "callcpp.h"
43+
#include "mfoutline.h"
44+
#include "ndminx.h"
45+
#include "normfeat.h"
46+
#include "normmatch.h"
47+
#include "outfeat.h"
4748
#include "pageres.h"
4849
#include "params.h"
49-
#include "classify.h"
50+
#include "picofeat.h"
51+
#include "shapeclassifier.h"
5052
#include "shapetable.h"
5153
#include "tessclassifier.h"
5254
#include "trainingsample.h"
5355
#include "unicharset.h"
54-
#include "dict.h"
55-
#include "featdefs.h"
56-
#include "genericvector.h"
56+
#include "werd.h"
5757

5858
#include <stdio.h>
5959
#include <string.h>
@@ -420,7 +420,13 @@ void Classify::LearnPieces(const char* fontname, int start, int length,
420420
unicharset.id_to_unichar(class_id), threshold, font_id);
421421
// If filename is not NULL we are doing recognition
422422
// (as opposed to training), so we must have already set word fonts.
423-
AdaptToChar(rotated_blob, class_id, font_id, threshold);
423+
AdaptToChar(rotated_blob, class_id, font_id, threshold, AdaptedTemplates);
424+
if (BackupAdaptedTemplates != NULL) {
425+
// Adapt the backup templates too. They will be used if the primary gets
426+
// too full.
427+
AdaptToChar(rotated_blob, class_id, font_id, threshold,
428+
BackupAdaptedTemplates);
429+
}
424430
} else if (classify_debug_level >= 1) {
425431
tprintf("Can't adapt to %s not in unicharset\n", correct_text);
426432
}
@@ -470,6 +476,10 @@ void Classify::EndAdaptiveClassifier() {
470476
free_adapted_templates(AdaptedTemplates);
471477
AdaptedTemplates = NULL;
472478
}
479+
if (BackupAdaptedTemplates != NULL) {
480+
free_adapted_templates(BackupAdaptedTemplates);
481+
BackupAdaptedTemplates = NULL;
482+
}
473483

474484
if (PreTrainedTemplates != NULL) {
475485
free_int_templates(PreTrainedTemplates);
@@ -607,10 +617,35 @@ void Classify::ResetAdaptiveClassifierInternal() {
607617
}
608618
free_adapted_templates(AdaptedTemplates);
609619
AdaptedTemplates = NewAdaptedTemplates(true);
620+
if (BackupAdaptedTemplates != NULL)
621+
free_adapted_templates(BackupAdaptedTemplates);
622+
BackupAdaptedTemplates = NULL;
610623
NumAdaptationsFailed = 0;
611624
}
612625

626+
// If there are backup adapted templates, switches to those, otherwise resets
627+
// the main adaptive classifier (because it is full.)
628+
void Classify::SwitchAdaptiveClassifier() {
629+
if (BackupAdaptedTemplates == NULL) {
630+
ResetAdaptiveClassifierInternal();
631+
return;
632+
}
633+
if (classify_learning_debug_level > 0) {
634+
tprintf("Switch to backup adaptive classifier (NumAdaptationsFailed=%d)\n",
635+
NumAdaptationsFailed);
636+
}
637+
free_adapted_templates(AdaptedTemplates);
638+
AdaptedTemplates = BackupAdaptedTemplates;
639+
BackupAdaptedTemplates = NULL;
640+
NumAdaptationsFailed = 0;
641+
}
613642

643+
// Resets the backup adaptive classifier to empty.
644+
void Classify::StartBackupAdaptiveClassifier() {
645+
if (BackupAdaptedTemplates != NULL)
646+
free_adapted_templates(BackupAdaptedTemplates);
647+
BackupAdaptedTemplates = NewAdaptedTemplates(true);
648+
}
614649

615650
/*---------------------------------------------------------------------------*/
616651
/**
@@ -839,20 +874,19 @@ bool Classify::AdaptableWord(WERD_RES* word) {
839874
* @param ClassId class to add blob to
840875
* @param FontinfoId font information from pre-trained templates
841876
* @param Threshold minimum match rating to existing template
877+
* @param adaptive_templates current set of adapted templates
842878
*
843879
* Globals:
844-
* - AdaptedTemplates current set of adapted templates
845880
* - AllProtosOn dummy mask to match against all protos
846881
* - AllConfigsOn dummy mask to match against all configs
847882
*
848883
* @return none
849884
* @note Exceptions: none
850885
* @note History: Thu Mar 14 09:36:03 1991, DSJ, Created.
851886
*/
852-
void Classify::AdaptToChar(TBLOB *Blob,
853-
CLASS_ID ClassId,
854-
int FontinfoId,
855-
FLOAT32 Threshold) {
887+
void Classify::AdaptToChar(TBLOB* Blob, CLASS_ID ClassId, int FontinfoId,
888+
FLOAT32 Threshold,
889+
ADAPT_TEMPLATES adaptive_templates) {
856890
int NumFeatures;
857891
INT_FEATURE_ARRAY IntFeatures;
858892
UnicharRating int_result;
@@ -866,12 +900,12 @@ void Classify::AdaptToChar(TBLOB *Blob,
866900
return;
867901

868902
int_result.unichar_id = ClassId;
869-
Class = AdaptedTemplates->Class[ClassId];
903+
Class = adaptive_templates->Class[ClassId];
870904
assert(Class != NULL);
871905
if (IsEmptyAdaptedClass(Class)) {
872-
InitAdaptedClass(Blob, ClassId, FontinfoId, Class, AdaptedTemplates);
906+
InitAdaptedClass(Blob, ClassId, FontinfoId, Class, adaptive_templates);
873907
} else {
874-
IClass = ClassForClassId(AdaptedTemplates->Templates, ClassId);
908+
IClass = ClassForClassId(adaptive_templates->Templates, ClassId);
875909

876910
NumFeatures = GetAdaptiveFeatures(Blob, IntFeatures, &FloatFeatures);
877911
if (NumFeatures <= 0)
@@ -913,7 +947,7 @@ void Classify::AdaptToChar(TBLOB *Blob,
913947
int_result.config, TempConfig->NumTimesSeen);
914948

915949
if (TempConfigReliable(ClassId, TempConfig)) {
916-
MakePermanent(AdaptedTemplates, ClassId, int_result.config, Blob);
950+
MakePermanent(adaptive_templates, ClassId, int_result.config, Blob);
917951
UpdateAmbigsGroup(ClassId, Blob);
918952
}
919953
} else {
@@ -923,15 +957,12 @@ void Classify::AdaptToChar(TBLOB *Blob,
923957
if (classify_learning_debug_level > 2)
924958
DisplayAdaptedChar(Blob, IClass);
925959
}
926-
NewTempConfigId = MakeNewTemporaryConfig(AdaptedTemplates,
927-
ClassId,
928-
FontinfoId,
929-
NumFeatures,
930-
IntFeatures,
931-
FloatFeatures);
960+
NewTempConfigId =
961+
MakeNewTemporaryConfig(adaptive_templates, ClassId, FontinfoId,
962+
NumFeatures, IntFeatures, FloatFeatures);
932963
if (NewTempConfigId >= 0 &&
933964
TempConfigReliable(ClassId, TempConfigFor(Class, NewTempConfigId))) {
934-
MakePermanent(AdaptedTemplates, ClassId, NewTempConfigId, Blob);
965+
MakePermanent(adaptive_templates, ClassId, NewTempConfigId, Blob);
935966
UpdateAmbigsGroup(ClassId, Blob);
936967
}
937968

@@ -1547,7 +1578,7 @@ void Classify::DebugAdaptiveClassifier(TBLOB *blob,
15471578
* Globals:
15481579
* - PreTrainedTemplates built-in training templates
15491580
* - AdaptedTemplates templates adapted for this page
1550-
* - matcher_great_threshold rating limit for a great match
1581+
* - matcher_reliable_adaptive_result rating limit for a great match
15511582
*
15521583
* @note Exceptions: none
15531584
* @note History: Tue Mar 12 08:50:11 1991, DSJ, Created.
@@ -1569,7 +1600,8 @@ void Classify::DoAdaptiveMatch(TBLOB *Blob, ADAPT_RESULTS *Results) {
15691600
Ambiguities = BaselineClassifier(Blob, bl_features, fx_info,
15701601
AdaptedTemplates, Results);
15711602
if ((!Results->match.empty() &&
1572-
MarginalMatch(Results->best_rating, matcher_great_threshold) &&
1603+
MarginalMatch(Results->best_rating,
1604+
matcher_reliable_adaptive_result) &&
15731605
!tess_bn_matching) ||
15741606
Results->match.empty()) {
15751607
CharNormClassifier(Blob, *sample, Results);

classify/classify.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ Classify::Classify()
171171
fontset_table_.set_clear_callback(
172172
NewPermanentTessCallback(FontSetDeleteCallback));
173173
AdaptedTemplates = NULL;
174+
BackupAdaptedTemplates = NULL;
174175
PreTrainedTemplates = NULL;
175176
AllProtosOn = NULL;
176177
AllConfigsOn = NULL;

classify/classify.h

+13-6
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,8 @@ class Classify : public CCStruct {
253253
GenericVector<UnicharRating>* results);
254254
UNICHAR_ID *GetAmbiguities(TBLOB *Blob, CLASS_ID CorrectClass);
255255
void DoAdaptiveMatch(TBLOB *Blob, ADAPT_RESULTS *Results);
256-
void AdaptToChar(TBLOB *Blob,
257-
CLASS_ID ClassId,
258-
int FontinfoId,
259-
FLOAT32 Threshold);
256+
void AdaptToChar(TBLOB* Blob, CLASS_ID ClassId, int FontinfoId,
257+
FLOAT32 Threshold, ADAPT_TEMPLATES adaptive_templates);
260258
void DisplayAdaptedChar(TBLOB* blob, INT_CLASS_STRUCT* int_class);
261259
bool AdaptableWord(WERD_RES* word);
262260
void EndAdaptiveClassifier();
@@ -265,6 +263,8 @@ class Classify : public CCStruct {
265263
void AdaptiveClassifier(TBLOB *Blob, BLOB_CHOICE_LIST *Choices);
266264
void ClassifyAsNoise(ADAPT_RESULTS *Results);
267265
void ResetAdaptiveClassifierInternal();
266+
void SwitchAdaptiveClassifier();
267+
void StartBackupAdaptiveClassifier();
268268

269269
int GetCharNormFeature(const INT_FX_RESULT_STRUCT& fx_info,
270270
INT_TEMPLATES templates,
@@ -281,7 +281,10 @@ class Classify : public CCStruct {
281281
bool TempConfigReliable(CLASS_ID class_id, const TEMP_CONFIG &config);
282282
void UpdateAmbigsGroup(CLASS_ID class_id, TBLOB *Blob);
283283

284-
bool AdaptiveClassifierIsFull() { return NumAdaptationsFailed > 0; }
284+
bool AdaptiveClassifierIsFull() const { return NumAdaptationsFailed > 0; }
285+
bool AdaptiveClassifierIsEmpty() const {
286+
return AdaptedTemplates->NumPermClasses == 0;
287+
}
285288
bool LooksLikeGarbage(TBLOB *blob);
286289
void RefreshDebugWindow(ScrollView **win, const char *msg,
287290
int y_offset, const TBOX &wbox);
@@ -415,7 +418,7 @@ class Classify : public CCStruct {
415418
INT_VAR_H(matcher_debug_flags, 0, "Matcher Debug Flags");
416419
INT_VAR_H(classify_learning_debug_level, 0, "Learning Debug Level: ");
417420
double_VAR_H(matcher_good_threshold, 0.125, "Good Match (0-1)");
418-
double_VAR_H(matcher_great_threshold, 0.0, "Great Match (0-1)");
421+
double_VAR_H(matcher_reliable_adaptive_result, 0.0, "Great Match (0-1)");
419422
double_VAR_H(matcher_perfect_threshold, 0.02, "Perfect Match (0-1)");
420423
double_VAR_H(matcher_bad_match_pad, 0.15, "Bad Match Pad (0-1)");
421424
double_VAR_H(matcher_rating_margin, 0.1, "New template margin (0-1)");
@@ -468,6 +471,10 @@ class Classify : public CCStruct {
468471
// Use class variables to hold onto built-in templates and adapted templates.
469472
INT_TEMPLATES PreTrainedTemplates;
470473
ADAPT_TEMPLATES AdaptedTemplates;
474+
// The backup adapted templates are created from the previous page (only)
475+
// so they are always ready and reasonably well trained if the primary
476+
// adapted templates become full.
477+
ADAPT_TEMPLATES BackupAdaptedTemplates;
471478

472479
// Create dummy proto and config masks for use with the built-in templates.
473480
BIT_VECTOR AllProtosOn;

0 commit comments

Comments
 (0)