/*! * pixGenHalftoneMask() * * Input: pixs (1 bpp, assumed to be 150 to 200 ppi) * &pixtext (<optional return> text part of pixs) * &htfound (<optional return> 1 if the mask is not empty) * debug (flag: 1 for debug output) * Return: pixd (halftone mask), or null on error */ PIX * pixGenHalftoneMask(PIX *pixs, PIX **ppixtext, l_int32 *phtfound, l_int32 debug) { l_int32 empty; PIX *pixt1, *pixt2, *pixhs, *pixhm, *pixd; PROCNAME("pixGenHalftoneMask"); if (ppixtext) *ppixtext = NULL; if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL); /* Compute seed for halftone parts at 8x reduction */ pixt1 = pixReduceRankBinaryCascade(pixs, 4, 4, 3, 0); pixt2 = pixOpenBrick(NULL, pixt1, 5, 5); pixhs = pixExpandReplicate(pixt2, 8); /* back to 2x reduction */ pixDestroy(&pixt1); pixDestroy(&pixt2); pixDisplayWriteFormat(pixhs, debug, IFF_PNG); /* Compute mask for connected regions */ pixhm = pixCloseSafeBrick(NULL, pixs, 4, 4); pixDisplayWriteFormat(pixhm, debug, IFF_PNG); /* Fill seed into mask to get halftone mask */ pixd = pixSeedfillBinary(NULL, pixhs, pixhm, 4); #if 0 /* Moderate opening to remove thin lines, etc. */ pixOpenBrick(pixd, pixd, 10, 10); pixDisplayWrite(pixd, debug); #endif /* Check if mask is empty */ pixZero(pixd, &empty); if (phtfound) { *phtfound = 0; if (!empty) *phtfound = 1; } /* Optionally, get all pixels that are not under the halftone mask */ if (ppixtext) { if (empty) *ppixtext = pixCopy(NULL, pixs); else *ppixtext = pixSubtract(NULL, pixs, pixd); pixDisplayWriteFormat(*ppixtext, debug, IFF_PNG); } pixDestroy(&pixhs); pixDestroy(&pixhm); return pixd; }
/*! * \brief pixCountConnComp() * * \param[in] pixs 1 bpp * \param[in] connectivity 4 or 8 * \param[out] pcount * \return 0 if OK, 1 on error * * Notes: * (1 This is the top-level call for getting the number of * 4- or 8-connected components in a 1 bpp image. * 2 It works on a copy of the input pix. The c.c. are located * in raster order and erased one at a time. */ l_int32 pixCountConnComp(PIX *pixs, l_int32 connectivity, l_int32 *pcount) { l_int32 h, iszero; l_int32 x, y, xstart, ystart; PIX *pixt; L_STACK *stack, *auxstack; PROCNAME("pixCountConnComp"); if (!pcount) return ERROR_INT("&count not defined", procName, 1); *pcount = 0; /* initialize the count to 0 */ if (!pixs || pixGetDepth(pixs) != 1) return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); if (connectivity != 4 && connectivity != 8) return ERROR_INT("connectivity not 4 or 8", procName, 1); pixt = NULL; stack = NULL; pixZero(pixs, &iszero); if (iszero) return 0; if ((pixt = pixCopy(NULL, pixs)) == NULL) return ERROR_INT("pixt not made", procName, 1); h = pixGetHeight(pixs); if ((stack = lstackCreate(h)) == NULL) { L_ERROR("stack not made\n", procName); goto cleanup; } auxstack = lstackCreate(0); stack->auxstack = auxstack; xstart = 0; ystart = 0; while (1) { if (!nextOnPixelInRaster(pixt, xstart, ystart, &x, &y)) break; pixSeedfill(pixt, stack, x, y, connectivity); (*pcount)++; xstart = x; ystart = y; } /* Cleanup, freeing the fillsegs on each stack */ cleanup: lstackDestroy(&stack, TRUE); pixDestroy(&pixt); return 0; }
/*! * pixSelectByWidthHeightRatio() * * Input: pixs (1 bpp) * thresh (threshold ratio of width/height) * connectivity (4 or 8) * type (L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE) * &changed (<optional return> 1 if changed; 0 if clone returned) * Return: pixd, or null on error * * Notes: * (1) The args specify constraints on the width-to-height ratio * for components that are kept. * (2) If unchanged, returns a copy of pixs. Otherwise, * returns a new pix with the filtered components. * (3) This filters components based on the width-to-height ratios. * (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components * with less than the threshold ratio, and * L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them. */ PIX * pixSelectByWidthHeightRatio(PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged) { l_int32 w, h, empty, changed, count; BOXA *boxa; PIX *pixd; PIXA *pixas, *pixad; PROCNAME("pixSelectByWidthHeightRatio"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIX *)ERROR_PTR("invalid type", procName, NULL); if (pchanged) *pchanged = FALSE; /* Check if any components exist */ pixZero(pixs, &empty); if (empty) return pixCopy(NULL, pixs); /* Filter components */ boxa = pixConnComp(pixs, &pixas, connectivity); pixad = pixaSelectByWidthHeightRatio(pixas, thresh, type, &changed); boxaDestroy(&boxa); pixaDestroy(&pixas); /* Render the result */ if (!changed) { pixaDestroy(&pixad); return pixCopy(NULL, pixs); } else { if (pchanged) *pchanged = TRUE; pixGetDimensions(pixs, &w, &h, NULL); count = pixaGetCount(pixad); if (count == 0) /* return empty pix */ pixd = pixCreateTemplate(pixs); else { pixd = pixaDisplay(pixad, w, h); pixCopyResolution(pixd, pixs); pixCopyColormap(pixd, pixs); pixCopyText(pixd, pixs); pixCopyInputFormat(pixd, pixs); } pixaDestroy(&pixad); return pixd; } }
/*! * \brief pixConnCompIncrInit() * * \param[in] pixs 1 bpp * \param[in] conn connectivity: 4 or 8 * \param[out] ppixd 32 bpp, with c.c. labelled * \param[out] pptaa with pixel locations indexed by c.c. * \param[out] pncc initial number of c.c. * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) This labels the connected components in a 1 bpp pix, and * additionally sets up a ptaa that lists the locations of pixels * in each of the components. * (2) It can be used to initialize the output image and arrays for * an application that maintains information about connected * components incrementally as pixels are added. * (3) pixs can be empty or have some foreground pixels. * (4) The connectivity is stored in pixd->special. * (5) Always initialize with the first pta in ptaa being empty * and representing the background value (index 0) in the pix. * </pre> */ l_int32 pixConnCompIncrInit(PIX *pixs, l_int32 conn, PIX **ppixd, PTAA **pptaa, l_int32 *pncc) { l_int32 empty, w, h, ncc; PIX *pixd; PTA *pta; PTAA *ptaa; PROCNAME("pixConnCompIncrInit"); if (ppixd) *ppixd = NULL; if (pptaa) *pptaa = NULL; if (pncc) *pncc = 0; if (!ppixd || !pptaa || !pncc) return ERROR_INT("&pixd, &ptaa, &ncc not all defined", procName, 1); if (!pixs || pixGetDepth(pixs) != 1) return ERROR_INT("pixs undefined or not 1 bpp", procName, 1); if (conn != 4 && conn != 8) return ERROR_INT("connectivity must be 4 or 8", procName, 1); pixGetDimensions(pixs, &w, &h, NULL); pixZero(pixs, &empty); if (empty) { *ppixd = pixCreate(w, h, 32); pixSetSpp(*ppixd, 1); pixSetSpecial(*ppixd, conn); *pptaa = ptaaCreate(0); pta = ptaCreate(1); ptaaAddPta(*pptaa, pta, L_INSERT); /* reserve index 0 for background */ return 0; } /* Set up the initial labeled image and indexed pixel arrays */ if ((pixd = pixConnCompTransform(pixs, conn, 32)) == NULL) return ERROR_INT("pixd not made", procName, 1); pixSetSpecial(pixd, conn); *ppixd = pixd; if ((ptaa = ptaaIndexLabeledPixels(pixd, &ncc)) == NULL) return ERROR_INT("ptaa not made", procName, 1); *pptaa = ptaa; *pncc = ncc; return 0; }
/*! * \brief pixConnCompBB() * * \param[in] pixs 1 bpp * \param[in] connectivity 4 or 8 * \return boxa, or NULL on error * * <pre> * Notes: * (1) Finds bounding boxes of 4- or 8-connected components * in a binary image. * (2) This works on a copy of the input pix. The c.c. are located * in raster order and erased one at a time. In the process, * the b.b. is computed and saved. * </pre> */ BOXA * pixConnCompBB(PIX *pixs, l_int32 connectivity) { l_int32 h, iszero; l_int32 x, y, xstart, ystart; PIX *pixt; BOX *box; BOXA *boxa; L_STACK *stack, *auxstack; PROCNAME("pixConnCompBB"); if (!pixs || pixGetDepth(pixs) != 1) return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (connectivity != 4 && connectivity != 8) return (BOXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); boxa = NULL; pixt = NULL; stack = NULL; pixZero(pixs, &iszero); if (iszero) return boxaCreate(1); /* return empty boxa */ if ((pixt = pixCopy(NULL, pixs)) == NULL) return (BOXA *)ERROR_PTR("pixt not made", procName, NULL); h = pixGetHeight(pixs); if ((stack = lstackCreate(h)) == NULL) { L_ERROR("stack not made\n", procName); goto cleanup; } auxstack = lstackCreate(0); stack->auxstack = auxstack; boxa = boxaCreate(0); xstart = 0; ystart = 0; while (1) { if (!nextOnPixelInRaster(pixt, xstart, ystart, &x, &y)) break; if ((box = pixSeedfillBB(pixt, stack, x, y, connectivity)) == NULL) { L_ERROR("box not made\n", procName); boxaDestroy(&boxa); goto cleanup; } boxaAddBox(boxa, box, L_INSERT); xstart = x; ystart = y; } #if DEBUG pixCountPixels(pixt, &iszero, NULL); fprintf(stderr, "Number of remaining pixels = %d\n", iszero); pixWrite("junkremain", pixt1, IFF_PNG); #endif /* DEBUG */ /* Cleanup, freeing the fillsegs on each stack */ cleanup: lstackDestroy(&stack, TRUE); pixDestroy(&pixt); return boxa; }
/*! * \brief pixConnCompPixa() * * \param[in] pixs 1 bpp * \param[out] ppixa pixa of each c.c. * \param[in] connectivity 4 or 8 * \return boxa, or NULL on error * * <pre> * Notes: * (1) This finds bounding boxes of 4- or 8-connected components * in a binary image, and saves images of each c.c * in a pixa array. * (2) It sets up 2 temporary pix, and for each c.c. that is * located in raster order, it erases the c.c. from one pix, * then uses the b.b. to extract the c.c. from the two pix using * an XOR, and finally erases the c.c. from the second pix. * (3) A clone of the returned boxa (where all boxes in the array * are clones) is inserted into the pixa. * (4) If the input is valid, this always returns a boxa and a pixa. * If pixs is empty, the boxa and pixa will be empty. * </pre> */ BOXA * pixConnCompPixa(PIX *pixs, PIXA **ppixa, l_int32 connectivity) { l_int32 h, iszero; l_int32 x, y, xstart, ystart; PIX *pix1, *pix2, *pix3, *pix4; PIXA *pixa; BOX *box; BOXA *boxa; L_STACK *stack, *auxstack; PROCNAME("pixConnCompPixa"); if (!ppixa) return (BOXA *)ERROR_PTR("&pixa not defined", procName, NULL); *ppixa = NULL; if (!pixs || pixGetDepth(pixs) != 1) return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (connectivity != 4 && connectivity != 8) return (BOXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); boxa = NULL; pix1 = pix2 = pix3 = pix4 = NULL; stack = NULL; pixZero(pixs, &iszero); if (iszero) return boxaCreate(1); /* return empty boxa */ pix1 = pixCopy(NULL, pixs); pix2 = pixCopy(NULL, pixs); if (!pix1 || !pix2) { L_ERROR("pix1 or pix2 not made\n", procName); goto cleanup; } h = pixGetHeight(pixs); if ((stack = lstackCreate(h)) == NULL) { L_ERROR("stack not made\n", procName); goto cleanup; } auxstack = lstackCreate(0); stack->auxstack = auxstack; pixa = pixaCreate(0); boxa = boxaCreate(0); xstart = 0; ystart = 0; while (1) { if (!nextOnPixelInRaster(pix1, xstart, ystart, &x, &y)) break; if ((box = pixSeedfillBB(pix1, stack, x, y, connectivity)) == NULL) { L_ERROR("box not made\n", procName); pixaDestroy(&pixa); boxaDestroy(&boxa); goto cleanup; } boxaAddBox(boxa, box, L_INSERT); /* Save the c.c. and remove from pix2 as well */ pix3 = pixClipRectangle(pix1, box, NULL); pix4 = pixClipRectangle(pix2, box, NULL); pixXor(pix3, pix3, pix4); pixRasterop(pix2, box->x, box->y, box->w, box->h, PIX_SRC ^ PIX_DST, pix3, 0, 0); pixaAddPix(pixa, pix3, L_INSERT); pixDestroy(&pix4); xstart = x; ystart = y; } #if DEBUG pixCountPixels(pix1, &iszero, NULL); fprintf(stderr, "Number of remaining pixels = %d\n", iszero); pixWrite("junkremain", pix1, IFF_PNG); #endif /* DEBUG */ /* Remove old boxa of pixa and replace with a clone copy */ boxaDestroy(&pixa->boxa); pixa->boxa = boxaCopy(boxa, L_CLONE); *ppixa = pixa; /* Cleanup, freeing the fillsegs on each stack */ cleanup: lstackDestroy(&stack, TRUE); pixDestroy(&pix1); pixDestroy(&pix2); return boxa; }
int main(int argc, char **argv) { l_int32 i, j, w, h, empty; l_uint32 redval, greenval; l_float32 f; L_WSHED *wshed; PIX *pixs, *pixc, *pixd; PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8; PIXA *pixac; PTA *pta; static char mainName[] = "watershedtest"; if (argc != 1) return ERROR_INT(" Syntax: watershedtest", mainName, 1); pixac = pixaCreate(0); pixs = pixCreate(500, 500, 8); pixGetDimensions(pixs, &w, &h, NULL); for (i = 0; i < 500; i++) { for (j = 0; j < 500; j++) { #if 1 f = 128.0 + 26.3 * sin(0.0438 * (l_float32) i); f += 33.4 * cos(0.0712 * (l_float32) i); f += 18.6 * sin(0.0561 * (l_float32) j); f += 23.6 * cos(0.0327 * (l_float32) j); #else f = 128.0 + 26.3 * sin(0.0238 * (l_float32)i); f += 33.4 * cos(0.0312 * (l_float32)i); f += 18.6 * sin(0.0261 * (l_float32)j); f += 23.6 * cos(0.0207 * (l_float32)j); #endif pixSetPixel(pixs, j, i, (l_int32) f); } } pixSaveTiled(pixs, pixac, 1.0, 1, 10, 32); pixWrite("/tmp/pattern.png", pixs, IFF_PNG); startTimer(); pixLocalExtrema(pixs, 0, 0, &pix1, &pix2); fprintf(stderr, "Time for extrema: %7.3f\n", stopTimer()); pixSetOrClearBorder(pix1, 2, 2, 2, 2, PIX_CLR); composeRGBPixel(255, 0, 0, &redval); composeRGBPixel(0, 255, 0, &greenval); pixc = pixConvertTo32(pixs); pixPaintThroughMask(pixc, pix2, 0, 0, greenval); pixPaintThroughMask(pixc, pix1, 0, 0, redval); pixSaveTiled(pixc, pixac, 1.0, 0, 10, 32); pixWrite("/tmp/pixc.png", pixc, IFF_PNG); pixSaveTiled(pix1, pixac, 1.0, 0, 10, 32); pixSelectMinInConnComp(pixs, pix1, &pta, NULL); /* ptaWriteStream(stderr, pta, 1); */ pix3 = pixGenerateFromPta(pta, w, h); pixSaveTiled(pix3, pixac, 1.0, 1, 10, 32); pix4 = pixConvertTo32(pixs); pixPaintThroughMask(pix4, pix3, 0, 0, greenval); pixSaveTiled(pix4, pixac, 1.0, 0, 10, 32); pix5 = pixRemoveSeededComponents(NULL, pix3, pix1, 8, 2); pixSaveTiled(pix5, pixac, 1.0, 0, 10, 32); pixZero(pix5, &empty); fprintf(stderr, "Is empty? %d\n", empty); pixDestroy(&pix4); pixDestroy(&pix5); wshed = wshedCreate(pixs, pix3, 10, 0); startTimer(); wshedApply(wshed); fprintf(stderr, "Time for wshed: %7.3f\n", stopTimer()); pix6 = pixaDisplayRandomCmap(wshed->pixad, w, h); pixSaveTiled(pix6, pixac, 1.0, 1, 10, 32); numaWriteStream(stderr, wshed->nalevels); pix7 = wshedRenderFill(wshed); pixSaveTiled(pix7, pixac, 1.0, 0, 10, 32); pix8 = wshedRenderColors(wshed); pixSaveTiled(pix8, pixac, 1.0, 0, 10, 32); wshedDestroy(&wshed); pixd = pixaDisplay(pixac, 0, 0); pixDisplay(pixd, 100, 100); pixWrite("/tmp/wshed.png", pixd, IFF_PNG); pixDestroy(&pixd); pixaDestroy(&pixac); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix6); pixDestroy(&pix7); pixDestroy(&pix8); pixDestroy(&pixs); pixDestroy(&pixc); ptaDestroy(&pta); return 0; }
/* * pixWriteSegmentedPageToPS() * * Input: pixs (all depths; colormap ok) * pixm (<optional> 1 bpp segmentation mask over image region) * textscale (scale of text output relative to pixs) * imagescale (scale of image output relative to pixs) * threshold (threshold for binarization; typ. 190) * pageno (page number in set; use 1 for new output file) * fileout (output ps file) * Return: 0 if OK, 1 on error * * Notes: * (1) This generates the PS string for a mixed text/image page, * and adds it to an existing file if @pageno > 1. * The PS output is determined by fitting the result to * a letter-size (8.5 x 11 inch) page. * (2) The two images (pixs and pixm) are at the same resolution * (typically 300 ppi). They are used to generate two compressed * images, pixb and pixc, that are put directly into the output * PS file. * (3) pixb is the text component. In the PostScript world, we think of * it as a mask through which we paint black. It is produced by * scaling pixs by @textscale, and thresholding to 1 bpp. * (4) pixc is the image component, which is that part of pixs under * the mask pixm. It is scaled from pixs by @imagescale. * (5) Typical values are textscale = 2.0 and imagescale = 0.5. * (6) If pixm == NULL, the page has only text. If it is all black, * the page is all image and has no text. * (7) This can be used to write a multi-page PS file, by using * sequential page numbers with the same output file. It can * also be used to write separate PS files for each page, * by using different output files with @pageno = 0 or 1. */ l_int32 pixWriteSegmentedPageToPS(PIX *pixs, PIX *pixm, l_float32 textscale, l_float32 imagescale, l_int32 threshold, l_int32 pageno, const char *fileout) { l_int32 alltext, notext, d, ret; l_uint32 val; l_float32 scaleratio; PIX *pixmi, *pixmis, *pixt, *pixg, *pixsc, *pixb, *pixc; PROCNAME("pixWriteSegmentedPageToPS"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!fileout) return ERROR_INT("fileout not defined", procName, 1); if (imagescale <= 0.0 || textscale <= 0.0) return ERROR_INT("relative scales must be > 0.0", procName, 1); /* Analyze the page. Determine the ratio by which the * binary text mask is scaled relative to the image part. * If there is no image region (alltext == TRUE), the * text mask will be rendered directly to fit the page, * and scaleratio = 1.0. */ alltext = TRUE; notext = FALSE; scaleratio = 1.0; if (pixm) { pixZero(pixm, &alltext); /* pixm empty: all text */ if (alltext) pixm = NULL; /* treat it as not existing here */ else { pixmi = pixInvert(NULL, pixm); pixZero(pixmi, ¬ext); /* pixm full; no text */ pixDestroy(&pixmi); scaleratio = textscale / imagescale; } } if (pixGetDepth(pixs) == 1) { /* render tiff g4 */ pixb = pixClone(pixs); pixc = NULL; } else { pixt = pixConvertTo8Or32(pixs, 0, 0); /* this can be a clone of pixs */ /* Get the binary text mask. Note that pixg cannot be a * clone of pixs, because it may be altered by pixSetMasked(). */ pixb = NULL; if (notext == FALSE) { d = pixGetDepth(pixt); if (d == 8) pixg = pixCopy(NULL, pixt); else /* d == 32 */ pixg = pixConvertRGBToLuminance(pixt); if (pixm) /* clear out the image parts */ pixSetMasked(pixg, pixm, 255); if (textscale == 1.0) pixsc = pixClone(pixg); else if (textscale >= 0.7) pixsc = pixScaleGrayLI(pixg, textscale, textscale); else pixsc = pixScaleAreaMap(pixg, textscale, textscale); pixb = pixThresholdToBinary(pixsc, threshold); pixDestroy(&pixg); pixDestroy(&pixsc); } /* Get the scaled image region */ pixc = NULL; if (pixm) { if (imagescale == 1.0) pixsc = pixClone(pixt); /* can possibly be a clone of pixs */ else pixsc = pixScale(pixt, imagescale, imagescale); /* If pixm is not full, clear the pixels in pixsc * corresponding to bg in pixm, where there can be text * that is written through the mask pixb. Note that * we could skip this and use pixsc directly in * pixWriteMixedToPS(); however, clearing these * non-image regions to a white background will reduce * the size of pixc (relative to pixsc), and hence * reduce the size of the PS file that is generated. * Use a copy so that we don't accidentally alter pixs. */ if (notext == FALSE) { pixmis = pixScale(pixm, imagescale, imagescale); pixmi = pixInvert(NULL, pixmis); val = (d == 8) ? 0xff : 0xffffff00; pixc = pixCopy(NULL, pixsc); pixSetMasked(pixc, pixmi, val); /* clear non-image part */ pixDestroy(&pixmis); pixDestroy(&pixmi); } else pixc = pixClone(pixsc); pixDestroy(&pixsc); } pixDestroy(&pixt); } ret = pixWriteMixedToPS(pixb, pixc, scaleratio, pageno, fileout); pixDestroy(&pixb); pixDestroy(&pixc); return ret; }
void RotateOrthTest(PIX *pixs, L_REGPARAMS *rp) { l_int32 zero, count; PIX *pixt, *pixd; PIXCMAP *cmap; cmap = pixGetColormap(pixs); /* Test 4 successive 90 degree rotations */ pixt = pixRotate90(pixs, 1); pixd = pixRotate90(pixt, 1); pixDestroy(&pixt); pixt = pixRotate90(pixd, 1); pixDestroy(&pixd); pixd = pixRotate90(pixt, 1); pixDestroy(&pixt); regTestComparePix(rp, pixs, pixd); if (!cmap) { pixXor(pixd, pixd, pixs); pixZero(pixd, &zero); if (zero) fprintf(stderr, "OK. Four 90-degree rotations gives I\n"); else { pixCountPixels(pixd, &count, NULL); fprintf(stderr, "Failure for four 90-degree rots; count = %d\n", count); } } pixDestroy(&pixd); /* Test 2 successive 180 degree rotations */ pixt = pixRotate180(NULL, pixs); pixRotate180(pixt, pixt); regTestComparePix(rp, pixs, pixt); if (!cmap) { pixXor(pixt, pixt, pixs); pixZero(pixt, &zero); if (zero) fprintf(stderr, "OK. Two 180-degree rotations gives I\n"); else { pixCountPixels(pixt, &count, NULL); fprintf(stderr, "Failure for two 180-degree rots; count = %d\n", count); } } pixDestroy(&pixt); /* Test 2 successive LR flips */ pixt = pixFlipLR(NULL, pixs); pixFlipLR(pixt, pixt); regTestComparePix(rp, pixs, pixt); if (!cmap) { pixXor(pixt, pixt, pixs); pixZero(pixt, &zero); if (zero) fprintf(stderr, "OK. Two LR flips gives I\n"); else { pixCountPixels(pixt, &count, NULL); fprintf(stderr, "Failure for two LR flips; count = %d\n", count); } } pixDestroy(&pixt); /* Test 2 successive TB flips */ pixt = pixFlipTB(NULL, pixs); pixFlipTB(pixt, pixt); regTestComparePix(rp, pixs, pixt); if (!cmap) { pixXor(pixt, pixt, pixs); pixZero(pixt, &zero); if (zero) fprintf(stderr, "OK. Two TB flips gives I\n"); else { pixCountPixels(pixt, &count, NULL); fprintf(stderr, "Failure for two TB flips; count = %d\n", count); } } pixDestroy(&pixt); return; }
l_int32 DoPageSegmentation(PIX *pixs, /* should be at least 300 ppi */ l_int32 which) /* 1, 2, 3, 4 */ { char buf[256]; l_int32 zero; BOXA *boxatm, *boxahm; PIX *pixr; /* image reduced to 150 ppi */ PIX *pixhs; /* image of halftone seed, 150 ppi */ PIX *pixm; /* image of mask of components, 150 ppi */ PIX *pixhm1; /* image of halftone mask, 150 ppi */ PIX *pixhm2; /* image of halftone mask, 300 ppi */ PIX *pixht; /* image of halftone components, 150 ppi */ PIX *pixnht; /* image without halftone components, 150 ppi */ PIX *pixi; /* inverted image, 150 ppi */ PIX *pixvws; /* image of vertical whitespace, 150 ppi */ PIX *pixm1; /* image of closed textlines, 150 ppi */ PIX *pixm2; /* image of refined text line mask, 150 ppi */ PIX *pixm3; /* image of refined text line mask, 300 ppi */ PIX *pixb1; /* image of text block mask, 150 ppi */ PIX *pixb2; /* image of text block mask, 300 ppi */ PIX *pixnon; /* image of non-text or halftone, 150 ppi */ PIX *pix1, *pix2, *pix3, *pix4; PIXA *pixa; PIXCMAP *cmap; PTAA *ptaa; l_int32 ht_flag = 0; l_int32 ws_flag = 0; l_int32 text_flag = 0; l_int32 block_flag = 0; PROCNAME("DoPageSegmentation"); if (which == 1) ht_flag = 1; else if (which == 2) ws_flag = 1; else if (which == 3) text_flag = 1; else if (which == 4) block_flag = 1; else return ERROR_INT("invalid parameter: not in [1...4]", procName, 1); pixa = pixaCreate(0); lept_mkdir("lept/livre"); /* Reduce to 150 ppi */ pix1 = pixScaleToGray2(pixs); if (ws_flag || ht_flag || block_flag) pixaAddPix(pixa, pix1, L_COPY); if (which == 1) pixWrite("/tmp/lept/livre/orig.gray.150.png", pix1, IFF_PNG); pixDestroy(&pix1); pixr = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0); /* Get seed for halftone parts */ pix1 = pixReduceRankBinaryCascade(pixr, 4, 4, 3, 0); pix2 = pixOpenBrick(NULL, pix1, 5, 5); pixhs = pixExpandBinaryPower2(pix2, 8); if (ht_flag) pixaAddPix(pixa, pixhs, L_COPY); if (which == 1) pixWrite("/tmp/lept/livre/htseed.150.png", pixhs, IFF_PNG); pixDestroy(&pix1); pixDestroy(&pix2); /* Get mask for connected regions */ pixm = pixCloseSafeBrick(NULL, pixr, 4, 4); if (ht_flag) pixaAddPix(pixa, pixm, L_COPY); if (which == 1) pixWrite("/tmp/lept/livre/ccmask.150.png", pixm, IFF_PNG); /* Fill seed into mask to get halftone mask */ pixhm1 = pixSeedfillBinary(NULL, pixhs, pixm, 4); if (ht_flag) pixaAddPix(pixa, pixhm1, L_COPY); if (which == 1) pixWrite("/tmp/lept/livre/htmask.150.png", pixhm1, IFF_PNG); pixhm2 = pixExpandBinaryPower2(pixhm1, 2); /* Extract halftone stuff */ pixht = pixAnd(NULL, pixhm1, pixr); if (which == 1) pixWrite("/tmp/lept/livre/ht.150.png", pixht, IFF_PNG); /* Extract non-halftone stuff */ pixnht = pixXor(NULL, pixht, pixr); if (text_flag) pixaAddPix(pixa, pixnht, L_COPY); if (which == 1) pixWrite("/tmp/lept/livre/text.150.png", pixnht, IFF_PNG); pixZero(pixht, &zero); if (zero) fprintf(stderr, "No halftone parts found\n"); else fprintf(stderr, "Halftone parts found\n"); /* Get bit-inverted image */ pixi = pixInvert(NULL, pixnht); if (ws_flag) pixaAddPix(pixa, pixi, L_COPY); if (which == 1) pixWrite("/tmp/lept/livre/invert.150.png", pixi, IFF_PNG); /* The whitespace mask will break textlines where there * is a large amount of white space below or above. * We can prevent this by identifying regions of the * inverted image that have large horizontal (bigger than * the separation between columns) and significant * vertical extent (bigger than the separation between * textlines), and subtracting this from the whitespace mask. */ pix1 = pixMorphCompSequence(pixi, "o80.60", 0); pix2 = pixSubtract(NULL, pixi, pix1); if (ws_flag) pixaAddPix(pixa, pix2, L_COPY); pixDestroy(&pix1); /* Identify vertical whitespace by opening inverted image */ pix3 = pixOpenBrick(NULL, pix2, 5, 1); /* removes thin vertical lines */ pixvws = pixOpenBrick(NULL, pix3, 1, 200); /* gets long vertical lines */ if (text_flag || ws_flag) pixaAddPix(pixa, pixvws, L_COPY); if (which == 1) pixWrite("/tmp/lept/livre/vertws.150.png", pixvws, IFF_PNG); pixDestroy(&pix2); pixDestroy(&pix3); /* Get proto (early processed) text line mask. */ /* First close the characters and words in the textlines */ pixm1 = pixCloseSafeBrick(NULL, pixnht, 30, 1); if (text_flag) pixaAddPix(pixa, pixm1, L_COPY); if (which == 1) pixWrite("/tmp/lept/livre/textmask1.150.png", pixm1, IFF_PNG); /* Next open back up the vertical whitespace corridors */ pixm2 = pixSubtract(NULL, pixm1, pixvws); if (which == 1) pixWrite("/tmp/lept/livre/textmask2.150.png", pixm2, IFF_PNG); /* Do a small opening to remove noise */ pixOpenBrick(pixm2, pixm2, 3, 3); if (text_flag) pixaAddPix(pixa, pixm2, L_COPY); if (which == 1) pixWrite("/tmp/lept/livre/textmask3.150.png", pixm2, IFF_PNG); pixm3 = pixExpandBinaryPower2(pixm2, 2); /* Join pixels vertically to make text block mask */ pixb1 = pixMorphSequence(pixm2, "c1.10 + o4.1", 0); if (block_flag) pixaAddPix(pixa, pixb1, L_COPY); if (which == 1) pixWrite("/tmp/lept/livre/textblock1.150.png", pixb1, IFF_PNG); /* Solidify the textblock mask and remove noise: * (1) For each c.c., close the blocks and dilate slightly * to form a solid mask. * (2) Small horizontal closing between components * (3) Open the white space between columns, again * (4) Remove small components */ pix1 = pixMorphSequenceByComponent(pixb1, "c30.30 + d3.3", 8, 0, 0, NULL); pixCloseSafeBrick(pix1, pix1, 10, 1); if (block_flag) pixaAddPix(pixa, pix1, L_COPY); pix2 = pixSubtract(NULL, pix1, pixvws); pix3 = pixSelectBySize(pix2, 25, 5, 8, L_SELECT_IF_BOTH, L_SELECT_IF_GTE, NULL); if (block_flag) pixaAddPix(pixa, pix3, L_COPY); if (which == 1) pixWrite("/tmp/lept/livre/textblock2.150.png", pix3, IFF_PNG); pixb2 = pixExpandBinaryPower2(pix3, 2); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); /* Identify the outlines of each textblock */ ptaa = pixGetOuterBordersPtaa(pixb2); pix1 = pixRenderRandomCmapPtaa(pixb2, ptaa, 1, 8, 1); cmap = pixGetColormap(pix1); pixcmapResetColor(cmap, 0, 130, 130, 130); /* set interior to gray */ if (which == 1) pixWrite("/tmp/lept/livre/textblock3.300.png", pix1, IFF_PNG); pixDisplayWithTitle(pix1, 480, 360, "textblock mask with outlines", DFLAG); ptaaDestroy(&ptaa); pixDestroy(&pix1); /* Fill line mask (as seed) into the original */ pix1 = pixSeedfillBinary(NULL, pixm3, pixs, 8); pixOr(pixm3, pixm3, pix1); pixDestroy(&pix1); if (which == 1) pixWrite("/tmp/lept/livre/textmask.300.png", pixm3, IFF_PNG); pixDisplayWithTitle(pixm3, 480, 360, "textline mask 4", DFLAG); /* Fill halftone mask (as seed) into the original */ pix1 = pixSeedfillBinary(NULL, pixhm2, pixs, 8); pixOr(pixhm2, pixhm2, pix1); pixDestroy(&pix1); if (which == 1) pixWrite("/tmp/lept/livre/htmask.300.png", pixhm2, IFF_PNG); pixDisplayWithTitle(pixhm2, 520, 390, "halftonemask 2", DFLAG); /* Find objects that are neither text nor halftones */ pix1 = pixSubtract(NULL, pixs, pixm3); /* remove text pixels */ pixnon = pixSubtract(NULL, pix1, pixhm2); /* remove halftone pixels */ pixDestroy(&pix1); if (which == 1) pixWrite("/tmp/lept/livre/other.300.png", pixnon, IFF_PNG); pixDisplayWithTitle(pixnon, 540, 420, "other stuff", DFLAG); /* Write out b.b. for text line mask and halftone mask components */ boxatm = pixConnComp(pixm3, NULL, 4); boxahm = pixConnComp(pixhm2, NULL, 8); if (which == 1) { boxaWrite("/tmp/lept/livre/textmask.boxa", boxatm); boxaWrite("/tmp/lept/livre/htmask.boxa", boxahm); } pix1 = pixaDisplayTiledAndScaled(pixa, 8, 250, 4, 0, 25, 2); pixDisplay(pix1, 0, 375 * (which - 1)); snprintf(buf, sizeof(buf), "/tmp/lept/livre/segout.%d.png", which); pixWrite(buf, pix1, IFF_PNG); pixDestroy(&pix1); pixaDestroy(&pixa); /* clean up to test with valgrind */ pixDestroy(&pixr); pixDestroy(&pixhs); pixDestroy(&pixm); pixDestroy(&pixhm1); pixDestroy(&pixhm2); pixDestroy(&pixht); pixDestroy(&pixi); pixDestroy(&pixnht); pixDestroy(&pixvws); pixDestroy(&pixm1); pixDestroy(&pixm2); pixDestroy(&pixm3); pixDestroy(&pixb1); pixDestroy(&pixb2); pixDestroy(&pixnon); boxaDestroy(&boxatm); boxaDestroy(&boxahm); return 0; }
/*! * pixGenTextlineMask() * * Input: pixs (1 bpp, assumed to be 150 to 200 ppi) * &pixvws (<return> vertical whitespace mask) * &tlfound (<optional return> 1 if the mask is not empty) * debug (flag: 1 for debug output) * Return: pixd (textline mask), or null on error * * Notes: * (1) The input pixs should be deskewed. * (2) pixs should have no halftone pixels. * (3) Both the input image and the returned textline mask * are at the same resolution. */ PIX * pixGenTextlineMask(PIX *pixs, PIX **ppixvws, l_int32 *ptlfound, l_int32 debug) { l_int32 empty; PIX *pixt1, *pixt2, *pixvws, *pixd; PROCNAME("pixGenTextlineMask"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!ppixvws) return (PIX *)ERROR_PTR("&pixvws not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL); /* First we need a vertical whitespace mask. Invert the image. */ pixt1 = pixInvert(NULL, pixs); /* The whitespace mask will break textlines where there * is a large amount of white space below or above. * This can be prevented by identifying regions of the * inverted image that have large horizontal extent (bigger than * the separation between columns) and significant * vertical extent (bigger than the separation between * textlines), and subtracting this from the bg. */ pixt2 = pixMorphCompSequence(pixt1, "o80.60", 0); pixSubtract(pixt1, pixt1, pixt2); pixDisplayWriteFormat(pixt1, debug, IFF_PNG); pixDestroy(&pixt2); /* Identify vertical whitespace by opening the remaining bg. * o5.1 removes thin vertical bg lines and o1.200 extracts * long vertical bg lines. */ pixvws = pixMorphCompSequence(pixt1, "o5.1 + o1.200", 0); *ppixvws = pixvws; pixDisplayWriteFormat(pixvws, debug, IFF_PNG); pixDestroy(&pixt1); /* Three steps to getting text line mask: * (1) close the characters and words in the textlines * (2) open the vertical whitespace corridors back up * (3) small opening to remove noise */ pixt1 = pixCloseSafeBrick(NULL, pixs, 30, 1); pixDisplayWrite(pixt1, debug); pixd = pixSubtract(NULL, pixt1, pixvws); pixOpenBrick(pixd, pixd, 3, 3); pixDisplayWriteFormat(pixd, debug, IFF_PNG); pixDestroy(&pixt1); /* Check if text line mask is empty */ if (ptlfound) { *ptlfound = 0; pixZero(pixd, &empty); if (!empty) *ptlfound = 1; } return pixd; }
int main(int argc, char **argv) { l_int32 w, h, n, i, sum, sumi, empty; BOX *box1, *box2, *box3, *box4; BOXA *boxa, *boxat; NUMA *na1, *na2, *na3, *na4, *na5; NUMA *na2i, *na3i, *na4i, *nat, *naw, *nah; PIX *pixs, *pixc, *pixt, *pixt2, *pixd, *pixcount; PIXA *pixas, *pixad, *pixac; pixDisplayWrite(NULL, -1); /* Draw 4 filled boxes of different sizes */ pixs = pixCreate(200, 200, 1); box1 = boxCreate(10, 10, 20, 30); box2 = boxCreate(50, 10, 40, 20); box3 = boxCreate(110, 10, 35, 5); box4 = boxCreate(160, 10, 5, 15); boxa = boxaCreate(4); boxaAddBox(boxa, box1, L_INSERT); boxaAddBox(boxa, box2, L_INSERT); boxaAddBox(boxa, box3, L_INSERT); boxaAddBox(boxa, box4, L_INSERT); pixRenderBox(pixs, box1, 1, L_SET_PIXELS); pixRenderBox(pixs, box2, 1, L_SET_PIXELS); pixRenderBox(pixs, box3, 1, L_SET_PIXELS); pixRenderBox(pixs, box4, 1, L_SET_PIXELS); pixt = pixFillClosedBorders(pixs, 4); pixDisplayWrite(pixt, 1); pixt2 = pixCreateTemplate(pixs); pixRenderHashBox(pixt2, box1, 6, 4, L_POS_SLOPE_LINE, 1, L_SET_PIXELS); pixRenderHashBox(pixt2, box2, 7, 2, L_POS_SLOPE_LINE, 1, L_SET_PIXELS); pixRenderHashBox(pixt2, box3, 4, 2, L_VERTICAL_LINE, 1, L_SET_PIXELS); pixRenderHashBox(pixt2, box4, 3, 1, L_HORIZONTAL_LINE, 1, L_SET_PIXELS); pixDisplayWrite(pixt2, 1); /* Exercise the parameters */ pixd = pixSelectBySize(pixt, 0, 22, 8, L_SELECT_HEIGHT, L_SELECT_IF_GT, NULL); count_pieces(pixd, 1); pixd = pixSelectBySize(pixt, 0, 30, 8, L_SELECT_HEIGHT, L_SELECT_IF_LT, NULL); count_pieces(pixd, 3); pixd = pixSelectBySize(pixt, 0, 5, 8, L_SELECT_HEIGHT, L_SELECT_IF_GT, NULL); count_pieces(pixd, 3); pixd = pixSelectBySize(pixt, 0, 6, 8, L_SELECT_HEIGHT, L_SELECT_IF_LT, NULL); count_pieces(pixd, 1); pixd = pixSelectBySize(pixt, 20, 0, 8, L_SELECT_WIDTH, L_SELECT_IF_GT, NULL); count_pieces(pixd, 2); pixd = pixSelectBySize(pixt, 31, 0, 8, L_SELECT_WIDTH, L_SELECT_IF_LT, NULL); count_pieces(pixd, 2); pixd = pixSelectBySize(pixt, 21, 10, 8, L_SELECT_IF_EITHER, L_SELECT_IF_LT, NULL); count_pieces(pixd, 3); pixd = pixSelectBySize(pixt, 20, 30, 8, L_SELECT_IF_EITHER, L_SELECT_IF_GT, NULL); count_pieces(pixd, 2); pixd = pixSelectBySize(pixt, 22, 32, 8, L_SELECT_IF_BOTH, L_SELECT_IF_LT, NULL); count_pieces(pixd, 2); pixd = pixSelectBySize(pixt, 6, 32, 8, L_SELECT_IF_BOTH, L_SELECT_IF_LT, NULL); count_pieces(pixd, 1); pixd = pixSelectBySize(pixt, 5, 25, 8, L_SELECT_IF_BOTH, L_SELECT_IF_GT, NULL); count_pieces(pixd, 1); pixd = pixSelectBySize(pixt, 25, 5, 8, L_SELECT_IF_BOTH, L_SELECT_IF_GT, NULL); count_pieces(pixd, 1); pixd = pixSelectByPerimToAreaRatio(pixt, 0.3, 8, L_SELECT_IF_GT, NULL); count_pieces(pixd, 2); pixd = pixSelectByPerimToAreaRatio(pixt, 0.15, 8, L_SELECT_IF_GT, NULL); count_pieces(pixd, 3); pixd = pixSelectByPerimToAreaRatio(pixt, 0.4, 8, L_SELECT_IF_LTE, NULL); count_pieces(pixd, 2); pixd = pixSelectByPerimToAreaRatio(pixt, 0.45, 8, L_SELECT_IF_LT, NULL); count_pieces(pixd, 3); pixd = pixSelectByPerimSizeRatio(pixt2, 2.3, 8, L_SELECT_IF_GT, NULL); count_pieces(pixd, 2); pixd = pixSelectByPerimSizeRatio(pixt2, 1.2, 8, L_SELECT_IF_GT, NULL); count_pieces(pixd, 3); pixd = pixSelectByPerimSizeRatio(pixt2, 1.7, 8, L_SELECT_IF_LTE, NULL); count_pieces(pixd, 1); pixd = pixSelectByPerimSizeRatio(pixt2, 2.9, 8, L_SELECT_IF_LT, NULL); count_pieces(pixd, 3); pixd = pixSelectByAreaFraction(pixt2, 0.3, 8, L_SELECT_IF_LT, NULL); count_pieces(pixd, 0); pixd = pixSelectByAreaFraction(pixt2, 0.9, 8, L_SELECT_IF_LT, NULL); count_pieces(pixd, 4); pixd = pixSelectByAreaFraction(pixt2, 0.5, 8, L_SELECT_IF_GTE, NULL); count_pieces(pixd, 3); pixd = pixSelectByAreaFraction(pixt2, 0.7, 8, L_SELECT_IF_GT, NULL); count_pieces(pixd, 2); boxat = boxaSelectBySize(boxa, 21, 10, L_SELECT_IF_EITHER, L_SELECT_IF_LT, NULL); count_pieces2(boxat, 3); boxat = boxaSelectBySize(boxa, 22, 32, L_SELECT_IF_BOTH, L_SELECT_IF_LT, NULL); count_pieces2(boxat, 2); boxaDestroy(&boxa); pixDestroy(&pixt); pixDestroy(&pixt2); pixDestroy(&pixs); /* Here's the most general method for selecting components. * We do it for area fraction, but any combination of * size, area/perimeter ratio and area fraction can be used. */ pixs = pixRead("feyn.tif"); /* pixs = pixRead("rabi.png"); */ pixc = pixCopy(NULL, pixs); /* subtract bands from this */ pixt = pixCreateTemplate(pixs); /* add bands to this */ pixGetDimensions(pixs, &w, &h, NULL); boxa = pixConnComp(pixs, &pixas, 8); n = boxaGetCount(boxa); fprintf(stderr, "total: %d\n", n); na1 = pixaFindAreaFraction(pixas); nat = numaCreate(0); numaSetCount(nat, n); /* initialize to all 0 */ sum = sumi = 0; pixac = pixaCreate(0); for (i = 0; i < 12; i++) { /* Compute within the intervals using an intersection. */ na2 = numaMakeThresholdIndicator(na1, edges[i], L_SELECT_IF_GTE); if (i != 11) na3 = numaMakeThresholdIndicator(na1, edges[i + 1], L_SELECT_IF_LT); else na3 = numaMakeThresholdIndicator(na1, edges[i + 1], L_SELECT_IF_LTE); na4 = numaLogicalOp(NULL, na2, na3, L_INTERSECTION); sum += count_ones(na4, 0, 0, NULL); /* Compute outside the intervals using a union, and invert */ na2i = numaMakeThresholdIndicator(na1, edges[i], L_SELECT_IF_LT); if (i != 11) na3i = numaMakeThresholdIndicator(na1, edges[i + 1], L_SELECT_IF_GTE); else na3i = numaMakeThresholdIndicator(na1, edges[i + 1], L_SELECT_IF_GT); na4i = numaLogicalOp(NULL, na3i, na2i, L_UNION); numaInvert(na4i, na4i); sumi += count_ones(na4i, 0, 0, NULL); /* Compare the two methods */ if (sum == sumi) fprintf(stderr, "\nCorrect: sum = sumi = %d\n", sum); else fprintf(stderr, "\nWRONG: sum = %d, sumi = %d\n", sum, sumi); /* Reconstruct the image, band by band. */ numaLogicalOp(nat, nat, na4, L_UNION); pixad = pixaSelectWithIndicator(pixas, na4, NULL); pixd = pixaDisplay(pixad, w, h); pixOr(pixt, pixt, pixd); /* add them in */ pixcount = pixCopy(NULL, pixt); /* destroyed by count_pieces */ count_ones(na4, band[i], i, "band"); count_pieces(pixd, band[i]); count_ones(nat, total[i], i, "total"); count_pieces(pixcount, total[i]); pixaDestroy(&pixad); /* Remove band successively from full image */ pixRemoveWithIndicator(pixc, pixas, na4); pixSaveTiled(pixc, pixac, 0.25, 1 - i % 2, 25, 8); numaDestroy(&na2); numaDestroy(&na3); numaDestroy(&na4); numaDestroy(&na2i); numaDestroy(&na3i); numaDestroy(&na4i); } /* Did we remove all components from pixc? */ pixZero(pixc, &empty); if (!empty) fprintf(stderr, "\nWRONG: not all pixels removed from pixc\n"); pixDestroy(&pixs); pixDestroy(&pixc); pixDestroy(&pixt); boxaDestroy(&boxa); pixaDestroy(&pixas); numaDestroy(&na1); numaDestroy(&nat); /* One last extraction. Get all components that have either * a height of at least 50 or a width of between 30 and 35, * and also have a relatively large perimeter/area ratio. */ pixs = pixRead("feyn.tif"); boxa = pixConnComp(pixs, &pixas, 8); n = boxaGetCount(boxa); pixaFindDimensions(pixas, &naw, &nah); na1 = pixaFindPerimToAreaRatio(pixas); na2 = numaMakeThresholdIndicator(nah, 50, L_SELECT_IF_GTE); na3 = numaMakeThresholdIndicator(naw, 30, L_SELECT_IF_GTE); na4 = numaMakeThresholdIndicator(naw, 35, L_SELECT_IF_LTE); na5 = numaMakeThresholdIndicator(na1, 0.4, L_SELECT_IF_GTE); numaLogicalOp(na3, na3, na4, L_INTERSECTION); numaLogicalOp(na2, na2, na3, L_UNION); numaLogicalOp(na2, na2, na5, L_INTERSECTION); numaInvert(na2, na2); /* get components to be removed */ pixRemoveWithIndicator(pixs, pixas, na2); pixSaveTiled(pixs, pixac, 0.25, 1, 25, 8); pixDestroy(&pixs); boxaDestroy(&boxa); pixaDestroy(&pixas); numaDestroy(&naw); numaDestroy(&nah); numaDestroy(&na1); numaDestroy(&na2); numaDestroy(&na3); numaDestroy(&na4); numaDestroy(&na5); pixDisplayMultiple("/tmp/display/file*"); pixd = pixaDisplay(pixac, 0, 0); pixDisplay(pixd, 100, 100); pixWrite("/tmp/comp.jpg", pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); pixaDestroy(&pixac); return 0; }
/*! * pixFindSkewSweepAndSearchScorePivot() * * Input: pixs (1 bpp) * &angle (<return> angle required to deskew; in degrees) * &conf (<return> confidence given by ratio of max/min score) * &endscore (<optional return> max score; use NULL to ignore) * redsweep (sweep reduction factor = 1, 2, 4 or 8) * redsearch (binary search reduction factor = 1, 2, 4 or 8; * and must not exceed redsweep) * sweepcenter (angle about which sweep is performed; in degrees) * sweeprange (half the full range, taken about sweepcenter; * in degrees) * sweepdelta (angle increment of sweep; in degrees) * minbsdelta (min binary search increment angle; in degrees) * pivot (L_SHEAR_ABOUT_CORNER, L_SHEAR_ABOUT_CENTER) * Return: 0 if OK, 1 on error or if angle measurment not valid * * Notes: * (1) See notes in pixFindSkewSweepAndSearchScore(). * (2) This allows choice of shear pivoting from either the UL corner * or the center. For small angles, the ability to discriminate * angles is better with shearing from the UL corner. However, * for large angles (say, greater than 20 degrees), it is better * to shear about the center because a shear from the UL corner * loses too much of the image. */ l_int32 pixFindSkewSweepAndSearchScorePivot(PIX *pixs, l_float32 *pangle, l_float32 *pconf, l_float32 *pendscore, l_int32 redsweep, l_int32 redsearch, l_float32 sweepcenter, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, l_int32 pivot) { l_int32 ret, bzero, i, nangles, n, ratio, maxindex, minloc; l_int32 width, height; l_float32 deg2rad, theta, delta; l_float32 sum, maxscore, maxangle; l_float32 centerangle, leftcenterangle, rightcenterangle; l_float32 lefttemp, righttemp; l_float32 bsearchscore[5]; l_float32 minscore, minthresh; l_float32 rangeleft; NUMA *natheta, *nascore; PIX *pixsw, *pixsch, *pixt1, *pixt2; PROCNAME("pixFindSkewSweepAndSearchScorePivot"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (pixGetDepth(pixs) != 1) return ERROR_INT("pixs not 1 bpp", procName, 1); if (!pangle) return ERROR_INT("&angle not defined", procName, 1); if (!pconf) return ERROR_INT("&conf not defined", procName, 1); if (redsweep != 1 && redsweep != 2 && redsweep != 4 && redsweep != 8) return ERROR_INT("redsweep must be in {1,2,4,8}", procName, 1); if (redsearch != 1 && redsearch != 2 && redsearch != 4 && redsearch != 8) return ERROR_INT("redsearch must be in {1,2,4,8}", procName, 1); if (redsearch > redsweep) return ERROR_INT("redsearch must not exceed redsweep", procName, 1); if (pivot != L_SHEAR_ABOUT_CORNER && pivot != L_SHEAR_ABOUT_CENTER) return ERROR_INT("invalid pivot", procName, 1); *pangle = 0.0; *pconf = 0.0; deg2rad = 3.1415926535 / 180.; ret = 0; /* Generate reduced image for binary search, if requested */ if (redsearch == 1) pixsch = pixClone(pixs); else if (redsearch == 2) pixsch = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0); else if (redsearch == 4) pixsch = pixReduceRankBinaryCascade(pixs, 1, 1, 0, 0); else /* redsearch == 8 */ pixsch = pixReduceRankBinaryCascade(pixs, 1, 1, 2, 0); pixZero(pixsch, &bzero); if (bzero) { pixDestroy(&pixsch); return 1; } /* Generate reduced image for sweep, if requested */ ratio = redsweep / redsearch; if (ratio == 1) { pixsw = pixClone(pixsch); } else { /* ratio > 1 */ if (ratio == 2) pixsw = pixReduceRankBinaryCascade(pixsch, 1, 0, 0, 0); else if (ratio == 4) pixsw = pixReduceRankBinaryCascade(pixsch, 1, 2, 0, 0); else /* ratio == 8 */ pixsw = pixReduceRankBinaryCascade(pixsch, 1, 2, 2, 0); } pixt1 = pixCreateTemplate(pixsw); if (ratio == 1) pixt2 = pixClone(pixt1); else pixt2 = pixCreateTemplate(pixsch); nangles = (l_int32)((2. * sweeprange) / sweepdelta + 1); natheta = numaCreate(nangles); nascore = numaCreate(nangles); if (!pixsch || !pixsw) { ret = ERROR_INT("pixsch and pixsw not both made", procName, 1); goto cleanup; } if (!pixt1 || !pixt2) { ret = ERROR_INT("pixt1 and pixt2 not both made", procName, 1); goto cleanup; } if (!natheta || !nascore) { ret = ERROR_INT("natheta and nascore not both made", procName, 1); goto cleanup; } /* Do sweep */ rangeleft = sweepcenter - sweeprange; for (i = 0; i < nangles; i++) { theta = rangeleft + i * sweepdelta; /* degrees */ /* Shear pix and put the result in pixt1 */ if (pivot == L_SHEAR_ABOUT_CORNER) pixVShearCorner(pixt1, pixsw, deg2rad * theta, L_BRING_IN_WHITE); else pixVShearCenter(pixt1, pixsw, deg2rad * theta, L_BRING_IN_WHITE); /* Get score */ pixFindDifferentialSquareSum(pixt1, &sum); #if DEBUG_PRINT_SCORES L_INFO("sum(%7.2f) = %7.0f\n", procName, theta, sum); #endif /* DEBUG_PRINT_SCORES */ /* Save the result in the output arrays */ numaAddNumber(nascore, sum); numaAddNumber(natheta, theta); } /* Find the largest of the set (maxscore at maxangle) */ numaGetMax(nascore, &maxscore, &maxindex); numaGetFValue(natheta, maxindex, &maxangle); #if DEBUG_PRINT_SWEEP L_INFO(" From sweep: angle = %7.3f, score = %7.3f\n", procName, maxangle, maxscore); #endif /* DEBUG_PRINT_SWEEP */ #if DEBUG_PLOT_SCORES /* Plot the sweep result -- the scores versus rotation angle -- * using gnuplot with GPLOT_LINES (lines connecting data points). */ {GPLOT *gplot; gplot = gplotCreate("sweep_output", GPLOT_PNG, "Sweep. Variance of difference of ON pixels vs. angle", "angle (deg)", "score"); gplotAddPlot(gplot, natheta, nascore, GPLOT_LINES, "plot1"); gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot2"); gplotMakeOutput(gplot); gplotDestroy(&gplot); } #endif /* DEBUG_PLOT_SCORES */ /* Check if the max is at the end of the sweep. */ n = numaGetCount(natheta); if (maxindex == 0 || maxindex == n - 1) { L_WARNING("max found at sweep edge\n", procName); goto cleanup; } /* Empty the numas for re-use */ numaEmpty(nascore); numaEmpty(natheta); /* Do binary search to find skew angle. * First, set up initial three points. */ centerangle = maxangle; if (pivot == L_SHEAR_ABOUT_CORNER) { pixVShearCorner(pixt2, pixsch, deg2rad * centerangle, L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[2]); pixVShearCorner(pixt2, pixsch, deg2rad * (centerangle - sweepdelta), L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[0]); pixVShearCorner(pixt2, pixsch, deg2rad * (centerangle + sweepdelta), L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[4]); } else { pixVShearCenter(pixt2, pixsch, deg2rad * centerangle, L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[2]); pixVShearCenter(pixt2, pixsch, deg2rad * (centerangle - sweepdelta), L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[0]); pixVShearCenter(pixt2, pixsch, deg2rad * (centerangle + sweepdelta), L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[4]); } numaAddNumber(nascore, bsearchscore[2]); numaAddNumber(natheta, centerangle); numaAddNumber(nascore, bsearchscore[0]); numaAddNumber(natheta, centerangle - sweepdelta); numaAddNumber(nascore, bsearchscore[4]); numaAddNumber(natheta, centerangle + sweepdelta); /* Start the search */ delta = 0.5 * sweepdelta; while (delta >= minbsdelta) { /* Get the left intermediate score */ leftcenterangle = centerangle - delta; if (pivot == L_SHEAR_ABOUT_CORNER) pixVShearCorner(pixt2, pixsch, deg2rad * leftcenterangle, L_BRING_IN_WHITE); else pixVShearCenter(pixt2, pixsch, deg2rad * leftcenterangle, L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[1]); numaAddNumber(nascore, bsearchscore[1]); numaAddNumber(natheta, leftcenterangle); /* Get the right intermediate score */ rightcenterangle = centerangle + delta; if (pivot == L_SHEAR_ABOUT_CORNER) pixVShearCorner(pixt2, pixsch, deg2rad * rightcenterangle, L_BRING_IN_WHITE); else pixVShearCenter(pixt2, pixsch, deg2rad * rightcenterangle, L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[3]); numaAddNumber(nascore, bsearchscore[3]); numaAddNumber(natheta, rightcenterangle); /* Find the maximum of the five scores and its location. * Note that the maximum must be in the center * three values, not in the end two. */ maxscore = bsearchscore[1]; maxindex = 1; for (i = 2; i < 4; i++) { if (bsearchscore[i] > maxscore) { maxscore = bsearchscore[i]; maxindex = i; } } /* Set up score array to interpolate for the next iteration */ lefttemp = bsearchscore[maxindex - 1]; righttemp = bsearchscore[maxindex + 1]; bsearchscore[2] = maxscore; bsearchscore[0] = lefttemp; bsearchscore[4] = righttemp; /* Get new center angle and delta for next iteration */ centerangle = centerangle + delta * (maxindex - 2); delta = 0.5 * delta; } *pangle = centerangle; #if DEBUG_PRINT_SCORES L_INFO(" Binary search score = %7.3f\n", procName, bsearchscore[2]); #endif /* DEBUG_PRINT_SCORES */ if (pendscore) /* save if requested */ *pendscore = bsearchscore[2]; /* Return the ratio of Max score over Min score * as a confidence value. Don't trust if the Min score * is too small, which can happen if the image is all black * with only a few white pixels interspersed. In that case, * we get a contribution from the top and bottom edges when * vertically sheared, but this contribution becomes zero when * the shear angle is zero. For zero shear angle, the only * contribution will be from the white pixels. We expect that * the signal goes as the product of the (height * width^2), * so we compute a (hopefully) normalized minimum threshold as * a function of these dimensions. */ numaGetMin(nascore, &minscore, &minloc); width = pixGetWidth(pixsch); height = pixGetHeight(pixsch); minthresh = MINSCORE_THRESHOLD_CONSTANT * width * width * height; #if DEBUG_THRESHOLD L_INFO(" minthresh = %10.2f, minscore = %10.2f\n", procName, minthresh, minscore); L_INFO(" maxscore = %10.2f\n", procName, maxscore); #endif /* DEBUG_THRESHOLD */ if (minscore > minthresh) *pconf = maxscore / minscore; else *pconf = 0.0; /* Don't trust it if too close to the edge of the sweep * range or if maxscore is small */ if ((centerangle > rangeleft + 2 * sweeprange - sweepdelta) || (centerangle < rangeleft + sweepdelta) || (maxscore < MIN_VALID_MAXSCORE)) *pconf = 0.0; #if DEBUG_PRINT_BINARY fprintf(stderr, "Binary search: angle = %7.3f, score ratio = %6.2f\n", *pangle, *pconf); fprintf(stderr, " max score = %8.0f\n", maxscore); #endif /* DEBUG_PRINT_BINARY */ #if DEBUG_PLOT_SCORES /* Plot the result -- the scores versus rotation angle -- * using gnuplot with GPLOT_POINTS. Because the data * points are not ordered by theta (increasing or decreasing), * using GPLOT_LINES would be confusing! */ {GPLOT *gplot; gplot = gplotCreate("search_output", GPLOT_PNG, "Binary search. Variance of difference of ON pixels vs. angle", "angle (deg)", "score"); gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot1"); gplotMakeOutput(gplot); gplotDestroy(&gplot); } #endif /* DEBUG_PLOT_SCORES */ cleanup: pixDestroy(&pixsw); pixDestroy(&pixsch); pixDestroy(&pixt1); pixDestroy(&pixt2); numaDestroy(&nascore); numaDestroy(&natheta); return ret; }
/*! * pixFindSkewSweep() * * Input: pixs (1 bpp) * &angle (<return> angle required to deskew, in degrees) * reduction (factor = 1, 2, 4 or 8) * sweeprange (half the full range; assumed about 0; in degrees) * sweepdelta (angle increment of sweep; in degrees) * Return: 0 if OK, 1 on error or if angle measurment not valid * * Notes: * (1) This examines the 'score' for skew angles with equal intervals. * (2) Caller must check the return value for validity of the result. */ l_int32 pixFindSkewSweep(PIX *pixs, l_float32 *pangle, l_int32 reduction, l_float32 sweeprange, l_float32 sweepdelta) { l_int32 ret, bzero, i, nangles; l_float32 deg2rad, theta; l_float32 sum, maxscore, maxangle; NUMA *natheta, *nascore; PIX *pix, *pixt; PROCNAME("pixFindSkewSweep"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (pixGetDepth(pixs) != 1) return ERROR_INT("pixs not 1 bpp", procName, 1); if (!pangle) return ERROR_INT("&angle not defined", procName, 1); if (reduction != 1 && reduction != 2 && reduction != 4 && reduction != 8) return ERROR_INT("reduction must be in {1,2,4,8}", procName, 1); *pangle = 0.0; /* init */ deg2rad = 3.1415926535 / 180.; ret = 0; /* Generate reduced image, if requested */ if (reduction == 1) pix = pixClone(pixs); else if (reduction == 2) pix = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0); else if (reduction == 4) pix = pixReduceRankBinaryCascade(pixs, 1, 1, 0, 0); else /* reduction == 8 */ pix = pixReduceRankBinaryCascade(pixs, 1, 1, 2, 0); pixZero(pix, &bzero); if (bzero) { pixDestroy(&pix); return 1; } nangles = (l_int32)((2. * sweeprange) / sweepdelta + 1); natheta = numaCreate(nangles); nascore = numaCreate(nangles); pixt = pixCreateTemplate(pix); if (!pix || !pixt) { ret = ERROR_INT("pix and pixt not both made", procName, 1); goto cleanup; } if (!natheta || !nascore) { ret = ERROR_INT("natheta and nascore not both made", procName, 1); goto cleanup; } for (i = 0; i < nangles; i++) { theta = -sweeprange + i * sweepdelta; /* degrees */ /* Shear pix about the UL corner and put the result in pixt */ pixVShearCorner(pixt, pix, deg2rad * theta, L_BRING_IN_WHITE); /* Get score */ pixFindDifferentialSquareSum(pixt, &sum); #if DEBUG_PRINT_SCORES L_INFO("sum(%7.2f) = %7.0f\n", procName, theta, sum); #endif /* DEBUG_PRINT_SCORES */ /* Save the result in the output arrays */ numaAddNumber(nascore, sum); numaAddNumber(natheta, theta); } /* Find the location of the maximum (i.e., the skew angle) * by fitting the largest data point and its two neighbors * to a quadratic, using lagrangian interpolation. */ numaFitMax(nascore, &maxscore, natheta, &maxangle); *pangle = maxangle; #if DEBUG_PRINT_SWEEP L_INFO(" From sweep: angle = %7.3f, score = %7.3f\n", procName, maxangle, maxscore); #endif /* DEBUG_PRINT_SWEEP */ #if DEBUG_PLOT_SCORES /* Plot the result -- the scores versus rotation angle -- * using gnuplot with GPLOT_LINES (lines connecting data points). * The GPLOT data structure is first created, with the * appropriate data incorporated from the two input NUMAs, * and then the function gplotMakeOutput() uses gnuplot to * generate the output plot. This can be either a .png file * or a .ps file, depending on whether you use GPLOT_PNG * or GPLOT_PS. */ {GPLOT *gplot; gplot = gplotCreate("sweep_output", GPLOT_PNG, "Sweep. Variance of difference of ON pixels vs. angle", "angle (deg)", "score"); gplotAddPlot(gplot, natheta, nascore, GPLOT_LINES, "plot1"); gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot2"); gplotMakeOutput(gplot); gplotDestroy(&gplot); } #endif /* DEBUG_PLOT_SCORES */ cleanup: pixDestroy(&pix); pixDestroy(&pixt); numaDestroy(&nascore); numaDestroy(&natheta); return ret; }
void DoWatershed(L_REGPARAMS *rp, PIX *pixs) { l_uint8 *data; size_t size; l_int32 w, h, empty; l_uint32 redval, greenval; L_WSHED *wshed; PIX *pixc, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8, *pix9; PIXA *pixa; PTA *pta; /* Find local extrema */ pixa = pixaCreate(0); pixGetDimensions(pixs, &w, &h, NULL); regTestWritePixAndCheck(rp, pixs, IFF_PNG); /* 0 */ pixSaveTiled(pixs, pixa, 1.0, 1, 10, 32); startTimer(); pixLocalExtrema(pixs, 0, 0, &pix1, &pix2); fprintf(stderr, "Time for extrema: %7.3f\n", stopTimer()); pixSetOrClearBorder(pix1, 2, 2, 2, 2, PIX_CLR); composeRGBPixel(255, 0, 0, &redval); composeRGBPixel(0, 255, 0, &greenval); pixc = pixConvertTo32(pixs); pixPaintThroughMask(pixc, pix2, 0, 0, greenval); pixPaintThroughMask(pixc, pix1, 0, 0, redval); regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 1 */ pixSaveTiled(pixc, pixa, 1.0, 0, 10, 32); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 2 */ pixSaveTiled(pix1, pixa, 1.0, 0, 10, 32); /* Generate seeds for watershed */ pixSelectMinInConnComp(pixs, pix1, &pta, NULL); pix3 = pixGenerateFromPta(pta, w, h); regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3 */ pixSaveTiled(pix3, pixa, 1.0, 1, 10, 32); pix4 = pixConvertTo32(pixs); pixPaintThroughMask(pix4, pix3, 0, 0, greenval); regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 4 */ pixSaveTiled(pix4, pixa, 1.0, 0, 10, 32); pix5 = pixRemoveSeededComponents(NULL, pix3, pix1, 8, 2); regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 5 */ pixSaveTiled(pix5, pixa, 1.0, 0, 10, 32); pixZero(pix5, &empty); regTestCompareValues(rp, 1, empty, 0.0); /* 6 */ /* Make and display watershed */ wshed = wshedCreate(pixs, pix3, 10, 0); startTimer(); wshedApply(wshed); fprintf(stderr, "Time for wshed: %7.3f\n", stopTimer()); pix6 = pixaDisplayRandomCmap(wshed->pixad, w, h); regTestWritePixAndCheck(rp, pix6, IFF_PNG); /* 7 */ pixSaveTiled(pix6, pixa, 1.0, 1, 10, 32); numaWriteMem(&data, &size, wshed->nalevels); regTestWriteDataAndCheck(rp, data, size, "na"); /* 8 */ pix7 = wshedRenderFill(wshed); regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 9 */ pixSaveTiled(pix7, pixa, 1.0, 0, 10, 32); pix8 = wshedRenderColors(wshed); regTestWritePixAndCheck(rp, pix8, IFF_PNG); /* 10 */ pixSaveTiled(pix8, pixa, 1.0, 0, 10, 32); wshedDestroy(&wshed); pix9 = pixaDisplay(pixa, 0, 0); regTestWritePixAndCheck(rp, pix9, IFF_PNG); /* 11 */ pixDisplayWithTitle(pix9, 100, 100, NULL, rp->display); lept_free(data); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); pixDestroy(&pix5); pixDestroy(&pix6); pixDestroy(&pix7); pixDestroy(&pix8); pixDestroy(&pix9); pixDestroy(&pixc); pixaDestroy(&pixa); ptaDestroy(&pta); }