tesseract/cube/cube_line_object.cpp

256 lines
9.1 KiB
C++

/**********************************************************************
* File: cube_line_object.cpp
* Description: Implementation of the Cube Line Object Class
* Author: Ahmad Abdulkader
* Created: 2007
*
* (C) Copyright 2008, Google Inc.
** 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 <algorithm>
#include "cube_line_object.h"
namespace tesseract {
CubeLineObject::CubeLineObject(CubeRecoContext *cntxt, Pix *pix) {
line_pix_ = pix;
own_pix_ = false;
processed_ = false;
cntxt_ = cntxt;
phrase_cnt_ = 0;
phrases_ = NULL;
}
CubeLineObject::~CubeLineObject() {
if (line_pix_ != NULL && own_pix_ == true) {
pixDestroy(&line_pix_);
line_pix_ = NULL;
}
if (phrases_ != NULL) {
for (int phrase_idx = 0; phrase_idx < phrase_cnt_; phrase_idx++) {
if (phrases_[phrase_idx] != NULL) {
delete phrases_[phrase_idx];
}
}
delete []phrases_;
phrases_ = NULL;
}
}
// Recognize the specified pix as one line returning the recognized
bool CubeLineObject::Process() {
// do nothing if pix had already been processed
if (processed_) {
return true;
}
// validate data
if (line_pix_ == NULL || cntxt_ == NULL) {
return false;
}
// create a CharSamp
CharSamp *char_samp = CubeUtils::CharSampleFromPix(line_pix_, 0, 0,
line_pix_->w,
line_pix_->h);
if (char_samp == NULL) {
return false;
}
// compute connected components.
int con_comp_cnt = 0;
ConComp **con_comps = char_samp->FindConComps(&con_comp_cnt,
cntxt_->Params()->MinConCompSize());
// no longer need char_samp, delete it
delete char_samp;
// no connected components, bail out
if (con_comp_cnt <= 0 || con_comps == NULL) {
return false;
}
// sort connected components based on reading order
bool rtl = (cntxt_->ReadingOrder() == tesseract::CubeRecoContext::R2L);
qsort(con_comps, con_comp_cnt, sizeof(*con_comps), rtl ?
ConComp::Right2LeftComparer : ConComp::Left2RightComparer);
// compute work breaking threshold as a ratio of line height
bool ret_val = false;
int word_break_threshold = ComputeWordBreakThreshold(con_comp_cnt, con_comps,
rtl);
if (word_break_threshold > 0) {
// over-allocate phrases object buffer
phrases_ = new CubeObject *[con_comp_cnt];
if (phrases_ != NULL) {
// create a phrase if the horizontal distance between two consecutive
// concomps is higher than threshold
int start_con_idx = 0;
int current_phrase_limit = rtl ? con_comps[0]->Left() :
con_comps[0]->Right();
for (int con_idx = 1; con_idx <= con_comp_cnt; con_idx++) {
bool create_new_phrase = true;
// if not at the end, compute the distance between two consecutive
// concomps
if (con_idx < con_comp_cnt) {
int dist = 0;
if (cntxt_->ReadingOrder() == tesseract::CubeRecoContext::R2L) {
dist = current_phrase_limit - con_comps[con_idx]->Right();
} else {
dist = con_comps[con_idx]->Left() - current_phrase_limit;
}
create_new_phrase = (dist > word_break_threshold);
}
// create a new phrase
if (create_new_phrase) {
// create a phrase corresponding to a range on components
bool left_most;
bool right_most;
CharSamp *phrase_char_samp =
CharSamp::FromConComps(con_comps, start_con_idx,
con_idx - start_con_idx, NULL,
&left_most, &right_most,
line_pix_->h);
if (phrase_char_samp == NULL) {
break;
}
phrases_[phrase_cnt_] = new CubeObject(cntxt_, phrase_char_samp);
if (phrases_[phrase_cnt_] == NULL) {
delete phrase_char_samp;
break;
}
// set the ownership of the charsamp to the cube object
phrases_[phrase_cnt_]->SetCharSampOwnership(true);
phrase_cnt_++;
// advance the starting index to the current index
start_con_idx = con_idx;
// set the limit of the newly starting phrase (if any)
if (con_idx < con_comp_cnt) {
current_phrase_limit = rtl ? con_comps[con_idx]->Left() :
con_comps[con_idx]->Right();
}
} else {
// update the limit of the current phrase
if (cntxt_->ReadingOrder() == tesseract::CubeRecoContext::R2L) {
current_phrase_limit = MIN(current_phrase_limit,
con_comps[con_idx]->Left());
} else {
current_phrase_limit = MAX(current_phrase_limit,
con_comps[con_idx]->Right());
}
}
}
ret_val = true;
}
}
// clean-up connected comps
for (int con_idx = 0; con_idx < con_comp_cnt; con_idx++) {
delete con_comps[con_idx];
}
delete []con_comps;
// success
processed_ = true;
return ret_val;
}
// Compute the least word breaking threshold that is required to produce a
// valid set of phrases. Phrases are validated using the Aspect ratio
// constraints specified in the language specific Params object
int CubeLineObject::ComputeWordBreakThreshold(int con_comp_cnt,
ConComp **con_comps, bool rtl) {
// initial estimate of word breaking threshold
int word_break_threshold =
static_cast<int>(line_pix_->h * cntxt_->Params()->MaxSpaceHeightRatio());
bool valid = false;
// compute the resulting words and validate each's aspect ratio
do {
// group connected components into words based on breaking threshold
int start_con_idx = 0;
int current_phrase_limit = (rtl ? con_comps[0]->Left() :
con_comps[0]->Right());
int min_x = con_comps[0]->Left();
int max_x = con_comps[0]->Right();
int min_y = con_comps[0]->Top();
int max_y = con_comps[0]->Bottom();
valid = true;
for (int con_idx = 1; con_idx <= con_comp_cnt; con_idx++) {
bool create_new_phrase = true;
// if not at the end, compute the distance between two consecutive
// concomps
if (con_idx < con_comp_cnt) {
int dist = 0;
if (rtl) {
dist = current_phrase_limit - con_comps[con_idx]->Right();
} else {
dist = con_comps[con_idx]->Left() - current_phrase_limit;
}
create_new_phrase = (dist > word_break_threshold);
}
// create a new phrase
if (create_new_phrase) {
// check aspect ratio. Break if invalid
if ((max_x - min_x + 1) >
(cntxt_->Params()->MaxWordAspectRatio() * (max_y - min_y + 1))) {
valid = false;
break;
}
// advance the starting index to the current index
start_con_idx = con_idx;
// set the limit of the newly starting phrase (if any)
if (con_idx < con_comp_cnt) {
current_phrase_limit = rtl ? con_comps[con_idx]->Left() :
con_comps[con_idx]->Right();
// re-init bounding box
min_x = con_comps[con_idx]->Left();
max_x = con_comps[con_idx]->Right();
min_y = con_comps[con_idx]->Top();
max_y = con_comps[con_idx]->Bottom();
}
} else {
// update the limit of the current phrase
if (rtl) {
current_phrase_limit = MIN(current_phrase_limit,
con_comps[con_idx]->Left());
} else {
current_phrase_limit = MAX(current_phrase_limit,
con_comps[con_idx]->Right());
}
// update bounding box
UpdateRange(con_comps[con_idx]->Left(),
con_comps[con_idx]->Right(), &min_x, &max_x);
UpdateRange(con_comps[con_idx]->Top(),
con_comps[con_idx]->Bottom(), &min_y, &max_y);
}
}
// return the breaking threshold if all broken word dimensions are valid
if (valid) {
return word_break_threshold;
}
// decrease the threshold and try again
word_break_threshold--;
} while (!valid && word_break_threshold > 0);
// failed to find a threshold that acheives the target aspect ratio.
// Just use the default threshold
return static_cast<int>(line_pix_->h *
cntxt_->Params()->MaxSpaceHeightRatio());
}
}