/*! * pixOtsuAdaptiveThreshold() * * Input: pixs (8 bpp) * sx, sy (desired tile dimensions; actual size may vary) * smoothx, smoothy (half-width of convolution kernel applied to * threshold array: use 0 for no smoothing) * scorefract (fraction of the max Otsu score; typ. 0.1; * use 0.0 for standard Otsu) * &pixth (<optional return> array of threshold values * found for each tile) * &pixd (<optional return> thresholded input pixs, based on * the threshold array) * Return: 0 if OK, 1 on error * * Notes: * (1) The Otsu method finds a single global threshold for an image. * This function allows a locally adapted threshold to be * found for each tile into which the image is broken up. * (2) The array of threshold values, one for each tile, constitutes * a highly downscaled image. This array is optionally * smoothed using a convolution. The full width and height of the * convolution kernel are (2 * @smoothx + 1) and (2 * @smoothy + 1). * (3) The minimum tile dimension allowed is 16. If such small * tiles are used, it is recommended to use smoothing, because * without smoothing, each small tile determines the splitting * threshold independently. A tile that is entirely in the * image bg will then hallucinate fg, resulting in a very noisy * binarization. The smoothing should be large enough that no * tile is only influenced by one type (fg or bg) of pixels, * because it will force a split of its pixels. * (4) To get a single global threshold for the entire image, use * input values of @sx and @sy that are larger than the image. * For this situation, the smoothing parameters are ignored. * (5) The threshold values partition the image pixels into two classes: * one whose values are less than the threshold and another * whose values are greater than or equal to the threshold. * This is the same use of 'threshold' as in pixThresholdToBinary(). * (6) The scorefract is the fraction of the maximum Otsu score, which * is used to determine the range over which the histogram minimum * is searched. See numaSplitDistribution() for details on the * underlying method of choosing a threshold. * (7) This uses enables a modified version of the Otsu criterion for * splitting the distribution of pixels in each tile into a * fg and bg part. The modification consists of searching for * a minimum in the histogram over a range of pixel values where * the Otsu score is within a defined fraction, @scorefract, * of the max score. To get the original Otsu algorithm, set * @scorefract == 0. */ l_int32 pixOtsuAdaptiveThreshold(PIX *pixs, l_int32 sx, l_int32 sy, l_int32 smoothx, l_int32 smoothy, l_float32 scorefract, PIX **ppixth, PIX **ppixd) { l_int32 w, h, nx, ny, i, j, thresh; l_uint32 val; PIX *pixt, *pixb, *pixthresh, *pixth, *pixd; PIXTILING *pt; PROCNAME("pixOtsuAdaptiveThreshold"); if (!ppixth && !ppixd){ return ERROR_INT("neither &pixth nor &pixd defined", procName, 1); LOGE("neither &pixth nor &pixd defined"); } if (ppixth) *ppixth = NULL; if (ppixd) *ppixd = NULL; if (!pixs || pixGetDepth(pixs) != 8){ return ERROR_INT("pixs not defined or not 8 bpp", procName, 1); LOGE("pixs not defined or not 8 bpp"); } if (sx < 16 || sy < 16){ return ERROR_INT("sx and sy must be >= 16", procName, 1); LOGE("sx and sy must be >= 16"); } /* Compute the threshold array for the tiles */ pixGetDimensions(pixs, &w, &h, NULL); nx = L_MAX(1, w / sx); ny = L_MAX(1, h / sy); smoothx = L_MIN(smoothx, (nx - 1) / 2); smoothy = L_MIN(smoothy, (ny - 1) / 2); pt = pixTilingCreate(pixs, nx, ny, 0, 0, 0, 0); pixthresh = pixCreate(nx, ny, 8); for (i = 0; i < ny; i++) { for (j = 0; j < nx; j++) { pixt = pixTilingGetTile(pt, i, j); pixSplitDistributionFgBg(pixt, scorefract, 1, &thresh, NULL, NULL, 0); pixSetPixel(pixthresh, j, i, thresh); /* see note (4) */ pixDestroy(&pixt); } } /* Optionally smooth the threshold array */ if (smoothx > 0 || smoothy > 0) pixth = pixBlockconv(pixthresh, smoothx, smoothy); else pixth = pixClone(pixthresh); pixDestroy(&pixthresh); /* Optionally apply the threshold array to binarize pixs */ if (ppixd) { pixd = pixCreate(w, h, 1); for (i = 0; i < ny; i++) { for (j = 0; j < nx; j++) { pixt = pixTilingGetTile(pt, i, j); pixGetPixel(pixth, j, i, &val); pixb = pixThresholdToBinary(pixt, val); pixTilingPaintTile(pixd, i, j, pixb, pt); pixDestroy(&pixt); pixDestroy(&pixb); } } *ppixd = pixd; } if (ppixth) *ppixth = pixth; else pixDestroy(&pixth); pixTilingDestroy(&pt); return 0; }
int main(int argc, char **argv) { char textstr[256]; l_int32 i, thresh, fgval, bgval; l_float32 scorefract; L_BMF *bmf; PIX *pixs, *pixb, *pixb2, *pixb3, *pixg, *pixp, *pixt1, *pixt2; PIXA *pixa; pixs = pixRead("1555-7.jpg"); pixg = pixConvertTo8(pixs, 0); bmf = bmfCreate("fonts", 8); for (i = 0; i < 3; i++) { pixa = pixaCreate(3); scorefract = 0.1 * i; pixOtsuAdaptiveThreshold(pixg, 2000, 2000, 0, 0, scorefract, NULL, &pixb); pixSaveTiledOutline(pixb, pixa, 0.5, 1, 20, 2, 32); pixSplitDistributionFgBg(pixg, scorefract, 1, &thresh, &fgval, &bgval, 1); fprintf(stderr, "thresh = %d, fgval = %d, bgval = %d\n", thresh, fgval, bgval); /* Give gnuplot time to write out the plot */ #ifndef _WIN32 sleep(1); #else Sleep(1000); #endif /* _WIN32 */ pixp = pixRead("/tmp/histplot.png"); pixSaveTiled(pixp, pixa, 1.0, 0, 20, 1); pixt1 = pixaDisplay(pixa, 0, 0); snprintf(textstr, sizeof(textstr), "Scorefract = %3.1f ........... Thresh = %d", scorefract, thresh); pixt2 = pixAddSingleTextblock(pixt1, bmf, textstr, 0x00ff0000, L_ADD_BELOW, NULL); pixDisplay(pixt2, 100, 100); snprintf(textstr, sizeof(textstr), "/tmp/otsu.%d.png", i); pixWrite(textstr, pixt2, IFF_PNG); pixDestroy(&pixb); pixDestroy(&pixp); pixDestroy(&pixt1); pixDestroy(&pixt2); pixaDestroy(&pixa); } pixa = pixaCreate(2); for (i = 0; i < 2; i++) { scorefract = 0.1 * i; pixOtsuAdaptiveThreshold(pixg, 300, 300, 0, 0, scorefract, NULL, &pixb); pixb2 = pixAddBlackOrWhiteBorder(pixb, 2, 2, 2, 2, L_GET_BLACK_VAL); snprintf(textstr, sizeof(textstr), "Scorefract = %3.1f", scorefract); pixb3 = pixAddSingleTextblock(pixb2, bmf, textstr, 1, L_ADD_BELOW, NULL); pixSaveTiled(pixb3, pixa, 2, (i + 1) % 1, 20, 32); pixDestroy(&pixb); pixDestroy(&pixb2); } pixb = pixaDisplay(pixa, 0, 0); pixWrite("/tmp/otsu-tiled.jpg", pixb, IFF_PNG); pixDestroy(&pixb); pixaDestroy(&pixa); bmfDestroy(&bmf); pixDestroy(&pixs); pixDestroy(&pixg); return 0; }