|
tesseract 3.04.01
|
00001 /****************************************************************************** 00002 ** Filename: adaptmatch.c 00003 ** Purpose: High level adaptive matcher. 00004 ** Author: Dan Johnson 00005 ** History: Mon Mar 11 10:00:10 1991, DSJ, Created. 00006 ** 00007 ** (c) Copyright Hewlett-Packard Company, 1988. 00008 ** Licensed under the Apache License, Version 2.0 (the "License"); 00009 ** you may not use this file except in compliance with the License. 00010 ** You may obtain a copy of the License at 00011 ** http://www.apache.org/licenses/LICENSE-2.0 00012 ** Unless required by applicable law or agreed to in writing, software 00013 ** distributed under the License is distributed on an "AS IS" BASIS, 00014 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00015 ** See the License for the specific language governing permissions and 00016 ** limitations under the License. 00017 ******************************************************************************/ 00018 00019 /*----------------------------------------------------------------------------- 00020 Include Files and Type Defines 00021 -----------------------------------------------------------------------------*/ 00022 #ifdef HAVE_CONFIG_H 00023 #include "config_auto.h" 00024 #endif 00025 00026 #include <ctype.h> 00027 #include "shapeclassifier.h" 00028 #include "ambigs.h" 00029 #include "blobclass.h" 00030 #include "blobs.h" 00031 #include "callcpp.h" 00032 #include "classify.h" 00033 #include "const.h" 00034 #include "dict.h" 00035 #include "efio.h" 00036 #include "emalloc.h" 00037 #include "featdefs.h" 00038 #include "float2int.h" 00039 #include "genericvector.h" 00040 #include "globals.h" 00041 #include "helpers.h" 00042 #include "intfx.h" 00043 #include "intproto.h" 00044 #include "mfoutline.h" 00045 #include "ndminx.h" 00046 #include "normfeat.h" 00047 #include "normmatch.h" 00048 #include "outfeat.h" 00049 #include "pageres.h" 00050 #include "params.h" 00051 #include "picofeat.h" 00052 #include "shapetable.h" 00053 #include "tessclassifier.h" 00054 #include "trainingsample.h" 00055 #include "unicharset.h" 00056 #include "werd.h" 00057 00058 #include <stdio.h> 00059 #include <string.h> 00060 #include <stdlib.h> 00061 #include <math.h> 00062 #ifdef __UNIX__ 00063 #include <assert.h> 00064 #endif 00065 00066 #define ADAPT_TEMPLATE_SUFFIX ".a" 00067 00068 #define MAX_MATCHES 10 00069 #define UNLIKELY_NUM_FEAT 200 00070 #define NO_DEBUG 0 00071 #define MAX_ADAPTABLE_WERD_SIZE 40 00072 00073 #define ADAPTABLE_WERD_ADJUSTMENT (0.05) 00074 00075 #define Y_DIM_OFFSET (Y_SHIFT - BASELINE_Y_SHIFT) 00076 00077 #define WORST_POSSIBLE_RATING (0.0f) 00078 00079 using tesseract::UnicharRating; 00080 using tesseract::ScoredFont; 00081 00082 struct ADAPT_RESULTS { 00083 inT32 BlobLength; 00084 bool HasNonfragment; 00085 UNICHAR_ID best_unichar_id; 00086 int best_match_index; 00087 FLOAT32 best_rating; 00088 GenericVector<UnicharRating> match; 00089 GenericVector<CP_RESULT_STRUCT> CPResults; 00090 00093 inline void Initialize() { 00094 BlobLength = MAX_INT32; 00095 HasNonfragment = false; 00096 ComputeBest(); 00097 } 00098 // Computes best_unichar_id, best_match_index and best_rating. 00099 void ComputeBest() { 00100 best_unichar_id = INVALID_UNICHAR_ID; 00101 best_match_index = -1; 00102 best_rating = WORST_POSSIBLE_RATING; 00103 for (int i = 0; i < match.size(); ++i) { 00104 if (match[i].rating > best_rating) { 00105 best_rating = match[i].rating; 00106 best_unichar_id = match[i].unichar_id; 00107 best_match_index = i; 00108 } 00109 } 00110 } 00111 }; 00112 00113 struct PROTO_KEY { 00114 ADAPT_TEMPLATES Templates; 00115 CLASS_ID ClassId; 00116 int ConfigId; 00117 }; 00118 00119 /*----------------------------------------------------------------------------- 00120 Private Macros 00121 -----------------------------------------------------------------------------*/ 00122 inline bool MarginalMatch(float confidence, float matcher_great_threshold) { 00123 return (1.0f - confidence) > matcher_great_threshold; 00124 } 00125 00126 /*----------------------------------------------------------------------------- 00127 Private Function Prototypes 00128 -----------------------------------------------------------------------------*/ 00129 // Returns the index of the given id in results, if present, or the size of the 00130 // vector (index it will go at) if not present. 00131 static int FindScoredUnichar(UNICHAR_ID id, const ADAPT_RESULTS& results) { 00132 for (int i = 0; i < results.match.size(); i++) { 00133 if (results.match[i].unichar_id == id) 00134 return i; 00135 } 00136 return results.match.size(); 00137 } 00138 00139 // Returns the current rating for a unichar id if we have rated it, defaulting 00140 // to WORST_POSSIBLE_RATING. 00141 static float ScoredUnichar(UNICHAR_ID id, const ADAPT_RESULTS& results) { 00142 int index = FindScoredUnichar(id, results); 00143 if (index >= results.match.size()) return WORST_POSSIBLE_RATING; 00144 return results.match[index].rating; 00145 } 00146 00147 void InitMatcherRatings(register FLOAT32 *Rating); 00148 00149 int MakeTempProtoPerm(void *item1, void *item2); 00150 00151 void SetAdaptiveThreshold(FLOAT32 Threshold); 00152 00153 00154 /*----------------------------------------------------------------------------- 00155 Public Code 00156 -----------------------------------------------------------------------------*/ 00157 /*---------------------------------------------------------------------------*/ 00158 namespace tesseract { 00185 void Classify::AdaptiveClassifier(TBLOB *Blob, BLOB_CHOICE_LIST *Choices) { 00186 assert(Choices != NULL); 00187 ADAPT_RESULTS *Results = new ADAPT_RESULTS; 00188 Results->Initialize(); 00189 00190 ASSERT_HOST(AdaptedTemplates != NULL); 00191 00192 DoAdaptiveMatch(Blob, Results); 00193 00194 RemoveBadMatches(Results); 00195 Results->match.sort(&UnicharRating::SortDescendingRating); 00196 RemoveExtraPuncs(Results); 00197 Results->ComputeBest(); 00198 ConvertMatchesToChoices(Blob->denorm(), Blob->bounding_box(), Results, 00199 Choices); 00200 00201 // TODO(rays) Move to before ConvertMatchesToChoices! 00202 if (LargeSpeckle(*Blob) || Choices->length() == 0) 00203 AddLargeSpeckleTo(Results->BlobLength, Choices); 00204 00205 if (matcher_debug_level >= 1) { 00206 tprintf("AD Matches = "); 00207 PrintAdaptiveMatchResults(*Results); 00208 } 00209 00210 #ifndef GRAPHICS_DISABLED 00211 if (classify_enable_adaptive_debugger) 00212 DebugAdaptiveClassifier(Blob, Results); 00213 #endif 00214 00215 delete Results; 00216 } /* AdaptiveClassifier */ 00217 00218 // If *win is NULL, sets it to a new ScrollView() object with title msg. 00219 // Clears the window and draws baselines. 00220 void Classify::RefreshDebugWindow(ScrollView **win, const char *msg, 00221 int y_offset, const TBOX &wbox) { 00222 #ifndef GRAPHICS_DISABLED 00223 const int kSampleSpaceWidth = 500; 00224 if (*win == NULL) { 00225 *win = new ScrollView(msg, 100, y_offset, kSampleSpaceWidth * 2, 200, 00226 kSampleSpaceWidth * 2, 200, true); 00227 } 00228 (*win)->Clear(); 00229 (*win)->Pen(64, 64, 64); 00230 (*win)->Line(-kSampleSpaceWidth, kBlnBaselineOffset, 00231 kSampleSpaceWidth, kBlnBaselineOffset); 00232 (*win)->Line(-kSampleSpaceWidth, kBlnXHeight + kBlnBaselineOffset, 00233 kSampleSpaceWidth, kBlnXHeight + kBlnBaselineOffset); 00234 (*win)->ZoomToRectangle(wbox.left(), wbox.top(), 00235 wbox.right(), wbox.bottom()); 00236 #endif // GRAPHICS_DISABLED 00237 } 00238 00239 // Learns the given word using its chopped_word, seam_array, denorm, 00240 // box_word, best_state, and correct_text to learn both correctly and 00241 // incorrectly segmented blobs. If fontname is not NULL, then LearnBlob 00242 // is called and the data will be saved in an internal buffer. 00243 // Otherwise AdaptToBlob is called for adaption within a document. 00244 void Classify::LearnWord(const char* fontname, WERD_RES* word) { 00245 int word_len = word->correct_text.size(); 00246 if (word_len == 0) return; 00247 00248 float* thresholds = NULL; 00249 if (fontname == NULL) { 00250 // Adaption mode. 00251 if (!EnableLearning || word->best_choice == NULL) 00252 return; // Can't or won't adapt. 00253 00254 if (classify_learning_debug_level >= 1) 00255 tprintf("\n\nAdapting to word = %s\n", 00256 word->best_choice->debug_string().string()); 00257 thresholds = new float[word_len]; 00258 word->ComputeAdaptionThresholds(certainty_scale, 00259 matcher_perfect_threshold, 00260 matcher_good_threshold, 00261 matcher_rating_margin, thresholds); 00262 } 00263 int start_blob = 0; 00264 00265 #ifndef GRAPHICS_DISABLED 00266 if (classify_debug_character_fragments) { 00267 if (learn_fragmented_word_debug_win_ != NULL) { 00268 window_wait(learn_fragmented_word_debug_win_); 00269 } 00270 RefreshDebugWindow(&learn_fragments_debug_win_, "LearnPieces", 400, 00271 word->chopped_word->bounding_box()); 00272 RefreshDebugWindow(&learn_fragmented_word_debug_win_, "LearnWord", 200, 00273 word->chopped_word->bounding_box()); 00274 word->chopped_word->plot(learn_fragmented_word_debug_win_); 00275 ScrollView::Update(); 00276 } 00277 #endif // GRAPHICS_DISABLED 00278 00279 for (int ch = 0; ch < word_len; ++ch) { 00280 if (classify_debug_character_fragments) { 00281 tprintf("\nLearning %s\n", word->correct_text[ch].string()); 00282 } 00283 if (word->correct_text[ch].length() > 0) { 00284 float threshold = thresholds != NULL ? thresholds[ch] : 0.0f; 00285 00286 LearnPieces(fontname, start_blob, word->best_state[ch], threshold, 00287 CST_WHOLE, word->correct_text[ch].string(), word); 00288 00289 if (word->best_state[ch] > 1 && !disable_character_fragments) { 00290 // Check that the character breaks into meaningful fragments 00291 // that each match a whole character with at least 00292 // classify_character_fragments_garbage_certainty_threshold 00293 bool garbage = false; 00294 int frag; 00295 for (frag = 0; frag < word->best_state[ch]; ++frag) { 00296 TBLOB* frag_blob = word->chopped_word->blobs[start_blob + frag]; 00297 if (classify_character_fragments_garbage_certainty_threshold < 0) { 00298 garbage |= LooksLikeGarbage(frag_blob); 00299 } 00300 } 00301 // Learn the fragments. 00302 if (!garbage) { 00303 bool pieces_all_natural = word->PiecesAllNatural(start_blob, 00304 word->best_state[ch]); 00305 if (pieces_all_natural || !prioritize_division) { 00306 for (frag = 0; frag < word->best_state[ch]; ++frag) { 00307 GenericVector<STRING> tokens; 00308 word->correct_text[ch].split(' ', &tokens); 00309 00310 tokens[0] = CHAR_FRAGMENT::to_string( 00311 tokens[0].string(), frag, word->best_state[ch], 00312 pieces_all_natural); 00313 00314 STRING full_string; 00315 for (int i = 0; i < tokens.size(); i++) { 00316 full_string += tokens[i]; 00317 if (i != tokens.size() - 1) 00318 full_string += ' '; 00319 } 00320 LearnPieces(fontname, start_blob + frag, 1, threshold, 00321 CST_FRAGMENT, full_string.string(), word); 00322 } 00323 } 00324 } 00325 } 00326 00327 // TODO(rays): re-enable this part of the code when we switch to the 00328 // new classifier that needs to see examples of garbage. 00329 /* 00330 if (word->best_state[ch] > 1) { 00331 // If the next blob is good, make junk with the rightmost fragment. 00332 if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) { 00333 LearnPieces(fontname, start_blob + word->best_state[ch] - 1, 00334 word->best_state[ch + 1] + 1, 00335 threshold, CST_IMPROPER, INVALID_UNICHAR, word); 00336 } 00337 // If the previous blob is good, make junk with the leftmost fragment. 00338 if (ch > 0 && word->correct_text[ch - 1].length() > 0) { 00339 LearnPieces(fontname, start_blob - word->best_state[ch - 1], 00340 word->best_state[ch - 1] + 1, 00341 threshold, CST_IMPROPER, INVALID_UNICHAR, word); 00342 } 00343 } 00344 // If the next blob is good, make a join with it. 00345 if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) { 00346 STRING joined_text = word->correct_text[ch]; 00347 joined_text += word->correct_text[ch + 1]; 00348 LearnPieces(fontname, start_blob, 00349 word->best_state[ch] + word->best_state[ch + 1], 00350 threshold, CST_NGRAM, joined_text.string(), word); 00351 } 00352 */ 00353 } 00354 start_blob += word->best_state[ch]; 00355 } 00356 delete [] thresholds; 00357 } // LearnWord. 00358 00359 // Builds a blob of length fragments, from the word, starting at start, 00360 // and then learns it, as having the given correct_text. 00361 // If fontname is not NULL, then LearnBlob is called and the data will be 00362 // saved in an internal buffer for static training. 00363 // Otherwise AdaptToBlob is called for adaption within a document. 00364 // threshold is a magic number required by AdaptToChar and generated by 00365 // ComputeAdaptionThresholds. 00366 // Although it can be partly inferred from the string, segmentation is 00367 // provided to explicitly clarify the character segmentation. 00368 void Classify::LearnPieces(const char* fontname, int start, int length, 00369 float threshold, CharSegmentationType segmentation, 00370 const char* correct_text, WERD_RES* word) { 00371 // TODO(daria) Remove/modify this if/when we want 00372 // to train and/or adapt to n-grams. 00373 if (segmentation != CST_WHOLE && 00374 (segmentation != CST_FRAGMENT || disable_character_fragments)) 00375 return; 00376 00377 if (length > 1) { 00378 SEAM::JoinPieces(word->seam_array, word->chopped_word->blobs, start, 00379 start + length - 1); 00380 } 00381 TBLOB* blob = word->chopped_word->blobs[start]; 00382 // Rotate the blob if needed for classification. 00383 TBLOB* rotated_blob = blob->ClassifyNormalizeIfNeeded(); 00384 if (rotated_blob == NULL) 00385 rotated_blob = blob; 00386 00387 #ifndef GRAPHICS_DISABLED 00388 // Draw debug windows showing the blob that is being learned if needed. 00389 if (strcmp(classify_learn_debug_str.string(), correct_text) == 0) { 00390 RefreshDebugWindow(&learn_debug_win_, "LearnPieces", 600, 00391 word->chopped_word->bounding_box()); 00392 rotated_blob->plot(learn_debug_win_, ScrollView::GREEN, ScrollView::BROWN); 00393 learn_debug_win_->Update(); 00394 window_wait(learn_debug_win_); 00395 } 00396 if (classify_debug_character_fragments && segmentation == CST_FRAGMENT) { 00397 ASSERT_HOST(learn_fragments_debug_win_ != NULL); // set up in LearnWord 00398 blob->plot(learn_fragments_debug_win_, 00399 ScrollView::BLUE, ScrollView::BROWN); 00400 learn_fragments_debug_win_->Update(); 00401 } 00402 #endif // GRAPHICS_DISABLED 00403 00404 if (fontname != NULL) { 00405 classify_norm_method.set_value(character); // force char norm spc 30/11/93 00406 tess_bn_matching.set_value(false); // turn it off 00407 tess_cn_matching.set_value(false); 00408 DENORM bl_denorm, cn_denorm; 00409 INT_FX_RESULT_STRUCT fx_info; 00410 SetupBLCNDenorms(*rotated_blob, classify_nonlinear_norm, 00411 &bl_denorm, &cn_denorm, &fx_info); 00412 LearnBlob(fontname, rotated_blob, cn_denorm, fx_info, correct_text); 00413 } else if (unicharset.contains_unichar(correct_text)) { 00414 UNICHAR_ID class_id = unicharset.unichar_to_id(correct_text); 00415 int font_id = word->fontinfo != NULL 00416 ? fontinfo_table_.get_id(*word->fontinfo) 00417 : 0; 00418 if (classify_learning_debug_level >= 1) 00419 tprintf("Adapting to char = %s, thr= %g font_id= %d\n", 00420 unicharset.id_to_unichar(class_id), threshold, font_id); 00421 // If filename is not NULL we are doing recognition 00422 // (as opposed to training), so we must have already set word fonts. 00423 AdaptToChar(rotated_blob, class_id, font_id, threshold, AdaptedTemplates); 00424 if (BackupAdaptedTemplates != NULL) { 00425 // Adapt the backup templates too. They will be used if the primary gets 00426 // too full. 00427 AdaptToChar(rotated_blob, class_id, font_id, threshold, 00428 BackupAdaptedTemplates); 00429 } 00430 } else if (classify_debug_level >= 1) { 00431 tprintf("Can't adapt to %s not in unicharset\n", correct_text); 00432 } 00433 if (rotated_blob != blob) { 00434 delete rotated_blob; 00435 } 00436 00437 SEAM::BreakPieces(word->seam_array, word->chopped_word->blobs, start, 00438 start + length - 1); 00439 } // LearnPieces. 00440 00441 /*---------------------------------------------------------------------------*/ 00456 void Classify::EndAdaptiveClassifier() { 00457 STRING Filename; 00458 FILE *File; 00459 00460 if (AdaptedTemplates != NULL && 00461 classify_enable_adaptive_matcher && classify_save_adapted_templates) { 00462 Filename = imagefile + ADAPT_TEMPLATE_SUFFIX; 00463 File = fopen (Filename.string(), "wb"); 00464 if (File == NULL) 00465 cprintf ("Unable to save adapted templates to %s!\n", Filename.string()); 00466 else { 00467 cprintf ("\nSaving adapted templates to %s ...", Filename.string()); 00468 fflush(stdout); 00469 WriteAdaptedTemplates(File, AdaptedTemplates); 00470 cprintf ("\n"); 00471 fclose(File); 00472 } 00473 } 00474 00475 if (AdaptedTemplates != NULL) { 00476 free_adapted_templates(AdaptedTemplates); 00477 AdaptedTemplates = NULL; 00478 } 00479 if (BackupAdaptedTemplates != NULL) { 00480 free_adapted_templates(BackupAdaptedTemplates); 00481 BackupAdaptedTemplates = NULL; 00482 } 00483 00484 if (PreTrainedTemplates != NULL) { 00485 free_int_templates(PreTrainedTemplates); 00486 PreTrainedTemplates = NULL; 00487 } 00488 getDict().EndDangerousAmbigs(); 00489 FreeNormProtos(); 00490 if (AllProtosOn != NULL) { 00491 FreeBitVector(AllProtosOn); 00492 FreeBitVector(AllConfigsOn); 00493 FreeBitVector(AllConfigsOff); 00494 FreeBitVector(TempProtoMask); 00495 AllProtosOn = NULL; 00496 AllConfigsOn = NULL; 00497 AllConfigsOff = NULL; 00498 TempProtoMask = NULL; 00499 } 00500 delete shape_table_; 00501 shape_table_ = NULL; 00502 if (static_classifier_ != NULL) { 00503 delete static_classifier_; 00504 static_classifier_ = NULL; 00505 } 00506 } /* EndAdaptiveClassifier */ 00507 00508 00509 /*---------------------------------------------------------------------------*/ 00527 void Classify::InitAdaptiveClassifier(bool load_pre_trained_templates) { 00528 if (!classify_enable_adaptive_matcher) 00529 return; 00530 if (AllProtosOn != NULL) 00531 EndAdaptiveClassifier(); // Don't leak with multiple inits. 00532 00533 // If there is no language_data_path_prefix, the classifier will be 00534 // adaptive only. 00535 if (language_data_path_prefix.length() > 0 && 00536 load_pre_trained_templates) { 00537 ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_INTTEMP)); 00538 PreTrainedTemplates = 00539 ReadIntTemplates(tessdata_manager.GetDataFilePtr()); 00540 if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded inttemp\n"); 00541 00542 if (tessdata_manager.SeekToStart(TESSDATA_SHAPE_TABLE)) { 00543 shape_table_ = new ShapeTable(unicharset); 00544 if (!shape_table_->DeSerialize(tessdata_manager.swap(), 00545 tessdata_manager.GetDataFilePtr())) { 00546 tprintf("Error loading shape table!\n"); 00547 delete shape_table_; 00548 shape_table_ = NULL; 00549 } else if (tessdata_manager.DebugLevel() > 0) { 00550 tprintf("Successfully loaded shape table!\n"); 00551 } 00552 } 00553 00554 ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_PFFMTABLE)); 00555 ReadNewCutoffs(tessdata_manager.GetDataFilePtr(), 00556 tessdata_manager.swap(), 00557 tessdata_manager.GetEndOffset(TESSDATA_PFFMTABLE), 00558 CharNormCutoffs); 00559 if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded pffmtable\n"); 00560 00561 ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_NORMPROTO)); 00562 NormProtos = 00563 ReadNormProtos(tessdata_manager.GetDataFilePtr(), 00564 tessdata_manager.GetEndOffset(TESSDATA_NORMPROTO)); 00565 if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded normproto\n"); 00566 static_classifier_ = new TessClassifier(false, this); 00567 } 00568 00569 im_.Init(&classify_debug_level); 00570 InitIntegerFX(); 00571 00572 AllProtosOn = NewBitVector(MAX_NUM_PROTOS); 00573 AllConfigsOn = NewBitVector(MAX_NUM_CONFIGS); 00574 AllConfigsOff = NewBitVector(MAX_NUM_CONFIGS); 00575 TempProtoMask = NewBitVector(MAX_NUM_PROTOS); 00576 set_all_bits(AllProtosOn, WordsInVectorOfSize(MAX_NUM_PROTOS)); 00577 set_all_bits(AllConfigsOn, WordsInVectorOfSize(MAX_NUM_CONFIGS)); 00578 zero_all_bits(AllConfigsOff, WordsInVectorOfSize(MAX_NUM_CONFIGS)); 00579 00580 for (int i = 0; i < MAX_NUM_CLASSES; i++) { 00581 BaselineCutoffs[i] = 0; 00582 } 00583 00584 if (classify_use_pre_adapted_templates) { 00585 FILE *File; 00586 STRING Filename; 00587 00588 Filename = imagefile; 00589 Filename += ADAPT_TEMPLATE_SUFFIX; 00590 File = fopen(Filename.string(), "rb"); 00591 if (File == NULL) { 00592 AdaptedTemplates = NewAdaptedTemplates(true); 00593 } else { 00594 cprintf("\nReading pre-adapted templates from %s ...\n", 00595 Filename.string()); 00596 fflush(stdout); 00597 AdaptedTemplates = ReadAdaptedTemplates(File); 00598 cprintf("\n"); 00599 fclose(File); 00600 PrintAdaptedTemplates(stdout, AdaptedTemplates); 00601 00602 for (int i = 0; i < AdaptedTemplates->Templates->NumClasses; i++) { 00603 BaselineCutoffs[i] = CharNormCutoffs[i]; 00604 } 00605 } 00606 } else { 00607 if (AdaptedTemplates != NULL) 00608 free_adapted_templates(AdaptedTemplates); 00609 AdaptedTemplates = NewAdaptedTemplates(true); 00610 } 00611 } /* InitAdaptiveClassifier */ 00612 00613 void Classify::ResetAdaptiveClassifierInternal() { 00614 if (classify_learning_debug_level > 0) { 00615 tprintf("Resetting adaptive classifier (NumAdaptationsFailed=%d)\n", 00616 NumAdaptationsFailed); 00617 } 00618 free_adapted_templates(AdaptedTemplates); 00619 AdaptedTemplates = NewAdaptedTemplates(true); 00620 if (BackupAdaptedTemplates != NULL) 00621 free_adapted_templates(BackupAdaptedTemplates); 00622 BackupAdaptedTemplates = NULL; 00623 NumAdaptationsFailed = 0; 00624 } 00625 00626 // If there are backup adapted templates, switches to those, otherwise resets 00627 // the main adaptive classifier (because it is full.) 00628 void Classify::SwitchAdaptiveClassifier() { 00629 if (BackupAdaptedTemplates == NULL) { 00630 ResetAdaptiveClassifierInternal(); 00631 return; 00632 } 00633 if (classify_learning_debug_level > 0) { 00634 tprintf("Switch to backup adaptive classifier (NumAdaptationsFailed=%d)\n", 00635 NumAdaptationsFailed); 00636 } 00637 free_adapted_templates(AdaptedTemplates); 00638 AdaptedTemplates = BackupAdaptedTemplates; 00639 BackupAdaptedTemplates = NULL; 00640 NumAdaptationsFailed = 0; 00641 } 00642 00643 // Resets the backup adaptive classifier to empty. 00644 void Classify::StartBackupAdaptiveClassifier() { 00645 if (BackupAdaptedTemplates != NULL) 00646 free_adapted_templates(BackupAdaptedTemplates); 00647 BackupAdaptedTemplates = NewAdaptedTemplates(true); 00648 } 00649 00650 /*---------------------------------------------------------------------------*/ 00670 void Classify::SettupPass1() { 00671 EnableLearning = classify_enable_learning; 00672 00673 getDict().SettupStopperPass1(); 00674 00675 } /* SettupPass1 */ 00676 00677 00678 /*---------------------------------------------------------------------------*/ 00690 void Classify::SettupPass2() { 00691 EnableLearning = FALSE; 00692 getDict().SettupStopperPass2(); 00693 00694 } /* SettupPass2 */ 00695 00696 00697 /*---------------------------------------------------------------------------*/ 00717 void Classify::InitAdaptedClass(TBLOB *Blob, 00718 CLASS_ID ClassId, 00719 int FontinfoId, 00720 ADAPT_CLASS Class, 00721 ADAPT_TEMPLATES Templates) { 00722 FEATURE_SET Features; 00723 int Fid, Pid; 00724 FEATURE Feature; 00725 int NumFeatures; 00726 TEMP_PROTO TempProto; 00727 PROTO Proto; 00728 INT_CLASS IClass; 00729 TEMP_CONFIG Config; 00730 00731 classify_norm_method.set_value(baseline); 00732 Features = ExtractOutlineFeatures(Blob); 00733 NumFeatures = Features->NumFeatures; 00734 if (NumFeatures > UNLIKELY_NUM_FEAT || NumFeatures <= 0) { 00735 FreeFeatureSet(Features); 00736 return; 00737 } 00738 00739 Config = NewTempConfig(NumFeatures - 1, FontinfoId); 00740 TempConfigFor(Class, 0) = Config; 00741 00742 /* this is a kludge to construct cutoffs for adapted templates */ 00743 if (Templates == AdaptedTemplates) 00744 BaselineCutoffs[ClassId] = CharNormCutoffs[ClassId]; 00745 00746 IClass = ClassForClassId (Templates->Templates, ClassId); 00747 00748 for (Fid = 0; Fid < Features->NumFeatures; Fid++) { 00749 Pid = AddIntProto (IClass); 00750 assert (Pid != NO_PROTO); 00751 00752 Feature = Features->Features[Fid]; 00753 TempProto = NewTempProto (); 00754 Proto = &(TempProto->Proto); 00755 00756 /* compute proto params - NOTE that Y_DIM_OFFSET must be used because 00757 ConvertProto assumes that the Y dimension varies from -0.5 to 0.5 00758 instead of the -0.25 to 0.75 used in baseline normalization */ 00759 Proto->Angle = Feature->Params[OutlineFeatDir]; 00760 Proto->X = Feature->Params[OutlineFeatX]; 00761 Proto->Y = Feature->Params[OutlineFeatY] - Y_DIM_OFFSET; 00762 Proto->Length = Feature->Params[OutlineFeatLength]; 00763 FillABC(Proto); 00764 00765 TempProto->ProtoId = Pid; 00766 SET_BIT (Config->Protos, Pid); 00767 00768 ConvertProto(Proto, Pid, IClass); 00769 AddProtoToProtoPruner(Proto, Pid, IClass, 00770 classify_learning_debug_level >= 2); 00771 00772 Class->TempProtos = push (Class->TempProtos, TempProto); 00773 } 00774 FreeFeatureSet(Features); 00775 00776 AddIntConfig(IClass); 00777 ConvertConfig (AllProtosOn, 0, IClass); 00778 00779 if (classify_learning_debug_level >= 1) { 00780 tprintf("Added new class '%s' with class id %d and %d protos.\n", 00781 unicharset.id_to_unichar(ClassId), ClassId, NumFeatures); 00782 if (classify_learning_debug_level > 1) 00783 DisplayAdaptedChar(Blob, IClass); 00784 } 00785 00786 if (IsEmptyAdaptedClass(Class)) 00787 (Templates->NumNonEmptyClasses)++; 00788 } /* InitAdaptedClass */ 00789 00790 00791 /*---------------------------------------------------------------------------*/ 00812 int Classify::GetAdaptiveFeatures(TBLOB *Blob, 00813 INT_FEATURE_ARRAY IntFeatures, 00814 FEATURE_SET *FloatFeatures) { 00815 FEATURE_SET Features; 00816 int NumFeatures; 00817 00818 classify_norm_method.set_value(baseline); 00819 Features = ExtractPicoFeatures(Blob); 00820 00821 NumFeatures = Features->NumFeatures; 00822 if (NumFeatures > UNLIKELY_NUM_FEAT) { 00823 FreeFeatureSet(Features); 00824 return 0; 00825 } 00826 00827 ComputeIntFeatures(Features, IntFeatures); 00828 *FloatFeatures = Features; 00829 00830 return NumFeatures; 00831 } /* GetAdaptiveFeatures */ 00832 00833 00834 /*----------------------------------------------------------------------------- 00835 Private Code 00836 -----------------------------------------------------------------------------*/ 00837 /*---------------------------------------------------------------------------*/ 00850 bool Classify::AdaptableWord(WERD_RES* word) { 00851 if (word->best_choice == NULL) return false; 00852 int BestChoiceLength = word->best_choice->length(); 00853 float adaptable_score = 00854 getDict().segment_penalty_dict_case_ok + ADAPTABLE_WERD_ADJUSTMENT; 00855 return // rules that apply in general - simplest to compute first 00856 BestChoiceLength > 0 && 00857 BestChoiceLength == word->rebuild_word->NumBlobs() && 00858 BestChoiceLength <= MAX_ADAPTABLE_WERD_SIZE && 00859 // This basically ensures that the word is at least a dictionary match 00860 // (freq word, user word, system dawg word, etc). 00861 // Since all the other adjustments will make adjust factor higher 00862 // than higher than adaptable_score=1.1+0.05=1.15 00863 // Since these are other flags that ensure that the word is dict word, 00864 // this check could be at times redundant. 00865 word->best_choice->adjust_factor() <= adaptable_score && 00866 // Make sure that alternative choices are not dictionary words. 00867 word->AlternativeChoiceAdjustmentsWorseThan(adaptable_score); 00868 } 00869 00870 /*---------------------------------------------------------------------------*/ 00886 void Classify::AdaptToChar(TBLOB* Blob, CLASS_ID ClassId, int FontinfoId, 00887 FLOAT32 Threshold, 00888 ADAPT_TEMPLATES adaptive_templates) { 00889 int NumFeatures; 00890 INT_FEATURE_ARRAY IntFeatures; 00891 UnicharRating int_result; 00892 INT_CLASS IClass; 00893 ADAPT_CLASS Class; 00894 TEMP_CONFIG TempConfig; 00895 FEATURE_SET FloatFeatures; 00896 int NewTempConfigId; 00897 00898 if (!LegalClassId (ClassId)) 00899 return; 00900 00901 int_result.unichar_id = ClassId; 00902 Class = adaptive_templates->Class[ClassId]; 00903 assert(Class != NULL); 00904 if (IsEmptyAdaptedClass(Class)) { 00905 InitAdaptedClass(Blob, ClassId, FontinfoId, Class, adaptive_templates); 00906 } else { 00907 IClass = ClassForClassId(adaptive_templates->Templates, ClassId); 00908 00909 NumFeatures = GetAdaptiveFeatures(Blob, IntFeatures, &FloatFeatures); 00910 if (NumFeatures <= 0) 00911 return; 00912 00913 // Only match configs with the matching font. 00914 BIT_VECTOR MatchingFontConfigs = NewBitVector(MAX_NUM_PROTOS); 00915 for (int cfg = 0; cfg < IClass->NumConfigs; ++cfg) { 00916 if (GetFontinfoId(Class, cfg) == FontinfoId) { 00917 SET_BIT(MatchingFontConfigs, cfg); 00918 } else { 00919 reset_bit(MatchingFontConfigs, cfg); 00920 } 00921 } 00922 im_.Match(IClass, AllProtosOn, MatchingFontConfigs, 00923 NumFeatures, IntFeatures, 00924 &int_result, classify_adapt_feature_threshold, 00925 NO_DEBUG, matcher_debug_separate_windows); 00926 FreeBitVector(MatchingFontConfigs); 00927 00928 SetAdaptiveThreshold(Threshold); 00929 00930 if (1.0f - int_result.rating <= Threshold) { 00931 if (ConfigIsPermanent(Class, int_result.config)) { 00932 if (classify_learning_debug_level >= 1) 00933 tprintf("Found good match to perm config %d = %4.1f%%.\n", 00934 int_result.config, int_result.rating * 100.0); 00935 FreeFeatureSet(FloatFeatures); 00936 return; 00937 } 00938 00939 TempConfig = TempConfigFor(Class, int_result.config); 00940 IncreaseConfidence(TempConfig); 00941 if (TempConfig->NumTimesSeen > Class->MaxNumTimesSeen) { 00942 Class->MaxNumTimesSeen = TempConfig->NumTimesSeen; 00943 } 00944 if (classify_learning_debug_level >= 1) 00945 tprintf("Increasing reliability of temp config %d to %d.\n", 00946 int_result.config, TempConfig->NumTimesSeen); 00947 00948 if (TempConfigReliable(ClassId, TempConfig)) { 00949 MakePermanent(adaptive_templates, ClassId, int_result.config, Blob); 00950 UpdateAmbigsGroup(ClassId, Blob); 00951 } 00952 } else { 00953 if (classify_learning_debug_level >= 1) { 00954 tprintf("Found poor match to temp config %d = %4.1f%%.\n", 00955 int_result.config, int_result.rating * 100.0); 00956 if (classify_learning_debug_level > 2) 00957 DisplayAdaptedChar(Blob, IClass); 00958 } 00959 NewTempConfigId = 00960 MakeNewTemporaryConfig(adaptive_templates, ClassId, FontinfoId, 00961 NumFeatures, IntFeatures, FloatFeatures); 00962 if (NewTempConfigId >= 0 && 00963 TempConfigReliable(ClassId, TempConfigFor(Class, NewTempConfigId))) { 00964 MakePermanent(adaptive_templates, ClassId, NewTempConfigId, Blob); 00965 UpdateAmbigsGroup(ClassId, Blob); 00966 } 00967 00968 #ifndef GRAPHICS_DISABLED 00969 if (classify_learning_debug_level > 1) { 00970 DisplayAdaptedChar(Blob, IClass); 00971 } 00972 #endif 00973 } 00974 FreeFeatureSet(FloatFeatures); 00975 } 00976 } /* AdaptToChar */ 00977 00978 void Classify::DisplayAdaptedChar(TBLOB* blob, INT_CLASS_STRUCT* int_class) { 00979 #ifndef GRAPHICS_DISABLED 00980 INT_FX_RESULT_STRUCT fx_info; 00981 GenericVector<INT_FEATURE_STRUCT> bl_features; 00982 TrainingSample* sample = 00983 BlobToTrainingSample(*blob, classify_nonlinear_norm, &fx_info, 00984 &bl_features); 00985 if (sample == NULL) return; 00986 00987 UnicharRating int_result; 00988 im_.Match(int_class, AllProtosOn, AllConfigsOn, 00989 bl_features.size(), &bl_features[0], 00990 &int_result, classify_adapt_feature_threshold, 00991 NO_DEBUG, matcher_debug_separate_windows); 00992 tprintf("Best match to temp config %d = %4.1f%%.\n", 00993 int_result.config, int_result.rating * 100.0); 00994 if (classify_learning_debug_level >= 2) { 00995 uinT32 ConfigMask; 00996 ConfigMask = 1 << int_result.config; 00997 ShowMatchDisplay(); 00998 im_.Match(int_class, AllProtosOn, (BIT_VECTOR)&ConfigMask, 00999 bl_features.size(), &bl_features[0], 01000 &int_result, classify_adapt_feature_threshold, 01001 6 | 0x19, matcher_debug_separate_windows); 01002 UpdateMatchDisplay(); 01003 } 01004 #endif 01005 } 01006 01007 01008 01029 void Classify::AddNewResult(const UnicharRating& new_result, 01030 ADAPT_RESULTS *results) { 01031 int old_match = FindScoredUnichar(new_result.unichar_id, *results); 01032 01033 if (new_result.rating + matcher_bad_match_pad < results->best_rating || 01034 (old_match < results->match.size() && 01035 new_result.rating <= results->match[old_match].rating)) 01036 return; // New one not good enough. 01037 01038 if (!unicharset.get_fragment(new_result.unichar_id)) 01039 results->HasNonfragment = true; 01040 01041 if (old_match < results->match.size()) { 01042 results->match[old_match].rating = new_result.rating; 01043 } else { 01044 results->match.push_back(new_result); 01045 } 01046 01047 if (new_result.rating > results->best_rating && 01048 // Ensure that fragments do not affect best rating, class and config. 01049 // This is needed so that at least one non-fragmented character is 01050 // always present in the results. 01051 // TODO(daria): verify that this helps accuracy and does not 01052 // hurt performance. 01053 !unicharset.get_fragment(new_result.unichar_id)) { 01054 results->best_match_index = old_match; 01055 results->best_rating = new_result.rating; 01056 results->best_unichar_id = new_result.unichar_id; 01057 } 01058 } /* AddNewResult */ 01059 01060 01061 /*---------------------------------------------------------------------------*/ 01083 void Classify::AmbigClassifier( 01084 const GenericVector<INT_FEATURE_STRUCT>& int_features, 01085 const INT_FX_RESULT_STRUCT& fx_info, 01086 const TBLOB *blob, 01087 INT_TEMPLATES templates, 01088 ADAPT_CLASS *classes, 01089 UNICHAR_ID *ambiguities, 01090 ADAPT_RESULTS *results) { 01091 if (int_features.empty()) return; 01092 uinT8* CharNormArray = new uinT8[unicharset.size()]; 01093 UnicharRating int_result; 01094 01095 results->BlobLength = GetCharNormFeature(fx_info, templates, NULL, 01096 CharNormArray); 01097 bool debug = matcher_debug_level >= 2 || classify_debug_level > 1; 01098 if (debug) 01099 tprintf("AM Matches = "); 01100 01101 int top = blob->bounding_box().top(); 01102 int bottom = blob->bounding_box().bottom(); 01103 while (*ambiguities >= 0) { 01104 CLASS_ID class_id = *ambiguities; 01105 01106 int_result.unichar_id = class_id; 01107 im_.Match(ClassForClassId(templates, class_id), 01108 AllProtosOn, AllConfigsOn, 01109 int_features.size(), &int_features[0], 01110 &int_result, 01111 classify_adapt_feature_threshold, NO_DEBUG, 01112 matcher_debug_separate_windows); 01113 01114 ExpandShapesAndApplyCorrections(NULL, debug, class_id, bottom, top, 0, 01115 results->BlobLength, 01116 classify_integer_matcher_multiplier, 01117 CharNormArray, &int_result, results); 01118 ambiguities++; 01119 } 01120 delete [] CharNormArray; 01121 } /* AmbigClassifier */ 01122 01123 /*---------------------------------------------------------------------------*/ 01126 void Classify::MasterMatcher(INT_TEMPLATES templates, 01127 inT16 num_features, 01128 const INT_FEATURE_STRUCT* features, 01129 const uinT8* norm_factors, 01130 ADAPT_CLASS* classes, 01131 int debug, 01132 int matcher_multiplier, 01133 const TBOX& blob_box, 01134 const GenericVector<CP_RESULT_STRUCT>& results, 01135 ADAPT_RESULTS* final_results) { 01136 int top = blob_box.top(); 01137 int bottom = blob_box.bottom(); 01138 UnicharRating int_result; 01139 for (int c = 0; c < results.size(); c++) { 01140 CLASS_ID class_id = results[c].Class; 01141 BIT_VECTOR protos = classes != NULL ? classes[class_id]->PermProtos 01142 : AllProtosOn; 01143 BIT_VECTOR configs = classes != NULL ? classes[class_id]->PermConfigs 01144 : AllConfigsOn; 01145 01146 int_result.unichar_id = class_id; 01147 im_.Match(ClassForClassId(templates, class_id), 01148 protos, configs, 01149 num_features, features, 01150 &int_result, classify_adapt_feature_threshold, debug, 01151 matcher_debug_separate_windows); 01152 bool debug = matcher_debug_level >= 2 || classify_debug_level > 1; 01153 ExpandShapesAndApplyCorrections(classes, debug, class_id, bottom, top, 01154 results[c].Rating, 01155 final_results->BlobLength, 01156 matcher_multiplier, norm_factors, 01157 &int_result, final_results); 01158 } 01159 } 01160 01161 // Converts configs to fonts, and if the result is not adapted, and a 01162 // shape_table_ is present, the shape is expanded to include all 01163 // unichar_ids represented, before applying a set of corrections to the 01164 // distance rating in int_result, (see ComputeCorrectedRating.) 01165 // The results are added to the final_results output. 01166 void Classify::ExpandShapesAndApplyCorrections( 01167 ADAPT_CLASS* classes, bool debug, int class_id, int bottom, int top, 01168 float cp_rating, int blob_length, int matcher_multiplier, 01169 const uinT8* cn_factors, 01170 UnicharRating* int_result, ADAPT_RESULTS* final_results) { 01171 if (classes != NULL) { 01172 // Adapted result. Convert configs to fontinfo_ids. 01173 int_result->adapted = true; 01174 for (int f = 0; f < int_result->fonts.size(); ++f) { 01175 int_result->fonts[f].fontinfo_id = 01176 GetFontinfoId(classes[class_id], int_result->fonts[f].fontinfo_id); 01177 } 01178 } else { 01179 // Pre-trained result. Map fonts using font_sets_. 01180 int_result->adapted = false; 01181 for (int f = 0; f < int_result->fonts.size(); ++f) { 01182 int_result->fonts[f].fontinfo_id = 01183 ClassAndConfigIDToFontOrShapeID(class_id, 01184 int_result->fonts[f].fontinfo_id); 01185 } 01186 if (shape_table_ != NULL) { 01187 // Two possible cases: 01188 // 1. Flat shapetable. All unichar-ids of the shapes referenced by 01189 // int_result->fonts are the same. In this case build a new vector of 01190 // mapped fonts and replace the fonts in int_result. 01191 // 2. Multi-unichar shapetable. Variable unichars in the shapes referenced 01192 // by int_result. In this case, build a vector of UnicharRating to 01193 // gather together different font-ids for each unichar. Also covers case1. 01194 GenericVector<UnicharRating> mapped_results; 01195 for (int f = 0; f < int_result->fonts.size(); ++f) { 01196 int shape_id = int_result->fonts[f].fontinfo_id; 01197 const Shape& shape = shape_table_->GetShape(shape_id); 01198 for (int c = 0; c < shape.size(); ++c) { 01199 int unichar_id = shape[c].unichar_id; 01200 if (!unicharset.get_enabled(unichar_id)) continue; 01201 // Find the mapped_result for unichar_id. 01202 int r = 0; 01203 for (r = 0; r < mapped_results.size() && 01204 mapped_results[r].unichar_id != unichar_id; ++r) {} 01205 if (r == mapped_results.size()) { 01206 mapped_results.push_back(*int_result); 01207 mapped_results[r].unichar_id = unichar_id; 01208 mapped_results[r].fonts.truncate(0); 01209 } 01210 for (int i = 0; i < shape[c].font_ids.size(); ++i) { 01211 mapped_results[r].fonts.push_back( 01212 ScoredFont(shape[c].font_ids[i], int_result->fonts[f].score)); 01213 } 01214 } 01215 } 01216 for (int m = 0; m < mapped_results.size(); ++m) { 01217 mapped_results[m].rating = 01218 ComputeCorrectedRating(debug, mapped_results[m].unichar_id, 01219 cp_rating, int_result->rating, 01220 int_result->feature_misses, bottom, top, 01221 blob_length, matcher_multiplier, cn_factors); 01222 AddNewResult(mapped_results[m], final_results); 01223 } 01224 return; 01225 } 01226 } 01227 if (unicharset.get_enabled(class_id)) { 01228 int_result->rating = ComputeCorrectedRating(debug, class_id, cp_rating, 01229 int_result->rating, 01230 int_result->feature_misses, 01231 bottom, top, blob_length, 01232 matcher_multiplier, cn_factors); 01233 AddNewResult(*int_result, final_results); 01234 } 01235 } 01236 01237 // Applies a set of corrections to the confidence im_rating, 01238 // including the cn_correction, miss penalty and additional penalty 01239 // for non-alnums being vertical misfits. Returns the corrected confidence. 01240 double Classify::ComputeCorrectedRating(bool debug, int unichar_id, 01241 double cp_rating, double im_rating, 01242 int feature_misses, 01243 int bottom, int top, 01244 int blob_length, int matcher_multiplier, 01245 const uinT8* cn_factors) { 01246 // Compute class feature corrections. 01247 double cn_corrected = im_.ApplyCNCorrection(1.0 - im_rating, blob_length, 01248 cn_factors[unichar_id], 01249 matcher_multiplier); 01250 double miss_penalty = tessedit_class_miss_scale * feature_misses; 01251 double vertical_penalty = 0.0; 01252 // Penalize non-alnums for being vertical misfits. 01253 if (!unicharset.get_isalpha(unichar_id) && 01254 !unicharset.get_isdigit(unichar_id) && 01255 cn_factors[unichar_id] != 0 && classify_misfit_junk_penalty > 0.0) { 01256 int min_bottom, max_bottom, min_top, max_top; 01257 unicharset.get_top_bottom(unichar_id, &min_bottom, &max_bottom, 01258 &min_top, &max_top); 01259 if (debug) { 01260 tprintf("top=%d, vs [%d, %d], bottom=%d, vs [%d, %d]\n", 01261 top, min_top, max_top, bottom, min_bottom, max_bottom); 01262 } 01263 if (top < min_top || top > max_top || 01264 bottom < min_bottom || bottom > max_bottom) { 01265 vertical_penalty = classify_misfit_junk_penalty; 01266 } 01267 } 01268 double result = 1.0 - (cn_corrected + miss_penalty + vertical_penalty); 01269 if (result < WORST_POSSIBLE_RATING) 01270 result = WORST_POSSIBLE_RATING; 01271 if (debug) { 01272 tprintf("%s: %2.1f%%(CP%2.1f, IM%2.1f + CN%.2f(%d) + MP%2.1f + VP%2.1f)\n", 01273 unicharset.id_to_unichar(unichar_id), 01274 result * 100.0, 01275 cp_rating * 100.0, 01276 (1.0 - im_rating) * 100.0, 01277 (cn_corrected - (1.0 - im_rating)) * 100.0, 01278 cn_factors[unichar_id], 01279 miss_penalty * 100.0, 01280 vertical_penalty * 100.0); 01281 } 01282 return result; 01283 } 01284 01285 /*---------------------------------------------------------------------------*/ 01305 UNICHAR_ID *Classify::BaselineClassifier( 01306 TBLOB *Blob, const GenericVector<INT_FEATURE_STRUCT>& int_features, 01307 const INT_FX_RESULT_STRUCT& fx_info, 01308 ADAPT_TEMPLATES Templates, ADAPT_RESULTS *Results) { 01309 if (int_features.empty()) return NULL; 01310 uinT8* CharNormArray = new uinT8[unicharset.size()]; 01311 ClearCharNormArray(CharNormArray); 01312 01313 Results->BlobLength = IntCastRounded(fx_info.Length / kStandardFeatureLength); 01314 PruneClasses(Templates->Templates, int_features.size(), -1, &int_features[0], 01315 CharNormArray, BaselineCutoffs, &Results->CPResults); 01316 01317 if (matcher_debug_level >= 2 || classify_debug_level > 1) 01318 tprintf("BL Matches = "); 01319 01320 MasterMatcher(Templates->Templates, int_features.size(), &int_features[0], 01321 CharNormArray, 01322 Templates->Class, matcher_debug_flags, 0, 01323 Blob->bounding_box(), Results->CPResults, Results); 01324 01325 delete [] CharNormArray; 01326 CLASS_ID ClassId = Results->best_unichar_id; 01327 if (ClassId == INVALID_UNICHAR_ID || Results->best_match_index < 0) 01328 return NULL; 01329 01330 return Templates->Class[ClassId]-> 01331 Config[Results->match[Results->best_match_index].config].Perm->Ambigs; 01332 } /* BaselineClassifier */ 01333 01334 01335 /*---------------------------------------------------------------------------*/ 01354 int Classify::CharNormClassifier(TBLOB *blob, 01355 const TrainingSample& sample, 01356 ADAPT_RESULTS *adapt_results) { 01357 // This is the length that is used for scaling ratings vs certainty. 01358 adapt_results->BlobLength = 01359 IntCastRounded(sample.outline_length() / kStandardFeatureLength); 01360 GenericVector<UnicharRating> unichar_results; 01361 static_classifier_->UnicharClassifySample(sample, blob->denorm().pix(), 0, 01362 -1, &unichar_results); 01363 // Convert results to the format used internally by AdaptiveClassifier. 01364 for (int r = 0; r < unichar_results.size(); ++r) { 01365 AddNewResult(unichar_results[r], adapt_results); 01366 } 01367 return sample.num_features(); 01368 } /* CharNormClassifier */ 01369 01370 // As CharNormClassifier, but operates on a TrainingSample and outputs to 01371 // a GenericVector of ShapeRating without conversion to classes. 01372 int Classify::CharNormTrainingSample(bool pruner_only, 01373 int keep_this, 01374 const TrainingSample& sample, 01375 GenericVector<UnicharRating>* results) { 01376 results->clear(); 01377 ADAPT_RESULTS* adapt_results = new ADAPT_RESULTS(); 01378 adapt_results->Initialize(); 01379 // Compute the bounding box of the features. 01380 int num_features = sample.num_features(); 01381 // Only the top and bottom of the blob_box are used by MasterMatcher, so 01382 // fabricate right and left using top and bottom. 01383 TBOX blob_box(sample.geo_feature(GeoBottom), sample.geo_feature(GeoBottom), 01384 sample.geo_feature(GeoTop), sample.geo_feature(GeoTop)); 01385 // Compute the char_norm_array from the saved cn_feature. 01386 FEATURE norm_feature = sample.GetCNFeature(); 01387 uinT8* char_norm_array = new uinT8[unicharset.size()]; 01388 int num_pruner_classes = MAX(unicharset.size(), 01389 PreTrainedTemplates->NumClasses); 01390 uinT8* pruner_norm_array = new uinT8[num_pruner_classes]; 01391 adapt_results->BlobLength = 01392 static_cast<int>(ActualOutlineLength(norm_feature) * 20 + 0.5); 01393 ComputeCharNormArrays(norm_feature, PreTrainedTemplates, char_norm_array, 01394 pruner_norm_array); 01395 01396 PruneClasses(PreTrainedTemplates, num_features, keep_this, sample.features(), 01397 pruner_norm_array, 01398 shape_table_ != NULL ? &shapetable_cutoffs_[0] : CharNormCutoffs, 01399 &adapt_results->CPResults); 01400 delete [] pruner_norm_array; 01401 if (keep_this >= 0) { 01402 adapt_results->CPResults[0].Class = keep_this; 01403 adapt_results->CPResults.truncate(1); 01404 } 01405 if (pruner_only) { 01406 // Convert pruner results to output format. 01407 for (int i = 0; i < adapt_results->CPResults.size(); ++i) { 01408 int class_id = adapt_results->CPResults[i].Class; 01409 results->push_back( 01410 UnicharRating(class_id, 1.0f - adapt_results->CPResults[i].Rating)); 01411 } 01412 } else { 01413 MasterMatcher(PreTrainedTemplates, num_features, sample.features(), 01414 char_norm_array, 01415 NULL, matcher_debug_flags, 01416 classify_integer_matcher_multiplier, 01417 blob_box, adapt_results->CPResults, adapt_results); 01418 // Convert master matcher results to output format. 01419 for (int i = 0; i < adapt_results->match.size(); i++) { 01420 results->push_back(adapt_results->match[i]); 01421 } 01422 results->sort(&UnicharRating::SortDescendingRating); 01423 } 01424 delete [] char_norm_array; 01425 delete adapt_results; 01426 return num_features; 01427 } /* CharNormTrainingSample */ 01428 01429 01430 /*---------------------------------------------------------------------------*/ 01445 void Classify::ClassifyAsNoise(ADAPT_RESULTS *results) { 01446 float rating = results->BlobLength / matcher_avg_noise_size; 01447 rating *= rating; 01448 rating /= 1.0 + rating; 01449 01450 AddNewResult(UnicharRating(UNICHAR_SPACE, 1.0f - rating), results); 01451 } /* ClassifyAsNoise */ 01452 01459 void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box, 01460 ADAPT_RESULTS *Results, 01461 BLOB_CHOICE_LIST *Choices) { 01462 assert(Choices != NULL); 01463 FLOAT32 Rating; 01464 FLOAT32 Certainty; 01465 BLOB_CHOICE_IT temp_it; 01466 bool contains_nonfrag = false; 01467 temp_it.set_to_list(Choices); 01468 int choices_length = 0; 01469 // With no shape_table_ maintain the previous MAX_MATCHES as the maximum 01470 // number of returned results, but with a shape_table_ we want to have room 01471 // for at least the biggest shape (which might contain hundreds of Indic 01472 // grapheme fragments) and more, so use double the size of the biggest shape 01473 // if that is more than the default. 01474 int max_matches = MAX_MATCHES; 01475 if (shape_table_ != NULL) { 01476 max_matches = shape_table_->MaxNumUnichars() * 2; 01477 if (max_matches < MAX_MATCHES) 01478 max_matches = MAX_MATCHES; 01479 } 01480 01481 float best_certainty = -MAX_FLOAT32; 01482 for (int i = 0; i < Results->match.size(); i++) { 01483 const UnicharRating& result = Results->match[i]; 01484 bool adapted = result.adapted; 01485 bool current_is_frag = (unicharset.get_fragment(result.unichar_id) != NULL); 01486 if (temp_it.length()+1 == max_matches && 01487 !contains_nonfrag && current_is_frag) { 01488 continue; // look for a non-fragmented character to fill the 01489 // last spot in Choices if only fragments are present 01490 } 01491 // BlobLength can never be legally 0, this means recognition failed. 01492 // But we must return a classification result because some invoking 01493 // functions (chopper/permuter) do not anticipate a null blob choice. 01494 // So we need to assign a poor, but not infinitely bad score. 01495 if (Results->BlobLength == 0) { 01496 Certainty = -20; 01497 Rating = 100; // should be -certainty * real_blob_length 01498 } else { 01499 Rating = Certainty = (1.0f - result.rating); 01500 Rating *= rating_scale * Results->BlobLength; 01501 Certainty *= -(getDict().certainty_scale); 01502 } 01503 // Adapted results, by their very nature, should have good certainty. 01504 // Those that don't are at best misleading, and often lead to errors, 01505 // so don't accept adapted results that are too far behind the best result, 01506 // whether adapted or static. 01507 // TODO(rays) find some way of automatically tuning these constants. 01508 if (Certainty > best_certainty) { 01509 best_certainty = MIN(Certainty, classify_adapted_pruning_threshold); 01510 } else if (adapted && 01511 Certainty / classify_adapted_pruning_factor < best_certainty) { 01512 continue; // Don't accept bad adapted results. 01513 } 01514 01515 float min_xheight, max_xheight, yshift; 01516 denorm.XHeightRange(result.unichar_id, unicharset, box, 01517 &min_xheight, &max_xheight, &yshift); 01518 BLOB_CHOICE* choice = 01519 new BLOB_CHOICE(result.unichar_id, Rating, Certainty, 01520 unicharset.get_script(result.unichar_id), 01521 min_xheight, max_xheight, yshift, 01522 adapted ? BCC_ADAPTED_CLASSIFIER 01523 : BCC_STATIC_CLASSIFIER); 01524 choice->set_fonts(result.fonts); 01525 temp_it.add_to_end(choice); 01526 contains_nonfrag |= !current_is_frag; // update contains_nonfrag 01527 choices_length++; 01528 if (choices_length >= max_matches) break; 01529 } 01530 Results->match.truncate(choices_length); 01531 } // ConvertMatchesToChoices 01532 01533 01534 /*---------------------------------------------------------------------------*/ 01535 #ifndef GRAPHICS_DISABLED 01536 01546 void Classify::DebugAdaptiveClassifier(TBLOB *blob, 01547 ADAPT_RESULTS *Results) { 01548 if (static_classifier_ == NULL) return; 01549 INT_FX_RESULT_STRUCT fx_info; 01550 GenericVector<INT_FEATURE_STRUCT> bl_features; 01551 TrainingSample* sample = 01552 BlobToTrainingSample(*blob, false, &fx_info, &bl_features); 01553 if (sample == NULL) return; 01554 static_classifier_->DebugDisplay(*sample, blob->denorm().pix(), 01555 Results->best_unichar_id); 01556 } /* DebugAdaptiveClassifier */ 01557 #endif 01558 01559 /*---------------------------------------------------------------------------*/ 01582 void Classify::DoAdaptiveMatch(TBLOB *Blob, ADAPT_RESULTS *Results) { 01583 UNICHAR_ID *Ambiguities; 01584 01585 INT_FX_RESULT_STRUCT fx_info; 01586 GenericVector<INT_FEATURE_STRUCT> bl_features; 01587 TrainingSample* sample = 01588 BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info, 01589 &bl_features); 01590 if (sample == NULL) return; 01591 01592 if (AdaptedTemplates->NumPermClasses < matcher_permanent_classes_min || 01593 tess_cn_matching) { 01594 CharNormClassifier(Blob, *sample, Results); 01595 } else { 01596 Ambiguities = BaselineClassifier(Blob, bl_features, fx_info, 01597 AdaptedTemplates, Results); 01598 if ((!Results->match.empty() && 01599 MarginalMatch(Results->best_rating, 01600 matcher_reliable_adaptive_result) && 01601 !tess_bn_matching) || 01602 Results->match.empty()) { 01603 CharNormClassifier(Blob, *sample, Results); 01604 } else if (Ambiguities && *Ambiguities >= 0 && !tess_bn_matching) { 01605 AmbigClassifier(bl_features, fx_info, Blob, 01606 PreTrainedTemplates, 01607 AdaptedTemplates->Class, 01608 Ambiguities, 01609 Results); 01610 } 01611 } 01612 01613 // Force the blob to be classified as noise 01614 // if the results contain only fragments. 01615 // TODO(daria): verify that this is better than 01616 // just adding a NULL classification. 01617 if (!Results->HasNonfragment || Results->match.empty()) 01618 ClassifyAsNoise(Results); 01619 delete sample; 01620 } /* DoAdaptiveMatch */ 01621 01622 /*---------------------------------------------------------------------------*/ 01639 UNICHAR_ID *Classify::GetAmbiguities(TBLOB *Blob, 01640 CLASS_ID CorrectClass) { 01641 ADAPT_RESULTS *Results = new ADAPT_RESULTS(); 01642 UNICHAR_ID *Ambiguities; 01643 int i; 01644 01645 Results->Initialize(); 01646 INT_FX_RESULT_STRUCT fx_info; 01647 GenericVector<INT_FEATURE_STRUCT> bl_features; 01648 TrainingSample* sample = 01649 BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info, 01650 &bl_features); 01651 if (sample == NULL) { 01652 delete Results; 01653 return NULL; 01654 } 01655 01656 CharNormClassifier(Blob, *sample, Results); 01657 delete sample; 01658 RemoveBadMatches(Results); 01659 Results->match.sort(&UnicharRating::SortDescendingRating); 01660 01661 /* copy the class id's into an string of ambiguities - don't copy if 01662 the correct class is the only class id matched */ 01663 Ambiguities = new UNICHAR_ID[Results->match.size() + 1]; 01664 if (Results->match.size() > 1 || 01665 (Results->match.size() == 1 && 01666 Results->match[0].unichar_id != CorrectClass)) { 01667 for (i = 0; i < Results->match.size(); i++) 01668 Ambiguities[i] = Results->match[i].unichar_id; 01669 Ambiguities[i] = -1; 01670 } else { 01671 Ambiguities[0] = -1; 01672 } 01673 01674 delete Results; 01675 return Ambiguities; 01676 } /* GetAmbiguities */ 01677 01678 // Returns true if the given blob looks too dissimilar to any character 01679 // present in the classifier templates. 01680 bool Classify::LooksLikeGarbage(TBLOB *blob) { 01681 BLOB_CHOICE_LIST *ratings = new BLOB_CHOICE_LIST(); 01682 AdaptiveClassifier(blob, ratings); 01683 BLOB_CHOICE_IT ratings_it(ratings); 01684 const UNICHARSET &unicharset = getDict().getUnicharset(); 01685 if (classify_debug_character_fragments) { 01686 print_ratings_list("======================\nLooksLikeGarbage() got ", 01687 ratings, unicharset); 01688 } 01689 for (ratings_it.mark_cycle_pt(); !ratings_it.cycled_list(); 01690 ratings_it.forward()) { 01691 if (unicharset.get_fragment(ratings_it.data()->unichar_id()) != NULL) { 01692 continue; 01693 } 01694 float certainty = ratings_it.data()->certainty(); 01695 delete ratings; 01696 return certainty < 01697 classify_character_fragments_garbage_certainty_threshold; 01698 } 01699 delete ratings; 01700 return true; // no whole characters in ratings 01701 } 01702 01703 /*---------------------------------------------------------------------------*/ 01727 int Classify::GetCharNormFeature(const INT_FX_RESULT_STRUCT& fx_info, 01728 INT_TEMPLATES templates, 01729 uinT8* pruner_norm_array, 01730 uinT8* char_norm_array) { 01731 FEATURE norm_feature = NewFeature(&CharNormDesc); 01732 float baseline = kBlnBaselineOffset; 01733 float scale = MF_SCALE_FACTOR; 01734 norm_feature->Params[CharNormY] = (fx_info.Ymean - baseline) * scale; 01735 norm_feature->Params[CharNormLength] = 01736 fx_info.Length * scale / LENGTH_COMPRESSION; 01737 norm_feature->Params[CharNormRx] = fx_info.Rx * scale; 01738 norm_feature->Params[CharNormRy] = fx_info.Ry * scale; 01739 // Deletes norm_feature. 01740 ComputeCharNormArrays(norm_feature, templates, char_norm_array, 01741 pruner_norm_array); 01742 return IntCastRounded(fx_info.Length / kStandardFeatureLength); 01743 } /* GetCharNormFeature */ 01744 01745 // Computes the char_norm_array for the unicharset and, if not NULL, the 01746 // pruner_array as appropriate according to the existence of the shape_table. 01747 void Classify::ComputeCharNormArrays(FEATURE_STRUCT* norm_feature, 01748 INT_TEMPLATES_STRUCT* templates, 01749 uinT8* char_norm_array, 01750 uinT8* pruner_array) { 01751 ComputeIntCharNormArray(*norm_feature, char_norm_array); 01752 if (pruner_array != NULL) { 01753 if (shape_table_ == NULL) { 01754 ComputeIntCharNormArray(*norm_feature, pruner_array); 01755 } else { 01756 memset(pruner_array, MAX_UINT8, 01757 templates->NumClasses * sizeof(pruner_array[0])); 01758 // Each entry in the pruner norm array is the MIN of all the entries of 01759 // the corresponding unichars in the CharNormArray. 01760 for (int id = 0; id < templates->NumClasses; ++id) { 01761 int font_set_id = templates->Class[id]->font_set_id; 01762 const FontSet &fs = fontset_table_.get(font_set_id); 01763 for (int config = 0; config < fs.size; ++config) { 01764 const Shape& shape = shape_table_->GetShape(fs.configs[config]); 01765 for (int c = 0; c < shape.size(); ++c) { 01766 if (char_norm_array[shape[c].unichar_id] < pruner_array[id]) 01767 pruner_array[id] = char_norm_array[shape[c].unichar_id]; 01768 } 01769 } 01770 } 01771 } 01772 } 01773 FreeFeature(norm_feature); 01774 } 01775 01776 /*---------------------------------------------------------------------------*/ 01791 int Classify::MakeNewTemporaryConfig(ADAPT_TEMPLATES Templates, 01792 CLASS_ID ClassId, 01793 int FontinfoId, 01794 int NumFeatures, 01795 INT_FEATURE_ARRAY Features, 01796 FEATURE_SET FloatFeatures) { 01797 INT_CLASS IClass; 01798 ADAPT_CLASS Class; 01799 PROTO_ID OldProtos[MAX_NUM_PROTOS]; 01800 FEATURE_ID BadFeatures[MAX_NUM_INT_FEATURES]; 01801 int NumOldProtos; 01802 int NumBadFeatures; 01803 int MaxProtoId, OldMaxProtoId; 01804 int BlobLength = 0; 01805 int MaskSize; 01806 int ConfigId; 01807 TEMP_CONFIG Config; 01808 int i; 01809 int debug_level = NO_DEBUG; 01810 01811 if (classify_learning_debug_level >= 3) 01812 debug_level = 01813 PRINT_MATCH_SUMMARY | PRINT_FEATURE_MATCHES | PRINT_PROTO_MATCHES; 01814 01815 IClass = ClassForClassId(Templates->Templates, ClassId); 01816 Class = Templates->Class[ClassId]; 01817 01818 if (IClass->NumConfigs >= MAX_NUM_CONFIGS) { 01819 ++NumAdaptationsFailed; 01820 if (classify_learning_debug_level >= 1) 01821 cprintf("Cannot make new temporary config: maximum number exceeded.\n"); 01822 return -1; 01823 } 01824 01825 OldMaxProtoId = IClass->NumProtos - 1; 01826 01827 NumOldProtos = im_.FindGoodProtos(IClass, AllProtosOn, AllConfigsOff, 01828 BlobLength, NumFeatures, Features, 01829 OldProtos, classify_adapt_proto_threshold, 01830 debug_level); 01831 01832 MaskSize = WordsInVectorOfSize(MAX_NUM_PROTOS); 01833 zero_all_bits(TempProtoMask, MaskSize); 01834 for (i = 0; i < NumOldProtos; i++) 01835 SET_BIT(TempProtoMask, OldProtos[i]); 01836 01837 NumBadFeatures = im_.FindBadFeatures(IClass, TempProtoMask, AllConfigsOn, 01838 BlobLength, NumFeatures, Features, 01839 BadFeatures, 01840 classify_adapt_feature_threshold, 01841 debug_level); 01842 01843 MaxProtoId = MakeNewTempProtos(FloatFeatures, NumBadFeatures, BadFeatures, 01844 IClass, Class, TempProtoMask); 01845 if (MaxProtoId == NO_PROTO) { 01846 ++NumAdaptationsFailed; 01847 if (classify_learning_debug_level >= 1) 01848 cprintf("Cannot make new temp protos: maximum number exceeded.\n"); 01849 return -1; 01850 } 01851 01852 ConfigId = AddIntConfig(IClass); 01853 ConvertConfig(TempProtoMask, ConfigId, IClass); 01854 Config = NewTempConfig(MaxProtoId, FontinfoId); 01855 TempConfigFor(Class, ConfigId) = Config; 01856 copy_all_bits(TempProtoMask, Config->Protos, Config->ProtoVectorSize); 01857 01858 if (classify_learning_debug_level >= 1) 01859 cprintf("Making new temp config %d fontinfo id %d" 01860 " using %d old and %d new protos.\n", 01861 ConfigId, Config->FontinfoId, 01862 NumOldProtos, MaxProtoId - OldMaxProtoId); 01863 01864 return ConfigId; 01865 } /* MakeNewTemporaryConfig */ 01866 01867 /*---------------------------------------------------------------------------*/ 01888 PROTO_ID Classify::MakeNewTempProtos(FEATURE_SET Features, 01889 int NumBadFeat, 01890 FEATURE_ID BadFeat[], 01891 INT_CLASS IClass, 01892 ADAPT_CLASS Class, 01893 BIT_VECTOR TempProtoMask) { 01894 FEATURE_ID *ProtoStart; 01895 FEATURE_ID *ProtoEnd; 01896 FEATURE_ID *LastBad; 01897 TEMP_PROTO TempProto; 01898 PROTO Proto; 01899 FEATURE F1, F2; 01900 FLOAT32 X1, X2, Y1, Y2; 01901 FLOAT32 A1, A2, AngleDelta; 01902 FLOAT32 SegmentLength; 01903 PROTO_ID Pid; 01904 01905 for (ProtoStart = BadFeat, LastBad = ProtoStart + NumBadFeat; 01906 ProtoStart < LastBad; ProtoStart = ProtoEnd) { 01907 F1 = Features->Features[*ProtoStart]; 01908 X1 = F1->Params[PicoFeatX]; 01909 Y1 = F1->Params[PicoFeatY]; 01910 A1 = F1->Params[PicoFeatDir]; 01911 01912 for (ProtoEnd = ProtoStart + 1, 01913 SegmentLength = GetPicoFeatureLength(); 01914 ProtoEnd < LastBad; 01915 ProtoEnd++, SegmentLength += GetPicoFeatureLength()) { 01916 F2 = Features->Features[*ProtoEnd]; 01917 X2 = F2->Params[PicoFeatX]; 01918 Y2 = F2->Params[PicoFeatY]; 01919 A2 = F2->Params[PicoFeatDir]; 01920 01921 AngleDelta = fabs(A1 - A2); 01922 if (AngleDelta > 0.5) 01923 AngleDelta = 1.0 - AngleDelta; 01924 01925 if (AngleDelta > matcher_clustering_max_angle_delta || 01926 fabs(X1 - X2) > SegmentLength || 01927 fabs(Y1 - Y2) > SegmentLength) 01928 break; 01929 } 01930 01931 F2 = Features->Features[*(ProtoEnd - 1)]; 01932 X2 = F2->Params[PicoFeatX]; 01933 Y2 = F2->Params[PicoFeatY]; 01934 A2 = F2->Params[PicoFeatDir]; 01935 01936 Pid = AddIntProto(IClass); 01937 if (Pid == NO_PROTO) 01938 return (NO_PROTO); 01939 01940 TempProto = NewTempProto(); 01941 Proto = &(TempProto->Proto); 01942 01943 /* compute proto params - NOTE that Y_DIM_OFFSET must be used because 01944 ConvertProto assumes that the Y dimension varies from -0.5 to 0.5 01945 instead of the -0.25 to 0.75 used in baseline normalization */ 01946 Proto->Length = SegmentLength; 01947 Proto->Angle = A1; 01948 Proto->X = (X1 + X2) / 2.0; 01949 Proto->Y = (Y1 + Y2) / 2.0 - Y_DIM_OFFSET; 01950 FillABC(Proto); 01951 01952 TempProto->ProtoId = Pid; 01953 SET_BIT(TempProtoMask, Pid); 01954 01955 ConvertProto(Proto, Pid, IClass); 01956 AddProtoToProtoPruner(Proto, Pid, IClass, 01957 classify_learning_debug_level >= 2); 01958 01959 Class->TempProtos = push(Class->TempProtos, TempProto); 01960 } 01961 return IClass->NumProtos - 1; 01962 } /* MakeNewTempProtos */ 01963 01964 /*---------------------------------------------------------------------------*/ 01977 void Classify::MakePermanent(ADAPT_TEMPLATES Templates, 01978 CLASS_ID ClassId, 01979 int ConfigId, 01980 TBLOB *Blob) { 01981 UNICHAR_ID *Ambigs; 01982 TEMP_CONFIG Config; 01983 ADAPT_CLASS Class; 01984 PROTO_KEY ProtoKey; 01985 01986 Class = Templates->Class[ClassId]; 01987 Config = TempConfigFor(Class, ConfigId); 01988 01989 MakeConfigPermanent(Class, ConfigId); 01990 if (Class->NumPermConfigs == 0) 01991 Templates->NumPermClasses++; 01992 Class->NumPermConfigs++; 01993 01994 // Initialize permanent config. 01995 Ambigs = GetAmbiguities(Blob, ClassId); 01996 PERM_CONFIG Perm = (PERM_CONFIG) alloc_struct(sizeof(PERM_CONFIG_STRUCT), 01997 "PERM_CONFIG_STRUCT"); 01998 Perm->Ambigs = Ambigs; 01999 Perm->FontinfoId = Config->FontinfoId; 02000 02001 // Free memory associated with temporary config (since ADAPTED_CONFIG 02002 // is a union we need to clean up before we record permanent config). 02003 ProtoKey.Templates = Templates; 02004 ProtoKey.ClassId = ClassId; 02005 ProtoKey.ConfigId = ConfigId; 02006 Class->TempProtos = delete_d(Class->TempProtos, &ProtoKey, MakeTempProtoPerm); 02007 FreeTempConfig(Config); 02008 02009 // Record permanent config. 02010 PermConfigFor(Class, ConfigId) = Perm; 02011 02012 if (classify_learning_debug_level >= 1) { 02013 tprintf("Making config %d for %s (ClassId %d) permanent:" 02014 " fontinfo id %d, ambiguities '", 02015 ConfigId, getDict().getUnicharset().debug_str(ClassId).string(), 02016 ClassId, PermConfigFor(Class, ConfigId)->FontinfoId); 02017 for (UNICHAR_ID *AmbigsPointer = Ambigs; 02018 *AmbigsPointer >= 0; ++AmbigsPointer) 02019 tprintf("%s", unicharset.id_to_unichar(*AmbigsPointer)); 02020 tprintf("'.\n"); 02021 } 02022 } /* MakePermanent */ 02023 } // namespace tesseract 02024 02025 /*---------------------------------------------------------------------------*/ 02040 int MakeTempProtoPerm(void *item1, void *item2) { 02041 ADAPT_CLASS Class; 02042 TEMP_CONFIG Config; 02043 TEMP_PROTO TempProto; 02044 PROTO_KEY *ProtoKey; 02045 02046 TempProto = (TEMP_PROTO) item1; 02047 ProtoKey = (PROTO_KEY *) item2; 02048 02049 Class = ProtoKey->Templates->Class[ProtoKey->ClassId]; 02050 Config = TempConfigFor(Class, ProtoKey->ConfigId); 02051 02052 if (TempProto->ProtoId > Config->MaxProtoId || 02053 !test_bit (Config->Protos, TempProto->ProtoId)) 02054 return FALSE; 02055 02056 MakeProtoPermanent(Class, TempProto->ProtoId); 02057 AddProtoToClassPruner(&(TempProto->Proto), ProtoKey->ClassId, 02058 ProtoKey->Templates->Templates); 02059 FreeTempProto(TempProto); 02060 02061 return TRUE; 02062 } /* MakeTempProtoPerm */ 02063 02064 /*---------------------------------------------------------------------------*/ 02065 namespace tesseract { 02076 void Classify::PrintAdaptiveMatchResults(const ADAPT_RESULTS& results) { 02077 for (int i = 0; i < results.match.size(); ++i) { 02078 tprintf("%s ", unicharset.debug_str(results.match[i].unichar_id).string()); 02079 results.match[i].Print(); 02080 } 02081 } /* PrintAdaptiveMatchResults */ 02082 02083 /*---------------------------------------------------------------------------*/ 02099 void Classify::RemoveBadMatches(ADAPT_RESULTS *Results) { 02100 int Next, NextGood; 02101 FLOAT32 BadMatchThreshold; 02102 static const char* romans = "i v x I V X"; 02103 BadMatchThreshold = Results->best_rating - matcher_bad_match_pad; 02104 02105 if (classify_bln_numeric_mode) { 02106 UNICHAR_ID unichar_id_one = unicharset.contains_unichar("1") ? 02107 unicharset.unichar_to_id("1") : -1; 02108 UNICHAR_ID unichar_id_zero = unicharset.contains_unichar("0") ? 02109 unicharset.unichar_to_id("0") : -1; 02110 float scored_one = ScoredUnichar(unichar_id_one, *Results); 02111 float scored_zero = ScoredUnichar(unichar_id_zero, *Results); 02112 02113 for (Next = NextGood = 0; Next < Results->match.size(); Next++) { 02114 const UnicharRating& match = Results->match[Next]; 02115 if (match.rating >= BadMatchThreshold) { 02116 if (!unicharset.get_isalpha(match.unichar_id) || 02117 strstr(romans, 02118 unicharset.id_to_unichar(match.unichar_id)) != NULL) { 02119 } else if (unicharset.eq(match.unichar_id, "l") && 02120 scored_one < BadMatchThreshold) { 02121 Results->match[Next].unichar_id = unichar_id_one; 02122 } else if (unicharset.eq(match.unichar_id, "O") && 02123 scored_zero < BadMatchThreshold) { 02124 Results->match[Next].unichar_id = unichar_id_zero; 02125 } else { 02126 Results->match[Next].unichar_id = INVALID_UNICHAR_ID; // Don't copy. 02127 } 02128 if (Results->match[Next].unichar_id != INVALID_UNICHAR_ID) { 02129 if (NextGood == Next) { 02130 ++NextGood; 02131 } else { 02132 Results->match[NextGood++] = Results->match[Next]; 02133 } 02134 } 02135 } 02136 } 02137 } else { 02138 for (Next = NextGood = 0; Next < Results->match.size(); Next++) { 02139 if (Results->match[Next].rating >= BadMatchThreshold) { 02140 if (NextGood == Next) { 02141 ++NextGood; 02142 } else { 02143 Results->match[NextGood++] = Results->match[Next]; 02144 } 02145 } 02146 } 02147 } 02148 Results->match.truncate(NextGood); 02149 } /* RemoveBadMatches */ 02150 02151 /*----------------------------------------------------------------------------*/ 02161 void Classify::RemoveExtraPuncs(ADAPT_RESULTS *Results) { 02162 int Next, NextGood; 02163 int punc_count; /*no of garbage characters */ 02164 int digit_count; 02165 /*garbage characters */ 02166 static char punc_chars[] = ". , ; : / ` ~ ' - = \\ | \" ! _ ^"; 02167 static char digit_chars[] = "0 1 2 3 4 5 6 7 8 9"; 02168 02169 punc_count = 0; 02170 digit_count = 0; 02171 for (Next = NextGood = 0; Next < Results->match.size(); Next++) { 02172 const UnicharRating& match = Results->match[Next]; 02173 bool keep = true; 02174 if (strstr(punc_chars, 02175 unicharset.id_to_unichar(match.unichar_id)) != NULL) { 02176 if (punc_count >= 2) 02177 keep = false; 02178 punc_count++; 02179 } else { 02180 if (strstr(digit_chars, 02181 unicharset.id_to_unichar(match.unichar_id)) != NULL) { 02182 if (digit_count >= 1) 02183 keep = false; 02184 digit_count++; 02185 } 02186 } 02187 if (keep) { 02188 if (NextGood == Next) { 02189 ++NextGood; 02190 } else { 02191 Results->match[NextGood++] = match; 02192 } 02193 } 02194 } 02195 Results->match.truncate(NextGood); 02196 } /* RemoveExtraPuncs */ 02197 02198 /*---------------------------------------------------------------------------*/ 02212 void Classify::SetAdaptiveThreshold(FLOAT32 Threshold) { 02213 Threshold = (Threshold == matcher_good_threshold) ? 0.9: (1.0 - Threshold); 02214 classify_adapt_proto_threshold.set_value( 02215 ClipToRange<int>(255 * Threshold, 0, 255)); 02216 classify_adapt_feature_threshold.set_value( 02217 ClipToRange<int>(255 * Threshold, 0, 255)); 02218 } /* SetAdaptiveThreshold */ 02219 02220 /*---------------------------------------------------------------------------*/ 02233 void Classify::ShowBestMatchFor(int shape_id, 02234 const INT_FEATURE_STRUCT* features, 02235 int num_features) { 02236 #ifndef GRAPHICS_DISABLED 02237 uinT32 config_mask; 02238 if (UnusedClassIdIn(PreTrainedTemplates, shape_id)) { 02239 tprintf("No built-in templates for class/shape %d\n", shape_id); 02240 return; 02241 } 02242 if (num_features <= 0) { 02243 tprintf("Illegal blob (char norm features)!\n"); 02244 return; 02245 } 02246 UnicharRating cn_result; 02247 classify_norm_method.set_value(character); 02248 im_.Match(ClassForClassId(PreTrainedTemplates, shape_id), 02249 AllProtosOn, AllConfigsOn, 02250 num_features, features, &cn_result, 02251 classify_adapt_feature_threshold, NO_DEBUG, 02252 matcher_debug_separate_windows); 02253 tprintf("\n"); 02254 config_mask = 1 << cn_result.config; 02255 02256 tprintf("Static Shape ID: %d\n", shape_id); 02257 ShowMatchDisplay(); 02258 im_.Match(ClassForClassId(PreTrainedTemplates, shape_id), 02259 AllProtosOn, reinterpret_cast<BIT_VECTOR>(&config_mask), 02260 num_features, features, &cn_result, 02261 classify_adapt_feature_threshold, 02262 matcher_debug_flags, 02263 matcher_debug_separate_windows); 02264 UpdateMatchDisplay(); 02265 #endif // GRAPHICS_DISABLED 02266 } /* ShowBestMatchFor */ 02267 02268 // Returns a string for the classifier class_id: either the corresponding 02269 // unicharset debug_str or the shape_table_ debug str. 02270 STRING Classify::ClassIDToDebugStr(const INT_TEMPLATES_STRUCT* templates, 02271 int class_id, int config_id) const { 02272 STRING class_string; 02273 if (templates == PreTrainedTemplates && shape_table_ != NULL) { 02274 int shape_id = ClassAndConfigIDToFontOrShapeID(class_id, config_id); 02275 class_string = shape_table_->DebugStr(shape_id); 02276 } else { 02277 class_string = unicharset.debug_str(class_id); 02278 } 02279 return class_string; 02280 } 02281 02282 // Converts a classifier class_id index to a shape_table_ index 02283 int Classify::ClassAndConfigIDToFontOrShapeID(int class_id, 02284 int int_result_config) const { 02285 int font_set_id = PreTrainedTemplates->Class[class_id]->font_set_id; 02286 // Older inttemps have no font_ids. 02287 if (font_set_id < 0) 02288 return kBlankFontinfoId; 02289 const FontSet &fs = fontset_table_.get(font_set_id); 02290 ASSERT_HOST(int_result_config >= 0 && int_result_config < fs.size); 02291 return fs.configs[int_result_config]; 02292 } 02293 02294 // Converts a shape_table_ index to a classifier class_id index (not a 02295 // unichar-id!). Uses a search, so not fast. 02296 int Classify::ShapeIDToClassID(int shape_id) const { 02297 for (int id = 0; id < PreTrainedTemplates->NumClasses; ++id) { 02298 int font_set_id = PreTrainedTemplates->Class[id]->font_set_id; 02299 ASSERT_HOST(font_set_id >= 0); 02300 const FontSet &fs = fontset_table_.get(font_set_id); 02301 for (int config = 0; config < fs.size; ++config) { 02302 if (fs.configs[config] == shape_id) 02303 return id; 02304 } 02305 } 02306 tprintf("Shape %d not found\n", shape_id); 02307 return -1; 02308 } 02309 02310 // Returns true if the given TEMP_CONFIG is good enough to make it 02311 // a permanent config. 02312 bool Classify::TempConfigReliable(CLASS_ID class_id, 02313 const TEMP_CONFIG &config) { 02314 if (classify_learning_debug_level >= 1) { 02315 tprintf("NumTimesSeen for config of %s is %d\n", 02316 getDict().getUnicharset().debug_str(class_id).string(), 02317 config->NumTimesSeen); 02318 } 02319 if (config->NumTimesSeen >= matcher_sufficient_examples_for_prototyping) { 02320 return true; 02321 } else if (config->NumTimesSeen < matcher_min_examples_for_prototyping) { 02322 return false; 02323 } else if (use_ambigs_for_adaption) { 02324 // Go through the ambigs vector and see whether we have already seen 02325 // enough times all the characters represented by the ambigs vector. 02326 const UnicharIdVector *ambigs = 02327 getDict().getUnicharAmbigs().AmbigsForAdaption(class_id); 02328 int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size(); 02329 for (int ambig = 0; ambig < ambigs_size; ++ambig) { 02330 ADAPT_CLASS ambig_class = AdaptedTemplates->Class[(*ambigs)[ambig]]; 02331 assert(ambig_class != NULL); 02332 if (ambig_class->NumPermConfigs == 0 && 02333 ambig_class->MaxNumTimesSeen < 02334 matcher_min_examples_for_prototyping) { 02335 if (classify_learning_debug_level >= 1) { 02336 tprintf("Ambig %s has not been seen enough times," 02337 " not making config for %s permanent\n", 02338 getDict().getUnicharset().debug_str( 02339 (*ambigs)[ambig]).string(), 02340 getDict().getUnicharset().debug_str(class_id).string()); 02341 } 02342 return false; 02343 } 02344 } 02345 } 02346 return true; 02347 } 02348 02349 void Classify::UpdateAmbigsGroup(CLASS_ID class_id, TBLOB *Blob) { 02350 const UnicharIdVector *ambigs = 02351 getDict().getUnicharAmbigs().ReverseAmbigsForAdaption(class_id); 02352 int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size(); 02353 if (classify_learning_debug_level >= 1) { 02354 tprintf("Running UpdateAmbigsGroup for %s class_id=%d\n", 02355 getDict().getUnicharset().debug_str(class_id).string(), class_id); 02356 } 02357 for (int ambig = 0; ambig < ambigs_size; ++ambig) { 02358 CLASS_ID ambig_class_id = (*ambigs)[ambig]; 02359 const ADAPT_CLASS ambigs_class = AdaptedTemplates->Class[ambig_class_id]; 02360 for (int cfg = 0; cfg < MAX_NUM_CONFIGS; ++cfg) { 02361 if (ConfigIsPermanent(ambigs_class, cfg)) continue; 02362 const TEMP_CONFIG config = 02363 TempConfigFor(AdaptedTemplates->Class[ambig_class_id], cfg); 02364 if (config != NULL && TempConfigReliable(ambig_class_id, config)) { 02365 if (classify_learning_debug_level >= 1) { 02366 tprintf("Making config %d of %s permanent\n", cfg, 02367 getDict().getUnicharset().debug_str( 02368 ambig_class_id).string()); 02369 } 02370 MakePermanent(AdaptedTemplates, ambig_class_id, cfg, Blob); 02371 } 02372 } 02373 } 02374 } 02375 02376 } // namespace tesseract