tesseract
3.03
|
00001 /* -*-C-*- 00002 ******************************************************************************** 00003 * 00004 * File: blobs.c (Formerly blobs.c) 00005 * Description: Blob definition 00006 * Author: Mark Seaman, OCR Technology 00007 * Created: Fri Oct 27 15:39:52 1989 00008 * Modified: Thu Mar 28 15:33:26 1991 (Mark Seaman) marks@hpgrlt 00009 * Language: C 00010 * Package: N/A 00011 * Status: Experimental (Do Not Distribute) 00012 * 00013 * (c) Copyright 1989, Hewlett-Packard Company. 00014 ** Licensed under the Apache License, Version 2.0 (the "License"); 00015 ** you may not use this file except in compliance with the License. 00016 ** You may obtain a copy of the License at 00017 ** http://www.apache.org/licenses/LICENSE-2.0 00018 ** Unless required by applicable law or agreed to in writing, software 00019 ** distributed under the License is distributed on an "AS IS" BASIS, 00020 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00021 ** See the License for the specific language governing permissions and 00022 ** limitations under the License. 00023 * 00024 *********************************************************************************/ 00025 00026 /*---------------------------------------------------------------------- 00027 I n c l u d e s 00028 ----------------------------------------------------------------------*/ 00029 // Include automatically generated configuration file if running autoconf. 00030 #ifdef HAVE_CONFIG_H 00031 #include "config_auto.h" 00032 #endif 00033 00034 #include "blobs.h" 00035 #include "ccstruct.h" 00036 #include "clst.h" 00037 #include "cutil.h" 00038 #include "emalloc.h" 00039 #include "helpers.h" 00040 #include "linlsq.h" 00041 #include "ndminx.h" 00042 #include "normalis.h" 00043 #include "ocrblock.h" 00044 #include "ocrrow.h" 00045 #include "points.h" 00046 #include "polyaprx.h" 00047 #include "structures.h" 00048 #include "werd.h" 00049 00050 using tesseract::CCStruct; 00051 00052 // A Vector representing the "vertical" direction when measuring the 00053 // divisiblity of blobs into multiple blobs just by separating outlines. 00054 // See divisible_blob below for the use. 00055 const TPOINT kDivisibleVerticalUpright(0, 1); 00056 // A vector representing the "vertical" direction for italic text for use 00057 // when separating outlines. Using it actually deteriorates final accuracy, 00058 // so it is only used for ApplyBoxes chopping to get a better segmentation. 00059 const TPOINT kDivisibleVerticalItalic(1, 5); 00060 00061 /*---------------------------------------------------------------------- 00062 F u n c t i o n s 00063 ----------------------------------------------------------------------*/ 00064 00065 CLISTIZE(EDGEPT); 00066 00067 // Consume the circular list of EDGEPTs to make a TESSLINE. 00068 TESSLINE* TESSLINE::BuildFromOutlineList(EDGEPT* outline) { 00069 TESSLINE* result = new TESSLINE; 00070 result->loop = outline; 00071 if (outline->src_outline != NULL) { 00072 // ASSUMPTION: This function is only ever called from ApproximateOutline 00073 // and therefore either all points have a src_outline or all do not. 00074 // Just as SetupFromPos sets the vectors from the vertices, setup the 00075 // step_count members to indicate the (positive) number of original 00076 // C_OUTLINE steps to the next vertex. 00077 EDGEPT* pt = outline; 00078 do { 00079 pt->step_count = pt->next->start_step - pt->start_step; 00080 if (pt->step_count < 0) 00081 pt->step_count += pt->src_outline->pathlength(); 00082 pt = pt->next; 00083 } while (pt != outline); 00084 } 00085 result->SetupFromPos(); 00086 return result; 00087 } 00088 00089 // Copies the data and the outline, but leaves next untouched. 00090 void TESSLINE::CopyFrom(const TESSLINE& src) { 00091 Clear(); 00092 topleft = src.topleft; 00093 botright = src.botright; 00094 start = src.start; 00095 is_hole = src.is_hole; 00096 if (src.loop != NULL) { 00097 EDGEPT* prevpt = NULL; 00098 EDGEPT* newpt = NULL; 00099 EDGEPT* srcpt = src.loop; 00100 do { 00101 newpt = new EDGEPT(*srcpt); 00102 if (prevpt == NULL) { 00103 loop = newpt; 00104 } else { 00105 newpt->prev = prevpt; 00106 prevpt->next = newpt; 00107 } 00108 prevpt = newpt; 00109 srcpt = srcpt->next; 00110 } while (srcpt != src.loop); 00111 loop->prev = newpt; 00112 newpt->next = loop; 00113 } 00114 } 00115 00116 // Deletes owned data. 00117 void TESSLINE::Clear() { 00118 if (loop == NULL) 00119 return; 00120 00121 EDGEPT* this_edge = loop; 00122 do { 00123 EDGEPT* next_edge = this_edge->next; 00124 delete this_edge; 00125 this_edge = next_edge; 00126 } while (this_edge != loop); 00127 loop = NULL; 00128 } 00129 00130 // Normalize in-place using the DENORM. 00131 void TESSLINE::Normalize(const DENORM& denorm) { 00132 EDGEPT* pt = loop; 00133 do { 00134 denorm.LocalNormTransform(pt->pos, &pt->pos); 00135 pt = pt->next; 00136 } while (pt != loop); 00137 SetupFromPos(); 00138 } 00139 00140 // Rotates by the given rotation in place. 00141 void TESSLINE::Rotate(const FCOORD rot) { 00142 EDGEPT* pt = loop; 00143 do { 00144 int tmp = static_cast<int>(floor(pt->pos.x * rot.x() - 00145 pt->pos.y * rot.y() + 0.5)); 00146 pt->pos.y = static_cast<int>(floor(pt->pos.y * rot.x() + 00147 pt->pos.x * rot.y() + 0.5)); 00148 pt->pos.x = tmp; 00149 pt = pt->next; 00150 } while (pt != loop); 00151 SetupFromPos(); 00152 } 00153 00154 // Moves by the given vec in place. 00155 void TESSLINE::Move(const ICOORD vec) { 00156 EDGEPT* pt = loop; 00157 do { 00158 pt->pos.x += vec.x(); 00159 pt->pos.y += vec.y(); 00160 pt = pt->next; 00161 } while (pt != loop); 00162 SetupFromPos(); 00163 } 00164 00165 // Scales by the given factor in place. 00166 void TESSLINE::Scale(float factor) { 00167 EDGEPT* pt = loop; 00168 do { 00169 pt->pos.x = static_cast<int>(floor(pt->pos.x * factor + 0.5)); 00170 pt->pos.y = static_cast<int>(floor(pt->pos.y * factor + 0.5)); 00171 pt = pt->next; 00172 } while (pt != loop); 00173 SetupFromPos(); 00174 } 00175 00176 // Sets up the start and vec members of the loop from the pos members. 00177 void TESSLINE::SetupFromPos() { 00178 EDGEPT* pt = loop; 00179 do { 00180 pt->vec.x = pt->next->pos.x - pt->pos.x; 00181 pt->vec.y = pt->next->pos.y - pt->pos.y; 00182 pt = pt->next; 00183 } while (pt != loop); 00184 start = pt->pos; 00185 ComputeBoundingBox(); 00186 } 00187 00188 // Recomputes the bounding box from the points in the loop. 00189 void TESSLINE::ComputeBoundingBox() { 00190 int minx = MAX_INT32; 00191 int miny = MAX_INT32; 00192 int maxx = -MAX_INT32; 00193 int maxy = -MAX_INT32; 00194 00195 // Find boundaries. 00196 start = loop->pos; 00197 EDGEPT* this_edge = loop; 00198 do { 00199 if (!this_edge->IsHidden() || !this_edge->prev->IsHidden()) { 00200 if (this_edge->pos.x < minx) 00201 minx = this_edge->pos.x; 00202 if (this_edge->pos.y < miny) 00203 miny = this_edge->pos.y; 00204 if (this_edge->pos.x > maxx) 00205 maxx = this_edge->pos.x; 00206 if (this_edge->pos.y > maxy) 00207 maxy = this_edge->pos.y; 00208 } 00209 this_edge = this_edge->next; 00210 } while (this_edge != loop); 00211 // Reset bounds. 00212 topleft.x = minx; 00213 topleft.y = maxy; 00214 botright.x = maxx; 00215 botright.y = miny; 00216 } 00217 00218 // Computes the min and max cross product of the outline points with the 00219 // given vec and returns the results in min_xp and max_xp. Geometrically 00220 // this is the left and right edge of the outline perpendicular to the 00221 // given direction, but to get the distance units correct, you would 00222 // have to divide by the modulus of vec. 00223 void TESSLINE::MinMaxCrossProduct(const TPOINT vec, 00224 int* min_xp, int* max_xp) const { 00225 *min_xp = MAX_INT32; 00226 *max_xp = MIN_INT32; 00227 EDGEPT* this_edge = loop; 00228 do { 00229 if (!this_edge->IsHidden() || !this_edge->prev->IsHidden()) { 00230 int product = CROSS(this_edge->pos, vec); 00231 UpdateRange(product, min_xp, max_xp); 00232 } 00233 this_edge = this_edge->next; 00234 } while (this_edge != loop); 00235 } 00236 00237 TBOX TESSLINE::bounding_box() const { 00238 return TBOX(topleft.x, botright.y, botright.x, topleft.y); 00239 } 00240 00241 #ifndef GRAPHICS_DISABLED 00242 void TESSLINE::plot(ScrollView* window, ScrollView::Color color, 00243 ScrollView::Color child_color) { 00244 if (is_hole) 00245 window->Pen(child_color); 00246 else 00247 window->Pen(color); 00248 window->SetCursor(start.x, start.y); 00249 EDGEPT* pt = loop; 00250 do { 00251 bool prev_hidden = pt->IsHidden(); 00252 pt = pt->next; 00253 if (prev_hidden) 00254 window->SetCursor(pt->pos.x, pt->pos.y); 00255 else 00256 window->DrawTo(pt->pos.x, pt->pos.y); 00257 } while (pt != loop); 00258 } 00259 #endif // GRAPHICS_DISABLED 00260 00261 // Returns the first non-hidden EDGEPT that has a different src_outline to 00262 // its predecessor, or, if all the same, the lowest indexed point. 00263 EDGEPT* TESSLINE::FindBestStartPt() const { 00264 EDGEPT* best_start = loop; 00265 int best_step = loop->start_step; 00266 // Iterate the polygon. 00267 EDGEPT* pt = loop; 00268 do { 00269 if (pt->IsHidden()) continue; 00270 if (pt->prev->IsHidden() || pt->prev->src_outline != pt->src_outline) 00271 return pt; // Qualifies as the best. 00272 if (pt->start_step < best_step) { 00273 best_step = pt->start_step; 00274 best_start = pt; 00275 } 00276 } while ((pt = pt->next) != loop); 00277 return best_start; 00278 } 00279 00280 // Iterate the given list of outlines, converting to TESSLINE by polygonal 00281 // approximation and recursively any children, returning the current tail 00282 // of the resulting list of TESSLINEs. 00283 static TESSLINE** ApproximateOutlineList(bool allow_detailed_fx, 00284 C_OUTLINE_LIST* outlines, 00285 bool children, 00286 TESSLINE** tail) { 00287 C_OUTLINE_IT ol_it(outlines); 00288 for (ol_it.mark_cycle_pt(); !ol_it.cycled_list(); ol_it.forward()) { 00289 C_OUTLINE* outline = ol_it.data(); 00290 if (outline->pathlength() > 0) { 00291 TESSLINE* tessline = ApproximateOutline(allow_detailed_fx, outline); 00292 tessline->is_hole = children; 00293 *tail = tessline; 00294 tail = &tessline->next; 00295 } 00296 if (!outline->child()->empty()) { 00297 tail = ApproximateOutlineList(allow_detailed_fx, outline->child(), true, 00298 tail); 00299 } 00300 } 00301 return tail; 00302 } 00303 00304 // Factory to build a TBLOB from a C_BLOB with polygonal approximation along 00305 // the way. If allow_detailed_fx is true, the EDGEPTs in the returned TBLOB 00306 // contain pointers to the input C_OUTLINEs that enable higher-resolution 00307 // feature extraction that does not use the polygonal approximation. 00308 TBLOB* TBLOB::PolygonalCopy(bool allow_detailed_fx, C_BLOB* src) { 00309 TBLOB* tblob = new TBLOB; 00310 ApproximateOutlineList(allow_detailed_fx, src->out_list(), false, 00311 &tblob->outlines); 00312 return tblob; 00313 } 00314 00315 // Factory builds a blob with no outlines, but copies the other member data. 00316 TBLOB* TBLOB::ShallowCopy(const TBLOB& src) { 00317 TBLOB* blob = new TBLOB; 00318 blob->denorm_ = src.denorm_; 00319 return blob; 00320 } 00321 00322 // Normalizes the blob for classification only if needed. 00323 // (Normally this means a non-zero classify rotation.) 00324 // If no Normalization is needed, then NULL is returned, and the input blob 00325 // can be used directly. Otherwise a new TBLOB is returned which must be 00326 // deleted after use. 00327 TBLOB* TBLOB::ClassifyNormalizeIfNeeded() const { 00328 TBLOB* rotated_blob = NULL; 00329 // If necessary, copy the blob and rotate it. The rotation is always 00330 // +/- 90 degrees, as 180 was already taken care of. 00331 if (denorm_.block() != NULL && 00332 denorm_.block()->classify_rotation().y() != 0.0) { 00333 TBOX box = bounding_box(); 00334 int x_middle = (box.left() + box.right()) / 2; 00335 int y_middle = (box.top() + box.bottom()) / 2; 00336 rotated_blob = new TBLOB(*this); 00337 const FCOORD& rotation = denorm_.block()->classify_rotation(); 00338 // Move the rotated blob back to the same y-position so that we 00339 // can still distinguish similar glyphs with differeny y-position. 00340 float target_y = kBlnBaselineOffset + 00341 (rotation.y() > 0 ? x_middle - box.left() : box.right() - x_middle); 00342 rotated_blob->Normalize(NULL, &rotation, &denorm_, x_middle, y_middle, 00343 1.0f, 1.0f, 0.0f, target_y, 00344 denorm_.inverse(), denorm_.pix()); 00345 } 00346 return rotated_blob; 00347 } 00348 00349 // Copies the data and the outline, but leaves next untouched. 00350 void TBLOB::CopyFrom(const TBLOB& src) { 00351 Clear(); 00352 TESSLINE* prev_outline = NULL; 00353 for (TESSLINE* srcline = src.outlines; srcline != NULL; 00354 srcline = srcline->next) { 00355 TESSLINE* new_outline = new TESSLINE(*srcline); 00356 if (outlines == NULL) 00357 outlines = new_outline; 00358 else 00359 prev_outline->next = new_outline; 00360 prev_outline = new_outline; 00361 } 00362 denorm_ = src.denorm_; 00363 } 00364 00365 // Deletes owned data. 00366 void TBLOB::Clear() { 00367 for (TESSLINE* next_outline = NULL; outlines != NULL; 00368 outlines = next_outline) { 00369 next_outline = outlines->next; 00370 delete outlines; 00371 } 00372 } 00373 00374 // Sets up the built-in DENORM and normalizes the blob in-place. 00375 // For parameters see DENORM::SetupNormalization, plus the inverse flag for 00376 // this blob and the Pix for the full image. 00377 void TBLOB::Normalize(const BLOCK* block, 00378 const FCOORD* rotation, 00379 const DENORM* predecessor, 00380 float x_origin, float y_origin, 00381 float x_scale, float y_scale, 00382 float final_xshift, float final_yshift, 00383 bool inverse, Pix* pix) { 00384 denorm_.SetupNormalization(block, rotation, predecessor, x_origin, y_origin, 00385 x_scale, y_scale, final_xshift, final_yshift); 00386 denorm_.set_inverse(inverse); 00387 denorm_.set_pix(pix); 00388 // TODO(rays) outline->Normalize is more accurate, but breaks tests due 00389 // the changes it makes. Reinstate this code with a retraining. 00390 // The reason this change is troublesome is that it normalizes for the 00391 // baseline value computed independently at each x-coord. If the baseline 00392 // is not horizontal, this introduces shear into the normalized blob, which 00393 // is useful on the rare occasions that the baseline is really curved, but 00394 // the baselines need to be stabilized the rest of the time. 00395 #if 0 00396 for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next) { 00397 outline->Normalize(denorm_); 00398 } 00399 #else 00400 denorm_.LocalNormBlob(this); 00401 #endif 00402 } 00403 00404 // Rotates by the given rotation in place. 00405 void TBLOB::Rotate(const FCOORD rotation) { 00406 for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next) { 00407 outline->Rotate(rotation); 00408 } 00409 } 00410 00411 // Moves by the given vec in place. 00412 void TBLOB::Move(const ICOORD vec) { 00413 for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next) { 00414 outline->Move(vec); 00415 } 00416 } 00417 00418 // Scales by the given factor in place. 00419 void TBLOB::Scale(float factor) { 00420 for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next) { 00421 outline->Scale(factor); 00422 } 00423 } 00424 00425 // Recomputes the bounding boxes of the outlines. 00426 void TBLOB::ComputeBoundingBoxes() { 00427 for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next) { 00428 outline->ComputeBoundingBox(); 00429 } 00430 } 00431 00432 // Returns the number of outlines. 00433 int TBLOB::NumOutlines() const { 00434 int result = 0; 00435 for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next) 00436 ++result; 00437 return result; 00438 } 00439 00440 /********************************************************************** 00441 * TBLOB::bounding_box() 00442 * 00443 * Compute the bounding_box of a compound blob, defined to be the 00444 * bounding box of the union of all top-level outlines in the blob. 00445 **********************************************************************/ 00446 TBOX TBLOB::bounding_box() const { 00447 if (outlines == NULL) 00448 return TBOX(0, 0, 0, 0); 00449 TESSLINE *outline = outlines; 00450 TBOX box = outline->bounding_box(); 00451 for (outline = outline->next; outline != NULL; outline = outline->next) { 00452 box += outline->bounding_box(); 00453 } 00454 return box; 00455 } 00456 00457 #ifndef GRAPHICS_DISABLED 00458 void TBLOB::plot(ScrollView* window, ScrollView::Color color, 00459 ScrollView::Color child_color) { 00460 for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next) 00461 outline->plot(window, color, child_color); 00462 } 00463 #endif // GRAPHICS_DISABLED 00464 00465 // Computes the center of mass and second moments for the old baseline and 00466 // 2nd moment normalizations. Returns the outline length. 00467 // The input denorm should be the normalizations that have been applied from 00468 // the image to the current state of this TBLOB. 00469 int TBLOB::ComputeMoments(FCOORD* center, FCOORD* second_moments) const { 00470 // Compute 1st and 2nd moments of the original outline. 00471 LLSQ accumulator; 00472 TBOX box = bounding_box(); 00473 // Iterate the outlines, accumulating edges relative the box.botleft(). 00474 CollectEdges(box, NULL, &accumulator, NULL, NULL); 00475 *center = accumulator.mean_point() + box.botleft(); 00476 // The 2nd moments are just the standard deviation of the point positions. 00477 double x2nd = sqrt(accumulator.x_variance()); 00478 double y2nd = sqrt(accumulator.y_variance()); 00479 if (x2nd < 1.0) x2nd = 1.0; 00480 if (y2nd < 1.0) y2nd = 1.0; 00481 second_moments->set_x(x2nd); 00482 second_moments->set_y(y2nd); 00483 return accumulator.count(); 00484 } 00485 00486 // Computes the precise bounding box of the coords that are generated by 00487 // GetEdgeCoords. This may be different from the bounding box of the polygon. 00488 void TBLOB::GetPreciseBoundingBox(TBOX* precise_box) const { 00489 TBOX box = bounding_box(); 00490 *precise_box = TBOX(); 00491 CollectEdges(box, precise_box, NULL, NULL, NULL); 00492 precise_box->move(box.botleft()); 00493 } 00494 00495 // Adds edges to the given vectors. 00496 // For all the edge steps in all the outlines, or polygonal approximation 00497 // where there are no edge steps, collects the steps into x_coords/y_coords. 00498 // x_coords is a collection of the x-coords of vertical edges for each 00499 // y-coord starting at box.bottom(). 00500 // y_coords is a collection of the y-coords of horizontal edges for each 00501 // x-coord starting at box.left(). 00502 // Eg x_coords[0] is a collection of the x-coords of edges at y=bottom. 00503 // Eg x_coords[1] is a collection of the x-coords of edges at y=bottom + 1. 00504 void TBLOB::GetEdgeCoords(const TBOX& box, 00505 GenericVector<GenericVector<int> >* x_coords, 00506 GenericVector<GenericVector<int> >* y_coords) const { 00507 GenericVector<int> empty; 00508 x_coords->init_to_size(box.height(), empty); 00509 y_coords->init_to_size(box.width(), empty); 00510 CollectEdges(box, NULL, NULL, x_coords, y_coords); 00511 // Sort the output vectors. 00512 for (int i = 0; i < x_coords->size(); ++i) 00513 (*x_coords)[i].sort(); 00514 for (int i = 0; i < y_coords->size(); ++i) 00515 (*y_coords)[i].sort(); 00516 } 00517 00518 // Accumulates the segment between pt1 and pt2 in the LLSQ, quantizing over 00519 // the integer coordinate grid to properly weight long vectors. 00520 static void SegmentLLSQ(const FCOORD& pt1, const FCOORD& pt2, 00521 LLSQ* accumulator) { 00522 FCOORD step(pt2); 00523 step -= pt1; 00524 int xstart = IntCastRounded(MIN(pt1.x(), pt2.x())); 00525 int xend = IntCastRounded(MAX(pt1.x(), pt2.x())); 00526 int ystart = IntCastRounded(MIN(pt1.y(), pt2.y())); 00527 int yend = IntCastRounded(MAX(pt1.y(), pt2.y())); 00528 if (xstart == xend && ystart == yend) return; // Nothing to do. 00529 double weight = step.length() / (xend - xstart + yend - ystart); 00530 // Compute and save the y-position at the middle of each x-step. 00531 for (int x = xstart; x < xend; ++x) { 00532 double y = pt1.y() + step.y() * (x + 0.5 - pt1.x()) / step.x(); 00533 accumulator->add(x + 0.5, y, weight); 00534 } 00535 // Compute and save the x-position at the middle of each y-step. 00536 for (int y = ystart; y < yend; ++y) { 00537 double x = pt1.x() + step.x() * (y + 0.5 - pt1.y()) / step.y(); 00538 accumulator->add(x, y + 0.5, weight); 00539 } 00540 } 00541 00542 // Adds any edges from a single segment of outline between pt1 and pt2 to 00543 // the x_coords, y_coords vectors. pt1 and pt2 should be relative to the 00544 // bottom-left of the bounding box, hence indices to x_coords, y_coords 00545 // are clipped to ([0,x_limit], [0,y_limit]). 00546 // See GetEdgeCoords above for a description of x_coords, y_coords. 00547 static void SegmentCoords(const FCOORD& pt1, const FCOORD& pt2, 00548 int x_limit, int y_limit, 00549 GenericVector<GenericVector<int> >* x_coords, 00550 GenericVector<GenericVector<int> >* y_coords) { 00551 FCOORD step(pt2); 00552 step -= pt1; 00553 int start = ClipToRange(IntCastRounded(MIN(pt1.x(), pt2.x())), 0, x_limit); 00554 int end = ClipToRange(IntCastRounded(MAX(pt1.x(), pt2.x())), 0, x_limit); 00555 for (int x = start; x < end; ++x) { 00556 int y = IntCastRounded(pt1.y() + step.y() * (x + 0.5 - pt1.x()) / step.x()); 00557 (*y_coords)[x].push_back(y); 00558 } 00559 start = ClipToRange(IntCastRounded(MIN(pt1.y(), pt2.y())), 0, y_limit); 00560 end = ClipToRange(IntCastRounded(MAX(pt1.y(), pt2.y())), 0, y_limit); 00561 for (int y = start; y < end; ++y) { 00562 int x = IntCastRounded(pt1.x() + step.x() * (y + 0.5 - pt1.y()) / step.y()); 00563 (*x_coords)[y].push_back(x); 00564 } 00565 } 00566 00567 // Adds any edges from a single segment of outline between pt1 and pt2 to 00568 // the bbox such that it guarantees to contain anything produced by 00569 // SegmentCoords. 00570 static void SegmentBBox(const FCOORD& pt1, const FCOORD& pt2, TBOX* bbox) { 00571 FCOORD step(pt2); 00572 step -= pt1; 00573 int x1 = IntCastRounded(MIN(pt1.x(), pt2.x())); 00574 int x2 = IntCastRounded(MAX(pt1.x(), pt2.x())); 00575 if (x2 > x1) { 00576 int y1 = IntCastRounded(pt1.y() + step.y() * (x1 + 0.5 - pt1.x()) / 00577 step.x()); 00578 int y2 = IntCastRounded(pt1.y() + step.y() * (x2 - 0.5 - pt1.x()) / 00579 step.x()); 00580 TBOX point(x1, MIN(y1, y2), x2, MAX(y1, y2)); 00581 *bbox += point; 00582 } 00583 int y1 = IntCastRounded(MIN(pt1.y(), pt2.y())); 00584 int y2 = IntCastRounded(MAX(pt1.y(), pt2.y())); 00585 if (y2 > y1) { 00586 int x1 = IntCastRounded(pt1.x() + step.x() * (y1 + 0.5 - pt1.y()) / 00587 step.y()); 00588 int x2 = IntCastRounded(pt1.x() + step.x() * (y2 - 0.5 - pt1.y()) / 00589 step.y()); 00590 TBOX point(MIN(x1, x2), y1, MAX(x1, x2), y2); 00591 *bbox += point; 00592 } 00593 } 00594 00595 // Collects edges into the given bounding box, LLSQ accumulator and/or x_coords, 00596 // y_coords vectors. 00597 // For a description of x_coords/y_coords, see GetEdgeCoords above. 00598 // Startpt to lastpt, inclusive, MUST have the same src_outline member, 00599 // which may be NULL. The vector from lastpt to its next is included in 00600 // the accumulation. Hidden edges should be excluded by the caller. 00601 // The input denorm should be the normalizations that have been applied from 00602 // the image to the current state of the TBLOB from which startpt, lastpt come. 00603 // box is the bounding box of the blob from which the EDGEPTs are taken and 00604 // indices into x_coords, y_coords are offset by box.botleft(). 00605 static void CollectEdgesOfRun(const EDGEPT* startpt, const EDGEPT* lastpt, 00606 const DENORM& denorm, const TBOX& box, 00607 TBOX* bounding_box, 00608 LLSQ* accumulator, 00609 GenericVector<GenericVector<int> > *x_coords, 00610 GenericVector<GenericVector<int> > *y_coords) { 00611 const C_OUTLINE* outline = startpt->src_outline; 00612 int x_limit = box.width() - 1; 00613 int y_limit = box.height() - 1; 00614 if (outline != NULL) { 00615 // Use higher-resolution edge points stored on the outline. 00616 // The outline coordinates may not match the binary image because of the 00617 // rotation for vertical text lines, but the root_denorm IS the matching 00618 // start of the DENORM chain. 00619 const DENORM* root_denorm = denorm.RootDenorm(); 00620 int step_length = outline->pathlength(); 00621 int start_index = startpt->start_step; 00622 // Note that if this run straddles the wrap-around point of the outline, 00623 // that lastpt->start_step may have a lower index than startpt->start_step, 00624 // and we want to use an end_index that allows us to use a positive 00625 // increment, so we add step_length if necessary, but that may be beyond the 00626 // bounds of the outline steps/ due to wrap-around, so we use % step_length 00627 // everywhere, except for start_index. 00628 int end_index = lastpt->start_step + lastpt->step_count; 00629 if (end_index <= start_index) 00630 end_index += step_length; 00631 // pos is the integer coordinates of the binary image steps. 00632 ICOORD pos = outline->position_at_index(start_index); 00633 FCOORD origin(box.left(), box.bottom()); 00634 // f_pos is a floating-point version of pos that offers improved edge 00635 // positioning using greyscale information or smoothing of edge steps. 00636 FCOORD f_pos = outline->sub_pixel_pos_at_index(pos, start_index); 00637 // pos_normed is f_pos after the appropriate normalization, and relative 00638 // to origin. 00639 // prev_normed is the previous value of pos_normed. 00640 FCOORD prev_normed; 00641 denorm.NormTransform(root_denorm, f_pos, &prev_normed); 00642 prev_normed -= origin; 00643 for (int index = start_index; index < end_index; ++index) { 00644 ICOORD step = outline->step(index % step_length); 00645 // Only use the point if its edge strength is positive. This excludes 00646 // points that don't provide useful information, eg 00647 // ___________ 00648 // |___________ 00649 // The vertical step provides only noisy, damaging information, as even 00650 // with a greyscale image, the positioning of the edge there may be a 00651 // fictitious extrapolation, so previous processing has eliminated it. 00652 if (outline->edge_strength_at_index(index % step_length) > 0) { 00653 FCOORD f_pos = outline->sub_pixel_pos_at_index(pos, 00654 index % step_length); 00655 FCOORD pos_normed; 00656 denorm.NormTransform(root_denorm, f_pos, &pos_normed); 00657 pos_normed -= origin; 00658 // Accumulate the information that is selected by the caller. 00659 if (bounding_box != NULL) { 00660 SegmentBBox(pos_normed, prev_normed, bounding_box); 00661 } 00662 if (accumulator != NULL) { 00663 SegmentLLSQ(pos_normed, prev_normed, accumulator); 00664 } 00665 if (x_coords != NULL && y_coords != NULL) { 00666 SegmentCoords(pos_normed, prev_normed, x_limit, y_limit, 00667 x_coords, y_coords); 00668 } 00669 prev_normed = pos_normed; 00670 } 00671 pos += step; 00672 } 00673 } else { 00674 // There is no outline, so we are forced to use the polygonal approximation. 00675 const EDGEPT* endpt = lastpt->next; 00676 const EDGEPT* pt = startpt; 00677 do { 00678 FCOORD next_pos(pt->next->pos.x - box.left(), 00679 pt->next->pos.y - box.bottom()); 00680 FCOORD pos(pt->pos.x - box.left(), pt->pos.y - box.bottom()); 00681 if (bounding_box != NULL) { 00682 SegmentBBox(next_pos, pos, bounding_box); 00683 } 00684 if (accumulator != NULL) { 00685 SegmentLLSQ(next_pos, pos, accumulator); 00686 } 00687 if (x_coords != NULL && y_coords != NULL) { 00688 SegmentCoords(next_pos, pos, x_limit, y_limit, x_coords, y_coords); 00689 } 00690 } while ((pt = pt->next) != endpt); 00691 } 00692 } 00693 00694 // For all the edge steps in all the outlines, or polygonal approximation 00695 // where there are no edge steps, collects the steps into the bounding_box, 00696 // llsq and/or the x_coords/y_coords. Both are used in different kinds of 00697 // normalization. 00698 // For a description of x_coords, y_coords, see GetEdgeCoords above. 00699 void TBLOB::CollectEdges(const TBOX& box, 00700 TBOX* bounding_box, LLSQ* llsq, 00701 GenericVector<GenericVector<int> >* x_coords, 00702 GenericVector<GenericVector<int> >* y_coords) const { 00703 // Iterate the outlines. 00704 for (const TESSLINE* ol = outlines; ol != NULL; ol = ol->next) { 00705 // Iterate the polygon. 00706 EDGEPT* loop_pt = ol->FindBestStartPt(); 00707 EDGEPT* pt = loop_pt; 00708 if (pt == NULL) continue; 00709 do { 00710 if (pt->IsHidden()) continue; 00711 // Find a run of equal src_outline. 00712 EDGEPT* last_pt = pt; 00713 do { 00714 last_pt = last_pt->next; 00715 } while (last_pt != loop_pt && !last_pt->IsHidden() && 00716 last_pt->src_outline == pt->src_outline); 00717 last_pt = last_pt->prev; 00718 CollectEdgesOfRun(pt, last_pt, denorm_, box, 00719 bounding_box, llsq, x_coords, y_coords); 00720 pt = last_pt; 00721 } while ((pt = pt->next) != loop_pt); 00722 } 00723 } 00724 00725 // Factory to build a TWERD from a (C_BLOB) WERD, with polygonal 00726 // approximation along the way. 00727 TWERD* TWERD::PolygonalCopy(bool allow_detailed_fx, WERD* src) { 00728 TWERD* tessword = new TWERD; 00729 tessword->latin_script = src->flag(W_SCRIPT_IS_LATIN); 00730 C_BLOB_IT b_it(src->cblob_list()); 00731 for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) { 00732 C_BLOB* blob = b_it.data(); 00733 TBLOB* tblob = TBLOB::PolygonalCopy(allow_detailed_fx, blob); 00734 tessword->blobs.push_back(tblob); 00735 } 00736 return tessword; 00737 } 00738 00739 // Baseline normalizes the blobs in-place, recording the normalization in the 00740 // DENORMs in the blobs. 00741 void TWERD::BLNormalize(const BLOCK* block, const ROW* row, Pix* pix, 00742 bool inverse, float x_height, bool numeric_mode, 00743 tesseract::OcrEngineMode hint, 00744 const TBOX* norm_box, 00745 DENORM* word_denorm) { 00746 TBOX word_box = bounding_box(); 00747 if (norm_box != NULL) word_box = *norm_box; 00748 float word_middle = (word_box.left() + word_box.right()) / 2.0f; 00749 float input_y_offset = 0.0f; 00750 float final_y_offset = static_cast<float>(kBlnBaselineOffset); 00751 float scale = kBlnXHeight / x_height; 00752 if (hint == tesseract::OEM_CUBE_ONLY || row == NULL) { 00753 word_middle = word_box.left(); 00754 input_y_offset = word_box.bottom(); 00755 final_y_offset = 0.0f; 00756 if (hint == tesseract::OEM_CUBE_ONLY) 00757 scale = 1.0f; 00758 } else { 00759 input_y_offset = row->base_line(word_middle); 00760 } 00761 for (int b = 0; b < blobs.size(); ++b) { 00762 TBLOB* blob = blobs[b]; 00763 TBOX blob_box = blob->bounding_box(); 00764 float mid_x = (blob_box.left() + blob_box.right()) / 2.0f; 00765 float baseline = input_y_offset; 00766 float blob_scale = scale; 00767 if (numeric_mode) { 00768 baseline = blob_box.bottom(); 00769 blob_scale = ClipToRange(kBlnXHeight * 4.0f / (3 * blob_box.height()), 00770 scale, scale * 1.5f); 00771 } else if (row != NULL && hint != tesseract::OEM_CUBE_ONLY) { 00772 baseline = row->base_line(mid_x); 00773 } 00774 // The image will be 8-bit grey if the input was grey or color. Note that in 00775 // a grey image 0 is black and 255 is white. If the input was binary, then 00776 // the pix will be binary and 0 is white, with 1 being black. 00777 // To tell the difference pixGetDepth() will return 8 or 1. 00778 // The inverse flag will be true iff the word has been determined to be 00779 // white on black, and is independent of whether the pix is 8 bit or 1 bit. 00780 blob->Normalize(block, NULL, NULL, word_middle, baseline, blob_scale, 00781 blob_scale, 0.0f, final_y_offset, inverse, pix); 00782 } 00783 if (word_denorm != NULL) { 00784 word_denorm->SetupNormalization(block, NULL, NULL, word_middle, 00785 input_y_offset, scale, scale, 00786 0.0f, final_y_offset); 00787 word_denorm->set_inverse(inverse); 00788 word_denorm->set_pix(pix); 00789 } 00790 } 00791 00792 // Copies the data and the blobs, but leaves next untouched. 00793 void TWERD::CopyFrom(const TWERD& src) { 00794 Clear(); 00795 latin_script = src.latin_script; 00796 for (int b = 0; b < src.blobs.size(); ++b) { 00797 TBLOB* new_blob = new TBLOB(*src.blobs[b]); 00798 blobs.push_back(new_blob); 00799 } 00800 } 00801 00802 // Deletes owned data. 00803 void TWERD::Clear() { 00804 blobs.delete_data_pointers(); 00805 blobs.clear(); 00806 } 00807 00808 // Recomputes the bounding boxes of the blobs. 00809 void TWERD::ComputeBoundingBoxes() { 00810 for (int b = 0; b < blobs.size(); ++b) { 00811 blobs[b]->ComputeBoundingBoxes(); 00812 } 00813 } 00814 00815 TBOX TWERD::bounding_box() const { 00816 TBOX result; 00817 for (int b = 0; b < blobs.size(); ++b) { 00818 TBOX box = blobs[b]->bounding_box(); 00819 result += box; 00820 } 00821 return result; 00822 } 00823 00824 // Merges the blobs from start to end, not including end, and deletes 00825 // the blobs between start and end. 00826 void TWERD::MergeBlobs(int start, int end) { 00827 if (start >= blobs.size() - 1) return; // Nothing to do. 00828 TESSLINE* outline = blobs[start]->outlines; 00829 for (int i = start + 1; i < end && i < blobs.size(); ++i) { 00830 TBLOB* next_blob = blobs[i]; 00831 // Take the outlines from the next blob. 00832 if (outline == NULL) { 00833 blobs[start]->outlines = next_blob->outlines; 00834 outline = blobs[start]->outlines; 00835 } else { 00836 while (outline->next != NULL) 00837 outline = outline->next; 00838 outline->next = next_blob->outlines; 00839 next_blob->outlines = NULL; 00840 } 00841 // Delete the next blob and move on. 00842 delete next_blob; 00843 blobs[i] = NULL; 00844 } 00845 // Remove dead blobs from the vector. 00846 for (int i = start + 1; i < end && start + 1 < blobs.size(); ++i) { 00847 blobs.remove(start + 1); 00848 } 00849 } 00850 00851 #ifndef GRAPHICS_DISABLED 00852 void TWERD::plot(ScrollView* window) { 00853 ScrollView::Color color = WERD::NextColor(ScrollView::BLACK); 00854 for (int b = 0; b < blobs.size(); ++b) { 00855 blobs[b]->plot(window, color, ScrollView::BROWN); 00856 color = WERD::NextColor(color); 00857 } 00858 } 00859 #endif // GRAPHICS_DISABLED 00860 00861 /********************************************************************** 00862 * blob_origin 00863 * 00864 * Compute the origin of a compound blob, define to be the centre 00865 * of the bounding box. 00866 **********************************************************************/ 00867 void blob_origin(TBLOB *blob, /*blob to compute on */ 00868 TPOINT *origin) { /*return value */ 00869 TBOX bbox = blob->bounding_box(); 00870 *origin = (bbox.topleft() + bbox.botright()) / 2; 00871 } 00872 00873 /********************************************************************** 00874 * divisible_blob 00875 * 00876 * Returns true if the blob contains multiple outlines than can be 00877 * separated using divide_blobs. Sets the location to be used in the 00878 * call to divide_blobs. 00879 **********************************************************************/ 00880 bool divisible_blob(TBLOB *blob, bool italic_blob, TPOINT* location) { 00881 if (blob->outlines == NULL || blob->outlines->next == NULL) 00882 return false; // Need at least 2 outlines for it to be possible. 00883 int max_gap = 0; 00884 TPOINT vertical = italic_blob ? kDivisibleVerticalItalic 00885 : kDivisibleVerticalUpright; 00886 for (TESSLINE* outline1 = blob->outlines; outline1 != NULL; 00887 outline1 = outline1->next) { 00888 if (outline1->is_hole) 00889 continue; // Holes do not count as separable. 00890 TPOINT mid_pt1( 00891 static_cast<inT16>((outline1->topleft.x + outline1->botright.x) / 2), 00892 static_cast<inT16>((outline1->topleft.y + outline1->botright.y) / 2)); 00893 int mid_prod1 = CROSS(mid_pt1, vertical); 00894 int min_prod1, max_prod1; 00895 outline1->MinMaxCrossProduct(vertical, &min_prod1, &max_prod1); 00896 for (TESSLINE* outline2 = outline1->next; outline2 != NULL; 00897 outline2 = outline2->next) { 00898 if (outline2->is_hole) 00899 continue; // Holes do not count as separable. 00900 TPOINT mid_pt2( 00901 static_cast<inT16>((outline2->topleft.x + outline2->botright.x) / 2), 00902 static_cast<inT16>((outline2->topleft.y + outline2->botright.y) / 2)); 00903 int mid_prod2 = CROSS(mid_pt2, vertical); 00904 int min_prod2, max_prod2; 00905 outline2->MinMaxCrossProduct(vertical, &min_prod2, &max_prod2); 00906 int mid_gap = abs(mid_prod2 - mid_prod1); 00907 int overlap = MIN(max_prod1, max_prod2) - MAX(min_prod1, min_prod2); 00908 if (mid_gap - overlap / 4 > max_gap) { 00909 max_gap = mid_gap - overlap / 4; 00910 *location = mid_pt1; 00911 *location += mid_pt2; 00912 *location /= 2; 00913 } 00914 } 00915 } 00916 // Use the y component of the vertical vector as an approximation to its 00917 // length. 00918 return max_gap > vertical.y; 00919 } 00920 00921 /********************************************************************** 00922 * divide_blobs 00923 * 00924 * Create two blobs by grouping the outlines in the appropriate blob. 00925 * The outlines that are beyond the location point are moved to the 00926 * other blob. The ones whose x location is less than that point are 00927 * retained in the original blob. 00928 **********************************************************************/ 00929 void divide_blobs(TBLOB *blob, TBLOB *other_blob, bool italic_blob, 00930 const TPOINT& location) { 00931 TPOINT vertical = italic_blob ? kDivisibleVerticalItalic 00932 : kDivisibleVerticalUpright; 00933 TESSLINE *outline1 = NULL; 00934 TESSLINE *outline2 = NULL; 00935 00936 TESSLINE *outline = blob->outlines; 00937 blob->outlines = NULL; 00938 int location_prod = CROSS(location, vertical); 00939 00940 while (outline != NULL) { 00941 TPOINT mid_pt( 00942 static_cast<inT16>((outline->topleft.x + outline->botright.x) / 2), 00943 static_cast<inT16>((outline->topleft.y + outline->botright.y) / 2)); 00944 int mid_prod = CROSS(mid_pt, vertical); 00945 if (mid_prod < location_prod) { 00946 // Outline is in left blob. 00947 if (outline1) 00948 outline1->next = outline; 00949 else 00950 blob->outlines = outline; 00951 outline1 = outline; 00952 } else { 00953 // Outline is in right blob. 00954 if (outline2) 00955 outline2->next = outline; 00956 else 00957 other_blob->outlines = outline; 00958 outline2 = outline; 00959 } 00960 outline = outline->next; 00961 } 00962 00963 if (outline1) 00964 outline1->next = NULL; 00965 if (outline2) 00966 outline2->next = NULL; 00967 }