Font and classifier output structure cleanup.

Font recognition was poor, due to forcing a 1st and 2nd choice at
a character level, when the total score for the correct font is often
correct at the word level, so allowed the propagation of a full set
of fonts and scores to the word recognizer, which can now decide word
level fonts using the scores instead of simple votes.

Change precipitated a cleanup of output data structures for classifier
results, eliminating ScoredClass and INT_RESULT_STRUCT, with a few
extra elements going in UnicharRating, and using that wherever possible.
That added the extra complexity of 1-rating due to a flip between 0 is
good and 0 is bad for the internal classifier scores before they are
converted to rating and certainty.
This commit is contained in:
Ray Smith 2015-05-12 17:24:34 -07:00
parent 0e868ef377
commit 84920b92b3
19 changed files with 430 additions and 429 deletions

View File

@ -272,7 +272,7 @@ void Tesseract::MaximallyChopWord(const GenericVector<TBOX>& boxes,
// limited by the ability of the chopper to find suitable chop points,
// and not by the value of the certainties.
BLOB_CHOICE* choice =
new BLOB_CHOICE(0, rating, -rating, -1, -1, 0, 0, 0, 0, BCC_FAKE);
new BLOB_CHOICE(0, rating, -rating, -1, 0.0f, 0.0f, 0.0f, BCC_FAKE);
blob_choices.push_back(choice);
rating -= 0.125f;
}
@ -291,8 +291,8 @@ void Tesseract::MaximallyChopWord(const GenericVector<TBOX>& boxes,
left_choice->set_certainty(-rating);
// combine confidence w/ serial #
BLOB_CHOICE* right_choice = new BLOB_CHOICE(++right_chop_index,
rating - 0.125f, -rating,
-1, -1, 0, 0, 0, 0, BCC_FAKE);
rating - 0.125f, -rating, -1,
0.0f, 0.0f, 0.0f, BCC_FAKE);
blob_choices.insert(right_choice, blob_number + 1);
}
}

View File

