tesseract
3.03
|
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 "ambigs.h" 00028 #include "blobclass.h" 00029 #include "blobs.h" 00030 #include "helpers.h" 00031 #include "normfeat.h" 00032 #include "mfoutline.h" 00033 #include "picofeat.h" 00034 #include "float2int.h" 00035 #include "outfeat.h" 00036 #include "emalloc.h" 00037 #include "intfx.h" 00038 #include "efio.h" 00039 #include "normmatch.h" 00040 #include "ndminx.h" 00041 #include "intproto.h" 00042 #include "const.h" 00043 #include "globals.h" 00044 #include "werd.h" 00045 #include "callcpp.h" 00046 #include "pageres.h" 00047 #include "params.h" 00048 #include "classify.h" 00049 #include "shapetable.h" 00050 #include "tessclassifier.h" 00051 #include "trainingsample.h" 00052 #include "unicharset.h" 00053 #include "dict.h" 00054 #include "featdefs.h" 00055 #include "genericvector.h" 00056 00057 #include <stdio.h> 00058 #include <string.h> 00059 #include <stdlib.h> 00060 #include <math.h> 00061 #ifdef __UNIX__ 00062 #include <assert.h> 00063 #endif 00064 00065 #define ADAPT_TEMPLATE_SUFFIX ".a" 00066 00067 #define MAX_MATCHES 10 00068 #define UNLIKELY_NUM_FEAT 200 00069 #define NO_DEBUG 0 00070 #define MAX_ADAPTABLE_WERD_SIZE 40 00071 00072 #define ADAPTABLE_WERD_ADJUSTMENT (0.05) 00073 00074 #define Y_DIM_OFFSET (Y_SHIFT - BASELINE_Y_SHIFT) 00075 00076 #define WORST_POSSIBLE_RATING (1.0) 00077 00078 struct ScoredClass { 00079 CLASS_ID unichar_id; 00080 int shape_id; 00081 FLOAT32 rating; 00082 bool adapted; 00083 inT16 config; 00084 inT16 fontinfo_id; 00085 inT16 fontinfo_id2; 00086 }; 00087 00088 struct ADAPT_RESULTS { 00089 inT32 BlobLength; 00090 bool HasNonfragment; 00091 GenericVector<ScoredClass> match; 00092 ScoredClass best_match; 00093 GenericVector<CP_RESULT_STRUCT> CPResults; 00094 00097 inline void Initialize() { 00098 BlobLength = MAX_INT32; 00099 HasNonfragment = false; 00100 best_match.unichar_id = NO_CLASS; 00101 best_match.shape_id = -1; 00102 best_match.rating = WORST_POSSIBLE_RATING; 00103 best_match.adapted = false; 00104 best_match.config = 0; 00105 best_match.fontinfo_id = kBlankFontinfoId; 00106 best_match.fontinfo_id2 = kBlankFontinfoId; 00107 } 00108 }; 00109 00110 struct PROTO_KEY { 00111 ADAPT_TEMPLATES Templates; 00112 CLASS_ID ClassId; 00113 int ConfigId; 00114 }; 00115 00116 /*----------------------------------------------------------------------------- 00117 Private Macros 00118 -----------------------------------------------------------------------------*/ 00119 #define MarginalMatch(Rating) \ 00120 ((Rating) > matcher_great_threshold) 00121 00122 /*----------------------------------------------------------------------------- 00123 Private Function Prototypes 00124 -----------------------------------------------------------------------------*/ 00125 int CompareByRating(const void *arg1, const void *arg2); 00126 00127 ScoredClass *FindScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id); 00128 00129 ScoredClass ScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id); 00130 00131 void InitMatcherRatings(register FLOAT32 *Rating); 00132 00133 int MakeTempProtoPerm(void *item1, void *item2); 00134 00135 void SetAdaptiveThreshold(FLOAT32 Threshold); 00136 00137 00138 /*----------------------------------------------------------------------------- 00139 Public Code 00140 -----------------------------------------------------------------------------*/ 00141 /*---------------------------------------------------------------------------*/ 00142 namespace tesseract { 00169 void Classify::AdaptiveClassifier(TBLOB *Blob, BLOB_CHOICE_LIST *Choices) { 00170 assert(Choices != NULL); 00171 ADAPT_RESULTS *Results = new ADAPT_RESULTS; 00172 Results->Initialize(); 00173 00174 ASSERT_HOST(AdaptedTemplates != NULL); 00175 00176 DoAdaptiveMatch(Blob, Results); 00177 00178 RemoveBadMatches(Results); 00179 Results->match.sort(CompareByRating); 00180 RemoveExtraPuncs(Results); 00181 ConvertMatchesToChoices(Blob->denorm(), Blob->bounding_box(), Results, 00182 Choices); 00183 00184 if (matcher_debug_level >= 1) { 00185 cprintf ("AD Matches = "); 00186 PrintAdaptiveMatchResults(stdout, Results); 00187 } 00188 00189 if (LargeSpeckle(*Blob) || Choices->length() == 0) 00190 AddLargeSpeckleTo(Results->BlobLength, Choices); 00191 00192 #ifndef GRAPHICS_DISABLED 00193 if (classify_enable_adaptive_debugger) 00194 DebugAdaptiveClassifier(Blob, Results); 00195 #endif 00196 00197 delete Results; 00198 } /* AdaptiveClassifier */ 00199 00200 // If *win is NULL, sets it to a new ScrollView() object with title msg. 00201 // Clears the window and draws baselines. 00202 void Classify::RefreshDebugWindow(ScrollView **win, const char *msg, 00203 int y_offset, const TBOX &wbox) { 00204 #ifndef GRAPHICS_DISABLED 00205 const int kSampleSpaceWidth = 500; 00206 if (*win == NULL) { 00207 *win = new ScrollView(msg, 100, y_offset, kSampleSpaceWidth * 2, 200, 00208 kSampleSpaceWidth * 2, 200, true); 00209 } 00210 (*win)->Clear(); 00211 (*win)->Pen(64, 64, 64); 00212 (*win)->Line(-kSampleSpaceWidth, kBlnBaselineOffset, 00213 kSampleSpaceWidth, kBlnBaselineOffset); 00214 (*win)->Line(-kSampleSpaceWidth, kBlnXHeight + kBlnBaselineOffset, 00215 kSampleSpaceWidth, kBlnXHeight + kBlnBaselineOffset); 00216 (*win)->ZoomToRectangle(wbox.left(), wbox.top(), 00217 wbox.right(), wbox.bottom()); 00218 #endif // GRAPHICS_DISABLED 00219 } 00220 00221 // Learns the given word using its chopped_word, seam_array, denorm, 00222 // box_word, best_state, and correct_text to learn both correctly and 00223 // incorrectly segmented blobs. If filename is not NULL, then LearnBlob 00224 // is called and the data will be written to a file for static training. 00225 // Otherwise AdaptToBlob is called for adaption within a document. 00226 // If rejmap is not NULL, then only chars with a rejmap entry of '1' will 00227 // be learned, otherwise all chars with good correct_text are learned. 00228 void Classify::LearnWord(const char* filename, WERD_RES *word) { 00229 int word_len = word->correct_text.size(); 00230 if (word_len == 0) return; 00231 00232 float* thresholds = NULL; 00233 if (filename == NULL) { 00234 // Adaption mode. 00235 if (!EnableLearning || word->best_choice == NULL) 00236 return; // Can't or won't adapt. 00237 00238 if (classify_learning_debug_level >= 1) 00239 tprintf("\n\nAdapting to word = %s\n", 00240 word->best_choice->debug_string().string()); 00241 thresholds = new float[word_len]; 00242 word->ComputeAdaptionThresholds(certainty_scale, 00243 matcher_perfect_threshold, 00244 matcher_good_threshold, 00245 matcher_rating_margin, thresholds); 00246 } 00247 int start_blob = 0; 00248 00249 #ifndef GRAPHICS_DISABLED 00250 if (classify_debug_character_fragments) { 00251 if (learn_fragmented_word_debug_win_ != NULL) { 00252 window_wait(learn_fragmented_word_debug_win_); 00253 } 00254 RefreshDebugWindow(&learn_fragments_debug_win_, "LearnPieces", 400, 00255 word->chopped_word->bounding_box()); 00256 RefreshDebugWindow(&learn_fragmented_word_debug_win_, "LearnWord", 200, 00257 word->chopped_word->bounding_box()); 00258 word->chopped_word->plot(learn_fragmented_word_debug_win_); 00259 ScrollView::Update(); 00260 } 00261 #endif // GRAPHICS_DISABLED 00262 00263 for (int ch = 0; ch < word_len; ++ch) { 00264 if (classify_debug_character_fragments) { 00265 tprintf("\nLearning %s\n", word->correct_text[ch].string()); 00266 } 00267 if (word->correct_text[ch].length() > 0) { 00268 float threshold = thresholds != NULL ? thresholds[ch] : 0.0f; 00269 00270 LearnPieces(filename, start_blob, word->best_state[ch], 00271 threshold, CST_WHOLE, word->correct_text[ch].string(), word); 00272 00273 if (word->best_state[ch] > 1 && !disable_character_fragments) { 00274 // Check that the character breaks into meaningful fragments 00275 // that each match a whole character with at least 00276 // classify_character_fragments_garbage_certainty_threshold 00277 bool garbage = false; 00278 int frag; 00279 for (frag = 0; frag < word->best_state[ch]; ++frag) { 00280 TBLOB* frag_blob = word->chopped_word->blobs[start_blob + frag]; 00281 if (classify_character_fragments_garbage_certainty_threshold < 0) { 00282 garbage |= LooksLikeGarbage(frag_blob); 00283 } 00284 } 00285 // Learn the fragments. 00286 if (!garbage) { 00287 bool pieces_all_natural = word->PiecesAllNatural(start_blob, 00288 word->best_state[ch]); 00289 if (pieces_all_natural || !prioritize_division) { 00290 for (frag = 0; frag < word->best_state[ch]; ++frag) { 00291 GenericVector<STRING> tokens; 00292 word->correct_text[ch].split(' ', &tokens); 00293 00294 tokens[0] = CHAR_FRAGMENT::to_string( 00295 tokens[0].string(), frag, word->best_state[ch], 00296 pieces_all_natural); 00297 00298 STRING full_string; 00299 for (int i = 0; i < tokens.size(); i++) { 00300 full_string += tokens[i]; 00301 if (i != tokens.size() - 1) 00302 full_string += ' '; 00303 } 00304 LearnPieces(filename, start_blob + frag, 1, 00305 threshold, CST_FRAGMENT, full_string.string(), word); 00306 } 00307 } 00308 } 00309 } 00310 00311 // TODO(rays): re-enable this part of the code when we switch to the 00312 // new classifier that needs to see examples of garbage. 00313 /* 00314 if (word->best_state[ch] > 1) { 00315 // If the next blob is good, make junk with the rightmost fragment. 00316 if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) { 00317 LearnPieces(filename, start_blob + word->best_state[ch] - 1, 00318 word->best_state[ch + 1] + 1, 00319 threshold, CST_IMPROPER, INVALID_UNICHAR, word); 00320 } 00321 // If the previous blob is good, make junk with the leftmost fragment. 00322 if (ch > 0 && word->correct_text[ch - 1].length() > 0) { 00323 LearnPieces(filename, start_blob - word->best_state[ch - 1], 00324 word->best_state[ch - 1] + 1, 00325 threshold, CST_IMPROPER, INVALID_UNICHAR, word); 00326 } 00327 } 00328 // If the next blob is good, make a join with it. 00329 if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) { 00330 STRING joined_text = word->correct_text[ch]; 00331 joined_text += word->correct_text[ch + 1]; 00332 LearnPieces(filename, start_blob, 00333 word->best_state[ch] + word->best_state[ch + 1], 00334 threshold, CST_NGRAM, joined_text.string(), word); 00335 } 00336 */ 00337 } 00338 start_blob += word->best_state[ch]; 00339 } 00340 delete [] thresholds; 00341 } // LearnWord. 00342 00343 // Builds a blob of length fragments, from the word, starting at start, 00344 // and then learns it, as having the given correct_text. 00345 // If filename is not NULL, then LearnBlob 00346 // is called and the data will be written to a file for static training. 00347 // Otherwise AdaptToBlob is called for adaption within a document. 00348 // threshold is a magic number required by AdaptToChar and generated by 00349 // ComputeAdaptionThresholds. 00350 // Although it can be partly inferred from the string, segmentation is 00351 // provided to explicitly clarify the character segmentation. 00352 void Classify::LearnPieces(const char* filename, int start, int length, 00353 float threshold, CharSegmentationType segmentation, 00354 const char* correct_text, WERD_RES *word) { 00355 // TODO(daria) Remove/modify this if/when we want 00356 // to train and/or adapt to n-grams. 00357 if (segmentation != CST_WHOLE && 00358 (segmentation != CST_FRAGMENT || disable_character_fragments)) 00359 return; 00360 00361 if (length > 1) { 00362 join_pieces(word->seam_array, start, start + length - 1, 00363 word->chopped_word); 00364 } 00365 TBLOB* blob = word->chopped_word->blobs[start]; 00366 // Rotate the blob if needed for classification. 00367 TBLOB* rotated_blob = blob->ClassifyNormalizeIfNeeded(); 00368 if (rotated_blob == NULL) 00369 rotated_blob = blob; 00370 00371 #ifndef GRAPHICS_DISABLED 00372 // Draw debug windows showing the blob that is being learned if needed. 00373 if (strcmp(classify_learn_debug_str.string(), correct_text) == 0) { 00374 RefreshDebugWindow(&learn_debug_win_, "LearnPieces", 600, 00375 word->chopped_word->bounding_box()); 00376 rotated_blob->plot(learn_debug_win_, ScrollView::GREEN, ScrollView::BROWN); 00377 learn_debug_win_->Update(); 00378 window_wait(learn_debug_win_); 00379 } 00380 if (classify_debug_character_fragments && segmentation == CST_FRAGMENT) { 00381 ASSERT_HOST(learn_fragments_debug_win_ != NULL); // set up in LearnWord 00382 blob->plot(learn_fragments_debug_win_, 00383 ScrollView::BLUE, ScrollView::BROWN); 00384 learn_fragments_debug_win_->Update(); 00385 } 00386 #endif // GRAPHICS_DISABLED 00387 00388 if (filename != NULL) { 00389 classify_norm_method.set_value(character); // force char norm spc 30/11/93 00390 tess_bn_matching.set_value(false); // turn it off 00391 tess_cn_matching.set_value(false); 00392 DENORM bl_denorm, cn_denorm; 00393 INT_FX_RESULT_STRUCT fx_info; 00394 SetupBLCNDenorms(*rotated_blob, classify_nonlinear_norm, 00395 &bl_denorm, &cn_denorm, &fx_info); 00396 LearnBlob(feature_defs_, filename, rotated_blob, bl_denorm, cn_denorm, 00397 fx_info, correct_text); 00398 } else if (unicharset.contains_unichar(correct_text)) { 00399 UNICHAR_ID class_id = unicharset.unichar_to_id(correct_text); 00400 int font_id = word->fontinfo != NULL 00401 ? fontinfo_table_.get_id(*word->fontinfo) 00402 : 0; 00403 if (classify_learning_debug_level >= 1) 00404 tprintf("Adapting to char = %s, thr= %g font_id= %d\n", 00405 unicharset.id_to_unichar(class_id), threshold, font_id); 00406 // If filename is not NULL we are doing recognition 00407 // (as opposed to training), so we must have already set word fonts. 00408 AdaptToChar(rotated_blob, class_id, font_id, threshold); 00409 } else if (classify_debug_level >= 1) { 00410 tprintf("Can't adapt to %s not in unicharset\n", correct_text); 00411 } 00412 if (rotated_blob != blob) { 00413 delete rotated_blob; 00414 } 00415 00416 break_pieces(word->seam_array, start, start + length - 1, word->chopped_word); 00417 } // LearnPieces. 00418 00419 /*---------------------------------------------------------------------------*/ 00434 void Classify::EndAdaptiveClassifier() { 00435 STRING Filename; 00436 FILE *File; 00437 00438 #ifndef SECURE_NAMES 00439 if (AdaptedTemplates != NULL && 00440 classify_enable_adaptive_matcher && classify_save_adapted_templates) { 00441 Filename = imagefile + ADAPT_TEMPLATE_SUFFIX; 00442 File = fopen (Filename.string(), "wb"); 00443 if (File == NULL) 00444 cprintf ("Unable to save adapted templates to %s!\n", Filename.string()); 00445 else { 00446 cprintf ("\nSaving adapted templates to %s ...", Filename.string()); 00447 fflush(stdout); 00448 WriteAdaptedTemplates(File, AdaptedTemplates); 00449 cprintf ("\n"); 00450 fclose(File); 00451 } 00452 } 00453 #endif 00454 00455 if (AdaptedTemplates != NULL) { 00456 free_adapted_templates(AdaptedTemplates); 00457 AdaptedTemplates = NULL; 00458 } 00459 00460 if (PreTrainedTemplates != NULL) { 00461 free_int_templates(PreTrainedTemplates); 00462 PreTrainedTemplates = NULL; 00463 } 00464 getDict().EndDangerousAmbigs(); 00465 FreeNormProtos(); 00466 if (AllProtosOn != NULL) { 00467 FreeBitVector(AllProtosOn); 00468 FreeBitVector(AllConfigsOn); 00469 FreeBitVector(AllConfigsOff); 00470 FreeBitVector(TempProtoMask); 00471 AllProtosOn = NULL; 00472 AllConfigsOn = NULL; 00473 AllConfigsOff = NULL; 00474 TempProtoMask = NULL; 00475 } 00476 delete shape_table_; 00477 shape_table_ = NULL; 00478 if (static_classifier_ != NULL) { 00479 delete static_classifier_; 00480 static_classifier_ = NULL; 00481 } 00482 } /* EndAdaptiveClassifier */ 00483 00484 00485 /*---------------------------------------------------------------------------*/ 00503 void Classify::InitAdaptiveClassifier(bool load_pre_trained_templates) { 00504 if (!classify_enable_adaptive_matcher) 00505 return; 00506 if (AllProtosOn != NULL) 00507 EndAdaptiveClassifier(); // Don't leak with multiple inits. 00508 00509 // If there is no language_data_path_prefix, the classifier will be 00510 // adaptive only. 00511 if (language_data_path_prefix.length() > 0 && 00512 load_pre_trained_templates) { 00513 ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_INTTEMP)); 00514 PreTrainedTemplates = 00515 ReadIntTemplates(tessdata_manager.GetDataFilePtr()); 00516 if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded inttemp\n"); 00517 00518 if (tessdata_manager.SeekToStart(TESSDATA_SHAPE_TABLE)) { 00519 shape_table_ = new ShapeTable(unicharset); 00520 if (!shape_table_->DeSerialize(tessdata_manager.swap(), 00521 tessdata_manager.GetDataFilePtr())) { 00522 tprintf("Error loading shape table!\n"); 00523 delete shape_table_; 00524 shape_table_ = NULL; 00525 } else if (tessdata_manager.DebugLevel() > 0) { 00526 tprintf("Successfully loaded shape table!\n"); 00527 } 00528 } 00529 00530 ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_PFFMTABLE)); 00531 ReadNewCutoffs(tessdata_manager.GetDataFilePtr(), 00532 tessdata_manager.swap(), 00533 tessdata_manager.GetEndOffset(TESSDATA_PFFMTABLE), 00534 CharNormCutoffs); 00535 if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded pffmtable\n"); 00536 00537 ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_NORMPROTO)); 00538 NormProtos = 00539 ReadNormProtos(tessdata_manager.GetDataFilePtr(), 00540 tessdata_manager.GetEndOffset(TESSDATA_NORMPROTO)); 00541 if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded normproto\n"); 00542 static_classifier_ = new TessClassifier(false, this); 00543 } 00544 00545 im_.Init(&classify_debug_level); 00546 InitIntegerFX(); 00547 00548 AllProtosOn = NewBitVector(MAX_NUM_PROTOS); 00549 AllConfigsOn = NewBitVector(MAX_NUM_CONFIGS); 00550 AllConfigsOff = NewBitVector(MAX_NUM_CONFIGS); 00551 TempProtoMask = NewBitVector(MAX_NUM_PROTOS); 00552 set_all_bits(AllProtosOn, WordsInVectorOfSize(MAX_NUM_PROTOS)); 00553 set_all_bits(AllConfigsOn, WordsInVectorOfSize(MAX_NUM_CONFIGS)); 00554 zero_all_bits(AllConfigsOff, WordsInVectorOfSize(MAX_NUM_CONFIGS)); 00555 00556 for (int i = 0; i < MAX_NUM_CLASSES; i++) { 00557 BaselineCutoffs[i] = 0; 00558 } 00559 00560 if (classify_use_pre_adapted_templates) { 00561 FILE *File; 00562 STRING Filename; 00563 00564 Filename = imagefile; 00565 Filename += ADAPT_TEMPLATE_SUFFIX; 00566 File = fopen(Filename.string(), "rb"); 00567 if (File == NULL) { 00568 AdaptedTemplates = NewAdaptedTemplates(true); 00569 } else { 00570 #ifndef SECURE_NAMES 00571 cprintf("\nReading pre-adapted templates from %s ...\n", 00572 Filename.string()); 00573 fflush(stdout); 00574 #endif 00575 AdaptedTemplates = ReadAdaptedTemplates(File); 00576 cprintf("\n"); 00577 fclose(File); 00578 PrintAdaptedTemplates(stdout, AdaptedTemplates); 00579 00580 for (int i = 0; i < AdaptedTemplates->Templates->NumClasses; i++) { 00581 BaselineCutoffs[i] = CharNormCutoffs[i]; 00582 } 00583 } 00584 } else { 00585 if (AdaptedTemplates != NULL) 00586 free_adapted_templates(AdaptedTemplates); 00587 AdaptedTemplates = NewAdaptedTemplates(true); 00588 } 00589 } /* InitAdaptiveClassifier */ 00590 00591 void Classify::ResetAdaptiveClassifierInternal() { 00592 if (classify_learning_debug_level > 0) { 00593 tprintf("Resetting adaptive classifier (NumAdaptationsFailed=%d)\n", 00594 NumAdaptationsFailed); 00595 } 00596 free_adapted_templates(AdaptedTemplates); 00597 AdaptedTemplates = NewAdaptedTemplates(true); 00598 NumAdaptationsFailed = 0; 00599 } 00600 00601 00602 00603 /*---------------------------------------------------------------------------*/ 00623 void Classify::SettupPass1() { 00624 EnableLearning = classify_enable_learning; 00625 00626 getDict().SettupStopperPass1(); 00627 00628 } /* SettupPass1 */ 00629 00630 00631 /*---------------------------------------------------------------------------*/ 00643 void Classify::SettupPass2() { 00644 EnableLearning = FALSE; 00645 getDict().SettupStopperPass2(); 00646 00647 } /* SettupPass2 */ 00648 00649 00650 /*---------------------------------------------------------------------------*/ 00670 void Classify::InitAdaptedClass(TBLOB *Blob, 00671 CLASS_ID ClassId, 00672 int FontinfoId, 00673 ADAPT_CLASS Class, 00674 ADAPT_TEMPLATES Templates) { 00675 FEATURE_SET Features; 00676 int Fid, Pid; 00677 FEATURE Feature; 00678 int NumFeatures; 00679 TEMP_PROTO TempProto; 00680 PROTO Proto; 00681 INT_CLASS IClass; 00682 TEMP_CONFIG Config; 00683 00684 classify_norm_method.set_value(baseline); 00685 Features = ExtractOutlineFeatures(Blob); 00686 NumFeatures = Features->NumFeatures; 00687 if (NumFeatures > UNLIKELY_NUM_FEAT || NumFeatures <= 0) { 00688 FreeFeatureSet(Features); 00689 return; 00690 } 00691 00692 Config = NewTempConfig(NumFeatures - 1, FontinfoId); 00693 TempConfigFor(Class, 0) = Config; 00694 00695 /* this is a kludge to construct cutoffs for adapted templates */ 00696 if (Templates == AdaptedTemplates) 00697 BaselineCutoffs[ClassId] = CharNormCutoffs[ClassId]; 00698 00699 IClass = ClassForClassId (Templates->Templates, ClassId); 00700 00701 for (Fid = 0; Fid < Features->NumFeatures; Fid++) { 00702 Pid = AddIntProto (IClass); 00703 assert (Pid != NO_PROTO); 00704 00705 Feature = Features->Features[Fid]; 00706 TempProto = NewTempProto (); 00707 Proto = &(TempProto->Proto); 00708 00709 /* compute proto params - NOTE that Y_DIM_OFFSET must be used because 00710 ConvertProto assumes that the Y dimension varies from -0.5 to 0.5 00711 instead of the -0.25 to 0.75 used in baseline normalization */ 00712 Proto->Angle = Feature->Params[OutlineFeatDir]; 00713 Proto->X = Feature->Params[OutlineFeatX]; 00714 Proto->Y = Feature->Params[OutlineFeatY] - Y_DIM_OFFSET; 00715 Proto->Length = Feature->Params[OutlineFeatLength]; 00716 FillABC(Proto); 00717 00718 TempProto->ProtoId = Pid; 00719 SET_BIT (Config->Protos, Pid); 00720 00721 ConvertProto(Proto, Pid, IClass); 00722 AddProtoToProtoPruner(Proto, Pid, IClass, 00723 classify_learning_debug_level >= 2); 00724 00725 Class->TempProtos = push (Class->TempProtos, TempProto); 00726 } 00727 FreeFeatureSet(Features); 00728 00729 AddIntConfig(IClass); 00730 ConvertConfig (AllProtosOn, 0, IClass); 00731 00732 if (classify_learning_debug_level >= 1) { 00733 cprintf ("Added new class '%s' with class id %d and %d protos.\n", 00734 unicharset.id_to_unichar(ClassId), ClassId, NumFeatures); 00735 if (classify_learning_debug_level > 1) 00736 DisplayAdaptedChar(Blob, IClass); 00737 } 00738 00739 if (IsEmptyAdaptedClass(Class)) 00740 (Templates->NumNonEmptyClasses)++; 00741 } /* InitAdaptedClass */ 00742 00743 00744 /*---------------------------------------------------------------------------*/ 00765 int Classify::GetAdaptiveFeatures(TBLOB *Blob, 00766 INT_FEATURE_ARRAY IntFeatures, 00767 FEATURE_SET *FloatFeatures) { 00768 FEATURE_SET Features; 00769 int NumFeatures; 00770 00771 classify_norm_method.set_value(baseline); 00772 Features = ExtractPicoFeatures(Blob); 00773 00774 NumFeatures = Features->NumFeatures; 00775 if (NumFeatures > UNLIKELY_NUM_FEAT) { 00776 FreeFeatureSet(Features); 00777 return 0; 00778 } 00779 00780 ComputeIntFeatures(Features, IntFeatures); 00781 *FloatFeatures = Features; 00782 00783 return NumFeatures; 00784 } /* GetAdaptiveFeatures */ 00785 00786 00787 /*----------------------------------------------------------------------------- 00788 Private Code 00789 -----------------------------------------------------------------------------*/ 00790 /*---------------------------------------------------------------------------*/ 00804 bool Classify::AdaptableWord(WERD_RES* word) { 00805 if (word->best_choice == NULL) return false; 00806 int BestChoiceLength = word->best_choice->length(); 00807 float adaptable_score = 00808 getDict().segment_penalty_dict_case_ok + ADAPTABLE_WERD_ADJUSTMENT; 00809 return // rules that apply in general - simplest to compute first 00810 BestChoiceLength > 0 && 00811 BestChoiceLength == word->rebuild_word->NumBlobs() && 00812 BestChoiceLength <= MAX_ADAPTABLE_WERD_SIZE && 00813 // This basically ensures that the word is at least a dictionary match 00814 // (freq word, user word, system dawg word, etc). 00815 // Since all the other adjustments will make adjust factor higher 00816 // than higher than adaptable_score=1.1+0.05=1.15 00817 // Since these are other flags that ensure that the word is dict word, 00818 // this check could be at times redundant. 00819 word->best_choice->adjust_factor() <= adaptable_score && 00820 // Make sure that alternative choices are not dictionary words. 00821 word->AlternativeChoiceAdjustmentsWorseThan(adaptable_score); 00822 } 00823 00824 /*---------------------------------------------------------------------------*/ 00840 void Classify::AdaptToChar(TBLOB *Blob, 00841 CLASS_ID ClassId, 00842 int FontinfoId, 00843 FLOAT32 Threshold) { 00844 int NumFeatures; 00845 INT_FEATURE_ARRAY IntFeatures; 00846 INT_RESULT_STRUCT IntResult; 00847 INT_CLASS IClass; 00848 ADAPT_CLASS Class; 00849 TEMP_CONFIG TempConfig; 00850 FEATURE_SET FloatFeatures; 00851 int NewTempConfigId; 00852 00853 if (!LegalClassId (ClassId)) 00854 return; 00855 00856 Class = AdaptedTemplates->Class[ClassId]; 00857 assert(Class != NULL); 00858 if (IsEmptyAdaptedClass(Class)) { 00859 InitAdaptedClass(Blob, ClassId, FontinfoId, Class, AdaptedTemplates); 00860 } 00861 else { 00862 IClass = ClassForClassId (AdaptedTemplates->Templates, ClassId); 00863 00864 NumFeatures = GetAdaptiveFeatures(Blob, IntFeatures, &FloatFeatures); 00865 if (NumFeatures <= 0) 00866 return; 00867 00868 // Only match configs with the matching font. 00869 BIT_VECTOR MatchingFontConfigs = NewBitVector(MAX_NUM_PROTOS); 00870 for (int cfg = 0; cfg < IClass->NumConfigs; ++cfg) { 00871 if (GetFontinfoId(Class, cfg) == FontinfoId) { 00872 SET_BIT(MatchingFontConfigs, cfg); 00873 } else { 00874 reset_bit(MatchingFontConfigs, cfg); 00875 } 00876 } 00877 im_.Match(IClass, AllProtosOn, MatchingFontConfigs, 00878 NumFeatures, IntFeatures, 00879 &IntResult, classify_adapt_feature_threshold, 00880 NO_DEBUG, matcher_debug_separate_windows); 00881 FreeBitVector(MatchingFontConfigs); 00882 00883 SetAdaptiveThreshold(Threshold); 00884 00885 if (IntResult.Rating <= Threshold) { 00886 if (ConfigIsPermanent (Class, IntResult.Config)) { 00887 if (classify_learning_debug_level >= 1) 00888 cprintf ("Found good match to perm config %d = %4.1f%%.\n", 00889 IntResult.Config, (1.0 - IntResult.Rating) * 100.0); 00890 FreeFeatureSet(FloatFeatures); 00891 return; 00892 } 00893 00894 TempConfig = TempConfigFor (Class, IntResult.Config); 00895 IncreaseConfidence(TempConfig); 00896 if (TempConfig->NumTimesSeen > Class->MaxNumTimesSeen) { 00897 Class->MaxNumTimesSeen = TempConfig->NumTimesSeen; 00898 } 00899 if (classify_learning_debug_level >= 1) 00900 cprintf ("Increasing reliability of temp config %d to %d.\n", 00901 IntResult.Config, TempConfig->NumTimesSeen); 00902 00903 if (TempConfigReliable(ClassId, TempConfig)) { 00904 MakePermanent(AdaptedTemplates, ClassId, IntResult.Config, Blob); 00905 UpdateAmbigsGroup(ClassId, Blob); 00906 } 00907 } 00908 else { 00909 if (classify_learning_debug_level >= 1) { 00910 cprintf ("Found poor match to temp config %d = %4.1f%%.\n", 00911 IntResult.Config, (1.0 - IntResult.Rating) * 100.0); 00912 if (classify_learning_debug_level > 2) 00913 DisplayAdaptedChar(Blob, IClass); 00914 } 00915 NewTempConfigId = MakeNewTemporaryConfig(AdaptedTemplates, 00916 ClassId, 00917 FontinfoId, 00918 NumFeatures, 00919 IntFeatures, 00920 FloatFeatures); 00921 if (NewTempConfigId >= 0 && 00922 TempConfigReliable(ClassId, TempConfigFor(Class, NewTempConfigId))) { 00923 MakePermanent(AdaptedTemplates, ClassId, NewTempConfigId, Blob); 00924 UpdateAmbigsGroup(ClassId, Blob); 00925 } 00926 00927 #ifndef GRAPHICS_DISABLED 00928 if (classify_learning_debug_level > 1) { 00929 DisplayAdaptedChar(Blob, IClass); 00930 } 00931 #endif 00932 } 00933 FreeFeatureSet(FloatFeatures); 00934 } 00935 } /* AdaptToChar */ 00936 00937 void Classify::DisplayAdaptedChar(TBLOB* blob, INT_CLASS_STRUCT* int_class) { 00938 #ifndef GRAPHICS_DISABLED 00939 INT_FX_RESULT_STRUCT fx_info; 00940 GenericVector<INT_FEATURE_STRUCT> bl_features; 00941 TrainingSample* sample = 00942 BlobToTrainingSample(*blob, classify_nonlinear_norm, &fx_info, 00943 &bl_features); 00944 if (sample == NULL) return; 00945 00946 INT_RESULT_STRUCT IntResult; 00947 im_.Match(int_class, AllProtosOn, AllConfigsOn, 00948 bl_features.size(), &bl_features[0], 00949 &IntResult, classify_adapt_feature_threshold, 00950 NO_DEBUG, matcher_debug_separate_windows); 00951 cprintf ("Best match to temp config %d = %4.1f%%.\n", 00952 IntResult.Config, (1.0 - IntResult.Rating) * 100.0); 00953 if (classify_learning_debug_level >= 2) { 00954 uinT32 ConfigMask; 00955 ConfigMask = 1 << IntResult.Config; 00956 ShowMatchDisplay(); 00957 im_.Match(int_class, AllProtosOn, (BIT_VECTOR)&ConfigMask, 00958 bl_features.size(), &bl_features[0], 00959 &IntResult, classify_adapt_feature_threshold, 00960 6 | 0x19, matcher_debug_separate_windows); 00961 UpdateMatchDisplay(); 00962 } 00963 #endif 00964 } 00965 00966 00967 00968 /*---------------------------------------------------------------------------*/ 00995 void Classify::AddNewResult(ADAPT_RESULTS *results, 00996 CLASS_ID class_id, 00997 int shape_id, 00998 FLOAT32 rating, 00999 bool adapted, 01000 int config, 01001 int fontinfo_id, 01002 int fontinfo_id2) { 01003 ScoredClass *old_match = FindScoredUnichar(results, class_id); 01004 ScoredClass match = 01005 { class_id, 01006 shape_id, 01007 rating, 01008 adapted, 01009 static_cast<inT16>(config), 01010 static_cast<inT16>(fontinfo_id), 01011 static_cast<inT16>(fontinfo_id2) }; 01012 01013 if (rating > results->best_match.rating + matcher_bad_match_pad || 01014 (old_match && rating >= old_match->rating)) 01015 return; 01016 01017 if (!unicharset.get_fragment(class_id)) 01018 results->HasNonfragment = true; 01019 01020 if (old_match) 01021 old_match->rating = rating; 01022 else 01023 results->match.push_back(match); 01024 01025 if (rating < results->best_match.rating && 01026 // Ensure that fragments do not affect best rating, class and config. 01027 // This is needed so that at least one non-fragmented character is 01028 // always present in the results. 01029 // TODO(daria): verify that this helps accuracy and does not 01030 // hurt performance. 01031 !unicharset.get_fragment(class_id)) { 01032 results->best_match = match; 01033 } 01034 } /* AddNewResult */ 01035 01036 01037 /*---------------------------------------------------------------------------*/ 01057 void Classify::AmbigClassifier( 01058 const GenericVector<INT_FEATURE_STRUCT>& int_features, 01059 const INT_FX_RESULT_STRUCT& fx_info, 01060 const TBLOB *blob, 01061 INT_TEMPLATES templates, 01062 ADAPT_CLASS *classes, 01063 UNICHAR_ID *ambiguities, 01064 ADAPT_RESULTS *results) { 01065 if (int_features.empty()) return; 01066 uinT8* CharNormArray = new uinT8[unicharset.size()]; 01067 INT_RESULT_STRUCT IntResult; 01068 01069 results->BlobLength = GetCharNormFeature(fx_info, templates, NULL, 01070 CharNormArray); 01071 bool debug = matcher_debug_level >= 2 || classify_debug_level > 1; 01072 if (debug) 01073 tprintf("AM Matches = "); 01074 01075 int top = blob->bounding_box().top(); 01076 int bottom = blob->bounding_box().bottom(); 01077 while (*ambiguities >= 0) { 01078 CLASS_ID class_id = *ambiguities; 01079 01080 im_.Match(ClassForClassId(templates, class_id), 01081 AllProtosOn, AllConfigsOn, 01082 int_features.size(), &int_features[0], 01083 &IntResult, 01084 classify_adapt_feature_threshold, NO_DEBUG, 01085 matcher_debug_separate_windows); 01086 01087 ExpandShapesAndApplyCorrections(NULL, debug, class_id, bottom, top, 0, 01088 results->BlobLength, 01089 classify_integer_matcher_multiplier, 01090 CharNormArray, IntResult, results); 01091 ambiguities++; 01092 } 01093 delete [] CharNormArray; 01094 } /* AmbigClassifier */ 01095 01096 /*---------------------------------------------------------------------------*/ 01099 void Classify::MasterMatcher(INT_TEMPLATES templates, 01100 inT16 num_features, 01101 const INT_FEATURE_STRUCT* features, 01102 const uinT8* norm_factors, 01103 ADAPT_CLASS* classes, 01104 int debug, 01105 int matcher_multiplier, 01106 const TBOX& blob_box, 01107 const GenericVector<CP_RESULT_STRUCT>& results, 01108 ADAPT_RESULTS* final_results) { 01109 int top = blob_box.top(); 01110 int bottom = blob_box.bottom(); 01111 for (int c = 0; c < results.size(); c++) { 01112 CLASS_ID class_id = results[c].Class; 01113 INT_RESULT_STRUCT& int_result = results[c].IMResult; 01114 BIT_VECTOR protos = classes != NULL ? classes[class_id]->PermProtos 01115 : AllProtosOn; 01116 BIT_VECTOR configs = classes != NULL ? classes[class_id]->PermConfigs 01117 : AllConfigsOn; 01118 01119 im_.Match(ClassForClassId(templates, class_id), 01120 protos, configs, 01121 num_features, features, 01122 &int_result, classify_adapt_feature_threshold, debug, 01123 matcher_debug_separate_windows); 01124 bool debug = matcher_debug_level >= 2 || classify_debug_level > 1; 01125 ExpandShapesAndApplyCorrections(classes, debug, class_id, bottom, top, 01126 results[c].Rating, 01127 final_results->BlobLength, 01128 matcher_multiplier, norm_factors, 01129 int_result, final_results); 01130 } 01131 } 01132 01133 // Converts configs to fonts, and if the result is not adapted, and a 01134 // shape_table_ is present, the shape is expanded to include all 01135 // unichar_ids represented, before applying a set of corrections to the 01136 // distance rating in int_result, (see ComputeCorrectedRating.) 01137 // The results are added to the final_results output. 01138 void Classify::ExpandShapesAndApplyCorrections( 01139 ADAPT_CLASS* classes, bool debug, int class_id, int bottom, int top, 01140 float cp_rating, int blob_length, int matcher_multiplier, 01141 const uinT8* cn_factors, 01142 INT_RESULT_STRUCT& int_result, ADAPT_RESULTS* final_results) { 01143 // Compute the fontinfo_ids. 01144 int fontinfo_id = kBlankFontinfoId; 01145 int fontinfo_id2 = kBlankFontinfoId; 01146 if (classes != NULL) { 01147 // Adapted result. 01148 fontinfo_id = GetFontinfoId(classes[class_id], int_result.Config); 01149 if (int_result.Config2 >= 0) 01150 fontinfo_id2 = GetFontinfoId(classes[class_id], int_result.Config2); 01151 } else { 01152 // Pre-trained result. 01153 fontinfo_id = ClassAndConfigIDToFontOrShapeID(class_id, int_result.Config); 01154 if (int_result.Config2 >= 0) { 01155 fontinfo_id2 = ClassAndConfigIDToFontOrShapeID(class_id, 01156 int_result.Config2); 01157 } 01158 if (shape_table_ != NULL) { 01159 // Actually fontinfo_id is an index into the shape_table_ and it 01160 // contains a list of unchar_id/font_id pairs. 01161 int shape_id = fontinfo_id; 01162 const Shape& shape = shape_table_->GetShape(fontinfo_id); 01163 double min_rating = 0.0; 01164 for (int c = 0; c < shape.size(); ++c) { 01165 int unichar_id = shape[c].unichar_id; 01166 fontinfo_id = shape[c].font_ids[0]; 01167 if (shape[c].font_ids.size() > 1) 01168 fontinfo_id2 = shape[c].font_ids[1]; 01169 else if (fontinfo_id2 != kBlankFontinfoId) 01170 fontinfo_id2 = shape_table_->GetShape(fontinfo_id2)[0].font_ids[0]; 01171 double rating = ComputeCorrectedRating(debug, unichar_id, cp_rating, 01172 int_result.Rating, 01173 int_result.FeatureMisses, 01174 bottom, top, blob_length, 01175 matcher_multiplier, cn_factors); 01176 if (c == 0 || rating < min_rating) 01177 min_rating = rating; 01178 if (unicharset.get_enabled(unichar_id)) { 01179 AddNewResult(final_results, unichar_id, shape_id, rating, 01180 classes != NULL, int_result.Config, 01181 fontinfo_id, fontinfo_id2); 01182 } 01183 } 01184 int_result.Rating = min_rating; 01185 return; 01186 } 01187 } 01188 double rating = ComputeCorrectedRating(debug, class_id, cp_rating, 01189 int_result.Rating, 01190 int_result.FeatureMisses, 01191 bottom, top, blob_length, 01192 matcher_multiplier, cn_factors); 01193 if (unicharset.get_enabled(class_id)) { 01194 AddNewResult(final_results, class_id, -1, rating, 01195 classes != NULL, int_result.Config, 01196 fontinfo_id, fontinfo_id2); 01197 } 01198 int_result.Rating = rating; 01199 } 01200 01201 // Applies a set of corrections to the distance im_rating, 01202 // including the cn_correction, miss penalty and additional penalty 01203 // for non-alnums being vertical misfits. Returns the corrected distance. 01204 double Classify::ComputeCorrectedRating(bool debug, int unichar_id, 01205 double cp_rating, double im_rating, 01206 int feature_misses, 01207 int bottom, int top, 01208 int blob_length, int matcher_multiplier, 01209 const uinT8* cn_factors) { 01210 // Compute class feature corrections. 01211 double cn_corrected = im_.ApplyCNCorrection(im_rating, blob_length, 01212 cn_factors[unichar_id], 01213 matcher_multiplier); 01214 double miss_penalty = tessedit_class_miss_scale * feature_misses; 01215 double vertical_penalty = 0.0; 01216 // Penalize non-alnums for being vertical misfits. 01217 if (!unicharset.get_isalpha(unichar_id) && 01218 !unicharset.get_isdigit(unichar_id) && 01219 cn_factors[unichar_id] != 0 && classify_misfit_junk_penalty > 0.0) { 01220 int min_bottom, max_bottom, min_top, max_top; 01221 unicharset.get_top_bottom(unichar_id, &min_bottom, &max_bottom, 01222 &min_top, &max_top); 01223 if (debug) { 01224 tprintf("top=%d, vs [%d, %d], bottom=%d, vs [%d, %d]\n", 01225 top, min_top, max_top, bottom, min_bottom, max_bottom); 01226 } 01227 if (top < min_top || top > max_top || 01228 bottom < min_bottom || bottom > max_bottom) { 01229 vertical_penalty = classify_misfit_junk_penalty; 01230 } 01231 } 01232 double result =cn_corrected + miss_penalty + vertical_penalty; 01233 if (result > WORST_POSSIBLE_RATING) 01234 result = WORST_POSSIBLE_RATING; 01235 if (debug) { 01236 tprintf("%s: %2.1f(CP%2.1f, IM%2.1f + CN%.2f(%d) + MP%2.1f + VP%2.1f)\n", 01237 unicharset.id_to_unichar(unichar_id), 01238 result * 100.0, 01239 cp_rating * 100.0, 01240 im_rating * 100.0, 01241 (cn_corrected - im_rating) * 100.0, 01242 cn_factors[unichar_id], 01243 miss_penalty * 100.0, 01244 vertical_penalty * 100.0); 01245 } 01246 return result; 01247 } 01248 01249 /*---------------------------------------------------------------------------*/ 01267 UNICHAR_ID *Classify::BaselineClassifier( 01268 TBLOB *Blob, const GenericVector<INT_FEATURE_STRUCT>& int_features, 01269 const INT_FX_RESULT_STRUCT& fx_info, 01270 ADAPT_TEMPLATES Templates, ADAPT_RESULTS *Results) { 01271 if (int_features.empty()) return NULL; 01272 uinT8* CharNormArray = new uinT8[unicharset.size()]; 01273 ClearCharNormArray(CharNormArray); 01274 01275 Results->BlobLength = IntCastRounded(fx_info.Length / kStandardFeatureLength); 01276 PruneClasses(Templates->Templates, int_features.size(), &int_features[0], 01277 CharNormArray, BaselineCutoffs, &Results->CPResults); 01278 01279 if (matcher_debug_level >= 2 || classify_debug_level > 1) 01280 cprintf ("BL Matches = "); 01281 01282 MasterMatcher(Templates->Templates, int_features.size(), &int_features[0], 01283 CharNormArray, 01284 Templates->Class, matcher_debug_flags, 0, 01285 Blob->bounding_box(), Results->CPResults, Results); 01286 01287 delete [] CharNormArray; 01288 CLASS_ID ClassId = Results->best_match.unichar_id; 01289 if (ClassId == NO_CLASS) 01290 return (NULL); 01291 /* this is a bug - maybe should return "" */ 01292 01293 return Templates->Class[ClassId]-> 01294 Config[Results->best_match.config].Perm->Ambigs; 01295 } /* BaselineClassifier */ 01296 01297 01298 /*---------------------------------------------------------------------------*/ 01317 int Classify::CharNormClassifier(TBLOB *blob, 01318 const TrainingSample& sample, 01319 ADAPT_RESULTS *adapt_results) { 01320 // This is the length that is used for scaling ratings vs certainty. 01321 adapt_results->BlobLength = 01322 IntCastRounded(sample.outline_length() / kStandardFeatureLength); 01323 GenericVector<UnicharRating> unichar_results; 01324 static_classifier_->UnicharClassifySample(sample, blob->denorm().pix(), 0, 01325 -1, &unichar_results); 01326 // Convert results to the format used internally by AdaptiveClassifier. 01327 for (int r = 0; r < unichar_results.size(); ++r) { 01328 int unichar_id = unichar_results[r].unichar_id; 01329 // Fonts are listed in order of preference. 01330 int font1 = unichar_results[r].fonts.size() >= 1 01331 ? unichar_results[r].fonts[0] : kBlankFontinfoId; 01332 int font2 = unichar_results[r].fonts.size() >= 2 01333 ? unichar_results[r].fonts[1] : kBlankFontinfoId; 01334 float rating = 1.0f - unichar_results[r].rating; 01335 AddNewResult(adapt_results, unichar_id, -1, rating, false, 0, font1, font2); 01336 } 01337 return sample.num_features(); 01338 } /* CharNormClassifier */ 01339 01340 // As CharNormClassifier, but operates on a TrainingSample and outputs to 01341 // a GenericVector of ShapeRating without conversion to classes. 01342 int Classify::CharNormTrainingSample(bool pruner_only, 01343 int keep_this, 01344 const TrainingSample& sample, 01345 GenericVector<UnicharRating>* results) { 01346 results->clear(); 01347 ADAPT_RESULTS* adapt_results = new ADAPT_RESULTS(); 01348 adapt_results->Initialize(); 01349 // Compute the bounding box of the features. 01350 int num_features = sample.num_features(); 01351 // Only the top and bottom of the blob_box are used by MasterMatcher, so 01352 // fabricate right and left using top and bottom. 01353 TBOX blob_box(sample.geo_feature(GeoBottom), sample.geo_feature(GeoBottom), 01354 sample.geo_feature(GeoTop), sample.geo_feature(GeoTop)); 01355 // Compute the char_norm_array from the saved cn_feature. 01356 FEATURE norm_feature = sample.GetCNFeature(); 01357 uinT8* char_norm_array = new uinT8[unicharset.size()]; 01358 int num_pruner_classes = MAX(unicharset.size(), 01359 PreTrainedTemplates->NumClasses); 01360 uinT8* pruner_norm_array = new uinT8[num_pruner_classes]; 01361 adapt_results->BlobLength = 01362 static_cast<int>(ActualOutlineLength(norm_feature) * 20 + 0.5); 01363 ComputeCharNormArrays(norm_feature, PreTrainedTemplates, char_norm_array, 01364 pruner_norm_array); 01365 01366 PruneClasses(PreTrainedTemplates, num_features, sample.features(), 01367 pruner_norm_array, 01368 shape_table_ != NULL ? &shapetable_cutoffs_[0] : CharNormCutoffs, 01369 &adapt_results->CPResults); 01370 delete [] pruner_norm_array; 01371 if (keep_this >= 0) { 01372 adapt_results->CPResults[0].Class = keep_this; 01373 adapt_results->CPResults.truncate(1); 01374 } 01375 if (pruner_only) { 01376 // Convert pruner results to output format. 01377 for (int i = 0; i < adapt_results->CPResults.size(); ++i) { 01378 int class_id = adapt_results->CPResults[i].Class; 01379 results->push_back( 01380 UnicharRating(class_id, 1.0f - adapt_results->CPResults[i].Rating)); 01381 } 01382 } else { 01383 MasterMatcher(PreTrainedTemplates, num_features, sample.features(), 01384 char_norm_array, 01385 NULL, matcher_debug_flags, 01386 classify_integer_matcher_multiplier, 01387 blob_box, adapt_results->CPResults, adapt_results); 01388 // Convert master matcher results to output format. 01389 for (int i = 0; i < adapt_results->match.size(); i++) { 01390 ScoredClass next = adapt_results->match[i]; 01391 UnicharRating rating(next.unichar_id, 1.0f - next.rating); 01392 if (next.fontinfo_id >= 0) { 01393 rating.fonts.push_back(next.fontinfo_id); 01394 if (next.fontinfo_id2 >= 0) 01395 rating.fonts.push_back(next.fontinfo_id2); 01396 } 01397 results->push_back(rating); 01398 } 01399 results->sort(&UnicharRating::SortDescendingRating); 01400 } 01401 delete [] char_norm_array; 01402 delete adapt_results; 01403 return num_features; 01404 } /* CharNormTrainingSample */ 01405 01406 01407 /*---------------------------------------------------------------------------*/ 01422 void Classify::ClassifyAsNoise(ADAPT_RESULTS *Results) { 01423 register FLOAT32 Rating; 01424 01425 Rating = Results->BlobLength / matcher_avg_noise_size; 01426 Rating *= Rating; 01427 Rating /= 1.0 + Rating; 01428 01429 AddNewResult(Results, NO_CLASS, -1, Rating, false, -1, 01430 kBlankFontinfoId, kBlankFontinfoId); 01431 } /* ClassifyAsNoise */ 01432 } // namespace tesseract 01433 01434 01435 /*---------------------------------------------------------------------------*/ 01436 // Return a pointer to the scored unichar in results, or NULL if not present. 01437 ScoredClass *FindScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id) { 01438 for (int i = 0; i < results->match.size(); i++) { 01439 if (results->match[i].unichar_id == id) 01440 return &results->match[i]; 01441 } 01442 return NULL; 01443 } 01444 01445 // Retrieve the current rating for a unichar id if we have rated it, defaulting 01446 // to WORST_POSSIBLE_RATING. 01447 ScoredClass ScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id) { 01448 ScoredClass poor_result = 01449 {id, -1, WORST_POSSIBLE_RATING, false, -1, 01450 kBlankFontinfoId, kBlankFontinfoId}; 01451 ScoredClass *entry = FindScoredUnichar(results, id); 01452 return (entry == NULL) ? poor_result : *entry; 01453 } 01454 01455 // Compare character classes by rating as for qsort(3). 01456 // For repeatability, use character class id as a tie-breaker. 01457 int CompareByRating(const void *arg1, // ScoredClass *class1 01458 const void *arg2) { // ScoredClass *class2 01459 const ScoredClass *class1 = (const ScoredClass *)arg1; 01460 const ScoredClass *class2 = (const ScoredClass *)arg2; 01461 01462 if (class1->rating < class2->rating) 01463 return -1; 01464 else if (class1->rating > class2->rating) 01465 return 1; 01466 01467 if (class1->unichar_id < class2->unichar_id) 01468 return -1; 01469 else if (class1->unichar_id > class2->unichar_id) 01470 return 1; 01471 return 0; 01472 } 01473 01474 /*---------------------------------------------------------------------------*/ 01475 namespace tesseract { 01482 void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box, 01483 ADAPT_RESULTS *Results, 01484 BLOB_CHOICE_LIST *Choices) { 01485 assert(Choices != NULL); 01486 FLOAT32 Rating; 01487 FLOAT32 Certainty; 01488 BLOB_CHOICE_IT temp_it; 01489 bool contains_nonfrag = false; 01490 temp_it.set_to_list(Choices); 01491 int choices_length = 0; 01492 // With no shape_table_ maintain the previous MAX_MATCHES as the maximum 01493 // number of returned results, but with a shape_table_ we want to have room 01494 // for at least the biggest shape (which might contain hundreds of Indic 01495 // grapheme fragments) and more, so use double the size of the biggest shape 01496 // if that is more than the default. 01497 int max_matches = MAX_MATCHES; 01498 if (shape_table_ != NULL) { 01499 max_matches = shape_table_->MaxNumUnichars() * 2; 01500 if (max_matches < MAX_MATCHES) 01501 max_matches = MAX_MATCHES; 01502 } 01503 01504 float best_certainty = -MAX_FLOAT32; 01505 for (int i = 0; i < Results->match.size(); i++) { 01506 ScoredClass next = Results->match[i]; 01507 int fontinfo_id = next.fontinfo_id; 01508 int fontinfo_id2 = next.fontinfo_id2; 01509 bool adapted = next.adapted; 01510 bool current_is_frag = (unicharset.get_fragment(next.unichar_id) != NULL); 01511 if (temp_it.length()+1 == max_matches && 01512 !contains_nonfrag && current_is_frag) { 01513 continue; // look for a non-fragmented character to fill the 01514 // last spot in Choices if only fragments are present 01515 } 01516 // BlobLength can never be legally 0, this means recognition failed. 01517 // But we must return a classification result because some invoking 01518 // functions (chopper/permuter) do not anticipate a null blob choice. 01519 // So we need to assign a poor, but not infinitely bad score. 01520 if (Results->BlobLength == 0) { 01521 Certainty = -20; 01522 Rating = 100; // should be -certainty * real_blob_length 01523 } else { 01524 Rating = Certainty = next.rating; 01525 Rating *= rating_scale * Results->BlobLength; 01526 Certainty *= -(getDict().certainty_scale); 01527 } 01528 // Adapted results, by their very nature, should have good certainty. 01529 // Those that don't are at best misleading, and often lead to errors, 01530 // so don't accept adapted results that are too far behind the best result, 01531 // whether adapted or static. 01532 // TODO(rays) find some way of automatically tuning these constants. 01533 if (Certainty > best_certainty) { 01534 best_certainty = MIN(Certainty, classify_adapted_pruning_threshold); 01535 } else if (adapted && 01536 Certainty / classify_adapted_pruning_factor < best_certainty) { 01537 continue; // Don't accept bad adapted results. 01538 } 01539 01540 float min_xheight, max_xheight, yshift; 01541 denorm.XHeightRange(next.unichar_id, unicharset, box, 01542 &min_xheight, &max_xheight, &yshift); 01543 temp_it.add_to_end(new BLOB_CHOICE(next.unichar_id, Rating, Certainty, 01544 fontinfo_id, fontinfo_id2, 01545 unicharset.get_script(next.unichar_id), 01546 min_xheight, max_xheight, yshift, 01547 adapted ? BCC_ADAPTED_CLASSIFIER 01548 : BCC_STATIC_CLASSIFIER)); 01549 contains_nonfrag |= !current_is_frag; // update contains_nonfrag 01550 choices_length++; 01551 if (choices_length >= max_matches) break; 01552 } 01553 Results->match.truncate(choices_length); 01554 } // ConvertMatchesToChoices 01555 01556 01557 /*---------------------------------------------------------------------------*/ 01558 #ifndef GRAPHICS_DISABLED 01559 01569 void Classify::DebugAdaptiveClassifier(TBLOB *blob, 01570 ADAPT_RESULTS *Results) { 01571 if (static_classifier_ == NULL) return; 01572 for (int i = 0; i < Results->match.size(); i++) { 01573 if (i == 0 || Results->match[i].rating < Results->best_match.rating) 01574 Results->best_match = Results->match[i]; 01575 } 01576 INT_FX_RESULT_STRUCT fx_info; 01577 GenericVector<INT_FEATURE_STRUCT> bl_features; 01578 TrainingSample* sample = 01579 BlobToTrainingSample(*blob, false, &fx_info, &bl_features); 01580 if (sample == NULL) return; 01581 static_classifier_->DebugDisplay(*sample, blob->denorm().pix(), 01582 Results->best_match.unichar_id); 01583 } /* DebugAdaptiveClassifier */ 01584 #endif 01585 01586 /*---------------------------------------------------------------------------*/ 01609 void Classify::DoAdaptiveMatch(TBLOB *Blob, ADAPT_RESULTS *Results) { 01610 UNICHAR_ID *Ambiguities; 01611 01612 INT_FX_RESULT_STRUCT fx_info; 01613 GenericVector<INT_FEATURE_STRUCT> bl_features; 01614 TrainingSample* sample = 01615 BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info, 01616 &bl_features); 01617 if (sample == NULL) return; 01618 01619 if (AdaptedTemplates->NumPermClasses < matcher_permanent_classes_min || 01620 tess_cn_matching) { 01621 CharNormClassifier(Blob, *sample, Results); 01622 } else { 01623 Ambiguities = BaselineClassifier(Blob, bl_features, fx_info, 01624 AdaptedTemplates, Results); 01625 if ((!Results->match.empty() && MarginalMatch(Results->best_match.rating) && 01626 !tess_bn_matching) || 01627 Results->match.empty()) { 01628 CharNormClassifier(Blob, *sample, Results); 01629 } else if (Ambiguities && *Ambiguities >= 0 && !tess_bn_matching) { 01630 AmbigClassifier(bl_features, fx_info, Blob, 01631 PreTrainedTemplates, 01632 AdaptedTemplates->Class, 01633 Ambiguities, 01634 Results); 01635 } 01636 } 01637 01638 // Force the blob to be classified as noise 01639 // if the results contain only fragments. 01640 // TODO(daria): verify that this is better than 01641 // just adding a NULL classification. 01642 if (!Results->HasNonfragment || Results->match.empty()) 01643 ClassifyAsNoise(Results); 01644 delete sample; 01645 } /* DoAdaptiveMatch */ 01646 01647 /*---------------------------------------------------------------------------*/ 01664 UNICHAR_ID *Classify::GetAmbiguities(TBLOB *Blob, 01665 CLASS_ID CorrectClass) { 01666 ADAPT_RESULTS *Results = new ADAPT_RESULTS(); 01667 UNICHAR_ID *Ambiguities; 01668 int i; 01669 01670 Results->Initialize(); 01671 INT_FX_RESULT_STRUCT fx_info; 01672 GenericVector<INT_FEATURE_STRUCT> bl_features; 01673 TrainingSample* sample = 01674 BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info, 01675 &bl_features); 01676 if (sample == NULL) { 01677 delete Results; 01678 return NULL; 01679 } 01680 01681 CharNormClassifier(Blob, *sample, Results); 01682 delete sample; 01683 RemoveBadMatches(Results); 01684 Results->match.sort(CompareByRating); 01685 01686 /* copy the class id's into an string of ambiguities - don't copy if 01687 the correct class is the only class id matched */ 01688 Ambiguities = new UNICHAR_ID[Results->match.size() + 1]; 01689 if (Results->match.size() > 1 || 01690 (Results->match.size() == 1 && 01691 Results->match[0].unichar_id != CorrectClass)) { 01692 for (i = 0; i < Results->match.size(); i++) 01693 Ambiguities[i] = Results->match[i].unichar_id; 01694 Ambiguities[i] = -1; 01695 } else { 01696 Ambiguities[0] = -1; 01697 } 01698 01699 delete Results; 01700 return Ambiguities; 01701 } /* GetAmbiguities */ 01702 01703 // Returns true if the given blob looks too dissimilar to any character 01704 // present in the classifier templates. 01705 bool Classify::LooksLikeGarbage(TBLOB *blob) { 01706 BLOB_CHOICE_LIST *ratings = new BLOB_CHOICE_LIST(); 01707 AdaptiveClassifier(blob, ratings); 01708 BLOB_CHOICE_IT ratings_it(ratings); 01709 const UNICHARSET &unicharset = getDict().getUnicharset(); 01710 if (classify_debug_character_fragments) { 01711 print_ratings_list("======================\nLooksLikeGarbage() got ", 01712 ratings, unicharset); 01713 } 01714 for (ratings_it.mark_cycle_pt(); !ratings_it.cycled_list(); 01715 ratings_it.forward()) { 01716 if (unicharset.get_fragment(ratings_it.data()->unichar_id()) != NULL) { 01717 continue; 01718 } 01719 float certainty = ratings_it.data()->certainty(); 01720 delete ratings; 01721 return certainty < 01722 classify_character_fragments_garbage_certainty_threshold; 01723 } 01724 delete ratings; 01725 return true; // no whole characters in ratings 01726 } 01727 01728 /*---------------------------------------------------------------------------*/ 01754 int Classify::GetCharNormFeature(const INT_FX_RESULT_STRUCT& fx_info, 01755 INT_TEMPLATES templates, 01756 uinT8* pruner_norm_array, 01757 uinT8* char_norm_array) { 01758 FEATURE norm_feature = NewFeature(&CharNormDesc); 01759 float baseline = kBlnBaselineOffset; 01760 float scale = MF_SCALE_FACTOR; 01761 norm_feature->Params[CharNormY] = (fx_info.Ymean - baseline) * scale; 01762 norm_feature->Params[CharNormLength] = 01763 fx_info.Length * scale / LENGTH_COMPRESSION; 01764 norm_feature->Params[CharNormRx] = fx_info.Rx * scale; 01765 norm_feature->Params[CharNormRy] = fx_info.Ry * scale; 01766 // Deletes norm_feature. 01767 ComputeCharNormArrays(norm_feature, templates, char_norm_array, 01768 pruner_norm_array); 01769 return IntCastRounded(fx_info.Length / kStandardFeatureLength); 01770 } /* GetCharNormFeature */ 01771 01772 // Computes the char_norm_array for the unicharset and, if not NULL, the 01773 // pruner_array as appropriate according to the existence of the shape_table. 01774 void Classify::ComputeCharNormArrays(FEATURE_STRUCT* norm_feature, 01775 INT_TEMPLATES_STRUCT* templates, 01776 uinT8* char_norm_array, 01777 uinT8* pruner_array) { 01778 ComputeIntCharNormArray(*norm_feature, char_norm_array); 01779 if (pruner_array != NULL) { 01780 if (shape_table_ == NULL) { 01781 ComputeIntCharNormArray(*norm_feature, pruner_array); 01782 } else { 01783 memset(pruner_array, MAX_UINT8, 01784 templates->NumClasses * sizeof(pruner_array[0])); 01785 // Each entry in the pruner norm array is the MIN of all the entries of 01786 // the corresponding unichars in the CharNormArray. 01787 for (int id = 0; id < templates->NumClasses; ++id) { 01788 int font_set_id = templates->Class[id]->font_set_id; 01789 const FontSet &fs = fontset_table_.get(font_set_id); 01790 for (int config = 0; config < fs.size; ++config) { 01791 const Shape& shape = shape_table_->GetShape(fs.configs[config]); 01792 for (int c = 0; c < shape.size(); ++c) { 01793 if (char_norm_array[shape[c].unichar_id] < pruner_array[id]) 01794 pruner_array[id] = char_norm_array[shape[c].unichar_id]; 01795 } 01796 } 01797 } 01798 } 01799 } 01800 FreeFeature(norm_feature); 01801 } 01802 01803 /*---------------------------------------------------------------------------*/ 01818 int Classify::MakeNewTemporaryConfig(ADAPT_TEMPLATES Templates, 01819 CLASS_ID ClassId, 01820 int FontinfoId, 01821 int NumFeatures, 01822 INT_FEATURE_ARRAY Features, 01823 FEATURE_SET FloatFeatures) { 01824 INT_CLASS IClass; 01825 ADAPT_CLASS Class; 01826 PROTO_ID OldProtos[MAX_NUM_PROTOS]; 01827 FEATURE_ID BadFeatures[MAX_NUM_INT_FEATURES]; 01828 int NumOldProtos; 01829 int NumBadFeatures; 01830 int MaxProtoId, OldMaxProtoId; 01831 int BlobLength = 0; 01832 int MaskSize; 01833 int ConfigId; 01834 TEMP_CONFIG Config; 01835 int i; 01836 int debug_level = NO_DEBUG; 01837 01838 if (classify_learning_debug_level >= 3) 01839 debug_level = 01840 PRINT_MATCH_SUMMARY | PRINT_FEATURE_MATCHES | PRINT_PROTO_MATCHES; 01841 01842 IClass = ClassForClassId(Templates->Templates, ClassId); 01843 Class = Templates->Class[ClassId]; 01844 01845 if (IClass->NumConfigs >= MAX_NUM_CONFIGS) { 01846 ++NumAdaptationsFailed; 01847 if (classify_learning_debug_level >= 1) 01848 cprintf("Cannot make new temporary config: maximum number exceeded.\n"); 01849 return -1; 01850 } 01851 01852 OldMaxProtoId = IClass->NumProtos - 1; 01853 01854 NumOldProtos = im_.FindGoodProtos(IClass, AllProtosOn, AllConfigsOff, 01855 BlobLength, NumFeatures, Features, 01856 OldProtos, classify_adapt_proto_threshold, 01857 debug_level); 01858 01859 MaskSize = WordsInVectorOfSize(MAX_NUM_PROTOS); 01860 zero_all_bits(TempProtoMask, MaskSize); 01861 for (i = 0; i < NumOldProtos; i++) 01862 SET_BIT(TempProtoMask, OldProtos[i]); 01863 01864 NumBadFeatures = im_.FindBadFeatures(IClass, TempProtoMask, AllConfigsOn, 01865 BlobLength, NumFeatures, Features, 01866 BadFeatures, 01867 classify_adapt_feature_threshold, 01868 debug_level); 01869 01870 MaxProtoId = MakeNewTempProtos(FloatFeatures, NumBadFeatures, BadFeatures, 01871 IClass, Class, TempProtoMask); 01872 if (MaxProtoId == NO_PROTO) { 01873 ++NumAdaptationsFailed; 01874 if (classify_learning_debug_level >= 1) 01875 cprintf("Cannot make new temp protos: maximum number exceeded.\n"); 01876 return -1; 01877 } 01878 01879 ConfigId = AddIntConfig(IClass); 01880 ConvertConfig(TempProtoMask, ConfigId, IClass); 01881 Config = NewTempConfig(MaxProtoId, FontinfoId); 01882 TempConfigFor(Class, ConfigId) = Config; 01883 copy_all_bits(TempProtoMask, Config->Protos, Config->ProtoVectorSize); 01884 01885 if (classify_learning_debug_level >= 1) 01886 cprintf("Making new temp config %d fontinfo id %d" 01887 " using %d old and %d new protos.\n", 01888 ConfigId, Config->FontinfoId, 01889 NumOldProtos, MaxProtoId - OldMaxProtoId); 01890 01891 return ConfigId; 01892 } /* MakeNewTemporaryConfig */ 01893 01894 /*---------------------------------------------------------------------------*/ 01915 PROTO_ID Classify::MakeNewTempProtos(FEATURE_SET Features, 01916 int NumBadFeat, 01917 FEATURE_ID BadFeat[], 01918 INT_CLASS IClass, 01919 ADAPT_CLASS Class, 01920 BIT_VECTOR TempProtoMask) { 01921 FEATURE_ID *ProtoStart; 01922 FEATURE_ID *ProtoEnd; 01923 FEATURE_ID *LastBad; 01924 TEMP_PROTO TempProto; 01925 PROTO Proto; 01926 FEATURE F1, F2; 01927 FLOAT32 X1, X2, Y1, Y2; 01928 FLOAT32 A1, A2, AngleDelta; 01929 FLOAT32 SegmentLength; 01930 PROTO_ID Pid; 01931 01932 for (ProtoStart = BadFeat, LastBad = ProtoStart + NumBadFeat; 01933 ProtoStart < LastBad; ProtoStart = ProtoEnd) { 01934 F1 = Features->Features[*ProtoStart]; 01935 X1 = F1->Params[PicoFeatX]; 01936 Y1 = F1->Params[PicoFeatY]; 01937 A1 = F1->Params[PicoFeatDir]; 01938 01939 for (ProtoEnd = ProtoStart + 1, 01940 SegmentLength = GetPicoFeatureLength(); 01941 ProtoEnd < LastBad; 01942 ProtoEnd++, SegmentLength += GetPicoFeatureLength()) { 01943 F2 = Features->Features[*ProtoEnd]; 01944 X2 = F2->Params[PicoFeatX]; 01945 Y2 = F2->Params[PicoFeatY]; 01946 A2 = F2->Params[PicoFeatDir]; 01947 01948 AngleDelta = fabs(A1 - A2); 01949 if (AngleDelta > 0.5) 01950 AngleDelta = 1.0 - AngleDelta; 01951 01952 if (AngleDelta > matcher_clustering_max_angle_delta || 01953 fabs(X1 - X2) > SegmentLength || 01954 fabs(Y1 - Y2) > SegmentLength) 01955 break; 01956 } 01957 01958 F2 = Features->Features[*(ProtoEnd - 1)]; 01959 X2 = F2->Params[PicoFeatX]; 01960 Y2 = F2->Params[PicoFeatY]; 01961 A2 = F2->Params[PicoFeatDir]; 01962 01963 Pid = AddIntProto(IClass); 01964 if (Pid == NO_PROTO) 01965 return (NO_PROTO); 01966 01967 TempProto = NewTempProto(); 01968 Proto = &(TempProto->Proto); 01969 01970 /* compute proto params - NOTE that Y_DIM_OFFSET must be used because 01971 ConvertProto assumes that the Y dimension varies from -0.5 to 0.5 01972 instead of the -0.25 to 0.75 used in baseline normalization */ 01973 Proto->Length = SegmentLength; 01974 Proto->Angle = A1; 01975 Proto->X = (X1 + X2) / 2.0; 01976 Proto->Y = (Y1 + Y2) / 2.0 - Y_DIM_OFFSET; 01977 FillABC(Proto); 01978 01979 TempProto->ProtoId = Pid; 01980 SET_BIT(TempProtoMask, Pid); 01981 01982 ConvertProto(Proto, Pid, IClass); 01983 AddProtoToProtoPruner(Proto, Pid, IClass, 01984 classify_learning_debug_level >= 2); 01985 01986 Class->TempProtos = push(Class->TempProtos, TempProto); 01987 } 01988 return IClass->NumProtos - 1; 01989 } /* MakeNewTempProtos */ 01990 01991 /*---------------------------------------------------------------------------*/ 02004 void Classify::MakePermanent(ADAPT_TEMPLATES Templates, 02005 CLASS_ID ClassId, 02006 int ConfigId, 02007 TBLOB *Blob) { 02008 UNICHAR_ID *Ambigs; 02009 TEMP_CONFIG Config; 02010 ADAPT_CLASS Class; 02011 PROTO_KEY ProtoKey; 02012 02013 Class = Templates->Class[ClassId]; 02014 Config = TempConfigFor(Class, ConfigId); 02015 02016 MakeConfigPermanent(Class, ConfigId); 02017 if (Class->NumPermConfigs == 0) 02018 Templates->NumPermClasses++; 02019 Class->NumPermConfigs++; 02020 02021 // Initialize permanent config. 02022 Ambigs = GetAmbiguities(Blob, ClassId); 02023 PERM_CONFIG Perm = (PERM_CONFIG) alloc_struct(sizeof(PERM_CONFIG_STRUCT), 02024 "PERM_CONFIG_STRUCT"); 02025 Perm->Ambigs = Ambigs; 02026 Perm->FontinfoId = Config->FontinfoId; 02027 02028 // Free memory associated with temporary config (since ADAPTED_CONFIG 02029 // is a union we need to clean up before we record permanent config). 02030 ProtoKey.Templates = Templates; 02031 ProtoKey.ClassId = ClassId; 02032 ProtoKey.ConfigId = ConfigId; 02033 Class->TempProtos = delete_d(Class->TempProtos, &ProtoKey, MakeTempProtoPerm); 02034 FreeTempConfig(Config); 02035 02036 // Record permanent config. 02037 PermConfigFor(Class, ConfigId) = Perm; 02038 02039 if (classify_learning_debug_level >= 1) { 02040 tprintf("Making config %d for %s (ClassId %d) permanent:" 02041 " fontinfo id %d, ambiguities '", 02042 ConfigId, getDict().getUnicharset().debug_str(ClassId).string(), 02043 ClassId, PermConfigFor(Class, ConfigId)->FontinfoId); 02044 for (UNICHAR_ID *AmbigsPointer = Ambigs; 02045 *AmbigsPointer >= 0; ++AmbigsPointer) 02046 tprintf("%s", unicharset.id_to_unichar(*AmbigsPointer)); 02047 tprintf("'.\n"); 02048 } 02049 } /* MakePermanent */ 02050 } // namespace tesseract 02051 02052 /*---------------------------------------------------------------------------*/ 02067 int MakeTempProtoPerm(void *item1, void *item2) { 02068 ADAPT_CLASS Class; 02069 TEMP_CONFIG Config; 02070 TEMP_PROTO TempProto; 02071 PROTO_KEY *ProtoKey; 02072 02073 TempProto = (TEMP_PROTO) item1; 02074 ProtoKey = (PROTO_KEY *) item2; 02075 02076 Class = ProtoKey->Templates->Class[ProtoKey->ClassId]; 02077 Config = TempConfigFor(Class, ProtoKey->ConfigId); 02078 02079 if (TempProto->ProtoId > Config->MaxProtoId || 02080 !test_bit (Config->Protos, TempProto->ProtoId)) 02081 return FALSE; 02082 02083 MakeProtoPermanent(Class, TempProto->ProtoId); 02084 AddProtoToClassPruner(&(TempProto->Proto), ProtoKey->ClassId, 02085 ProtoKey->Templates->Templates); 02086 FreeTempProto(TempProto); 02087 02088 return TRUE; 02089 } /* MakeTempProtoPerm */ 02090 02091 /*---------------------------------------------------------------------------*/ 02092 namespace tesseract { 02104 void Classify::PrintAdaptiveMatchResults(FILE *File, ADAPT_RESULTS *Results) { 02105 for (int i = 0; i < Results->match.size(); ++i) { 02106 tprintf("%s(%d), shape %d, %.2f ", 02107 unicharset.debug_str(Results->match[i].unichar_id).string(), 02108 Results->match[i].unichar_id, Results->match[i].shape_id, 02109 Results->match[i].rating * 100.0); 02110 } 02111 tprintf("\n"); 02112 } /* PrintAdaptiveMatchResults */ 02113 02114 /*---------------------------------------------------------------------------*/ 02130 void Classify::RemoveBadMatches(ADAPT_RESULTS *Results) { 02131 int Next, NextGood; 02132 FLOAT32 BadMatchThreshold; 02133 static const char* romans = "i v x I V X"; 02134 BadMatchThreshold = Results->best_match.rating + matcher_bad_match_pad; 02135 02136 if (classify_bln_numeric_mode) { 02137 UNICHAR_ID unichar_id_one = unicharset.contains_unichar("1") ? 02138 unicharset.unichar_to_id("1") : -1; 02139 UNICHAR_ID unichar_id_zero = unicharset.contains_unichar("0") ? 02140 unicharset.unichar_to_id("0") : -1; 02141 ScoredClass scored_one = ScoredUnichar(Results, unichar_id_one); 02142 ScoredClass scored_zero = ScoredUnichar(Results, unichar_id_zero); 02143 02144 for (Next = NextGood = 0; Next < Results->match.size(); Next++) { 02145 if (Results->match[Next].rating <= BadMatchThreshold) { 02146 ScoredClass match = Results->match[Next]; 02147 if (!unicharset.get_isalpha(match.unichar_id) || 02148 strstr(romans, 02149 unicharset.id_to_unichar(match.unichar_id)) != NULL) { 02150 Results->match[NextGood++] = Results->match[Next]; 02151 } else if (unicharset.eq(match.unichar_id, "l") && 02152 scored_one.rating >= BadMatchThreshold) { 02153 Results->match[NextGood] = scored_one; 02154 Results->match[NextGood].rating = match.rating; 02155 NextGood++; 02156 } else if (unicharset.eq(match.unichar_id, "O") && 02157 scored_zero.rating >= BadMatchThreshold) { 02158 Results->match[NextGood] = scored_zero; 02159 Results->match[NextGood].rating = match.rating; 02160 NextGood++; 02161 } 02162 } 02163 } 02164 } else { 02165 for (Next = NextGood = 0; Next < Results->match.size(); Next++) { 02166 if (Results->match[Next].rating <= BadMatchThreshold) 02167 Results->match[NextGood++] = Results->match[Next]; 02168 } 02169 } 02170 Results->match.truncate(NextGood); 02171 } /* RemoveBadMatches */ 02172 02173 /*----------------------------------------------------------------------------*/ 02183 void Classify::RemoveExtraPuncs(ADAPT_RESULTS *Results) { 02184 int Next, NextGood; 02185 int punc_count; /*no of garbage characters */ 02186 int digit_count; 02187 /*garbage characters */ 02188 static char punc_chars[] = ". , ; : / ` ~ ' - = \\ | \" ! _ ^"; 02189 static char digit_chars[] = "0 1 2 3 4 5 6 7 8 9"; 02190 02191 punc_count = 0; 02192 digit_count = 0; 02193 for (Next = NextGood = 0; Next < Results->match.size(); Next++) { 02194 ScoredClass match = Results->match[Next]; 02195 if (strstr(punc_chars, 02196 unicharset.id_to_unichar(match.unichar_id)) != NULL) { 02197 if (punc_count < 2) 02198 Results->match[NextGood++] = match; 02199 punc_count++; 02200 } else { 02201 if (strstr(digit_chars, 02202 unicharset.id_to_unichar(match.unichar_id)) != NULL) { 02203 if (digit_count < 1) 02204 Results->match[NextGood++] = match; 02205 digit_count++; 02206 } else { 02207 Results->match[NextGood++] = match; 02208 } 02209 } 02210 } 02211 Results->match.truncate(NextGood); 02212 } /* RemoveExtraPuncs */ 02213 02214 /*---------------------------------------------------------------------------*/ 02228 void Classify::SetAdaptiveThreshold(FLOAT32 Threshold) { 02229 Threshold = (Threshold == matcher_good_threshold) ? 0.9: (1.0 - Threshold); 02230 classify_adapt_proto_threshold.set_value( 02231 ClipToRange<int>(255 * Threshold, 0, 255)); 02232 classify_adapt_feature_threshold.set_value( 02233 ClipToRange<int>(255 * Threshold, 0, 255)); 02234 } /* SetAdaptiveThreshold */ 02235 02236 /*---------------------------------------------------------------------------*/ 02249 void Classify::ShowBestMatchFor(int shape_id, 02250 const INT_FEATURE_STRUCT* features, 02251 int num_features) { 02252 #ifndef GRAPHICS_DISABLED 02253 uinT32 config_mask; 02254 if (UnusedClassIdIn(PreTrainedTemplates, shape_id)) { 02255 tprintf("No built-in templates for class/shape %d\n", shape_id); 02256 return; 02257 } 02258 if (num_features <= 0) { 02259 tprintf("Illegal blob (char norm features)!\n"); 02260 return; 02261 } 02262 INT_RESULT_STRUCT cn_result; 02263 classify_norm_method.set_value(character); 02264 im_.Match(ClassForClassId(PreTrainedTemplates, shape_id), 02265 AllProtosOn, AllConfigsOn, 02266 num_features, features, &cn_result, 02267 classify_adapt_feature_threshold, NO_DEBUG, 02268 matcher_debug_separate_windows); 02269 tprintf("\n"); 02270 config_mask = 1 << cn_result.Config; 02271 02272 tprintf("Static Shape ID: %d\n", shape_id); 02273 ShowMatchDisplay(); 02274 im_.Match(ClassForClassId(PreTrainedTemplates, shape_id), 02275 AllProtosOn, reinterpret_cast<BIT_VECTOR>(&config_mask), 02276 num_features, features, &cn_result, 02277 classify_adapt_feature_threshold, 02278 matcher_debug_flags, 02279 matcher_debug_separate_windows); 02280 UpdateMatchDisplay(); 02281 #endif // GRAPHICS_DISABLED 02282 } /* ShowBestMatchFor */ 02283 02284 // Returns a string for the classifier class_id: either the corresponding 02285 // unicharset debug_str or the shape_table_ debug str. 02286 STRING Classify::ClassIDToDebugStr(const INT_TEMPLATES_STRUCT* templates, 02287 int class_id, int config_id) const { 02288 STRING class_string; 02289 if (templates == PreTrainedTemplates && shape_table_ != NULL) { 02290 int shape_id = ClassAndConfigIDToFontOrShapeID(class_id, config_id); 02291 class_string = shape_table_->DebugStr(shape_id); 02292 } else { 02293 class_string = unicharset.debug_str(class_id); 02294 } 02295 return class_string; 02296 } 02297 02298 // Converts a classifier class_id index to a shape_table_ index 02299 int Classify::ClassAndConfigIDToFontOrShapeID(int class_id, 02300 int int_result_config) const { 02301 int font_set_id = PreTrainedTemplates->Class[class_id]->font_set_id; 02302 // Older inttemps have no font_ids. 02303 if (font_set_id < 0) 02304 return kBlankFontinfoId; 02305 const FontSet &fs = fontset_table_.get(font_set_id); 02306 ASSERT_HOST(int_result_config >= 0 && int_result_config < fs.size); 02307 return fs.configs[int_result_config]; 02308 } 02309 02310 // Converts a shape_table_ index to a classifier class_id index (not a 02311 // unichar-id!). Uses a search, so not fast. 02312 int Classify::ShapeIDToClassID(int shape_id) const { 02313 for (int id = 0; id < PreTrainedTemplates->NumClasses; ++id) { 02314 int font_set_id = PreTrainedTemplates->Class[id]->font_set_id; 02315 ASSERT_HOST(font_set_id >= 0); 02316 const FontSet &fs = fontset_table_.get(font_set_id); 02317 for (int config = 0; config < fs.size; ++config) { 02318 if (fs.configs[config] == shape_id) 02319 return id; 02320 } 02321 } 02322 tprintf("Shape %d not found\n", shape_id); 02323 return -1; 02324 } 02325 02326 // Returns true if the given TEMP_CONFIG is good enough to make it 02327 // a permanent config. 02328 bool Classify::TempConfigReliable(CLASS_ID class_id, 02329 const TEMP_CONFIG &config) { 02330 if (classify_learning_debug_level >= 1) { 02331 tprintf("NumTimesSeen for config of %s is %d\n", 02332 getDict().getUnicharset().debug_str(class_id).string(), 02333 config->NumTimesSeen); 02334 } 02335 if (config->NumTimesSeen >= matcher_sufficient_examples_for_prototyping) { 02336 return true; 02337 } else if (config->NumTimesSeen < matcher_min_examples_for_prototyping) { 02338 return false; 02339 } else if (use_ambigs_for_adaption) { 02340 // Go through the ambigs vector and see whether we have already seen 02341 // enough times all the characters represented by the ambigs vector. 02342 const UnicharIdVector *ambigs = 02343 getDict().getUnicharAmbigs().AmbigsForAdaption(class_id); 02344 int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size(); 02345 for (int ambig = 0; ambig < ambigs_size; ++ambig) { 02346 ADAPT_CLASS ambig_class = AdaptedTemplates->Class[(*ambigs)[ambig]]; 02347 assert(ambig_class != NULL); 02348 if (ambig_class->NumPermConfigs == 0 && 02349 ambig_class->MaxNumTimesSeen < 02350 matcher_min_examples_for_prototyping) { 02351 if (classify_learning_debug_level >= 1) { 02352 tprintf("Ambig %s has not been seen enough times," 02353 " not making config for %s permanent\n", 02354 getDict().getUnicharset().debug_str( 02355 (*ambigs)[ambig]).string(), 02356 getDict().getUnicharset().debug_str(class_id).string()); 02357 } 02358 return false; 02359 } 02360 } 02361 } 02362 return true; 02363 } 02364 02365 void Classify::UpdateAmbigsGroup(CLASS_ID class_id, TBLOB *Blob) { 02366 const UnicharIdVector *ambigs = 02367 getDict().getUnicharAmbigs().ReverseAmbigsForAdaption(class_id); 02368 int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size(); 02369 if (classify_learning_debug_level >= 1) { 02370 tprintf("Running UpdateAmbigsGroup for %s class_id=%d\n", 02371 getDict().getUnicharset().debug_str(class_id).string(), class_id); 02372 } 02373 for (int ambig = 0; ambig < ambigs_size; ++ambig) { 02374 CLASS_ID ambig_class_id = (*ambigs)[ambig]; 02375 const ADAPT_CLASS ambigs_class = AdaptedTemplates->Class[ambig_class_id]; 02376 for (int cfg = 0; cfg < MAX_NUM_CONFIGS; ++cfg) { 02377 if (ConfigIsPermanent(ambigs_class, cfg)) continue; 02378 const TEMP_CONFIG config = 02379 TempConfigFor(AdaptedTemplates->Class[ambig_class_id], cfg); 02380 if (config != NULL && TempConfigReliable(ambig_class_id, config)) { 02381 if (classify_learning_debug_level >= 1) { 02382 tprintf("Making config %d of %s permanent\n", cfg, 02383 getDict().getUnicharset().debug_str( 02384 ambig_class_id).string()); 02385 } 02386 MakePermanent(AdaptedTemplates, ambig_class_id, cfg, Blob); 02387 } 02388 } 02389 } 02390 } 02391 02392 } // namespace tesseract