| /********************************************************************** |
| * File: charsample.cpp (Formerly charsample.c) |
| * Description: Class to contain character samples and match scores |
| * to be used for adaption |
| * Author: Chris Newton |
| * Created: Thu Oct 7 13:40:37 BST 1993 |
| * |
| * (C) Copyright 1993, Hewlett-Packard Ltd. |
| ** Licensed under the Apache License, Version 2.0 (the "License"); |
| ** you may not use this file except in compliance with the License. |
| ** You may obtain a copy of the License at |
| ** http://www.apache.org/licenses/LICENSE-2.0 |
| ** Unless required by applicable law or agreed to in writing, software |
| ** distributed under the License is distributed on an "AS IS" BASIS, |
| ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| ** See the License for the specific language governing permissions and |
| ** limitations under the License. |
| * |
| **********************************************************************/ |
| |
| #include "mfcpch.h" |
| #include <stdio.h> |
| #include <ctype.h> |
| #include <math.h> |
| #ifdef __UNIX__ |
| #include <assert.h> |
| #include <unistd.h> |
| #endif |
| #include "memry.h" |
| #include "tessvars.h" |
| #include "statistc.h" |
| #include "charsample.h" |
| #include "paircmp.h" |
| #include "matmatch.h" |
| #include "adaptions.h" |
| #include "secname.h" |
| #include "notdll.h" |
| #include "tesseractclass.h" |
| |
| extern inT32 demo_word; // Hack for demos |
| |
| ELISTIZE (CHAR_SAMPLE) ELISTIZE (CHAR_SAMPLES) CHAR_SAMPLE::CHAR_SAMPLE () { |
| sample_blob = NULL; |
| sample_denorm = NULL; |
| sample_image = NULL; |
| ch = '\0'; |
| n_samples_matched = 0; |
| total_match_scores = 0.0; |
| sumsq_match_scores = 0.0; |
| } |
| |
| |
| CHAR_SAMPLE::CHAR_SAMPLE(PBLOB *blob, DENORM *denorm, char c) { |
| sample_blob = blob; |
| sample_denorm = denorm; |
| sample_image = NULL; |
| ch = c; |
| n_samples_matched = 0; |
| total_match_scores = 0.0; |
| sumsq_match_scores = 0.0; |
| } |
| |
| |
| CHAR_SAMPLE::CHAR_SAMPLE(IMAGE *image, char c) { |
| sample_blob = NULL; |
| sample_denorm = NULL; |
| sample_image = image; |
| ch = c; |
| n_samples_matched = 0; |
| total_match_scores = 0.0; |
| sumsq_match_scores = 0.0; |
| } |
| |
| |
| float CHAR_SAMPLE::match_sample( // Update match scores |
| CHAR_SAMPLE *test_sample, |
| BOOL8 updating, |
| tesseract::Tesseract* tess) { |
| float score1; |
| float score2; |
| IMAGE *image = test_sample->image (); |
| |
| if (sample_blob != NULL && test_sample->blob () != NULL) { |
| PBLOB *blob = test_sample->blob (); |
| DENORM *denorm = test_sample->denorm (); |
| |
| score1 = tess->compare_bln_blobs (sample_blob, sample_denorm, blob, denorm); |
| score2 = tess->compare_bln_blobs (blob, denorm, sample_blob, sample_denorm); |
| |
| score1 = (score1 > score2) ? score1 : score2; |
| } |
| else if (sample_image != NULL && image != NULL) { |
| CHAR_PROTO *sample = new CHAR_PROTO (this); |
| |
| score1 = matrix_match (sample_image, image); |
| delete sample; |
| } |
| else |
| return BAD_SCORE; |
| |
| if ((tessedit_use_best_sample || tessedit_cluster_debug) && updating) { |
| n_samples_matched++; |
| total_match_scores += score1; |
| sumsq_match_scores += score1 * score1; |
| } |
| return score1; |
| } |
| |
| |
| double CHAR_SAMPLE::mean_score() { |
| if (n_samples_matched > 0) |
| return (total_match_scores / n_samples_matched); |
| else |
| return BAD_SCORE; |
| } |
| |
| |
| double CHAR_SAMPLE::variance() { |
| double mean = mean_score (); |
| |
| if (n_samples_matched > 0) { |
| return (sumsq_match_scores / n_samples_matched) - mean * mean; |
| } |
| else |
| return BAD_SCORE; |
| } |
| |
| |
| void CHAR_SAMPLE::print(FILE *f) { |
| if (!tessedit_cluster_debug) |
| return; |
| |
| if (n_samples_matched > 0) |
| fprintf (f, |
| "%c - sample matched against " INT32FORMAT |
| " blobs, mean: %f, var: %f\n", ch, n_samples_matched, |
| mean_score (), variance ()); |
| else |
| fprintf (f, "No matches for this sample (%c)\n", ch); |
| } |
| |
| |
| void CHAR_SAMPLE::reset_match_statistics() { |
| n_samples_matched = 0; |
| total_match_scores = 0.0; |
| sumsq_match_scores = 0.0; |
| } |
| |
| |
| CHAR_SAMPLES::CHAR_SAMPLES() { |
| type = UNKNOWN; |
| samples.clear (); |
| ch = '\0'; |
| best_sample = NULL; |
| proto = NULL; |
| } |
| |
| |
| CHAR_SAMPLES::CHAR_SAMPLES(CHAR_SAMPLE *sample) { |
| CHAR_SAMPLE_IT sample_it = &samples; |
| |
| ASSERT_HOST (sample->image () != NULL || sample->blob () != NULL); |
| |
| if (sample->image () != NULL) |
| type = IMAGE_CLUSTER; |
| else if (sample->blob () != NULL) |
| type = BLOB_CLUSTER; |
| |
| samples.clear (); |
| sample_it.add_to_end (sample); |
| if (tessedit_mm_only_match_same_char) |
| ch = sample->character (); |
| else |
| ch = '\0'; |
| best_sample = NULL; |
| proto = NULL; |
| } |
| |
| |
| void CHAR_SAMPLES::add_sample(CHAR_SAMPLE *sample, tesseract::Tesseract* tess) { |
| CHAR_SAMPLE_IT sample_it = &samples; |
| |
| if (tessedit_use_best_sample || tessedit_cluster_debug) |
| for (sample_it.mark_cycle_pt (); |
| !sample_it.cycled_list (); sample_it.forward ()) { |
| sample_it.data ()->match_sample (sample, TRUE, tess); |
| sample->match_sample (sample_it.data (), TRUE, tess); |
| } |
| |
| sample_it.add_to_end (sample); |
| |
| if (tessedit_mm_use_prototypes && type == IMAGE_CLUSTER) { |
| if (samples.length () == tessedit_mm_prototype_min_size) |
| this->build_prototype (); |
| else if (samples.length () > tessedit_mm_prototype_min_size) |
| this->add_sample_to_prototype (sample); |
| } |
| } |
| |
| |
| void CHAR_SAMPLES::add_sample_to_prototype(CHAR_SAMPLE *sample) { |
| BOOL8 rebuild = FALSE; |
| inT32 new_xsize = proto->x_size (); |
| inT32 new_ysize = proto->y_size (); |
| inT32 sample_xsize = sample->image ()->get_xsize (); |
| inT32 sample_ysize = sample->image ()->get_ysize (); |
| |
| if (sample_xsize > new_xsize) { |
| new_xsize = sample_xsize; |
| rebuild = TRUE; |
| } |
| if (sample_ysize > new_ysize) { |
| new_ysize = sample_ysize; |
| rebuild = TRUE; |
| } |
| |
| if (rebuild) |
| proto->enlarge_prototype (new_xsize, new_ysize); |
| |
| proto->add_sample (sample); |
| } |
| |
| |
| void CHAR_SAMPLES::build_prototype() { |
| CHAR_SAMPLE_IT sample_it = &samples; |
| CHAR_SAMPLE *sample; |
| inT32 proto_xsize = 0; |
| inT32 proto_ysize = 0; |
| |
| if (type != IMAGE_CLUSTER |
| || samples.length () < tessedit_mm_prototype_min_size) |
| return; |
| |
| for (sample_it.mark_cycle_pt (); |
| !sample_it.cycled_list (); sample_it.forward ()) { |
| sample = sample_it.data (); |
| if (sample->image ()->get_xsize () > proto_xsize) |
| proto_xsize = sample->image ()->get_xsize (); |
| if (sample->image ()->get_ysize () > proto_ysize) |
| proto_ysize = sample->image ()->get_ysize (); |
| } |
| |
| proto = new CHAR_PROTO (proto_xsize, proto_ysize, 0, 0, '\0'); |
| |
| for (sample_it.mark_cycle_pt (); |
| !sample_it.cycled_list (); sample_it.forward ()) |
| this->add_sample_to_prototype (sample_it.data ()); |
| |
| } |
| |
| |
| void CHAR_SAMPLES::find_best_sample() { |
| CHAR_SAMPLE_IT sample_it = &samples; |
| double score; |
| double best_score = MAX_INT32; |
| |
| if (ch == '\0' || samples.length () < tessedit_mm_prototype_min_size) |
| return; |
| |
| for (sample_it.mark_cycle_pt (); |
| !sample_it.cycled_list (); sample_it.forward ()) { |
| score = sample_it.data ()->mean_score (); |
| if (score < best_score) { |
| best_score = score; |
| best_sample = sample_it.data (); |
| } |
| } |
| #ifndef SECURE_NAMES |
| if (tessedit_cluster_debug) { |
| tprintf ("Best sample for this %c cluster:\n", ch); |
| best_sample->print (debug_fp); |
| } |
| #endif |
| } |
| |
| |
| float CHAR_SAMPLES::match_score(CHAR_SAMPLE *sample, |
| tesseract::Tesseract* tess) { |
| if (tessedit_mm_only_match_same_char && sample->character () != ch) |
| return BAD_SCORE; |
| |
| if (tessedit_use_best_sample && best_sample != NULL) |
| return best_sample->match_sample (sample, FALSE, tess); |
| else if ((tessedit_mm_use_prototypes |
| || tessedit_mm_adapt_using_prototypes) && proto != NULL) |
| return proto->match_sample (sample); |
| else |
| return this->nn_match_score (sample, tess); |
| } |
| |
| |
| float CHAR_SAMPLES::nn_match_score(CHAR_SAMPLE *sample, |
| tesseract::Tesseract* tess) { |
| CHAR_SAMPLE_IT sample_it = &samples; |
| float score; |
| float min_score = MAX_INT32; |
| |
| for (sample_it.mark_cycle_pt (); |
| !sample_it.cycled_list (); sample_it.forward ()) { |
| score = sample_it.data ()->match_sample (sample, FALSE, tess); |
| if (score < min_score) |
| min_score = score; |
| } |
| |
| return min_score; |
| } |
| |
| |
| void CHAR_SAMPLES::assign_to_char() { |
| STATS char_frequency(FIRST_CHAR, LAST_CHAR); |
| CHAR_SAMPLE_IT sample_it = &samples; |
| inT32 i; |
| inT32 max_index = 0; |
| inT32 max_freq = 0; |
| |
| if (samples.length () == 0 || tessedit_mm_only_match_same_char) |
| return; |
| |
| for (sample_it.mark_cycle_pt (); |
| !sample_it.cycled_list (); sample_it.forward ()) |
| char_frequency.add ((inT32) sample_it.data ()->character (), 1); |
| |
| for (i = FIRST_CHAR; i <= LAST_CHAR; i++) |
| if (char_frequency.pile_count (i) > max_freq) { |
| max_index = i; |
| max_freq = char_frequency.pile_count (i); |
| } |
| |
| if (samples.length () >= tessedit_cluster_min_size |
| && max_freq > samples.length () * tessedit_cluster_accept_fraction) |
| ch = (char) max_index; |
| } |
| |
| |
| void CHAR_SAMPLES::print(FILE *f) { |
| CHAR_SAMPLE_IT sample_it = &samples; |
| |
| fprintf (f, "Collected " INT32FORMAT " samples\n", samples.length ()); |
| |
| #ifndef SECURE_NAMES |
| if (tessedit_cluster_debug) |
| for (sample_it.mark_cycle_pt (); |
| !sample_it.cycled_list (); sample_it.forward ()) |
| sample_it.data ()->print (f); |
| |
| if (ch == '\0') |
| fprintf (f, "\nCluster not used for adaption\n"); |
| else |
| fprintf (f, "\nCluster used to adapt to '%c's\n", ch); |
| #endif |
| } |
| |
| |
| CHAR_PROTO::CHAR_PROTO() { |
| xsize = 0; |
| ysize = 0; |
| ch = '\0'; |
| nsamples = 0; |
| proto_data = NULL; |
| proto = NULL; |
| } |
| |
| |
| CHAR_PROTO::CHAR_PROTO(inT32 x_size, |
| inT32 y_size, |
| inT32 n_samples, |
| float initial_value, |
| char c) { |
| inT32 x; |
| inT32 y; |
| |
| xsize = x_size; |
| ysize = y_size; |
| ch = c; |
| nsamples = n_samples; |
| |
| ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float); |
| |
| for (y = 0; y < ysize; y++) |
| for (x = 0; x < xsize; x++) |
| proto[x][y] = initial_value; |
| } |
| |
| |
| CHAR_PROTO::CHAR_PROTO(CHAR_SAMPLE *sample) { |
| inT32 x; |
| inT32 y; |
| IMAGELINE imline_s; |
| |
| if (sample->image () == NULL) { |
| xsize = 0; |
| ysize = 0; |
| ch = '\0'; |
| nsamples = 0; |
| proto_data = NULL; |
| proto = NULL; |
| } |
| else { |
| ch = sample->character (); |
| xsize = sample->image ()->get_xsize (); |
| ysize = sample->image ()->get_ysize (); |
| nsamples = 1; |
| |
| ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float); |
| |
| for (y = 0; y < ysize; y++) { |
| sample->image ()->fast_get_line (0, y, xsize, &imline_s); |
| for (x = 0; x < xsize; x++) |
| if (imline_s.pixels[x] == BINIM_WHITE) |
| proto[x][y] = 1.0; |
| else |
| proto[x][y] = -1.0; |
| } |
| } |
| } |
| |
| |
| CHAR_PROTO::~CHAR_PROTO () { |
| if (proto_data != NULL) |
| FREE_2D_ARRAY(proto_data, proto); |
| } |
| |
| |
| float CHAR_PROTO::match_sample(CHAR_SAMPLE *test_sample) { |
| CHAR_PROTO *test_proto; |
| float score; |
| |
| if (test_sample->image () != NULL) { |
| test_proto = new CHAR_PROTO (test_sample); |
| if (xsize > test_proto->x_size ()) |
| score = this->match (test_proto); |
| else { |
| demo_word = -demo_word; // Flag different call |
| score = test_proto->match (this); |
| } |
| } |
| else |
| return BAD_SCORE; |
| |
| delete test_proto; |
| |
| return score; |
| } |
| |
| |
| float CHAR_PROTO::match(CHAR_PROTO *test_proto) { |
| inT32 xsize2 = test_proto->x_size (); |
| inT32 y_size; |
| inT32 y_size2; |
| inT32 x_offset; |
| inT32 y_offset; |
| inT32 x; |
| inT32 y; |
| CHAR_PROTO *match_proto; |
| float score; |
| float sum = 0.0; |
| |
| ASSERT_HOST (xsize >= xsize2); |
| |
| x_offset = (xsize - xsize2) / 2; |
| |
| if (ysize < test_proto->y_size ()) { |
| y_size = test_proto->y_size (); |
| y_size2 = ysize; |
| y_offset = (y_size - y_size2) / 2; |
| |
| match_proto = new CHAR_PROTO (xsize, |
| y_size, |
| nsamples * test_proto->n_samples (), |
| 0, '\0'); |
| |
| for (y = 0; y < y_offset; y++) { |
| for (x = 0; x < xsize2; x++) { |
| match_proto->data ()[x + x_offset][y] = |
| test_proto->data ()[x][y] * nsamples; |
| sum += match_proto->data ()[x + x_offset][y]; |
| } |
| } |
| |
| for (y = y_offset + y_size2; y < y_size; y++) { |
| for (x = 0; x < xsize2; x++) { |
| match_proto->data ()[x + x_offset][y] = |
| test_proto->data ()[x][y] * nsamples; |
| sum += match_proto->data ()[x + x_offset][y]; |
| } |
| } |
| |
| for (y = y_offset; y < y_offset + y_size2; y++) { |
| for (x = 0; x < x_offset; x++) { |
| match_proto->data ()[x][y] = proto[x][y - y_offset] * |
| test_proto->n_samples (); |
| sum += match_proto->data ()[x][y]; |
| } |
| |
| for (x = x_offset + xsize2; x < xsize; x++) { |
| match_proto->data ()[x][y] = proto[x][y - y_offset] * |
| test_proto->n_samples (); |
| sum += match_proto->data ()[x][y]; |
| } |
| |
| for (x = x_offset; x < x_offset + xsize2; x++) { |
| match_proto->data ()[x][y] = |
| proto[x][y - y_offset] * test_proto->data ()[x - x_offset][y]; |
| sum += match_proto->data ()[x][y]; |
| } |
| } |
| } |
| else { |
| y_size = ysize; |
| y_size2 = test_proto->y_size (); |
| y_offset = (y_size - y_size2) / 2; |
| |
| match_proto = new CHAR_PROTO (xsize, |
| y_size, |
| nsamples * test_proto->n_samples (), |
| 0, '\0'); |
| |
| for (y = 0; y < y_offset; y++) |
| for (x = 0; x < xsize; x++) { |
| match_proto->data ()[x][y] = |
| proto[x][y] * test_proto->n_samples (); |
| sum += match_proto->data ()[x][y]; |
| } |
| |
| for (y = y_offset + y_size2; y < y_size; y++) |
| for (x = 0; x < xsize; x++) { |
| match_proto->data ()[x][y] = |
| proto[x][y] * test_proto->n_samples (); |
| sum += match_proto->data ()[x][y]; |
| } |
| |
| for (y = y_offset; y < y_offset + y_size2; y++) { |
| for (x = 0; x < x_offset; x++) { |
| match_proto->data ()[x][y] = |
| proto[x][y] * test_proto->n_samples (); |
| sum += match_proto->data ()[x][y]; |
| } |
| |
| for (x = x_offset + xsize2; x < xsize; x++) { |
| match_proto->data ()[x][y] = |
| proto[x][y] * test_proto->n_samples (); |
| sum += match_proto->data ()[x][y]; |
| } |
| |
| for (x = x_offset; x < x_offset + xsize2; x++) { |
| match_proto->data ()[x][y] = proto[x][y] * |
| test_proto->data ()[x - x_offset][y - y_offset]; |
| sum += match_proto->data ()[x][y]; |
| } |
| } |
| } |
| |
| score = (1.0 - sum / |
| (xsize * y_size * nsamples * test_proto->n_samples ())); |
| |
| if (tessedit_mm_debug) { |
| if (score < 0) { |
| tprintf ("Match score %f\n", score); |
| tprintf ("x: %d, y: %d, ns: %d, nt: %d, dx %d, dy: %d\n", |
| xsize, y_size, nsamples, test_proto->n_samples (), |
| x_offset, y_offset); |
| for (y = 0; y < y_size; y++) { |
| tprintf ("\n%d", y); |
| for (x = 0; x < xsize; x++) |
| tprintf ("\t%d", match_proto->data ()[x][y]); |
| |
| } |
| tprintf ("\n"); |
| fflush(debug_fp); |
| } |
| } |
| |
| #ifndef GRAPHICS_DISABLED |
| if (tessedit_display_mm) { |
| tprintf ("Match score %f\n", score); |
| display_images (this->make_image (), |
| test_proto->make_image (), match_proto->make_image ()); |
| } |
| else if (demo_word != 0) { |
| if (demo_word > 0) |
| display_image (test_proto->make_image (), "Test sample", |
| 300, 400, FALSE); |
| else |
| display_image (this->make_image (), "Test sample", 300, 400, FALSE); |
| |
| display_image (match_proto->make_image (), "Best match", |
| 700, 400, TRUE); |
| } |
| #endif |
| |
| delete match_proto; |
| |
| return score; |
| } |
| |
| |
| void CHAR_PROTO::enlarge_prototype(inT32 new_xsize, inT32 new_ysize) { |
| float *old_proto_data = proto_data; |
| float **old_proto = proto; |
| inT32 old_xsize = xsize; |
| inT32 old_ysize = ysize; |
| inT32 x_offset; |
| inT32 y_offset; |
| inT32 x; |
| inT32 y; |
| |
| ASSERT_HOST (new_xsize >= xsize && new_ysize >= ysize); |
| |
| xsize = new_xsize; |
| ysize = new_ysize; |
| ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float); |
| x_offset = (xsize - old_xsize) / 2; |
| y_offset = (ysize - old_ysize) / 2; |
| |
| for (y = 0; y < y_offset; y++) |
| for (x = 0; x < xsize; x++) |
| proto[x][y] = nsamples; |
| |
| for (y = y_offset + old_ysize; y < ysize; y++) |
| for (x = 0; x < xsize; x++) |
| proto[x][y] = nsamples; |
| |
| for (y = y_offset; y < y_offset + old_ysize; y++) { |
| for (x = 0; x < x_offset; x++) |
| proto[x][y] = nsamples; |
| |
| for (x = x_offset + old_xsize; x < xsize; x++) |
| proto[x][y] = nsamples; |
| |
| for (x = x_offset; x < x_offset + old_xsize; x++) |
| proto[x][y] = old_proto[x - x_offset][y - y_offset]; |
| } |
| |
| FREE_2D_ARRAY(old_proto_data, old_proto); |
| } |
| |
| |
| void CHAR_PROTO::add_sample(CHAR_SAMPLE *sample) { |
| inT32 x_offset; |
| inT32 y_offset; |
| inT32 x; |
| inT32 y; |
| IMAGELINE imline_s; |
| inT32 sample_xsize = sample->image ()->get_xsize (); |
| inT32 sample_ysize = sample->image ()->get_ysize (); |
| |
| x_offset = (xsize - sample_xsize) / 2; |
| y_offset = (ysize - sample_ysize) / 2; |
| |
| ASSERT_HOST (x_offset >= 0 && y_offset >= 0); |
| |
| for (y = 0; y < y_offset; y++) |
| for (x = 0; x < xsize; x++) |
| proto[x][y]++; // Treat pixels outside the |
| // range as white |
| for (y = y_offset + sample_ysize; y < ysize; y++) |
| for (x = 0; x < xsize; x++) |
| proto[x][y]++; |
| |
| for (y = y_offset; y < y_offset + sample_ysize; y++) { |
| sample->image ()->fast_get_line (0, |
| y - y_offset, sample_xsize, &imline_s); |
| for (x = x_offset; x < x_offset + sample_xsize; x++) { |
| if (imline_s.pixels[x - x_offset] == BINIM_WHITE) |
| proto[x][y]++; |
| else |
| proto[x][y]--; |
| } |
| |
| for (x = 0; x < x_offset; x++) |
| proto[x][y]++; |
| |
| for (x = x_offset + sample_xsize; x < xsize; x++) |
| proto[x][y]++; |
| } |
| |
| nsamples++; |
| } |
| |
| |
| IMAGE *CHAR_PROTO::make_image() { |
| IMAGE *image; |
| IMAGELINE imline_p; |
| inT32 x; |
| inT32 y; |
| |
| ASSERT_HOST (nsamples != 0); |
| |
| image = new (IMAGE); |
| image->create (xsize, ysize, 8); |
| |
| for (y = 0; y < ysize; y++) { |
| image->fast_get_line (0, y, xsize, &imline_p); |
| |
| for (x = 0; x < xsize; x++) { |
| imline_p.pixels[x] = 128 + |
| (uinT8) ((proto[x][y] * 128.0) / (0.00001 + nsamples)); |
| } |
| |
| image->fast_put_line (0, y, xsize, &imline_p); |
| } |
| return image; |
| } |