@ -1885,62 +1885,54 @@ void Tesseract::set_word_fonts(WERD_RES *word) {
if (word->chopped_word == NULL) return;
ASSERT_HOST(word->best_choice != NULL);
inT32 index; // char id index
// character iterator
BLOB_CHOICE_IT choice_it; // choice iterator
int fontinfo_size = get_fontinfo_table().size();
int fontset_size = get_fontset_table().size();
if (fontinfo_size == 0 || fontset_size == 0) return;
STATS fonts(0, fontinfo_size); // font counters
if (fontinfo_size == 0) return;
GenericVector<int> font_total_score;
font_total_score.init_to_size(fontinfo_size, 0);
word->italic = 0;
word->bold = 0;
if (!word->best_choice_fontinfo_ids.empty()) {
word->best_choice_fontinfo_ids.clear();
// Compute the font scores for the word
if (tessedit_debug_fonts) {
tprintf("Examining fonts in %s\n",
word->best_choice->debug_string().string());
}
// Compute the modal font for the word
for (index = 0; index < word->best_choice->length(); ++index) {
UNICHAR_ID word_ch_id = word->best_choice->unichar_id(index);
choice_it.set_to_list(word->GetBlobChoices(index));
if (tessedit_debug_fonts) {
tprintf("Examining fonts in %s\n",
word->best_choice->debug_string().string());
}
for (choice_it.mark_cycle_pt(); !choice_it.cycled_list();
choice_it.forward()) {
UNICHAR_ID blob_ch_id = choice_it.data()->unichar_id();
if (blob_ch_id == word_ch_id) {
if (tessedit_debug_fonts) {
tprintf("%s font %s (%d) font2 %s (%d)\n",
word->uch_set->id_to_unichar(blob_ch_id),
choice_it.data()->fontinfo_id() < 0 ? "unknown" :
fontinfo_table_.get(choice_it.data()->fontinfo_id()).name,
choice_it.data()->fontinfo_id(),
choice_it.data()->fontinfo_id2() < 0 ? "unknown" :
fontinfo_table_.get(choice_it.data()->fontinfo_id2()).name,
choice_it.data()->fontinfo_id2());
}
// 1st choice font gets 2 pts, 2nd choice 1 pt.
if (choice_it.data()->fontinfo_id() >= 0) {
fonts.add(choice_it.data()->fontinfo_id(), 2);
}
if (choice_it.data()->fontinfo_id2() >= 0) {
fonts.add(choice_it.data()->fontinfo_id2(), 1);
}
break;
for (int b = 0; b < word->best_choice->length(); ++b) {
BLOB_CHOICE* choice = word->GetBlobChoice(b);
if (choice == NULL) continue;
const GenericVector<ScoredFont>& fonts = choice->fonts();
for (int f = 0; f < fonts.size(); ++f) {
int fontinfo_id = fonts[f].fontinfo_id;
if (0 <= fontinfo_id && fontinfo_id < fontinfo_size) {
font_total_score[fontinfo_id] += fonts[f].score;
}
}
}
inT16 font_id1, font_id2;
find_modal_font(&fonts, &font_id1, &word->fontinfo_id_count);
find_modal_font(&fonts, &font_id2, &word->fontinfo_id2_count);
// Find the top and 2nd choice for the word.
int score1 = 0, score2 = 0;
inT16 font_id1 = -1, font_id2 = -1;
for (int f = 0; f < fontinfo_size; ++f) {
if (tessedit_debug_fonts && font_total_score[f] > 0) {
tprintf("Font %s, total score = %d\n",
fontinfo_table_.get(f).name, font_total_score[f]);
}
if (font_total_score[f] > score1) {
score2 = score1;
font_id2 = font_id1;
score1 = font_total_score[f];
font_id1 = f;
} else if (font_total_score[f] > score2) {
score2 = font_total_score[f];
font_id2 = f;
}
}
word->fontinfo = font_id1 >= 0 ? &fontinfo_table_.get(font_id1) : NULL;
word->fontinfo2 = font_id2 >= 0 ? &fontinfo_table_.get(font_id2) : NULL;
// All the blobs get the word's best choice font.
for (int i = 0; i < word->best_choice->length(); ++i) {
word->best_choice_fontinfo_ids.push_back(font_id1);
}
if (word->fontinfo_id_count > 0) {
// Each score has a limit of MAX_UINT16, so divide by that to get the number
// of "votes" for that font, ie number of perfect scores.
word->fontinfo_id_count = ClipToRange(score1 / MAX_UINT16, 1, MAX_INT8);
word->fontinfo_id2_count = ClipToRange(score2 / MAX_UINT16, 0, MAX_INT8);
if (score1 > 0) {
FontInfo fi = fontinfo_table_.get(font_id1);
if (tessedit_debug_fonts) {
if (word->fontinfo_id2_count > 0) {
@ -1953,9 +1945,8 @@ void Tesseract::set_word_fonts(WERD_RES *word) {
fi.name, word->fontinfo_id_count);
}
}
// 1st choices got 2 pts, so we need to halve the score for the mode.
word->italic = (fi.is_italic() ? 1 : -1) * (word->fontinfo_id_count + 1) / 2;
word->bold = (fi.is_bold() ? 1 : -1) * (word->fontinfo_id_count + 1) / 2;
word->italic = (fi.is_italic() ? 1 : -1) * word->fontinfo_id_count;
word->bold = (fi.is_bold() ? 1 : -1) * word->fontinfo_id_count;
}
}
@ -2009,8 +2000,7 @@ void Tesseract::font_recognition_pass(PAGE_RES* page_res) {
word = page_res_it.word();
int length = word->best_choice->length();
// 1st choices got 2 pts, so we need to halve the score for the mode.
int count = (word->fontinfo_id_count + 1) / 2;
int count = word->fontinfo_id_count;
if (!(count == length || (length > 3 && count >= length * 3 / 4))) {
word->fontinfo = modal_font;
// Counts only get 1 as it came from the doc.

View File

@ -384,7 +384,7 @@ bool Tesseract::cube_recognize(CubeObject *cube_obj, BLOCK* block,
UNICHAR_ID uch_id =
cube_cntxt_->CharacterSet()->UnicharID(char_samples[i]->StrLabel());
choices[i] = new BLOB_CHOICE(uch_id, -cube_certainty, cube_certainty,
-1, -1, 0, 0, 0, 0, BCC_STATIC_CLASSIFIER);
-1, 0.0f, 0.0f, 0.0f, BCC_STATIC_CLASSIFIER);
}
word->FakeClassifyWord(num_chars, choices);
// within a word, cube recognizes the word in reading order.

View File

@ -59,10 +59,10 @@ bool FontInfoTable::DeSerialize(bool swap, FILE* fp) {
// Returns true if the given set of fonts includes one with the same
// properties as font_id.
bool FontInfoTable::SetContainsFontProperties(
int font_id, const GenericVector<int>& font_set) const {
int font_id, const GenericVector<ScoredFont>& font_set) const {
uinT32 properties = get(font_id).properties;
for (int f = 0; f < font_set.size(); ++f) {
if (get(font_set[f]).properties == properties)
if (get(font_set[f].fontinfo_id).properties == properties)
return true;
}
return false;
@ -70,12 +70,12 @@ bool FontInfoTable::SetContainsFontProperties(
// Returns true if the given set of fonts includes multiple properties.
bool FontInfoTable::SetContainsMultipleFontProperties(
const GenericVector<int>& font_set) const {
const GenericVector<ScoredFont>& font_set) const {
if (font_set.empty()) return false;
int first_font = font_set[0];
int first_font = font_set[0].fontinfo_id;
uinT32 properties = get(first_font).properties;
for (int f = 1; f < font_set.size(); ++f) {
if (get(font_set[f]).properties != properties)
if (get(font_set[f].fontinfo_id).properties != properties)
return true;
}
return false;

View File

@ -31,6 +31,22 @@ namespace tesseract {
class BitVector;
// Simple struct to hold a font and a score. The scores come from the low-level
// integer matcher, so they are in the uinT16 range. Fonts are an index to
// fontinfo_table.
// These get copied around a lot, so best to keep them small.
struct ScoredFont {
ScoredFont() : fontinfo_id(-1), score(0) {}
ScoredFont(int font_id, uinT16 classifier_score)
: fontinfo_id(font_id), score(classifier_score) {}
// Index into fontinfo table, but inside the classifier, may be a shapetable
// index.
inT32 fontinfo_id;
// Raw score from the low-level classifier.
uinT16 score;
};
// Struct for information about spacing between characters in a particular font.
struct FontSpacingInfo {
inT16 x_gap_before;
@ -140,11 +156,11 @@ class FontInfoTable : public GenericVector<FontInfo> {
// Returns true if the given set of fonts includes one with the same
// properties as font_id.
bool SetContainsFontProperties(int font_id,
const GenericVector<int>& font_set) const;
bool SetContainsFontProperties(
int font_id, const GenericVector<ScoredFont>& font_set) const;
// Returns true if the given set of fonts includes multiple properties.
bool SetContainsMultipleFontProperties(
const GenericVector<int>& font_set) const;
const GenericVector<ScoredFont>& font_set) const;
// Moves any non-empty FontSpacingInfo entries from other to this.
void MoveSpacingInfoFrom(FontInfoTable* other);

View File

@ -148,6 +148,7 @@ ROW_RES::ROW_RES(bool merge_similar_words, ROW *the_row) {
add_next_word = false;
}
}
next_word->set_flag(W_FUZZY_NON, add_next_word);
} else {
add_next_word = next_word->flag(W_FUZZY_NON);
}
@ -206,12 +207,8 @@ WERD_RES& WERD_RES::operator=(const WERD_RES & source) {
if (!wc_dest_it.empty()) {
wc_dest_it.move_to_first();
best_choice = wc_dest_it.data();
best_choice_fontinfo_ids = source.best_choice_fontinfo_ids;
} else {
best_choice = NULL;
if (!best_choice_fontinfo_ids.empty()) {
best_choice_fontinfo_ids.clear();
}
}
if (source.raw_choice != NULL) {

View File

@ -315,8 +315,6 @@ class WERD_RES : public ELIST_LINK {
BOOL8 combination; //of two fuzzy gap wds
BOOL8 part_of_combo; //part of a combo
BOOL8 reject_spaces; //Reject spacing?
// FontInfo ids for each unichar in best_choice.
GenericVector<inT8> best_choice_fontinfo_ids;
WERD_RES() {
InitNonPointers();

View File

@ -90,8 +90,6 @@ static const char * const kPermuterTypeNames[] = {
BLOB_CHOICE::BLOB_CHOICE(UNICHAR_ID src_unichar_id, // character id
float src_rating, // rating
float src_cert, // certainty
inT16 src_fontinfo_id, // font
inT16 src_fontinfo_id2, // 2nd choice font
int src_script_id, // script
float min_xheight, // min xheight allowed
float max_xheight, // max xheight by this char
@ -100,8 +98,8 @@ BLOB_CHOICE::BLOB_CHOICE(UNICHAR_ID src_unichar_id, // character id
unichar_id_ = src_unichar_id;
rating_ = src_rating;
certainty_ = src_cert;
fontinfo_id_ = src_fontinfo_id;
fontinfo_id2_ = src_fontinfo_id2;
fontinfo_id_ = -1;
fontinfo_id2_ = -1;
script_id_ = src_script_id;
min_xheight_ = min_xheight;
max_xheight_ = max_xheight;
@ -126,6 +124,7 @@ BLOB_CHOICE::BLOB_CHOICE(const BLOB_CHOICE &other) {
max_xheight_ = other.max_xheight_;
yshift_ = other.yshift();
classifier_ = other.classifier_;
fonts_ = other.fonts_;
}
// Returns true if *this and other agree on the baseline and x-height

View File

@ -24,6 +24,7 @@
#include "clst.h"
#include "elst.h"
#include "fontinfo.h"
#include "genericvector.h"
#include "matrix.h"
#include "unichar.h"
@ -64,8 +65,6 @@ class BLOB_CHOICE: public ELIST_LINK
BLOB_CHOICE(UNICHAR_ID src_unichar_id, // character id
float src_rating, // rating
float src_cert, // certainty
inT16 src_fontinfo_id, // font
inT16 src_fontinfo_id2, // 2nd choice font
int script_id, // script
float min_xheight, // min xheight in image pixel units
float max_xheight, // max xheight allowed by this char
@ -89,6 +88,26 @@ class BLOB_CHOICE: public ELIST_LINK
inT16 fontinfo_id2() const {
return fontinfo_id2_;
}
const GenericVector<tesseract::ScoredFont>& fonts() const {
return fonts_;
}
void set_fonts(const GenericVector<tesseract::ScoredFont>& fonts) {
fonts_ = fonts;
int score1 = 0, score2 = 0;
fontinfo_id_ = -1;
fontinfo_id2_ = -1;
for (int f = 0; f < fonts_.size(); ++f) {
if (fonts_[f].score > score1) {
score2 = score1;
fontinfo_id2_ = fontinfo_id_;
score1 = fonts_[f].score;
fontinfo_id_ = fonts_[f].fontinfo_id;
} else if (fonts_[f].score > score2) {
score2 = fonts_[f].score;
fontinfo_id2_ = fonts_[f].fontinfo_id;
}
}
}
int script_id() const {
return script_id_;
}
@ -131,12 +150,6 @@ class BLOB_CHOICE: public ELIST_LINK
void set_certainty(float newrat) {
certainty_ = newrat;
}
void set_fontinfo_id(inT16 newfont) {
fontinfo_id_ = newfont;
}
void set_fontinfo_id2(inT16 newfont) {
fontinfo_id2_ = newfont;
}
void set_script(int newscript_id) {
script_id_ = newscript_id;
}
@ -186,6 +199,8 @@ class BLOB_CHOICE: public ELIST_LINK
private:
UNICHAR_ID unichar_id_; // unichar id
// Fonts and scores. Allowed to be empty.
GenericVector<tesseract::ScoredFont> fonts_;
inT16 fontinfo_id_; // char font information
inT16 fontinfo_id2_; // 2nd choice font information
// Rating is the classifier distance weighted by the length of the outline

View File

@ -445,8 +445,10 @@ class PointerVector : public GenericVector<T*> {
}
PointerVector<T>& operator=(const PointerVector& other) {
this->truncate(0);
this->operator+=(other);
if (&other != this) {
this->truncate(0);
this->operator+=(other);
}
return *this;
}
@ -777,8 +779,10 @@ GenericVector<T> &GenericVector<T>::operator+=(const GenericVector& other) {
template <typename T>
GenericVector<T> &GenericVector<T>::operator=(const GenericVector& other) {
this->truncate(0);
this->operator+=(other);
if (&other != this) {
this->truncate(0);
this->operator+=(other);
}
return *this;
}

View File

@ -73,37 +73,39 @@
#define Y_DIM_OFFSET (Y_SHIFT - BASELINE_Y_SHIFT)
#define WORST_POSSIBLE_RATING (1.0)
#define WORST_POSSIBLE_RATING (0.0f)
struct ScoredClass {
CLASS_ID unichar_id;
int shape_id;
FLOAT32 rating;
bool adapted;
inT16 config;
inT16 fontinfo_id;
inT16 fontinfo_id2;
};
using tesseract::UnicharRating;
using tesseract::ScoredFont;
struct ADAPT_RESULTS {
inT32 BlobLength;
bool HasNonfragment;
GenericVector<ScoredClass> match;
ScoredClass best_match;
UNICHAR_ID best_unichar_id;
int best_match_index;
FLOAT32 best_rating;
GenericVector<UnicharRating> match;
GenericVector<CP_RESULT_STRUCT> CPResults;
/// Initializes data members to the default values. Sets the initial
/// rating of each class to be the worst possible rating (1.0).
inline void Initialize() {
BlobLength = MAX_INT32;
HasNonfragment = false;
best_match.unichar_id = NO_CLASS;
best_match.shape_id = -1;
best_match.rating = WORST_POSSIBLE_RATING;
best_match.adapted = false;
best_match.config = 0;
best_match.fontinfo_id = kBlankFontinfoId;
best_match.fontinfo_id2 = kBlankFontinfoId;
BlobLength = MAX_INT32;
HasNonfragment = false;
ComputeBest();
}
// Computes best_unichar_id, best_match_index and best_rating.
void ComputeBest() {
best_unichar_id = INVALID_UNICHAR_ID;
best_match_index = -1;
best_rating = WORST_POSSIBLE_RATING;
for (int i = 0; i < match.size(); ++i) {
if (match[i].rating > best_rating) {
best_rating = match[i].rating;
best_unichar_id = match[i].unichar_id;
best_match_index = i;
}
}
}
};
@ -116,17 +118,30 @@ struct PROTO_KEY {
/*-----------------------------------------------------------------------------
Private Macros
-----------------------------------------------------------------------------*/
#define MarginalMatch(Rating) \
((Rating) > matcher_great_threshold)
inline bool MarginalMatch(float confidence, float matcher_great_threshold) {
return (1.0f - confidence) > matcher_great_threshold;
}
/*-----------------------------------------------------------------------------
Private Function Prototypes
-----------------------------------------------------------------------------*/
int CompareByRating(const void *arg1, const void *arg2);
// Returns the index of the given id in results, if present, or the size of the
// vector (index it will go at) if not present.
static int FindScoredUnichar(UNICHAR_ID id, const ADAPT_RESULTS& results) {
for (int i = 0; i < results.match.size(); i++) {
if (results.match[i].unichar_id == id)
return i;
}
return results.match.size();
}
ScoredClass *FindScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id);
ScoredClass ScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id);
// Returns the current rating for a unichar id if we have rated it, defaulting
// to WORST_POSSIBLE_RATING.
static float ScoredUnichar(UNICHAR_ID id, const ADAPT_RESULTS& results) {
int index = FindScoredUnichar(id, results);
if (index >= results.match.size()) return WORST_POSSIBLE_RATING;
return results.match[index].rating;
}
void InitMatcherRatings(register FLOAT32 *Rating);
@ -176,14 +191,19 @@ void Classify::AdaptiveClassifier(TBLOB *Blob, BLOB_CHOICE_LIST *Choices) {
DoAdaptiveMatch(Blob, Results);
RemoveBadMatches(Results);
Results->match.sort(CompareByRating);
Results->match.sort(&UnicharRating::SortDescendingRating);
RemoveExtraPuncs(Results);
Results->ComputeBest();
ConvertMatchesToChoices(Blob->denorm(), Blob->bounding_box(), Results,
Choices);
// TODO(rays) Move to before ConvertMatchesToChoices!
if (LargeSpeckle(*Blob) || Choices->length() == 0)
AddLargeSpeckleTo(Results->BlobLength, Choices);
if (matcher_debug_level >= 1) {
cprintf ("AD Matches = ");
PrintAdaptiveMatchResults(stdout, Results);
tprintf("AD Matches = ");
PrintAdaptiveMatchResults(*Results);
}
if (LargeSpeckle(*Blob) || Choices->length() == 0)
@ -724,8 +744,8 @@ void Classify::InitAdaptedClass(TBLOB *Blob,
ConvertConfig (AllProtosOn, 0, IClass);
if (classify_learning_debug_level >= 1) {
cprintf ("Added new class '%s' with class id %d and %d protos.\n",
unicharset.id_to_unichar(ClassId), ClassId, NumFeatures);
tprintf("Added new class '%s' with class id %d and %d protos.\n",
unicharset.id_to_unichar(ClassId), ClassId, NumFeatures);
if (classify_learning_debug_level > 1)
DisplayAdaptedChar(Blob, IClass);
}
@ -837,7 +857,7 @@ void Classify::AdaptToChar(TBLOB *Blob,
FLOAT32 Threshold) {
int NumFeatures;
INT_FEATURE_ARRAY IntFeatures;
INT_RESULT_STRUCT IntResult;
UnicharRating int_result;
INT_CLASS IClass;
ADAPT_CLASS Class;
TEMP_CONFIG TempConfig;
@ -847,13 +867,13 @@ void Classify::AdaptToChar(TBLOB *Blob,
if (!LegalClassId (ClassId))
return;
int_result.unichar_id = ClassId;
Class = AdaptedTemplates->Class[ClassId];
assert(Class != NULL);
if (IsEmptyAdaptedClass(Class)) {
InitAdaptedClass(Blob, ClassId, FontinfoId, Class, AdaptedTemplates);
}
else {
IClass = ClassForClassId (AdaptedTemplates->Templates, ClassId);
} else {
IClass = ClassForClassId(AdaptedTemplates->Templates, ClassId);
NumFeatures = GetAdaptiveFeatures(Blob, IntFeatures, &FloatFeatures);
if (NumFeatures <= 0)
@ -870,39 +890,38 @@ void Classify::AdaptToChar(TBLOB *Blob,
}
im_.Match(IClass, AllProtosOn, MatchingFontConfigs,
NumFeatures, IntFeatures,
&IntResult, classify_adapt_feature_threshold,
&int_result, classify_adapt_feature_threshold,
NO_DEBUG, matcher_debug_separate_windows);
FreeBitVector(MatchingFontConfigs);
SetAdaptiveThreshold(Threshold);
if (IntResult.Rating <= Threshold) {
if (ConfigIsPermanent (Class, IntResult.Config)) {
if (1.0f - int_result.rating <= Threshold) {
if (ConfigIsPermanent(Class, int_result.config)) {
if (classify_learning_debug_level >= 1)
cprintf ("Found good match to perm config %d = %4.1f%%.\n",
IntResult.Config, (1.0 - IntResult.Rating) * 100.0);
tprintf("Found good match to perm config %d = %4.1f%%.\n",
int_result.config, int_result.rating * 100.0);
FreeFeatureSet(FloatFeatures);
return;
}
TempConfig = TempConfigFor (Class, IntResult.Config);
TempConfig = TempConfigFor(Class, int_result.config);
IncreaseConfidence(TempConfig);
if (TempConfig->NumTimesSeen > Class->MaxNumTimesSeen) {
Class->MaxNumTimesSeen = TempConfig->NumTimesSeen;
}
if (classify_learning_debug_level >= 1)
cprintf ("Increasing reliability of temp config %d to %d.\n",
IntResult.Config, TempConfig->NumTimesSeen);
tprintf("Increasing reliability of temp config %d to %d.\n",
int_result.config, TempConfig->NumTimesSeen);
if (TempConfigReliable(ClassId, TempConfig)) {
MakePermanent(AdaptedTemplates, ClassId, IntResult.Config, Blob);
MakePermanent(AdaptedTemplates, ClassId, int_result.config, Blob);
UpdateAmbigsGroup(ClassId, Blob);
}
}
else {
} else {
if (classify_learning_debug_level >= 1) {
cprintf ("Found poor match to temp config %d = %4.1f%%.\n",
IntResult.Config, (1.0 - IntResult.Rating) * 100.0);
tprintf("Found poor match to temp config %d = %4.1f%%.\n",
int_result.config, int_result.rating * 100.0);
if (classify_learning_debug_level > 2)
DisplayAdaptedChar(Blob, IClass);
}
@ -937,20 +956,20 @@ void Classify::DisplayAdaptedChar(TBLOB* blob, INT_CLASS_STRUCT* int_class) {
&bl_features);
if (sample == NULL) return;
INT_RESULT_STRUCT IntResult;
UnicharRating int_result;
im_.Match(int_class, AllProtosOn, AllConfigsOn,
bl_features.size(), &bl_features[0],
&IntResult, classify_adapt_feature_threshold,
&int_result, classify_adapt_feature_threshold,
NO_DEBUG, matcher_debug_separate_windows);
cprintf ("Best match to temp config %d = %4.1f%%.\n",
IntResult.Config, (1.0 - IntResult.Rating) * 100.0);
tprintf("Best match to temp config %d = %4.1f%%.\n",
int_result.config, int_result.rating * 100.0);
if (classify_learning_debug_level >= 2) {
uinT32 ConfigMask;
ConfigMask = 1 << IntResult.Config;
ConfigMask = 1 << int_result.config;
ShowMatchDisplay();
im_.Match(int_class, AllProtosOn, (BIT_VECTOR)&ConfigMask,
bl_features.size(), &bl_features[0],
&IntResult, classify_adapt_feature_threshold,
&int_result, classify_adapt_feature_threshold,
6 | 0x19, matcher_debug_separate_windows);
UpdateMatchDisplay();
}
@ -986,44 +1005,34 @@ void Classify::DisplayAdaptedChar(TBLOB* blob, INT_CLASS_STRUCT* int_class) {
* @note Exceptions: none
* @note History: Tue Mar 12 18:19:29 1991, DSJ, Created.
*/
void Classify::AddNewResult(ADAPT_RESULTS *results,
CLASS_ID class_id,
int shape_id,
FLOAT32 rating,
bool adapted,
int config,
int fontinfo_id,
int fontinfo_id2) {
ScoredClass *old_match = FindScoredUnichar(results, class_id);
ScoredClass match =
{ class_id,
shape_id,
rating,
adapted,
static_cast<inT16>(config),
static_cast<inT16>(fontinfo_id),
static_cast<inT16>(fontinfo_id2) };
void Classify::AddNewResult(const UnicharRating& new_result,
ADAPT_RESULTS *results) {
int old_match = FindScoredUnichar(new_result.unichar_id, *results);
if (rating > results->best_match.rating + matcher_bad_match_pad ||
(old_match && rating >= old_match->rating))
return;
if (new_result.rating + matcher_bad_match_pad < results->best_rating ||
(old_match < results->match.size() &&
new_result.rating <= results->match[old_match].rating))
return; // New one not good enough.
if (!unicharset.get_fragment(class_id))
if (!unicharset.get_fragment(new_result.unichar_id))
results->HasNonfragment = true;
if (old_match)
old_match->rating = rating;
else
results->match.push_back(match);
if (old_match < results->match.size()) {
results->match[old_match].rating = new_result.rating;
} else {
results->match.push_back(new_result);
}
if (rating < results->best_match.rating &&
if (new_result.rating > results->best_rating &&
// Ensure that fragments do not affect best rating, class and config.
// This is needed so that at least one non-fragmented character is
// always present in the results.
// TODO(daria): verify that this helps accuracy and does not
// hurt performance.
!unicharset.get_fragment(class_id)) {
results->best_match = match;
!unicharset.get_fragment(new_result.unichar_id)) {
results->best_match_index = old_match;
results->best_rating = new_result.rating;
results->best_unichar_id = new_result.unichar_id;
}
} /* AddNewResult */
@ -1058,7 +1067,7 @@ void Classify::AmbigClassifier(
ADAPT_RESULTS *results) {
if (int_features.empty()) return;
uinT8* CharNormArray = new uinT8[unicharset.size()];
INT_RESULT_STRUCT IntResult;
UnicharRating int_result;
results->BlobLength = GetCharNormFeature(fx_info, templates, NULL,
CharNormArray);
@ -1071,17 +1080,18 @@ void Classify::AmbigClassifier(
while (*ambiguities >= 0) {
CLASS_ID class_id = *ambiguities;
int_result.unichar_id = class_id;
im_.Match(ClassForClassId(templates, class_id),
AllProtosOn, AllConfigsOn,
int_features.size(), &int_features[0],
&IntResult,
&int_result,
classify_adapt_feature_threshold, NO_DEBUG,
matcher_debug_separate_windows);
ExpandShapesAndApplyCorrections(NULL, debug, class_id, bottom, top, 0,
results->BlobLength,
classify_integer_matcher_multiplier,
CharNormArray, IntResult, results);
CharNormArray, &int_result, results);
ambiguities++;
}
delete [] CharNormArray;
@ -1102,14 +1112,15 @@ void Classify::MasterMatcher(INT_TEMPLATES templates,
ADAPT_RESULTS* final_results) {
int top = blob_box.top();
int bottom = blob_box.bottom();
UnicharRating int_result;
for (int c = 0; c < results.size(); c++) {
CLASS_ID class_id = results[c].Class;
INT_RESULT_STRUCT& int_result = results[c].IMResult;
BIT_VECTOR protos = classes != NULL ? classes[class_id]->PermProtos
: AllProtosOn;
BIT_VECTOR configs = classes != NULL ? classes[class_id]->PermConfigs
: AllConfigsOn;
int_result.unichar_id = class_id;
im_.Match(ClassForClassId(templates, class_id),
protos, configs,
num_features, features,
@ -1120,7 +1131,7 @@ void Classify::MasterMatcher(INT_TEMPLATES templates,
results[c].Rating,
final_results->BlobLength,
matcher_multiplier, norm_factors,
int_result, final_results);
&int_result, final_results);
}
}
@ -1133,65 +1144,76 @@ void Classify::ExpandShapesAndApplyCorrections(
ADAPT_CLASS* classes, bool debug, int class_id, int bottom, int top,
float cp_rating, int blob_length, int matcher_multiplier,
const uinT8* cn_factors,
INT_RESULT_STRUCT& int_result, ADAPT_RESULTS* final_results) {
// Compute the fontinfo_ids.
int fontinfo_id = kBlankFontinfoId;
int fontinfo_id2 = kBlankFontinfoId;
UnicharRating* int_result, ADAPT_RESULTS* final_results) {
if (classes != NULL) {
// Adapted result.
fontinfo_id = GetFontinfoId(classes[class_id], int_result.Config);
fontinfo_id2 = GetFontinfoId(classes[class_id], int_result.Config2);
// Adapted result. Convert configs to fontinfo_ids.
int_result->adapted = true;
for (int f = 0; f < int_result->fonts.size(); ++f) {
int_result->fonts[f].fontinfo_id =
GetFontinfoId(classes[class_id], int_result->fonts[f].fontinfo_id);
}
} else {
// Pre-trained result.
fontinfo_id = ClassAndConfigIDToFontOrShapeID(class_id, int_result.Config);
fontinfo_id2 = ClassAndConfigIDToFontOrShapeID(class_id,
int_result.Config2);
// Pre-trained result. Map fonts using font_sets_.
int_result->adapted = false;
for (int f = 0; f < int_result->fonts.size(); ++f) {
int_result->fonts[f].fontinfo_id =
ClassAndConfigIDToFontOrShapeID(class_id,
int_result->fonts[f].fontinfo_id);
}
if (shape_table_ != NULL) {
// Actually fontinfo_id is an index into the shape_table_ and it
// contains a list of unchar_id/font_id pairs.
int shape_id = fontinfo_id;
const Shape& shape = shape_table_->GetShape(fontinfo_id);
double min_rating = 0.0;
for (int c = 0; c < shape.size(); ++c) {
int unichar_id = shape[c].unichar_id;
fontinfo_id = shape[c].font_ids[0];
if (shape[c].font_ids.size() > 1)
fontinfo_id2 = shape[c].font_ids[1];
else if (fontinfo_id2 != kBlankFontinfoId)
fontinfo_id2 = shape_table_->GetShape(fontinfo_id2)[0].font_ids[0];
double rating = ComputeCorrectedRating(debug, unichar_id, cp_rating,
int_result.Rating,
int_result.FeatureMisses,
bottom, top, blob_length,
matcher_multiplier, cn_factors);
if (c == 0 || rating < min_rating)
min_rating = rating;
if (unicharset.get_enabled(unichar_id)) {
AddNewResult(final_results, unichar_id, shape_id, rating,
classes != NULL, int_result.Config,
fontinfo_id, fontinfo_id2);
// Two possible cases:
// 1. Flat shapetable. All unichar-ids of the shapes referenced by
// int_result->fonts are the same. In this case build a new vector of
// mapped fonts and replace the fonts in int_result.
// 2. Multi-unichar shapetable. Variable unichars in the shapes referenced
// by int_result. In this case, build a vector of UnicharRating to
// gather together different font-ids for each unichar. Also covers case1.
GenericVector<UnicharRating> mapped_results;
for (int f = 0; f < int_result->fonts.size(); ++f) {
int shape_id = int_result->fonts[f].fontinfo_id;
const Shape& shape = shape_table_->GetShape(shape_id);
for (int c = 0; c < shape.size(); ++c) {
int unichar_id = shape[c].unichar_id;
if (!unicharset.get_enabled(unichar_id)) continue;
// Find the mapped_result for unichar_id.
int r = 0;
for (r = 0; r < mapped_results.size() &&
mapped_results[r].unichar_id != unichar_id; ++r) {}
if (r == mapped_results.size()) {
mapped_results.push_back(*int_result);
mapped_results[r].unichar_id = unichar_id;
mapped_results[r].fonts.truncate(0);
}
for (int i = 0; i < shape[c].font_ids.size(); ++i) {
mapped_results[r].fonts.push_back(
ScoredFont(shape[c].font_ids[i], int_result->fonts[f].score));
}
}
}
int_result.Rating = min_rating;
for (int m = 0; m < mapped_results.size(); ++m) {
mapped_results[m].rating =
ComputeCorrectedRating(debug, mapped_results[m].unichar_id,
cp_rating, int_result->rating,
int_result->feature_misses, bottom, top,
blob_length, matcher_multiplier, cn_factors);
AddNewResult(mapped_results[m], final_results);
}
return;
}
}
double rating = ComputeCorrectedRating(debug, class_id, cp_rating,
int_result.Rating,
int_result.FeatureMisses,
bottom, top, blob_length,
matcher_multiplier, cn_factors);
if (unicharset.get_enabled(class_id)) {
AddNewResult(final_results, class_id, -1, rating,
classes != NULL, int_result.Config,
fontinfo_id, fontinfo_id2);
int_result->rating = ComputeCorrectedRating(debug, class_id, cp_rating,
int_result->rating,
int_result->feature_misses,
bottom, top, blob_length,
matcher_multiplier, cn_factors);
AddNewResult(*int_result, final_results);
}
int_result.Rating = rating;
}
// Applies a set of corrections to the distance im_rating,
// Applies a set of corrections to the confidence im_rating,
// including the cn_correction, miss penalty and additional penalty
// for non-alnums being vertical misfits. Returns the corrected distance.
// for non-alnums being vertical misfits. Returns the corrected confidence.
double Classify::ComputeCorrectedRating(bool debug, int unichar_id,
double cp_rating, double im_rating,
int feature_misses,
@ -1199,7 +1221,7 @@ double Classify::ComputeCorrectedRating(bool debug, int unichar_id,
int blob_length, int matcher_multiplier,
const uinT8* cn_factors) {
// Compute class feature corrections.
double cn_corrected = im_.ApplyCNCorrection(im_rating, blob_length,
double cn_corrected = im_.ApplyCNCorrection(1.0 - im_rating, blob_length,
cn_factors[unichar_id],
matcher_multiplier);
double miss_penalty = tessedit_class_miss_scale * feature_misses;
@ -1220,16 +1242,16 @@ double Classify::ComputeCorrectedRating(bool debug, int unichar_id,
vertical_penalty = classify_misfit_junk_penalty;
}
}
double result =cn_corrected + miss_penalty + vertical_penalty;
if (result > WORST_POSSIBLE_RATING)
double result = 1.0 - (cn_corrected + miss_penalty + vertical_penalty);
if (result < WORST_POSSIBLE_RATING)
result = WORST_POSSIBLE_RATING;
if (debug) {
tprintf("%s: %2.1f(CP%2.1f, IM%2.1f + CN%.2f(%d) + MP%2.1f + VP%2.1f)\n",
tprintf("%s: %2.1f%%(CP%2.1f, IM%2.1f + CN%.2f(%d) + MP%2.1f + VP%2.1f)\n",
unicharset.id_to_unichar(unichar_id),
result * 100.0,
cp_rating * 100.0,
im_rating * 100.0,
(cn_corrected - im_rating) * 100.0,
(1.0 - im_rating) * 100.0,
(cn_corrected - (1.0 - im_rating)) * 100.0,
cn_factors[unichar_id],
miss_penalty * 100.0,
vertical_penalty * 100.0);
@ -1268,7 +1290,7 @@ UNICHAR_ID *Classify::BaselineClassifier(
CharNormArray, BaselineCutoffs, &Results->CPResults);
if (matcher_debug_level >= 2 || classify_debug_level > 1)
cprintf ("BL Matches = ");
tprintf("BL Matches = ");
MasterMatcher(Templates->Templates, int_features.size(), &int_features[0],
CharNormArray,
@ -1276,13 +1298,12 @@ UNICHAR_ID *Classify::BaselineClassifier(
Blob->bounding_box(), Results->CPResults, Results);
delete [] CharNormArray;
CLASS_ID ClassId = Results->best_match.unichar_id;
if (ClassId == NO_CLASS)
return (NULL);
/* this is a bug - maybe should return "" */
CLASS_ID ClassId = Results->best_unichar_id;
if (ClassId == INVALID_UNICHAR_ID || Results->best_match_index < 0)
return NULL;
return Templates->Class[ClassId]->
Config[Results->best_match.config].Perm->Ambigs;
Config[Results->match[Results->best_match_index].config].Perm->Ambigs;
} /* BaselineClassifier */
@ -1316,14 +1337,7 @@ int Classify::CharNormClassifier(TBLOB *blob,
-1, &unichar_results);
// Convert results to the format used internally by AdaptiveClassifier.
for (int r = 0; r < unichar_results.size(); ++r) {
int unichar_id = unichar_results[r].unichar_id;
// Fonts are listed in order of preference.
int font1 = unichar_results[r].fonts.size() >= 1
? unichar_results[r].fonts[0] : kBlankFontinfoId;
int font2 = unichar_results[r].fonts.size() >= 2
? unichar_results[r].fonts[1] : kBlankFontinfoId;
float rating = 1.0f - unichar_results[r].rating;
AddNewResult(adapt_results, unichar_id, -1, rating, false, 0, font1, font2);
AddNewResult(unichar_results[r], adapt_results);
}
return sample.num_features();
} /* CharNormClassifier */
@ -1378,14 +1392,7 @@ int Classify::CharNormTrainingSample(bool pruner_only,
blob_box, adapt_results->CPResults, adapt_results);
// Convert master matcher results to output format.
for (int i = 0; i < adapt_results->match.size(); i++) {
ScoredClass next = adapt_results->match[i];
UnicharRating rating(next.unichar_id, 1.0f - next.rating);
if (next.fontinfo_id >= 0) {
rating.fonts.push_back(next.fontinfo_id);
if (next.fontinfo_id2 >= 0)
rating.fonts.push_back(next.fontinfo_id2);
}
results->push_back(rating);
results->push_back(adapt_results->match[i]);
}
results->sort(&UnicharRating::SortDescendingRating);
}
@ -1410,60 +1417,14 @@ int Classify::CharNormTrainingSample(bool pruner_only,
* @note Exceptions: none
* @note History: Tue Mar 12 18:36:52 1991, DSJ, Created.
*/
void Classify::ClassifyAsNoise(ADAPT_RESULTS *Results) {
register FLOAT32 Rating;
void Classify::ClassifyAsNoise(ADAPT_RESULTS *results) {
float rating = results->BlobLength / matcher_avg_noise_size;
rating *= rating;
rating /= 1.0 + rating;
Rating = Results->BlobLength / matcher_avg_noise_size;
Rating *= Rating;
Rating /= 1.0 + Rating;
AddNewResult(Results, NO_CLASS, -1, Rating, false, -1,
kBlankFontinfoId, kBlankFontinfoId);
AddNewResult(UnicharRating(UNICHAR_SPACE, 1.0f - rating), results);
} /* ClassifyAsNoise */
} // namespace tesseract
/*---------------------------------------------------------------------------*/
// Return a pointer to the scored unichar in results, or NULL if not present.
ScoredClass *FindScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id) {
for (int i = 0; i < results->match.size(); i++) {
if (results->match[i].unichar_id == id)
return &results->match[i];
}
return NULL;
}
// Retrieve the current rating for a unichar id if we have rated it, defaulting
// to WORST_POSSIBLE_RATING.
ScoredClass ScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id) {
ScoredClass poor_result =
{id, -1, WORST_POSSIBLE_RATING, false, -1,
kBlankFontinfoId, kBlankFontinfoId};
ScoredClass *entry = FindScoredUnichar(results, id);
return (entry == NULL) ? poor_result : *entry;
}
// Compare character classes by rating as for qsort(3).
// For repeatability, use character class id as a tie-breaker.
int CompareByRating(const void *arg1, // ScoredClass *class1
const void *arg2) { // ScoredClass *class2
const ScoredClass *class1 = (const ScoredClass *)arg1;
const ScoredClass *class2 = (const ScoredClass *)arg2;
if (class1->rating < class2->rating)
return -1;
else if (class1->rating > class2->rating)
return 1;
if (class1->unichar_id < class2->unichar_id)
return -1;
else if (class1->unichar_id > class2->unichar_id)
return 1;
return 0;
}
/*---------------------------------------------------------------------------*/
namespace tesseract {
/// The function converts the given match ratings to the list of blob
/// choices with ratings and certainties (used by the context checkers).
/// If character fragments are present in the results, this function also makes
@ -1494,11 +1455,9 @@ void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
float best_certainty = -MAX_FLOAT32;
for (int i = 0; i < Results->match.size(); i++) {
ScoredClass next = Results->match[i];
int fontinfo_id = next.fontinfo_id;
int fontinfo_id2 = next.fontinfo_id2;
bool adapted = next.adapted;
bool current_is_frag = (unicharset.get_fragment(next.unichar_id) != NULL);
const UnicharRating& result = Results->match[i];
bool adapted = result.adapted;
bool current_is_frag = (unicharset.get_fragment(result.unichar_id) != NULL);
if (temp_it.length()+1 == max_matches &&
!contains_nonfrag && current_is_frag) {
continue; // look for a non-fragmented character to fill the
@ -1512,7 +1471,7 @@ void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
Certainty = -20;
Rating = 100; // should be -certainty * real_blob_length
} else {
Rating = Certainty = next.rating;
Rating = Certainty = (1.0f - result.rating);
Rating *= rating_scale * Results->BlobLength;
Certainty *= -(getDict().certainty_scale);
}
@ -1529,14 +1488,16 @@ void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
}
float min_xheight, max_xheight, yshift;
denorm.XHeightRange(next.unichar_id, unicharset, box,
denorm.XHeightRange(result.unichar_id, unicharset, box,
&min_xheight, &max_xheight, &yshift);
temp_it.add_to_end(new BLOB_CHOICE(next.unichar_id, Rating, Certainty,
fontinfo_id, fontinfo_id2,
unicharset.get_script(next.unichar_id),
min_xheight, max_xheight, yshift,
adapted ? BCC_ADAPTED_CLASSIFIER
: BCC_STATIC_CLASSIFIER));
BLOB_CHOICE* choice =
new BLOB_CHOICE(result.unichar_id, Rating, Certainty,
unicharset.get_script(result.unichar_id),
min_xheight, max_xheight, yshift,
adapted ? BCC_ADAPTED_CLASSIFIER
: BCC_STATIC_CLASSIFIER);
choice->set_fonts(result.fonts);
temp_it.add_to_end(choice);
contains_nonfrag |= !current_is_frag; // update contains_nonfrag
choices_length++;
if (choices_length >= max_matches) break;
@ -1560,17 +1521,13 @@ void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
void Classify::DebugAdaptiveClassifier(TBLOB *blob,
ADAPT_RESULTS *Results) {
if (static_classifier_ == NULL) return;
for (int i = 0; i < Results->match.size(); i++) {
if (i == 0 || Results->match[i].rating < Results->best_match.rating)
Results->best_match = Results->match[i];
}
INT_FX_RESULT_STRUCT fx_info;
GenericVector<INT_FEATURE_STRUCT> bl_features;
TrainingSample* sample =
BlobToTrainingSample(*blob, false, &fx_info, &bl_features);
if (sample == NULL) return;
static_classifier_->DebugDisplay(*sample, blob->denorm().pix(),
Results->best_match.unichar_id);
Results->best_unichar_id);
} /* DebugAdaptiveClassifier */
#endif
@ -1613,7 +1570,8 @@ void Classify::DoAdaptiveMatch(TBLOB *Blob, ADAPT_RESULTS *Results) {
} else {
Ambiguities = BaselineClassifier(Blob, bl_features, fx_info,
AdaptedTemplates, Results);
if ((!Results->match.empty() && MarginalMatch(Results->best_match.rating) &&
if ((!Results->match.empty() &&
MarginalMatch(Results->best_rating, matcher_great_threshold) &&
!tess_bn_matching) ||
Results->match.empty()) {
CharNormClassifier(Blob, *sample, Results);
@ -1672,7 +1630,7 @@ UNICHAR_ID *Classify::GetAmbiguities(TBLOB *Blob,
CharNormClassifier(Blob, *sample, Results);
delete sample;
RemoveBadMatches(Results);
Results->match.sort(CompareByRating);
Results->match.sort(&UnicharRating::SortDescendingRating);
/* copy the class id's into an string of ambiguities - don't copy if
the correct class is the only class id matched */
@ -2092,14 +2050,11 @@ namespace tesseract {
* @note Exceptions: none
* @note History: Mon Mar 18 09:24:53 1991, DSJ, Created.
*/
void Classify::PrintAdaptiveMatchResults(FILE *File, ADAPT_RESULTS *Results) {
for (int i = 0; i < Results->match.size(); ++i) {
tprintf("%s(%d), shape %d, %.2f ",
unicharset.debug_str(Results->match[i].unichar_id).string(),
Results->match[i].unichar_id, Results->match[i].shape_id,
Results->match[i].rating * 100.0);
void Classify::PrintAdaptiveMatchResults(const ADAPT_RESULTS& results) {
for (int i = 0; i < results.match.size(); ++i) {
tprintf("%s ", unicharset.debug_str(results.match[i].unichar_id).string());
results.match[i].Print();
}
tprintf("\n");
} /* PrintAdaptiveMatchResults */
/*---------------------------------------------------------------------------*/
@ -2122,40 +2077,49 @@ void Classify::RemoveBadMatches(ADAPT_RESULTS *Results) {
int Next, NextGood;
FLOAT32 BadMatchThreshold;
static const char* romans = "i v x I V X";
BadMatchThreshold = Results->best_match.rating + matcher_bad_match_pad;
BadMatchThreshold = Results->best_rating - matcher_bad_match_pad;
if (classify_bln_numeric_mode) {
UNICHAR_ID unichar_id_one = unicharset.contains_unichar("1") ?
unicharset.unichar_to_id("1") : -1;
UNICHAR_ID unichar_id_zero = unicharset.contains_unichar("0") ?
unicharset.unichar_to_id("0") : -1;
ScoredClass scored_one = ScoredUnichar(Results, unichar_id_one);
ScoredClass scored_zero = ScoredUnichar(Results, unichar_id_zero);
float scored_one = ScoredUnichar(unichar_id_one, *Results);
float scored_zero = ScoredUnichar(unichar_id_zero, *Results);
for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
if (Results->match[Next].rating <= BadMatchThreshold) {
ScoredClass match = Results->match[Next];
const UnicharRating& match = Results->match[Next];
if (match.rating >= BadMatchThreshold) {
if (!unicharset.get_isalpha(match.unichar_id) ||
strstr(romans,
unicharset.id_to_unichar(match.unichar_id)) != NULL) {
Results->match[NextGood++] = Results->match[Next];
} else if (unicharset.eq(match.unichar_id, "l") &&
scored_one.rating >= BadMatchThreshold) {
Results->match[NextGood] = scored_one;
Results->match[NextGood].rating = match.rating;
NextGood++;
scored_one < BadMatchThreshold) {
Results->match[Next].unichar_id = unichar_id_one;
} else if (unicharset.eq(match.unichar_id, "O") &&
scored_zero.rating >= BadMatchThreshold) {
Results->match[NextGood] = scored_zero;
Results->match[NextGood].rating = match.rating;
NextGood++;
scored_zero < BadMatchThreshold) {
Results->match[Next].unichar_id = unichar_id_zero;
} else {
Results->match[Next].unichar_id = INVALID_UNICHAR_ID; // Don't copy.
}
if (Results->match[Next].unichar_id != INVALID_UNICHAR_ID) {
if (NextGood == Next) {
++NextGood;
} else {
Results->match[NextGood++] = Results->match[Next];
}
}
}
}
} else {
for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
if (Results->match[Next].rating <= BadMatchThreshold)
Results->match[NextGood++] = Results->match[Next];
if (Results->match[Next].rating >= BadMatchThreshold) {
if (NextGood == Next) {
++NextGood;
} else {
Results->match[NextGood++] = Results->match[Next];
}
}
}
}
Results->match.truncate(NextGood);
@ -2182,18 +2146,24 @@ void Classify::RemoveExtraPuncs(ADAPT_RESULTS *Results) {
punc_count = 0;
digit_count = 0;
for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
ScoredClass match = Results->match[Next];
const UnicharRating& match = Results->match[Next];
bool keep = true;
if (strstr(punc_chars,
unicharset.id_to_unichar(match.unichar_id)) != NULL) {
if (punc_count < 2)
Results->match[NextGood++] = match;
if (punc_count >= 2)
keep = false;
punc_count++;
} else {
if (strstr(digit_chars,
unicharset.id_to_unichar(match.unichar_id)) != NULL) {
if (digit_count < 1)
Results->match[NextGood++] = match;
if (digit_count >= 1)
keep = false;
digit_count++;
}
}
if (keep) {
if (NextGood == Next) {
++NextGood;
} else {
Results->match[NextGood++] = match;
}
@ -2250,7 +2220,7 @@ void Classify::ShowBestMatchFor(int shape_id,
tprintf("Illegal blob (char norm features)!\n");
return;
}
INT_RESULT_STRUCT cn_result;
UnicharRating cn_result;
classify_norm_method.set_value(character);
im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
AllProtosOn, AllConfigsOn,
@ -2258,7 +2228,7 @@ void Classify::ShowBestMatchFor(int shape_id,
classify_adapt_feature_threshold, NO_DEBUG,
matcher_debug_separate_windows);
tprintf("\n");
config_mask = 1 << cn_result.Config;
config_mask = 1 << cn_result.config;
tprintf("Static Shape ID: %d\n", shape_id);
ShowMatchDisplay();

View File

@ -217,7 +217,7 @@ void Classify::AddLargeSpeckleTo(int blob_length, BLOB_CHOICE_LIST *choices) {
(rating_scale * blob_length);
}
BLOB_CHOICE* blob_choice = new BLOB_CHOICE(UNICHAR_SPACE, rating, certainty,
-1, -1, 0, 0, MAX_FLOAT32, 0,
-1, 0.0f, MAX_FLOAT32, 0,
BCC_SPECKLE_CLASSIFIER);
bc_it.add_to_end(blob_choice);
}

View File

@ -175,7 +175,7 @@ class Classify : public CCStruct {
int blob_length,
int matcher_multiplier,
const uinT8* cn_factors,
INT_RESULT_STRUCT& int_result,
UnicharRating* int_result,
ADAPT_RESULTS* final_results);
// Applies a set of corrections to the distance im_rating,
// including the cn_correction, miss penalty and additional penalty
@ -188,14 +188,7 @@ class Classify : public CCStruct {
void ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
ADAPT_RESULTS *Results,
BLOB_CHOICE_LIST *Choices);
void AddNewResult(ADAPT_RESULTS *results,
CLASS_ID class_id,
int shape_id,
FLOAT32 rating,
bool adapted,
int config,
int fontinfo_id,
int fontinfo_id2);
void AddNewResult(const UnicharRating& new_result, ADAPT_RESULTS *results);
int GetAdaptiveFeatures(TBLOB *Blob,
INT_FEATURE_ARRAY IntFeatures,
FEATURE_SET *FloatFeatures);
@ -220,7 +213,7 @@ class Classify : public CCStruct {
CLASS_ID ClassId,
int ConfigId,
TBLOB *Blob);
void PrintAdaptiveMatchResults(FILE *File, ADAPT_RESULTS *Results);
void PrintAdaptiveMatchResults(const ADAPT_RESULTS& results);
void RemoveExtraPuncs(ADAPT_RESULTS *Results);
void RemoveBadMatches(ADAPT_RESULTS *Results);
void SetAdaptiveThreshold(FLOAT32 Threshold);

View File

@ -26,6 +26,8 @@
Include Files and Type Defines
----------------------------------------------------------------------------*/
#include "intmatcher.h"
#include "fontinfo.h"
#include "intproto.h"
#include "callcpp.h"
#include "scrollview.h"
@ -36,6 +38,9 @@
#include "shapetable.h"
#include <math.h>
using tesseract::ScoredFont;
using tesseract::UnicharRating;
/*----------------------------------------------------------------------------
Global Data Definitions and Declarations
----------------------------------------------------------------------------*/
@ -464,7 +469,7 @@ void IntegerMatcher::Match(INT_CLASS ClassTemplate,
BIT_VECTOR ConfigMask,
inT16 NumFeatures,
const INT_FEATURE_STRUCT* Features,
INT_RESULT Result,
UnicharRating* Result,
int AdaptFeatureThreshold,
int Debug,
bool SeparateDebugWindows) {
@ -477,7 +482,7 @@ void IntegerMatcher::Match(INT_CLASS ClassTemplate,
** NormalizationFactor Fudge factor from blob
** normalization process
** Result Class rating & configuration:
** (0.0 -> 1.0), 0=good, 1=bad
** (0.0 -> 1.0), 0=bad, 1=good
** Debug Debugger flag: 1=debugger on
** Globals:
** local_matcher_multiplier_ Normalization factor multiplier
@ -498,7 +503,7 @@ void IntegerMatcher::Match(INT_CLASS ClassTemplate,
cprintf ("Integer Matcher -------------------------------------------\n");
tables->Clear(ClassTemplate);
Result->FeatureMisses = 0;
Result->feature_misses = 0;
for (Feature = 0; Feature < NumFeatures; Feature++) {
int csum = UpdateTablesForFeature(ClassTemplate, ProtoMask, ConfigMask,
@ -506,7 +511,7 @@ void IntegerMatcher::Match(INT_CLASS ClassTemplate,
tables, Debug);
// Count features that were missed over all configs.
if (csum == 0)
Result->FeatureMisses++;
++Result->feature_misses;
}
#ifndef GRAPHICS_DISABLED
@ -534,7 +539,7 @@ void IntegerMatcher::Match(INT_CLASS ClassTemplate,
#ifndef GRAPHICS_DISABLED
if (PrintMatchSummaryOn(Debug))
DebugBestMatch(BestMatch, Result);
Result->Print();
if (MatchDebuggingOn(Debug))
cprintf("Match Complete --------------------------------------------\n");
@ -1222,9 +1227,9 @@ void ScratchEvidence::NormalizeSums(
/*---------------------------------------------------------------------------*/
int IntegerMatcher::FindBestMatch(
INT_CLASS ClassTemplate,
INT_CLASS class_template,
const ScratchEvidence &tables,
INT_RESULT Result) {
UnicharRating* result) {
/*
** Parameters:
** Globals:
@ -1236,35 +1241,27 @@ int IntegerMatcher::FindBestMatch(
** Exceptions: none
** History: Wed Feb 27 14:12:28 MST 1991, RWM, Created.
*/
int BestMatch = 0;
int Best2Match = 0;
Result->Config = 0;
Result->Config2 = 0;
int best_match = 0;
result->config = 0;
result->fonts.truncate(0);
result->fonts.reserve(class_template->NumConfigs);
/* Find best match */
for (int ConfigNum = 0; ConfigNum < ClassTemplate->NumConfigs; ConfigNum++) {
int rating = tables.sum_feature_evidence_[ConfigNum];
for (int c = 0; c < class_template->NumConfigs; ++c) {
int rating = tables.sum_feature_evidence_[c];
if (*classify_debug_level_ > 2)
cprintf("Config %d, rating=%d\n", ConfigNum, rating);
if (rating > BestMatch) {
if (BestMatch > 0) {
Result->Config2 = Result->Config;
Best2Match = BestMatch;
} else {
Result->Config2 = ConfigNum;
}
Result->Config = ConfigNum;
BestMatch = rating;
} else if (rating > Best2Match) {
Result->Config2 = ConfigNum;
Best2Match = rating;
tprintf("Config %d, rating=%d\n", c, rating);
if (rating > best_match) {
result->config = c;
best_match = rating;
}
result->fonts.push_back(ScoredFont(c, rating));
}
/* Compute Certainty Rating */
Result->Rating = (65536.0 - BestMatch) / 65536.0;
// Compute confidence on a Probability scale.
result->rating = best_match / 65536.0f;
return BestMatch;
return best_match;
}
// Applies the CN normalization factor to the given rating and returns

View File

@ -38,25 +38,14 @@ extern INT_VAR_H(classify_integer_matcher_multiplier, 10,
#include "intproto.h"
#include "cutoffs.h"
struct INT_RESULT_STRUCT {
INT_RESULT_STRUCT() : Rating(0.0f), Config(0), Config2(0), FeatureMisses(0) {}
FLOAT32 Rating;
// TODO(rays) It might be desirable for these to be able to represent a
// null config.
uinT8 Config;
uinT8 Config2;
uinT16 FeatureMisses;
};
typedef INT_RESULT_STRUCT *INT_RESULT;
namespace tesseract {
class UnicharRating;
}
struct CP_RESULT_STRUCT {
CP_RESULT_STRUCT() : Rating(0.0f), Class(0) {}
FLOAT32 Rating;
INT_RESULT_STRUCT IMResult;
CLASS_ID Class;
};
@ -113,7 +102,7 @@ class IntegerMatcher {
BIT_VECTOR ConfigMask,
inT16 NumFeatures,
const INT_FEATURE_STRUCT* Features,
INT_RESULT Result,
tesseract::UnicharRating* Result,
int AdaptFeatureThreshold,
int Debug,
bool SeparateDebugWindows);
@ -155,7 +144,7 @@ class IntegerMatcher {
int FindBestMatch(INT_CLASS ClassTemplate,
const ScratchEvidence &tables,
INT_RESULT Result);
tesseract::UnicharRating* Result);
#ifndef GRAPHICS_DISABLED
void DebugFeatureProtoError(
@ -182,8 +171,6 @@ class IntegerMatcher {
int AdaptFeatureThreshold,
int Debug,
bool SeparateDebugWindows);
void DebugBestMatch(int BestMatch, INT_RESULT Result);
#endif

View File

@ -710,7 +710,11 @@ void ShapeTable::AddShapeToResults(const ShapeRating& shape_rating,
int result_index = AddUnicharToResults(shape[u].unichar_id,
shape_rating.rating,
unichar_map, results);
(*results)[result_index].fonts += shape[u].font_ids;
for (int f = 0; f < shape[u].font_ids.size(); ++f) {
(*results)[result_index].fonts.push_back(
ScoredFont(shape[u].font_ids[f],
IntCastRounded(shape_rating.rating * MAX_INT16)));
}
}
}

View File

@ -24,6 +24,7 @@
#define TESSERACT_CLASSIFY_SHAPETABLE_H_
#include "bitvector.h"
#include "fontinfo.h"
#include "genericheap.h"
#include "genericvector.h"
#include "intmatcher.h"
@ -33,16 +34,23 @@ class UNICHARSET;
namespace tesseract {
struct FontInfo;
class FontInfoTable;
class ShapeTable;
// Simple struct to hold a single classifier unichar selection, a corresponding
// rating, and a list of appropriate fonts.
struct UnicharRating {
UnicharRating() : unichar_id(0), rating(0.0f) {}
UnicharRating()
: unichar_id(0), rating(0.0f), adapted(false), config(0),
feature_misses(0) {}
UnicharRating(int u, float r)
: unichar_id(u), rating(r) {}
: unichar_id(u), rating(r), adapted(false), config(0), feature_misses(0) {}
// Print debug info.
void Print() const {
tprintf("Unichar-id=%d, rating=%g, adapted=%d, config=%d, misses=%d,"
" %d fonts\n", unichar_id, rating, adapted, config, feature_misses,
fonts.size());
}
// Sort function to sort ratings appropriately by descending rating.
static int SortDescendingRating(const void* t1, const void* t2) {
@ -68,9 +76,16 @@ struct UnicharRating {
// Rating from classifier with 1.0 perfect and 0.0 impossible.
// Call it a probability if you must.
float rating;
// Set of fonts for this shape in order of decreasing preference.
// (There is no mechanism for storing scores for fonts as yet.)
GenericVector<int> fonts;
// True if this result is from the adaptive classifier.
bool adapted;
// Index of best matching font configuration of result.
uinT8 config;
// Number of features that were total misses - were liked by no classes.
uinT16 feature_misses;
// Unsorted collection of fontinfo ids and scores. Note that a raw result
// from the IntegerMatch will contain config ids, that require transforming
// to fontinfo ids via fontsets and (possibly) shapetable.
GenericVector<ScoredFont> fonts;
};
// Classifier result from a low-level classification is an index into some

View File

@ -192,8 +192,7 @@ bool Dict::NoDangerousAmbig(WERD_CHOICE *best_choice,
BLOB_CHOICE_IT lst_it(lst);
// TODO(rays/antonova) Put real xheights and y shifts here.
lst_it.add_to_end(new BLOB_CHOICE(best_choice->unichar_id(i),
0.0, 0.0, -1, -1, -1, 0, 1, 0,
BCC_AMBIG));
0.0, 0.0, -1, 0, 1, 0, BCC_AMBIG));
ambig_blob_choices.push_back(lst);
}
}
@ -278,7 +277,7 @@ bool Dict::NoDangerousAmbig(WERD_CHOICE *best_choice,
BLOB_CHOICE_IT bc_it(ambig_blob_choices[i+tmp_index]);
bc_it.add_to_end(new BLOB_CHOICE(
ambig_spec->correct_fragments[tmp_index], -1.0, 0.0,
-1, -1, -1, 0, 1, 0, BCC_AMBIG));
-1, 0, 1, 0, BCC_AMBIG));
}
}
spec_it.forward();

View File

@ -40,6 +40,8 @@
#include "config_auto.h"
#endif
using tesseract::ScoredFont;
/*----------------------------------------------------------------------
F u n c t i o n s
----------------------------------------------------------------------*/
@ -194,8 +196,8 @@ void Wordrec::merge_and_put_fragment_lists(inT16 row, inT16 column,
if (same_unichar) {
// Add the merged character to the result
UNICHAR_ID merged_unichar_id = first_unichar_id;
inT16 merged_fontinfo_id = choice_lists_it[0].data()->fontinfo_id();
inT16 merged_fontinfo_id2 = choice_lists_it[0].data()->fontinfo_id2();
GenericVector<ScoredFont> merged_fonts =
choice_lists_it[0].data()->fonts();
float merged_min_xheight = choice_lists_it[0].data()->min_xheight();
float merged_max_xheight = choice_lists_it[0].data()->max_xheight();
float positive_yshift = 0, negative_yshift = 0;
@ -220,21 +222,36 @@ void Wordrec::merge_and_put_fragment_lists(inT16 row, inT16 column,
float yshift = choice_lists_it[i].data()->yshift();
if (yshift > positive_yshift) positive_yshift = yshift;
if (yshift < negative_yshift) negative_yshift = yshift;
// Use the min font rating over the parts.
// TODO(rays) font lists are unsorted. Need to be faster?
const GenericVector<ScoredFont>& frag_fonts =
choice_lists_it[i].data()->fonts();
for (int f = 0; f < frag_fonts.size(); ++f) {
int merged_f = 0;
for (merged_f = 0; merged_f < merged_fonts.size() &&
merged_fonts[merged_f].fontinfo_id != frag_fonts[f].fontinfo_id;
++merged_f) {}
if (merged_f == merged_fonts.size()) {
merged_fonts.push_back(frag_fonts[f]);
} else if (merged_fonts[merged_f].score > frag_fonts[f].score) {
merged_fonts[merged_f].score = frag_fonts[f].score;
}
}
}
float merged_yshift = positive_yshift != 0
? (negative_yshift != 0 ? 0 : positive_yshift)
: negative_yshift;
merged_choice_it.add_to_end(new BLOB_CHOICE(merged_unichar_id,
merged_rating,
merged_certainty,
merged_fontinfo_id,
merged_fontinfo_id2,
merged_script_id,
merged_min_xheight,
merged_max_xheight,
merged_yshift,
classifier));
BLOB_CHOICE* choice = new BLOB_CHOICE(merged_unichar_id,
merged_rating,
merged_certainty,
merged_script_id,
merged_min_xheight,
merged_max_xheight,
merged_yshift,
classifier);
choice->set_fonts(merged_fonts);
merged_choice_it.add_to_end(choice);
}
}