// -*- C++ -*- // Copyright 2006-2008 Deutsches Forschungszentrum fuer Kuenstliche Intelligenz // or its licensors, as applicable. // // You may not use this file except under the terms of the accompanying license. // // 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. // // Project: OCRopus // File: ocr-ctextline-rast.cc // Purpose: Constrained textline finding using RAST // Responsible: Faisal Shafait (faisal.shafait@dfki.de) // Reviewer: // Primary Repository: // Web Sites: www.iupr.org, www.dfki.de #include "colib.h" #include "imgio.h" #include "imglib.h" #include "ocr-layout-rast.h" using namespace ocropus; using namespace colib; namespace ocropus { void CTextlineRASTBasic::setDefaultParameters() { generation = 0; lsq = 1; epsilon = 5.0; maxsplits = 1; delta = 1.0; adelta = 0.001; min_length = 100; min_gap = 200; min_q = 3.0; min_count = 6; max_results = 1000; use_whitespace = true; splitscale[0] = 1.0; splitscale[1] = 2000.0; splitscale[2] = 1.0; all_params[0] = interval(0,6000); all_params[1] = interval(-0.05,0.05); all_params[2] = interval(0,20); empty_parameters[0] = interval(1,0); empty_parameters[1] = interval(1,0); empty_parameters[2] = interval(1,0); } CTextlineRASTBasic::CTextlineRASTBasic(){ setDefaultParameters(); } CTextlineRASTBasic::TLStateBasic::TLStateBasic() { depth = -(1<<14); quality = 0.0; matches.clear(); splits = 0; splittable = true; } void CTextlineRASTBasic::setMaxSlope(double max_slope){ all_params[1] = interval(-max_slope,max_slope); } void CTextlineRASTBasic::setMaxYintercept(double ymin, double ymax){ all_params[0] = interval(ymin,ymax); } void CTextlineRASTBasic::TLStateBasic::set(CTextlineRASTBasic &line, int depth,Parameters ¶ms, Matches &candidates,int splits) { this->splits = splits; this->splittable = (splitsdepth = depth; this->params = params; rank = -1; matches.clear(); update(line,candidates); } void CTextlineRASTBasic::TLStateBasic::reeval(CTextlineRASTBasic &line) { Matches temp,rest; copy(temp,matches); copy(matches,rest); //temp.swapwith(matches); update(line,temp); } void CTextlineRASTBasic::TLStateBasic::update(CTextlineRASTBasic &line, Matches &candidates) { matches.clear(); quality = 0.0; interval r = params[0]; interval m = params[1]; interval d = params[2]; for(int i = 0;iy+d)) q = interval(1e-3,1e-3); } if(q.hi>0.0) { matches.push(bi); } quality = quality + q; } // check for total length; we can do that because the cboxes // are in sorted order if(matches.length() >= 2) { int start = matches[0]; int end = matches[matches.length()-1]; float length = line.cboxes[end].x1-line.cboxes[start].x0; if(lengthset(*this,0,all_params,all_matches,0); queue.clear(); queue.insert(initial,initial->priority); } void CTextlineRASTBasic::makeSubStates(narray &substates,CState &state) { substates.clear(); Parameters &p = state->params; int mi = -1; double mv = -1e30; for(int i = 0;imv) { mi = i; mv = v; } } Parameters left = state->params.split(mi,0); CState sleft; sleft->set(*this,state->depth+1,left,state->matches,state->splits); substates.push(sleft); Parameters right = state->params.split(mi,1); CState sright; sright->set(*this,state->depth+1,right,state->matches,state->splits); substates.push(sright); } //Assuming horizontal lines with slope in the interval [-0.05, 0.05] int CTextlineRASTBasic::wboxIntersection(CState &top){ Matches &matches = top->matches; if(matches.length() <= 1) return -1; float start = (int) cboxes[matches[0]].xcenter(); float end = (int) cboxes[matches[matches.length()-1]].xcenter(); for(int i = 0; i < wboxes.length(); i++){ interval y = top->params[1]*wboxes[i].x0 + top->params[0]; //If the box cuts the whole parameter interval if( (y.lo >= wboxes[i].y0) && (y.hi <= wboxes[i].y1) ) if( (start < wboxes[i].x0) && (end > wboxes[i].x1) ) return i; } return -1; } void CTextlineRASTBasic::search() { for(int iter = 0;;iter++) { if(results.length() >= max_results) break; if(queue.length()<1) break; CState top; top = queue.extractMax(); if(top->generation != generation) { top->reeval(*this); if(top->quality.himatches.length()priority); continue; } if(use_whitespace){ Matches &matches = top->matches; int index = wboxIntersection(top); if(index >= 0){ Matches leftmatches,rightmatches; for(int i = 0;i wboxes[index].x1) rightmatches.push(matches[i]); } CState sleft,sright; sleft->set(*this,top->depth+1,top->params,leftmatches,top->splits+1); sright->set(*this,top->depth+1,top->params,rightmatches,top->splits+1); queue.insert(sleft,sleft->priority); queue.insert(sright,sright->priority); continue; } } float threshold = min_gap; if(threshold>0){ Matches &matches = top->matches; int mi = -1; float mgap = -1e38; for(int i = 1;ithreshold) { Matches leftmatches,rightmatches; for(int i = 0;iset(*this,top->depth+1,top->params,leftmatches,top->splits+1); sright->set(*this,top->depth+1,top->params,rightmatches,top->splits+1); queue.insert(sleft,sleft->priority); queue.insert(sright,sright->priority); continue; } } if(final(top->quality,top->params)) { pushResult(top); continue; } narray substates; makeSubStates(substates,top); for(int i = 0;iquality.himatches.length()priority); } } } void CTextlineRASTBasic::pushResult(CState &result){ Matches &matches = result->matches; for(int i = 0;i &textlines, rectarray &columns, autodel &charstats){ if(charstats->char_boxes.length() <= 1) return; linestats = make_CharStats(*charstats); for(int i = 0, l = charstats->char_boxes.length(); ichar_boxes[i].fraction_covered_by(columns[j]) > 0.5){ contained=true; break; } } if(!contained) cboxes.push(charstats->char_boxes[i]); } for(int i = 0, l = columns.length(); ireturnLineParam()); } } void CTextlineRASTBasic::extract(narray &textlines, autodel &charstats){ if(charstats->char_boxes.length() <= 1) return; linestats = make_CharStats(*charstats); for(int i = 0, l = charstats->char_boxes.length(); ichar_boxes[i]); } use_whitespace = false; sort_boxes_by_x0(cboxes); prepare(); search(); for(int i = 0, l = results.length(); ireturnLineParam()); } } CTextlineRASTBasic *make_CTextlineRASTBasic(){ return new CTextlineRASTBasic(); } void CTextlineRAST::setDefaultParameters() { generation = 0; all_params[0] = interval(0,6000); all_params[1] = interval(-0.05,0.05); all_params[2] = interval(0,20); empty_parameters[0] = interval(1,0); empty_parameters[1] = interval(1,0); empty_parameters[2] = interval(1,0); lsq = 1; epsilon = 5.0; min_length = 100; min_gap = 200; maxsplits = 1; min_q = 3.0; min_count = 6; delta = 1.0; adelta = 0.001; minoverlap = 0.9; //rejection threshold for the height of a box = tr*xheight min_box_height = 3.1; extend = 0; max_results = 1000; word_gap = 30; //Maximum distance between words min_height = 5; assign_boxes = 1; aggressive = true; use_whitespace = true; splitscale[0] = 1.0; splitscale[1] = 2000.0; splitscale[2] = 1.0; } CTextlineRAST::CTextlineRAST(){ setDefaultParameters(); } void CTextlineRAST::pushResult(CState &result){ Matches &matches = result->matches; for(int i = 0;ireturnLineParam(); TextLine tl = TextLine(tlp); tl.xheight = xheight; //Calculate bounding box of the line tl.bbox = rectangle(); for(int i = 0;i min_box_height * xheight){ used[matches[i]] = false; continue; } tl.bbox.include(cboxes[matches[i]]); } //Assign all char_boxes contained in the line bb to the line rectangle temp = rectangle(tl.bbox); if(assign_boxes){ temp.x0 -= word_gap; temp.x1 += word_gap; temp.y0 -= 3; temp.y1 += 3; for(int i = 0;i minoverlap) ){ tl.bbox.include(cboxes[i]); used[i] = true; } } if(aggressive){ int all_len = cboxes_all.length(); for(int i = 0;iminoverlap){ tl.bbox.include(cboxes_all[i]); used_all[i] = true; } } } } if(extend){ temp = rectangle(tl.bbox); int new_start = 0; int new_end = pagewidth-1; for(int j = 0; j wboxes[j].y0) ) { // if(wbox_intersection(lines[i], wboxes[j])){ if(wboxes[j].x1 <= temp.x0) new_start = (new_start > wboxes[j].x1) ? new_start : wboxes[j].x1; else if(wboxes[j].x0 >= temp.x1) new_end = (new_end < wboxes[j].x0) ? new_end : wboxes[j].x0; } } new_start = max(new_start,temp.x0-min_gap); new_end = min(new_end,temp.x1+min_gap); temp.x0 = (temp.x0 > new_start) ? new_start : temp.x0; temp.x1 = (temp.x1 < new_end ) ? new_end : temp.x1; tl.bbox = rectangle(temp); } result_lines.push(tl); results.push(result); generation++; } void CTextlineRAST::extract(narray &textlines, rectarray &columns, autodel &charstats){ if(charstats->char_boxes.length() <= 1) return; linestats = make_CharStats(*charstats); int dist = int (charstats->word_spacing*1.5); word_gap = (word_gap < dist) ? word_gap : dist; pagewidth = charstats->img_width; pageheight = charstats->img_height; result_lines.clear(); cboxes.clear(); cboxes_all.clear(); for(int i = 0, l = charstats->char_boxes.length(); ichar_boxes[i].fraction_covered_by(columns[j]) > 0.5){ contained=true; break; } } if(!contained) cboxes.push(charstats->char_boxes[i]); } used.resize(cboxes.length()); fill(used,false); for(int i = 0, l = charstats->dot_boxes.length(); idot_boxes[i]); used_all.resize(cboxes_all.length()); fill(used_all,false); for(int i = 0, l = columns.length(); i &textlines, autodel &charstats){ if(charstats->char_boxes.length() <= 1) return; linestats = make_CharStats(*charstats); int dist = int (charstats->word_spacing*1.5); word_gap = (word_gap < dist) ? word_gap : dist; pagewidth = charstats->img_width; pageheight = charstats->img_height; result_lines.clear(); cboxes.clear(); cboxes_all.clear(); for(int i = 0, l = charstats->char_boxes.length(); ichar_boxes[i]); used.resize(cboxes.length()); fill(used,false); for(int i = 0, l = charstats->dot_boxes.length(); idot_boxes[i]); used_all.resize(cboxes_all.length()); fill(used_all,false); use_whitespace = false; sort_boxes_by_x0(cboxes); prepare(); search(); copy(textlines,result_lines); } CTextlineRAST *make_CTextlineRAST() { return new CTextlineRAST(); } }