mirror of
https://github.com/tesseract-ocr/tesseract.git
synced 2024-11-27 12:49:35 +08:00
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:
parent
0e868ef377
commit
84920b92b3
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user