// Get a set of bounding boxes of possible vertical lines in the image. // The input resolution overrides any resolution set in src_pix. // The output line_pix contains just all the detected lines. Boxa* LineFinder::GetVLineBoxes(int resolution, Pix* src_pix, Pix** line_pix) { #ifdef HAVE_LIBLEPT // Remove any parts of 1 inch/kThinLineFraction wide or more, by opening // away the thin lines and subtracting what's left. // This is very generous and will leave in even quite wide lines. Pix* pixt1 = pixOpenBrick(NULL, src_pix, resolution / kThinLineFraction, 1); pixSubtract(pixt1, src_pix, pixt1); // Spread sideways to allow for some skew. Pix* pixt2 = pixDilateBrick(NULL, pixt1, 3, 1); // Now keep only tall stuff of height at least 1 inch/kMinLineLengthFraction. pixOpenBrick(pixt1, pixt2, 1, resolution / kMinLineLengthFraction); pixDestroy(&pixt2); // Put a single pixel crack in every line at an arbitrary spacing, // so they break up and the bounding boxes can be used to get the // direction accurately enough without needing outlines. int wpl = pixGetWpl(pixt1); int height = pixGetHeight(pixt1); l_uint32* data = pixGetData(pixt1); for (int y = kCrackSpacing; y < height; y += kCrackSpacing) { memset(data + wpl * y, 0, wpl * sizeof(*data)); } if (textord_tabfind_show_vlines) pixWrite("vlines.png", pixt1, IFF_PNG); Boxa* boxa = pixConnComp(pixt1, NULL, 8); *line_pix = pixt1; return boxa; #else return NULL; #endif }
/** * Returns an image of the current object at the given level in greyscale * if available in the input. To guarantee a binary image use BinaryImage. * NOTE that in order to give the best possible image, the bounds are * expanded slightly over the binary connected component, by the supplied * padding, so the top-left position of the returned image is returned * in (left,top). These will most likely not match the coordinates * returned by BoundingBox. * Use pixDestroy to delete the image after use. */ Pix* PageIterator::GetImage(PageIteratorLevel level, int padding, int* left, int* top) const { int right, bottom; if (!BoundingBox(level, left, top, &right, &bottom)) return NULL; Pix* pix = tesseract_->pix_grey(); if (pix == NULL) return GetBinaryImage(level); // Expand the box. *left = MAX(*left - padding, 0); *top = MAX(*top - padding, 0); right = MIN(right + padding, rect_width_); bottom = MIN(bottom + padding, rect_height_); Box* box = boxCreate(*left, *top, right - *left, bottom - *top); Pix* grey_pix = pixClipRectangle(pix, box, NULL); boxDestroy(&box); if (level == RIL_BLOCK) { Pix* mask = it_->block()->block->render_mask(); Pix* expanded_mask = pixCreate(right - *left, bottom - *top, 1); pixRasterop(expanded_mask, padding, padding, pixGetWidth(mask), pixGetHeight(mask), PIX_SRC, mask, 0, 0); pixDestroy(&mask); pixDilateBrick(expanded_mask, expanded_mask, 2*padding + 1, 2*padding + 1); pixInvert(expanded_mask, expanded_mask); pixSetMasked(grey_pix, expanded_mask, 255); pixDestroy(&expanded_mask); } return grey_pix; }
// Finds horizontal line objects in the given pix. // Uses the given resolution to determine size thresholds instead of any // that may be present in the pix. // The output vectors are owned by the list and Frozen (cannot refit) by // having no boxes, as there is no need to refit or merge separator lines. void LineFinder::FindHorizontalLines(int resolution, Pix* pix, TabVector_LIST* vectors) { #ifdef HAVE_LIBLEPT Pix* line_pix; Boxa* boxes = GetHLineBoxes(resolution, pix, &line_pix); C_BLOB_LIST line_cblobs; int width = pixGetWidth(pix); int height = pixGetHeight(pix); ConvertBoxaToBlobs(height, width, &boxes, &line_cblobs); // Make the BLOBNBOXes from the C_BLOBs. BLOBNBOX_LIST line_bblobs; C_BLOB_IT blob_it(&line_cblobs); BLOBNBOX_IT bbox_it(&line_bblobs); for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) { C_BLOB* cblob = blob_it.data(); BLOBNBOX* bblob = new BLOBNBOX(cblob); bbox_it.add_to_end(bblob); } ICOORD bleft(0, 0); ICOORD tright(height, width); int vertical_x, vertical_y; FindLineVectors(bleft, tright, &line_bblobs, &vertical_x, &vertical_y, vectors); if (!vectors->empty()) { // Some lines were found, so erase the unused blobs from the line image // and then subtract the line image from the source. bbox_it.move_to_first(); for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) { BLOBNBOX* blob = bbox_it.data(); if (blob->left_tab_type() == TT_UNCONFIRMED) { const TBOX& box = blob->bounding_box(); // Coords are in tess format so filp x and y and then covert // to leptonica by height -y. Box* pixbox = boxCreate(box.bottom(), height - box.right(), box.height(), box.width()); pixClearInRect(line_pix, pixbox); boxDestroy(&pixbox); } } pixDilateBrick(line_pix, line_pix, 3, 1); pixSubtract(pix, pix, line_pix); if (textord_tabfind_show_vlines) pixWrite("hlinesclean.png", line_pix, IFF_PNG); ICOORD vertical; vertical.set_with_shrink(vertical_x, vertical_y); TabVector::MergeSimilarTabVectors(vertical, vectors, NULL); // Iterate the vectors to flip them. TabVector_IT h_it(vectors); for (h_it.mark_cycle_pt(); !h_it.cycled_list(); h_it.forward()) { h_it.data()->XYFlip(); } } pixDestroy(&line_pix); #endif }
// Helper subtracts the line_pix image from the src_pix, and removes residue // as well by removing components that touch the line, but are not in the // non_line_pix mask. It is assumed that the non_line_pix mask has already // been prepared to required accuracy. static void SubtractLinesAndResidue(Pix* line_pix, Pix* non_line_pix, int resolution, Pix* src_pix) { // First remove the lines themselves. pixSubtract(src_pix, src_pix, line_pix); // Subtract the non-lines from the image to get the residue. Pix* residue_pix = pixSubtract(NULL, src_pix, non_line_pix); // Dilate the lines so they touch the residue. Pix* fat_line_pix = pixDilateBrick(NULL, line_pix, 3, 3); // Seed fill the fat lines to get all the residue. pixSeedfillBinary(fat_line_pix, fat_line_pix, residue_pix, 8); // Subtract the residue from the original image. pixSubtract(src_pix, src_pix, fat_line_pix); pixDestroy(&fat_line_pix); pixDestroy(&residue_pix); }
// Finds vertical line objects in the given pix. // Uses the given resolution to determine size thresholds instead of any // that may be present in the pix. // The output vertical_x and vertical_y contain a sum of the output vectors, // thereby giving the mean vertical direction. // The output vectors are owned by the list and Frozen (cannot refit) by // having no boxes, as there is no need to refit or merge separator lines. void LineFinder::FindVerticalLines(int resolution, Pix* pix, int* vertical_x, int* vertical_y, TabVector_LIST* vectors) { #ifdef HAVE_LIBLEPT Pix* line_pix; Boxa* boxes = GetVLineBoxes(resolution, pix, &line_pix); C_BLOB_LIST line_cblobs; int width = pixGetWidth(pix); int height = pixGetHeight(pix); ConvertBoxaToBlobs(width, height, &boxes, &line_cblobs); // Make the BLOBNBOXes from the C_BLOBs. BLOBNBOX_LIST line_bblobs; C_BLOB_IT blob_it(&line_cblobs); BLOBNBOX_IT bbox_it(&line_bblobs); for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) { C_BLOB* cblob = blob_it.data(); BLOBNBOX* bblob = new BLOBNBOX(cblob); bbox_it.add_to_end(bblob); } ICOORD bleft(0, 0); ICOORD tright(width, height); FindLineVectors(bleft, tright, &line_bblobs, vertical_x, vertical_y, vectors); if (!vectors->empty()) { // Some lines were found, so erase the unused blobs from the line image // and then subtract the line image from the source. bbox_it.move_to_first(); for (bbox_it.mark_cycle_pt(); !bbox_it.cycled_list(); bbox_it.forward()) { BLOBNBOX* blob = bbox_it.data(); if (blob->left_tab_type() == TT_UNCONFIRMED) { const TBOX& box = blob->bounding_box(); Box* pixbox = boxCreate(box.left(), height - box.top(), box.width(), box.height()); pixClearInRect(line_pix, pixbox); boxDestroy(&pixbox); } } pixDilateBrick(line_pix, line_pix, 1, 3); pixSubtract(pix, pix, line_pix); if (textord_tabfind_show_vlines) pixWrite("vlinesclean.png", line_pix, IFF_PNG); ICOORD vertical; vertical.set_with_shrink(*vertical_x, *vertical_y); TabVector::MergeSimilarTabVectors(vertical, vectors, NULL); } pixDestroy(&line_pix); #endif }
// Get a set of bounding boxes of possible horizontal lines in the image. // The input resolution overrides any resolution set in src_pix. // The output line_pix contains just all the detected lines. // The output boxes undergo the transformation (x,y)->(height-y,x) so the // lines can be found with a vertical line finder afterwards. // This transformation allows a simple x/y flip to reverse it in tesseract // coordinates and it is faster to flip the lines than rotate the image. Boxa* LineFinder::GetHLineBoxes(int resolution, Pix* src_pix, Pix** line_pix) { #ifdef HAVE_LIBLEPT // Remove any parts of 1 inch/kThinLineFraction high or more, by opening // away the thin lines and subtracting what's left. // This is very generous and will leave in even quite wide lines. Pix* pixt1 = pixOpenBrick(NULL, src_pix, 1, resolution / kThinLineFraction); pixSubtract(pixt1, src_pix, pixt1); // Spread vertically to allow for some skew. Pix* pixt2 = pixDilateBrick(NULL, pixt1, 1, 3); // Now keep only wide stuff of width at least 1 inch/kMinLineLengthFraction. pixOpenBrick(pixt1, pixt2, resolution / kMinLineLengthFraction, 1); pixDestroy(&pixt2); // Put a single pixel crack in every line at an arbitrary spacing, // so they break up and the bounding boxes can be used to get the // direction accurately enough without needing outlines. int wpl = pixGetWpl(pixt1); int width = pixGetWidth(pixt1); int height = pixGetHeight(pixt1); l_uint32* data = pixGetData(pixt1); for (int y = 0; y < height; ++y, data += wpl) { for (int x = kCrackSpacing; x < width; x += kCrackSpacing) { CLEAR_DATA_BIT(data, x); } } if (textord_tabfind_show_vlines) pixWrite("hlines.png", pixt1, IFF_PNG); Boxa* boxa = pixConnComp(pixt1, NULL, 8); *line_pix = pixt1; // Iterate the boxes to flip x and y. int nboxes = boxaGetCount(boxa); for (int i = 0; i < nboxes; ++i) { l_int32 x, y, box_width, box_height; boxaGetBoxGeometry(boxa, i, &x, &y, &box_width, &box_height); Box* box = boxCreate(height - (y + box_height), width - (x + box_width), box_height, box_width); boxaReplaceBox(boxa, i, box); } return boxa; #else return NULL; #endif }
// Finds image regions within the source pix (page image) and returns // the image regions as a Boxa, Pixa pair, analgous to pixConnComp. // The returned boxa, pixa may be NULL, meaning no images found. // If not NULL, they must be destroyed by the caller. void ImageFinder::FindImages(Pix* pix, Boxa** boxa, Pixa** pixa) { *boxa = NULL; *pixa = NULL; #ifdef HAVE_LIBLEPT if (pixGetWidth(pix) < kMinImageFindSize || pixGetHeight(pix) < kMinImageFindSize) return; // Not worth looking at small images. // Reduce by factor 2. Pix *pixr = pixReduceRankBinaryCascade(pix, 1, 0, 0, 0); pixDisplayWrite(pixr, textord_tabfind_show_images); // Get the halftone mask directly from Leptonica. Pix *pixht2 = pixGenHalftoneMask(pixr, NULL, NULL, textord_tabfind_show_images); pixDestroy(&pixr); if (pixht2 == NULL) return; // Expand back up again. Pix *pixht = pixExpandReplicate(pixht2, 2); pixDisplayWrite(pixht, textord_tabfind_show_images); pixDestroy(&pixht2); // Fill to capture pixels near the mask edges that were missed Pix *pixt = pixSeedfillBinary(NULL, pixht, pix, 8); pixOr(pixht, pixht, pixt); pixDestroy(&pixt); // Eliminate lines and bars that may be joined to images. Pix* pixfinemask = pixReduceRankBinaryCascade(pixht, 1, 1, 3, 3); pixDilateBrick(pixfinemask, pixfinemask, 5, 5); pixDisplayWrite(pixfinemask, textord_tabfind_show_images); Pix* pixreduced = pixReduceRankBinaryCascade(pixht, 1, 1, 1, 1); Pix* pixreduced2 = pixReduceRankBinaryCascade(pixreduced, 3, 3, 3, 0); pixDestroy(&pixreduced); pixDilateBrick(pixreduced2, pixreduced2, 5, 5); Pix* pixcoarsemask = pixExpandReplicate(pixreduced2, 8); pixDestroy(&pixreduced2); pixDisplayWrite(pixcoarsemask, textord_tabfind_show_images); // Combine the coarse and fine image masks. pixAnd(pixcoarsemask, pixcoarsemask, pixfinemask); pixDestroy(&pixfinemask); // Dilate a bit to make sure we get everything. pixDilateBrick(pixcoarsemask, pixcoarsemask, 3, 3); Pix* pixmask = pixExpandReplicate(pixcoarsemask, 16); pixDestroy(&pixcoarsemask); pixDisplayWrite(pixmask, textord_tabfind_show_images); // And the image mask with the line and bar remover. pixAnd(pixht, pixht, pixmask); pixDestroy(&pixmask); pixDisplayWrite(pixht, textord_tabfind_show_images); // Find the individual image regions in the mask image. *boxa = pixConnComp(pixht, pixa, 8); pixDestroy(&pixht); // Rectangularize the individual images. If a sharp edge in vertical and/or // horizontal occupancy can be found, it indicates a probably rectangular // image with unwanted bits merged on, so clip to the approximate rectangle. int npixes = pixaGetCount(*pixa); for (int i = 0; i < npixes; ++i) { int x_start, x_end, y_start, y_end; Pix* img_pix = pixaGetPix(*pixa, i, L_CLONE); pixDisplayWrite(img_pix, textord_tabfind_show_images); if (pixNearlyRectangular(img_pix, kMinRectangularFraction, kMaxRectangularFraction, kMaxRectangularGradient, &x_start, &y_start, &x_end, &y_end)) { // Add 1 to the size as a kludgy flag to indicate to the later stages // of processing that it is a clipped rectangular image . Pix* simple_pix = pixCreate(pixGetWidth(img_pix) + 1, pixGetHeight(img_pix), 1); pixDestroy(&img_pix); pixRasterop(simple_pix, x_start, y_start, x_end - x_start, y_end - y_start, PIX_SET, NULL, 0, 0); // pixaReplacePix takes ownership of the simple_pix. pixaReplacePix(*pixa, i, simple_pix, NULL); img_pix = pixaGetPix(*pixa, i, L_CLONE); } // Subtract the pix from the correct location in the master image. l_int32 x, y, width, height; pixDisplayWrite(img_pix, textord_tabfind_show_images); boxaGetBoxGeometry(*boxa, i, &x, &y, &width, &height); pixRasterop(pix, x, y, width, height, PIX_NOT(PIX_SRC) & PIX_DST, img_pix, 0, 0); pixDestroy(&img_pix); } #endif }
main(int argc, char **argv) { l_int32 i, ok, same; char sequence[512]; PIX *pixs, *pixref; PIX *pixt1, *pixt2, *pixt3, *pixt4, *pixt5, *pixt6; PIX *pixt7, *pixt8, *pixt9, *pixt10, *pixt11; PIX *pixt12, *pixt13, *pixt14; SEL *sel; static char mainName[] = "binmorph1_reg"; if (argc != 1) exit(ERROR_INT(" Syntax: binmorph1_reg", mainName, 1)); if ((pixs = pixRead("feyn.tif")) == NULL) exit(ERROR_INT("pix not made", mainName, 1)); #if TEST_SYMMETRIC /* This works properly if there is an added border */ resetMorphBoundaryCondition(SYMMETRIC_MORPH_BC); #if 1 pixt1 = pixAddBorder(pixs, 32, 0); pixTransferAllData(pixs, &pixt1, 0, 0); #endif #endif /* TEST_SYMMETRIC */ /* This is our test sel */ sel = selCreateBrick(HEIGHT, WIDTH, HEIGHT / 2, WIDTH / 2, SEL_HIT); /* Dilation */ fprintf(stderr, "Testing dilation\n"); ok = TRUE; pixref = pixDilate(NULL, pixs, sel); /* new one */ pixt1 = pixCreateTemplate(pixs); pixDilate(pixt1, pixs, sel); /* existing one */ pixEqual(pixref, pixt1, &same); if (!same) { fprintf(stderr, "pixref != pixt1 !\n"); ok = FALSE; } pixt2 = pixCopy(NULL, pixs); pixDilate(pixt2, pixt2, sel); /* in-place */ pixEqual(pixref, pixt2, &same); if (!same) { fprintf(stderr, "pixref != pixt2 !\n"); ok = FALSE; } sprintf(sequence, "d%d.%d", WIDTH, HEIGHT); pixt3 = pixMorphSequence(pixs, sequence, 0); /* sequence, atomic */ pixEqual(pixref, pixt3, &same); if (!same) { fprintf(stderr, "pixref != pixt3 !\n"); ok = FALSE; } sprintf(sequence, "d%d.1 + d1.%d", WIDTH, HEIGHT); pixt4 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable */ pixEqual(pixref, pixt4, &same); if (!same) { fprintf(stderr, "pixref != pixt4 !\n"); ok = FALSE; } pixt5 = pixDilateBrick(NULL, pixs, WIDTH, HEIGHT); /* new one */ pixEqual(pixref, pixt5, &same); if (!same) { fprintf(stderr, "pixref != pixt5 !\n"); ok = FALSE; } pixt6 = pixCreateTemplate(pixs); pixDilateBrick(pixt6, pixs, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt6, &same); if (!same) { fprintf(stderr, "pixref != pixt6 !\n"); ok = FALSE; } pixt7 = pixCopy(NULL, pixs); pixDilateBrick(pixt7, pixt7, WIDTH, HEIGHT); /* in-place */ pixEqual(pixref, pixt7, &same); if (!same) { fprintf(stderr, "pixref != pixt7 !\n"); ok = FALSE; } pixt8 = pixDilateBrickDwa(NULL, pixs, WIDTH, HEIGHT); /* new one */ pixEqual(pixref, pixt8, &same); if (!same) { fprintf(stderr, "pixref != pixt8 !\n"); ok = FALSE; } pixt9 = pixCreateTemplate(pixs); pixDilateBrickDwa(pixt9, pixs, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt9, &same); if (!same) { fprintf(stderr, "pixref != pixt9 !\n"); ok = FALSE; } pixt10 = pixCopy(NULL, pixs); pixDilateBrickDwa(pixt10, pixt10, WIDTH, HEIGHT); /* in-place */ pixEqual(pixref, pixt10, &same); if (!same) { fprintf(stderr, "pixref != pixt10 !\n"); ok = FALSE; } pixt11 = pixCreateTemplate(pixs); pixDilateCompBrickDwa(pixt11, pixs, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt11, &same); if (!same) { fprintf(stderr, "pixref != pixt11 !\n"); ok = FALSE; } sprintf(sequence, "d%d.%d", WIDTH, HEIGHT); pixt12 = pixMorphCompSequence(pixs, sequence, 0); /* comp sequence */ pixEqual(pixref, pixt12, &same); if (!same) { fprintf(stderr, "pixref != pixt12!\n"); ok = FALSE; } pixt13 = pixMorphSequenceDwa(pixs, sequence, 0); /* dwa sequence */ pixEqual(pixref, pixt13, &same); if (!same) { fprintf(stderr, "pixref != pixt13!\n"); ok = FALSE; } pixDestroy(&pixref); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); pixDestroy(&pixt4); pixDestroy(&pixt5); pixDestroy(&pixt6); pixDestroy(&pixt7); pixDestroy(&pixt8); pixDestroy(&pixt9); pixDestroy(&pixt10); pixDestroy(&pixt11); pixDestroy(&pixt12); pixDestroy(&pixt13); /* Erosion */ fprintf(stderr, "Testing erosion\n"); pixref = pixErode(NULL, pixs, sel); /* new one */ pixt1 = pixCreateTemplate(pixs); pixErode(pixt1, pixs, sel); /* existing one */ pixEqual(pixref, pixt1, &same); if (!same) { fprintf(stderr, "pixref != pixt1 !\n"); ok = FALSE; } pixt2 = pixCopy(NULL, pixs); pixErode(pixt2, pixt2, sel); /* in-place */ pixEqual(pixref, pixt2, &same); if (!same) { fprintf(stderr, "pixref != pixt2 !\n"); ok = FALSE; } sprintf(sequence, "e%d.%d", WIDTH, HEIGHT); pixt3 = pixMorphSequence(pixs, sequence, 0); /* sequence, atomic */ pixEqual(pixref, pixt3, &same); if (!same) { fprintf(stderr, "pixref != pixt3 !\n"); ok = FALSE; } sprintf(sequence, "e%d.1 + e1.%d", WIDTH, HEIGHT); pixt4 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable */ pixEqual(pixref, pixt4, &same); if (!same) { fprintf(stderr, "pixref != pixt4 !\n"); ok = FALSE; } pixt5 = pixErodeBrick(NULL, pixs, WIDTH, HEIGHT); /* new one */ pixEqual(pixref, pixt5, &same); if (!same) { fprintf(stderr, "pixref != pixt5 !\n"); ok = FALSE; } pixt6 = pixCreateTemplate(pixs); pixErodeBrick(pixt6, pixs, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt6, &same); if (!same) { fprintf(stderr, "pixref != pixt6 !\n"); ok = FALSE; } pixt7 = pixCopy(NULL, pixs); pixErodeBrick(pixt7, pixt7, WIDTH, HEIGHT); /* in-place */ pixEqual(pixref, pixt7, &same); if (!same) { fprintf(stderr, "pixref != pixt7 !\n"); ok = FALSE; } pixt8 = pixErodeBrickDwa(NULL, pixs, WIDTH, HEIGHT); /* new one */ pixEqual(pixref, pixt8, &same); if (!same) { fprintf(stderr, "pixref != pixt8 !\n"); ok = FALSE; } pixt9 = pixCreateTemplate(pixs); pixErodeBrickDwa(pixt9, pixs, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt9, &same); if (!same) { fprintf(stderr, "pixref != pixt9 !\n"); ok = FALSE; } pixt10 = pixCopy(NULL, pixs); pixErodeBrickDwa(pixt10, pixt10, WIDTH, HEIGHT); /* in-place */ pixEqual(pixref, pixt10, &same); if (!same) { fprintf(stderr, "pixref != pixt10 !\n"); ok = FALSE; } pixt11 = pixCreateTemplate(pixs); pixErodeCompBrickDwa(pixt11, pixs, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt11, &same); if (!same) { fprintf(stderr, "pixref != pixt11 !\n"); ok = FALSE; } sprintf(sequence, "e%d.%d", WIDTH, HEIGHT); pixt12 = pixMorphCompSequence(pixs, sequence, 0); /* comp sequence */ pixEqual(pixref, pixt12, &same); if (!same) { fprintf(stderr, "pixref != pixt12!\n"); ok = FALSE; } pixt13 = pixMorphSequenceDwa(pixs, sequence, 0); /* dwa sequence */ pixEqual(pixref, pixt13, &same); if (!same) { fprintf(stderr, "pixref != pixt13!\n"); ok = FALSE; } pixDestroy(&pixref); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); pixDestroy(&pixt4); pixDestroy(&pixt5); pixDestroy(&pixt6); pixDestroy(&pixt7); pixDestroy(&pixt8); pixDestroy(&pixt9); pixDestroy(&pixt10); pixDestroy(&pixt11); pixDestroy(&pixt12); pixDestroy(&pixt13); /* Opening */ fprintf(stderr, "Testing opening\n"); pixref = pixOpen(NULL, pixs, sel); /* new one */ pixt1 = pixCreateTemplate(pixs); pixOpen(pixt1, pixs, sel); /* existing one */ pixEqual(pixref, pixt1, &same); if (!same) { fprintf(stderr, "pixref != pixt1 !\n"); ok = FALSE; } pixt2 = pixCopy(NULL, pixs); pixOpen(pixt2, pixt2, sel); /* in-place */ pixEqual(pixref, pixt2, &same); if (!same) { fprintf(stderr, "pixref != pixt2 !\n"); ok = FALSE; } sprintf(sequence, "o%d.%d", WIDTH, HEIGHT); pixt3 = pixMorphSequence(pixs, sequence, 0); /* sequence, atomic */ pixEqual(pixref, pixt3, &same); if (!same) { fprintf(stderr, "pixref != pixt3 !\n"); ok = FALSE; } sprintf(sequence, "e%d.%d + d%d.%d", WIDTH, HEIGHT, WIDTH, HEIGHT); pixt4 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable */ pixEqual(pixref, pixt4, &same); if (!same) { fprintf(stderr, "pixref != pixt4 !\n"); ok = FALSE; } sprintf(sequence, "e%d.1 + e1.%d + d%d.1 + d1.%d", WIDTH, HEIGHT, WIDTH, HEIGHT); pixt5 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable^2 */ pixEqual(pixref, pixt5, &same); if (!same) { fprintf(stderr, "pixref != pixt5 !\n"); ok = FALSE; } pixt6 = pixOpenBrick(NULL, pixs, WIDTH, HEIGHT); /* new one */ pixEqual(pixref, pixt6, &same); if (!same) { fprintf(stderr, "pixref != pixt6 !\n"); ok = FALSE; } pixt7 = pixCreateTemplate(pixs); pixOpenBrick(pixt7, pixs, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt7, &same); if (!same) { fprintf(stderr, "pixref != pixt7 !\n"); ok = FALSE; } pixt8 = pixCopy(NULL, pixs); /* in-place */ pixOpenBrick(pixt8, pixt8, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt8, &same); if (!same) { fprintf(stderr, "pixref != pixt8 !\n"); ok = FALSE; } pixt9 = pixOpenBrickDwa(NULL, pixs, WIDTH, HEIGHT); /* new one */ pixEqual(pixref, pixt9, &same); if (!same) { fprintf(stderr, "pixref != pixt9 !\n"); ok = FALSE; } pixt10 = pixCreateTemplate(pixs); pixOpenBrickDwa(pixt10, pixs, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt10, &same); if (!same) { fprintf(stderr, "pixref != pixt10 !\n"); ok = FALSE; } pixt11 = pixCopy(NULL, pixs); pixOpenBrickDwa(pixt11, pixt11, WIDTH, HEIGHT); /* in-place */ pixEqual(pixref, pixt11, &same); if (!same) { fprintf(stderr, "pixref != pixt11 !\n"); ok = FALSE; } sprintf(sequence, "o%d.%d", WIDTH, HEIGHT); pixt12 = pixMorphCompSequence(pixs, sequence, 0); /* comp sequence */ pixEqual(pixref, pixt12, &same); if (!same) { fprintf(stderr, "pixref != pixt12!\n"); ok = FALSE; } #if 0 pixWrite("/tmp/junkref.png", pixref, IFF_PNG); pixWrite("/tmp/junk12.png", pixt12, IFF_PNG); pixt13 = pixXor(NULL, pixref, pixt12); pixWrite("/tmp/junk12a.png", pixt13, IFF_PNG); pixDestroy(&pixt13); #endif pixt13 = pixMorphSequenceDwa(pixs, sequence, 0); /* dwa sequence */ pixEqual(pixref, pixt13, &same); if (!same) { fprintf(stderr, "pixref != pixt13!\n"); ok = FALSE; } pixt14 = pixCreateTemplate(pixs); pixOpenCompBrickDwa(pixt14, pixs, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt14, &same); if (!same) { fprintf(stderr, "pixref != pixt14 !\n"); ok = FALSE; } pixDestroy(&pixref); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); pixDestroy(&pixt4); pixDestroy(&pixt5); pixDestroy(&pixt6); pixDestroy(&pixt7); pixDestroy(&pixt8); pixDestroy(&pixt9); pixDestroy(&pixt10); pixDestroy(&pixt11); pixDestroy(&pixt12); pixDestroy(&pixt13); pixDestroy(&pixt14); /* Closing */ fprintf(stderr, "Testing closing\n"); pixref = pixClose(NULL, pixs, sel); /* new one */ pixt1 = pixCreateTemplate(pixs); pixClose(pixt1, pixs, sel); /* existing one */ pixEqual(pixref, pixt1, &same); if (!same) { fprintf(stderr, "pixref != pixt1 !\n"); ok = FALSE; } pixt2 = pixCopy(NULL, pixs); pixClose(pixt2, pixt2, sel); /* in-place */ pixEqual(pixref, pixt2, &same); if (!same) { fprintf(stderr, "pixref != pixt2 !\n"); ok = FALSE; } sprintf(sequence, "d%d.%d + e%d.%d", WIDTH, HEIGHT, WIDTH, HEIGHT); pixt3 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable */ pixEqual(pixref, pixt3, &same); if (!same) { fprintf(stderr, "pixref != pixt3 !\n"); ok = FALSE; } sprintf(sequence, "d%d.1 + d1.%d + e%d.1 + e1.%d", WIDTH, HEIGHT, WIDTH, HEIGHT); pixt4 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable^2 */ pixEqual(pixref, pixt4, &same); if (!same) { fprintf(stderr, "pixref != pixt4 !\n"); ok = FALSE; } pixt5 = pixCloseBrick(NULL, pixs, WIDTH, HEIGHT); /* new one */ pixEqual(pixref, pixt5, &same); if (!same) { fprintf(stderr, "pixref != pixt5 !\n"); ok = FALSE; } pixt6 = pixCreateTemplate(pixs); pixCloseBrick(pixt6, pixs, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt6, &same); if (!same) { fprintf(stderr, "pixref != pixt6 !\n"); ok = FALSE; } pixt7 = pixCopy(NULL, pixs); /* in-place */ pixCloseBrick(pixt7, pixt7, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt7, &same); if (!same) { fprintf(stderr, "pixref != pixt7 !\n"); ok = FALSE; } pixDestroy(&pixref); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); pixDestroy(&pixt4); pixDestroy(&pixt5); pixDestroy(&pixt6); pixDestroy(&pixt7); /* Safe closing (using pix, not pixs) */ fprintf(stderr, "Testing safe closing\n"); pixref = pixCloseSafe(NULL, pixs, sel); /* new one */ pixt1 = pixCreateTemplate(pixs); pixCloseSafe(pixt1, pixs, sel); /* existing one */ pixEqual(pixref, pixt1, &same); if (!same) { fprintf(stderr, "pixref != pixt1 !\n"); ok = FALSE; } pixt2 = pixCopy(NULL, pixs); pixCloseSafe(pixt2, pixt2, sel); /* in-place */ pixEqual(pixref, pixt2, &same); if (!same) { fprintf(stderr, "pixref != pixt2 !\n"); ok = FALSE; } sprintf(sequence, "c%d.%d", WIDTH, HEIGHT); pixt3 = pixMorphSequence(pixs, sequence, 0); /* sequence, atomic */ pixEqual(pixref, pixt3, &same); if (!same) { fprintf(stderr, "pixref != pixt3 !\n"); ok = FALSE; } sprintf(sequence, "b32 + d%d.%d + e%d.%d", WIDTH, HEIGHT, WIDTH, HEIGHT); pixt4 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable */ pixEqual(pixref, pixt4, &same); if (!same) { fprintf(stderr, "pixref != pixt4 !\n"); ok = FALSE; } sprintf(sequence, "b32 + d%d.1 + d1.%d + e%d.1 + e1.%d", WIDTH, HEIGHT, WIDTH, HEIGHT); pixt5 = pixMorphSequence(pixs, sequence, 0); /* sequence, separable^2 */ pixEqual(pixref, pixt5, &same); if (!same) { fprintf(stderr, "pixref != pixt5 !\n"); ok = FALSE; } pixt6 = pixCloseSafeBrick(NULL, pixs, WIDTH, HEIGHT); /* new one */ pixEqual(pixref, pixt6, &same); if (!same) { fprintf(stderr, "pixref != pixt6 !\n"); ok = FALSE; } pixt7 = pixCreateTemplate(pixs); pixCloseSafeBrick(pixt7, pixs, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt7, &same); if (!same) { fprintf(stderr, "pixref != pixt7 !\n"); ok = FALSE; } pixt8 = pixCopy(NULL, pixs); /* in-place */ pixCloseSafeBrick(pixt8, pixt8, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt8, &same); if (!same) { fprintf(stderr, "pixref != pixt8 !\n"); ok = FALSE; } pixt9 = pixCloseBrickDwa(NULL, pixs, WIDTH, HEIGHT); /* new one */ pixEqual(pixref, pixt9, &same); if (!same) { fprintf(stderr, "pixref != pixt9 !\n"); ok = FALSE; } pixt10 = pixCreateTemplate(pixs); pixCloseBrickDwa(pixt10, pixs, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt10, &same); if (!same) { fprintf(stderr, "pixref != pixt10 !\n"); ok = FALSE; } pixt11 = pixCopy(NULL, pixs); pixCloseBrickDwa(pixt11, pixt11, WIDTH, HEIGHT); /* in-place */ pixEqual(pixref, pixt11, &same); if (!same) { fprintf(stderr, "pixref != pixt11 !\n"); ok = FALSE; } sprintf(sequence, "c%d.%d", WIDTH, HEIGHT); pixt12 = pixMorphCompSequence(pixs, sequence, 0); /* comp sequence */ pixEqual(pixref, pixt12, &same); if (!same) { fprintf(stderr, "pixref != pixt12!\n"); ok = FALSE; } pixt13 = pixMorphSequenceDwa(pixs, sequence, 0); /* dwa sequence */ pixEqual(pixref, pixt13, &same); if (!same) { fprintf(stderr, "pixref != pixt13!\n"); ok = FALSE; } pixt14 = pixCreateTemplate(pixs); pixCloseCompBrickDwa(pixt14, pixs, WIDTH, HEIGHT); /* existing one */ pixEqual(pixref, pixt14, &same); if (!same) { fprintf(stderr, "pixref != pixt14 !\n"); ok = FALSE; } #if 0 pixWrite("/tmp/junkref.png", pixref, IFF_PNG); pixWrite("/tmp/junk12.png", pixt12, IFF_PNG); pixt13 = pixXor(NULL, pixref, pixt12); pixWrite("/tmp/junk12a.png", pixt13, IFF_PNG); pixDestroy(&pixt13); #endif pixDestroy(&pixref); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); pixDestroy(&pixt4); pixDestroy(&pixt5); pixDestroy(&pixt6); pixDestroy(&pixt7); pixDestroy(&pixt8); pixDestroy(&pixt9); pixDestroy(&pixt10); pixDestroy(&pixt11); pixDestroy(&pixt12); pixDestroy(&pixt13); pixDestroy(&pixt14); if (ok) fprintf(stderr, "All morph tests OK!\n"); pixDestroy(&pixs); selDestroy(&sel); exit(0); }
main(int argc, char **argv) { char *filename; l_int32 w, h, type, maxboxes; l_float32 ovlap; BOX *box; BOXA *boxa, *boxat, *boxad; PIX *pix, *pixt, *pixs, *pixd; static char mainName[] = "partitiontest"; if (argc != 3 && argc != 5) return ERROR_INT("syntax: partitiontest <fname> type [maxboxes ovlap]", mainName, 1); filename = argv[1]; type = atoi(argv[2]); if (type == L_SORT_BY_WIDTH) fprintf(stderr, "Sorting by width:\n"); else if (type == L_SORT_BY_HEIGHT) fprintf(stderr, "Sorting by height:\n"); else if (type == L_SORT_BY_MAX_DIMENSION) fprintf(stderr, "Sorting by maximum dimension:\n"); else if (type == L_SORT_BY_MIN_DIMENSION) fprintf(stderr, "Sorting by minimum dimension:\n"); else if (type == L_SORT_BY_PERIMETER) fprintf(stderr, "Sorting by perimeter:\n"); else if (type == L_SORT_BY_AREA) fprintf(stderr, "Sorting by area:\n"); else { fprintf(stderr, "Use one of the following for 'type':\n" " 5: L_SORT_BY_WIDTH\n" " 6: L_SORT_BY_HEIGHT\n" " 7: L_SORT_BY_MIN_DIMENSION\n" " 8: L_SORT_BY_MAX_DIMENSION\n" " 9: L_SORT_BY_PERIMETER\n" " 10: L_SORT_BY_AREA\n"); return ERROR_INT("invalid type: see source", mainName, 1); } if (argc == 5) { maxboxes = atoi(argv[3]); ovlap = atof(argv[4]); } else { maxboxes = 100; ovlap = 0.2; } pix = pixRead(filename); pixs = pixConvertTo1(pix, 128); pixDilateBrick(pixs, pixs, 5, 5); boxa = pixConnComp(pixs, NULL, 4); pixGetDimensions(pixs, &w, &h, NULL); box = boxCreate(0, 0, w, h); startTimer(); boxaPermuteRandom(boxa, boxa); boxat = boxaSelectBySize(boxa, 500, 500, L_SELECT_IF_BOTH, L_SELECT_IF_LT, NULL); boxad = boxaGetWhiteblocks(boxat, box, type, maxboxes, ovlap, 200, 0.15, 20000); fprintf(stderr, "Time: %7.3f sec\n", stopTimer()); boxaWriteStream(stderr, boxad); pixDisplayWrite(NULL, -1); pixDisplayWrite(pixs, REDUCTION); /* Display box outlines in a single color in a cmapped image */ pixd = pixDrawBoxa(pixs, boxad, 7, 0xe0708000); pixDisplayWrite(pixd, REDUCTION); pixDestroy(&pixd); /* Display box outlines in a single color in an RGB image */ pixt = pixConvertTo8(pixs, FALSE); pixd = pixDrawBoxa(pixt, boxad, 7, 0x40a0c000); pixDisplayWrite(pixd, REDUCTION); pixDestroy(&pixt); pixDestroy(&pixd); /* Display box outlines with random colors in a cmapped image */ pixd = pixDrawBoxaRandom(pixs, boxad, 7); pixDisplayWrite(pixd, REDUCTION); pixDestroy(&pixd); /* Display box outlines with random colors in an RGB image */ pixt = pixConvertTo8(pixs, FALSE); pixd = pixDrawBoxaRandom(pixt, boxad, 7); pixDisplayWrite(pixd, REDUCTION); pixDestroy(&pixt); pixDestroy(&pixd); /* Display boxes in the same color in a cmapped image */ pixd = pixPaintBoxa(pixs, boxad, 0x60e0a000); pixDisplayWrite(pixd, REDUCTION); pixDestroy(&pixd); /* Display boxes in the same color in an RGB image */ pixt = pixConvertTo8(pixs, FALSE); pixd = pixPaintBoxa(pixt, boxad, 0xc030a000); pixDisplayWrite(pixd, REDUCTION); pixDestroy(&pixt); pixDestroy(&pixd); /* Display boxes in random colors in a cmapped image */ pixd = pixPaintBoxaRandom(pixs, boxad); pixDisplayWrite(pixd, REDUCTION); pixDestroy(&pixd); /* Display boxes in random colors in an RGB image */ pixt = pixConvertTo8(pixs, FALSE); pixd = pixPaintBoxaRandom(pixt, boxad); pixDisplayWrite(pixd, REDUCTION); pixDestroy(&pixt); pixDestroy(&pixd); pixDisplayMultiple("/tmp/junk_write_display*"); pixDestroy(&pix); pixDestroy(&pixs); boxDestroy(&box); boxaDestroy(&boxa); boxaDestroy(&boxat); boxaDestroy(&boxad); return 0; }
l_int32 DoComparisonDwa2(PIX *pixs, PIX *pixt1, PIX *pixt2, PIX *pixt3, PIX *pixt4, PIX *pixt5, PIX *pixt6, l_int32 size) /* exactly decomposable */ { fprintf(stderr, "..%d..", size); if (TIMING) startTimer(); pixDilateCompBrickExtendDwa(pixt1, pixs, size, 1); pixDilateCompBrickExtendDwa(pixt3, pixs, 1, size); pixDilateCompBrickExtendDwa(pixt5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixDilateBrick(pixt2, pixs, size, 1); pixDilateBrick(pixt4, pixs, 1, size); pixDilateBrick(pixt6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(size, "dilate", pixt1, pixt2, pixt3, pixt4, pixt5, pixt6); if (TIMING) startTimer(); pixErodeCompBrickExtendDwa(pixt1, pixs, size, 1); pixErodeCompBrickExtendDwa(pixt3, pixs, 1, size); pixErodeCompBrickExtendDwa(pixt5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixErodeBrick(pixt2, pixs, size, 1); pixErodeBrick(pixt4, pixs, 1, size); pixErodeBrick(pixt6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(size, "erode", pixt1, pixt2, pixt3, pixt4, pixt5, pixt6); if (TIMING) startTimer(); pixOpenCompBrickExtendDwa(pixt1, pixs, size, 1); pixOpenCompBrickExtendDwa(pixt3, pixs, 1, size); pixOpenCompBrickExtendDwa(pixt5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixOpenBrick(pixt2, pixs, size, 1); pixOpenBrick(pixt4, pixs, 1, size); pixOpenBrick(pixt6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(size, "open", pixt1, pixt2, pixt3, pixt4, pixt5, pixt6); if (TIMING) startTimer(); pixCloseCompBrickExtendDwa(pixt1, pixs, size, 1); pixCloseCompBrickExtendDwa(pixt3, pixs, 1, size); pixCloseCompBrickExtendDwa(pixt5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixCloseSafeBrick(pixt2, pixs, size, 1); pixCloseSafeBrick(pixt4, pixs, 1, size); pixCloseSafeBrick(pixt6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(size, "close", pixt1, pixt2, pixt3, pixt4, pixt5, pixt6); #if 0 pixWrite("/tmp/junkpixt3.png", pixt3, IFF_PNG); pixWrite("/tmp/junkpixt4.png", pixt4, IFF_PNG); pixXor(pixt3, pixt3, pixt4); pixWrite("/tmp/junkxor.png", pixt3, IFF_PNG); #endif return 0; }
/* dwa composite with morph non-composite */ l_int32 DoComparisonDwa5(PIX *pixs, PIX *pixt1, PIX *pixt2, PIX *pixt3, PIX *pixt4, PIX *pixt5, PIX *pixt6, l_int32 isize) { l_int32 fact1, fact2, size; selectComposableSizes(isize, &fact1, &fact2); size = fact1 * fact2; fprintf(stderr, "..%d..", size); if (TIMING) startTimer(); pixDilateCompBrickDwa(pixt1, pixs, size, 1); pixDilateCompBrickDwa(pixt3, pixs, 1, size); pixDilateCompBrickDwa(pixt5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixDilateBrick(pixt2, pixs, size, 1); pixDilateBrick(pixt4, pixs, 1, size); pixDilateBrick(pixt6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(size, "dilate", pixt1, pixt2, pixt3, pixt4, pixt5, pixt6); /* pixDisplay(pixt1, 100, 100); */ /* pixDisplay(pixt2, 800, 100); */ if (TIMING) startTimer(); pixErodeCompBrickDwa(pixt1, pixs, size, 1); pixErodeCompBrickDwa(pixt3, pixs, 1, size); pixErodeCompBrickDwa(pixt5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixErodeBrick(pixt2, pixs, size, 1); pixErodeBrick(pixt4, pixs, 1, size); pixErodeBrick(pixt6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(size, "erode", pixt1, pixt2, pixt3, pixt4, pixt5, pixt6); if (TIMING) startTimer(); pixOpenCompBrickDwa(pixt1, pixs, size, 1); pixOpenCompBrickDwa(pixt3, pixs, 1, size); pixOpenCompBrickDwa(pixt5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixOpenBrick(pixt2, pixs, size, 1); pixOpenBrick(pixt4, pixs, 1, size); pixOpenBrick(pixt6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(size, "open", pixt1, pixt2, pixt3, pixt4, pixt5, pixt6); if (TIMING) startTimer(); pixCloseCompBrickDwa(pixt1, pixs, size, 1); pixCloseCompBrickDwa(pixt3, pixs, 1, size); pixCloseCompBrickDwa(pixt5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixCloseSafeBrick(pixt2, pixs, size, 1); pixCloseSafeBrick(pixt4, pixs, 1, size); pixCloseSafeBrick(pixt6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(size, "close", pixt1, pixt2, pixt3, pixt4, pixt5, pixt6); return 0; }
int main(int argc, char **argv) { PIX *pixs, *pix1, *pix2, *pix3, *pix4; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pixs = pixRead("test1.png"); /* pixInvert */ pix1 = pixInvert(NULL, pixs); pix2 = pixCreateTemplate(pixs); /* into pixd of same size */ pixInvert(pix2, pixs); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */ regTestComparePix(rp, pix1, pix2); /* 1 */ pix3 = pixRead("marge.jpg"); /* into pixd of different size */ pixInvert(pix3, pixs); regTestComparePix(rp, pix1, pix3); /* 2 */ pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pix1 = pixOpenBrick(NULL, pixs, 1, 9); pix2 = pixDilateBrick(NULL, pixs, 1, 9); /* pixOr */ pix3 = pixCreateTemplate(pixs); pixOr(pix3, pixs, pix1); /* existing */ pix4 = pixOr(NULL, pixs, pix1); /* new */ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3 */ regTestComparePix(rp, pix3, pix4); /* 4 */ pixCopy(pix4, pix1); pixOr(pix4, pix4, pixs); /* in-place */ regTestComparePix(rp, pix3, pix4); /* 5 */ pixDestroy(&pix3); pixDestroy(&pix4); pix3 = pixCreateTemplate(pixs); pixOr(pix3, pixs, pix2); /* existing */ pix4 = pixOr(NULL, pixs, pix2); /* new */ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 6 */ regTestComparePix(rp, pix3, pix4); /* 7 */ pixCopy(pix4, pix2); pixOr(pix4, pix4, pixs); /* in-place */ regTestComparePix(rp, pix3, pix4); /* 8 */ pixDestroy(&pix3); pixDestroy(&pix4); /* pixAnd */ pix3 = pixCreateTemplate(pixs); pixAnd(pix3, pixs, pix1); /* existing */ pix4 = pixAnd(NULL, pixs, pix1); /* new */ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 9 */ regTestComparePix(rp, pix3, pix4); /* 10 */ pixCopy(pix4, pix1); pixAnd(pix4, pix4, pixs); /* in-place */ regTestComparePix(rp, pix3, pix4); /* 11 */ pixDestroy(&pix3); pixDestroy(&pix4); pix3 = pixCreateTemplate(pixs); pixAnd(pix3, pixs, pix2); /* existing */ pix4 = pixAnd(NULL, pixs, pix2); /* new */ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 12 */ regTestComparePix(rp, pix3, pix4); /* 13 */ pixCopy(pix4, pix2); pixAnd(pix4, pix4, pixs); /* in-place */ regTestComparePix(rp, pix3, pix4); /* 14 */ pixDestroy(&pix3); pixDestroy(&pix4); /* pixXor */ pix3 = pixCreateTemplate(pixs); pixXor(pix3, pixs, pix1); /* existing */ pix4 = pixXor(NULL, pixs, pix1); /* new */ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 15 */ regTestComparePix(rp, pix3, pix4); /* 16 */ pixCopy(pix4, pix1); pixXor(pix4, pix4, pixs); /* in-place */ regTestComparePix(rp, pix3, pix4); /* 17 */ pixDestroy(&pix3); pixDestroy(&pix4); pix3 = pixCreateTemplate(pixs); pixXor(pix3, pixs, pix2); /* existing */ pix4 = pixXor(NULL, pixs, pix2); /* new */ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 18 */ regTestComparePix(rp, pix3, pix4); /* 19 */ pixCopy(pix4, pix2); pixXor(pix4, pix4, pixs); /* in-place */ regTestComparePix(rp, pix3, pix4); /* 20 */ pixDestroy(&pix3); pixDestroy(&pix4); /* pixSubtract */ pix3 = pixCreateTemplate(pixs); pixSubtract(pix3, pixs, pix1); /* existing */ pix4 = pixSubtract(NULL, pixs, pix1); /* new */ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 21 */ regTestComparePix(rp, pix3, pix4); /* 22 */ pixCopy(pix4, pix1); pixSubtract(pix4, pixs, pix4); /* in-place */ regTestComparePix(rp, pix3, pix4); /* 23 */ pixDestroy(&pix3); pixDestroy(&pix4); pix3 = pixCreateTemplate(pixs); pixSubtract(pix3, pixs, pix2); /* existing */ pix4 = pixSubtract(NULL, pixs, pix2); /* new */ regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 24 */ regTestComparePix(rp, pix3, pix4); /* 25 */ pixCopy(pix4, pix2); pixSubtract(pix4, pixs, pix4); /* in-place */ regTestComparePix(rp, pix3, pix4); /* 26 */ pixDestroy(&pix3); pixDestroy(&pix4); pix4 = pixRead("marge.jpg"); pixSubtract(pix4, pixs, pixs); /* subtract from itself; should be empty */ pix3 = pixCreateTemplate(pixs); regTestComparePix(rp, pix3, pix4); /* 27*/ pixDestroy(&pix3); pixDestroy(&pix4); pixSubtract(pixs, pixs, pixs); /* subtract from itself; should be empty */ pix3 = pixCreateTemplate(pixs); regTestComparePix(rp, pix3, pixs); /* 28*/ pixDestroy(&pix3); pixDestroy(&pixs); pixDestroy(&pix1); pixDestroy(&pix2); return regTestCleanup(rp); }
/*! * pixMorphSequence() * * Input: pixs * sequence (string specifying sequence) * dispsep (horizontal separation in pixels between * successive displays; use zero to suppress display) * Return: pixd, or null on error * * Notes: * (1) This does rasterop morphology on binary images. * (2) This runs a pipeline of operations; no branching is allowed. * (3) This only uses brick Sels, which are created on the fly. * In the future this will be generalized to extract Sels from * a Sela by name. * (4) A new image is always produced; the input image is not changed. * (5) This contains an interpreter, allowing sequences to be * generated and run. * (6) The format of the sequence string is defined below. * (7) In addition to morphological operations, rank order reduction * and replicated expansion allow operations to take place * downscaled by a power of 2. * (8) Intermediate results can optionally be displayed. * (9) Thanks to Dar-Shyang Lee, who had the idea for this and * built the first implementation. * (10) The sequence string is formatted as follows: * - An arbitrary number of operations, each separated * by a '+' character. White space is ignored. * - Each operation begins with a case-independent character * specifying the operation: * d or D (dilation) * e or E (erosion) * o or O (opening) * c or C (closing) * r or R (rank binary reduction) * x or X (replicative binary expansion) * b or B (add a border of 0 pixels of this size) * - The args to the morphological operations are bricks of hits, * and are formatted as a.b, where a and b are horizontal and * vertical dimensions, rsp. * - The args to the reduction are a sequence of up to 4 integers, * each from 1 to 4. * - The arg to the expansion is a power of two, in the set * {2, 4, 8, 16}. * (11) An example valid sequence is: * "b32 + o1.3 + C3.1 + r23 + e2.2 + D3.2 + X4" * In this example, the following operation sequence is carried out: * * b32: Add a 32 pixel border around the input image * * o1.3: Opening with vert sel of length 3 (e.g., 1 x 3) * * C3.1: Closing with horiz sel of length 3 (e.g., 3 x 1) * * r23: Two successive 2x2 reductions with rank 2 in the first * and rank 3 in the second. The result is a 4x reduced pix. * * e2.2: Erosion with a 2x2 sel (origin will be at x,y: 0,0) * * d3.2: Dilation with a 3x2 sel (origin will be at x,y: 1,0) * * X4: 4x replicative expansion, back to original resolution * (12) The safe closing is used. However, if you implement a * closing as separable dilations followed by separable erosions, * it will not be safe. For that situation, you need to add * a sufficiently large border as the first operation in * the sequence. This will be removed automatically at the * end. There are two cautions: * - When computing what is sufficient, remember that if * reductions are carried out, the border is also reduced. * - The border is removed at the end, so if a border is * added at the beginning, the result must be at the * same resolution as the input! */ PIX * pixMorphSequence(PIX *pixs, const char *sequence, l_int32 dispsep) { char *rawop, *op; l_int32 nops, i, j, nred, fact, w, h, x, y, border; l_int32 level[4]; PIX *pixt1, *pixt2; SARRAY *sa; PROCNAME("pixMorphSequence"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!sequence) return (PIX *)ERROR_PTR("sequence not defined", procName, NULL); /* Split sequence into individual operations */ sa = sarrayCreate(0); sarraySplitString(sa, sequence, "+"); nops = sarrayGetCount(sa); if (!morphSequenceVerify(sa)) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence not valid", procName, NULL); } /* Parse and operate */ border = 0; pixt1 = pixCopy(NULL, pixs); pixt2 = NULL; x = y = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, 0); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixDilateBrick(NULL, pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixErodeBrick(NULL, pixt1, w, h); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixOpenBrick(pixt1, pixt1, w, h); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixCloseSafeBrick(pixt1, pixt1, w, h); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'r': case 'R': nred = strlen(op) - 1; for (j = 0; j < nred; j++) level[j] = op[j + 1] - '0'; for (j = nred; j < 4; j++) level[j] = 0; pixt2 = pixReduceRankBinaryCascade(pixt1, level[0], level[1], level[2], level[3]); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'x': case 'X': sscanf(&op[1], "%d", &fact); pixt2 = pixExpandReplicate(pixt1, fact); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; case 'b': case 'B': sscanf(&op[1], "%d", &border); pixt2 = pixAddBorder(pixt1, border, 0); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } break; default: /* All invalid ops are caught in the first pass */ break; } FREE(op); } if (border > 0) { pixt2 = pixRemoveBorder(pixt1, border); pixDestroy(&pixt1); pixt1 = pixClone(pixt2); pixDestroy(&pixt2); } sarrayDestroy(&sa); return pixt1; }
/*! * Note: this method is generally inferior to pixHasColorRegions(); it * is retained as a reference only * * \brief pixFindColorRegionsLight() * * \param[in] pixs 32 bpp rgb * \param[in] pixm [optional] 1 bpp mask image * \param[in] factor subsample factor; integer >= 1 * \param[in] darkthresh threshold to eliminate dark pixels (e.g., text) * from consideration; typ. 70; -1 for default. * \param[in] lightthresh threshold for minimum gray value at 95% rank * near white; typ. 220; -1 for default * \param[in] mindiff minimum difference from 95% rank value, used * to count darker pixels; typ. 50; -1 for default * \param[in] colordiff minimum difference in (max - min) component to * qualify as a color pixel; typ. 40; -1 for default * \param[out] pcolorfract fraction of 'color' pixels found * \param[out] pcolormask1 [optional] mask over background color, if any * \param[out] pcolormask2 [optional] filtered mask over background color * \param[out] pixadb [optional] debug intermediate results * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) This function tries to determine if there is a significant * color or darker region on a scanned page image where part * of the image is very close to "white". It will also allow * extraction of small regions of lightly colored pixels. * If the background is darker (and reddish), use instead * pixHasColorRegions2(). * (2) If %pixm exists, only pixels under fg are considered. Typically, * the inverse of %pixm would have fg pixels over a photograph. * (3) There are four thresholds. * * %darkthresh: ignore pixels darker than this (typ. fg text). * We make a 1 bpp mask of these pixels, and then dilate it to * remove all vestiges of fg from their vicinity. * * %lightthresh: let val95 be the pixel value for which 95% * of the non-masked pixels have a lower value (darker) of * their min component. Then if val95 is darker than * %lightthresh, the image is not considered to have a * light bg, and this returns 0.0 for %colorfract. * * %mindiff: we are interested in the fraction of pixels that * have two conditions. The first is that their min component * is at least %mindiff darker than val95. * * %colordiff: the second condition is that the max-min diff * of the pixel components exceeds %colordiff. * (4) This returns in %pcolorfract the fraction of pixels that have * both a min component that is at least %mindiff below that at the * 95% rank value (where 100% rank is the lightest value), and * a max-min diff that is at least %colordiff. Without the * %colordiff constraint, gray pixels of intermediate value * could get flagged by this function. * (5) No masks are returned unless light color pixels are found. * If colorfract > 0.0 and %pcolormask1 is defined, this returns * a 1 bpp mask with fg pixels over the color background. * This mask may have some holes in it. * (6) If colorfract > 0.0 and %pcolormask2 is defined, this returns * a filtered version of colormask1. The two changes are * (a) small holes have been filled * (b) components near the border have been removed. * The latter insures that dark pixels near the edge of the * image are not included. * (7) To generate a boxa of rectangular regions from the overlap * of components in the filtered mask: * boxa1 = pixConnCompBB(colormask2, 8); * boxa2 = boxaCombineOverlaps(boxa1); * This is done here in debug mode. * </pre> */ static l_int32 pixFindColorRegionsLight(PIX *pixs, PIX *pixm, l_int32 factor, l_int32 darkthresh, l_int32 lightthresh, l_int32 mindiff, l_int32 colordiff, l_float32 *pcolorfract, PIX **pcolormask1, PIX **pcolormask2, PIXA *pixadb) { l_int32 lightbg, w, h, count; l_float32 ratio, val95, rank; BOXA *boxa1, *boxa2; NUMA *nah; PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pixm1, *pixm2, *pixm3; PROCNAME("pixFindColorRegionsLight"); if (pcolormask1) *pcolormask1 = NULL; if (pcolormask2) *pcolormask2 = NULL; if (!pcolorfract) return ERROR_INT("&colorfract not defined", procName, 1); *pcolorfract = 0.0; if (!pixs || pixGetDepth(pixs) != 32) return ERROR_INT("pixs not defined or not 32 bpp", procName, 1); if (factor < 1) factor = 1; if (darkthresh < 0) darkthresh = 70; /* defaults */ if (lightthresh < 0) lightthresh = 220; if (mindiff < 0) mindiff = 50; if (colordiff < 0) colordiff = 40; /* Check if pixm covers most of the image. If so, just return. */ pixGetDimensions(pixs, &w, &h, NULL); if (pixm) { pixCountPixels(pixm, &count, NULL); ratio = (l_float32)count / ((l_float32)(w) * h); if (ratio > 0.7) { if (pixadb) L_INFO("pixm has big fg: %f5.2\n", procName, ratio); return 0; } } /* Make a mask pixm1 over the dark pixels in the image: * convert to gray using the average of the components; * threshold using %darkthresh; do a small dilation; * combine with pixm. */ pix1 = pixConvertRGBToGray(pixs, 0.33, 0.34, 0.33); if (pixadb) pixaAddPix(pixadb, pixs, L_COPY); if (pixadb) pixaAddPix(pixadb, pix1, L_COPY); pixm1 = pixThresholdToBinary(pix1, darkthresh); pixDilateBrick(pixm1, pixm1, 7, 7); if (pixadb) pixaAddPix(pixadb, pixm1, L_COPY); if (pixm) { pixOr(pixm1, pixm1, pixm); if (pixadb) pixaAddPix(pixadb, pixm1, L_COPY); } pixDestroy(&pix1); /* Convert to gray using the minimum component value and * find the gray value at rank 0.95, that represents the light * pixels in the image. If it is too dark, quit. */ pix1 = pixConvertRGBToGrayMinMax(pixs, L_SELECT_MIN); pix2 = pixInvert(NULL, pixm1); /* pixels that are not dark */ pixGetRankValueMasked(pix1, pix2, 0, 0, factor, 0.95, &val95, &nah); pixDestroy(&pix2); if (pixadb) { L_INFO("val at 0.95 rank = %5.1f\n", procName, val95); gplotSimple1(nah, GPLOT_PNG, "/tmp/lept/histo1", "gray histo"); pix3 = pixRead("/tmp/lept/histo1.png"); pix4 = pixExpandReplicate(pix3, 2); pixaAddPix(pixadb, pix4, L_INSERT); pixDestroy(&pix3); } lightbg = (l_int32)val95 >= lightthresh; numaDestroy(&nah); if (!lightbg) { pixDestroy(&pix1); pixDestroy(&pixm1); return 0; } /* Make mask pixm2 over pixels that are darker than val95 - mindiff. */ pixm2 = pixThresholdToBinary(pix1, val95 - mindiff); if (pixadb) pixaAddPix(pixadb, pixm2, L_COPY); pixDestroy(&pix1); /* Make a mask pixm3 over pixels that have some color saturation, * with a (max - min) component difference >= %colordiff, * and combine using AND with pixm2. */ pix2 = pixConvertRGBToGrayMinMax(pixs, L_CHOOSE_MAXDIFF); pixm3 = pixThresholdToBinary(pix2, colordiff); pixDestroy(&pix2); pixInvert(pixm3, pixm3); /* need pixels above threshold */ if (pixadb) pixaAddPix(pixadb, pixm3, L_COPY); pixAnd(pixm2, pixm2, pixm3); if (pixadb) pixaAddPix(pixadb, pixm2, L_COPY); pixDestroy(&pixm3); /* Subtract the dark pixels represented by pixm1. * pixm2 now holds all the color pixels of interest */ pixSubtract(pixm2, pixm2, pixm1); pixDestroy(&pixm1); if (pixadb) pixaAddPix(pixadb, pixm2, L_COPY); /* But we're not quite finished. Remove pixels from any component * that is touching the image border. False color pixels can * sometimes be found there if the image is much darker near * the border, due to oxidation or reduced illumination. */ pixm3 = pixRemoveBorderConnComps(pixm2, 8); pixDestroy(&pixm2); if (pixadb) pixaAddPix(pixadb, pixm3, L_COPY); /* Get the fraction of light color pixels */ pixCountPixels(pixm3, &count, NULL); *pcolorfract = (l_float32)count / (w * h); if (pixadb) { if (count == 0) L_INFO("no light color pixels found\n", procName); else L_INFO("fraction of light color pixels = %5.3f\n", procName, *pcolorfract); } /* Debug: extract the color pixels from pixs */ if (pixadb && count > 0) { /* Use pixm3 to extract the color pixels */ pix3 = pixCreateTemplate(pixs); pixSetAll(pix3); pixCombineMasked(pix3, pixs, pixm3); pixaAddPix(pixadb, pix3, L_INSERT); /* Use additional filtering to extract the color pixels */ pix3 = pixCloseSafeBrick(NULL, pixm3, 15, 15); pixaAddPix(pixadb, pix3, L_INSERT); pix5 = pixCreateTemplate(pixs); pixSetAll(pix5); pixCombineMasked(pix5, pixs, pix3); pixaAddPix(pixadb, pix5, L_INSERT); /* Get the combined bounding boxes of the mask components * in pix3, and extract those pixels from pixs. */ boxa1 = pixConnCompBB(pix3, 8); boxa2 = boxaCombineOverlaps(boxa1, NULL); pix4 = pixCreateTemplate(pix3); pixMaskBoxa(pix4, pix4, boxa2, L_SET_PIXELS); pixaAddPix(pixadb, pix4, L_INSERT); pix5 = pixCreateTemplate(pixs); pixSetAll(pix5); pixCombineMasked(pix5, pixs, pix4); pixaAddPix(pixadb, pix5, L_INSERT); boxaDestroy(&boxa1); boxaDestroy(&boxa2); pixaAddPix(pixadb, pixs, L_COPY); } /* Optional colormask returns */ if (pcolormask2 && count > 0) *pcolormask2 = pixCloseSafeBrick(NULL, pixm3, 15, 15); if (pcolormask1 && count > 0) *pcolormask1 = pixm3; else pixDestroy(&pixm3); return 0; }
int main(int argc, char **argv) { char *filein, *fileout; l_int32 i; l_uint32 val; l_float32 size; PIX *pixs, *pixd, *pixm, *pixmi, *pixt1, *pixt2, *pixt3; static char mainName[] = "seedfilltest"; if (argc != 3) return ERROR_INT(" Syntax: seedfilltest filein fileout", mainName, 1); filein = argv[1]; fileout = argv[2]; pixd = NULL; if ((pixm = pixRead(filein)) == NULL) return ERROR_INT("pixm not made", mainName, 1); pixmi = pixInvert(NULL, pixm); size = pixGetWidth(pixm) * pixGetHeight(pixm); pixs = pixCreateTemplate(pixm); for (i = 0; i < 100; i++) { pixGetPixel(pixm, XS + 5 * i, YS + 5 * i, &val); if (val == 0) break; } if (i == 100) return ERROR_INT("no seed pixel found", mainName, 1); pixSetPixel(pixs, XS + 5 * i, YS + 5 * i, 1); #if 0 /* hole filling; use "hole-filler.png" */ pixt1 = pixHDome(pixmi, 100, 4); pixt2 = pixThresholdToBinary(pixt1, 10); /* pixInvert(pixt1, pixt1); */ pixDisplay(pixt1, 100, 500); pixDisplay(pixt2, 600, 500); pixt3 = pixHolesByFilling(pixt2, 4); pixDilateBrick(pixt3, pixt3, 7, 7); pixd = pixConvertTo8(pixt3, FALSE); pixDisplay(pixd, 0, 100); pixSeedfillGray(pixd, pixmi, CONNECTIVITY); pixInvert(pixd, pixd); pixDisplay(pixmi, 500, 100); pixDisplay(pixd, 1000, 100); pixWrite("/tmp/junkpixm.png", pixmi, IFF_PNG); pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG); #endif #if 0 /* hole filling; use "hole-filler.png" */ pixt1 = pixThresholdToBinary(pixm, 110); pixInvert(pixt1, pixt1); pixDisplay(pixt1, 100, 500); pixt2 = pixHolesByFilling(pixt1, 4); pixd = pixConvertTo8(pixt2, FALSE); pixDisplay(pixd, 0, 100); pixSeedfillGray(pixd, pixmi, CONNECTIVITY); pixInvert(pixd, pixd); pixDisplay(pixmi, 500, 100); pixDisplay(pixd, 1000, 100); pixWrite("/tmp/junkpixm.png", pixmi, IFF_PNG); pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG); #endif #if 0 /* hole filling; use "hole-filler.png" */ pixd = pixInvert(NULL, pixm); pixAddConstantGray(pixd, -50); pixDisplay(pixd, 0, 100); /* pixt1 = pixThresholdToBinary(pixd, 20); pixDisplayWithTitle(pixt1, 600, 600, "pixt1", DFLAG); */ pixSeedfillGray(pixd, pixmi, CONNECTIVITY); /* pixInvert(pixd, pixd); */ pixDisplay(pixmi, 500, 100); pixDisplay(pixd, 1000, 100); pixWrite("/tmp/junkpixm.png", pixmi, IFF_PNG); pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG); #endif #if 0 /* test in-place seedfill for speed */ pixd = pixClone(pixs); startTimer(); pixSeedfillBinary(pixs, pixs, pixmi, CONNECTIVITY); fprintf(stderr, "Filling rate: %7.4f Mpix/sec\n", (size/1000000.) / stopTimer()); pixWrite(fileout, pixd, IFF_PNG); pixOr(pixd, pixd, pixm); pixWrite("/tmp/junkout1.png", pixd, IFF_PNG); #endif #if 0 /* test seedfill to dest for speed */ pixd = pixCreateTemplate(pixm); startTimer(); for (i = 0; i < NTIMES; i++) { pixSeedfillBinary(pixd, pixs, pixmi, CONNECTIVITY); } fprintf(stderr, "Filling rate: %7.4f Mpix/sec\n", (size/1000000.) * NTIMES / stopTimer()); pixWrite(fileout, pixd, IFF_PNG); pixOr(pixd, pixd, pixm); pixWrite("/tmp/junkout1.png", pixd, IFF_PNG); #endif /* use same connectivity to compare with the result of the * slow parallel operation */ #if 1 pixDestroy(&pixd); pixd = pixSeedfillMorph(pixs, pixmi, 100, CONNECTIVITY); pixOr(pixd, pixd, pixm); pixWrite("/tmp/junkout2.png", pixd, IFF_PNG); #endif pixDestroy(&pixs); pixDestroy(&pixm); pixDestroy(&pixmi); pixDestroy(&pixd); return 0; }
/*! * \brief pixItalicWords() * * \param[in] pixs 1 bpp * \param[in] boxaw [optional] word bounding boxes; can be NULL * \param[in] pixw [optional] word box mask; can be NULL * \param[out] pboxa boxa of italic words * \param[in] debugflag 1 for debug output; 0 otherwise * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) You can input the bounding boxes for the words in one of * two forms: as bounding boxes (%boxaw) or as a word mask with * the word bounding boxes filled (%pixw). For example, * to compute %pixw, you can use pixWordMaskByDilation(). * (2) Alternatively, you can set both of these inputs to NULL, * in which case the word mask is generated here. This is * done by dilating and closing the input image to connect * letters within a word, while leaving the words separated. * The parameters are chosen under the assumption that the * input is 10 to 12 pt text, scanned at about 300 ppi. * (3) sel_ital1 and sel_ital2 detect the right edges that are * nearly vertical, at approximately the angle of italic * strokes. We use the right edge to avoid getting seeds * from lower-case 'y'. The typical italic slant has a smaller * angle with the vertical than the 'W', so in most cases we * will not trigger on the slanted lines in the 'W'. * (4) Note that sel_ital2 is shorter than sel_ital1. It is * more appropriate for a typical font scanned at 200 ppi. * </pre> */ l_int32 pixItalicWords(PIX *pixs, BOXA *boxaw, PIX *pixw, BOXA **pboxa, l_int32 debugflag) { char opstring[32]; l_int32 size; BOXA *boxa; PIX *pixsd, *pixm, *pixd; SEL *sel_ital1, *sel_ital2, *sel_ital3; PROCNAME("pixItalicWords"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pboxa) return ERROR_INT("&boxa not defined", procName, 1); if (boxaw && pixw) return ERROR_INT("both boxaw and pixw are defined", procName, 1); sel_ital1 = selCreateFromString(str_ital1, 13, 6, NULL); sel_ital2 = selCreateFromString(str_ital2, 10, 6, NULL); sel_ital3 = selCreateFromString(str_ital3, 4, 2, NULL); /* Make the italic seed: extract with HMT; remove noise. * The noise removal close/open is important to exclude * situations where a small slanted line accidentally * matches sel_ital1. */ pixsd = pixHMT(NULL, pixs, sel_ital1); pixClose(pixsd, pixsd, sel_ital3); pixOpen(pixsd, pixsd, sel_ital3); /* Make the word mask. Use input boxes or mask if given. */ size = 0; /* init */ if (boxaw) { pixm = pixCreateTemplate(pixs); pixMaskBoxa(pixm, pixm, boxaw, L_SET_PIXELS); } else if (pixw) { pixm = pixClone(pixw); } else { pixWordMaskByDilation(pixs, NULL, &size, NULL); L_INFO("dilation size = %d\n", procName, size); snprintf(opstring, sizeof(opstring), "d1.5 + c%d.1", size); pixm = pixMorphSequence(pixs, opstring, 0); } /* Binary reconstruction to fill in those word mask * components for which there is at least one seed pixel. */ pixd = pixSeedfillBinary(NULL, pixsd, pixm, 8); boxa = pixConnComp(pixd, NULL, 8); *pboxa = boxa; if (debugflag) { /* Save results at at 2x reduction */ lept_mkdir("lept/ital"); l_int32 res, upper; BOXA *boxat; GPLOT *gplot; NUMA *na; PIXA *pad; PIX *pix1, *pix2, *pix3; pad = pixaCreate(0); boxat = pixConnComp(pixm, NULL, 8); boxaWrite("/tmp/lept/ital/ital.ba", boxat); pixSaveTiledOutline(pixs, pad, 0.5, 1, 20, 2, 32); /* orig */ pixSaveTiledOutline(pixsd, pad, 0.5, 1, 20, 2, 0); /* seed */ pix1 = pixConvertTo32(pixm); pixRenderBoxaArb(pix1, boxat, 3, 255, 0, 0); pixSaveTiledOutline(pix1, pad, 0.5, 1, 20, 2, 0); /* mask + outline */ pixDestroy(&pix1); pixSaveTiledOutline(pixd, pad, 0.5, 1, 20, 2, 0); /* ital mask */ pix1 = pixConvertTo32(pixs); pixRenderBoxaArb(pix1, boxa, 3, 255, 0, 0); pixSaveTiledOutline(pix1, pad, 0.5, 1, 20, 2, 0); /* orig + outline */ pixDestroy(&pix1); pix1 = pixCreateTemplate(pixs); pix2 = pixSetBlackOrWhiteBoxa(pix1, boxa, L_SET_BLACK); pixCopy(pix1, pixs); pix3 = pixDilateBrick(NULL, pixs, 3, 3); pixCombineMasked(pix1, pix3, pix2); pixSaveTiledOutline(pix1, pad, 0.5, 1, 20, 2, 0); /* ital bolded */ pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pix2 = pixaDisplay(pad, 0, 0); pixWrite("/tmp/lept/ital/ital.png", pix2, IFF_PNG); pixDestroy(&pix2); /* Assuming the image represents 6 inches of actual page width, * the pixs resolution is approximately * (width of pixs in pixels) / 6 * and the images have been saved at half this resolution. */ res = pixGetWidth(pixs) / 12; L_INFO("resolution = %d\n", procName, res); l_pdfSetDateAndVersion(0); pixaConvertToPdf(pad, res, 1.0, L_FLATE_ENCODE, 75, "Italic Finder", "/tmp/lept/ital/ital.pdf"); l_pdfSetDateAndVersion(1); pixaDestroy(&pad); boxaDestroy(&boxat); /* Plot histogram of horizontal white run sizes. A small * initial vertical dilation removes most runs that are neither * inter-character nor inter-word. The larger first peak is * from inter-character runs, and the smaller second peak is * from inter-word runs. */ pix1 = pixDilateBrick(NULL, pixs, 1, 15); upper = L_MAX(30, 3 * size); na = pixRunHistogramMorph(pix1, L_RUN_OFF, L_HORIZ, upper); pixDestroy(&pix1); gplot = gplotCreate("/tmp/lept/ital/runhisto", GPLOT_PNG, "Histogram of horizontal runs of white pixels, vs length", "run length", "number of runs"); gplotAddPlot(gplot, NULL, na, GPLOT_LINES, "plot1"); gplotMakeOutput(gplot); gplotDestroy(&gplot); numaDestroy(&na); } selDestroy(&sel_ital1); selDestroy(&sel_ital2); selDestroy(&sel_ital3); pixDestroy(&pixsd); pixDestroy(&pixm); pixDestroy(&pixd); return 0; }
/*! * \brief pixMorphSequence() * * \param[in] pixs * \param[in] sequence string specifying sequence * \param[in] dispsep controls debug display of each result in the sequence: * 0: no output * > 0: gives horizontal separation in pixels between * successive displays * < 0: pdf output; abs(dispsep) is used for naming * \return pixd, or NULL on error * * <pre> * Notes: * (1) This does rasterop morphology on binary images. * (2) This runs a pipeline of operations; no branching is allowed. * (3) This only uses brick Sels, which are created on the fly. * In the future this will be generalized to extract Sels from * a Sela by name. * (4) A new image is always produced; the input image is not changed. * (5) This contains an interpreter, allowing sequences to be * generated and run. * (6) The format of the sequence string is defined below. * (7) In addition to morphological operations, rank order reduction * and replicated expansion allow operations to take place * downscaled by a power of 2. * (8) Intermediate results can optionally be displayed. * (9) Thanks to Dar-Shyang Lee, who had the idea for this and * built the first implementation. * (10) The sequence string is formatted as follows: * ~ An arbitrary number of operations, each separated * by a '+' character. White space is ignored. * ~ Each operation begins with a case-independent character * specifying the operation: * d or D (dilation) * e or E (erosion) * o or O (opening) * c or C (closing) * r or R (rank binary reduction) * x or X (replicative binary expansion) * b or B (add a border of 0 pixels of this size) * ~ The args to the morphological operations are bricks of hits, * and are formatted as a.b, where a and b are horizontal and * vertical dimensions, rsp. * ~ The args to the reduction are a sequence of up to 4 integers, * each from 1 to 4. * ~ The arg to the expansion is a power of two, in the set * {2, 4, 8, 16}. * (11) An example valid sequence is: * "b32 + o1.3 + C3.1 + r23 + e2.2 + D3.2 + X4" * In this example, the following operation sequence is carried out: * * b32: Add a 32 pixel border around the input image * * o1.3: Opening with vert sel of length 3 (e.g., 1 x 3) * * C3.1: Closing with horiz sel of length 3 (e.g., 3 x 1) * * r23: Two successive 2x2 reductions with rank 2 in the first * and rank 3 in the second. The result is a 4x reduced pix. * * e2.2: Erosion with a 2x2 sel (origin will be at x,y: 0,0) * * d3.2: Dilation with a 3x2 sel (origin will be at x,y: 1,0) * * X4: 4x replicative expansion, back to original resolution * (12) The safe closing is used. However, if you implement a * closing as separable dilations followed by separable erosions, * it will not be safe. For that situation, you need to add * a sufficiently large border as the first operation in * the sequence. This will be removed automatically at the * end. There are two cautions: * ~ When computing what is sufficient, remember that if * reductions are carried out, the border is also reduced. * ~ The border is removed at the end, so if a border is * added at the beginning, the result must be at the * same resolution as the input! * </pre> */ PIX * pixMorphSequence(PIX *pixs, const char *sequence, l_int32 dispsep) { char *rawop, *op, *fname; char buf[256]; l_int32 nops, i, j, nred, fact, w, h, x, y, border, pdfout; l_int32 level[4]; PIX *pixt1, *pixt2; PIXA *pixa; SARRAY *sa; PROCNAME("pixMorphSequence"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!sequence) return (PIX *)ERROR_PTR("sequence not defined", procName, NULL); /* Split sequence into individual operations */ sa = sarrayCreate(0); sarraySplitString(sa, sequence, "+"); nops = sarrayGetCount(sa); pdfout = (dispsep < 0) ? 1 : 0; if (!morphSequenceVerify(sa)) { sarrayDestroy(&sa); return (PIX *)ERROR_PTR("sequence not valid", procName, NULL); } /* Parse and operate */ pixa = NULL; if (pdfout) { pixa = pixaCreate(0); pixaAddPix(pixa, pixs, L_CLONE); snprintf(buf, sizeof(buf), "/tmp/seq_output_%d.pdf", L_ABS(dispsep)); fname = genPathname(buf, NULL); } border = 0; pixt1 = pixCopy(NULL, pixs); pixt2 = NULL; x = y = 0; for (i = 0; i < nops; i++) { rawop = sarrayGetString(sa, i, L_NOCOPY); op = stringRemoveChars(rawop, " \n\t"); switch (op[0]) { case 'd': case 'D': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixDilateBrick(NULL, pixt1, w, h); pixSwapAndDestroy(&pixt1, &pixt2); break; case 'e': case 'E': sscanf(&op[1], "%d.%d", &w, &h); pixt2 = pixErodeBrick(NULL, pixt1, w, h); pixSwapAndDestroy(&pixt1, &pixt2); break; case 'o': case 'O': sscanf(&op[1], "%d.%d", &w, &h); pixOpenBrick(pixt1, pixt1, w, h); break; case 'c': case 'C': sscanf(&op[1], "%d.%d", &w, &h); pixCloseSafeBrick(pixt1, pixt1, w, h); break; case 'r': case 'R': nred = strlen(op) - 1; for (j = 0; j < nred; j++) level[j] = op[j + 1] - '0'; for (j = nred; j < 4; j++) level[j] = 0; pixt2 = pixReduceRankBinaryCascade(pixt1, level[0], level[1], level[2], level[3]); pixSwapAndDestroy(&pixt1, &pixt2); break; case 'x': case 'X': sscanf(&op[1], "%d", &fact); pixt2 = pixExpandReplicate(pixt1, fact); pixSwapAndDestroy(&pixt1, &pixt2); break; case 'b': case 'B': sscanf(&op[1], "%d", &border); pixt2 = pixAddBorder(pixt1, border, 0); pixSwapAndDestroy(&pixt1, &pixt2); break; default: /* All invalid ops are caught in the first pass */ break; } LEPT_FREE(op); /* Debug output */ if (dispsep > 0) { pixDisplay(pixt1, x, y); x += dispsep; } if (pdfout) pixaAddPix(pixa, pixt1, L_COPY); } if (border > 0) { pixt2 = pixRemoveBorder(pixt1, border); pixSwapAndDestroy(&pixt1, &pixt2); } if (pdfout) { pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname); LEPT_FREE(fname); pixaDestroy(&pixa); } sarrayDestroy(&sa); return pixt1; }
l_int32 DoComparisonDwa2(L_REGPARAMS *rp, PIX *pixs, PIX *pix1, PIX *pix2, PIX *pix3, PIX *pix4, PIX *pix5, PIX *pix6, l_int32 size) /* exactly decomposable */ { fprintf(stderr, "..%d..", size); if (TIMING) startTimer(); pixDilateCompBrickExtendDwa(pix1, pixs, size, 1); pixDilateCompBrickExtendDwa(pix3, pixs, 1, size); pixDilateCompBrickExtendDwa(pix5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixDilateBrick(pix2, pixs, size, 1); pixDilateBrick(pix4, pixs, 1, size); pixDilateBrick(pix6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(rp, size, "dilate", pix1, pix2, pix3, pix4, pix5, pix6); if (TIMING) startTimer(); pixErodeCompBrickExtendDwa(pix1, pixs, size, 1); pixErodeCompBrickExtendDwa(pix3, pixs, 1, size); pixErodeCompBrickExtendDwa(pix5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixErodeBrick(pix2, pixs, size, 1); pixErodeBrick(pix4, pixs, 1, size); pixErodeBrick(pix6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(rp, size, "erode", pix1, pix2, pix3, pix4, pix5, pix6); if (TIMING) startTimer(); pixOpenCompBrickExtendDwa(pix1, pixs, size, 1); pixOpenCompBrickExtendDwa(pix3, pixs, 1, size); pixOpenCompBrickExtendDwa(pix5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixOpenBrick(pix2, pixs, size, 1); pixOpenBrick(pix4, pixs, 1, size); pixOpenBrick(pix6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(rp, size, "open", pix1, pix2, pix3, pix4, pix5, pix6); if (TIMING) startTimer(); pixCloseCompBrickExtendDwa(pix1, pixs, size, 1); pixCloseCompBrickExtendDwa(pix3, pixs, 1, size); pixCloseCompBrickExtendDwa(pix5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixCloseSafeBrick(pix2, pixs, size, 1); pixCloseSafeBrick(pix4, pixs, 1, size); pixCloseSafeBrick(pix6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(rp, size, "close", pix1, pix2, pix3, pix4, pix5, pix6); return 0; }
/*! * pixGetRegionsBinary() * * Input: pixs (1 bpp, assumed to be 300 to 400 ppi) * &pixhm (<optional return> halftone mask) * &pixtm (<optional return> textline mask) * &pixtb (<optional return> textblock mask) * debug (flag: set to 1 for debug output) * Return: 0 if OK, 1 on error * * Notes: * (1) It is best to deskew the image before segmenting. * (2) The debug flag enables a number of outputs. These * are included to show how to generate and save/display * these results. */ l_int32 pixGetRegionsBinary(PIX *pixs, PIX **ppixhm, PIX **ppixtm, PIX **ppixtb, l_int32 debug) { char *tempname; l_int32 htfound, tlfound; PIX *pixr, *pixt1, *pixt2; PIX *pixtext; /* text pixels only */ PIX *pixhm2; /* halftone mask; 2x reduction */ PIX *pixhm; /* halftone mask; */ PIX *pixtm2; /* textline mask; 2x reduction */ PIX *pixtm; /* textline mask */ PIX *pixvws; /* vertical white space mask */ PIX *pixtb2; /* textblock mask; 2x reduction */ PIX *pixtbf2; /* textblock mask; 2x reduction; small comps filtered */ PIX *pixtb; /* textblock mask */ PROCNAME("pixGetRegionsBinary"); if (ppixhm) *ppixhm = NULL; if (ppixtm) *ppixtm = NULL; if (ppixtb) *ppixtb = NULL; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (pixGetDepth(pixs) != 1) return ERROR_INT("pixs not 1 bpp", procName, 1); /* 2x reduce, to 150 -200 ppi */ pixr = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0); pixDisplayWrite(pixr, debug); /* Get the halftone mask */ pixhm2 = pixGenHalftoneMask(pixr, &pixtext, &htfound, debug); /* Get the textline mask from the text pixels */ pixtm2 = pixGenTextlineMask(pixtext, &pixvws, &tlfound, debug); /* Get the textblock mask from the textline mask */ pixtb2 = pixGenTextblockMask(pixtm2, pixvws, debug); pixDestroy(&pixr); pixDestroy(&pixtext); pixDestroy(&pixvws); /* Remove small components from the mask, where a small * component is defined as one with both width and height < 60 */ pixtbf2 = pixSelectBySize(pixtb2, 60, 60, 4, L_SELECT_IF_EITHER, L_SELECT_IF_GTE, NULL); pixDestroy(&pixtb2); pixDisplayWriteFormat(pixtbf2, debug, IFF_PNG); /* Expand all masks to full resolution, and do filling or * small dilations for better coverage. */ pixhm = pixExpandReplicate(pixhm2, 2); pixt1 = pixSeedfillBinary(NULL, pixhm, pixs, 8); pixOr(pixhm, pixhm, pixt1); pixDestroy(&pixt1); pixDisplayWriteFormat(pixhm, debug, IFF_PNG); pixt1 = pixExpandReplicate(pixtm2, 2); pixtm = pixDilateBrick(NULL, pixt1, 3, 3); pixDestroy(&pixt1); pixDisplayWriteFormat(pixtm, debug, IFF_PNG); pixt1 = pixExpandReplicate(pixtbf2, 2); pixtb = pixDilateBrick(NULL, pixt1, 3, 3); pixDestroy(&pixt1); pixDisplayWriteFormat(pixtb, debug, IFF_PNG); pixDestroy(&pixhm2); pixDestroy(&pixtm2); pixDestroy(&pixtbf2); /* Debug: identify objects that are neither text nor halftone image */ if (debug) { pixt1 = pixSubtract(NULL, pixs, pixtm); /* remove text pixels */ pixt2 = pixSubtract(NULL, pixt1, pixhm); /* remove halftone pixels */ pixDisplayWriteFormat(pixt2, 1, IFF_PNG); pixDestroy(&pixt1); pixDestroy(&pixt2); } /* Debug: display textline components with random colors */ if (debug) { l_int32 w, h; BOXA *boxa; PIXA *pixa; boxa = pixConnComp(pixtm, &pixa, 8); pixGetDimensions(pixtm, &w, &h, NULL); pixt1 = pixaDisplayRandomCmap(pixa, w, h); pixcmapResetColor(pixGetColormap(pixt1), 0, 255, 255, 255); pixDisplay(pixt1, 100, 100); pixDisplayWriteFormat(pixt1, 1, IFF_PNG); pixaDestroy(&pixa); boxaDestroy(&boxa); pixDestroy(&pixt1); } /* Debug: identify the outlines of each textblock */ if (debug) { PIXCMAP *cmap; PTAA *ptaa; ptaa = pixGetOuterBordersPtaa(pixtb); tempname = genTempFilename("/tmp", "tb_outlines.ptaa", 0, 0); ptaaWrite(tempname, ptaa, 1); FREE(tempname); pixt1 = pixRenderRandomCmapPtaa(pixtb, ptaa, 1, 16, 1); cmap = pixGetColormap(pixt1); pixcmapResetColor(cmap, 0, 130, 130, 130); pixDisplay(pixt1, 500, 100); pixDisplayWriteFormat(pixt1, 1, IFF_PNG); pixDestroy(&pixt1); ptaaDestroy(&ptaa); } /* Debug: get b.b. for all mask components */ if (debug) { BOXA *bahm, *batm, *batb; bahm = pixConnComp(pixhm, NULL, 4); batm = pixConnComp(pixtm, NULL, 4); batb = pixConnComp(pixtb, NULL, 4); tempname = genTempFilename("/tmp", "htmask.boxa", 0, 0); boxaWrite(tempname, bahm); FREE(tempname); tempname = genTempFilename("/tmp", "textmask.boxa", 0, 0); boxaWrite(tempname, batm); FREE(tempname); tempname = genTempFilename("/tmp", "textblock.boxa", 0, 0); boxaWrite(tempname, batb); FREE(tempname); boxaDestroy(&bahm); boxaDestroy(&batm); boxaDestroy(&batb); } if (ppixhm) *ppixhm = pixhm; else pixDestroy(&pixhm); if (ppixtm) *ppixtm = pixtm; else pixDestroy(&pixtm); if (ppixtb) *ppixtb = pixtb; else pixDestroy(&pixtb); return 0; }
/* morph composite with morph non-composite */ l_int32 DoComparisonDwa1(PIX *pixs, PIX *pixt1, PIX *pixt2, PIX *pixt3, PIX *pixt4, PIX *pixt5, PIX *pixt6, l_int32 isize) { l_int32 fact1, fact2, size; selectComposableSizes(isize, &fact1, &fact2); size = fact1 * fact2; fprintf(stderr, "..%d..", size); if (TIMING) startTimer(); pixDilateCompBrick(pixt1, pixs, size, 1); pixDilateCompBrick(pixt3, pixs, 1, size); pixDilateCompBrick(pixt5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixDilateBrick(pixt2, pixs, size, 1); pixDilateBrick(pixt4, pixs, 1, size); pixDilateBrick(pixt6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(size, "dilate", pixt1, pixt2, pixt3, pixt4, pixt5, pixt6); if (TIMING) startTimer(); pixErodeCompBrick(pixt1, pixs, size, 1); pixErodeCompBrick(pixt3, pixs, 1, size); pixErodeCompBrick(pixt5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixErodeBrick(pixt2, pixs, size, 1); pixErodeBrick(pixt4, pixs, 1, size); pixErodeBrick(pixt6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(size, "erode", pixt1, pixt2, pixt3, pixt4, pixt5, pixt6); if (TIMING) startTimer(); pixOpenCompBrick(pixt1, pixs, size, 1); pixOpenCompBrick(pixt3, pixs, 1, size); pixOpenCompBrick(pixt5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixOpenBrick(pixt2, pixs, size, 1); pixOpenBrick(pixt4, pixs, 1, size); pixOpenBrick(pixt6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(size, "open", pixt1, pixt2, pixt3, pixt4, pixt5, pixt6); #if 1 pixWrite("/tmp/junko1.png", pixt1, IFF_PNG); pixWrite("/tmp/junko2.png", pixt2, IFF_PNG); pixXor(pixt1, pixt1, pixt2); pixWrite("/tmp/junkoxor.png", pixt1, IFF_PNG); #endif #if 0 pixDisplay(pixt1, 100, 100); pixDisplay(pixt2, 800, 100); pixWrite("/tmp/junkpixt1.png", pixt1, IFF_PNG); pixWrite("/tmp/junkpixt2.png", pixt2, IFF_PNG); #endif if (TIMING) startTimer(); pixCloseSafeCompBrick(pixt1, pixs, size, 1); pixCloseSafeCompBrick(pixt3, pixs, 1, size); pixCloseSafeCompBrick(pixt5, pixs, size, size); if (TIMING) fprintf(stderr, "Time Dwa: %7.3f sec\n", stopTimer()); if (TIMING) startTimer(); pixCloseSafeBrick(pixt2, pixs, size, 1); pixCloseSafeBrick(pixt4, pixs, 1, size); pixCloseSafeBrick(pixt6, pixs, size, size); if (TIMING) fprintf(stderr, "Time Rop: %7.3f sec\n", stopTimer()); PixCompareDwa(size, "close", pixt1, pixt2, pixt3, pixt4, pixt5, pixt6); #if 1 pixWrite("/tmp/junkc1.png", pixt1, IFF_PNG); pixWrite("/tmp/junkc2.png", pixt2, IFF_PNG); pixXor(pixt1, pixt1, pixt2); pixWrite("/tmp/junkcxor.png", pixt1, IFF_PNG); #endif return 0; }
int main(int argc, char **argv) { if (argc < 3) return usage(argv[0]); char highlight = 0; char ignore_scrollbars = 1; /* Default output filename; can be overridden by command line. */ const char *output_filename = "highlight.png"; int argi = 1; for (; argi < argc; ++argi) { if (strcmp("--highlight", argv[argi]) == 0) { highlight = 1; } else if (strcmp("--no-ignore-scrollbars", argv[argi]) == 0) { ignore_scrollbars = 0; } else if (strcmp("--output", argv[argi]) == 0) { if (argi + 1 >= argc) { fprintf(stderr, "missing argument to --output\n"); return 1; } output_filename = argv[++argi]; } else { break; } } if (argc - argi < 2) return usage(argv[0]); PIX *a = pixRead(argv[argi]); PIX *b = pixRead(argv[argi + 1]); if (!a) { fprintf(stderr, "Failed to open %s\n", argv[argi]); return 1; } if (!b) { fprintf(stderr, "Failed to open %s\n", argv[argi + 1]); return 1; } if (pixGetWidth(a) != pixGetWidth(b) || pixGetHeight(a) != pixGetHeight(b)) { fprintf(stderr, "Inputs are difference sizes\n"); return 1; } PIX *delta = pixAbsDifference(a, b); pixInvert(delta, delta); if (!highlight) pixDestroy(&a); pixDestroy(&b); PIX *deltagray = pixConvertRGBToGray(delta, 0, 0, 0); pixDestroy(&delta); PIX *deltabinary = pixThresholdToBinary(deltagray, 254); PIX *deltabinaryclipped; const int clipwidth = pixGetWidth(deltabinary) - 15; const int clipheight = pixGetHeight(deltabinary) - 15; if (ignore_scrollbars && clipwidth > 0 && clipheight > 0) { BOX *clip = boxCreate(0, 0, clipwidth, clipheight); deltabinaryclipped = pixClipRectangle(deltabinary, clip, NULL); boxDestroy(&clip); pixDestroy(&deltabinary); } else { deltabinaryclipped = deltabinary; deltabinary = NULL; } PIX *hopened = pixOpenBrick(NULL, deltabinaryclipped, 3, 1); PIX *vopened = pixOpenBrick(NULL, deltabinaryclipped, 1, 3); pixDestroy(&deltabinaryclipped); PIX *opened = pixOr(NULL, hopened, vopened); pixDestroy(&hopened); pixDestroy(&vopened); l_int32 count; pixCountPixels(opened, &count, NULL); fprintf(stderr, "%d\n", count); if (count && highlight) { PIX *d1 = pixDilateBrick(NULL, opened, 7, 7); PIX *d2 = pixDilateBrick(NULL, opened, 3, 3); pixInvert(d2, d2); pixAnd(d1, d1, d2); pixPaintThroughMask(a, d1, 0, 0, 0xff << 24); pixWrite(output_filename, a, IFF_PNG); } return count > 0; }