// Helper to remove an enclosing circle from an image. // If there isn't one, then the image will most likely get badly mangled. // The returned pix must be pixDestroyed after use. NULL may be returned // if the image doesn't meet the trivial conditions that it uses to determine // success. static Pix* RemoveEnclosingCircle(Pix* pixs) { Pix* pixsi = pixInvert(NULL, pixs); Pix* pixc = pixCreateTemplate(pixs); pixSetOrClearBorder(pixc, 1, 1, 1, 1, PIX_SET); pixSeedfillBinary(pixc, pixc, pixsi, 4); pixInvert(pixc, pixc); pixDestroy(&pixsi); Pix* pixt = pixAnd(NULL, pixs, pixc); l_int32 max_count; pixCountConnComp(pixt, 8, &max_count); // The count has to go up before we start looking for the minimum. l_int32 min_count = MAX_INT32; Pix* pixout = NULL; for (int i = 1; i < kMaxCircleErosions; i++) { pixDestroy(&pixt); pixErodeBrick(pixc, pixc, 3, 3); pixt = pixAnd(NULL, pixs, pixc); l_int32 count; pixCountConnComp(pixt, 8, &count); if (i == 1 || count > max_count) { max_count = count; min_count = count; } else if (i > 1 && count < min_count) { min_count = count; pixDestroy(&pixout); pixout = pixCopy(NULL, pixt); // Save the best. } else if (count >= min_count) { break; // We have passed by the best. } } pixDestroy(&pixt); pixDestroy(&pixc); return pixout; }
int main(int argc, char **argv) { l_uint8 *array1, *array2; l_int32 n1, n2, n3; size_t size1, size2; FILE *fp; BOXA *boxa1, *boxa2; PIX *pixs, *pix1; PIXA *pixa1; PIXCMAP *cmap; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pixs = pixRead("feyn.tif"); /* --------------------------------------------------------------- * * Test pixConnComp() and pixCountConnComp(), * * with output to both boxa and pixa * * --------------------------------------------------------------- */ /* First, test with 4-cc */ boxa1= pixConnComp(pixs, &pixa1, 4); n1 = boxaGetCount(boxa1); boxa2= pixConnComp(pixs, NULL, 4); n2 = boxaGetCount(boxa2); pixCountConnComp(pixs, 4, &n3); fprintf(stderr, "Number of 4 c.c.: n1 = %d; n2 = %d, n3 = %d\n", n1, n2, n3); regTestCompareValues(rp, n1, n2, 0); /* 0 */ regTestCompareValues(rp, n1, n3, 0); /* 1 */ regTestCompareValues(rp, n1, 4452, 0); /* 2 */ pix1 = pixaDisplay(pixa1, pixGetWidth(pixs), pixGetHeight(pixs)); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 3 */ regTestComparePix(rp, pixs, pix1); /* 4 */ pixaDestroy(&pixa1); boxaDestroy(&boxa1); boxaDestroy(&boxa2); pixDestroy(&pix1); /* Test with 8-cc */ boxa1= pixConnComp(pixs, &pixa1, 8); n1 = boxaGetCount(boxa1); boxa2= pixConnComp(pixs, NULL, 8); n2 = boxaGetCount(boxa2); pixCountConnComp(pixs, 8, &n3); fprintf(stderr, "Number of 8 c.c.: n1 = %d; n2 = %d, n3 = %d\n", n1, n2, n3); regTestCompareValues(rp, n1, n2, 0); /* 5 */ regTestCompareValues(rp, n1, n3, 0); /* 6 */ regTestCompareValues(rp, n1, 4305, 0); /* 7 */ pix1 = pixaDisplay(pixa1, pixGetWidth(pixs), pixGetHeight(pixs)); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 8 */ regTestComparePix(rp, pixs, pix1); /* 9 */ pixaDestroy(&pixa1); boxaDestroy(&boxa1); boxaDestroy(&boxa2); pixDestroy(&pix1); /* --------------------------------------------------------------- * * Test boxa I/O * * --------------------------------------------------------------- */ lept_mkdir("lept/conn"); boxa1 = pixConnComp(pixs, NULL, 4); fp = lept_fopen("/tmp/lept/conn/boxa1.ba", "wb+"); boxaWriteStream(fp, boxa1); lept_fclose(fp); fp = lept_fopen("/tmp/lept/conn/boxa1.ba", "rb"); boxa2 = boxaReadStream(fp); lept_fclose(fp); fp = lept_fopen("/tmp/lept/conn/boxa2.ba", "wb+"); boxaWriteStream(fp, boxa2); lept_fclose(fp); array1 = l_binaryRead("/tmp/lept/conn/boxa1.ba", &size1); array2 = l_binaryRead("/tmp/lept/conn/boxa2.ba", &size2); regTestCompareStrings(rp, array1, size1, array2, size2); /* 10 */ lept_free(array1); lept_free(array2); boxaDestroy(&boxa1); boxaDestroy(&boxa2); /* --------------------------------------------------------------- * * Just for fun, display each component as a random color in * * cmapped 8 bpp. Background is color 0; it is set to white. * * --------------------------------------------------------------- */ boxa1 = pixConnComp(pixs, &pixa1, 4); pix1 = pixaDisplayRandomCmap(pixa1, pixGetWidth(pixs), pixGetHeight(pixs)); cmap = pixGetColormap(pix1); pixcmapResetColor(cmap, 0, 255, 255, 255); /* reset background to white */ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 11 */ if (rp->display) pixDisplay(pix1, 100, 100); boxaDestroy(&boxa1); pixDestroy(&pix1); pixaDestroy(&pixa1); pixDestroy(&pixs); return regTestCleanup(rp); }
int main(int argc, char **argv) { char *filein; l_int32 i, n, count; BOX *box; BOXA *boxa; PIX *pixs, *pixd; PIXA *pixa; PIXCMAP *cmap; static char mainName[] = "cctest1"; if (argc != 2) return ERROR_INT(" Syntax: cctest1 filein", mainName, 1); filein = argv[1]; if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("pixs not made", mainName, 1); if (pixGetDepth(pixs) != 1) exit(ERROR_INT("pixs not 1 bpp", mainName, 1)); /* Test speed of pixCountConnComp() */ startTimer(); for (i = 0; i < NTIMES; i++) pixCountConnComp(pixs, 4, &count); fprintf(stderr, "Time to compute 4-cc: %6.3f sec\n", stopTimer()/NTIMES); fprintf(stderr, "Number of 4-cc: %d\n", count); startTimer(); for (i = 0; i < NTIMES; i++) pixCountConnComp(pixs, 8, &count); fprintf(stderr, "Time to compute 8-cc: %6.3f sec\n", stopTimer()/NTIMES); fprintf(stderr, "Number of 8-cc: %d\n", count); /* Test speed of pixConnComp(), with only boxa output */ startTimer(); for (i = 0; i < NTIMES; i++) { boxa = pixConnComp(pixs, NULL, 4); boxaDestroy(&boxa); } fprintf(stderr, "Time to compute 4-cc: %6.3f sec\n", stopTimer()/NTIMES); startTimer(); for (i = 0; i < NTIMES; i++) { boxa = pixConnComp(pixs, NULL, 8); boxaDestroy(&boxa); } fprintf(stderr, "Time to compute 8-cc: %6.3f sec\n", stopTimer()/NTIMES); /* Draw outline of each c.c. box */ boxa = pixConnComp(pixs, NULL, 4); n = boxaGetCount(boxa); fprintf(stderr, "Num 4-cc boxes: %d\n", n); for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); pixRenderBox(pixs, box, 3, L_FLIP_PIXELS); boxDestroy(&box); /* remember, clones need to be destroyed */ } pixDisplayWrite(pixs, 1); boxaDestroy(&boxa); /* Display each component as a random color in cmapped 8 bpp. * Background is color 0; it is set to white. */ boxa = pixConnComp(pixs, &pixa, 4); pixd = pixaDisplayRandomCmap(pixa, pixGetWidth(pixs), pixGetHeight(pixs)); cmap = pixGetColormap(pixd); pixcmapResetColor(cmap, 0, 255, 255, 255); /* reset background to white */ pixDisplay(pixd, 100, 100); pixDisplayWrite(pixd, 1); boxaDestroy(&boxa); pixDestroy(&pixd); pixaDestroy(&pixa); pixDestroy(&pixs); return 0; }
/*! * \brief pixThresholdByConnComp() * * \param[in] pixs depth > 1, colormap OK * \param[in] pixm [optional] 1 bpp mask giving region to ignore by setting * pixels to white; use NULL if no mask * \param[in] start, end, incr binarization threshold levels to test * \param[in] thresh48 threshold on normalized difference between the * numbers of 4 and 8 connected components * \param[in] threshdiff threshold on normalized difference between the * number of 4 cc at successive iterations * \param[out] pglobthresh [optional] best global threshold; 0 * if no threshold is found * \param[out] ppixd [optional] image thresholded to binary, or * null if no threshold is found * \param[in] debugflag 1 for plotted results * \return 0 if OK, 1 on error or if no threshold is found * * <pre> * Notes: * (1) This finds a global threshold based on connected components. * Although slow, it is reasonable to use it in a situation where * (a) the background in the image is relatively uniform, and * (b) the result will be fed to an OCR program that accepts 1 bpp * images and works best with easily segmented characters. * The reason for (b) is that this selects a threshold with a * minimum number of both broken characters and merged characters. * (2) If the pix has color, it is converted to gray using the * max component. * (3) Input 0 to use default values for any of these inputs: * %start, %end, %incr, %thresh48, %threshdiff. * (4) This approach can be understood as follows. When the * binarization threshold is varied, the numbers of c.c. identify * four regimes: * (a) For low thresholds, text is broken into small pieces, and * the number of c.c. is large, with the 4 c.c. significantly * exceeding the 8 c.c. * (b) As the threshold rises toward the optimum value, the text * characters coalesce and there is very little difference * between the numbers of 4 and 8 c.c, which both go * through a minimum. * (c) Above this, the image background gets noisy because some * pixels are(thresholded to foreground, and the numbers * of c.c. quickly increase, with the 4 c.c. significantly * larger than the 8 c.c. * (d) At even higher thresholds, the image background noise * coalesces as it becomes mostly foreground, and the * number of c.c. drops quickly. * (5) If there is no global threshold that distinguishes foreground * text from background (e.g., weak text over a background that * has significant variation and/or bleedthrough), this returns 1, * which the caller should check. * </pre> */ l_int32 pixThresholdByConnComp(PIX *pixs, PIX *pixm, l_int32 start, l_int32 end, l_int32 incr, l_float32 thresh48, l_float32 threshdiff, l_int32 *pglobthresh, PIX **ppixd, l_int32 debugflag) { l_int32 i, thresh, n, n4, n8, mincounts, found, globthresh; l_float32 count4, count8, firstcount4, prevcount4, diff48, diff4; GPLOT *gplot; NUMA *na4, *na8; PIX *pix1, *pix2, *pix3; PROCNAME("pixThresholdByConnComp"); if (pglobthresh) *pglobthresh = 0; if (ppixd) *ppixd = NULL; if (!pixs || pixGetDepth(pixs) == 1) return ERROR_INT("pixs undefined or 1 bpp", procName, 1); if (pixm && pixGetDepth(pixm) != 1) return ERROR_INT("pixm must be 1 bpp", procName, 1); /* Assign default values if requested */ if (start <= 0) start = 80; if (end <= 0) end = 200; if (incr <= 0) incr = 10; if (thresh48 <= 0.0) thresh48 = 0.01; if (threshdiff <= 0.0) threshdiff = 0.01; if (start > end) return ERROR_INT("invalid start,end", procName, 1); /* Make 8 bpp, using the max component if color. */ if (pixGetColormap(pixs)) pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC); else pix1 = pixClone(pixs); if (pixGetDepth(pix1) == 32) pix2 = pixConvertRGBToGrayMinMax(pix1, L_CHOOSE_MAX); else pix2 = pixConvertTo8(pix1, 0); pixDestroy(&pix1); /* Mask out any non-text regions. Do this in-place, because pix2 * can never be the same pix as pixs. */ if (pixm) pixSetMasked(pix2, pixm, 255); /* Make sure there are enough components to get a valid signal */ pix3 = pixConvertTo1(pix2, start); pixCountConnComp(pix3, 4, &n4); pixDestroy(&pix3); mincounts = 500; if (n4 < mincounts) { L_INFO("Insufficient component count: %d\n", procName, n4); pixDestroy(&pix2); return 1; } /* Compute the c.c. data */ na4 = numaCreate(0); na8 = numaCreate(0); numaSetParameters(na4, start, incr); numaSetParameters(na8, start, incr); for (thresh = start, i = 0; thresh <= end; thresh += incr, i++) { pix3 = pixConvertTo1(pix2, thresh); pixCountConnComp(pix3, 4, &n4); pixCountConnComp(pix3, 8, &n8); numaAddNumber(na4, n4); numaAddNumber(na8, n8); pixDestroy(&pix3); } if (debugflag) { gplot = gplotCreate("/tmp/threshroot", GPLOT_PNG, "number of cc vs. threshold", "threshold", "number of cc"); gplotAddPlot(gplot, NULL, na4, GPLOT_LINES, "plot 4cc"); gplotAddPlot(gplot, NULL, na8, GPLOT_LINES, "plot 8cc"); gplotMakeOutput(gplot); gplotDestroy(&gplot); } n = numaGetCount(na4); found = FALSE; for (i = 0; i < n; i++) { if (i == 0) { numaGetFValue(na4, i, &firstcount4); prevcount4 = firstcount4; } else { numaGetFValue(na4, i, &count4); numaGetFValue(na8, i, &count8); diff48 = (count4 - count8) / firstcount4; diff4 = L_ABS(prevcount4 - count4) / firstcount4; if (debugflag) { fprintf(stderr, "diff48 = %7.3f, diff4 = %7.3f\n", diff48, diff4); } if (diff48 < thresh48 && diff4 < threshdiff) { found = TRUE; break; } prevcount4 = count4; } } numaDestroy(&na4); numaDestroy(&na8); if (found) { globthresh = start + i * incr; if (pglobthresh) *pglobthresh = globthresh; if (ppixd) { *ppixd = pixConvertTo1(pix2, globthresh); pixCopyResolution(*ppixd, pixs); } if (debugflag) fprintf(stderr, "global threshold = %d\n", globthresh); pixDestroy(&pix2); return 0; } if (debugflag) fprintf(stderr, "no global threshold found\n"); pixDestroy(&pix2); return 1; }