| /*====================================================================* |
| - Copyright (C) 2001 Leptonica. All rights reserved. |
| - This software is distributed in the hope that it will be |
| - useful, but with NO WARRANTY OF ANY KIND. |
| - No author or distributor accepts responsibility to anyone for the |
| - consequences of using this software, or for whether it serves any |
| - particular purpose or works at all, unless he or she says so in |
| - writing. Everyone is granted permission to copy, modify and |
| - redistribute this source code, for commercial or non-commercial |
| - purposes, with the following restrictions: (1) the origin of this |
| - source code must not be misrepresented; (2) modified versions must |
| - be plainly marked as such; and (3) this notice may not be removed |
| - or altered from any source or modified source distribution. |
| *====================================================================*/ |
| |
| /* |
| * binarize.c |
| * |
| * =================================================================== |
| * Image binarization algorithms are found in: |
| * grayquant.c: standard, simple, general grayscale quantization |
| * adaptmap.c: local adaptive; mostly gray-to-gray in preparation |
| * for binarization |
| * binarize.c: special binarization methods, locally adaptive. |
| * =================================================================== |
| * |
| * Adaptive Otsu-based thresholding |
| * l_int32 pixOtsuAdaptiveThreshold() 8 bpp |
| * |
| * Otsu thresholding on adaptive background normalization |
| * PIX *pixOtsuThreshOnBackgroundNorm() 8 bpp |
| * |
| * Masking and Otsu estimate on adaptive background normalization |
| * PIX *pixMaskedThreshOnBackgroundNorm() 8 bpp |
| * |
| * Sauvola local thresholding |
| * l_int32 pixSauvolaBinarizeTiled() |
| * l_int32 pixSauvolaBinarize() |
| * PIX *pixSauvolaGetThreshold() |
| * PIX *pixApplyLocalThreshold(); |
| * |
| * Notes: |
| * (1) pixOtsuAdaptiveThreshold() computes a global threshold over each |
| * tile and performs the threshold operation, resulting in a |
| * binary image for each tile. These are stitched into the |
| * final result. |
| * (2) pixOtsuThreshOnBackgroundNorm() and |
| * pixMaskedThreshOnBackgroundNorm() are binarization functions |
| * that use background normalization with other techniques. |
| * (3) Sauvola binarization computes a local threshold based on |
| * the local average and square average. It takes two constants: |
| * the window size for the measurment at each pixel and a |
| * parameter that determines the amount of normalized local |
| * standard deviation to subtract from the local average value. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <math.h> |
| #include "allheaders.h" |
| |
| |
| /*------------------------------------------------------------------* |
| * Adaptive Otsu-based thresholding * |
| *------------------------------------------------------------------*/ |
| /*! |
| * 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) |
| * &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) 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. |
| * (4) 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. |
| * (5) This uses 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. |
| */ |
| 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, d, nx, ny, i, j, thresh; |
| l_uint32 val; |
| PIX *pixt, *pixb, *pixthresh, *pixth, *pixd; |
| PIXTILING *pt; |
| |
| PROCNAME("pixOtsuAdaptiveThreshold"); |
| |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| pixGetDimensions(pixs, &w, &h, &d); |
| if (d != 8) |
| return ERROR_INT("pixs not 8 bpp", procName, 1); |
| if (sx < 16 || sy < 16) |
| return ERROR_INT("sx and sy must be >= 16", procName, 1); |
| if (!ppixth && !ppixd) |
| return ERROR_INT("neither &pixth nor &pixd defined", procName, 1); |
| |
| /* Compute the threshold array for the tiles */ |
| 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); |
| 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; |
| } |
| |
| |
| |
| /*------------------------------------------------------------------* |
| * Otsu thresholding on adaptive background normalization * |
| *------------------------------------------------------------------*/ |
| /*! |
| * pixOtsuThreshOnBackgroundNorm() |
| * |
| * Input: pixs (8 bpp grayscale or 32 bpp rgb) |
| * 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) |
| * bgval (target bg val; typ. > 128) |
| * 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 does background normalization followed by Otsu |
| * thresholding. Otsu binarization attempts to split the |
| * image into two roughly equal sets of pixels, and it does |
| * a very poor job when there are large amounts of dark |
| * background. By doing a background normalization first, |
| * to get the background near 255, we remove this problem. |
| * Then we use a modified Otsu to estimate the best global |
| * threshold on the normalized image. |
| * (2) See pixBackgroundNorm() for meaning and typical values |
| * of input parameters. For a start, you can try: |
| * sx, sy = 10, 15 |
| * thresh = 100 |
| * mincount = 50 |
| * bgval = 255 |
| * smoothx, smoothy = 2 |
| */ |
| PIX * |
| pixOtsuThreshOnBackgroundNorm(PIX *pixs, |
| PIX *pixim, |
| l_int32 sx, |
| l_int32 sy, |
| l_int32 thresh, |
| l_int32 mincount, |
| l_int32 bgval, |
| l_int32 smoothx, |
| l_int32 smoothy, |
| l_float32 scorefract, |
| l_int32 *pthresh) |
| { |
| l_int32 w, h, d; |
| l_uint32 val; |
| PIX *pixn, *pixt, *pixd; |
| |
| PROCNAME("pixOtsuThreshOnBackgroundNorm"); |
| |
| if (pthresh) *pthresh = 0; |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| pixGetDimensions(pixs, &w, &h, &d); |
| if (d != 8 && d != 32) |
| return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", 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", procName); |
| mincount = (sx * sy) / 3; |
| } |
| |
| pixn = pixBackgroundNorm(pixs, pixim, NULL, sx, sy, thresh, |
| mincount, bgval, smoothx, smoothy); |
| if (!pixn) |
| return (PIX *)ERROR_PTR("pixn not made", procName, NULL); |
| |
| /* Just use 1 tile for a global threshold, which is stored |
| * as a single pixel in pixt. */ |
| pixOtsuAdaptiveThreshold(pixn, w, h, 0, 0, scorefract, &pixt, &pixd); |
| pixDestroy(&pixn); |
| |
| if (pixt && pthresh) { |
| pixGetPixel(pixt, 0, 0, &val); |
| *pthresh = val; |
| } |
| pixDestroy(&pixt); |
| |
| if (!pixd) |
| return (PIX *)ERROR_PTR("pixd not made", procName, NULL); |
| else |
| return pixd; |
| } |
| |
| |
| |
| /*----------------------------------------------------------------------* |
| * Masking and Otsu estimate on adaptive background normalization * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixMaskedThreshOnBackgroundNorm() |
| * |
| * Input: pixs (8 bpp grayscale or 32 bpp rgb) |
| * 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, d; |
| l_uint32 val; |
| PIX *pixn, *pixm, *pixd, *pixt1, *pixt2, *pixt3, *pixt4; |
| |
| PROCNAME("pixMaskedThreshOnBackgroundNorm"); |
| |
| if (pthresh) *pthresh = 0; |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| pixGetDimensions(pixs, &w, &h, &d); |
| if (d != 8 && d != 32) |
| return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", 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", 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. */ |
| 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; |
| } |
| |
| |
| /*----------------------------------------------------------------------* |
| * Sauvola binarization * |
| *----------------------------------------------------------------------*/ |
| /*! |
| * pixSauvolaBinarizeTiled() |
| * |
| * Input: pixs (8 bpp grayscale, not cmapped) |
| * whsize (window half-width for measuring local statistics) |
| * factor (factor for reducing threshold due to variance; >= 0) |
| * nx, ny (subdivision into tiles; >= 1) |
| * &pixth (<optional return> Sauvola threshold values) |
| * &pixd (<optional return> thresholded image) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) The window width and height are 2 * @whsize + 1. The minimum |
| * value for @whsize is 2; typically it is >= 7.. |
| * (2) For nx == ny == 1, this defaults to pixSauvolaBinarize(). |
| * (3) Why a tiled version? |
| * (a) Because the mean value accumulator is a uint32, overflow |
| * can occur for an image with more than 16M pixels. |
| * (b) The mean value accumulator array for 16M pixels is 64 MB. |
| * The mean square accumulator array for 16M pixels is 128 MB. |
| * Using tiles reduces the size of these arrays. |
| * (c) Each tile can be processed independently, in parallel, |
| * on a multicore processor. |
| * (4) The Sauvola threshold is determined from the formula: |
| * t = m * (1 - k * (1 - s / 128)) |
| * See pixSauvolaBinarize() for details. |
| */ |
| l_int32 |
| pixSauvolaBinarizeTiled(PIX *pixs, |
| l_int32 whsize, |
| l_float32 factor, |
| l_int32 nx, |
| l_int32 ny, |
| PIX **ppixth, |
| PIX **ppixd) |
| { |
| l_int32 i, j, w, h, xrat, yrat; |
| PIX *pixth, *pixd, *tileth, *tiled, *pixt; |
| PIX **ptileth, **ptiled; |
| PIXTILING *pt; |
| |
| PROCNAME("pixSauvolaBinarizeTiled"); |
| |
| if (!ppixth && !ppixd) |
| return ERROR_INT("no outputs", procName, 1); |
| if (ppixth) *ppixth = NULL; |
| if (ppixd) *ppixd = NULL; |
| if (!pixs || pixGetDepth(pixs) != 8) |
| return ERROR_INT("pixs undefined or not 8 bpp", procName, 1); |
| if (pixGetColormap(pixs)) |
| return ERROR_INT("pixs is cmapped", procName, 1); |
| pixGetDimensions(pixs, &w, &h, NULL); |
| if (whsize < 2) |
| return ERROR_INT("whsize must be >= 2", procName, 1); |
| if (w < 2 * whsize + 3 || h < 2 * whsize + 3) |
| return ERROR_INT("whsize too large for image", procName, 1); |
| if (factor < 0.0) |
| return ERROR_INT("factor must be >= 0", procName, 1); |
| |
| if (nx <= 1 && ny <= 1) |
| return pixSauvolaBinarize(pixs, whsize, factor, 1, NULL, NULL, |
| ppixth, ppixd); |
| |
| /* Test to see if the tiles are too small. The required |
| * condition is that the tile dimensions must be at least |
| * (whsize + 2) x (whsize + 2). */ |
| xrat = w / nx; |
| yrat = h / ny; |
| if (xrat < whsize + 2) { |
| nx = w / (whsize + 2); |
| L_WARNING_INT("tile width too small; nx reduced to %d", procName, nx); |
| } |
| if (yrat < whsize + 2) { |
| ny = h / (whsize + 2); |
| L_WARNING_INT("tile height too small; ny reduced to %d", procName, ny); |
| } |
| if (nx <= 1 && ny <= 1) |
| return pixSauvolaBinarize(pixs, whsize, factor, 1, NULL, NULL, |
| ppixth, ppixd); |
| |
| /* We can use pixtiling for painting both outputs, if requested */ |
| if (ppixth) { |
| pixth = pixCreateNoInit(w, h, 8); |
| *ppixth = pixth; |
| } |
| if (ppixd) { |
| pixd = pixCreateNoInit(w, h, 1); |
| *ppixd = pixd; |
| } |
| pt = pixTilingCreate(pixs, nx, ny, 0, 0, whsize + 1, whsize + 1); |
| pixTilingNoStripOnPaint(pt); /* pixSauvolaBinarize() does the stripping */ |
| |
| for (i = 0; i < ny; i++) { |
| for (j = 0; j < nx; j++) { |
| pixt = pixTilingGetTile(pt, i, j); |
| ptileth = (ppixth) ? &tileth : NULL; |
| ptiled = (ppixd) ? &tiled : NULL; |
| pixSauvolaBinarize(pixt, whsize, factor, 0, NULL, NULL, |
| ptileth, ptiled); |
| if (ppixth) { /* do not strip */ |
| pixTilingPaintTile(pixth, i, j, tileth, pt); |
| pixDestroy(&tileth); |
| } |
| if (ppixd) { |
| pixTilingPaintTile(pixd, i, j, tiled, pt); |
| pixDestroy(&tiled); |
| } |
| pixDestroy(&pixt); |
| } |
| } |
| |
| pixTilingDestroy(&pt); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixSauvolaBinarize() |
| * |
| * Input: pixs (8 bpp grayscale) |
| * whsize (window half-width for measuring local statistics) |
| * factor (factor for reducing threshold due to variance; >= 0) |
| * addborder (1 to add border of width (@whsize + 1) on all sides) |
| * &pixm (<optional return> local mean values) |
| * &pixsd (<optional return> local standard deviation values) |
| * &pixth (<optional return> threshold values) |
| * &pixd (<optional return> thresholded image) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) The window width and height are 2 * @whsize + 1. The minimum |
| * value for @whsize is 2; typically it is >= 7.. |
| * (2) The local statistics, measured over the window, are the |
| * average and standard deviation. |
| * (3) The measurements of the mean and standard deviation are |
| * performed inside a border of (@whsize + 1) pixels. If pixs does |
| * not have these added border pixels, use @addborder = 1 to add |
| * it here; otherwise use @addborder = 0. |
| * (4) The Sauvola threshold is determined from the formula: |
| * t = m * (1 - k * (1 - s / 128)) |
| * where: |
| * t = local threshold |
| * m = local mean |
| * k = @factor (>= 0) [ typ. 0.35 ] |
| * s = local standard deviation, which is maximized at |
| * 127.5 when half the samples are 0 and half are 255. |
| * (5) The basic idea of Niblack and Sauvola binarization is that |
| * the local threshold should be less than the median value, |
| * and the larger the variance, the closer to the median |
| * it should be chosen. Typical values for k are between |
| * 0.2 and 0.5. |
| */ |
| l_int32 |
| pixSauvolaBinarize(PIX *pixs, |
| l_int32 whsize, |
| l_float32 factor, |
| l_int32 addborder, |
| PIX **ppixm, |
| PIX **ppixsd, |
| PIX **ppixth, |
| PIX **ppixd) |
| { |
| l_int32 w, h; |
| PIX *pixg, *pixsc, *pixm, *pixms, *pixth, *pixd; |
| |
| PROCNAME("pixSauvolaBinarize"); |
| |
| |
| if (!ppixm && !ppixsd && !ppixth && !ppixd) |
| return ERROR_INT("no outputs", procName, 1); |
| if (ppixm) *ppixm = NULL; |
| if (ppixsd) *ppixsd = NULL; |
| if (ppixth) *ppixth = NULL; |
| if (ppixd) *ppixd = NULL; |
| if (!pixs || pixGetDepth(pixs) != 8) |
| return ERROR_INT("pixs undefined or not 8 bpp", procName, 1); |
| if (pixGetColormap(pixs)) |
| return ERROR_INT("pixs is cmapped", procName, 1); |
| pixGetDimensions(pixs, &w, &h, NULL); |
| if (whsize < 2) |
| return ERROR_INT("whsize must be >= 2", procName, 1); |
| if (w < 2 * whsize + 3 || h < 2 * whsize + 3) |
| return ERROR_INT("whsize too large for image", procName, 1); |
| if (factor < 0.0) |
| return ERROR_INT("factor must be >= 0", procName, 1); |
| |
| if (addborder) { |
| pixg = pixAddMirroredBorder(pixs, whsize + 1, whsize + 1, |
| whsize + 1, whsize + 1); |
| pixsc = pixClone(pixs); |
| } |
| else { |
| pixg = pixClone(pixs); |
| pixsc = pixRemoveBorder(pixs, whsize + 1); |
| } |
| if (!pixg || !pixsc) |
| return ERROR_INT("pixg and pixsc not made", procName, 1); |
| |
| /* All these functions strip off the border pixels. */ |
| if (ppixm || ppixth || ppixd) |
| pixm = pixWindowedMean(pixg, whsize, whsize, 1); |
| if (ppixsd || ppixth || ppixd) |
| pixms = pixWindowedMeanSquare(pixg, whsize); |
| if (ppixth || ppixd) |
| pixth = pixSauvolaGetThreshold(pixm, pixms, factor, ppixsd); |
| if (ppixd) |
| pixd = pixApplyLocalThreshold(pixsc, pixth, 1); |
| |
| if (ppixm) |
| *ppixm = pixm; |
| else |
| pixDestroy(&pixm); |
| pixDestroy(&pixms); |
| if (ppixth) |
| *ppixth = pixth; |
| else |
| pixDestroy(&pixth); |
| if (ppixd) |
| *ppixd = pixd; |
| else |
| pixDestroy(&pixd); |
| pixDestroy(&pixg); |
| pixDestroy(&pixsc); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixSauvolaGetThreshold() |
| * |
| * Input: pixm (8 bpp grayscale) |
| * pixms (32 bpp) |
| * factor (factor for reducing threshold due to variance; >= 0) |
| * &pixsd (<optional return> local standard deviation) |
| * Return: pixd (8 bpp, sauvola threshold values), or null on error |
| * |
| * Notes: |
| * (1) The Sauvola threshold is determined from the formula: |
| * t = m * (1 - k * (1 - s / 128)) |
| * where: |
| * t = local threshold |
| * m = local mean |
| * k = @factor (>= 0) [ typ. 0.35 ] |
| * s = local standard deviation, which is maximized at |
| * 127.5 when half the samples are 0 and half are 255. |
| * (2) See pixSauvolaBinarize() for other details. |
| * (3) Important definitions and relations for computing averages: |
| * v == pixel value |
| * E(p) == expected value of p == average of p over some pixel set |
| * S(v) == square of v == v * v |
| * mv == E(v) == expected pixel value == mean value |
| * ms == E(S(v)) == expected square of pixel values |
| * == mean square value |
| * var == variance == expected square of deviation from mean |
| * == E(S(v - mv)) = E(S(v) - 2 * S(v * mv) + S(mv)) |
| * = E(S(v)) - S(mv) |
| * = ms - mv * mv |
| * s == standard deviation = sqrt(var) |
| * So for evaluating the standard deviation in the Sauvola |
| * threshold, we take |
| * s = sqrt(ms - mv * mv) |
| */ |
| PIX * |
| pixSauvolaGetThreshold(PIX *pixm, |
| PIX *pixms, |
| l_float32 factor, |
| PIX **ppixsd) |
| { |
| l_int32 i, j, w, h, tabsize, wplm, wplms, wplsd, wpld, usetab; |
| l_int32 mv, ms, var, thresh; |
| l_uint32 *datam, *datams, *datasd, *datad; |
| l_uint32 *linem, *linems, *linesd, *lined; |
| l_float32 sd; |
| l_float32 *tab; /* of 2^16 square roots */ |
| PIX *pixsd, *pixd; |
| |
| PROCNAME("pixSauvolaGetThreshold"); |
| |
| if (ppixsd) *ppixsd = NULL; |
| if (!pixm || pixGetDepth(pixm) != 8) |
| return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL); |
| if (!pixms || pixGetDepth(pixms) != 32) |
| return (PIX *)ERROR_PTR("pixms undefined or not 32 bpp", |
| procName, NULL); |
| if (factor < 0.0) |
| return (PIX *)ERROR_PTR("factor must be >= 0", procName, NULL); |
| |
| /* Only make a table of 2^16 square roots if there |
| * are enough pixels to justify it. */ |
| pixGetDimensions(pixm, &w, &h, NULL); |
| usetab = (w * h > 100000) ? 1 : 0; |
| if (usetab) { |
| tabsize = 1 << 16; |
| tab = (l_float32 *)CALLOC(tabsize, sizeof(l_float32)); |
| for (i = 0; i < tabsize; i++) |
| tab[i] = (l_float32)sqrt(i); |
| } |
| |
| pixd = pixCreate(w, h, 8); |
| if (ppixsd) { |
| pixsd = pixCreate(w, h, 8); |
| *ppixsd = pixsd; |
| } |
| datam = pixGetData(pixm); |
| datams = pixGetData(pixms); |
| if (ppixsd) datasd = pixGetData(pixsd); |
| datad = pixGetData(pixd); |
| wplm = pixGetWpl(pixm); |
| wplms = pixGetWpl(pixms); |
| if (ppixsd) wplsd = pixGetWpl(pixsd); |
| wpld = pixGetWpl(pixd); |
| for (i = 0; i < h; i++) { |
| linem = datam + i * wplm; |
| linems = datams + i * wplms; |
| if (ppixsd) linesd = datasd + i * wplsd; |
| lined = datad + i * wpld; |
| for (j = 0; j < w; j++) { |
| mv = GET_DATA_BYTE(linem, j); |
| ms = linems[j]; |
| var = ms - mv * mv; |
| if (usetab) |
| sd = tab[var]; |
| else |
| sd = (l_float32)sqrt(var); |
| if (ppixsd) SET_DATA_BYTE(linesd, j, (l_int32)sd); |
| thresh = (l_int32)(mv * (1.0 - factor * (1.0 - sd / 128.))); |
| SET_DATA_BYTE(lined, j, thresh); |
| } |
| } |
| |
| if (usetab) FREE(tab); |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixApplyLocalThreshold() |
| * |
| * Input: pixs (8 bpp grayscale) |
| * pixth (8 bpp array of local thresholds) |
| * redfactor ( ... ) |
| * Return: pixd (1 bpp, thresholded image), or null on error |
| */ |
| PIX * |
| pixApplyLocalThreshold(PIX *pixs, |
| PIX *pixth, |
| l_int32 redfactor) |
| { |
| l_int32 i, j, w, h, wpls, wplt, wpld, vals, valt; |
| l_uint32 *datas, *datat, *datad, *lines, *linet, *lined; |
| PIX *pixd; |
| |
| PROCNAME("pixApplyLocalThreshold"); |
| |
| if (!pixs || pixGetDepth(pixs) != 8) |
| return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL); |
| if (!pixth || pixGetDepth(pixth) != 8) |
| return (PIX *)ERROR_PTR("pixth undefined or not 8 bpp", procName, NULL); |
| |
| pixGetDimensions(pixs, &w, &h, NULL); |
| pixd = pixCreate(w, h, 1); |
| datas = pixGetData(pixs); |
| datat = pixGetData(pixth); |
| datad = pixGetData(pixd); |
| wpls = pixGetWpl(pixs); |
| wplt = pixGetWpl(pixth); |
| wpld = pixGetWpl(pixd); |
| for (i = 0; i < h; i++) { |
| lines = datas + i * wpls; |
| linet = datat + i * wplt; |
| lined = datad + i * wpld; |
| for (j = 0; j < w; j++) { |
| vals = GET_DATA_BYTE(lines, j); |
| valt = GET_DATA_BYTE(linet, j); |
| if (vals < valt) |
| SET_DATA_BIT(lined, j); |
| } |
| } |
| |
| return pixd; |
| } |
| |
| |