mirror of
https://github.com/tesseract-ocr/tesseract.git
synced 2025-01-18 06:30:14 +08:00
Added a backup adaptive classifier to take over from primary when it fills on a large document
This commit is contained in:
parent
78b5e1a77d
commit
b1d99dfe23
@ -2380,7 +2380,8 @@ void TessBaseAPI::AdaptToCharacter(const char *unichar_repr,
|
||||
threshold = tesseract_->matcher_good_threshold;
|
||||
|
||||
if (blob->outlines)
|
||||
tesseract_->AdaptToChar(blob, id, kUnknownFontinfoId, threshold);
|
||||
tesseract_->AdaptToChar(blob, id, kUnknownFontinfoId, threshold,
|
||||
tesseract_->AdaptedTemplates);
|
||||
delete blob;
|
||||
}
|
||||
|
||||
|
@ -306,17 +306,22 @@ bool Tesseract::recog_all_words(PAGE_RES* page_res,
|
||||
page_res_it.restart_page();
|
||||
// ****************** Pass 1 *******************
|
||||
|
||||
// Clear adaptive classifier at the beginning of the page if it is full.
|
||||
// This is done only at the beginning of the page to ensure that the
|
||||
// classifier is not reset at an arbitrary point while processing the page,
|
||||
// which would cripple Passes 2+ if the reset happens towards the end of
|
||||
// Pass 1 on a page with very difficult text.
|
||||
// TODO(daria): preemptively clear the classifier if it is almost full.
|
||||
if (AdaptiveClassifierIsFull()) ResetAdaptiveClassifierInternal();
|
||||
// If the adaptive classifier is full switch to one we prepared earlier,
|
||||
// ie on the previous page. If the current adaptive classifier is non-empty,
|
||||
// prepare a backup starting at this page, in case it fills up. Do all this
|
||||
// independently for each language.
|
||||
if (AdaptiveClassifierIsFull()) {
|
||||
SwitchAdaptiveClassifier();
|
||||
} else if (!AdaptiveClassifierIsEmpty()) {
|
||||
StartBackupAdaptiveClassifier();
|
||||
}
|
||||
// Now check the sub-langs as well.
|
||||
for (int i = 0; i < sub_langs_.size(); ++i) {
|
||||
if (sub_langs_[i]->AdaptiveClassifierIsFull())
|
||||
sub_langs_[i]->ResetAdaptiveClassifierInternal();
|
||||
if (sub_langs_[i]->AdaptiveClassifierIsFull()) {
|
||||
sub_langs_[i]->SwitchAdaptiveClassifier();
|
||||
} else if (!sub_langs_[i]->AdaptiveClassifierIsEmpty()) {
|
||||
sub_langs_[i]->StartBackupAdaptiveClassifier();
|
||||
}
|
||||
}
|
||||
// Set up all words ready for recognition, so that if parallelism is on
|
||||
// all the input and output classes are ready to run the classifier.
|
||||
|
@ -24,36 +24,36 @@
|
||||
#endif
|
||||
|
||||
#include <ctype.h>
|
||||
#include "shapeclassifier.h"
|
||||
#include "ambigs.h"
|
||||
#include "blobclass.h"
|
||||
#include "blobs.h"
|
||||
#include "helpers.h"
|
||||
#include "normfeat.h"
|
||||
#include "mfoutline.h"
|
||||
#include "picofeat.h"
|
||||
#include "float2int.h"
|
||||
#include "outfeat.h"
|
||||
#include "emalloc.h"
|
||||
#include "intfx.h"
|
||||
#include "efio.h"
|
||||
#include "normmatch.h"
|
||||
#include "ndminx.h"
|
||||
#include "intproto.h"
|
||||
#include "const.h"
|
||||
#include "globals.h"
|
||||
#include "werd.h"
|
||||
#include "callcpp.h"
|
||||
#include "classify.h"
|
||||
#include "const.h"
|
||||
#include "dict.h"
|
||||
#include "efio.h"
|
||||
#include "emalloc.h"
|
||||
#include "featdefs.h"
|
||||
#include "float2int.h"
|
||||
#include "genericvector.h"
|
||||
#include "globals.h"
|
||||
#include "helpers.h"
|
||||
#include "intfx.h"
|
||||
#include "intproto.h"
|
||||
#include "mfoutline.h"
|
||||
#include "ndminx.h"
|
||||
#include "normfeat.h"
|
||||
#include "normmatch.h"
|
||||
#include "outfeat.h"
|
||||
#include "pageres.h"
|
||||
#include "params.h"
|
||||
#include "classify.h"
|
||||
#include "picofeat.h"
|
||||
#include "shapeclassifier.h"
|
||||
#include "shapetable.h"
|
||||
#include "tessclassifier.h"
|
||||
#include "trainingsample.h"
|
||||
#include "unicharset.h"
|
||||
#include "dict.h"
|
||||
#include "featdefs.h"
|
||||
#include "genericvector.h"
|
||||
#include "werd.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@ -420,7 +420,13 @@ void Classify::LearnPieces(const char* fontname, int start, int length,
|
||||
unicharset.id_to_unichar(class_id), threshold, font_id);
|
||||
// If filename is not NULL we are doing recognition
|
||||
// (as opposed to training), so we must have already set word fonts.
|
||||
AdaptToChar(rotated_blob, class_id, font_id, threshold);
|
||||
AdaptToChar(rotated_blob, class_id, font_id, threshold, AdaptedTemplates);
|
||||
if (BackupAdaptedTemplates != NULL) {
|
||||
// Adapt the backup templates too. They will be used if the primary gets
|
||||
// too full.
|
||||
AdaptToChar(rotated_blob, class_id, font_id, threshold,
|
||||
BackupAdaptedTemplates);
|
||||
}
|
||||
} else if (classify_debug_level >= 1) {
|
||||
tprintf("Can't adapt to %s not in unicharset\n", correct_text);
|
||||
}
|
||||
@ -470,6 +476,10 @@ void Classify::EndAdaptiveClassifier() {
|
||||
free_adapted_templates(AdaptedTemplates);
|
||||
AdaptedTemplates = NULL;
|
||||
}
|
||||
if (BackupAdaptedTemplates != NULL) {
|
||||
free_adapted_templates(BackupAdaptedTemplates);
|
||||
BackupAdaptedTemplates = NULL;
|
||||
}
|
||||
|
||||
if (PreTrainedTemplates != NULL) {
|
||||
free_int_templates(PreTrainedTemplates);
|
||||
@ -607,10 +617,35 @@ void Classify::ResetAdaptiveClassifierInternal() {
|
||||
}
|
||||
free_adapted_templates(AdaptedTemplates);
|
||||
AdaptedTemplates = NewAdaptedTemplates(true);
|
||||
if (BackupAdaptedTemplates != NULL)
|
||||
free_adapted_templates(BackupAdaptedTemplates);
|
||||
BackupAdaptedTemplates = NULL;
|
||||
NumAdaptationsFailed = 0;
|
||||
}
|
||||
|
||||
// If there are backup adapted templates, switches to those, otherwise resets
|
||||
// the main adaptive classifier (because it is full.)
|
||||
void Classify::SwitchAdaptiveClassifier() {
|
||||
if (BackupAdaptedTemplates == NULL) {
|
||||
ResetAdaptiveClassifierInternal();
|
||||
return;
|
||||
}
|
||||
if (classify_learning_debug_level > 0) {
|
||||
tprintf("Switch to backup adaptive classifier (NumAdaptationsFailed=%d)\n",
|
||||
NumAdaptationsFailed);
|
||||
}
|
||||
free_adapted_templates(AdaptedTemplates);
|
||||
AdaptedTemplates = BackupAdaptedTemplates;
|
||||
BackupAdaptedTemplates = NULL;
|
||||
NumAdaptationsFailed = 0;
|
||||
}
|
||||
|
||||
// Resets the backup adaptive classifier to empty.
|
||||
void Classify::StartBackupAdaptiveClassifier() {
|
||||
if (BackupAdaptedTemplates != NULL)
|
||||
free_adapted_templates(BackupAdaptedTemplates);
|
||||
BackupAdaptedTemplates = NewAdaptedTemplates(true);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/**
|
||||
@ -839,9 +874,9 @@ bool Classify::AdaptableWord(WERD_RES* word) {
|
||||
* @param ClassId class to add blob to
|
||||
* @param FontinfoId font information from pre-trained templates
|
||||
* @param Threshold minimum match rating to existing template
|
||||
* @param adaptive_templates current set of adapted templates
|
||||
*
|
||||
* Globals:
|
||||
* - AdaptedTemplates current set of adapted templates
|
||||
* - AllProtosOn dummy mask to match against all protos
|
||||
* - AllConfigsOn dummy mask to match against all configs
|
||||
*
|
||||
@ -849,10 +884,9 @@ bool Classify::AdaptableWord(WERD_RES* word) {
|
||||
* @note Exceptions: none
|
||||
* @note History: Thu Mar 14 09:36:03 1991, DSJ, Created.
|
||||
*/
|
||||
void Classify::AdaptToChar(TBLOB *Blob,
|
||||
CLASS_ID ClassId,
|
||||
int FontinfoId,
|
||||
FLOAT32 Threshold) {
|
||||
void Classify::AdaptToChar(TBLOB* Blob, CLASS_ID ClassId, int FontinfoId,
|
||||
FLOAT32 Threshold,
|
||||
ADAPT_TEMPLATES adaptive_templates) {
|
||||
int NumFeatures;
|
||||
INT_FEATURE_ARRAY IntFeatures;
|
||||
UnicharRating int_result;
|
||||
@ -866,12 +900,12 @@ void Classify::AdaptToChar(TBLOB *Blob,
|
||||
return;
|
||||
|
||||
int_result.unichar_id = ClassId;
|
||||
Class = AdaptedTemplates->Class[ClassId];
|
||||
Class = adaptive_templates->Class[ClassId];
|
||||
assert(Class != NULL);
|
||||
if (IsEmptyAdaptedClass(Class)) {
|
||||
InitAdaptedClass(Blob, ClassId, FontinfoId, Class, AdaptedTemplates);
|
||||
InitAdaptedClass(Blob, ClassId, FontinfoId, Class, adaptive_templates);
|
||||
} else {
|
||||
IClass = ClassForClassId(AdaptedTemplates->Templates, ClassId);
|
||||
IClass = ClassForClassId(adaptive_templates->Templates, ClassId);
|
||||
|
||||
NumFeatures = GetAdaptiveFeatures(Blob, IntFeatures, &FloatFeatures);
|
||||
if (NumFeatures <= 0)
|
||||
@ -913,7 +947,7 @@ void Classify::AdaptToChar(TBLOB *Blob,
|
||||
int_result.config, TempConfig->NumTimesSeen);
|
||||
|
||||
if (TempConfigReliable(ClassId, TempConfig)) {
|
||||
MakePermanent(AdaptedTemplates, ClassId, int_result.config, Blob);
|
||||
MakePermanent(adaptive_templates, ClassId, int_result.config, Blob);
|
||||
UpdateAmbigsGroup(ClassId, Blob);
|
||||
}
|
||||
} else {
|
||||
@ -923,15 +957,12 @@ void Classify::AdaptToChar(TBLOB *Blob,
|
||||
if (classify_learning_debug_level > 2)
|
||||
DisplayAdaptedChar(Blob, IClass);
|
||||
}
|
||||
NewTempConfigId = MakeNewTemporaryConfig(AdaptedTemplates,
|
||||
ClassId,
|
||||
FontinfoId,
|
||||
NumFeatures,
|
||||
IntFeatures,
|
||||
FloatFeatures);
|
||||
NewTempConfigId =
|
||||
MakeNewTemporaryConfig(adaptive_templates, ClassId, FontinfoId,
|
||||
NumFeatures, IntFeatures, FloatFeatures);
|
||||
if (NewTempConfigId >= 0 &&
|
||||
TempConfigReliable(ClassId, TempConfigFor(Class, NewTempConfigId))) {
|
||||
MakePermanent(AdaptedTemplates, ClassId, NewTempConfigId, Blob);
|
||||
MakePermanent(adaptive_templates, ClassId, NewTempConfigId, Blob);
|
||||
UpdateAmbigsGroup(ClassId, Blob);
|
||||
}
|
||||
|
||||
@ -1547,7 +1578,7 @@ void Classify::DebugAdaptiveClassifier(TBLOB *blob,
|
||||
* Globals:
|
||||
* - PreTrainedTemplates built-in training templates
|
||||
* - AdaptedTemplates templates adapted for this page
|
||||
* - matcher_great_threshold rating limit for a great match
|
||||
* - matcher_reliable_adaptive_result rating limit for a great match
|
||||
*
|
||||
* @note Exceptions: none
|
||||
* @note History: Tue Mar 12 08:50:11 1991, DSJ, Created.
|
||||
@ -1569,7 +1600,8 @@ void Classify::DoAdaptiveMatch(TBLOB *Blob, ADAPT_RESULTS *Results) {
|
||||
Ambiguities = BaselineClassifier(Blob, bl_features, fx_info,
|
||||
AdaptedTemplates, Results);
|
||||
if ((!Results->match.empty() &&
|
||||
MarginalMatch(Results->best_rating, matcher_great_threshold) &&
|
||||
MarginalMatch(Results->best_rating,
|
||||
matcher_reliable_adaptive_result) &&
|
||||
!tess_bn_matching) ||
|
||||
Results->match.empty()) {
|
||||
CharNormClassifier(Blob, *sample, Results);
|
||||
|
@ -171,6 +171,7 @@ Classify::Classify()
|
||||
fontset_table_.set_clear_callback(
|
||||
NewPermanentTessCallback(FontSetDeleteCallback));
|
||||
AdaptedTemplates = NULL;
|
||||
BackupAdaptedTemplates = NULL;
|
||||
PreTrainedTemplates = NULL;
|
||||
AllProtosOn = NULL;
|
||||
AllConfigsOn = NULL;
|
||||
|
@ -253,10 +253,8 @@ class Classify : public CCStruct {
|
||||
GenericVector<UnicharRating>* results);
|
||||
UNICHAR_ID *GetAmbiguities(TBLOB *Blob, CLASS_ID CorrectClass);
|
||||
void DoAdaptiveMatch(TBLOB *Blob, ADAPT_RESULTS *Results);
|
||||
void AdaptToChar(TBLOB *Blob,
|
||||
CLASS_ID ClassId,
|
||||
int FontinfoId,
|
||||
FLOAT32 Threshold);
|
||||
void AdaptToChar(TBLOB* Blob, CLASS_ID ClassId, int FontinfoId,
|
||||
FLOAT32 Threshold, ADAPT_TEMPLATES adaptive_templates);
|
||||
void DisplayAdaptedChar(TBLOB* blob, INT_CLASS_STRUCT* int_class);
|
||||
bool AdaptableWord(WERD_RES* word);
|
||||
void EndAdaptiveClassifier();
|
||||
@ -265,6 +263,8 @@ class Classify : public CCStruct {
|
||||
void AdaptiveClassifier(TBLOB *Blob, BLOB_CHOICE_LIST *Choices);
|
||||
void ClassifyAsNoise(ADAPT_RESULTS *Results);
|
||||
void ResetAdaptiveClassifierInternal();
|
||||
void SwitchAdaptiveClassifier();
|
||||
void StartBackupAdaptiveClassifier();
|
||||
|
||||
int GetCharNormFeature(const INT_FX_RESULT_STRUCT& fx_info,
|
||||
INT_TEMPLATES templates,
|
||||
@ -281,7 +281,10 @@ class Classify : public CCStruct {
|
||||
bool TempConfigReliable(CLASS_ID class_id, const TEMP_CONFIG &config);
|
||||
void UpdateAmbigsGroup(CLASS_ID class_id, TBLOB *Blob);
|
||||
|
||||
bool AdaptiveClassifierIsFull() { return NumAdaptationsFailed > 0; }
|
||||
bool AdaptiveClassifierIsFull() const { return NumAdaptationsFailed > 0; }
|
||||
bool AdaptiveClassifierIsEmpty() const {
|
||||
return AdaptedTemplates->NumPermClasses == 0;
|
||||
}
|
||||
bool LooksLikeGarbage(TBLOB *blob);
|
||||
void RefreshDebugWindow(ScrollView **win, const char *msg,
|
||||
int y_offset, const TBOX &wbox);
|
||||
@ -415,7 +418,7 @@ class Classify : public CCStruct {
|
||||
INT_VAR_H(matcher_debug_flags, 0, "Matcher Debug Flags");
|
||||
INT_VAR_H(classify_learning_debug_level, 0, "Learning Debug Level: ");
|
||||
double_VAR_H(matcher_good_threshold, 0.125, "Good Match (0-1)");
|
||||
double_VAR_H(matcher_great_threshold, 0.0, "Great Match (0-1)");
|
||||
double_VAR_H(matcher_reliable_adaptive_result, 0.0, "Great Match (0-1)");
|
||||
double_VAR_H(matcher_perfect_threshold, 0.02, "Perfect Match (0-1)");
|
||||
double_VAR_H(matcher_bad_match_pad, 0.15, "Bad Match Pad (0-1)");
|
||||
double_VAR_H(matcher_rating_margin, 0.1, "New template margin (0-1)");
|
||||
@ -468,6 +471,10 @@ class Classify : public CCStruct {
|
||||
// Use class variables to hold onto built-in templates and adapted templates.
|
||||
INT_TEMPLATES PreTrainedTemplates;
|
||||
ADAPT_TEMPLATES AdaptedTemplates;
|
||||
// The backup adapted templates are created from the previous page (only)
|
||||
// so they are always ready and reasonably well trained if the primary
|
||||
// adapted templates become full.
|
||||
ADAPT_TEMPLATES BackupAdaptedTemplates;
|
||||
|
||||
// Create dummy proto and config masks for use with the built-in templates.
|
||||
BIT_VECTOR AllProtosOn;
|
||||
|
Loading…
Reference in New Issue
Block a user