BOX* M_Utils::getColPartImCoords(ColPartition* cp, PIX* im) { BLOBNBOX_CLIST* blobnboxes = cp->boxes(); CLIST_ITERATOR bbox_it(blobnboxes); l_int32 height = (l_int32)im->h; l_int32 left = INT_MAX; l_int32 right = INT_MIN; l_int32 top = INT_MAX; l_int32 bottom = INT_MIN; l_int32 l, r, t, b; for (bbox_it.mark_cycle_pt (); !bbox_it.cycled_list(); bbox_it.forward()) { BLOBNBOX* blobnbox = (BLOBNBOX*)bbox_it.data(); l = (l_int32)blobnbox->cblob()->bounding_box().left(); r = (l_int32)blobnbox->cblob()->bounding_box().right(); t = height - (l_int32)blobnbox->cblob()->bounding_box().top(); b = height - (l_int32)blobnbox->cblob()->bounding_box().bottom(); if(l < left) left = l; if(r > right) right = r; if(t < top) top = t; if(b > bottom) bottom = b; } BOX* boxret = boxCreate(left, top, right-left, bottom-top); return boxret; }
// Helper to compute edge offsets for all the blobs on the list. // See coutln.h for an explanation of edge offsets. void BLOBNBOX::ComputeEdgeOffsets(Pix* thresholds, Pix* grey, BLOBNBOX_LIST* blobs) { int grey_height = 0; int thr_height = 0; int scale_factor = 1; if (thresholds != NULL && grey != NULL) { grey_height = pixGetHeight(grey); thr_height = pixGetHeight(thresholds); scale_factor = IntCastRounded(static_cast<double>(grey_height) / thr_height); } BLOBNBOX_IT blob_it(blobs); for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) { BLOBNBOX* blob = blob_it.data(); if (blob->cblob() != NULL) { // Get the threshold that applies to this blob. l_uint32 threshold = 128; if (thresholds != NULL && grey != NULL) { const TBOX& box = blob->cblob()->bounding_box(); // Transform the coordinates if required. TPOINT pt((box.left() + box.right()) / 2, (box.top() + box.bottom()) / 2); pixGetPixel(thresholds, pt.x / scale_factor, thr_height - 1 - pt.y / scale_factor, &threshold); } blob->cblob()->ComputeEdgeOffsets(threshold, grey); } } }
//yangjing01 modified : bool TAL_make_single_word(bool one_blob, TO_ROW_LIST* rows, ROW_LIST* real_rows) { TO_ROW_IT to_row_it(rows); ROW_IT row_it(real_rows); //to_real_row is the real row information of single row or single char mode TO_ROW* real_to_row = NULL; float row_max_height = 0.0; for (to_row_it.mark_cycle_pt(); !to_row_it.cycled_list(); to_row_it.forward()){ TO_ROW* row = to_row_it.data(); float row_min_y = row->min_y(); float row_max_y = row->max_y(); float row_height = abs(row_max_y - row_min_y); if (real_to_row == NULL || row_height > row_max_height || fabs(row_height - row_max_height) < 1.0f){ row_max_height = row_height; real_to_row = row; } } if (real_to_row == NULL){ return false; } C_BLOB_LIST cblobs; C_BLOB_IT cblob_it(&cblobs); BLOBNBOX_IT box_it(real_to_row->blob_list()); for (; !box_it.empty(); box_it.forward()){ BLOBNBOX* bblob = box_it.extract(); if (bblob->joined_to_prev() || (one_blob && !cblob_it.empty())) { if (bblob->cblob() != NULL){ C_OUTLINE_IT cout_it(cblob_it.data()->out_list()); cout_it.move_to_last(); cout_it.add_list_after(bblob->cblob()->out_list()); delete bblob->cblob(); } } else { if (bblob->cblob() != NULL) cblob_it.add_after_then_move(bblob->cblob()); } delete bblob; } // Convert the TO_ROW to a ROW. ROW* real_row = new ROW(real_to_row, static_cast<inT16>(real_to_row->kern_size), static_cast<inT16>(real_to_row->space_size)); WERD_IT word_it(real_row->word_list()); WERD* word = new WERD(&cblobs, 0, NULL); word->set_flag(W_BOL, TRUE); word->set_flag(W_EOL, TRUE); word->set_flag(W_DONT_CHOP, one_blob); word_it.add_after_then_move(word); row_it.add_after_then_move(real_row); return true; }
WERD *make_real_word(BLOBNBOX_IT *box_it, //iterator inT32 blobcount, //no of blobs to use BOOL8 bol, //start of line uinT8 blanks //no of blanks ) { OUTLINE_IT out_it; // outlines C_OUTLINE_IT cout_it; PBLOB_LIST blobs; // blobs in word C_BLOB_LIST cblobs; PBLOB_IT blob_it = &blobs; // iterator C_BLOB_IT cblob_it = &cblobs; WERD *word; // new word BLOBNBOX *bblob; // current blob inT32 blobindex; // in row for (blobindex = 0; blobindex < blobcount; blobindex++) { bblob = box_it->extract(); if (bblob->joined_to_prev()) { if (bblob->blob() != NULL) { out_it.set_to_list(blob_it.data()->out_list()); out_it.move_to_last(); out_it.add_list_after(bblob->blob()->out_list()); delete bblob->blob(); } else if (bblob->cblob() != NULL) { cout_it.set_to_list(cblob_it.data()->out_list()); cout_it.move_to_last(); cout_it.add_list_after(bblob->cblob()->out_list()); delete bblob->cblob(); } } else { if (bblob->blob() != NULL) blob_it.add_after_then_move(bblob->blob()); else if (bblob->cblob() != NULL) cblob_it.add_after_then_move(bblob->cblob()); } delete bblob; box_it->forward(); // next one } if (blanks < 1) blanks = 1; if (blob_it.empty()) word = new WERD(&cblobs, blanks, NULL); else word = new WERD(&blobs, blanks, NULL); if (bol) word->set_flag(W_BOL, TRUE); if (box_it->at_first()) word->set_flag(W_EOL, TRUE); // at end of line return word; }
static void clear_blobnboxes(BLOBNBOX_LIST* boxes) { BLOBNBOX_IT it = boxes; // A BLOBNBOX generally doesn't own its blobs, so if they do, you // have to delete them explicitly. for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) { BLOBNBOX* box = it.data(); if (box->cblob() != NULL) delete box->cblob(); } }
// Tests each blob in the list to see if it is certain non-text using 2 // conditions: // 1. blob overlaps a cell with high value in noise_density_ (previously set // by ComputeNoiseDensity). // OR 2. The blob overlaps more than max_blob_overlaps in *this grid. This // condition is disabled with max_blob_overlaps == -1. // If it does, the blob is declared non-text, and is used to mark up the // nontext_mask. Such blobs are fully deleted, and non-noise blobs have their // neighbours reset, as they may now point to deleted data. // WARNING: The blobs list blobs may be in the *this grid, but they are // not removed. If any deleted blobs might be in *this, then this must be // Clear()ed immediately after MarkAndDeleteNonTextBlobs is called. // If the win is not NULL, deleted blobs are drawn on it in red, and kept // blobs are drawn on it in ok_color. void CCNonTextDetect::MarkAndDeleteNonTextBlobs(BLOBNBOX_LIST* blobs, int max_blob_overlaps, ScrollView* win, ScrollView::Color ok_color, Pix* nontext_mask) { int imageheight = tright().y() - bleft().x(); BLOBNBOX_IT blob_it(blobs); BLOBNBOX_LIST dead_blobs; BLOBNBOX_IT dead_it(&dead_blobs); for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) { BLOBNBOX* blob = blob_it.data(); TBOX box = blob->bounding_box(); if (!noise_density_->RectMostlyOverThreshold(box, max_noise_count_) && (max_blob_overlaps < 0 || !BlobOverlapsTooMuch(blob, max_blob_overlaps))) { blob->ClearNeighbours(); #ifndef GRAPHICS_DISABLED if (win != NULL) blob->plot(win, ok_color, ok_color); #endif // GRAPHICS_DISABLED } else { if (noise_density_->AnyZeroInRect(box)) { // There is a danger that the bounding box may overlap real text, so // we need to render the outline. Pix* blob_pix = blob->cblob()->render_outline(); pixRasterop(nontext_mask, box.left(), imageheight - box.top(), box.width(), box.height(), PIX_SRC | PIX_DST, blob_pix, 0, 0); pixDestroy(&blob_pix); } else { if (box.area() < gridsize() * gridsize()) { // It is a really bad idea to make lots of small components in the // photo mask, so try to join it to a bigger area by expanding the // box in a way that does not touch any zero noise density cell. box = AttemptBoxExpansion(box, *noise_density_, gridsize()); } // All overlapped cells are non-zero, so just mark the rectangle. pixRasterop(nontext_mask, box.left(), imageheight - box.top(), box.width(), box.height(), PIX_SET, NULL, 0, 0); } #ifndef GRAPHICS_DISABLED if (win != NULL) blob->plot(win, ScrollView::RED, ScrollView::RED); #endif // GRAPHICS_DISABLED // It is safe to delete the cblob now, as it isn't used by the grid // or BlobOverlapsTooMuch, and the BLOBNBOXes will go away with the // dead_blobs list. // TODO(rays) delete the delete when the BLOBNBOX destructor deletes // the cblob. delete blob->cblob(); dead_it.add_to_end(blob_it.extract()); } } }
/** Handles a click event in a display window. */ void StrokeWidth::HandleClick(int x, int y) { BBGrid<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT>::HandleClick(x, y); // Run a radial search for blobs that overlap. BlobGridSearch radsearch(this); radsearch.StartRadSearch(x, y, 1); BLOBNBOX* neighbour; FCOORD click(static_cast<float>(x), static_cast<float>(y)); while ((neighbour = radsearch.NextRadSearch()) != NULL) { TBOX nbox = neighbour->bounding_box(); if (nbox.contains(click) && neighbour->cblob() != NULL) { PrintBoxWidths(neighbour); if (neighbour->neighbour(BND_LEFT) != NULL) PrintBoxWidths(neighbour->neighbour(BND_LEFT)); if (neighbour->neighbour(BND_RIGHT) != NULL) PrintBoxWidths(neighbour->neighbour(BND_RIGHT)); if (neighbour->neighbour(BND_ABOVE) != NULL) PrintBoxWidths(neighbour->neighbour(BND_ABOVE)); if (neighbour->neighbour(BND_BELOW) != NULL) PrintBoxWidths(neighbour->neighbour(BND_BELOW)); int gaps[BND_COUNT]; neighbour->NeighbourGaps(gaps); tprintf("Left gap=%d, right=%d, above=%d, below=%d, horz=%d, vert=%d\n" "Good= %d %d %d %d\n", gaps[BND_LEFT], gaps[BND_RIGHT], gaps[BND_ABOVE], gaps[BND_BELOW], neighbour->horz_possible(), neighbour->vert_possible(), neighbour->good_stroke_neighbour(BND_LEFT), neighbour->good_stroke_neighbour(BND_RIGHT), neighbour->good_stroke_neighbour(BND_ABOVE), neighbour->good_stroke_neighbour(BND_BELOW)); break; } } }
TBOX box_next( //get bounding box BLOBNBOX_IT *it //iterator to blobds ) { BLOBNBOX *blob; //current blob TBOX result; //total box blob = it->data (); result = blob->bounding_box (); do { it->forward (); blob = it->data (); if (blob->cblob() == NULL) //was pre-chopped result += blob->bounding_box (); } //until next real blob while ((blob->cblob() == NULL) || blob->joined_to_prev()); return result; }
// Helper to delete all the deletable blobs on the list. void BLOBNBOX::DeleteNoiseBlobs(BLOBNBOX_LIST* blobs) { BLOBNBOX_IT blob_it(blobs); for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) { BLOBNBOX* blob = blob_it.data(); if (blob->DeletableNoise()) { delete blob->cblob(); delete blob_it.extract(); } } }
WERD *make_real_word(BLOBNBOX_IT *box_it, //iterator int32_t blobcount, //no of blobs to use bool bol, //start of line uint8_t blanks //no of blanks ) { C_OUTLINE_IT cout_it; C_BLOB_LIST cblobs; C_BLOB_IT cblob_it = &cblobs; WERD *word; // new word BLOBNBOX *bblob; // current blob int32_t blobindex; // in row for (blobindex = 0; blobindex < blobcount; blobindex++) { bblob = box_it->extract(); if (bblob->joined_to_prev()) { if (bblob->cblob() != nullptr) { cout_it.set_to_list(cblob_it.data()->out_list()); cout_it.move_to_last(); cout_it.add_list_after(bblob->cblob()->out_list()); delete bblob->cblob(); } } else { if (bblob->cblob() != nullptr) cblob_it.add_after_then_move(bblob->cblob()); } delete bblob; box_it->forward(); // next one } if (blanks < 1) blanks = 1; word = new WERD(&cblobs, blanks, nullptr); if (bol) word->set_flag(W_BOL, true); if (box_it->at_first()) word->set_flag(W_EOL, true); // at end of line return word; }
void TO_ROW::compute_vertical_projection() { //project whole row TBOX row_box; //bound of row BLOBNBOX *blob; //current blob TBOX blob_box; //bounding box BLOBNBOX_IT blob_it = blob_list (); if (blob_it.empty ()) return; row_box = blob_it.data ()->bounding_box (); for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) row_box += blob_it.data ()->bounding_box (); projection.set_range (row_box.left () - PROJECTION_MARGIN, row_box.right () + PROJECTION_MARGIN); projection_left = row_box.left () - PROJECTION_MARGIN; projection_right = row_box.right () + PROJECTION_MARGIN; for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) { blob = blob_it.data(); if (blob->cblob() != NULL) vertical_cblob_projection(blob->cblob(), &projection); } }
// Places a copy of blobs that are near a word (after applying rotation to the // blob) in the most appropriate word, unless there is doubt, in which case a // blob can end up in two words. Source blobs are not touched. void Textord::TransferDiacriticsToWords(BLOBNBOX_LIST* diacritic_blobs, const FCOORD& rotation, WordGrid* word_grid) { WordSearch ws(word_grid); BLOBNBOX_IT b_it(diacritic_blobs); // Apply rotation to each blob before finding the nearest words. The rotation // allows us to only consider above/below placement and not left/right on // vertical text, because all text is horizontal here. for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) { BLOBNBOX* blobnbox = b_it.data(); TBOX blob_box = blobnbox->bounding_box(); blob_box.rotate(rotation); ws.StartRectSearch(blob_box); // Above/below refer to word position relative to diacritic. Since some // scripts eg Kannada/Telugu habitually put diacritics below words, and // others eg Thai/Vietnamese/Latin put most diacritics above words, try // for both if there isn't much in it. WordWithBox* best_above_word = nullptr; WordWithBox* best_below_word = nullptr; int best_above_distance = 0; int best_below_distance = 0; for (WordWithBox* word = ws.NextRectSearch(); word != nullptr; word = ws.NextRectSearch()) { if (word->word()->flag(W_REP_CHAR)) continue; TBOX word_box = word->true_bounding_box(); int x_distance = blob_box.x_gap(word_box); int y_distance = blob_box.y_gap(word_box); if (x_distance > 0) { // Arbitrarily divide x-distance by 2 if there is a major y overlap, // and the word is to the left of the diacritic. If the // diacritic is a dropped broken character between two words, this will // help send all the pieces to a single word, instead of splitting them // over the 2 words. if (word_box.major_y_overlap(blob_box) && blob_box.left() > word_box.right()) { x_distance /= 2; } y_distance += x_distance; } if (word_box.y_middle() > blob_box.y_middle() && (best_above_word == nullptr || y_distance < best_above_distance)) { best_above_word = word; best_above_distance = y_distance; } if (word_box.y_middle() <= blob_box.y_middle() && (best_below_word == nullptr || y_distance < best_below_distance)) { best_below_word = word; best_below_distance = y_distance; } } bool above_good = best_above_word != nullptr && (best_below_word == nullptr || best_above_distance < best_below_distance + blob_box.height()); bool below_good = best_below_word != nullptr && best_below_word != best_above_word && (best_above_word == nullptr || best_below_distance < best_above_distance + blob_box.height()); if (below_good) { C_BLOB* copied_blob = C_BLOB::deep_copy(blobnbox->cblob()); copied_blob->rotate(rotation); // Put the blob into the word's reject blobs list. C_BLOB_IT blob_it(best_below_word->RejBlobs()); blob_it.add_to_end(copied_blob); } if (above_good) { C_BLOB* copied_blob = C_BLOB::deep_copy(blobnbox->cblob()); copied_blob->rotate(rotation); // Put the blob into the word's reject blobs list. C_BLOB_IT blob_it(best_above_word->RejBlobs()); blob_it.add_to_end(copied_blob); } } }
// Creates and returns a Pix with the same resolution as the original // in which 1 (black) pixels represent likely non text (photo, line drawing) // areas of the page, deleting from the blob_block the blobs that were // determined to be non-text. // The photo_map is used to bias the decision towards non-text, rather than // supplying definite decision. // The blob_block is the usual result of connected component analysis, // holding the detected blobs. // The returned Pix should be PixDestroyed after use. Pix* CCNonTextDetect::ComputeNonTextMask(bool debug, Pix* photo_map, TO_BLOCK* blob_block) { // Insert the smallest blobs into the grid. InsertBlobList(&blob_block->small_blobs); InsertBlobList(&blob_block->noise_blobs); // Add the medium blobs that don't have a good strokewidth neighbour. // Those that do go into good_grid as an antidote to spreading beyond the // real reaches of a noise region. BlobGrid good_grid(gridsize(), bleft(), tright()); BLOBNBOX_IT blob_it(&blob_block->blobs); for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) { BLOBNBOX* blob = blob_it.data(); double perimeter_area_ratio = blob->cblob()->perimeter() / 4.0; perimeter_area_ratio *= perimeter_area_ratio / blob->enclosed_area(); if (blob->GoodTextBlob() == 0 || perimeter_area_ratio < kMinGoodTextPARatio) InsertBBox(true, true, blob); else good_grid.InsertBBox(true, true, blob); } noise_density_ = ComputeNoiseDensity(debug, photo_map, &good_grid); good_grid.Clear(); // Not needed any more. Pix* pix = noise_density_->ThresholdToPix(max_noise_count_); if (debug) { pixWrite("junknoisemask.png", pix, IFF_PNG); } ScrollView* win = NULL; #ifndef GRAPHICS_DISABLED if (debug) { win = MakeWindow(0, 400, "Photo Mask Blobs"); } #endif // GRAPHICS_DISABLED // Large and medium blobs are not text if they overlap with "a lot" of small // blobs. MarkAndDeleteNonTextBlobs(&blob_block->large_blobs, kMaxLargeOverlapsWithSmall, win, ScrollView::DARK_GREEN, pix); MarkAndDeleteNonTextBlobs(&blob_block->blobs, kMaxMediumOverlapsWithSmall, win, ScrollView::WHITE, pix); // Clear the grid of small blobs and insert the medium blobs. Clear(); InsertBlobList(&blob_block->blobs); MarkAndDeleteNonTextBlobs(&blob_block->large_blobs, kMaxLargeOverlapsWithMedium, win, ScrollView::DARK_GREEN, pix); // Clear again before we start deleting the blobs in the grid. Clear(); MarkAndDeleteNonTextBlobs(&blob_block->noise_blobs, -1, win, ScrollView::CORAL, pix); MarkAndDeleteNonTextBlobs(&blob_block->small_blobs, -1, win, ScrollView::GOLDENROD, pix); MarkAndDeleteNonTextBlobs(&blob_block->blobs, -1, win, ScrollView::WHITE, pix); if (debug) { #ifndef GRAPHICS_DISABLED win->Update(); #endif // GRAPHICS_DISABLED pixWrite("junkccphotomask.png", pix, IFF_PNG); #ifndef GRAPHICS_DISABLED delete win->AwaitEvent(SVET_DESTROY); delete win; #endif // GRAPHICS_DISABLED } return pix; }
void plot_word_decisions( //draw words ScrollView *win, //window tro draw in inT16 pitch, //of block TO_ROW *row //row to draw ) { ScrollView::Color colour = ScrollView::MAGENTA; //current colour ScrollView::Color rect_colour; //fuzzy colour inT32 prev_x; //end of prev blob inT16 blob_count; //blobs in word BLOBNBOX *blob; //current blob TBOX blob_box; //bounding box //iterator BLOBNBOX_IT blob_it = row->blob_list(); BLOBNBOX_IT start_it = blob_it;//word start rect_colour = ScrollView::BLACK; prev_x = -MAX_INT16; blob_count = 0; for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) { blob = blob_it.data(); blob_box = blob->bounding_box(); if (!blob->joined_to_prev() && blob_box.left() - prev_x > row->max_nonspace) { if ((blob_box.left() - prev_x >= row->min_space || blob_box.left() - prev_x > row->space_threshold) && blob_count > 0) { if (pitch > 0 && textord_show_fixed_cuts) plot_fp_cells(win, colour, &start_it, pitch, blob_count, &row->projection, row->projection_left, row->projection_right, row->xheight * textord_projection_scale); blob_count = 0; start_it = blob_it; } if (colour == ScrollView::MAGENTA) colour = ScrollView::RED; else colour = (ScrollView::Color)(colour + 1); if (blob_box.left() - prev_x < row->min_space) { if (blob_box.left() - prev_x > row->space_threshold) rect_colour = ScrollView::GOLDENROD; else rect_colour = ScrollView::CORAL; //fill_color_index(win, rect_colour); win->Brush(rect_colour); win->Rectangle(prev_x, blob_box.bottom(), blob_box.left(), blob_box.top()); } } if (!blob->joined_to_prev()) prev_x = blob_box.right(); if (blob->cblob() != NULL) blob->cblob()->plot(win, colour, colour); if (!blob->joined_to_prev() && blob->cblob() != NULL) blob_count++; } if (pitch > 0 && textord_show_fixed_cuts && blob_count > 0) plot_fp_cells(win, colour, &start_it, pitch, blob_count, &row->projection, row->projection_left, row->projection_right, row->xheight * textord_projection_scale); }