static void GetImageMask(PIX *pixs, l_int32 res, BOXA **pboxa, const char *debugfile) { PIX *pix1, *pix2, *pix3, *pix4; PIXA *pixa; pixSetResolution(pixs, 200, 200); pix1 = pixConvertTo1(pixs, 100); pix2 = pixGenerateHalftoneMask(pix1, NULL, NULL, NULL); pix3 = pixMorphSequence(pix2, "c20.1 + c1.20", 0); *pboxa = pixConnComp(pix3, NULL, 8); if (debugfile) { pixa = pixaCreate(0); pixaAddPix(pixa, pixs, L_COPY); pixaAddPix(pixa, pix1, L_INSERT); pixaAddPix(pixa, pix2, L_INSERT); pixaAddPix(pixa, pix3, L_INSERT); pix4 = pixaDisplayTiledInRows(pixa, 32, 1800, 0.25, 0, 25, 2); pixWrite(debugfile, pix4, IFF_JFIF_JPEG); pixDisplay(pix4, 100, 100); pixDestroy(&pix4); pixaDestroy(&pixa); } else { pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); } return; }
PIX * MakeReplacementMask(PIX *pixs) { PIX *pix1, *pix2, *pix3, *pix4; pix1 = pixMaskOverColorPixels(pixs, 95, 3); pix2 = pixMorphSequence(pix1, "o15.15", 0); pixSeedfillBinary(pix2, pix2, pix1, 8); pix3 = pixMorphSequence(pix2, "c15.15 + d61.31", 0); pix4 = pixRemoveBorderConnComps(pix3, 8); pixXor(pix4, pix4, pix3); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); return pix4; }
/*! * \brief pixModifyStrokeWidth() * * \param[in] pixa of 1 bpp pix * \param[in] width measured average stroke width * \param[in] targetw desired stroke width * \return pix with modified stroke width, or NULL on error */ PIX * pixModifyStrokeWidth(PIX *pixs, l_float32 width, l_float32 targetw) { char buf[16]; l_int32 diff, size; PROCNAME("pixModifyStrokeWidth"); if (!pixs || (pixGetDepth(pixs) != 1)) return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (targetw < 1) return (PIX *)ERROR_PTR("target width < 1", procName, NULL); diff = lept_roundftoi(targetw - width); if (diff == 0) return pixCopy(NULL, pixs); size = L_ABS(diff) + 1; if (diff < 0) /* erode */ snprintf(buf, sizeof(buf), "e%d.%d", size, size); else /* diff > 0; dilate */ snprintf(buf, sizeof(buf), "d%d.%d", size, size); return pixMorphSequence(pixs, buf, 0); }
int main(int argc, char **argv) { char *dirin, *dirout, *infile, *outfile, *tail; l_int32 i, nfiles, border, x, y, w, h, xb, yb, wb, hb; BOX *box1, *box2; BOXA *boxa1, *boxa2; PIX *pixs, *pixt1, *pixd; SARRAY *safiles; static char mainName[] = "croptext"; if (argc != 4) return ERROR_INT("Syntax: croptext dirin border dirout", mainName, 1); dirin = argv[1]; border = atoi(argv[2]); dirout = argv[3]; setLeptDebugOK(1); safiles = getSortedPathnamesInDirectory(dirin, NULL, 0, 0); nfiles = sarrayGetCount(safiles); for (i = 0; i < nfiles; i++) { infile = sarrayGetString(safiles, i, L_NOCOPY); splitPathAtDirectory(infile, NULL, &tail); outfile = genPathname(dirout, tail); pixs = pixRead(infile); pixt1 = pixMorphSequence(pixs, "r11 + c10.40 + o5.5 + x4", 0); boxa1 = pixConnComp(pixt1, NULL, 8); if (boxaGetCount(boxa1) == 0) { fprintf(stderr, "Warning: no components on page %s\n", tail); continue; } boxa2 = boxaSort(boxa1, L_SORT_BY_AREA, L_SORT_DECREASING, NULL); box1 = boxaGetBox(boxa2, 0, L_CLONE); boxGetGeometry(box1, &x, &y, &w, &h); xb = L_MAX(0, x - border); yb = L_MAX(0, y - border); wb = w + 2 * border; hb = h + 2 * border; box2 = boxCreate(xb, yb, wb, hb); pixd = pixClipRectangle(pixs, box2, NULL); pixWrite(outfile, pixd, IFF_TIFF_G4); pixDestroy(&pixs); pixDestroy(&pixt1); pixDestroy(&pixd); boxaDestroy(&boxa1); boxaDestroy(&boxa2); } return 0; }
/* * pixDebugFlipDetect() * * Input: filename (for output debug file) * pixs (input to pix*Detect) * pixhm (hit-miss result from ascenders or descenders) * enable (1 to enable this function; 0 to disable) * Return: void */ static void pixDebugFlipDetect(const char *filename, PIX *pixs, PIX *pixhm, l_int32 enable) { PIX *pixt, *pixthm; if (!enable) return; /* Display with red dot at counted locations */ pixt = pixConvert1To4Cmap(pixs); pixthm = pixMorphSequence(pixhm, "d5.5", 0); pixSetMaskedCmap(pixt, pixthm, 0, 0, 255, 0, 0); pixWrite(filename, pixt, IFF_PNG); pixDestroy(&pixthm); pixDestroy(&pixt); return; }
/*! * pixGenTextblockMask() * * Input: pixs (1 bpp, textline mask, assumed to be 150 to 200 ppi) * pixvws (vertical white space mask) * debug (flag: 1 for debug output) * Return: pixd (textblock mask), or null on error * * Notes: * (1) Both the input masks (textline and vertical white space) and * the returned textblock mask are at the same resolution. * (2) The result is somewhat noisy, in that small "blocks" of * text may be included. These can be removed by post-processing, * using, e.g., * pixSelectBySize(pix, 60, 60, 4, L_SELECT_IF_EITHER, * L_SELECT_IF_GTE, NULL); */ PIX * pixGenTextblockMask(PIX *pixs, PIX *pixvws, l_int32 debug) { PIX *pixt1, *pixt2, *pixt3, *pixd; PROCNAME("pixGenTextblockMask"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!pixvws) return (PIX *)ERROR_PTR("pixvws not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL); /* Join pixels vertically to make a textblock mask */ pixt1 = pixMorphSequence(pixs, "c1.10 + o4.1", 0); pixDisplayWriteFormat(pixt1, debug, IFF_PNG); /* Solidify the textblock mask and remove noise: * (1) For each cc, 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. */ pixt2 = pixMorphSequenceByComponent(pixt1, "c30.30 + d3.3", 8, 0, 0, NULL); pixCloseSafeBrick(pixt2, pixt2, 10, 1); pixDisplayWriteFormat(pixt2, debug, IFF_PNG); pixt3 = pixSubtract(NULL, pixt2, pixvws); pixDisplayWriteFormat(pixt3, debug, IFF_PNG); pixd = pixSelectBySize(pixt3, 25, 5, 8, L_SELECT_IF_BOTH, L_SELECT_IF_GTE, NULL); pixDisplayWriteFormat(pixd, debug, IFF_PNG); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); return pixd; }
/*! * \brief pixSetStrokeWidth() * * \param[in] pixs 1 bpp pix * \param[in] width set stroke width to this value, in [1 ... 100]. * \param[in] thinfirst 1 to thin all pix to a skeleton first; 0 to skip * \param[in] connectivity 4 or 8, to be used if %thinfirst == 1 * \return pixd with stroke width set to %width, or NULL on error * * <pre> * Notes: * (1) See notes in pixaSetStrokeWidth(). * (2) A white border of sufficient width to avoid boundary * artifacts in the thickening step is added before thinning. * (3) %connectivity == 8 usually gives a slightly smoother result. * </pre> */ PIX * pixSetStrokeWidth(PIX *pixs, l_int32 width, l_int32 thinfirst, l_int32 connectivity) { char buf[16]; l_int32 border; PIX *pix1, *pix2, *pixd; PROCNAME("pixSetStrokeWidth"); if (!pixs || (pixGetDepth(pixs) != 1)) return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (width < 1 || width > 100) return (PIX *)ERROR_PTR("width not in [1 ... 100]", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (!thinfirst && width == 1) /* nothing to do */ return pixCopy(NULL, pixs); /* Add a white border */ border = width / 2; pix1 = pixAddBorder(pixs, border, 0); /* Thin to a skeleton */ if (thinfirst) pix2 = pixThinConnected(pix1, L_THIN_FG, connectivity, 0); else pix2 = pixClone(pix1); pixDestroy(&pix1); /* Dilate */ snprintf(buf, sizeof(buf), "D%d.%d", width, width); pixd = pixMorphSequence(pix2, buf, 0); pixCopyText(pixd, pixs); pixDestroy(&pix2); return pixd; }
int main(int argc, char **argv) { char buffer1[256]; char buffer2[256]; l_int32 i, same, same2, factor1, factor2, diff, success; PIX *pixs, *pixsd, *pixt1, *pixt2, *pixt3; static char mainName[] = "binmorph2_reg"; if (argc != 1) return ERROR_INT(" Syntax: binmorph2_reg", mainName, 1); pixs = pixRead("rabi.png"); pixsd = pixMorphCompSequence(pixs, "d5.5", 0); success = TRUE; for (i = 1; i < MAX_SEL_SIZE; i++) { /* Check if the size is exactly decomposable */ selectComposableSizes(i, &factor1, &factor2); diff = factor1 * factor2 - i; fprintf(stderr, "%d: (%d, %d): %d\n", i, factor1, factor2, diff); /* Carry out operations on identical sized Sels: dilation */ sprintf(buffer1, "d%d.%d", i + diff, i + diff); sprintf(buffer2, "d%d.%d", i, i); pixt1 = pixMorphSequence(pixsd, buffer1, 0); pixt2 = pixMorphCompSequence(pixsd, buffer2, 0); pixEqual(pixt1, pixt2, &same); if (i < 64) { pixt3 = pixMorphCompSequenceDwa(pixsd, buffer2, 0); pixEqual(pixt1, pixt3, &same2); } else { pixt3 = NULL; same2 = TRUE; } if (same && same2) writeResult(buffer1, 1); else { writeResult(buffer1, 0); success = FALSE; } pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); /* ... erosion */ sprintf(buffer1, "e%d.%d", i + diff, i + diff); sprintf(buffer2, "e%d.%d", i, i); pixt1 = pixMorphSequence(pixsd, buffer1, 0); pixt2 = pixMorphCompSequence(pixsd, buffer2, 0); pixEqual(pixt1, pixt2, &same); if (i < 64) { pixt3 = pixMorphCompSequenceDwa(pixsd, buffer2, 0); pixEqual(pixt1, pixt3, &same2); } else { pixt3 = NULL; same2 = TRUE; } if (same && same2) writeResult(buffer1, 1); else { writeResult(buffer1, 0); success = FALSE; } pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); /* ... opening */ sprintf(buffer1, "o%d.%d", i + diff, i + diff); sprintf(buffer2, "o%d.%d", i, i); pixt1 = pixMorphSequence(pixsd, buffer1, 0); pixt2 = pixMorphCompSequence(pixsd, buffer2, 0); pixEqual(pixt1, pixt2, &same); if (i < 64) { pixt3 = pixMorphCompSequenceDwa(pixsd, buffer2, 0); pixEqual(pixt1, pixt3, &same2); } else { pixt3 = NULL; same2 = TRUE; } if (same && same2) writeResult(buffer1, 1); else { writeResult(buffer1, 0); success = FALSE; } pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); /* ... closing */ sprintf(buffer1, "c%d.%d", i + diff, i + diff); sprintf(buffer2, "c%d.%d", i, i); pixt1 = pixMorphSequence(pixsd, buffer1, 0); pixt2 = pixMorphCompSequence(pixsd, buffer2, 0); pixEqual(pixt1, pixt2, &same); if (i < 64) { pixt3 = pixMorphCompSequenceDwa(pixsd, buffer2, 0); pixEqual(pixt1, pixt3, &same2); } else { pixt3 = NULL; same2 = TRUE; } if (same && same2) writeResult(buffer1, 1); else { writeResult(buffer1, 0); success = FALSE; } pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); } pixDestroy(&pixs); pixDestroy(&pixsd); if (success) fprintf(stderr, "\n---------- Success: no errors ----------\n"); else fprintf(stderr, "\n---------- Failure: error(s) found -----------\n"); return 0; }
/*! * pixUpDownDetectGeneral() * * Input: pixs (1 bpp, deskewed, English text, 150 - 300 ppi) * &conf (<return> confidence that text is rightside-up) * mincount (min number of up + down; use 0 for default) * npixels (number of pixels removed from each side of word box) * debug (1 for debug output; 0 otherwise) * Return: 0 if OK, 1 on error * * Notes: * (1) See pixOrientDetect() for other details. * (2) @conf is the normalized difference between the number of * detected up and down ascenders, assuming that the text * is either rightside-up or upside-down and not rotated * at a 90 degree angle. * (3) The typical mode of operation is @npixels == 0. * If @npixels > 0, this removes HMT matches at the * beginning and ending of "words." This is useful for * pages that may have mostly digits, because if npixels == 0, * leading "1" and "3" digits can register as having * ascenders or descenders, and "7" digits can match descenders. * Consequently, a page image of only digits may register * as being upside-down. * (4) We want to count the number of instances found using the HMT. * An expensive way to do this would be to count the * number of connected components. A cheap way is to do a rank * reduction cascade that reduces each component to a single * pixel, and results (after two or three 2x reductions) * in one pixel for each of the original components. * After the reduction, you have a much smaller pix over * which to count pixels. We do only 2 reductions, because * this function is designed to work for input pix between * 150 and 300 ppi, and an 8x reduction on a 150 ppi image * is going too far -- components will get merged. */ l_int32 pixUpDownDetectGeneral(PIX *pixs, l_float32 *pconf, l_int32 mincount, l_int32 npixels, l_int32 debug) { l_int32 countup, countdown, nmax; l_float32 nup, ndown; PIX *pixt0, *pixt1, *pixt2, *pixt3, *pixm; SEL *sel1, *sel2, *sel3, *sel4; PROCNAME("pixUpDownDetectGeneral"); if (!pconf) return ERROR_INT("&conf not defined", procName, 1); *pconf = 0.0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (mincount == 0) mincount = DEFAULT_MIN_UP_DOWN_COUNT; if (npixels < 0) npixels = 0; sel1 = selCreateFromString(textsel1, 5, 6, NULL); sel2 = selCreateFromString(textsel2, 5, 6, NULL); sel3 = selCreateFromString(textsel3, 5, 6, NULL); sel4 = selCreateFromString(textsel4, 5, 6, NULL); /* One of many reasonable pre-filtering sequences: (1, 8) and (30, 1). * This closes holes in x-height characters and joins them at * the x-height. There is more noise in the descender detection * from this, but it works fairly well. */ pixt0 = pixMorphCompSequence(pixs, "c1.8 + c30.1", 0); /* Optionally, make a mask of the word bounding boxes, shortening * each of them by a fixed amount at each end. */ pixm = NULL; if (npixels > 0) { l_int32 i, nbox, x, y, w, h; BOX *box; BOXA *boxa; pixt1 = pixMorphSequence(pixt0, "o10.1", 0); boxa = pixConnComp(pixt1, NULL, 8); pixm = pixCreateTemplate(pixt1); pixDestroy(&pixt1); nbox = boxaGetCount(boxa); for (i = 0; i < nbox; i++) { box = boxaGetBox(boxa, i, L_CLONE); boxGetGeometry(box, &x, &y, &w, &h); if (w > 2 * npixels) pixRasterop(pixm, x + npixels, y - 6, w - 2 * npixels, h + 13, PIX_SET, NULL, 0, 0); boxDestroy(&box); } boxaDestroy(&boxa); } /* Find the ascenders and optionally filter with pixm. * For an explanation of the procedure used for counting the result * of the HMT, see comments at the beginning of this function. */ pixt1 = pixHMT(NULL, pixt0, sel1); pixt2 = pixHMT(NULL, pixt0, sel2); pixOr(pixt1, pixt1, pixt2); if (pixm) pixAnd(pixt1, pixt1, pixm); pixt3 = pixReduceRankBinaryCascade(pixt1, 1, 1, 0, 0); pixCountPixels(pixt3, &countup, NULL); pixDebugFlipDetect("junkpixup", pixs, pixt1, debug); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); /* Find the ascenders and optionally filter with pixm. */ pixt1 = pixHMT(NULL, pixt0, sel3); pixt2 = pixHMT(NULL, pixt0, sel4); pixOr(pixt1, pixt1, pixt2); if (pixm) pixAnd(pixt1, pixt1, pixm); pixt3 = pixReduceRankBinaryCascade(pixt1, 1, 1, 0, 0); pixCountPixels(pixt3, &countdown, NULL); pixDebugFlipDetect("junkpixdown", pixs, pixt1, debug); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); /* Evaluate statistically, generating a confidence that is * related to the probability with a gaussian distribution. */ nup = (l_float32)(countup); ndown = (l_float32)(countdown); nmax = L_MAX(countup, countdown); if (nmax > mincount) *pconf = 2. * ((nup - ndown) / sqrt(nup + ndown)); if (debug) { if (pixm) pixWrite("junkpixm1", pixm, IFF_PNG); fprintf(stderr, "nup = %7.3f, ndown = %7.3f, conf = %7.3f\n", nup, ndown, *pconf); if (*pconf > DEFAULT_MIN_UP_DOWN_CONF) fprintf(stderr, "Text is rightside-up\n"); if (*pconf < -DEFAULT_MIN_UP_DOWN_CONF) fprintf(stderr, "Text is upside-down\n"); } pixDestroy(&pixt0); pixDestroy(&pixm); selDestroy(&sel1); selDestroy(&sel2); selDestroy(&sel3); selDestroy(&sel4); return 0; }
int main(int argc, char **argv) { l_int32 i, w, h, nbox, npta, fgcount, bgcount, count; BOXA *boxa; PIX *pixs, *pixfg, *pixbg, *pixc, *pixb, *pixd; PIX *pix1, *pix2, *pix3, *pix4; PIXA *pixa; PTA *pta; PTAA *ptaafg, *ptaabg; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pixs = pixRead("feyn-fract.tif"); boxa = pixConnComp(pixs, NULL, 8); nbox = boxaGetCount(boxa); regTestCompareValues(rp, nbox, 464, 0); /* 0 */ /* Get fg and bg boundary pixels */ pixfg = pixMorphSequence(pixs, "e3.3", 0); pixXor(pixfg, pixfg, pixs); pixCountPixels(pixfg, &fgcount, NULL); regTestCompareValues(rp, fgcount, 58764, 0); /* 1 */ pixbg = pixMorphSequence(pixs, "d3.3", 0); pixXor(pixbg, pixbg, pixs); pixCountPixels(pixbg, &bgcount, NULL); regTestCompareValues(rp, bgcount, 60335, 0); /* 2 */ /* Get ptaa of fg pixels */ ptaafg = ptaaGetBoundaryPixels(pixs, L_BOUNDARY_FG, 8, NULL, NULL); npta = ptaaGetCount(ptaafg); regTestCompareValues(rp, npta, nbox, 0); /* 3 */ count = 0; for (i = 0; i < npta; i++) { pta = ptaaGetPta(ptaafg, i, L_CLONE); count += ptaGetCount(pta); ptaDestroy(&pta); } regTestCompareValues(rp, fgcount, count, 0); /* 4 */ /* Get ptaa of bg pixels. Note that the number of bg pts * is, in general, larger than the number of bg boundary pixels, * because bg boundary pixels are shared by two c.c. that * are 1 pixel apart. */ ptaabg = ptaaGetBoundaryPixels(pixs, L_BOUNDARY_BG, 8, NULL, NULL); npta = ptaaGetCount(ptaabg); regTestCompareValues(rp, npta, nbox, 0); /* 5 */ count = 0; for (i = 0; i < npta; i++) { pta = ptaaGetPta(ptaabg, i, L_CLONE); count += ptaGetCount(pta); ptaDestroy(&pta); } regTestCompareValues(rp, count, 60602, 0); /* 6 */ /* Render the fg boundary pixels on top of pixs. */ pixa = pixaCreate(4); pixc = pixRenderRandomCmapPtaa(pixs, ptaafg, 0, 0, 0); regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 7 */ pixSaveTiledOutline(pixc, pixa, 1.0, 1, 30, 2, 32); pixDestroy(&pixc); /* Render the bg boundary pixels on top of pixs. */ pixc = pixRenderRandomCmapPtaa(pixs, ptaabg, 0, 0, 0); regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 8 */ pixSaveTiledOutline(pixc, pixa, 1.0, 0, 30, 2, 32); pixDestroy(&pixc); pixClearAll(pixs); /* Render the fg boundary pixels alone. */ pixc = pixRenderRandomCmapPtaa(pixs, ptaafg, 0, 0, 0); regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 9 */ pixSaveTiledOutline(pixc, pixa, 1.0, 1, 30, 2, 32); /* Verify that the fg pixels are the same set as we * originally started with. */ pixb = pixConvertTo1(pixc, 255); regTestComparePix(rp, pixb, pixfg); /* 10 */ pixDestroy(&pixc); pixDestroy(&pixb); /* Render the bg boundary pixels alone. */ pixc = pixRenderRandomCmapPtaa(pixs, ptaabg, 0, 0, 0); regTestWritePixAndCheck(rp, pixc, IFF_PNG); /* 11 */ pixSaveTiledOutline(pixc, pixa, 1.0, 0, 30, 2, 32); /* Verify that the bg pixels are the same set as we * originally started with. */ pixb = pixConvertTo1(pixc, 255); regTestComparePix(rp, pixb, pixbg); /* 12 */ pixDestroy(&pixc); pixDestroy(&pixb); pixd = pixaDisplay(pixa, 0, 0); pixDisplayWithTitle(pixd, 0, 0, NULL, rp->display); ptaaDestroy(&ptaafg); ptaaDestroy(&ptaabg); pixDestroy(&pixs); pixDestroy(&pixfg); pixDestroy(&pixbg); pixDestroy(&pixd); pixaDestroy(&pixa); boxaDestroy(&boxa); /* Test rotation */ pix1 = pixRead("feyn-word.tif"); pix2 = pixAddBorderGeneral(pix1, 200, 200, 200, 200, 0); pixa = pixaCreate(0); pix3 = PtaDisplayRotate(pix2, 0, 0); pixaAddPix(pixa, pix3, L_INSERT); pix3 = PtaDisplayRotate(pix2, 500, 100); pixaAddPix(pixa, pix3, L_INSERT); pix3 = PtaDisplayRotate(pix2, 100, 410); pixaAddPix(pixa, pix3, L_INSERT); pix3 = PtaDisplayRotate(pix2, 500, 410); pixaAddPix(pixa, pix3, L_INSERT); pix4 = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 30, 2); regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 13 */ pixDisplayWithTitle(pix4, 800, 0, NULL, rp->display); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix4); pixaDestroy(&pixa); return regTestCleanup(rp); }
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); }
/*! * pixGetTextlineCenters() * * Input: pixs (1 bpp) * debugflag (1 for debug output) * Return: ptaa (of center values of textlines) * * Notes: * (1) This in general does not have a point for each value * of x, because there will be gaps between words. * It doesn't matter because we will fit a quadratic to the * points that we do have. */ PTAA * pixGetTextlineCenters(PIX *pixs, l_int32 debugflag) { l_int32 i, w, h, bx, by, nsegs; BOXA *boxa; PIX *pix, *pixt1, *pixt2, *pixt3; PIXA *pixa1, *pixa2; PTA *pta; PTAA *ptaa; PROCNAME("pixGetTextlineCenters"); if (!pixs || pixGetDepth(pixs) != 1) return (PTAA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); pixGetDimensions(pixs, &w, &h, NULL); /* Filter to solidify the text lines within the x-height region, * and to remove most of the ascenders and descenders. */ pixt1 = pixMorphSequence(pixs, "c15.1 + o15.1 + c30.1", 0); pixDisplayWithTitle(pixt1, 0, 800, "pix1", debugflag); /* Get the 8-connected components ... */ boxa = pixConnComp(pixt1, &pixa1, 8); pixDestroy(&pixt1); boxaDestroy(&boxa); if (pixaGetCount(pixa1) == 0) { pixaDestroy(&pixa1); return NULL; } /* ... and remove the short and thin c.c */ pixa2 = pixaSelectBySize(pixa1, 100, 4, L_SELECT_IF_BOTH, L_SELECT_IF_GT, 0); if ((nsegs = pixaGetCount(pixa2)) == 0) { pixaDestroy(&pixa2); return NULL; } if (debugflag) { pixt2 = pixaDisplay(pixa2, w, h); pixDisplayWithTitle(pixt2, 800, 800, "pix2", 1); pixDestroy(&pixt2); } /* For each c.c., get the weighted center of each vertical column. * The result is a set of points going approximately through * the center of the x-height part of the text line. */ ptaa = ptaaCreate(nsegs); for (i = 0; i < nsegs; i++) { pixaGetBoxGeometry(pixa2, i, &bx, &by, NULL, NULL); pix = pixaGetPix(pixa2, i, L_CLONE); pta = pixGetMeanVerticals(pix, bx, by); ptaaAddPta(ptaa, pta, L_INSERT); pixDestroy(&pix); } if (debugflag) { pixt3 = pixCreateTemplate(pixt2); pix = pixDisplayPtaa(pixt3, ptaa); pixDisplayWithTitle(pix, 0, 1400, "pix3", 1); pixDestroy(&pix); pixDestroy(&pixt3); } pixaDestroy(&pixa1); pixaDestroy(&pixa2); return ptaa; }
main(int argc, char **argv) { char *filein, *fileout; l_int32 thresh; PIX *pixs, *pixg, *pixb; PIX *pixmask4, *pixseed4, *pixsf4, *pixd4, *pixd; static char mainName[] = "pagesegtest2"; if (argc != 4) exit(ERROR_INT(" Syntax: pagesegtest2 filein thresh fileout", mainName, 1)); filein = argv[1]; thresh = atoi(argv[2]); fileout = argv[3]; /* Get a 1 bpp version of the page */ if ((pixs = pixRead(filein)) == NULL) exit(ERROR_INT("pixs not made", mainName, 1)); if (pixGetDepth(pixs) == 32) pixg = pixConvertRGBToGrayFast(pixs); else pixg = pixClone(pixs); if (pixGetDepth(pixg) == 8) pixb = pixThresholdToBinary(pixg, thresh); else pixb = pixClone(pixg); /* Make seed and mask, and fill seed into mask */ pixseed4 = pixMorphSequence(pixb, seed_sequence, 0); pixmask4 = pixMorphSequence(pixb, mask_sequence, 0); pixsf4 = pixSeedfillBinary(NULL, pixseed4, pixmask4, 8); pixd4 = pixMorphSequence(pixsf4, dilation_sequence, 0); /* Mask at full resolution */ pixd = pixExpandBinaryPower2(pixd4, 4); pixWrite(fileout, pixd, IFF_TIFF_G4); /* Extract non-image parts (e.g., text) at full resolution */ pixSubtract(pixb, pixb, pixd); pixDisplayWithTitle(pixseed4, 400, 100, "halftone seed", DFLAG); pixDisplayWithTitle(pixmask4, 100, 100, "halftone seed mask", DFLAG); pixDisplayWithTitle(pixd4, 700, 100, "halftone mask", DFLAG); pixDisplayWithTitle(pixb, 1000, 100, "non-halftone", DFLAG); #if 1 pixWrite("junkseed", pixseed4, IFF_TIFF_G4); pixWrite("junkmask", pixmask4, IFF_TIFF_G4); pixWrite("junkfill", pixd4, IFF_TIFF_G4); pixWrite("junktext", pixb, IFF_TIFF_G4); #endif pixDestroy(&pixs); pixDestroy(&pixg); pixDestroy(&pixb); pixDestroy(&pixseed4); pixDestroy(&pixmask4); pixDestroy(&pixsf4); pixDestroy(&pixd4); pixDestroy(&pixd); exit(0); }
int main(int argc, char **argv) { char buffer[512]; char *tempfile1, *tempfile2; l_uint8 *data; l_int32 i, j, w, h, seq, ret, same; size_t nbytes; const char *title; BOX *box; BOXA *boxa1, *boxa2; L_BYTEA *ba; L_PDF_DATA *lpd; PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6; PIX *pixs, *pixt, *pixg, *pixgc, *pixc; static char mainName[] = "pdfiotest"; if (argc != 1) return ERROR_INT("syntax: pdfiotest", mainName, 1); l_pdfSetDateAndVersion(0); lept_mkdir("lept/pdf"); #if 1 /* --------------- Single image tests ------------------- */ fprintf(stderr, "\n*** Writing single images as pdf files\n"); convertToPdf("weasel2.4c.png", L_FLATE_ENCODE, 0, "/tmp/lept/pdf/file01.pdf", 0, 0, 72, "weasel2.4c.png", NULL, 0); convertToPdf("test24.jpg", L_JPEG_ENCODE, 0, "/tmp/lept/pdf/file02.pdf", 0, 0, 72, "test24.jpg", NULL, 0); convertToPdf("feyn.tif", L_G4_ENCODE, 0, "/tmp/lept/pdf/file03.pdf", 0, 0, 300, "feyn.tif", NULL, 0); pixs = pixRead("feyn.tif"); pixConvertToPdf(pixs, L_G4_ENCODE, 0, "/tmp/lept/pdf/file04.pdf", 0, 0, 300, "feyn.tif", NULL, 0); pixDestroy(&pixs); pixs = pixRead("test24.jpg"); pixConvertToPdf(pixs, L_JPEG_ENCODE, 5, "/tmp/lept/pdf/file05.pdf", 0, 0, 72, "test24.jpg", NULL, 0); pixDestroy(&pixs); pixs = pixRead("feyn.tif"); pixt = pixScaleToGray2(pixs); pixWrite("/tmp/lept/pdf/feyn8.png", pixt, IFF_PNG); convertToPdf("/tmp/lept/pdf/feyn8.png", L_JPEG_ENCODE, 0, "/tmp/lept/pdf/file06.pdf", 0, 0, 150, "feyn8.png", NULL, 0); pixDestroy(&pixs); pixDestroy(&pixt); convertToPdf("weasel4.16g.png", L_FLATE_ENCODE, 0, "/tmp/lept/pdf/file07.pdf", 0, 0, 30, "weasel4.16g.png", NULL, 0); pixs = pixRead("test24.jpg"); pixg = pixConvertTo8(pixs, 0); box = boxCreate(100, 100, 100, 100); pixc = pixClipRectangle(pixs, box, NULL); pixgc = pixClipRectangle(pixg, box, NULL); pixWrite("/tmp/lept/pdf/pix32.jpg", pixc, IFF_JFIF_JPEG); pixWrite("/tmp/lept/pdf/pix8.jpg", pixgc, IFF_JFIF_JPEG); convertToPdf("/tmp/lept/pdf/pix32.jpg", L_FLATE_ENCODE, 0, "/tmp/lept/pdf/file08.pdf", 0, 0, 72, "pix32.jpg", NULL, 0); convertToPdf("/tmp/lept/pdf/pix8.jpg", L_FLATE_ENCODE, 0, "/tmp/lept/pdf/file09.pdf", 0, 0, 72, "pix8.jpg", NULL, 0); pixDestroy(&pixs); pixDestroy(&pixg); pixDestroy(&pixc); pixDestroy(&pixgc); boxDestroy(&box); #endif #if 1 /* --------------- Multiple image tests ------------------- */ fprintf(stderr, "\n*** Writing multiple images as single page pdf files\n"); pix1 = pixRead("feyn-fract.tif"); pix2 = pixRead("weasel8.240c.png"); /* l_pdfSetDateAndVersion(0); */ /* First, write the 1 bpp image through the mask onto the weasels */ for (i = 0; i < 5; i++) { for (j = 0; j < 10; j++) { seq = (i == 0 && j == 0) ? L_FIRST_IMAGE : L_NEXT_IMAGE; title = (i == 0 && j == 0) ? "feyn-fract.tif" : NULL; pixConvertToPdf(pix2, L_FLATE_ENCODE, 0, NULL, 100 * j, 100 * i, 70, title, &lpd, seq); } } pixConvertToPdf(pix1, L_G4_ENCODE, 0, "/tmp/lept/pdf/file10.pdf", 0, 0, 80, NULL, &lpd, L_LAST_IMAGE); /* Now, write the 1 bpp image over the weasels */ l_pdfSetG4ImageMask(0); for (i = 0; i < 5; i++) { for (j = 0; j < 10; j++) { seq = (i == 0 && j == 0) ? L_FIRST_IMAGE : L_NEXT_IMAGE; title = (i == 0 && j == 0) ? "feyn-fract.tif" : NULL; pixConvertToPdf(pix2, L_FLATE_ENCODE, 0, NULL, 100 * j, 100 * i, 70, title, &lpd, seq); } } pixConvertToPdf(pix1, L_G4_ENCODE, 0, "/tmp/lept/pdf/file11.pdf", 0, 0, 80, NULL, &lpd, L_LAST_IMAGE); l_pdfSetG4ImageMask(1); pixDestroy(&pix1); pixDestroy(&pix2); #endif #if 1 /* -------- pdf convert segmented with no image regions -------- */ fprintf(stderr, "\n*** Writing segmented images without image regions\n"); pix1 = pixRead("rabi.png"); pix2 = pixScaleToGray2(pix1); pixWrite("/tmp/lept/pdf/rabi8.jpg", pix2, IFF_JFIF_JPEG); pix3 = pixThresholdTo4bpp(pix2, 16, 1); pixWrite("/tmp/lept/pdf/rabi4.png", pix3, IFF_PNG); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); /* 1 bpp input */ convertToPdfSegmented("rabi.png", 300, L_G4_ENCODE, 128, NULL, 0, 0, NULL, "/tmp/lept/pdf/file12.pdf"); convertToPdfSegmented("rabi.png", 300, L_JPEG_ENCODE, 128, NULL, 0, 0, NULL, "/tmp/lept/pdf/file13.pdf"); convertToPdfSegmented("rabi.png", 300, L_FLATE_ENCODE, 128, NULL, 0, 0, NULL, "/tmp/lept/pdf/file14.pdf"); /* 8 bpp input, no cmap */ convertToPdfSegmented("/tmp/lept/pdf/rabi8.jpg", 150, L_G4_ENCODE, 128, NULL, 0, 0, NULL, "/tmp/lept/pdf/file15.pdf"); convertToPdfSegmented("/tmp/lept/pdf/rabi8.jpg", 150, L_JPEG_ENCODE, 128, NULL, 0, 0, NULL, "/tmp/lept/pdf/file16.pdf"); convertToPdfSegmented("/tmp/lept/pdf/rabi8.jpg", 150, L_FLATE_ENCODE, 128, NULL, 0, 0, NULL, "/tmp/lept/pdf/file17.pdf"); /* 4 bpp input, cmap */ convertToPdfSegmented("/tmp/lept/pdf/rabi4.png", 150, L_G4_ENCODE, 128, NULL, 0, 0, NULL, "/tmp/lept/pdf/file18.pdf"); convertToPdfSegmented("/tmp/lept/pdf/rabi4.png", 150, L_JPEG_ENCODE, 128, NULL, 0, 0, NULL, "/tmp/lept/pdf/file19.pdf"); convertToPdfSegmented("/tmp/lept/pdf/rabi4.png", 150, L_FLATE_ENCODE, 128, NULL, 0, 0, NULL, "/tmp/lept/pdf/file20.pdf"); #endif #if 1 /* ---------- pdf convert segmented with image regions ---------- */ fprintf(stderr, "\n*** Writing segmented images with image regions\n"); /* Get the image region(s) for rabi.png. There are two * small bogus regions at the top, but we'll keep them for * the demonstration. */ pix1 = pixRead("rabi.png"); pixSetResolution(pix1, 300, 300); pixGetDimensions(pix1, &w, &h, NULL); pix2 = pixGenerateHalftoneMask(pix1, NULL, NULL, NULL); pix3 = pixMorphSequence(pix2, "c20.1 + c1.20", 0); boxa1 = pixConnComp(pix3, NULL, 8); boxa2 = boxaTransform(boxa1, 0, 0, 0.5, 0.5); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); /* 1 bpp input */ convertToPdfSegmented("rabi.png", 300, L_G4_ENCODE, 128, boxa1, 0, 0.25, NULL, "/tmp/lept/pdf/file21.pdf"); convertToPdfSegmented("rabi.png", 300, L_JPEG_ENCODE, 128, boxa1, 0, 0.25, NULL, "/tmp/lept/pdf/file22.pdf"); convertToPdfSegmented("rabi.png", 300, L_FLATE_ENCODE, 128, boxa1, 0, 0.25, NULL, "/tmp/lept/pdf/file23.pdf"); /* 8 bpp input, no cmap */ convertToPdfSegmented("/tmp/lept/pdf/rabi8.jpg", 150, L_G4_ENCODE, 128, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf/file24.pdf"); convertToPdfSegmented("/tmp/lept/pdf/rabi8.jpg", 150, L_JPEG_ENCODE, 128, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf/file25.pdf"); convertToPdfSegmented("/tmp/lept/pdf/rabi8.jpg", 150, L_FLATE_ENCODE, 128, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf/file26.pdf"); /* 4 bpp input, cmap */ convertToPdfSegmented("/tmp/lept/pdf/rabi4.png", 150, L_G4_ENCODE, 128, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf/file27.pdf"); convertToPdfSegmented("/tmp/lept/pdf/rabi4.png", 150, L_JPEG_ENCODE, 128, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf/file28.pdf"); convertToPdfSegmented("/tmp/lept/pdf/rabi4.png", 150, L_FLATE_ENCODE, 128, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf/file29.pdf"); /* 4 bpp input, cmap, data output */ data = NULL; convertToPdfDataSegmented("/tmp/lept/pdf/rabi4.png", 150, L_G4_ENCODE, 128, boxa2, 0, 0.5, NULL, &data, &nbytes); l_binaryWrite("/tmp/lept/pdf/file30.pdf", "w", data, nbytes); lept_free(data); convertToPdfDataSegmented("/tmp/lept/pdf/rabi4.png", 150, L_JPEG_ENCODE, 128, boxa2, 0, 0.5, NULL, &data, &nbytes); l_binaryWrite("/tmp/lept/pdf/file31.pdf", "w", data, nbytes); lept_free(data); convertToPdfDataSegmented("/tmp/lept/pdf/rabi4.png", 150, L_FLATE_ENCODE, 128, boxa2, 0, 0.5, NULL, &data, &nbytes); l_binaryWrite("/tmp/lept/pdf/file32.pdf", "w", data, nbytes); lept_free(data); boxaDestroy(&boxa1); boxaDestroy(&boxa2); #endif #if 1 /* -------- pdf convert segmented from color image -------- */ fprintf(stderr, "\n*** Writing color segmented images\n"); pix1 = pixRead("candelabrum.011.jpg"); pix2 = pixScale(pix1, 3.0, 3.0); pixWrite("/tmp/lept/pdf/candelabrum3.jpg", pix2, IFF_JFIF_JPEG); GetImageMask(pix2, 200, &boxa1, "/tmp/lept/pdf/seg1.jpg"); convertToPdfSegmented("/tmp/lept/pdf/candelabrum3.jpg", 200, L_G4_ENCODE, 100, boxa1, 0, 0.25, NULL, "/tmp/lept/pdf/file33.pdf"); convertToPdfSegmented("/tmp/lept/pdf/candelabrum3.jpg", 200, L_JPEG_ENCODE, 100, boxa1, 0, 0.25, NULL, "/tmp/lept/pdf/file34.pdf"); convertToPdfSegmented("/tmp/lept/pdf/candelabrum3.jpg", 200, L_FLATE_ENCODE, 100, boxa1, 0, 0.25, NULL, "/tmp/lept/pdf/file35.pdf"); pixDestroy(&pix1); pixDestroy(&pix2); boxaDestroy(&boxa1); pix1 = pixRead("lion-page.00016.jpg"); pix2 = pixScale(pix1, 3.0, 3.0); pixWrite("/tmp/lept/pdf/lion16.jpg", pix2, IFF_JFIF_JPEG); pix3 = pixRead("lion-mask.00016.tif"); boxa1 = pixConnComp(pix3, NULL, 8); boxa2 = boxaTransform(boxa1, 0, 0, 3.0, 3.0); convertToPdfSegmented("/tmp/lept/pdf/lion16.jpg", 200, L_G4_ENCODE, 190, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf/file36.pdf"); convertToPdfSegmented("/tmp/lept/pdf/lion16.jpg", 200, L_JPEG_ENCODE, 190, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf/file37.pdf"); convertToPdfSegmented("/tmp/lept/pdf/lion16.jpg", 200, L_FLATE_ENCODE, 190, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf/file38.pdf"); /* Quantize the non-image part and flate encode. * This is useful because it results in a smaller file than * when you flate-encode the un-quantized non-image regions. */ pix4 = pixScale(pix3, 3.0, 3.0); /* higher res mask, for combining */ pix5 = QuantizeNonImageRegion(pix2, pix4, 12); pixWrite("/tmp/lept/pdf/lion16-quant.png", pix5, IFF_PNG); convertToPdfSegmented("/tmp/lept/pdf/lion16-quant.png", 200, L_FLATE_ENCODE, 190, boxa2, 0, 0.5, NULL, "/tmp/lept/pdf/file39.pdf"); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); pixDestroy(&pix5); boxaDestroy(&boxa1); boxaDestroy(&boxa2); #endif #if 1 /* ------------------ Test multipage pdf generation ----------------- */ fprintf(stderr, "\n*** Writing multipage pdfs from single page pdfs\n"); /* Generate a multi-page pdf from all these files */ startTimer(); concatenatePdf("/tmp/lept/pdf", "file", "/tmp/lept/pdf/cat_lept.pdf"); fprintf(stderr, "All files have been concatenated: /tmp/lept/pdf/cat_lept.pdf\n" "Concatenation time: %7.3f\n", stopTimer()); #endif #if 1 /* ----------- Test corruption recovery by concatenation ------------ */ /* Put two good pdf files in a directory */ lept_rmdir("lept/good"); lept_mkdir("lept/good"); lept_cp("testfile1.pdf", "lept/good", NULL, NULL); lept_cp("testfile2.pdf", "lept/good", NULL, NULL); concatenatePdf("/tmp/lept/good", "file", "/tmp/lept/pdf/good.pdf"); /* Make a bad version with the pdf id removed, so that it is not * recognized as a pdf */ lept_rmdir("lept/bad"); lept_mkdir("lept/bad"); ba = l_byteaInitFromFile("testfile2.pdf"); data = l_byteaGetData(ba, &nbytes); l_binaryWrite("/tmp/lept/bad/testfile0.notpdf.pdf", "w", data + 10, nbytes - 10); /* Make a version with a corrupted trailer */ if (data) data[2297] = '2'; /* munge trailer object 6: change 458 --> 428 */ l_binaryWrite("/tmp/lept/bad/testfile2.bad.pdf", "w", data, nbytes); l_byteaDestroy(&ba); /* Copy testfile1.pdf to the /tmp/lept/bad directory. Then * run concat on the bad files. The "not pdf" file should be * ignored, and the corrupted pdf file should be properly parsed, * so the resulting concatenated pdf files should be identical. */ fprintf(stderr, "\nWe attempt to build from the bad directory\n"); lept_cp("testfile1.pdf", "lept/bad", NULL, NULL); concatenatePdf("/tmp/lept/bad", "file", "/tmp/lept/pdf/bad.pdf"); filesAreIdentical("/tmp/lept/pdf/good.pdf", "/tmp/lept/pdf/bad.pdf", &same); if (same) fprintf(stderr, "Fixed: files are the same\n" "Attempt succeeded\n"); else fprintf(stderr, "Busted: files are different\n"); #endif #if 0 fprintf(stderr, "\n*** pdftk writes multipage pdfs from images\n"); tempfile1 = genPathname("/tmp/lept/pdf", "file*.pdf"); tempfile2 = genPathname("/tmp/lept/pdf", "cat_pdftk.pdf"); snprintf(buffer, sizeof(buffer), "pdftk %s output %s", tempfile1, tempfile2); ret = system(buffer); /* pdftk */ lept_free(tempfile1); lept_free(tempfile2); #endif #if 1 /* -- Test simple interface for generating multi-page pdf from images -- */ fprintf(stderr, "\n*** Writing multipage pdfs from images\n"); /* Put four image files in a directory. They will be encoded thus: * file1.png: flate (8 bpp, only 10 colors) * file2.jpg: dct (8 bpp, 256 colors because of the jpeg encoding) * file3.tif: g4 (1 bpp) * file4.jpg: dct (32 bpp) */ lept_mkdir("lept/image"); pix1 = pixRead("feyn.tif"); pix2 = pixRead("rabi.png"); pix3 = pixScaleToGray3(pix1); pix4 = pixScaleToGray3(pix2); pix5 = pixScale(pix1, 0.33, 0.33); pix6 = pixRead("test24.jpg"); pixWrite("/tmp/lept/image/file1.png", pix3, IFF_PNG); /* 10 colors */ pixWrite("/tmp/lept/image/file2.jpg", pix4, IFF_JFIF_JPEG); /* 256 colors */ pixWrite("/tmp/lept/image/file3.tif", pix5, IFF_TIFF_G4); pixWrite("/tmp/lept/image/file4.jpg", pix6, IFF_JFIF_JPEG); startTimer(); convertFilesToPdf("/tmp/lept/image", "file", 100, 0.8, 0, 75, "4 file test", "/tmp/lept/pdf/fourimages.pdf"); fprintf(stderr, "4-page pdf generated: /tmp/lept/pdf/fourimages.pdf\n" "Time: %7.3f\n", stopTimer()); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); pixDestroy(&pix5); pixDestroy(&pix6); #endif return 0; }
main(int argc, char **argv) { char *infile; PIX *pixs, *pixg, *pixc, *pixd; static char mainName[] = "morphseq_reg"; if (argc != 1) return ERROR_INT(" Syntax: morphseq_reg", mainName, 1); pixs = pixRead("feyn.tif"); /* 1 bpp */ pixd = pixMorphSequence(pixs, SEQUENCE1, -1); pixDestroy(&pixd); pixd = pixMorphSequence(pixs, SEQUENCE1, DISPLAY_SEPARATION); pixWrite("/tmp/morphseq1.png", pixd, IFF_PNG); pixDestroy(&pixd); pixd = pixMorphCompSequence(pixs, SEQUENCE2, -2); pixDestroy(&pixd); pixd = pixMorphCompSequence(pixs, SEQUENCE2, DISPLAY_SEPARATION); pixWrite("/tmp/morphseq2.png", pixd, IFF_PNG); pixDestroy(&pixd); pixd = pixMorphSequenceDwa(pixs, SEQUENCE2, -3); pixDestroy(&pixd); pixd = pixMorphSequenceDwa(pixs, SEQUENCE2, DISPLAY_SEPARATION); pixWrite("/tmp/morphseq3.png", pixd, IFF_PNG); pixDestroy(&pixd); pixd = pixMorphCompSequenceDwa(pixs, SEQUENCE2, -4); pixDestroy(&pixd); pixd = pixMorphCompSequenceDwa(pixs, SEQUENCE2, DISPLAY_SEPARATION); pixWrite("/tmp/morphseq4.png", pixd, IFF_PNG); pixDestroy(&pixd); /* 8 bpp */ pixg = pixScaleToGray(pixs, 0.25); pixd = pixGrayMorphSequence(pixg, SEQUENCE3, -5, 150); pixDestroy(&pixd); pixd = pixGrayMorphSequence(pixg, SEQUENCE3, DISPLAY_SEPARATION, 150); pixWrite("/tmp/morphseq5.png", pixd, IFF_PNG); pixDestroy(&pixd); pixd = pixGrayMorphSequence(pixg, SEQUENCE4, -6, 300); pixWrite("/tmp/morphseq6.png", pixd, IFF_PNG); pixDestroy(&pixd); /* 32 bpp */ pixc = pixRead("wyom.jpg"); pixd = pixColorMorphSequence(pixc, SEQUENCE5, -7, 150); pixDestroy(&pixd); pixd = pixColorMorphSequence(pixc, SEQUENCE5, DISPLAY_SEPARATION, 450); pixWrite("/tmp/morphseq7.png", pixd, IFF_PNG); pixDestroy(&pixc); pixDestroy(&pixd); /* Syntax error handling */ fprintf(stderr, " ------------ Error messages follow ------------------\n"); pixd = pixMorphSequence(pixs, BAD_SEQUENCE, 50); /* fails; returns null */ pixd = pixGrayMorphSequence(pixg, BAD_SEQUENCE, 50, 0); /* this fails */ pixDestroy(&pixg); pixDestroy(&pixs); return 0; }
/*! * \brief pixFindBaselines() * * \param[in] pixs 1 bpp, 300 ppi * \param[out] ppta [optional] pairs of pts corresponding to * approx. ends of each text line * \param[in] pixadb for debug output; use NULL to skip * \return na of baseline y values, or NULL on error * * <pre> * Notes: * (1) Input binary image must have text lines already aligned * horizontally. This can be done by either rotating the * image with pixDeskew(), or, if a projective transform * is required, by doing pixDeskewLocal() first. * (2) Input null for &pta if you don't want this returned. * The pta will come in pairs of points (left and right end * of each baseline). * (3) Caution: this will not work properly on text with multiple * columns, where the lines are not aligned between columns. * If there are multiple columns, they should be extracted * separately before finding the baselines. * (4) This function constructs different types of output * for baselines; namely, a set of raster line values and * a set of end points of each baseline. * (5) This function was designed to handle short and long text lines * without using dangerous thresholds on the peak heights. It does * this by combining the differential signal with a morphological * analysis of the locations of the text lines. One can also * combine this data to normalize the peak heights, by weighting * the differential signal in the region of each baseline * by the inverse of the width of the text line found there. * </pre> */ NUMA * pixFindBaselines(PIX *pixs, PTA **ppta, PIXA *pixadb) { l_int32 h, i, j, nbox, val1, val2, ndiff, bx, by, bw, bh; l_int32 imaxloc, peakthresh, zerothresh, inpeak; l_int32 mintosearch, max, maxloc, nloc, locval; l_int32 *array; l_float32 maxval; BOXA *boxa1, *boxa2, *boxa3; GPLOT *gplot; NUMA *nasum, *nadiff, *naloc, *naval; PIX *pix1, *pix2; PTA *pta; PROCNAME("pixFindBaselines"); if (ppta) *ppta = NULL; if (!pixs || pixGetDepth(pixs) != 1) return (NUMA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); /* Close up the text characters, removing noise */ pix1 = pixMorphSequence(pixs, "c25.1 + e15.1", 0); /* Estimate the resolution */ if (pixadb) pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT); /* Save the difference of adjacent row sums. * The high positive-going peaks are the baselines */ if ((nasum = pixCountPixelsByRow(pix1, NULL)) == NULL) { pixDestroy(&pix1); return (NUMA *)ERROR_PTR("nasum not made", procName, NULL); } h = pixGetHeight(pixs); nadiff = numaCreate(h); numaGetIValue(nasum, 0, &val2); for (i = 0; i < h - 1; i++) { val1 = val2; numaGetIValue(nasum, i + 1, &val2); numaAddNumber(nadiff, val1 - val2); } numaDestroy(&nasum); if (pixadb) { /* show the difference signal */ lept_mkdir("lept/baseline"); gplotSimple1(nadiff, GPLOT_PNG, "/tmp/lept/baseline/diff", "Diff Sig"); pix2 = pixRead("/tmp/lept/baseline/diff.png"); pixaAddPix(pixadb, pix2, L_INSERT); } /* Use the zeroes of the profile to locate each baseline. */ array = numaGetIArray(nadiff); ndiff = numaGetCount(nadiff); numaGetMax(nadiff, &maxval, &imaxloc); numaDestroy(&nadiff); /* Use this to begin locating a new peak: */ peakthresh = (l_int32)maxval / PEAK_THRESHOLD_RATIO; /* Use this to begin a region between peaks: */ zerothresh = (l_int32)maxval / ZERO_THRESHOLD_RATIO; naloc = numaCreate(0); naval = numaCreate(0); inpeak = FALSE; for (i = 0; i < ndiff; i++) { if (inpeak == FALSE) { if (array[i] > peakthresh) { /* transition to in-peak */ inpeak = TRUE; mintosearch = i + MIN_DIST_IN_PEAK; /* accept no zeros * between i and mintosearch */ max = array[i]; maxloc = i; } } else { /* inpeak == TRUE; look for max */ if (array[i] > max) { max = array[i]; maxloc = i; mintosearch = i + MIN_DIST_IN_PEAK; } else if (i > mintosearch && array[i] <= zerothresh) { /* leave */ inpeak = FALSE; numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } } } LEPT_FREE(array); /* If array[ndiff-1] is max, eg. no descenders, baseline at bottom */ if (inpeak) { numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } if (pixadb) { /* show the raster locations for the peaks */ gplot = gplotCreate("/tmp/lept/baseline/loc", GPLOT_PNG, "Peak locs", "rasterline", "height"); gplotAddPlot(gplot, naloc, naval, GPLOT_POINTS, "locs"); gplotMakeOutput(gplot); gplotDestroy(&gplot); pix2 = pixRead("/tmp/lept/baseline/loc.png"); pixaAddPix(pixadb, pix2, L_INSERT); } numaDestroy(&naval); /* Generate an approximate profile of text line width. * First, filter the boxes of text, where there may be * more than one box for a given textline. */ pix2 = pixMorphSequence(pix1, "r11 + c20.1 + o30.1 +c1.3", 0); if (pixadb) pixaAddPix(pixadb, pix2, L_COPY); boxa1 = pixConnComp(pix2, NULL, 4); pixDestroy(&pix1); pixDestroy(&pix2); if (boxaGetCount(boxa1) == 0) { numaDestroy(&naloc); boxaDestroy(&boxa1); L_INFO("no compnents after filtering\n", procName); return NULL; } boxa2 = boxaTransform(boxa1, 0, 0, 4., 4.); boxa3 = boxaSort(boxa2, L_SORT_BY_Y, L_SORT_INCREASING, NULL); boxaDestroy(&boxa1); boxaDestroy(&boxa2); /* Optionally, find the baseline segments */ pta = NULL; if (ppta) { pta = ptaCreate(0); *ppta = pta; } if (pta) { nloc = numaGetCount(naloc); nbox = boxaGetCount(boxa3); for (i = 0; i < nbox; i++) { boxaGetBoxGeometry(boxa3, i, &bx, &by, &bw, &bh); for (j = 0; j < nloc; j++) { numaGetIValue(naloc, j, &locval); if (L_ABS(locval - (by + bh)) > 25) continue; ptaAddPt(pta, bx, locval); ptaAddPt(pta, bx + bw, locval); break; } } } boxaDestroy(&boxa3); if (pixadb && pta) { /* display baselines */ l_int32 npts, x1, y1, x2, y2; pix1 = pixConvertTo32(pixs); npts = ptaGetCount(pta); for (i = 0; i < npts; i += 2) { ptaGetIPt(pta, i, &x1, &y1); ptaGetIPt(pta, i + 1, &x2, &y2); pixRenderLineArb(pix1, x1, y1, x2, y2, 2, 255, 0, 0); } pixWrite("/tmp/lept/baseline/baselines.png", pix1, IFF_PNG); pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT); pixDestroy(&pix1); } return naloc; }
/*! * pixMaskedThreshOnBackgroundNorm() * * Input: pixs (8 bpp grayscale; not colormapped) * pixim (<optional> 1 bpp 'image' mask; can be null) * sx, sy (tile size in pixels) * thresh (threshold for determining foreground) * mincount (min threshold on counts in a tile) * smoothx (half-width of block convolution kernel width) * smoothy (half-width of block convolution kernel height) * scorefract (fraction of the max Otsu score; typ. ~ 0.1) * &thresh (<optional return> threshold value that was * used on the normalized image) * Return: pixd (1 bpp thresholded image), or null on error * * Notes: * (1) This begins with a standard background normalization. * Additionally, there is a flexible background norm, that * will adapt to a rapidly varying background, and this * puts white pixels in the background near regions with * significant foreground. The white pixels are turned into * a 1 bpp selection mask by binarization followed by dilation. * Otsu thresholding is performed on the input image to get an * estimate of the threshold in the non-mask regions. * The background normalized image is thresholded with two * different values, and the result is combined using * the selection mask. * (2) Note that the numbers 255 (for bgval target) and 190 (for * thresholding on pixn) are tied together, and explicitly * defined in this function. * (3) See pixBackgroundNorm() for meaning and typical values * of input parameters. For a start, you can try: * sx, sy = 10, 15 * thresh = 100 * mincount = 50 * smoothx, smoothy = 2 */ PIX * pixMaskedThreshOnBackgroundNorm(PIX *pixs, PIX *pixim, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, l_int32 smoothx, l_int32 smoothy, l_float32 scorefract, l_int32 *pthresh) { l_int32 w, h; l_uint32 val; PIX *pixn, *pixm, *pixd, *pixt1, *pixt2, *pixt3, *pixt4; PROCNAME("pixMaskedThreshOnBackgroundNorm"); if (pthresh) *pthresh = 0; if (!pixs || pixGetDepth(pixs) != 8) return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL); if (pixGetColormap(pixs)) return (PIX *)ERROR_PTR("pixs is colormapped", procName, NULL); if (sx < 4 || sy < 4) return (PIX *)ERROR_PTR("sx and sy must be >= 4", procName, NULL); if (mincount > sx * sy) { L_WARNING("mincount too large for tile size\n", procName); mincount = (sx * sy) / 3; } /* Standard background normalization */ pixn = pixBackgroundNorm(pixs, pixim, NULL, sx, sy, thresh, mincount, 255, smoothx, smoothy); if (!pixn) return (PIX *)ERROR_PTR("pixn not made", procName, NULL); /* Special background normalization for adaptation to quickly * varying background. Threshold on the very light parts, * which tend to be near significant edges, and dilate to * form a mask over regions that are typically text. The * dilation size is chosen to cover the text completely, * except for very thick fonts. */ pixt1 = pixBackgroundNormFlex(pixs, 7, 7, 1, 1, 20); pixt2 = pixThresholdToBinary(pixt1, 240); pixInvert(pixt2, pixt2); pixm = pixMorphSequence(pixt2, "d21.21", 0); pixDestroy(&pixt1); pixDestroy(&pixt2); /* Use Otsu to get a global threshold estimate for the image, * which is stored as a single pixel in pixt3. */ pixGetDimensions(pixs, &w, &h, NULL); pixOtsuAdaptiveThreshold(pixs, w, h, 0, 0, scorefract, &pixt3, NULL); if (pixt3 && pthresh) { pixGetPixel(pixt3, 0, 0, &val); *pthresh = val; } pixDestroy(&pixt3); /* Threshold the background normalized images differentially, * using a high value correlated with the background normalization * for the part of the image under the mask (i.e., near the * darker, thicker foreground), and a value that depends on the Otsu * threshold for the rest of the image. This gives a solid * (high) thresholding for the foreground parts of the image, * while allowing the background and light foreground to be * reasonably well cleaned using a threshold adapted to the * input image. */ pixd = pixThresholdToBinary(pixn, val + 30); /* for bg and light fg */ pixt4 = pixThresholdToBinary(pixn, 190); /* for heavier fg */ pixCombineMasked(pixd, pixt4, pixm); pixDestroy(&pixt4); pixDestroy(&pixm); pixDestroy(&pixn); if (!pixd) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); else return pixd; }
/*! * \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; }
main(int argc, char **argv) { l_int32 h; l_float32 scalefactor; BOX *box; BOXA *boxa1, *boxa2; BOXAA *baa; PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8, *pix9; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; lept_rmdir("segtest"); lept_mkdir("segtest"); baa = boxaaCreate(5); /* Image region input. */ pix1 = pixRead("wet-day.jpg"); pix2 = pixScaleToSize(pix1, WIDTH, 0); pixWrite("/tmp/segtest/0.jpg", pix2, IFF_JFIF_JPEG); regTestCheckFile(rp, "/tmp/segtest/0.jpg"); /* 0 */ box = boxCreate(105, 161, 620, 872); /* image region */ boxa1 = boxaCreate(1); boxaAddBox(boxa1, box, L_INSERT); boxaaAddBoxa(baa, boxa1, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); /* Compute image region at w = 2 * WIDTH */ pix1 = pixRead("candelabrum-11.jpg"); pix2 = pixScaleToSize(pix1, WIDTH, 0); pix3 = pixConvertTo1(pix2, 100); pix4 = pixExpandBinaryPower2(pix3, 2); /* w = 2 * WIDTH */ pix5 = pixGenHalftoneMask(pix4, NULL, NULL, 1); pix6 = pixMorphSequence(pix5, "c20.1 + c1.20", 0); pix7 = pixMaskConnComp(pix6, 8, &boxa1); pix8 = pixReduceBinary2(pix7, NULL); /* back to w = WIDTH */ pix9 = pixBackgroundNormSimple(pix2, pix8, NULL); pixWrite("/tmp/segtest/1.jpg", pix9, IFF_JFIF_JPEG); regTestCheckFile(rp, "/tmp/segtest/1.jpg"); /* 1 */ boxa2 = boxaTransform(boxa1, 0, 0, 0.5, 0.5); /* back to w = WIDTH */ boxaaAddBoxa(baa, boxa2, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); pixDestroy(&pix5); pixDestroy(&pix6); pixDestroy(&pix7); pixDestroy(&pix8); pixDestroy(&pix9); boxaDestroy(&boxa1); /* Use mask to find image region */ pix1 = pixRead("lion-page.00016.jpg"); pix2 = pixScaleToSize(pix1, WIDTH, 0); pixWrite("/tmp/segtest/2.jpg", pix2, IFF_JFIF_JPEG); regTestCheckFile(rp, "/tmp/segtest/2.jpg"); /* 2 */ pix3 = pixRead("lion-mask.00016.tif"); pix4 = pixScaleToSize(pix3, WIDTH, 0); boxa1 = pixConnComp(pix4, NULL, 8); boxaaAddBoxa(baa, boxa1, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); /* Compute image region at full res */ pix1 = pixRead("rabi.png"); scalefactor = (l_float32)WIDTH / (l_float32)pixGetWidth(pix1); pix2 = pixScaleToGray(pix1, scalefactor); pixWrite("/tmp/segtest/3.jpg", pix2, IFF_JFIF_JPEG); regTestCheckFile(rp, "/tmp/segtest/3.jpg"); /* 3 */ pix3 = pixGenHalftoneMask(pix1, NULL, NULL, 0); pix4 = pixMorphSequence(pix3, "c20.1 + c1.20", 0); boxa1 = pixConnComp(pix4, NULL, 8); boxa2 = boxaTransform(boxa1, 0, 0, scalefactor, scalefactor); boxaaAddBoxa(baa, boxa2, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); boxaDestroy(&boxa1); /* Page with no image regions */ pix1 = pixRead("lucasta-47.jpg"); pix2 = pixScaleToSize(pix1, WIDTH, 0); boxa1 = boxaCreate(1); pixWrite("/tmp/segtest/4.jpg", pix2, IFF_JFIF_JPEG); regTestCheckFile(rp, "/tmp/segtest/4.jpg"); /* 4 */ boxaaAddBoxa(baa, boxa1, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); /* Page that is all image */ pix1 = pixRead("map1.jpg"); pix2 = pixScaleToSize(pix1, WIDTH, 0); pixWrite("/tmp/segtest/5.jpg", pix2, IFF_JFIF_JPEG); regTestCheckFile(rp, "/tmp/segtest/5.jpg"); /* 5 */ h = pixGetHeight(pix2); box = boxCreate(0, 0, WIDTH, h); boxa1 = boxaCreate(1); boxaAddBox(boxa1, box, L_INSERT); boxaaAddBoxa(baa, boxa1, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); /* Save the boxaa file */ boxaaWrite("/tmp/segtest/seg.baa", baa); regTestCheckFile(rp, "/tmp/segtest/seg.baa"); /* 6 */ /* Do the conversion */ l_pdfSetDateAndVersion(FALSE); convertSegmentedFilesToPdf("/tmp/segtest", ".jpg", 100, L_G4_ENCODE, 140, baa, 75, 0.6, "Segmentation Test", "/tmp/pdfseg.7.pdf"); regTestCheckFile(rp, "/tmp/pdfseg.7.pdf"); /* 7 */ boxaaDestroy(&baa); return regTestCleanup(rp); }
main(int argc, char **argv) { char *str; char buffer1[256]; char buffer2[256]; l_int32 i, same, same2, factor1, factor2, diff, success; PIX *pixs, *pixsd, *pixt1, *pixt2, *pixt3; SEL *sel1, *sel2; static char mainName[] = "binmorph2_reg"; #if 1 pixs = pixRead("rabi.png"); pixsd = pixMorphCompSequence(pixs, "d5.5", 0); success = TRUE; for (i = 1; i < MAX_SEL_SIZE; i++) { /* Check if the size is exactly decomposable */ selectComposableSizes(i, &factor1, &factor2); diff = factor1 * factor2 - i; fprintf(stderr, "%d: (%d, %d): %d\n", i, factor1, factor2, diff); /* Carry out operations on identical sized Sels: dilation */ sprintf(buffer1, "d%d.%d", i + diff, i + diff); sprintf(buffer2, "d%d.%d", i, i); pixt1 = pixMorphSequence(pixsd, buffer1, 0); pixt2 = pixMorphCompSequence(pixsd, buffer2, 0); pixEqual(pixt1, pixt2, &same); if (i < 64) { pixt3 = pixMorphCompSequenceDwa(pixsd, buffer2, 0); pixEqual(pixt1, pixt3, &same2); } else { pixt3 = NULL; same2 = TRUE; } if (same && same2) writeResult(buffer1, 1); else { writeResult(buffer1, 0); success = FALSE; } pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); /* ... erosion */ sprintf(buffer1, "e%d.%d", i + diff, i + diff); sprintf(buffer2, "e%d.%d", i, i); pixt1 = pixMorphSequence(pixsd, buffer1, 0); pixt2 = pixMorphCompSequence(pixsd, buffer2, 0); pixEqual(pixt1, pixt2, &same); if (i < 64) { pixt3 = pixMorphCompSequenceDwa(pixsd, buffer2, 0); pixEqual(pixt1, pixt3, &same2); } else { pixt3 = NULL; same2 = TRUE; } if (same && same2) writeResult(buffer1, 1); else { writeResult(buffer1, 0); success = FALSE; } pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); /* ... opening */ sprintf(buffer1, "o%d.%d", i + diff, i + diff); sprintf(buffer2, "o%d.%d", i, i); pixt1 = pixMorphSequence(pixsd, buffer1, 0); pixt2 = pixMorphCompSequence(pixsd, buffer2, 0); pixEqual(pixt1, pixt2, &same); if (i < 64) { pixt3 = pixMorphCompSequenceDwa(pixsd, buffer2, 0); pixEqual(pixt1, pixt3, &same2); } else { pixt3 = NULL; same2 = TRUE; } if (same && same2) writeResult(buffer1, 1); else { writeResult(buffer1, 0); success = FALSE; } pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); /* ... closing */ sprintf(buffer1, "c%d.%d", i + diff, i + diff); sprintf(buffer2, "c%d.%d", i, i); pixt1 = pixMorphSequence(pixsd, buffer1, 0); pixt2 = pixMorphCompSequence(pixsd, buffer2, 0); pixEqual(pixt1, pixt2, &same); if (i < 64) { pixt3 = pixMorphCompSequenceDwa(pixsd, buffer2, 0); pixEqual(pixt1, pixt3, &same2); } else { pixt3 = NULL; same2 = TRUE; } if (same && same2) writeResult(buffer1, 1); else { writeResult(buffer1, 0); success = FALSE; } pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); } pixDestroy(&pixs); pixDestroy(&pixsd); if (success) fprintf(stderr, "\n---------- Success: no errors ----------\n"); else fprintf(stderr, "\n---------- Failure: error(s) found -----------\n"); #endif #if 0 for (i = 1; i < 400; i++) { selectComposableSizes(i, &factor1, &factor2); diff = factor1 * factor2 - i; fprintf(stderr, "%d: (%d, %d): %d\n", i, factor1, factor2, diff); selectComposableSels(i, L_HORIZ, &sel1, &sel2); selDestroy(&sel1); selDestroy(&sel2); } #endif #if 0 selectComposableSels(68, L_HORIZ, &sel1, &sel2); /* 17, 4 */ str = selPrintToString(sel2); fprintf(stderr, str); selDestroy(&sel1); selDestroy(&sel2); FREE(str); selectComposableSels(70, L_HORIZ, &sel1, &sel2); /* 10, 7 */ str = selPrintToString(sel2); selDestroy(&sel1); selDestroy(&sel2); fprintf(stderr, str); FREE(str); selectComposableSels(85, L_HORIZ, &sel1, &sel2); /* 17, 5 */ str = selPrintToString(sel2); selDestroy(&sel1); selDestroy(&sel2); fprintf(stderr, str); FREE(str); selectComposableSels(96, L_HORIZ, &sel1, &sel2); /* 12, 8 */ str = selPrintToString(sel2); selDestroy(&sel1); selDestroy(&sel2); fprintf(stderr, str); FREE(str); { SELA *sela; sela = selaAddBasic(NULL); selaWrite("/tmp/junksela.sela", sela); selaDestroy(&sela); } #endif return 0; }
/*! * pixFindPageForeground() * * Input: pixs (full resolution (any type or depth) * threshold (for binarization; typically about 128) * mindist (min distance of text from border to allow * cleaning near border; at 2x reduction, this * should be larger than 50; typically about 70) * erasedist (when conditions are satisfied, erase anything * within this distance of the edge; * typically 30 at 2x reduction) * pagenum (use for debugging when called repeatedly; labels * debug images that are assembled into pdfdir) * showmorph (set to a negative integer to show steps in * generating masks; this is typically used * for debugging region extraction) * display (set to 1 to display mask and selected region * for debugging a single page) * pdfdir (subdirectory of /tmp where images showing the * result are placed when called repeatedly; use * null if no output requested) * Return: box (region including foreground, with some pixel noise * removed), or null if not found * * Notes: * (1) This doesn't simply crop to the fg. It attempts to remove * pixel noise and junk at the edge of the image before cropping. * The input @threshold is used if pixs is not 1 bpp. * (2) There are several debugging options, determined by the * last 4 arguments. * (3) If you want pdf output of results when called repeatedly, * the pagenum arg labels the images written, which go into * /tmp/<pdfdir>/<pagenum>.png. In that case, * you would clean out the /tmp directory before calling this * function on each page: * lept_rmdir(pdfdir); * lept_mkdir(pdfdir); */ BOX * pixFindPageForeground(PIX *pixs, l_int32 threshold, l_int32 mindist, l_int32 erasedist, l_int32 pagenum, l_int32 showmorph, l_int32 display, const char *pdfdir) { char buf[64]; l_int32 flag, nbox, intersects; l_int32 w, h, bx, by, bw, bh, left, right, top, bottom; PIX *pixb, *pixb2, *pixseed, *pixsf, *pixm, *pix1, *pixg2; BOX *box, *boxfg, *boxin, *boxd; BOXA *ba1, *ba2; PROCNAME("pixFindPageForeground"); if (!pixs) return (BOX *)ERROR_PTR("pixs not defined", procName, NULL); /* Binarize, downscale by 0.5, remove the noise to generate a seed, * and do a seedfill back from the seed into those 8-connected * components of the binarized image for which there was at least * one seed pixel. Also clear out any components that are within * 10 pixels of the edge at 2x reduction. */ flag = (showmorph) ? -1 : 0; /* if showmorph == -1, write intermediate * images to /tmp/seq_output_1.pdf */ pixb = pixConvertTo1(pixs, threshold); pixb2 = pixScale(pixb, 0.5, 0.5); pixseed = pixMorphSequence(pixb2, "o1.2 + c9.9 + o3.5", flag); pixsf = pixSeedfillBinary(NULL, pixseed, pixb2, 8); pixSetOrClearBorder(pixsf, 10, 10, 10, 10, PIX_SET); pixm = pixRemoveBorderConnComps(pixsf, 8); if (display) pixDisplay(pixm, 100, 100); /* Now, where is the main block of text? We want to remove noise near * the edge of the image, but to do that, we have to be convinced that * (1) there is noise and (2) it is far enough from the text block * and close enough to the edge. For each edge, if the block * is more than mindist from that edge, then clean 'erasedist' * pixels from the edge. */ pix1 = pixMorphSequence(pixm, "c50.50", flag - 1); ba1 = pixConnComp(pix1, NULL, 8); ba2 = boxaSort(ba1, L_SORT_BY_AREA, L_SORT_DECREASING, NULL); pixGetDimensions(pix1, &w, &h, NULL); nbox = boxaGetCount(ba2); if (nbox > 1) { box = boxaGetBox(ba2, 0, L_CLONE); boxGetGeometry(box, &bx, &by, &bw, &bh); left = (bx > mindist) ? erasedist : 0; right = (w - bx - bw > mindist) ? erasedist : 0; top = (by > mindist) ? erasedist : 0; bottom = (h - by - bh > mindist) ? erasedist : 0; pixSetOrClearBorder(pixm, left, right, top, bottom, PIX_CLR); boxDestroy(&box); } pixDestroy(&pix1); boxaDestroy(&ba1); boxaDestroy(&ba2); /* Locate the foreground region; don't bother cropping */ pixClipToForeground(pixm, NULL, &boxfg); /* Sanity check the fg region. Make sure it's not confined * to a thin boundary on the left and right sides of the image, * in which case it is likely to be noise. */ if (boxfg) { boxin = boxCreate(0.1 * w, 0, 0.8 * w, h); boxIntersects(boxfg, boxin, &intersects); if (!intersects) { L_INFO("found only noise on page %d\n", procName, pagenum); boxDestroy(&boxfg); } boxDestroy(&boxin); } boxd = NULL; if (!boxfg) { L_INFO("no fg region found for page %d\n", procName, pagenum); } else { boxAdjustSides(boxfg, boxfg, -2, 2, -2, 2); /* tiny expansion */ boxd = boxTransform(boxfg, 0, 0, 2.0, 2.0); /* Write image showing box for this page. This is to be * bundled up into a pdf of all the pages, which can be * generated by convertFilesToPdf() */ if (pdfdir) { pixg2 = pixConvert1To4Cmap(pixb); pixRenderBoxArb(pixg2, boxd, 3, 255, 0, 0); snprintf(buf, sizeof(buf), "/tmp/%s/%05d.png", pdfdir, pagenum); if (display) pixDisplay(pixg2, 700, 100); pixWrite(buf, pixg2, IFF_PNG); pixDestroy(&pixg2); } } pixDestroy(&pixb); pixDestroy(&pixb2); pixDestroy(&pixseed); pixDestroy(&pixsf); pixDestroy(&pixm); boxDestroy(&boxfg); return boxd; }
l_int32 main(int argc, char **argv) { l_int32 irval, igval, ibval; l_float32 rval, gval, bval, fract, fgfract; L_BMF *bmf; BOX *box; BOXA *boxa; FPIX *fpix; PIX *pixs, *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7; PIX *pix8, *pix9, *pix10, *pix11, *pix12, *pix13, *pix14, *pix15; PIXA *pixa; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pixa = pixaCreate(0); pixs = pixRead("breviar38.150.jpg"); /* pixs = pixRead("breviar32.150.jpg"); */ pixaAddPix(pixa, pixs, L_CLONE); regTestWritePixAndCheck(rp, pixs, IFF_JFIF_JPEG); /* 0 */ pixDisplayWithTitle(pixs, 0, 0, "Input image", rp->display); /* Extract the blue component, which is small in all the text * regions, including in the highlight color region */ pix1 = pixGetRGBComponent(pixs, COLOR_BLUE); pixaAddPix(pixa, pix1, L_CLONE); regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 1 */ pixDisplayWithTitle(pix1, 200, 0, "Blue component", rp->display); /* Do a background normalization, with the background set to * approximately 200 */ pix2 = pixBackgroundNormSimple(pix1, NULL, NULL); pixaAddPix(pixa, pix2, L_COPY); regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 2 */ pixDisplayWithTitle(pix2, 400, 0, "BG normalized to 200", rp->display); /* Do a linear transform on the gray pixels, with 50 going to * black and 160 going to white. 50 is sufficiently low to * make both the red and black print quite dark. Quantize * to a few equally spaced gray levels. This is the image * to which highlight color will be applied. */ pixGammaTRC(pix2, pix2, 1.0, 50, 160); pix3 = pixThresholdOn8bpp(pix2, 7, 1); pixaAddPix(pixa, pix3, L_CLONE); regTestWritePixAndCheck(rp, pix3, IFF_JFIF_JPEG); /* 3 */ pixDisplayWithTitle(pix3, 600, 0, "Basic quantized with white bg", rp->display); /* Identify the regions of red text. First, make a mask * consisting of all pixels such that (R-B)/B is larger * than 2.0. This will have all the red, plus a lot of * the dark pixels. */ fpix = pixComponentFunction(pixs, 1.0, 0.0, -1.0, 0.0, 0.0, 1.0); pix4 = fpixThresholdToPix(fpix, 2.0); pixInvert(pix4, pix4); /* red plus some dark text */ pixaAddPix(pixa, pix4, L_CLONE); regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 4 */ pixDisplayWithTitle(pix4, 800, 0, "Red plus dark pixels", rp->display); /* Make a mask consisting of all the red and background pixels */ pix5 = pixGetRGBComponent(pixs, COLOR_RED); pix6 = pixThresholdToBinary(pix5, 128); pixInvert(pix6, pix6); /* red plus background (white) */ /* Intersect the two masks to get a mask consisting of pixels * that are almost certainly red. This is the seed. */ pix7 = pixAnd(NULL, pix4, pix6); /* red only (seed) */ pixaAddPix(pixa, pix7, L_COPY); regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 5 */ pixDisplayWithTitle(pix7, 0, 600, "Seed for red color", rp->display); /* Make the clipping mask by thresholding the image with * the background cleaned to white. */ pix8 = pixThresholdToBinary(pix2, 230); /* mask */ pixaAddPix(pixa, pix8, L_CLONE); regTestWritePixAndCheck(rp, pix8, IFF_PNG); /* 6 */ pixDisplayWithTitle(pix8, 200, 600, "Clipping mask for red components", rp->display); /* Fill into the mask from the seed */ pixSeedfillBinary(pix7, pix7, pix8, 8); /* filled: red plus touching */ regTestWritePixAndCheck(rp, pix7, IFF_PNG); /* 7 */ pixDisplayWithTitle(pix7, 400, 600, "Red component mask filled", rp->display); /* Remove long horizontal and vertical lines from the filled result */ pix9 = pixMorphSequence(pix7, "o40.1", 0); pixSubtract(pix7, pix7, pix9); /* remove long horizontal lines */ pixDestroy(&pix9); pix9 = pixMorphSequence(pix7, "o1.40", 0); pixSubtract(pix7, pix7, pix9); /* remove long vertical lines */ /* Close the regions to be colored */ pix10 = pixMorphSequence(pix7, "c5.1", 0); pixaAddPix(pixa, pix10, L_CLONE); regTestWritePixAndCheck(rp, pix10, IFF_PNG); /* 8 */ pixDisplayWithTitle(pix10, 600, 600, "Components defining regions allowing coloring", rp->display); /* Sanity check on amount to be colored. Only accept images * with less than 10% of all the pixels with highlight color */ pixForegroundFraction(pix10, &fgfract); if (fgfract >= 0.10) { L_INFO("too much highlighting: fract = %6.3f; removing it\n", rp->testname, fgfract); pixClearAll(pix10); pixSetPixel(pix10, 0, 0, 1); } /* Get the bounding boxes of the regions to be colored */ boxa = pixConnCompBB(pix10, 8); /* Get a color to paint that is representative of the * actual highlight color in the image. Scale each * color component up from the average by an amount necessary * to saturate the red. Then divide the green and * blue components by 2.0. */ pixGetAverageMaskedRGB(pixs, pix7, 0, 0, 1, L_MEAN_ABSVAL, &rval, &gval, &bval); fract = 255.0 / rval; irval = lept_roundftoi(fract * rval); igval = lept_roundftoi(fract * gval / 2.0); ibval = lept_roundftoi(fract * bval / 2.0); fprintf(stderr, "(r,g,b) = (%d,%d,%d)\n", irval, igval, ibval); /* Color the quantized gray version in the selected regions */ pix11 = pixColorGrayRegions(pix3, boxa, L_PAINT_DARK, 220, irval, igval, ibval); pixaAddPix(pixa, pix11, L_CLONE); regTestWritePixAndCheck(rp, pix11, IFF_PNG); /* 9 */ pixDisplayWithTitle(pix11, 800, 600, "Final colored result", rp->display); pixaAddPix(pixa, pixs, L_CLONE); /* Test colorization on gray and cmapped gray */ pix12 = pixColorGrayRegions(pix2, boxa, L_PAINT_DARK, 220, 0, 255, 0); pixaAddPix(pixa, pix12, L_CLONE); regTestWritePixAndCheck(rp, pix12, IFF_PNG); /* 10 */ pixDisplayWithTitle(pix12, 900, 600, "Colorizing boxa gray", rp->display); box = boxCreate(200, 200, 250, 350); pix13 = pixCopy(NULL, pix2); pixColorGray(pix13, box, L_PAINT_DARK, 220, 0, 0, 255); pixaAddPix(pixa, pix13, L_CLONE); regTestWritePixAndCheck(rp, pix13, IFF_PNG); /* 11 */ pixDisplayWithTitle(pix13, 1000, 600, "Colorizing box gray", rp->display); pix14 = pixThresholdTo4bpp(pix2, 6, 1); pix15 = pixColorGrayRegions(pix14, boxa, L_PAINT_DARK, 220, 0, 0, 255); pixaAddPix(pixa, pix15, L_CLONE); regTestWritePixAndCheck(rp, pix15, IFF_PNG); /* 12 */ pixDisplayWithTitle(pix15, 1100, 600, "Colorizing boxa cmap", rp->display); pixColorGrayCmap(pix14, box, L_PAINT_DARK, 0, 255, 255); pixaAddPix(pixa, pix14, L_CLONE); regTestWritePixAndCheck(rp, pix14, IFF_PNG); /* 13 */ pixDisplayWithTitle(pix14, 1200, 600, "Colorizing box cmap", rp->display); boxDestroy(&box); /* Generate a pdf of the intermediate results */ lept_mkdir("lept"); L_INFO("Writing to /tmp/lept/colorize.pdf\n", rp->testname); pixaConvertToPdf(pixa, 90, 1.0, 0, 0, "Colorizing highlighted text", "/tmp/lept/colorize.pdf"); pixaDestroy(&pixa); fpixDestroy(&fpix); boxDestroy(&box); boxaDestroy(&boxa); pixDestroy(&pixs); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); pixDestroy(&pix5); pixDestroy(&pix6); pixDestroy(&pix7); pixDestroy(&pix8); pixDestroy(&pix9); pixDestroy(&pix10); pixDestroy(&pix11); pixDestroy(&pix12); pixDestroy(&pix13); pixDestroy(&pix14); pixDestroy(&pix15); /* Test the color detector */ pixa = pixaCreate(7); bmf = bmfCreate("./fonts", 4); pix1 = TestForRedColor(rp, "brev06.75.jpg", 1, bmf); /* 14 */ pixaAddPix(pixa, pix1, L_INSERT); pix1 = TestForRedColor(rp, "brev10.75.jpg", 0, bmf); /* 15 */ pixaAddPix(pixa, pix1, L_INSERT); pix1 = TestForRedColor(rp, "brev14.75.jpg", 1, bmf); /* 16 */ pixaAddPix(pixa, pix1, L_INSERT); pix1 = TestForRedColor(rp, "brev20.75.jpg", 1, bmf); /* 17 */ pixaAddPix(pixa, pix1, L_INSERT); pix1 = TestForRedColor(rp, "brev36.75.jpg", 0, bmf); /* 18 */ pixaAddPix(pixa, pix1, L_INSERT); pix1 = TestForRedColor(rp, "brev53.75.jpg", 1, bmf); /* 19 */ pixaAddPix(pixa, pix1, L_INSERT); pix1 = TestForRedColor(rp, "brev56.75.jpg", 1, bmf); /* 20 */ pixaAddPix(pixa, pix1, L_INSERT); /* Generate a pdf of the color detector results */ L_INFO("Writing to /tmp/lept/colordetect.pdf\n", rp->testname); pixaConvertToPdf(pixa, 45, 1.0, 0, 0, "Color detection", "/tmp/lept/colordetect.pdf"); pixaDestroy(&pixa); bmfDestroy(&bmf); return regTestCleanup(rp); }
/*! * pixSplitIntoCharacters() * * Input: pixs (1 bpp, contains only deskewed text) * minw (minimum component width for initial filtering; typ. 4) * minh (minimum component height for initial filtering; typ. 4) * &boxa (<optional return> character bounding boxes) * &pixa (<optional return> character images) * &pixdebug (<optional return> showing splittings) * * Return: 0 if OK, 1 on error * * Notes: * (1) This is a simple function that attempts to find split points * based on vertical pixel profiles. * (2) It should be given an image that has an arbitrary number * of text characters. * (3) The returned pixa includes the boxes from which the * (possibly split) components are extracted. */ l_int32 pixSplitIntoCharacters(PIX *pixs, l_int32 minw, l_int32 minh, BOXA **pboxa, PIXA **ppixa, PIX **ppixdebug) { l_int32 ncomp, i, xoff, yoff; BOXA *boxa1, *boxa2, *boxat1, *boxat2, *boxad; BOXAA *baa; PIX *pix, *pix1, *pix2, *pixdb; PIXA *pixa1, *pixadb; PROCNAME("pixSplitIntoCharacters"); if (pboxa) *pboxa = NULL; if (ppixa) *ppixa = NULL; if (ppixdebug) *ppixdebug = NULL; if (!pixs || pixGetDepth(pixs) != 1) return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); /* Remove the small stuff */ pix1 = pixSelectBySize(pixs, minw, minh, 8, L_SELECT_IF_BOTH, L_SELECT_IF_GT, NULL); /* Small vertical close for consolidation */ pix2 = pixMorphSequence(pix1, "c1.10", 0); pixDestroy(&pix1); /* Get the 8-connected components */ boxa1 = pixConnComp(pix2, &pixa1, 8); pixDestroy(&pix2); boxaDestroy(&boxa1); /* Split the components if obvious */ ncomp = pixaGetCount(pixa1); boxa2 = boxaCreate(ncomp); pixadb = (ppixdebug) ? pixaCreate(ncomp) : NULL; for (i = 0; i < ncomp; i++) { pix = pixaGetPix(pixa1, i, L_CLONE); if (ppixdebug) { boxat1 = pixSplitComponentWithProfile(pix, 10, 7, &pixdb); if (pixdb) pixaAddPix(pixadb, pixdb, L_INSERT); } else { boxat1 = pixSplitComponentWithProfile(pix, 10, 7, NULL); } pixaGetBoxGeometry(pixa1, i, &xoff, &yoff, NULL, NULL); boxat2 = boxaTransform(boxat1, xoff, yoff, 1.0, 1.0); boxaJoin(boxa2, boxat2, 0, -1); pixDestroy(&pix); boxaDestroy(&boxat1); boxaDestroy(&boxat2); } pixaDestroy(&pixa1); /* Generate the debug image */ if (ppixdebug) { if (pixaGetCount(pixadb) > 0) { *ppixdebug = pixaDisplayTiledInRows(pixadb, 32, 1500, 1.0, 0, 20, 1); } pixaDestroy(&pixadb); } /* Do a 2D sort on the bounding boxes, and flatten the result to 1D */ baa = boxaSort2d(boxa2, NULL, 0, 0, 5); boxad = boxaaFlattenToBoxa(baa, NULL, L_CLONE); boxaaDestroy(&baa); boxaDestroy(&boxa2); /* Optionally extract the pieces from the input image */ if (ppixa) *ppixa = pixClipRectangles(pixs, boxad); if (pboxa) *pboxa = boxad; else boxaDestroy(&boxad); return 0; }
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; }
/*! * pixaGenerateFont() * * Input: dir (directory holding image of character set) * size (4, 6, 8, ... , 20, in pts at 300 ppi) * &bl1 (<return> baseline of row 1) * &bl2 (<return> baseline of row 2) * &bl3 (<return> baseline of row 3) * Return: pixa of font bitmaps for 95 characters, or null on error * * These font generation functions use 9 sets, each with bitmaps * of 94 ascii characters, all in Palatino-Roman font. * Each input bitmap has 3 rows of characters. The range of * ascii values in each row is as follows: * row 0: 32-57 (32 is a space) * row 1: 58-91 (92, '\', is not represented in this font) * row 2: 93-126 * We LR flip the '/' char to generate a bitmap for the missing * '\' character, so that we have representations of all 95 * printable chars. * * Computation of the bitmaps and baselines for a single * font takes from 40 to 200 msec on a 2 GHz processor, * depending on the size. Use pixaGetFont() to read the * generated character set directly from files that were * produced in prog/genfonts.c using this function. */ PIXA * pixaGenerateFont(const char *dir, l_int32 size, l_int32 *pbl0, l_int32 *pbl1, l_int32 *pbl2) { char *pathname; l_int32 fileno; l_int32 i, j, nrows, nrowchars, nchars, h, yval; l_int32 width, height; l_int32 baseline[3]; l_int32 *tab; BOX *box, *box1, *box2; BOXA *boxar, *boxac, *boxacs; PIX *pixs, *pixt1, *pixt2, *pixt3; PIX *pixr, *pixrc, *pixc; PIXA *pixa; PROCNAME("pixaGenerateFont"); if (!pbl0 || !pbl1 || !pbl2) return (PIXA *)ERROR_PTR("&bl not all defined", procName, NULL); *pbl0 = *pbl1 = *pbl2 = 0; fileno = (size / 2) - 2; if (fileno < 0 || fileno > NFONTS) return (PIXA *)ERROR_PTR("font size invalid", procName, NULL); tab = makePixelSumTab8(); pathname = genPathname(dir, inputfonts[fileno]); if ((pixs = pixRead(pathname)) == NULL) return (PIXA *)ERROR_PTR("pixs not all defined", procName, NULL); FREE(pathname); pixa = pixaCreate(95); pixt1 = pixMorphSequence(pixs, "c1.35 + c101.1", 0); boxar = pixConnComp(pixt1, NULL, 8); /* one box for each row */ pixDestroy(&pixt1); nrows = boxaGetCount(boxar); #if DEBUG_FONT_GEN fprintf(stderr, "For font %s, number of rows is %d\n", inputfonts[fileno], nrows); #endif /* DEBUG_FONT_GEN */ if (nrows != 3) { L_INFO_INT2("nrows = %d; skipping font %d", procName, nrows, fileno); return (PIXA *)ERROR_PTR("3 rows not generated", procName, NULL); } for (i = 0; i < nrows; i++) { box = boxaGetBox(boxar, i, L_CLONE); pixr = pixClipRectangle(pixs, box, NULL); /* row of chars */ pixGetTextBaseline(pixr, tab, &yval); baseline[i] = yval; #if DEBUG_BASELINE { PIX *pixbl; fprintf(stderr, "row %d, yval = %d, h = %d\n", i, yval, pixGetHeight(pixr)); pixbl = pixCopy(NULL, pixr); pixRenderLine(pixbl, 0, yval, pixGetWidth(pixbl), yval, 1, L_FLIP_PIXELS); if (i == 0 ) pixWrite("junktl0", pixbl, IFF_PNG); else if (i == 1) pixWrite("junktl1", pixbl, IFF_PNG); else pixWrite("junktl2", pixbl, IFF_PNG); pixDestroy(&pixbl); } #endif /* DEBUG_BASELINE */ boxDestroy(&box); pixrc = pixCloseSafeBrick(NULL, pixr, 1, 35); boxac = pixConnComp(pixrc, NULL, 8); boxacs = boxaSort(boxac, L_SORT_BY_X, L_SORT_INCREASING, NULL); if (i == 0) { /* consolidate the two components of '"' */ box1 = boxaGetBox(boxacs, 1, L_CLONE); box2 = boxaGetBox(boxacs, 2, L_CLONE); box1->w = box2->x + box2->w - box1->x; /* increase width */ boxDestroy(&box1); boxDestroy(&box2); boxaRemoveBox(boxacs, 2); } h = pixGetHeight(pixr); nrowchars = boxaGetCount(boxacs); for (j = 0; j < nrowchars; j++) { box = boxaGetBox(boxacs, j, L_COPY); if (box->w <= 2 && box->h == 1) { /* skip 1x1, 2x1 components */ boxDestroy(&box); continue; } box->y = 0; box->h = h - 1; pixc = pixClipRectangle(pixr, box, NULL); boxDestroy(&box); if (i == 0 && j == 0) /* add a pix for the space; change later */ pixaAddPix(pixa, pixc, L_COPY); if (i == 2 && j == 0) /* add a pix for the '\'; change later */ pixaAddPix(pixa, pixc, L_COPY); pixaAddPix(pixa, pixc, L_INSERT); } pixDestroy(&pixr); pixDestroy(&pixrc); boxaDestroy(&boxac); boxaDestroy(&boxacs); } nchars = pixaGetCount(pixa); if (nchars != 95) return (PIXA *)ERROR_PTR("95 chars not generated", procName, NULL); *pbl0 = baseline[0]; *pbl1 = baseline[1]; *pbl2 = baseline[2]; /* Fix the space character up; it should have no ON pixels, * and be about twice as wide as the '!' character. */ pixt2 = pixaGetPix(pixa, 0, L_CLONE); width = 2 * pixGetWidth(pixt2); height = pixGetHeight(pixt2); pixDestroy(&pixt2); pixt2 = pixCreate(width, height, 1); pixaReplacePix(pixa, 0, pixt2, NULL); /* Fix up the '\' character; use a LR flip of the '/' char */ pixt2 = pixaGetPix(pixa, 15, L_CLONE); pixt3 = pixFlipLR(NULL, pixt2); pixDestroy(&pixt2); pixaReplacePix(pixa, 60, pixt3, NULL); #if DEBUG_CHARS { PIX *pixd; pixd = pixaDisplayTiled(pixa, 1500, 0, 10); pixDisplay(pixd, 100 * i, 200); pixDestroy(&pixd); } #endif /* DEBUG_CHARS */ pixDestroy(&pixs); boxaDestroy(&boxar); FREE(tab); return pixa; }