| /*====================================================================* |
| - 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. |
| *====================================================================*/ |
| |
| /* |
| * adaptmap.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 background normalization (top-level functions) |
| * PIX *pixBackgroundNormSimple() 8 and 32 bpp |
| * PIX *pixBackgroundNorm() 8 and 32 bpp |
| * PIX *pixBackgroundNormMorph() 8 and 32 bpp |
| * |
| * Arrays of inverted background values for normalization (16 bpp) |
| * l_int32 pixBackgroundNormGrayArray() 8 bpp input |
| * l_int32 pixBackgroundNormRGBArrays() 32 bpp input |
| * l_int32 pixBackgroundNormGrayArrayMorph() 8 bpp input |
| * l_int32 pixBackgroundNormRGBArraysMorph() 32 bpp input |
| * |
| * Measurement of local background |
| * l_int32 pixGetBackgroundGrayMap() 8 bpp |
| * l_int32 pixGetBackgroundRGBMap() 32 bpp |
| * l_int32 pixGetBackgroundGrayMapMorph() 8 bpp |
| * l_int32 pixGetBackgroundRGBMapMorph() 32 bpp |
| * l_int32 pixFillMapHoles() |
| * PIX *pixExtendByReplication() 8 bpp |
| * l_int32 pixSmoothConnectedRegions() 8 bpp |
| * |
| * Measurement of local foreground |
| * l_int32 pixGetForegroundGrayMap() 8 bpp |
| * |
| * Generate inverted background map for each component |
| * PIX *pixGetInvBackgroundMap() 16 bpp |
| * |
| * Apply inverse background map to image |
| * PIX *pixApplyInvBackgroundGrayMap() 8 bpp |
| * PIX *pixApplyInvBackgroundRGBMap() 32 bpp |
| * |
| * Apply variable map |
| * PIX *pixApplyVariableGrayMap() 8 bpp |
| * |
| * Non-adaptive (global) mapping |
| * PIX *pixGlobalNormRGB() 32 bpp or cmapped |
| * PIX *pixGlobalNormNoSatRGB() 32 bpp |
| * |
| * Adaptive threshold spread normalization |
| * l_int32 pixThresholdSpreadNorm() 8 bpp |
| * |
| * Adaptive background normalization (flexible adaptaption) |
| * PIX *pixBackgroundNormFlex() 8 bpp |
| * |
| * Adaptive contrast normalization |
| * PIX *pixContrastNorm() 8 bpp |
| * l_int32 pixMinMaxTiles() |
| * l_int32 pixSetLowContrast() |
| * PIX *pixLinearTRCTiled() |
| * static l_int32 *iaaGetLinearTRC() |
| * |
| * Background normalization is done by generating a reduced map (or set |
| * of maps) representing the estimated background value of the |
| * input image, and using this to shift the pixel values so that |
| * this background value is set to some constant value. |
| * |
| * Specifically, normalization has 3 steps: |
| * (1) Generate a background map at a reduced scale. |
| * (2) Make the array of inverted background values by inverting |
| * the map. The result is an array of local multiplicative factors. |
| * (3) Apply this inverse background map to the image |
| * |
| * The inverse background arrays can be generated in two different ways here: |
| * (1) Remove the 'foreground' pixels and average over the remaining |
| * pixels in each tile. Propagate values into tiles where |
| * values have not been assigned, either because there was not |
| * enough background in the tile or because the tile is covered |
| * by a foreground region described by an image mask. |
| * After the background map is made, the inverse map is generated by |
| * smoothing over some number of adjacent tiles |
| * (block convolution) and then inverting. |
| * (2) Remove the foreground pixels using a morphological closing |
| * on a subsampled version of the image. Propagate values |
| * into pixels covered by an optional image mask. Invert the |
| * background map without preconditioning by convolutional smoothing. |
| * |
| * Note: Several of these functions make an implicit assumption about RGB |
| * component ordering. |
| * |
| * Other methods for adaptively normalizing the image are also given here. |
| * |
| * (1) pixThresholdSpreadNorm() computes a local threshold over the image |
| * and normalizes the input pixel values so that this computed threshold |
| * is a constant across the entire image. |
| * |
| * (2) pixContrastNorm() computes and applies a local TRC so that the |
| * local dynamic range is expanded to the full 8 bits, where the |
| * darkest pixels are mapped to 0 and the lightest to 255. This is |
| * useful for improving the appearance of pages with very light |
| * foreground or very dark background, and where the local TRC |
| * function doesn't change rapidly with position. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include "allheaders.h" |
| |
| |
| /* Default input parameters for pixBackgroundNormSimple() |
| * Note: |
| * (1) mincount must never exceed the tile area (width * height) |
| * (2) bgval must be sufficiently below 255 to avoid accidental |
| * saturation; otherwise it should be large to avoid |
| * shrinking the dynamic range |
| * (3) results should otherwise not be sensitive to these values |
| */ |
| static const l_int32 DEFAULT_TILE_WIDTH = 10; |
| static const l_int32 DEFAULT_TILE_HEIGHT = 15; |
| static const l_int32 DEFAULT_FG_THRESHOLD = 60; |
| static const l_int32 DEFAULT_MIN_COUNT = 40; |
| static const l_int32 DEFAULT_BG_VAL = 200; |
| static const l_int32 DEFAULT_X_SMOOTH_SIZE = 2; |
| static const l_int32 DEFAULT_Y_SMOOTH_SIZE = 1; |
| |
| static l_int32 *iaaGetLinearTRC(l_int32 **iaa, l_int32 diff); |
| |
| #ifndef NO_CONSOLE_IO |
| #define DEBUG_GLOBAL 0 |
| #endif /* ~NO_CONSOLE_IO */ |
| |
| |
| |
| /*------------------------------------------------------------------* |
| * Adaptive background normalization * |
| *------------------------------------------------------------------*/ |
| /*! |
| * pixBackgroundNormSimple() |
| * |
| * Input: pixs (8 bpp grayscale or 32 bpp rgb) |
| * pixim (<optional> 1 bpp 'image' mask; can be null) |
| * pixg (<optional> 8 bpp grayscale version; can be null) |
| * Return: pixd (8 bpp or 32 bpp rgb), or null on error |
| * |
| * Notes: |
| * (1) This is a simplified interface to pixBackgroundNorm(), |
| * where seven parameters are defaulted. |
| * (2) The input image is either grayscale or rgb. |
| * (3) See pixBackgroundNorm() for usage and function. |
| */ |
| PIX * |
| pixBackgroundNormSimple(PIX *pixs, |
| PIX *pixim, |
| PIX *pixg) |
| { |
| return pixBackgroundNorm(pixs, pixim, pixg, |
| DEFAULT_TILE_WIDTH, DEFAULT_TILE_HEIGHT, |
| DEFAULT_FG_THRESHOLD, DEFAULT_MIN_COUNT, |
| DEFAULT_BG_VAL, DEFAULT_X_SMOOTH_SIZE, |
| DEFAULT_Y_SMOOTH_SIZE); |
| } |
| |
| |
| /*! |
| * pixBackgroundNorm() |
| * |
| * Input: pixs (8 bpp grayscale or 32 bpp rgb) |
| * pixim (<optional> 1 bpp 'image' mask; can be null) |
| * pixg (<optional> 8 bpp grayscale version; 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) |
| * Return: pixd (8 bpp or 32 bpp rgb), or null on error |
| * |
| * Notes: |
| * (1) This is a top-level interface for normalizing the image intensity |
| * by mapping the image so that the background is near the input |
| * value 'bgval'. |
| * (2) The input image is either grayscale or rgb. |
| * (3) For each component in the input image, the background value |
| * in each tile is estimated using the values in the tile that |
| * are not part of the foreground, where the foreground is |
| * determined by the input 'thresh' argument. |
| * (4) An optional binary mask can be specified, with the foreground |
| * pixels typically over image regions. The resulting background |
| * map values will be determined by surrounding pixels that are |
| * not under the mask foreground. The origin (0,0) of this mask |
| * is assumed to be aligned with the origin of the input image. |
| * This binary mask must not fully cover pixs, because then there |
| * will be no pixels in the input image available to compute |
| * the background. |
| * (5) An optional grayscale version of the input pixs can be supplied. |
| * The only reason to do this is if the input is RGB and this |
| * grayscale version can be used elsewhere. If the input is RGB |
| * and this is not supplied, it is made internally using only |
| * the green component, and destroyed after use. |
| * (6) The dimensions of the pixel tile (sx, sy) give the amount by |
| * by which the map is reduced in size from the input image. |
| * (7) The threshold is used to binarize the input image, in order to |
| * locate the foreground components. If this is set too low, |
| * some actual foreground may be used to determine the maps; |
| * if set too high, there may not be enough background |
| * to determine the map values accurately. Typically, it's |
| * better to err by setting the threshold too high. |
| * (8) A 'mincount' threshold is a minimum count of pixels in a |
| * tile for which a background reading is made, in order for that |
| * pixel in the map to be valid. This number should perhaps be |
| * at least 1/3 the size of the tile. |
| * (9) A 'bgval' target background value for the normalized image. This |
| * should be at least 128. If set too close to 255, some |
| * clipping will occur in the result. |
| * (10) Two factors, 'smoothx' and 'smoothy', are input for smoothing |
| * the map. Each low-pass filter kernel dimension is |
| * is 2 * (smoothing factor) + 1, so a |
| * value of 0 means no smoothing. A value of 1 or 2 is recommended. |
| */ |
| PIX * |
| pixBackgroundNorm(PIX *pixs, |
| PIX *pixim, |
| PIX *pixg, |
| l_int32 sx, |
| l_int32 sy, |
| l_int32 thresh, |
| l_int32 mincount, |
| l_int32 bgval, |
| l_int32 smoothx, |
| l_int32 smoothy) |
| { |
| l_int32 d, allfg; |
| PIX *pixm, *pixmi, *pixd; |
| PIX *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi; |
| |
| PROCNAME("pixBackgroundNorm"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| d = pixGetDepth(pixs); |
| 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; |
| } |
| |
| /* If pixim exists, verify that it is not all foreground. */ |
| if (pixim) { |
| pixInvert(pixim, pixim); |
| pixZero(pixim, &allfg); |
| pixInvert(pixim, pixim); |
| if (allfg) |
| return (PIX *)ERROR_PTR("pixim all foreground", procName, NULL); |
| } |
| |
| pixd = NULL; |
| if (d == 8) { |
| pixm = NULL; |
| pixGetBackgroundGrayMap(pixs, pixim, sx, sy, thresh, mincount, &pixm); |
| if (!pixm) { |
| L_WARNING("map not made; returning a copy of the source", procName); |
| return pixCopy(NULL, pixs); |
| } |
| |
| pixmi = pixGetInvBackgroundMap(pixm, bgval, smoothx, smoothy); |
| if (!pixmi) |
| ERROR_PTR("pixmi not made", procName, NULL); |
| else |
| pixd = pixApplyInvBackgroundGrayMap(pixs, pixmi, sx, sy); |
| |
| pixDestroy(&pixm); |
| pixDestroy(&pixmi); |
| } |
| else { |
| pixmr = pixmg = pixmb = NULL; |
| pixGetBackgroundRGBMap(pixs, pixim, pixg, sx, sy, thresh, |
| mincount, &pixmr, &pixmg, &pixmb); |
| if (!pixmr || !pixmg || !pixmb) { |
| pixDestroy(&pixmr); |
| pixDestroy(&pixmg); |
| pixDestroy(&pixmb); |
| L_WARNING("map not made; returning a copy of the source", procName); |
| return pixCopy(NULL, pixs); |
| } |
| |
| pixmri = pixGetInvBackgroundMap(pixmr, bgval, smoothx, smoothy); |
| pixmgi = pixGetInvBackgroundMap(pixmg, bgval, smoothx, smoothy); |
| pixmbi = pixGetInvBackgroundMap(pixmb, bgval, smoothx, smoothy); |
| if (!pixmri || !pixmgi || !pixmbi) |
| ERROR_PTR("not all pixm*i are made", procName, NULL); |
| else |
| pixd = pixApplyInvBackgroundRGBMap(pixs, pixmri, pixmgi, pixmbi, |
| sx, sy); |
| |
| pixDestroy(&pixmr); |
| pixDestroy(&pixmg); |
| pixDestroy(&pixmb); |
| pixDestroy(&pixmri); |
| pixDestroy(&pixmgi); |
| pixDestroy(&pixmbi); |
| } |
| |
| if (!pixd) |
| ERROR_PTR("pixd not made", procName, NULL); |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixBackgroundNormMorph() |
| * |
| * Input: pixs (8 bpp grayscale or 32 bpp rgb) |
| * pixim (<optional> 1 bpp 'image' mask; can be null) |
| * reduction (at which morph closings are done; between 2 and 16) |
| * size (of square Sel for the closing; use an odd number) |
| * bgval (target bg val; typ. > 128) |
| * Return: pixd (8 bpp), or null on error |
| * |
| * Notes: |
| * (1) This is a top-level interface for normalizing the image intensity |
| * by mapping the image so that the background is near the input |
| * value 'bgval'. |
| * (2) The input image is either grayscale or rgb. |
| * (3) For each component in the input image, the background value |
| * is estimated using a grayscale closing; hence the 'Morph' |
| * in the function name. |
| * (4) An optional binary mask can be specified, with the foreground |
| * pixels typically over image regions. The resulting background |
| * map values will be determined by surrounding pixels that are |
| * not under the mask foreground. The origin (0,0) of this mask |
| * is assumed to be aligned with the origin of the input image. |
| * This binary mask must not fully cover pixs, because then there |
| * will be no pixels in the input image available to compute |
| * the background. |
| * (5) The map is computed at reduced size (given by 'reduction') |
| * from the input pixs and optional pixim. At this scale, |
| * pixs is closed to remove the background, using a square Sel |
| * of odd dimension. The product of reduction * size should be |
| * large enough to remove most of the text foreground. |
| * (6) No convolutional smoothing needs to be done on the map before |
| * inverting it. |
| * (7) A 'bgval' target background value for the normalized image. This |
| * should be at least 128. If set too close to 255, some |
| * clipping will occur in the result. |
| */ |
| PIX * |
| pixBackgroundNormMorph(PIX *pixs, |
| PIX *pixim, |
| l_int32 reduction, |
| l_int32 size, |
| l_int32 bgval) |
| { |
| l_int32 d, allfg; |
| PIX *pixm, *pixmi, *pixd; |
| PIX *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi; |
| |
| PROCNAME("pixBackgroundNormMorph"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| d = pixGetDepth(pixs); |
| if (d != 8 && d != 32) |
| return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL); |
| if (reduction < 2 || reduction > 16) |
| return (PIX *)ERROR_PTR("reduction must be between 2 and 16", |
| procName, NULL); |
| |
| /* If pixim exists, verify that it is not all foreground. */ |
| if (pixim) { |
| pixInvert(pixim, pixim); |
| pixZero(pixim, &allfg); |
| pixInvert(pixim, pixim); |
| if (allfg) |
| return (PIX *)ERROR_PTR("pixim all foreground", procName, NULL); |
| } |
| |
| pixd = NULL; |
| if (d == 8) { |
| pixGetBackgroundGrayMapMorph(pixs, pixim, reduction, size, &pixm); |
| if (!pixm) |
| return (PIX *)ERROR_PTR("pixm not made", procName, NULL); |
| pixmi = pixGetInvBackgroundMap(pixm, bgval, 0, 0); |
| if (!pixmi) |
| ERROR_PTR("pixmi not made", procName, NULL); |
| else |
| pixd = pixApplyInvBackgroundGrayMap(pixs, pixmi, |
| reduction, reduction); |
| pixDestroy(&pixm); |
| pixDestroy(&pixmi); |
| } |
| else { /* d == 32 */ |
| pixmr = pixmg = pixmb = NULL; |
| pixGetBackgroundRGBMapMorph(pixs, pixim, reduction, size, |
| &pixmr, &pixmg, &pixmb); |
| if (!pixmr || !pixmg || !pixmb) { |
| pixDestroy(&pixmr); |
| pixDestroy(&pixmg); |
| pixDestroy(&pixmb); |
| return (PIX *)ERROR_PTR("not all pixm*", procName, NULL); |
| } |
| |
| pixmri = pixGetInvBackgroundMap(pixmr, bgval, 0, 0); |
| pixmgi = pixGetInvBackgroundMap(pixmg, bgval, 0, 0); |
| pixmbi = pixGetInvBackgroundMap(pixmb, bgval, 0, 0); |
| if (!pixmri || !pixmgi || !pixmbi) |
| ERROR_PTR("not all pixm*i are made", procName, NULL); |
| else |
| pixd = pixApplyInvBackgroundRGBMap(pixs, pixmri, pixmgi, pixmbi, |
| reduction, reduction); |
| |
| pixDestroy(&pixmr); |
| pixDestroy(&pixmg); |
| pixDestroy(&pixmb); |
| pixDestroy(&pixmri); |
| pixDestroy(&pixmgi); |
| pixDestroy(&pixmbi); |
| } |
| |
| if (!pixd) |
| ERROR_PTR("pixd not made", procName, NULL); |
| return pixd; |
| } |
| |
| |
| /*-------------------------------------------------------------------------* |
| * Arrays of inverted background values for normalization * |
| *-------------------------------------------------------------------------* |
| * Notes for these four functions: * |
| * (1) They are useful if you need to save the actual mapping array. * |
| * (2) They could be used in the top-level functions but are * |
| * not because their use makes those functions less clear. * |
| * (3) Each component in the input pixs generates a 16 bpp pix array. * |
| *-------------------------------------------------------------------------*/ |
| /*! |
| * pixBackgroundNormGrayArray() |
| * |
| * Input: pixs (8 bpp grayscale) |
| * 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) |
| * &pixd (<return> 16 bpp array of inverted background value) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) See notes in pixBackgroundNorm(). |
| * (2) This returns a 16 bpp pix that can be used by |
| * pixApplyInvBackgroundGrayMap() to generate a normalized version |
| * of the input pixs. |
| */ |
| l_int32 |
| pixBackgroundNormGrayArray(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, |
| PIX **ppixd) |
| { |
| l_int32 allfg; |
| PIX *pixm; |
| |
| PROCNAME("pixBackgroundNormGrayArray"); |
| |
| if (!ppixd) |
| return ERROR_INT("&pixd not defined", procName, 1); |
| *ppixd = NULL; |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (pixGetDepth(pixs) != 8) |
| return ERROR_INT("pixs not 8 bpp", procName, 1); |
| if (pixim && pixGetDepth(pixim) != 1) |
| return ERROR_INT("pixim not 1 bpp", procName, 1); |
| if (sx < 4 || sy < 4) |
| return ERROR_INT("sx and sy must be >= 4", procName, 1); |
| if (mincount > sx * sy) { |
| L_WARNING("mincount too large for tile size", procName); |
| mincount = (sx * sy) / 3; |
| } |
| |
| /* If pixim exists, verify that it is not all foreground. */ |
| if (pixim) { |
| pixInvert(pixim, pixim); |
| pixZero(pixim, &allfg); |
| pixInvert(pixim, pixim); |
| if (allfg) |
| return ERROR_INT("pixim all foreground", procName, 1); |
| } |
| |
| pixGetBackgroundGrayMap(pixs, pixim, sx, sy, thresh, mincount, &pixm); |
| if (!pixm) |
| return ERROR_INT("pixm not made", procName, 1); |
| *ppixd = pixGetInvBackgroundMap(pixm, bgval, smoothx, smoothy); |
| pixDestroy(&pixm); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixBackgroundNormRGBArrays() |
| * |
| * Input: pixs (32 bpp rgb) |
| * pixim (<optional> 1 bpp 'image' mask; can be null) |
| * pixg (<optional> 8 bpp grayscale version; 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) |
| * &pixr (<return> 16 bpp array of inverted R background value) |
| * &pixg (<return> 16 bpp array of inverted G background value) |
| * &pixb (<return> 16 bpp array of inverted B background value) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) See notes in pixBackgroundNorm(). |
| * (2) This returns a set of three 16 bpp pix that can be used by |
| * pixApplyInvBackgroundGrayMap() to generate a normalized version |
| * of each component of the input pixs. |
| */ |
| l_int32 |
| pixBackgroundNormRGBArrays(PIX *pixs, |
| PIX *pixim, |
| PIX *pixg, |
| l_int32 sx, |
| l_int32 sy, |
| l_int32 thresh, |
| l_int32 mincount, |
| l_int32 bgval, |
| l_int32 smoothx, |
| l_int32 smoothy, |
| PIX **ppixr, |
| PIX **ppixg, |
| PIX **ppixb) |
| { |
| l_int32 allfg; |
| PIX *pixmr, *pixmg, *pixmb; |
| |
| PROCNAME("pixBackgroundNormRGBArrays"); |
| |
| if (!ppixr || !ppixg || !ppixb) |
| return ERROR_INT("&pixr, &pixg, &pixb not all defined", procName, 1); |
| *ppixr = *ppixg = *ppixb = NULL; |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (pixGetDepth(pixs) != 32) |
| return ERROR_INT("pixs not 32 bpp", procName, 1); |
| if (pixim && pixGetDepth(pixim) != 1) |
| return ERROR_INT("pixim not 1 bpp", procName, 1); |
| if (sx < 4 || sy < 4) |
| return ERROR_INT("sx and sy must be >= 4", procName, 1); |
| if (mincount > sx * sy) { |
| L_WARNING("mincount too large for tile size", procName); |
| mincount = (sx * sy) / 3; |
| } |
| |
| /* If pixim exists, verify that it is not all foreground. */ |
| if (pixim) { |
| pixInvert(pixim, pixim); |
| pixZero(pixim, &allfg); |
| pixInvert(pixim, pixim); |
| if (allfg) |
| return ERROR_INT("pixim all foreground", procName, 1); |
| } |
| |
| pixGetBackgroundRGBMap(pixs, pixim, pixg, sx, sy, thresh, mincount, |
| &pixmr, &pixmg, &pixmb); |
| if (!pixmr || !pixmg || !pixmb) { |
| pixDestroy(&pixmr); |
| pixDestroy(&pixmg); |
| pixDestroy(&pixmb); |
| return ERROR_INT("not all pixm* made", procName, 1); |
| } |
| |
| *ppixr = pixGetInvBackgroundMap(pixmr, bgval, smoothx, smoothy); |
| *ppixg = pixGetInvBackgroundMap(pixmg, bgval, smoothx, smoothy); |
| *ppixb = pixGetInvBackgroundMap(pixmb, bgval, smoothx, smoothy); |
| pixDestroy(&pixmr); |
| pixDestroy(&pixmg); |
| pixDestroy(&pixmb); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixBackgroundNormGrayArrayMorph() |
| * |
| * Input: pixs (8 bpp grayscale) |
| * pixim (<optional> 1 bpp 'image' mask; can be null) |
| * reduction (at which morph closings are done; between 2 and 16) |
| * size (of square Sel for the closing; use an odd number) |
| * bgval (target bg val; typ. > 128) |
| * &pixd (<return> 16 bpp array of inverted background value) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) See notes in pixBackgroundNormMorph(). |
| * (2) This returns a 16 bpp pix that can be used by |
| * pixApplyInvBackgroundGrayMap() to generate a normalized version |
| * of the input pixs. |
| */ |
| l_int32 |
| pixBackgroundNormGrayArrayMorph(PIX *pixs, |
| PIX *pixim, |
| l_int32 reduction, |
| l_int32 size, |
| l_int32 bgval, |
| PIX **ppixd) |
| { |
| l_int32 allfg; |
| PIX *pixm; |
| |
| PROCNAME("pixBackgroundNormGrayArrayMorph"); |
| |
| if (!ppixd) |
| return ERROR_INT("&pixd not defined", procName, 1); |
| *ppixd = NULL; |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (pixGetDepth(pixs) != 8) |
| return ERROR_INT("pixs not 8 bpp", procName, 1); |
| if (pixim && pixGetDepth(pixim) != 1) |
| return ERROR_INT("pixim not 1 bpp", procName, 1); |
| if (reduction < 2 || reduction > 16) |
| return ERROR_INT("reduction must be between 2 and 16", procName, 1); |
| |
| /* If pixim exists, verify that it is not all foreground. */ |
| if (pixim) { |
| pixInvert(pixim, pixim); |
| pixZero(pixim, &allfg); |
| pixInvert(pixim, pixim); |
| if (allfg) |
| return ERROR_INT("pixim all foreground", procName, 1); |
| } |
| |
| pixGetBackgroundGrayMapMorph(pixs, pixim, reduction, size, &pixm); |
| if (!pixm) |
| return ERROR_INT("pixm not made", procName, 1); |
| *ppixd = pixGetInvBackgroundMap(pixm, bgval, 0, 0); |
| pixDestroy(&pixm); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixBackgroundNormRGBArraysMorph() |
| * |
| * Input: pixs (32 bpp rgb) |
| * pixim (<optional> 1 bpp 'image' mask; can be null) |
| * reduction (at which morph closings are done; between 2 and 16) |
| * size (of square Sel for the closing; use an odd number) |
| * bgval (target bg val; typ. > 128) |
| * &pixr (<return> 16 bpp array of inverted R background value) |
| * &pixg (<return> 16 bpp array of inverted G background value) |
| * &pixb (<return> 16 bpp array of inverted B background value) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) See notes in pixBackgroundNormMorph(). |
| * (2) This returns a set of three 16 bpp pix that can be used by |
| * pixApplyInvBackgroundGrayMap() to generate a normalized version |
| * of each component of the input pixs. |
| */ |
| l_int32 |
| pixBackgroundNormRGBArraysMorph(PIX *pixs, |
| PIX *pixim, |
| l_int32 reduction, |
| l_int32 size, |
| l_int32 bgval, |
| PIX **ppixr, |
| PIX **ppixg, |
| PIX **ppixb) |
| { |
| l_int32 allfg; |
| PIX *pixmr, *pixmg, *pixmb; |
| |
| PROCNAME("pixBackgroundNormRGBArraysMorph"); |
| |
| if (!ppixr || !ppixg || !ppixb) |
| return ERROR_INT("&pixr, &pixg, &pixb not all defined", procName, 1); |
| *ppixr = *ppixg = *ppixb = NULL; |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (pixGetDepth(pixs) != 32) |
| return ERROR_INT("pixs not 32 bpp", procName, 1); |
| if (pixim && pixGetDepth(pixim) != 1) |
| return ERROR_INT("pixim not 1 bpp", procName, 1); |
| if (reduction < 2 || reduction > 16) |
| return ERROR_INT("reduction must be between 2 and 16", procName, 1); |
| |
| /* If pixim exists, verify that it is not all foreground. */ |
| if (pixim) { |
| pixInvert(pixim, pixim); |
| pixZero(pixim, &allfg); |
| pixInvert(pixim, pixim); |
| if (allfg) |
| return ERROR_INT("pixim all foreground", procName, 1); |
| } |
| |
| pixGetBackgroundRGBMapMorph(pixs, pixim, reduction, size, |
| &pixmr, &pixmg, &pixmb); |
| if (!pixmr || !pixmg || !pixmb) { |
| pixDestroy(&pixmr); |
| pixDestroy(&pixmg); |
| pixDestroy(&pixmb); |
| return ERROR_INT("not all pixm* made", procName, 1); |
| } |
| |
| *ppixr = pixGetInvBackgroundMap(pixmr, bgval, 0, 0); |
| *ppixg = pixGetInvBackgroundMap(pixmg, bgval, 0, 0); |
| *ppixb = pixGetInvBackgroundMap(pixmb, bgval, 0, 0); |
| pixDestroy(&pixmr); |
| pixDestroy(&pixmg); |
| pixDestroy(&pixmb); |
| return 0; |
| } |
| |
| |
| /*------------------------------------------------------------------* |
| * Measurement of local background * |
| *------------------------------------------------------------------*/ |
| /*! |
| * pixGetBackgroundGrayMap() |
| * |
| * Input: pixs (8 bpp) |
| * pixim (<optional> 1 bpp 'image' mask; can be null; it |
| * should not have all foreground pixels) |
| * sx, sy (tile size in pixels) |
| * thresh (threshold for determining foreground) |
| * mincount (min threshold on counts in a tile) |
| * &pixd (<return> 8 bpp grayscale map) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) The background is measured in regions that don't have |
| * images. It is then propagated into the image regions, |
| * and finally smoothed in each image region. |
| */ |
| l_int32 |
| pixGetBackgroundGrayMap(PIX *pixs, |
| PIX *pixim, |
| l_int32 sx, |
| l_int32 sy, |
| l_int32 thresh, |
| l_int32 mincount, |
| PIX **ppixd) |
| { |
| l_int32 w, h, wd, hd, wim, him, wpls, wplim, wpld, wplf; |
| l_int32 xim, yim, delx, nx, ny, i, j, k, m; |
| l_int32 count, sum, val8; |
| l_int32 empty, fgpixels; |
| l_uint32 *datas, *dataim, *datad, *dataf, *lines, *lineim, *lined, *linef; |
| l_float32 scalex, scaley; |
| PIX *pixd, *piximi, *pixb, *pixf, *pixims; |
| |
| PROCNAME("pixGetBackgroundGrayMap"); |
| |
| if (!ppixd) |
| return ERROR_INT("&pixd not defined", procName, 1); |
| *ppixd = NULL; |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (pixGetDepth(pixs) != 8) |
| return ERROR_INT("pixs not 8 bpp", procName, 1); |
| if (pixim && pixGetDepth(pixim) != 1) |
| return ERROR_INT("pixim not 1 bpp", procName, 1); |
| if (sx < 4 || sy < 4) |
| return ERROR_INT("sx and sy must be >= 4", procName, 1); |
| if (mincount > sx * sy) { |
| L_WARNING("mincount too large for tile size", procName); |
| mincount = (sx * sy) / 3; |
| } |
| |
| /* Evaluate the 'image' mask, pixim, and make sure |
| * it is not all fg. */ |
| fgpixels = 0; /* boolean for existence of fg pixels in the image mask. */ |
| if (pixim) { |
| piximi = pixInvert(NULL, pixim); /* set non-'image' pixels to 1 */ |
| pixZero(piximi, &empty); |
| pixDestroy(&piximi); |
| if (empty) |
| return ERROR_INT("pixim all fg; no background", procName, 1); |
| pixZero(pixim, &empty); |
| if (!empty) /* there are fg pixels in pixim */ |
| fgpixels = 1; |
| } |
| |
| /* Generate the foreground mask, pixf, which is at |
| * full resolution. These pixels will be ignored when |
| * computing the background values. */ |
| pixb = pixThresholdToBinary(pixs, thresh); |
| pixf = pixMorphSequence(pixb, "d7.1 + d1.7", 0); |
| pixDestroy(&pixb); |
| |
| |
| /* ------------- Set up the output map pixd --------------- */ |
| /* Generate pixd, which is reduced by the factors (sx, sy). */ |
| w = pixGetWidth(pixs); |
| h = pixGetHeight(pixs); |
| wd = (w + sx - 1) / sx; |
| hd = (h + sy - 1) / sy; |
| pixd = pixCreate(wd, hd, 8); |
| |
| /* Note: we only compute map values in tiles that are complete. |
| * In general, tiles at right and bottom edges will not be |
| * complete, and we must fill them in later. */ |
| nx = w / sx; |
| ny = h / sy; |
| wpls = pixGetWpl(pixs); |
| datas = pixGetData(pixs); |
| wpld = pixGetWpl(pixd); |
| datad = pixGetData(pixd); |
| wplf = pixGetWpl(pixf); |
| dataf = pixGetData(pixf); |
| for (i = 0; i < ny; i++) { |
| lines = datas + sy * i * wpls; |
| linef = dataf + sy * i * wplf; |
| lined = datad + i * wpld; |
| for (j = 0; j < nx; j++) { |
| delx = j * sx; |
| sum = 0; |
| count = 0; |
| for (k = 0; k < sy; k++) { |
| for (m = 0; m < sx; m++) { |
| if (GET_DATA_BIT(linef + k * wplf, delx + m) == 0) { |
| sum += GET_DATA_BYTE(lines + k * wpls, delx + m); |
| count++; |
| } |
| } |
| } |
| if (count >= mincount) { |
| val8 = sum / count; |
| SET_DATA_BYTE(lined, j, val8); |
| } |
| } |
| } |
| pixDestroy(&pixf); |
| |
| /* If there is an optional mask with fg pixels, erase the previous |
| * calculation for the corresponding map pixels, setting the |
| * map values to 0. Then, when all the map holes are filled, |
| * these erased pixels will be set by the surrounding map values. |
| * |
| * The calculation here is relatively efficient: for each pixel |
| * in pixd (which corresponds to a tile of mask pixels in pixim) |
| * we look only at the pixel in pixim that is at the center |
| * of the tile. If the mask pixel is ON, we reset the map |
| * pixel in pixd to 0, so that it can later be filled in. */ |
| pixims = NULL; |
| if (pixim && fgpixels) { |
| wim = pixGetWidth(pixim); |
| him = pixGetHeight(pixim); |
| dataim = pixGetData(pixim); |
| wplim = pixGetWpl(pixim); |
| for (i = 0; i < ny; i++) { |
| yim = i * sy + sy / 2; |
| if (yim >= him) |
| break; |
| lineim = dataim + yim * wplim; |
| for (j = 0; j < nx; j++) { |
| xim = j * sx + sx / 2; |
| if (xim >= wim) |
| break; |
| if (GET_DATA_BIT(lineim, xim)) |
| pixSetPixel(pixd, j, i, 0); |
| } |
| } |
| } |
| |
| /* Fill all the holes in the map. */ |
| if (pixFillMapHoles(pixd, nx, ny, L_FILL_BLACK)) { |
| pixDestroy(&pixd); |
| L_WARNING("can't make the map", procName); |
| return 1; |
| } |
| |
| /* Finally, for each connected region corresponding to the |
| * 'image' mask, reset all pixels to their average value. |
| * Each of these components represents an image (or part of one) |
| * in the input, and this smooths the background values |
| * in each of these regions. */ |
| if (pixim && fgpixels) { |
| scalex = 1. / (l_float32)sx; |
| scaley = 1. / (l_float32)sy; |
| pixims = pixScaleBySampling(pixim, scalex, scaley); |
| pixSmoothConnectedRegions(pixd, pixims, 2); |
| pixDestroy(&pixims); |
| } |
| |
| *ppixd = pixd; |
| return 0; |
| } |
| |
| |
| /*! |
| * pixGetBackgroundRGBMap() |
| * |
| * Input: pixs (32 bpp rgb) |
| * pixim (<optional> 1 bpp 'image' mask; can be null; it |
| * should not have all foreground pixels) |
| * pixg (<optional> 8 bpp grayscale version; can be null) |
| * sx, sy (tile size in pixels) |
| * thresh (threshold for determining foreground) |
| * mincount (min threshold on counts in a tile) |
| * &pixmr, &pixmg, &pixmb (<return> rgb maps) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) If pixg, which is a grayscale version of pixs, is provided, |
| * use this internally to generate the foreground mask. |
| * Otherwise, a grayscale version of pixs will be generated |
| * from the green component only, used, and destroyed. |
| */ |
| l_int32 |
| pixGetBackgroundRGBMap(PIX *pixs, |
| PIX *pixim, |
| PIX *pixg, |
| l_int32 sx, |
| l_int32 sy, |
| l_int32 thresh, |
| l_int32 mincount, |
| PIX **ppixmr, |
| PIX **ppixmg, |
| PIX **ppixmb) |
| { |
| l_int32 w, h, wm, hm, wim, him, wpls, wplim, wplf; |
| l_int32 xim, yim, delx, nx, ny, i, j, k, m; |
| l_int32 count, rsum, gsum, bsum, rval, gval, bval; |
| l_int32 empty, fgpixels; |
| l_uint32 pixel; |
| l_uint32 *datas, *dataim, *dataf, *lines, *lineim, *linef; |
| l_float32 scalex, scaley; |
| PIX *piximi, *pixgc, *pixb, *pixf, *pixims; |
| PIX *pixmr, *pixmg, *pixmb; |
| |
| PROCNAME("pixGetBackgroundRGBMap"); |
| |
| if (!ppixmr || !ppixmg || !ppixmb) |
| return ERROR_INT("&pixm* not all defined", procName, 1); |
| *ppixmr = *ppixmg = *ppixmb = NULL; |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (pixGetDepth(pixs) != 32) |
| return ERROR_INT("pixs not 32 bpp", procName, 1); |
| if (pixim && pixGetDepth(pixim) != 1) |
| return ERROR_INT("pixim not 1 bpp", procName, 1); |
| if (sx < 4 || sy < 4) |
| return ERROR_INT("sx and sy must be >= 4", procName, 1); |
| if (mincount > sx * sy) { |
| L_WARNING("mincount too large for tile size", procName); |
| mincount = (sx * sy) / 3; |
| } |
| |
| /* Evaluate the mask pixim and make sure it is not all foreground */ |
| fgpixels = 0; /* boolean for existence of fg mask pixels */ |
| if (pixim) { |
| piximi = pixInvert(NULL, pixim); /* set non-'image' pixels to 1 */ |
| pixZero(piximi, &empty); |
| pixDestroy(&piximi); |
| if (empty) |
| return ERROR_INT("pixim all fg; no background", procName, 1); |
| pixZero(pixim, &empty); |
| if (!empty) /* there are fg pixels in pixim */ |
| fgpixels = 1; |
| } |
| |
| /* Generate the foreground mask. These pixels will be |
| * ignored when computing the background values. */ |
| if (pixg) /* use the input grayscale version if it is provided */ |
| pixgc = pixClone(pixg); |
| else |
| pixgc = pixConvertRGBToGrayFast(pixs); |
| pixb = pixThresholdToBinary(pixgc, thresh); |
| pixf = pixMorphSequence(pixb, "d7.1 + d1.7", 0); |
| pixDestroy(&pixgc); |
| pixDestroy(&pixb); |
| |
| /* Generate the output mask images */ |
| w = pixGetWidth(pixs); |
| h = pixGetHeight(pixs); |
| wm = (w + sx - 1) / sx; |
| hm = (h + sy - 1) / sy; |
| pixmr = pixCreate(wm, hm, 8); |
| pixmg = pixCreate(wm, hm, 8); |
| pixmb = pixCreate(wm, hm, 8); |
| |
| /* ------------- Set up the mapping images --------------- */ |
| /* Note: we only compute map values in tiles that are complete. |
| * In general, tiles at right and bottom edges will not be |
| * complete, and we must fill them in later. */ |
| nx = w / sx; |
| ny = h / sy; |
| wpls = pixGetWpl(pixs); |
| datas = pixGetData(pixs); |
| wplf = pixGetWpl(pixf); |
| dataf = pixGetData(pixf); |
| for (i = 0; i < ny; i++) { |
| lines = datas + sy * i * wpls; |
| linef = dataf + sy * i * wplf; |
| for (j = 0; j < nx; j++) { |
| delx = j * sx; |
| rsum = gsum = bsum = 0; |
| count = 0; |
| for (k = 0; k < sy; k++) { |
| for (m = 0; m < sx; m++) { |
| if (GET_DATA_BIT(linef + k * wplf, delx + m) == 0) { |
| pixel = *(lines + k * wpls + delx + m); |
| rsum += (pixel >> 24); |
| gsum += ((pixel >> 16) & 0xff); |
| bsum += ((pixel >> 8) & 0xff); |
| count++; |
| } |
| } |
| } |
| if (count >= mincount) { |
| rval = rsum / count; |
| gval = gsum / count; |
| bval = bsum / count; |
| pixSetPixel(pixmr, j, i, rval); |
| pixSetPixel(pixmg, j, i, gval); |
| pixSetPixel(pixmb, j, i, bval); |
| } |
| } |
| } |
| pixDestroy(&pixf); |
| |
| /* If there is an optional mask with fg pixels, erase the previous |
| * calculation for the corresponding map pixels, setting the |
| * map values in each of the 3 color maps to 0. Then, when |
| * all the map holes are filled, these erased pixels will |
| * be set by the surrounding map values. */ |
| if (pixim) { |
| wim = pixGetWidth(pixim); |
| him = pixGetHeight(pixim); |
| dataim = pixGetData(pixim); |
| wplim = pixGetWpl(pixim); |
| for (i = 0; i < ny; i++) { |
| yim = i * sy + sy / 2; |
| if (yim >= him) |
| break; |
| lineim = dataim + yim * wplim; |
| for (j = 0; j < nx; j++) { |
| xim = j * sx + sx / 2; |
| if (xim >= wim) |
| break; |
| if (GET_DATA_BIT(lineim, xim)) { |
| pixSetPixel(pixmr, j, i, 0); |
| pixSetPixel(pixmg, j, i, 0); |
| pixSetPixel(pixmb, j, i, 0); |
| } |
| } |
| } |
| } |
| |
| /* ----------------- Now fill in the holes ----------------------- */ |
| if (pixFillMapHoles(pixmr, nx, ny, L_FILL_BLACK) || |
| pixFillMapHoles(pixmg, nx, ny, L_FILL_BLACK) || |
| pixFillMapHoles(pixmb, nx, ny, L_FILL_BLACK)) { |
| pixDestroy(&pixmr); |
| pixDestroy(&pixmg); |
| pixDestroy(&pixmb); |
| L_WARNING("can't make the maps", procName); |
| return 1; |
| } |
| |
| /* Finally, for each connected region corresponding to the |
| * fg mask, reset all pixels to their average value. */ |
| if (pixim && fgpixels) { |
| scalex = 1. / (l_float32)sx; |
| scaley = 1. / (l_float32)sy; |
| pixims = pixScaleBySampling(pixim, scalex, scaley); |
| pixSmoothConnectedRegions(pixmr, pixims, 2); |
| pixSmoothConnectedRegions(pixmg, pixims, 2); |
| pixSmoothConnectedRegions(pixmb, pixims, 2); |
| pixDestroy(&pixims); |
| } |
| |
| *ppixmr = pixmr; |
| *ppixmg = pixmg; |
| *ppixmb = pixmb; |
| return 0; |
| } |
| |
| |
| /*! |
| * pixGetBackgroundGrayMapMorph() |
| * |
| * Input: pixs (8 bpp) |
| * pixim (<optional> 1 bpp 'image' mask; can be null; it |
| * should not have all foreground pixels) |
| * reduction (factor at which closing is performed) |
| * size (of square Sel for the closing; use an odd number) |
| * &pixm (<return> grayscale map) |
| * Return: 0 if OK, 1 on error |
| */ |
| l_int32 |
| pixGetBackgroundGrayMapMorph(PIX *pixs, |
| PIX *pixim, |
| l_int32 reduction, |
| l_int32 size, |
| PIX **ppixm) |
| { |
| l_int32 nx, ny, empty, fgpixels; |
| l_float32 scale; |
| PIX *pixm, *pixt1, *pixt2, *pixt3, *pixims; |
| |
| PROCNAME("pixGetBackgroundGrayMapMorph"); |
| |
| if (!ppixm) |
| return ERROR_INT("&pixm not defined", procName, 1); |
| *ppixm = NULL; |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (pixGetDepth(pixs) != 8) |
| return ERROR_INT("pixs not 8 bpp", procName, 1); |
| if (pixim && pixGetDepth(pixim) != 1) |
| return ERROR_INT("pixim not 1 bpp", procName, 1); |
| |
| /* Evaluate the mask pixim and make sure it is not all foreground. */ |
| fgpixels = 0; /* boolean for existence of fg mask pixels */ |
| if (pixim) { |
| pixInvert(pixim, pixim); /* set background pixels to 1 */ |
| pixZero(pixim, &empty); |
| if (empty) |
| return ERROR_INT("pixim all fg; no background", procName, 1); |
| pixInvert(pixim, pixim); /* revert to original mask */ |
| pixZero(pixim, &empty); |
| if (!empty) /* there are fg pixels in pixim */ |
| fgpixels = 1; |
| } |
| |
| /* Downscale as requested and do the closing to get the background. */ |
| scale = 1. / (l_float32)reduction; |
| pixt1 = pixScaleBySampling(pixs, scale, scale); |
| pixt2 = pixCloseGray(pixt1, size, size); |
| pixt3 = pixExtendByReplication(pixt2, 1, 1); |
| |
| /* Downscale the image mask, if any, and remove it from the |
| * background. These pixels will be filled in (twice). */ |
| pixims = NULL; |
| if (pixim) { |
| pixims = pixScale(pixim, scale, scale); |
| pixm = pixConvertTo8(pixims, FALSE); |
| pixAnd(pixm, pixm, pixt3); |
| } |
| else |
| pixm = pixClone(pixt3); |
| pixDestroy(&pixt1); |
| pixDestroy(&pixt2); |
| pixDestroy(&pixt3); |
| |
| /* Fill all the holes in the map. */ |
| nx = pixGetWidth(pixs) / reduction; |
| ny = pixGetHeight(pixs) / reduction; |
| if (pixFillMapHoles(pixm, nx, ny, L_FILL_BLACK)) { |
| pixDestroy(&pixm); |
| L_WARNING("can't make the map", procName); |
| return 1; |
| } |
| |
| /* Finally, for each connected region corresponding to the |
| * fg mask, reset all pixels to their average value. */ |
| if (pixim && fgpixels) { |
| pixSmoothConnectedRegions(pixm, pixims, 2); |
| pixDestroy(&pixims); |
| } |
| |
| *ppixm = pixm; |
| return 0; |
| } |
| |
| |
| /*! |
| * pixGetBackgroundRGBMapMorph() |
| * |
| * Input: pixs (32 bpp rgb) |
| * pixim (<optional> 1 bpp 'image' mask; can be null; it |
| * should not have all foreground pixels) |
| * reduction (factor at which closing is performed) |
| * size (of square Sel for the closing; use an odd number) |
| * &pixmr (<return> red component map) |
| * &pixmg (<return> green component map) |
| * &pixmb (<return> blue component map) |
| * Return: 0 if OK, 1 on error |
| */ |
| l_int32 |
| pixGetBackgroundRGBMapMorph(PIX *pixs, |
| PIX *pixim, |
| l_int32 reduction, |
| l_int32 size, |
| PIX **ppixmr, |
| PIX **ppixmg, |
| PIX **ppixmb) |
| { |
| l_int32 nx, ny, empty, fgpixels; |
| l_float32 scale; |
| PIX *pixm, *pixmr, *pixmg, *pixmb, *pixt1, *pixt2, *pixt3, *pixims; |
| |
| PROCNAME("pixGetBackgroundRGBMapMorph"); |
| |
| if (!ppixmr || !ppixmg || !ppixmb) |
| return ERROR_INT("&pixm* not all defined", procName, 1); |
| *ppixmr = *ppixmg = *ppixmb = NULL; |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (pixGetDepth(pixs) != 32) |
| return ERROR_INT("pixs not 32 bpp", procName, 1); |
| if (pixim && pixGetDepth(pixim) != 1) |
| return ERROR_INT("pixim not 1 bpp", procName, 1); |
| |
| /* Generate an 8 bpp version of the image mask, if it exists */ |
| scale = 1. / (l_float32)reduction; |
| pixm = NULL; |
| if (pixim) { |
| pixims = pixScale(pixim, scale, scale); |
| pixm = pixConvertTo8(pixims, FALSE); |
| } |
| |
| /* Evaluate the mask pixim and make sure it is not all foreground. */ |
| fgpixels = 0; /* boolean for existence of fg mask pixels */ |
| if (pixim) { |
| pixInvert(pixim, pixim); /* set background pixels to 1 */ |
| pixZero(pixim, &empty); |
| if (empty) |
| return ERROR_INT("pixim all fg; no background", procName, 1); |
| pixInvert(pixim, pixim); /* revert to original mask */ |
| pixZero(pixim, &empty); |
| if (!empty) /* there are fg pixels in pixim */ |
| fgpixels = 1; |
| } |
| |
| /* Downscale as requested and do the closing to get the background. |
| * Then remove the image mask pixels from the background. They |
| * will be filled in (twice) later. Do this for all 3 components. */ |
| pixt1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_RED); |
| pixt2 = pixCloseGray(pixt1, size, size); |
| pixt3 = pixExtendByReplication(pixt2, 1, 1); |
| if (pixim) |
| pixmr = pixAnd(NULL, pixm, pixt3); |
| else |
| pixmr = pixClone(pixt3); |
| pixDestroy(&pixt1); |
| pixDestroy(&pixt2); |
| pixDestroy(&pixt3); |
| |
| pixt1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_GREEN); |
| pixt2 = pixCloseGray(pixt1, size, size); |
| pixt3 = pixExtendByReplication(pixt2, 1, 1); |
| if (pixim) |
| pixmg = pixAnd(NULL, pixm, pixt3); |
| else |
| pixmg = pixClone(pixt3); |
| pixDestroy(&pixt1); |
| pixDestroy(&pixt2); |
| pixDestroy(&pixt3); |
| |
| pixt1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_BLUE); |
| pixt2 = pixCloseGray(pixt1, size, size); |
| pixt3 = pixExtendByReplication(pixt2, 1, 1); |
| if (pixim) |
| pixmb = pixAnd(NULL, pixm, pixt3); |
| else |
| pixmb = pixClone(pixt3); |
| pixDestroy(&pixm); |
| pixDestroy(&pixt1); |
| pixDestroy(&pixt2); |
| pixDestroy(&pixt3); |
| |
| /* Fill all the holes in the three maps. */ |
| nx = pixGetWidth(pixs) / reduction; |
| ny = pixGetHeight(pixs) / reduction; |
| if (pixFillMapHoles(pixmr, nx, ny, L_FILL_BLACK) || |
| pixFillMapHoles(pixmg, nx, ny, L_FILL_BLACK) || |
| pixFillMapHoles(pixmb, nx, ny, L_FILL_BLACK)) { |
| pixDestroy(&pixmr); |
| pixDestroy(&pixmg); |
| pixDestroy(&pixmb); |
| L_WARNING("can't make the maps", procName); |
| return 1; |
| } |
| |
| /* Finally, for each connected region corresponding to the |
| * fg mask in each component, reset all pixels to their |
| * average value. */ |
| if (pixim && fgpixels) { |
| pixSmoothConnectedRegions(pixmr, pixims, 2); |
| pixSmoothConnectedRegions(pixmg, pixims, 2); |
| pixSmoothConnectedRegions(pixmb, pixims, 2); |
| pixDestroy(&pixims); |
| } |
| |
| *ppixmr = pixmr; |
| *ppixmg = pixmg; |
| *ppixmb = pixmb; |
| return 0; |
| } |
| |
| |
| /*! |
| * pixFillMapHoles() |
| * |
| * Input: pix (8 bpp; a map, with one pixel for each tile in |
| * a larger image) |
| * nx (number of horizontal pixel tiles that are entirely |
| * covered with pixels in the original source image) |
| * ny (ditto for the number of vertical pixel tiles) |
| * filltype (L_FILL_WHITE or L_FILL_BLACK) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) This is an in-place operation on pix (the map). pix is |
| * typically a low-resolution version of some other image |
| * from which it was derived, where each pixel in pix |
| * corresponds to a rectangular tile (say, m x n) of pixels |
| * in the larger image. All we need to know about the larger |
| * image is whether or not the rightmost column and bottommost |
| * row of pixels in pix correspond to tiles that are |
| * only partially covered by pixels in the larger image. |
| * (2) Typically, some number of pixels in the input map are |
| * not known, and their values must be determined by near |
| * pixels that are known. These unknown pixels are the 'holes'. |
| * They can take on only two values, 0 and 255, and the |
| * instruction about which to fill is given by the filltype flag. |
| * (3) The "holes" can come from two sources. The first is when there |
| * are not enough foreground or background pixels in a tile; |
| * the second is when a tile is at least partially covered |
| * by an image mask. If we're filling holes in a fg mask, |
| * the holes are initialized to black (0) and use L_FILL_BLACK. |
| * For filling holes in a bg mask, initialize the holes to |
| * white (255) and use L_FILL_WHITE. |
| * (4) If w is the map width, nx = w or nx = w - 1; ditto for h and ny. |
| */ |
| l_int32 |
| pixFillMapHoles(PIX *pix, |
| l_int32 nx, |
| l_int32 ny, |
| l_int32 filltype) |
| { |
| l_int32 w, h, d, y, nmiss, goodcol, i, j, found, ival, valtest; |
| l_uint32 val, lastval; |
| NUMA *na; /* indicates if there is any data in the column */ |
| PIX *pixt; |
| |
| PROCNAME("pixFillMapHoles"); |
| |
| if (!pix) |
| return ERROR_INT("pix not defined", procName, 1); |
| pixGetDimensions(pix, &w, &h, &d); |
| if (d != 8) |
| return ERROR_INT("pix not 8 bpp", procName, 1); |
| |
| /* ------------- Fill holes in the mapping image columns ----------- */ |
| na = numaCreate(0); /* holds flag for which columns have data */ |
| nmiss = 0; |
| valtest = (filltype == L_FILL_WHITE) ? 255 : 0; |
| for (j = 0; j < nx; j++) { /* do it by columns */ |
| found = FALSE; |
| for (i = 0; i < ny; i++) { |
| pixGetPixel(pix, j, i, &val); |
| if (val != valtest) { |
| y = i; |
| found = TRUE; |
| break; |
| } |
| } |
| if (found == FALSE) { |
| numaAddNumber(na, 0); /* no data in the column */ |
| nmiss++; |
| } |
| else { |
| numaAddNumber(na, 1); /* data in the column */ |
| for (i = y - 1; i >= 0; i--) /* replicate upwards to top */ |
| pixSetPixel(pix, j, i, val); |
| pixGetPixel(pix, j, 0, &lastval); |
| for (i = 1; i < h; i++) { /* set going down to bottom */ |
| pixGetPixel(pix, j, i, &val); |
| if (val == valtest) |
| pixSetPixel(pix, j, i, lastval); |
| else |
| lastval = val; |
| } |
| } |
| } |
| numaAddNumber(na, 0); /* last column */ |
| |
| if (nmiss == nx) { /* no data in any column! */ |
| numaDestroy(&na); |
| L_WARNING("no bg found; no data in any column", procName); |
| return 1; |
| } |
| |
| /* ---------- Fill in missing columns by replication ----------- */ |
| if (nmiss > 0) { /* replicate columns */ |
| pixt = pixCopy(NULL, pix); |
| /* Find the first good column */ |
| goodcol = 0; |
| for (j = 0; j < w; j++) { |
| numaGetIValue(na, j, &ival); |
| if (ival == 1) { |
| goodcol = j; |
| break; |
| } |
| } |
| if (goodcol > 0) { /* copy cols backward */ |
| for (j = goodcol - 1; j >= 0; j--) { |
| pixRasterop(pix, j, 0, 1, h, PIX_SRC, pixt, j + 1, 0); |
| pixRasterop(pixt, j, 0, 1, h, PIX_SRC, pix, j, 0); |
| } |
| } |
| for (j = goodcol + 1; j < w; j++) { /* copy cols forward */ |
| numaGetIValue(na, j, &ival); |
| if (ival == 0) { |
| /* Copy the column to the left of j */ |
| pixRasterop(pix, j, 0, 1, h, PIX_SRC, pixt, j - 1, 0); |
| pixRasterop(pixt, j, 0, 1, h, PIX_SRC, pix, j, 0); |
| } |
| } |
| pixDestroy(&pixt); |
| } |
| if (w > nx) { /* replicate the last column */ |
| for (i = 0; i < h; i++) { |
| pixGetPixel(pix, w - 2, i, &val); |
| pixSetPixel(pix, w - 1, i, val); |
| } |
| } |
| |
| numaDestroy(&na); |
| return 0; |
| } |
| |
| |
| /*! |
| * pixExtendByReplication() |
| * |
| * Input: pixs (8 bpp) |
| * addw (number of extra pixels horizontally to add) |
| * addh (number of extra pixels vertically to add) |
| * Return: pixd (extended with replicated pixel values), or null on error |
| * |
| * Notes: |
| * (1) The pixel values are extended to the left and down, as required. |
| */ |
| PIX * |
| pixExtendByReplication(PIX *pixs, |
| l_int32 addw, |
| l_int32 addh) |
| { |
| l_int32 w, h, i, j; |
| l_uint32 val; |
| PIX *pixd; |
| |
| PROCNAME("pixExtendByReplication"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (pixGetDepth(pixs) != 8) |
| return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL); |
| |
| if (addw == 0 && addh == 0) |
| return pixCopy(NULL, pixs); |
| |
| w = pixGetWidth(pixs); |
| h = pixGetHeight(pixs); |
| if ((pixd = pixCreate(w + addw, h + addh, 8)) == NULL) |
| return (PIX *)ERROR_PTR("pixd not made", procName, NULL); |
| pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0); |
| |
| if (addw > 0) { |
| for (i = 0; i < h; i++) { |
| pixGetPixel(pixd, w - 1, i, &val); |
| for (j = 0; j < addw; j++) |
| pixSetPixel(pixd, w + j, i, val); |
| } |
| } |
| |
| if (addh > 0) { |
| for (j = 0; j < w + addw; j++) { |
| pixGetPixel(pixd, j, h - 1, &val); |
| for (i = 0; i < addh; i++) |
| pixSetPixel(pixd, j, h + i, val); |
| } |
| } |
| |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixSmoothConnectedRegions() |
| * |
| * Input: pixs (8 bpp) |
| * pixm (<optional> 1 bpp; if null, this is a no-op) |
| * factor (subsampling factor for getting average; >= 1) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) The pixels in pixs corresponding to those in each |
| * 8-connected region in the mask are set to the average value. |
| * (2) This is required for adaptive mapping to avoid the |
| * generation of stripes in the background map, due to |
| * variations in the pixel values near the edges of mask regions. |
| * (3) This function is optimized for background smoothing, where |
| * there are a relatively small number of components. It will |
| * be inefficient if used where there are many small components. |
| */ |
| l_int32 |
| pixSmoothConnectedRegions(PIX *pixs, |
| PIX *pixm, |
| l_int32 factor) |
| { |
| l_int32 empty, i, n, x, y; |
| l_float32 aveval; |
| BOXA *boxa; |
| PIX *pixmc; |
| PIXA *pixa; |
| |
| PROCNAME("pixSmoothConnectedRegions"); |
| |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (pixGetDepth(pixs) != 8) |
| return ERROR_INT("pixs not 8 bpp", procName, 1); |
| if (!pixm) { |
| L_INFO("pixm not defined", procName); |
| return 0; |
| } |
| if (pixGetDepth(pixm) != 1) |
| return ERROR_INT("pixm not 1 bpp", procName, 1); |
| pixZero(pixm, &empty); |
| if (empty) { |
| L_INFO("pixm has no fg pixels; nothing to do", procName); |
| return 0; |
| } |
| |
| boxa = pixConnComp(pixm, &pixa, 8); |
| n = boxaGetCount(boxa); |
| for (i = 0; i < n; i++) { |
| if ((pixmc = pixaGetPix(pixa, i, L_CLONE)) == NULL) { |
| L_WARNING("missing pixmc!", procName); |
| continue; |
| } |
| boxaGetBoxGeometry(boxa, i, &x, &y, NULL, NULL); |
| pixGetAverageMasked(pixs, pixmc, x, y, factor, L_MEAN_ABSVAL, &aveval); |
| pixPaintThroughMask(pixs, pixmc, x, y, (l_int32)aveval); |
| pixDestroy(&pixmc); |
| } |
| |
| boxaDestroy(&boxa); |
| pixaDestroy(&pixa); |
| return 0; |
| } |
| |
| |
| /*------------------------------------------------------------------* |
| * Measurement of local foreground * |
| *------------------------------------------------------------------*/ |
| #if 0 /* Not working properly: do not use */ |
| |
| /*! |
| * pixGetForegroundGrayMap() |
| * |
| * Input: pixs (8 bpp) |
| * pixim (<optional> 1 bpp 'image' mask; can be null) |
| * sx, sy (src tile size, in pixels) |
| * thresh (threshold for determining foreground) |
| * &pixd (<return> 8 bpp grayscale map) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) Each (sx, sy) tile of pixs gets mapped to one pixel in pixd. |
| * (2) pixd is the estimate of the fg (darkest) value within each tile. |
| * (3) All pixels in pixd that are in 'image' regions, as specified |
| * by pixim, are given the background value 0. |
| * (4) For pixels in pixd that can't directly be given a fg value, |
| * the value is inferred by propagating from neighboring pixels. |
| * (5) In practice, pixd can be used to normalize the fg, and |
| * it can be done after background normalization. |
| * (6) The overall procedure is: |
| * - reduce 2x by sampling |
| * - paint all 'image' pixels white, so that they don't |
| * participate in the Min reduction |
| * - do a further (sx, sy) Min reduction -- think of |
| * it as a large opening followed by subsampling by the |
| * reduction factors |
| * - threshold the result to identify fg, and set the |
| * bg pixels to 255 (these are 'holes') |
| * - fill holes by propagation from fg values |
| * - replicatively expand by 2x, arriving at the final |
| * resolution of pixd |
| * - smooth with a 17x17 kernel |
| * - paint the 'image' regions black |
| */ |
| l_int32 |
| pixGetForegroundGrayMap(PIX *pixs, |
| PIX *pixim, |
| l_int32 sx, |
| l_int32 sy, |
| l_int32 thresh, |
| PIX **ppixd) |
| { |
| l_int32 w, h, d, wd, hd; |
| l_int32 empty, fgpixels; |
| PIX *pixd, *piximi, *pixim2, *pixims, *pixs2, *pixb, *pixt1, *pixt2, *pixt3; |
| |
| PROCNAME("pixGetForegroundGrayMap"); |
| |
| if (!ppixd) |
| return ERROR_INT("&pixd not defined", procName, 1); |
| *ppixd = NULL; |
| 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 (pixim && pixGetDepth(pixim) != 1) |
| return ERROR_INT("pixim not 1 bpp", procName, 1); |
| if (sx < 2 || sy < 2) |
| return ERROR_INT("sx and sy must be >= 2", procName, 1); |
| |
| /* Generate pixd, which is reduced by the factors (sx, sy). */ |
| wd = (w + sx - 1) / sx; |
| hd = (h + sy - 1) / sy; |
| pixd = pixCreate(wd, hd, 8); |
| *ppixd = pixd; |
| |
| /* Evaluate the 'image' mask, pixim. If it is all fg, |
| * the output pixd has all pixels with value 0. */ |
| fgpixels = 0; /* boolean for existence of fg pixels in the image mask. */ |
| if (pixim) { |
| piximi = pixInvert(NULL, pixim); /* set non-image pixels to 1 */ |
| pixZero(piximi, &empty); |
| pixDestroy(&piximi); |
| if (empty) /* all 'image'; return with all pixels set to 0 */ |
| return 0; |
| pixZero(pixim, &empty); |
| if (!empty) /* there are fg pixels in pixim */ |
| fgpixels = 1; |
| } |
| |
| /* 2x subsampling; paint white through 'image' mask. */ |
| pixs2 = pixScaleBySampling(pixs, 0.5, 0.5); |
| if (pixim && fgpixels) { |
| pixim2 = pixReduceBinary2(pixim, NULL); |
| pixPaintThroughMask(pixs2, pixim2, 0, 0, 255); |
| pixDestroy(&pixim2); |
| } |
| |
| /* Min (erosion) downscaling; total reduction (4 sx, 4 sy). */ |
| pixt1 = pixScaleGrayMinMax(pixs2, sx, sy, L_CHOOSE_MIN); |
| |
| /* pixDisplay(pixt1, 300, 200); */ |
| |
| /* Threshold to identify fg; paint bg pixels to white. */ |
| pixb = pixThresholdToBinary(pixt1, thresh); /* fg pixels */ |
| pixInvert(pixb, pixb); |
| pixPaintThroughMask(pixt1, pixb, 0, 0, 255); |
| pixDestroy(&pixb); |
| |
| /* Replicative expansion by 2x to (sx, sy). */ |
| pixt2 = pixExpandReplicate(pixt1, 2); |
| |
| /* pixDisplay(pixt2, 500, 200); */ |
| |
| /* Fill holes in the fg by propagation */ |
| pixFillMapHoles(pixt2, w / sx, h / sy, L_FILL_WHITE); |
| |
| /* pixDisplay(pixt2, 700, 200); */ |
| |
| /* Smooth with 17x17 kernel. */ |
| pixt3 = pixBlockconv(pixt2, 8, 8); |
| pixRasterop(pixd, 0, 0, wd, hd, PIX_SRC, pixt3, 0, 0); |
| |
| /* Paint the image parts black. */ |
| pixims = pixScaleBySampling(pixim, 1. / sx, 1. / sy); |
| pixPaintThroughMask(pixd, pixims, 0, 0, 0); |
| |
| pixDestroy(&pixs2); |
| pixDestroy(&pixt1); |
| pixDestroy(&pixt2); |
| pixDestroy(&pixt3); |
| return 0; |
| } |
| #endif /* Not working properly: do not use */ |
| |
| |
| /*------------------------------------------------------------------* |
| * Generate inverted background map * |
| *------------------------------------------------------------------*/ |
| /*! |
| * pixGetInvBackgroundMap() |
| * |
| * Input: pixs (8 bpp) |
| * bgval (target bg val; typ. > 128) |
| * smoothx (half-width of block convolution kernel width) |
| * smoothy (half-width of block convolution kernel height) |
| * Return: pixd (16 bpp), or null on error |
| * |
| * Note: |
| * - bgval should typically be > 120 and < 240 |
| * - pixd is a normalization image; the original image is |
| * multiplied by pixd and the result is divided by 256. |
| */ |
| PIX * |
| pixGetInvBackgroundMap(PIX *pixs, |
| l_int32 bgval, |
| l_int32 smoothx, |
| l_int32 smoothy) |
| { |
| l_int32 w, h, wplsm, wpld, i, j; |
| l_int32 val, val16; |
| l_uint32 *datasm, *datad, *linesm, *lined; |
| PIX *pixsm, *pixd; |
| |
| PROCNAME("pixGetInvBackgroundMap"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (pixGetDepth(pixs) != 8) |
| return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL); |
| w = pixGetWidth(pixs); |
| h = pixGetHeight(pixs); |
| if (w < 5 || h < 5) |
| return (PIX *)ERROR_PTR("w and h must be >= 5", procName, NULL); |
| |
| /* smooth the map image */ |
| pixsm = pixBlockconv(pixs, smoothx, smoothy); |
| datasm = pixGetData(pixsm); |
| wplsm = pixGetWpl(pixsm); |
| |
| /* invert the map image, scaling up to preserve dynamic range */ |
| pixd = pixCreate(w, h, 16); |
| datad = pixGetData(pixd); |
| wpld = pixGetWpl(pixd); |
| for (i = 0; i < h; i++) { |
| linesm = datasm + i * wplsm; |
| lined = datad + i * wpld; |
| for (j = 0; j < w; j++) { |
| val = GET_DATA_BYTE(linesm, j); |
| if (val > 0) |
| val16 = (256 * bgval) / val; |
| else { /* shouldn't happen */ |
| L_WARNING("smoothed bg has 0 pixel!", procName); |
| val16 = bgval / 2; |
| } |
| SET_DATA_TWO_BYTES(lined, j, val16); |
| } |
| } |
| |
| pixDestroy(&pixsm); |
| return pixd; |
| } |
| |
| |
| /*------------------------------------------------------------------* |
| * Apply background map to image * |
| *------------------------------------------------------------------*/ |
| /*! |
| * pixApplyInvBackgroundGrayMap() |
| * |
| * Input: pixs (8 bpp) |
| * pixm (16 bpp, inverse background map) |
| * sx (tile width in pixels) |
| * sy (tile height in pixels) |
| * Return: pixd (8 bpp), or null on error |
| */ |
| PIX * |
| pixApplyInvBackgroundGrayMap(PIX *pixs, |
| PIX *pixm, |
| l_int32 sx, |
| l_int32 sy) |
| { |
| l_int32 w, h, wm, hm, wpls, wpld, i, j, k, m, xoff, yoff; |
| l_int32 vals, vald; |
| l_uint32 val16; |
| l_uint32 *datas, *datad, *lines, *lined, *flines, *flined; |
| PIX *pixd; |
| |
| PROCNAME("pixApplyInvBackgroundGrayMap"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (pixGetDepth(pixs) != 8) |
| return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL); |
| if (!pixm) |
| return (PIX *)ERROR_PTR("pixm not defined", procName, NULL); |
| if (pixGetDepth(pixm) != 16) |
| return (PIX *)ERROR_PTR("pixm not 16 bpp", procName, NULL); |
| if (sx == 0 || sy == 0) |
| return (PIX *)ERROR_PTR("invalid sx and/or sy", procName, NULL); |
| |
| datas = pixGetData(pixs); |
| wpls = pixGetWpl(pixs); |
| w = pixGetWidth(pixs); |
| h = pixGetHeight(pixs); |
| wm = pixGetWidth(pixm); |
| hm = pixGetHeight(pixm); |
| pixd = pixCreateTemplate(pixs); |
| datad = pixGetData(pixd); |
| wpld = pixGetWpl(pixd); |
| for (i = 0; i < hm; i++) { |
| lines = datas + sy * i * wpls; |
| lined = datad + sy * i * wpld; |
| yoff = sy * i; |
| for (j = 0; j < wm; j++) { |
| pixGetPixel(pixm, j, i, &val16); |
| xoff = sx * j; |
| for (k = 0; k < sy && yoff + k < h; k++) { |
| flines = lines + k * wpls; |
| flined = lined + k * wpld; |
| for (m = 0; m < sx && xoff + m < w; m++) { |
| vals = GET_DATA_BYTE(flines, xoff + m); |
| vald = (vals * val16) / 256; |
| vald = L_MIN(vald, 255); |
| SET_DATA_BYTE(flined, xoff + m, vald); |
| } |
| } |
| } |
| } |
| |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixApplyInvBackgroundRGBMap() |
| * |
| * Input: pixs (32 bpp rbg) |
| * pixmr (16 bpp, red inverse background map) |
| * pixmg (16 bpp, green inverse background map) |
| * pixmb (16 bpp, blue inverse background map) |
| * sx (tile width in pixels) |
| * sy (tile height in pixels) |
| * Return: pixd (32 bpp rbg), or null on error |
| */ |
| PIX * |
| pixApplyInvBackgroundRGBMap(PIX *pixs, |
| PIX *pixmr, |
| PIX *pixmg, |
| PIX *pixmb, |
| l_int32 sx, |
| l_int32 sy) |
| { |
| l_int32 w, h, wm, hm, wpls, wpld, i, j, k, m, xoff, yoff; |
| l_int32 rvald, gvald, bvald; |
| l_uint32 vals; |
| l_uint32 rval16, gval16, bval16; |
| l_uint32 *datas, *datad, *lines, *lined, *flines, *flined; |
| PIX *pixd; |
| |
| PROCNAME("pixApplyInvBackgroundRGBMap"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (pixGetDepth(pixs) != 32) |
| return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); |
| if (!pixmr || !pixmg || !pixmb) |
| return (PIX *)ERROR_PTR("pix maps not all defined", procName, NULL); |
| if (pixGetDepth(pixmr) != 16 || pixGetDepth(pixmg) != 16 || |
| pixGetDepth(pixmb) != 16) |
| return (PIX *)ERROR_PTR("pix maps not all 16 bpp", procName, NULL); |
| if (sx == 0 || sy == 0) |
| return (PIX *)ERROR_PTR("invalid sx and/or sy", procName, NULL); |
| |
| datas = pixGetData(pixs); |
| wpls = pixGetWpl(pixs); |
| w = pixGetWidth(pixs); |
| h = pixGetHeight(pixs); |
| wm = pixGetWidth(pixmr); |
| hm = pixGetHeight(pixmr); |
| pixd = pixCreateTemplate(pixs); |
| datad = pixGetData(pixd); |
| wpld = pixGetWpl(pixd); |
| for (i = 0; i < hm; i++) { |
| lines = datas + sy * i * wpls; |
| lined = datad + sy * i * wpld; |
| yoff = sy * i; |
| for (j = 0; j < wm; j++) { |
| pixGetPixel(pixmr, j, i, &rval16); |
| pixGetPixel(pixmg, j, i, &gval16); |
| pixGetPixel(pixmb, j, i, &bval16); |
| xoff = sx * j; |
| for (k = 0; k < sy && yoff + k < h; k++) { |
| flines = lines + k * wpls; |
| flined = lined + k * wpld; |
| for (m = 0; m < sx && xoff + m < w; m++) { |
| vals = *(flines + xoff + m); |
| rvald = ((vals >> 24) * rval16) / 256; |
| rvald = L_MIN(rvald, 255); |
| gvald = (((vals >> 16) & 0xff) * gval16) / 256; |
| gvald = L_MIN(gvald, 255); |
| bvald = (((vals >> 8) & 0xff) * bval16) / 256; |
| bvald = L_MIN(bvald, 255); |
| composeRGBPixel(rvald, gvald, bvald, flined + xoff + m); |
| } |
| } |
| } |
| } |
| |
| return pixd; |
| } |
| |
| |
| /*------------------------------------------------------------------* |
| * Apply variable map * |
| *------------------------------------------------------------------*/ |
| /*! |
| * pixApplyVariableGrayMap() |
| * |
| * Input: pixs (8 bpp) |
| * pixg (8 bpp, variable map) |
| * target (typ. 128 for threshold) |
| * Return: pixd (8 bpp), or null on error |
| * |
| * Notes: |
| * (1) Suppose you have an image that you want to transform based |
| * on some photometric measurement at each point, such as the |
| * threshold value for binarization. Representing the photometric |
| * measurement as an image pixg, you can threshold in input image |
| * using pixVarThresholdToBinary(). Alternatively, you can map |
| * the input image pointwise so that the threshold over the |
| * entire image becomes a constant, such as 128. For example, |
| * if a pixel in pixg is 150 and the target is 128, the |
| * corresponding pixel in pixs is mapped linearly to a value |
| * (128/150) of the input value. If the resulting mapped image |
| * pixd were then thresholded at 128, you would obtain the |
| * same result as a direct binarization using pixg with |
| * pixVarThresholdToBinary(). |
| * (2) The sizes of pixs and pixg must be equal. |
| */ |
| PIX * |
| pixApplyVariableGrayMap(PIX *pixs, |
| PIX *pixg, |
| l_int32 target) |
| { |
| l_int32 i, j, w, h, d, wpls, wplg, wpld, vals, valg, vald; |
| l_uint8 *lut; |
| l_uint32 *datas, *datag, *datad, *lines, *lineg, *lined; |
| l_float32 fval; |
| PIX *pixd; |
| |
| PROCNAME("pixApplyVariableGrayMap"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (!pixg) |
| return (PIX *)ERROR_PTR("pixg not defined", procName, NULL); |
| if (!pixSizesEqual(pixs, pixg)) |
| return (PIX *)ERROR_PTR("pix sizes not equal", procName, NULL); |
| pixGetDimensions(pixs, &w, &h, &d); |
| if (d != 8) |
| return (PIX *)ERROR_PTR("depth not 8 bpp", procName, NULL); |
| |
| /* Generate a LUT for the mapping if the image is large enough |
| * to warrant the overhead. The LUT is of size 2^16. For the |
| * index to the table, get the MSB from pixs and the LSB from pixg. |
| * Note: this LUT is bigger than the typical 32K L1 cache, so |
| * we expect cache misses. L2 latencies are about 5ns. But |
| * division is slooooow. For large images, this function is about |
| * 4x faster when using the LUT. C'est la vie. */ |
| lut = NULL; |
| if (w * h > 100000) { /* more pixels than 2^16 */ |
| if ((lut = (l_uint8 *)CALLOC(0x10000, sizeof(l_uint8))) == NULL) |
| return (PIX *)ERROR_PTR("lut not made", procName, NULL); |
| for (i = 0; i < 256; i++) { |
| for (j = 0; j < 256; j++) { |
| fval = (l_float32)(i * target) / (j + 0.5); |
| lut[(i << 8) + j] = L_MIN(255, (l_int32)(fval + 0.5)); |
| } |
| } |
| } |
| |
| pixd = pixCreateNoInit(w, h, 8); |
| datad = pixGetData(pixd); |
| wpld = pixGetWpl(pixd); |
| datas = pixGetData(pixs); |
| wpls = pixGetWpl(pixs); |
| datag = pixGetData(pixg); |
| wplg = pixGetWpl(pixg); |
| for (i = 0; i < h; i++) { |
| lines = datas + i * wpls; |
| lineg = datag + i * wplg; |
| lined = datad + i * wpld; |
| if (lut) { |
| for (j = 0; j < w; j++) { |
| vals = GET_DATA_BYTE(lines, j); |
| valg = GET_DATA_BYTE(lineg, j); |
| vald = lut[(vals << 8) + valg]; |
| SET_DATA_BYTE(lined, j, vald); |
| } |
| } |
| else { |
| for (j = 0; j < w; j++) { |
| vals = GET_DATA_BYTE(lines, j); |
| valg = GET_DATA_BYTE(lineg, j); |
| fval = (l_float32)(vals * target) / (valg + 0.5); |
| vald = L_MIN(255, (l_int32)(fval + 0.5)); |
| SET_DATA_BYTE(lined, j, vald); |
| } |
| } |
| } |
| |
| if (lut) FREE(lut); |
| return pixd; |
| } |
| |
| |
| /*------------------------------------------------------------------* |
| * Non-adaptive (global) mapping * |
| *------------------------------------------------------------------*/ |
| /*! |
| * pixGlobalNormRGB() |
| * |
| * Input: pixd (<optional> null, existing or equal to pixs) |
| * pixs (32 bpp rgb, or colormapped) |
| * rval, gval, bval (pixel values in pixs that are |
| * linearly mapped to mapval) |
| * mapval (use 255 for mapping to white) |
| * Return: pixd (32 bpp rgb or colormapped), or null on error |
| * |
| * Notes: |
| * (1) The value of pixd determines if the results are written to a |
| * new pix (use NULL), in-place to pixs (use pixs), or to some |
| * other existing pix. |
| * (2) This does a global normalization of an image where the |
| * r,g,b color components are not balanced. Thus, white in pixs is |
| * represented by a set of r,g,b values that are not all 255. |
| * (3) The input values (rval, gval, bval) should be chosen to |
| * represent the gray color (mapval, mapval, mapval) in src. |
| * Thus, this function will map (rval, gval, bval) to that gray color. |
| * (4) Typically, mapval = 255, so that (rval, gval, bval) |
| * corresponds to the white point of src. In that case, these |
| * parameters should be chosen so that few pixels have higher values. |
| * (5) In all cases, we do a linear TRC separately on each of the |
| * components, saturating at 255. |
| * (6) If the input pix is 8 bpp without a colormap, you can get |
| * this functionality with mapval = 255 by calling: |
| * pixGammaTRC(pixd, pixs, 1.0, 0, bgval); |
| * where bgval is the value you want to be mapped to 255. |
| * Or more generally, if you want bgval to be mapped to mapval: |
| * pixGammaTRC(pixd, pixs, 1.0, 0, 255 * bgval / mapval); |
| */ |
| PIX * |
| pixGlobalNormRGB(PIX *pixd, |
| PIX *pixs, |
| l_int32 rval, |
| l_int32 gval, |
| l_int32 bval, |
| l_int32 mapval) |
| { |
| l_int32 w, h, d, i, j, ncolors, rv, gv, bv, wpl; |
| l_int32 *rarray, *garray, *barray; |
| l_uint32 *data, *line; |
| NUMA *nar, *nag, *nab; |
| PIXCMAP *cmap; |
| |
| PROCNAME("pixGlobalNormRGB"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| cmap = pixGetColormap(pixs); |
| pixGetDimensions(pixs, &w, &h, &d); |
| if (!cmap && d != 32) |
| return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL); |
| if (mapval <= 0) { |
| L_WARNING("mapval must be > 0; setting to 255", procName); |
| mapval = 255; |
| } |
| |
| /* Prepare pixd to be a copy of pixs */ |
| if ((pixd = pixCopy(pixd, pixs)) == NULL) |
| return (PIX *)ERROR_PTR("pixd not made", procName, NULL); |
| |
| /* Generate the TRC maps for each component. Make sure the |
| * upper range for each color is greater than zero. */ |
| nar = numaGammaTRC(1.0, 0, L_MAX(1, 255 * rval / mapval)); |
| nag = numaGammaTRC(1.0, 0, L_MAX(1, 255 * gval / mapval)); |
| nab = numaGammaTRC(1.0, 0, L_MAX(1, 255 * bval / mapval)); |
| if (!nar || !nag || !nab) |
| return (PIX *)ERROR_PTR("trc maps not all made", procName, pixd); |
| |
| /* Extract copies of the internal arrays */ |
| rarray = numaGetIArray(nar); |
| garray = numaGetIArray(nag); |
| barray = numaGetIArray(nab); |
| if (!rarray || !garray || !barray) |
| return (PIX *)ERROR_PTR("*arrays not all made", procName, pixd); |
| |
| if (cmap) { |
| ncolors = pixcmapGetCount(cmap); |
| for (i = 0; i < ncolors; i++) { |
| pixcmapGetColor(cmap, i, &rv, &gv, &bv); |
| pixcmapResetColor(cmap, i, rarray[rv], garray[gv], barray[bv]); |
| } |
| } |
| else { |
| data = pixGetData(pixd); |
| wpl = pixGetWpl(pixd); |
| for (i = 0; i < h; i++) { |
| line = data + i * wpl; |
| for (j = 0; j < w; j++) { |
| extractRGBValues(line[j], &rv, &gv, &bv); |
| composeRGBPixel(rarray[rv], garray[gv], barray[bv], line + j); |
| } |
| } |
| } |
| |
| numaDestroy(&nar); |
| numaDestroy(&nag); |
| numaDestroy(&nab); |
| FREE(rarray); |
| FREE(garray); |
| FREE(barray); |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixGlobalNormNoSatRGB() |
| * |
| * Input: pixd (<optional> null, existing or equal to pixs) |
| * pixs (32 bpp rgb) |
| * rval, gval, bval (pixel values in pixs that are |
| * linearly mapped to mapval; but see below) |
| * factor (subsampling factor; integer >= 1) |
| * rank (between 0.0 and 1.0; typ. use a value near 1.0) |
| * Return: pixd (32 bpp rgb), or null on error |
| * |
| * Notes: |
| * (1) This is a version of pixGlobalNormRGB(), where the output |
| * intensity is scaled back so that a controlled fraction of |
| * pixel components is allowed to saturate. See comments in |
| * pixGlobalNormRGB(). |
| * (2) The value of pixd determines if the results are written to a |
| * new pix (use NULL), in-place to pixs (use pixs), or to some |
| * other existing pix. |
| * (3) This does a global normalization of an image where the |
| * r,g,b color components are not balanced. Thus, white in pixs is |
| * represented by a set of r,g,b values that are not all 255. |
| * (4) The input values (rval, gval, bval) can be chosen to be the |
| * color that, after normalization, becomes white background. |
| * For images that are mostly background, the closer these values |
| * are to the median component values, the closer the resulting |
| * background will be to gray, becoming white at the brightest places. |
| * (5) The mapval used in pixGlobalNormRGB() is computed here to |
| * avoid saturation of any component in the image (save for a |
| * fraction of the pixels given by the input rank value). |
| */ |
| PIX * |
| pixGlobalNormNoSatRGB(PIX *pixd, |
| PIX *pixs, |
| l_int32 rval, |
| l_int32 gval, |
| l_int32 bval, |
| l_int32 factor, |
| l_float32 rank) |
| { |
| l_int32 mapval; |
| l_float32 rankrval, rankgval, rankbval; |
| l_float32 rfract, gfract, bfract, maxfract; |
| |
| PROCNAME("pixGlobalNormNoSatRGB"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (pixGetDepth(pixs) != 32) |
| return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); |
| if (factor < 1) |
| return (PIX *)ERROR_PTR("sampling factor < 1", procName, NULL); |
| if (rank < 0.0 || rank > 1.0) |
| return (PIX *)ERROR_PTR("rank not in [0.0 ... 1.0]", procName, NULL); |
| if (rval <= 0 || gval <= 0 || bval <= 0) |
| return (PIX *)ERROR_PTR("invalid estim. color values", procName, NULL); |
| |
| /* The max value for each component may be larger than the |
| * input estimated background value. In that case, mapping |
| * for those pixels would saturate. To prevent saturation, |
| * we compute the fraction for each component by which we |
| * would oversaturate. Then take the max of these, and |
| * reduce, uniformly over all components, the output intensity |
| * by this value. Then no component will saturate. |
| * In practice, if rank < 1.0, a fraction of pixels |
| * may have a component saturate. By keeping rank close to 1.0, |
| * that fraction can be made arbitrarily small. */ |
| pixGetRankValueMaskedRGB(pixs, NULL, 0, 0, factor, rank, &rankrval, |
| &rankgval, &rankbval); |
| rfract = rankrval / (l_float32)rval; |
| gfract = rankgval / (l_float32)gval; |
| bfract = rankbval / (l_float32)bval; |
| maxfract = L_MAX(rfract, gfract); |
| maxfract = L_MAX(maxfract, bfract); |
| #if DEBUG_GLOBAL |
| fprintf(stderr, "rankrval = %7.2f, rankgval = %7.2f, rankbval = %7.2f\n", |
| rankrval, rankgval, rankbval); |
| fprintf(stderr, "rfract = %7.4f, gfract = %7.4f, bfract = %7.4f\n", |
| rfract, gfract, bfract); |
| #endif /* DEBUG_GLOBAL */ |
| |
| mapval = (l_int32)(255. / maxfract); |
| pixd = pixGlobalNormRGB(pixd, pixs, rval, gval, bval, mapval); |
| return pixd; |
| } |
| |
| |
| /*------------------------------------------------------------------* |
| * Adaptive threshold spread normalization * |
| *------------------------------------------------------------------*/ |
| /*! |
| * pixThresholdSpreadNorm() |
| * |
| * Input: pixs (8 bpp) |
| * filtertype (L_SOBEL_EDGE or L_TWO_SIDED_EDGE); |
| * edgethresh (threshold on magnitude of edge filter; typ 10-20) |
| * smoothx, smoothy (half-width of convolution kernel applied to |
| * spread threshold: use 0 for no smoothing) |
| * gamma (gamma correction; typ. about 0.7) |
| * minval (input value that gives 0 for output; typ. -25) |
| * maxval (input value that gives 255 for output; typ. 255) |
| * targetthresh (target threshold for normalization) |
| * &pixth (<optional return> computed local threshold value) |
| * &pixb (<optional return> thresholded normalized image) |
| * &pixd (<optional return> normalized image) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) The basis of this approach is the use of seed spreading |
| * on a (possibly) sparse set of estimates for the local threshold. |
| * The resulting dense estimates are smoothed by convolution |
| * and used to either threshold the input image or normalize it |
| * with a local transformation that linearly maps the pixels so |
| * that the local threshold estimate becomes constant over the |
| * resulting image. This approach is one of several that |
| * have been suggested (and implemented) by Ray Smith. |
| * (2) You can use either the Sobel or TwoSided edge filters. |
| * The results appear to be similar, using typical values |
| * of edgethresh in the rang 10-20. |
| * (3) To skip the trc enhancement, use gamma = 1.0, minval = 0 |
| * and maxval = 255. |
| * (4) For the normalized image pixd, each pixel is linearly mapped |
| * in such a way that the local threshold is equal to targetthresh. |
| * (5) The full width and height of the convolution kernel |
| * are (2 * smoothx + 1) and (2 * smoothy + 1). |
| * (6) This function can be used with the pixtiling utility if the |
| * images are too large. See pixOtsuAdaptiveThreshold() for |
| * an example of this. |
| */ |
| l_int32 |
| pixThresholdSpreadNorm(PIX *pixs, |
| l_int32 filtertype, |
| l_int32 edgethresh, |
| l_int32 smoothx, |
| l_int32 smoothy, |
| l_float32 gamma, |
| l_int32 minval, |
| l_int32 maxval, |
| l_int32 targetthresh, |
| PIX **ppixth, |
| PIX **ppixb, |
| PIX **ppixd) |
| { |
| l_int32 w, h, d; |
| PIX *pixe, *pixet, *pixsd, *pixg1, *pixg2, *pixth; |
| |
| PROCNAME("pixThresholdSpreadNorm"); |
| |
| if (ppixth) *ppixth = NULL; |
| if (ppixb) *ppixb = NULL; |
| if (ppixd) *ppixd = NULL; |
| 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 (!ppixth && !ppixb && !ppixd) |
| return ERROR_INT("no output requested", procName, 1); |
| if (filtertype != L_SOBEL_EDGE && filtertype != L_TWO_SIDED_EDGE) |
| return ERROR_INT("invalid filter type", procName, 1); |
| |
| /* Get the thresholded edge pixels. These are the ones |
| * that have values in pixs near the local optimal fg/bg threshold. */ |
| if (filtertype == L_SOBEL_EDGE) |
| pixe = pixSobelEdgeFilter(pixs, L_VERTICAL_EDGES); |
| else /* L_TWO_SIDED_EDGE */ |
| pixe = pixTwoSidedEdgeFilter(pixs, L_VERTICAL_EDGES); |
| pixet = pixThresholdToBinary(pixe, edgethresh); |
| pixInvert(pixet, pixet); |
| |
| /* Build a seed image whose only nonzero values are those |
| * values of pixs corresponding to pixels in the fg of pixet. */ |
| pixsd = pixCreateTemplate(pixs); |
| pixCombineMasked(pixsd, pixs, pixet); |
| |
| /* Spread the seed and optionally smooth to reduce noise */ |
| pixg1 = pixSeedspread(pixsd, 4); |
| pixg2 = pixBlockconv(pixg1, smoothx, smoothy); |
| |
| /* Optionally do a gamma enhancement */ |
| pixth = pixGammaTRC(NULL, pixg2, gamma, minval, maxval); |
| |
| /* Do the mapping and thresholding */ |
| if (ppixd) { |
| *ppixd = pixApplyVariableGrayMap(pixs, pixth, targetthresh); |
| if (ppixb) |
| *ppixb = pixThresholdToBinary(*ppixd, targetthresh); |
| } |
| else if (ppixb) |
| *ppixb = pixVarThresholdToBinary(pixs, pixth); |
| |
| if (ppixth) |
| *ppixth = pixth; |
| else |
| pixDestroy(&pixth); |
| |
| pixDestroy(&pixe); |
| pixDestroy(&pixet); |
| pixDestroy(&pixsd); |
| pixDestroy(&pixg1); |
| pixDestroy(&pixg2); |
| return 0; |
| } |
| |
| |
| /*------------------------------------------------------------------* |
| * Adaptive background normalization (flexible adaptaption) * |
| *------------------------------------------------------------------*/ |
| /*! |
| * pixBackgroundNormFlex() |
| * |
| * Input: pixs (8 bpp) |
| * sx, sy (desired tile dimensions; actual size may vary; use |
| * values between 3 and 10) |
| * smoothx, smoothy (half-width of convolution kernel applied to |
| * threshold array: use values between 1 and 3) |
| * delta (difference parameter in basin filling; use 0 |
| * to skip) |
| * Return: pixd (8 bpp, background-normalized), or null on error) |
| * |
| * Notes: |
| * (1) This does adaptation flexibly to a quickly varying background. |
| * For that reason, all input parameters should be small. |
| * (2) sx and sy give the tile size; they should be in [5 - 7]. |
| * (3) The full width and height of the convolution kernel |
| * are (2 * smoothx + 1) and (2 * smoothy + 1). They |
| * should be in [1 - 2]. |
| * (4) Basin filling is used to fill the large fg regions. The |
| * parameter @delta measures the height that the black |
| * background is raised from the local minima. By raising |
| * the background, it is possible to threshold the large |
| * fg regions to foreground. If @delta is too large, |
| * bg regions will be lifted, causing thickening of |
| * the fg regions. Use 0 to skip. |
| */ |
| PIX * |
| pixBackgroundNormFlex(PIX *pixs, |
| l_int32 sx, |
| l_int32 sy, |
| l_int32 smoothx, |
| l_int32 smoothy, |
| l_int32 delta) |
| { |
| l_float32 scalex, scaley; |
| PIX *pixt, *pixsd, *pixmin, *pixbg, *pixbgi, *pixd; |
| |
| PROCNAME("pixBackgroundNormFlex"); |
| |
| if (!pixs || pixGetDepth(pixs) != 8) |
| return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL); |
| if (sx < 3 || sy < 3) |
| return (PIX *)ERROR_PTR("sx and/or sy less than 3", procName, NULL); |
| if (sx > 10 || sy > 10) |
| return (PIX *)ERROR_PTR("sx and/or sy exceed 10", procName, NULL); |
| if (smoothx < 1 || smoothy < 1) |
| return (PIX *)ERROR_PTR("smooth params less than 1", procName, NULL); |
| if (smoothx > 3 || smoothy > 3) |
| return (PIX *)ERROR_PTR("smooth params exceed 3", procName, NULL); |
| |
| /* Generate the bg estimate using smoothed average with subsampling */ |
| scalex = 1. / (l_float32)sx; |
| scaley = 1. / (l_float32)sy; |
| pixt = pixScaleSmooth(pixs, scalex, scaley); |
| |
| /* Do basin filling on the bg estimate if requested */ |
| if (delta <= 0) |
| pixsd = pixClone(pixt); |
| else { |
| pixLocalExtrema(pixt, 0, 0, &pixmin, NULL); |
| pixsd = pixSeedfillGrayBasin(pixmin, pixt, delta, 4); |
| pixDestroy(&pixmin); |
| } |
| pixbg = pixExtendByReplication(pixsd, 1, 1); |
| |
| /* Map the bg to 200 */ |
| pixbgi = pixGetInvBackgroundMap(pixbg, 200, smoothx, smoothy); |
| pixd = pixApplyInvBackgroundGrayMap(pixs, pixbgi, sx, sy); |
| |
| pixDestroy(&pixt); |
| pixDestroy(&pixsd); |
| pixDestroy(&pixbg); |
| pixDestroy(&pixbgi); |
| return pixd; |
| } |
| |
| |
| /*------------------------------------------------------------------* |
| * Adaptive contrast normalization * |
| *------------------------------------------------------------------*/ |
| /*! |
| * pixContrastNorm() |
| * |
| * Input: pixd (<optional> 8 bpp; null or equal to pixs) |
| * pixs (8 bpp, not colormapped) |
| * sx, sy (tile dimensions) |
| * mindiff (minimum difference to accept as valid) |
| * smoothx, smoothy (half-width of convolution kernel applied to |
| * min and max arrays: use 0 for no smoothing) |
| * Return: pixd always |
| * |
| * Notes: |
| * (1) This function adaptively attempts to expand the contrast |
| * to the full dynamic range in each tile. If the contrast in |
| * a tile is smaller than @mindiff, it uses the min and max |
| * pixel values from neighboring tiles. It also can use |
| * convolution to smooth the min and max values from |
| * neighboring tiles. After all that processing, it is |
| * possible that the actual pixel values in the tile are outside |
| * the computed [min ... max] range for local contrast |
| * normalization. Such pixels are taken to be at either 0 |
| * (if below the min) or 255 (if above the max). |
| * (2) pixd can be equal to pixs (in-place operation) or |
| * null (makes a new pixd). |
| * (3) sx and sy give the tile size; they are typically at least 20. |
| * (4) mindiff is used to eliminate results for tiles where it is |
| * likely that either fg or bg is missing. A value around 50 |
| * or more is reasonable. |
| * (5) The full width and height of the convolution kernel |
| * are (2 * smoothx + 1) and (2 * smoothy + 1). Some smoothing |
| * is typically useful, and we limit the smoothing half-widths |
| * to the range from 0 to 8. |
| * (6) A linear TRC (gamma = 1.0) is applied to increase the contrast |
| * in each tile. The result can subsequently be globally corrected, |
| * by applying pixGammaTRC() with arbitrary values of gamma |
| * and the 0 and 255 points of the mapping. |
| */ |
| PIX * |
| pixContrastNorm(PIX *pixd, |
| PIX *pixs, |
| l_int32 sx, |
| l_int32 sy, |
| l_int32 mindiff, |
| l_int32 smoothx, |
| l_int32 smoothy) |
| { |
| PIX *pixmin, *pixmax; |
| |
| PROCNAME("pixContrastNorm"); |
| |
| if (!pixs || pixGetDepth(pixs) != 8) |
| return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, pixd); |
| if (pixd && pixd != pixs) |
| return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd); |
| if (pixGetColormap(pixs)) |
| return (PIX *)ERROR_PTR("pixs is colormapped", procName, pixd); |
| if (sx < 5 || sy < 5) |
| return (PIX *)ERROR_PTR("sx and/or sy less than 5", procName, pixd); |
| if (smoothx < 0 || smoothy < 0) |
| return (PIX *)ERROR_PTR("smooth params less than 0", procName, pixd); |
| if (smoothx > 8 || smoothy > 8) |
| return (PIX *)ERROR_PTR("smooth params exceed 8", procName, pixd); |
| |
| /* Get the min and max pixel values in each tile, and represent |
| * each value as a pixel in pixmin and pixmax, respectively. */ |
| pixMinMaxTiles(pixs, sx, sy, mindiff, smoothx, smoothy, &pixmin, &pixmax); |
| |
| /* For each tile, do a linear expansion of the dynamic range |
| * of pixels so that the min value is mapped to 0 and the |
| * max value is mapped to 255. */ |
| pixd = pixLinearTRCTiled(pixd, pixs, sx, sy, pixmin, pixmax); |
| |
| pixDestroy(&pixmin); |
| pixDestroy(&pixmax); |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixMinMaxTiles() |
| * |
| * Input: pixs (8 bpp, not colormapped) |
| * sx, sy (tile dimensions) |
| * mindiff (minimum difference to accept as valid) |
| * smoothx, smoothy (half-width of convolution kernel applied to |
| * min and max arrays: use 0 for no smoothing) |
| * &pixmin (<return> tiled minima) |
| * &pixmax (<return> tiled maxima) |
| * Return: 0 if OK, 1 on error |
| * |
| * Notes: |
| * (1) This computes filtered and smoothed values for the min and |
| * max pixel values in each tile of the image. |
| * (2) See pixContrastNorm() for usage. |
| */ |
| l_int32 |
| pixMinMaxTiles(PIX *pixs, |
| l_int32 sx, |
| l_int32 sy, |
| l_int32 mindiff, |
| l_int32 smoothx, |
| l_int32 smoothy, |
| PIX **ppixmin, |
| PIX **ppixmax) |
| { |
| l_int32 w, h; |
| PIX *pixmin1, *pixmax1, *pixmin2, *pixmax2; |
| |
| PROCNAME("pixMinMaxTiles"); |
| |
| if (!ppixmin || !ppixmax) |
| return ERROR_INT("&pixmin or &pixmax undefined", procName, 1); |
| *ppixmin = *ppixmax = 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 colormapped", procName, 1); |
| if (sx < 5 || sy < 5) |
| return ERROR_INT("sx and/or sy less than 3", procName, 1); |
| if (smoothx < 0 || smoothy < 0) |
| return ERROR_INT("smooth params less than 0", procName, 1); |
| if (smoothx > 5 || smoothy > 5) |
| return ERROR_INT("smooth params exceed 5", procName, 1); |
| |
| /* Get the min and max values in each tile */ |
| pixmin1 = pixScaleGrayMinMax(pixs, sx, sy, L_CHOOSE_MIN); |
| pixmax1 = pixScaleGrayMinMax(pixs, sx, sy, L_CHOOSE_MAX); |
| |
| pixmin2 = pixExtendByReplication(pixmin1, 1, 1); |
| pixmax2 = pixExtendByReplication(pixmax1, 1, 1); |
| pixDestroy(&pixmin1); |
| pixDestroy(&pixmax1); |
| |
| /* Make sure no value is 0 */ |
| pixAddConstantGray(pixmin2, 1); |
| pixAddConstantGray(pixmax2, 1); |
| |
| /* Generate holes where the contrast is too small */ |
| pixSetLowContrast(pixmin2, pixmax2, mindiff); |
| |
| /* Fill the holes (0 values) */ |
| pixGetDimensions(pixmin2, &w, &h, NULL); |
| pixFillMapHoles(pixmin2, w, h, L_FILL_BLACK); |
| pixFillMapHoles(pixmax2, w, h, L_FILL_BLACK); |
| |
| /* Smooth if requested */ |
| if (smoothx > 0 || smoothy > 0) { |
| smoothx = L_MIN(smoothx, (w - 1) / 2); |
| smoothy = L_MIN(smoothy, (h - 1) / 2); |
| *ppixmin = pixBlockconv(pixmin2, smoothx, smoothy); |
| *ppixmax = pixBlockconv(pixmax2, smoothx, smoothy); |
| } |
| else { |
| *ppixmin = pixClone(pixmin2); |
| *ppixmax = pixClone(pixmax2); |
| } |
| pixDestroy(&pixmin2); |
| pixDestroy(&pixmax2); |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pixSetLowContrast() |
| * |
| * Input: pixs1 (8 bpp) |
| * pixs2 (8 bpp) |
| * mindiff (minimum difference to accept as valid) |
| * Return: 0 if OK; 1 if no pixel diffs are large enough, or on error |
| * |
| * Notes: |
| * (1) This compares corresponding pixels in pixs1 and pixs2. |
| * When they differ by less than @mindiff, set the pixel |
| * values to 0 in each. Each pixel typically represents a tile |
| * in a larger image, and a very small difference between |
| * the min and max in the tile indicates that the min and max |
| * values are not to be trusted. |
| * (2) If contrast (pixel difference) detection is expected to fail, |
| * caller should check return value. |
| */ |
| l_int32 |
| pixSetLowContrast(PIX *pixs1, |
| PIX *pixs2, |
| l_int32 mindiff) |
| { |
| l_int32 i, j, w, h, d, wpl, val1, val2, found; |
| l_uint32 *data1, *data2, *line1, *line2; |
| |
| PROCNAME("pixSetLowContrast"); |
| |
| if (!pixs1 || !pixs2) |
| return ERROR_INT("pixs1 and pixs2 not both defined", procName, 1); |
| if (pixSizesEqual(pixs1, pixs2) == 0) |
| return ERROR_INT("pixs1 and pixs2 not equal size", procName, 1); |
| pixGetDimensions(pixs1, &w, &h, &d); |
| if (d != 8) |
| return ERROR_INT("depth not 8 bpp", procName, 1); |
| if (mindiff > 254) return 0; |
| |
| data1 = pixGetData(pixs1); |
| data2 = pixGetData(pixs2); |
| wpl = pixGetWpl(pixs1); |
| found = 0; /* init to not finding any diffs >= mindiff */ |
| for (i = 0; i < h; i++) { |
| line1 = data1 + i * wpl; |
| line2 = data2 + i * wpl; |
| for (j = 0; j < w; j++) { |
| val1 = GET_DATA_BYTE(line1, j); |
| val2 = GET_DATA_BYTE(line2, j); |
| if (L_ABS(val1 - val2) >= mindiff) { |
| found = 1; |
| break; |
| } |
| } |
| if (found) break; |
| } |
| if (!found) { |
| L_WARNING("no pixel pair diffs as large as mindiff", procName); |
| pixClearAll(pixs1); |
| pixClearAll(pixs2); |
| return 1; |
| } |
| |
| for (i = 0; i < h; i++) { |
| line1 = data1 + i * wpl; |
| line2 = data2 + i * wpl; |
| for (j = 0; j < w; j++) { |
| val1 = GET_DATA_BYTE(line1, j); |
| val2 = GET_DATA_BYTE(line2, j); |
| if (L_ABS(val1 - val2) < mindiff) { |
| SET_DATA_BYTE(line1, j, 0); |
| SET_DATA_BYTE(line2, j, 0); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pixLinearTRCTiled() |
| * |
| * Input: pixd (<optional> 8 bpp) |
| * pixs (8 bpp, not colormapped) |
| * sx, sy (tile dimensions) |
| * pixmin (pix of min values in tiles) |
| * pixmax (pix of max values in tiles) |
| * Return: pixd always |
| * |
| * Notes: |
| * (1) pixd can be equal to pixs (in-place operation) or |
| * null (makes a new pixd). |
| * (2) sx and sy give the tile size; they are typically at least 20. |
| * (3) pixmin and pixmax are generated by pixMinMaxTiles() |
| * (4) For each tile, this does a linear expansion of the dynamic |
| * range so that the min value in the tile becomes 0 and the |
| * max value in the tile becomes 255. |
| * (5) The LUTs that do the mapping are generated as needed |
| * and stored for reuse in an integer array within the ptr array iaa[]. |
| */ |
| PIX * |
| pixLinearTRCTiled(PIX *pixd, |
| PIX *pixs, |
| l_int32 sx, |
| l_int32 sy, |
| PIX *pixmin, |
| PIX *pixmax) |
| { |
| l_int32 i, j, k, m, w, h, wt, ht, wpl, wplt, xoff, yoff; |
| l_int32 minval, maxval, val, sval; |
| l_int32 *ia; |
| l_int32 **iaa; |
| l_uint32 *data, *datamin, *datamax, *line, *tline, *linemin, *linemax; |
| |
| PROCNAME("pixLinearTRCTiled"); |
| |
| if (!pixs || pixGetDepth(pixs) != 8) |
| return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, pixd); |
| if (pixd && pixd != pixs) |
| return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd); |
| if (pixGetColormap(pixs)) |
| return (PIX *)ERROR_PTR("pixs is colormapped", procName, pixd); |
| if (!pixmin || !pixmax) |
| return (PIX *)ERROR_PTR("pixmin & pixmax not defined", procName, pixd); |
| if (sx < 5 || sy < 5) |
| return (PIX *)ERROR_PTR("sx and/or sy less than 5", procName, pixd); |
| |
| pixd = pixCopy(pixd, pixs); |
| iaa = (l_int32 **)CALLOC(256, sizeof(l_int32 *)); |
| pixGetDimensions(pixd, &w, &h, NULL); |
| |
| data = pixGetData(pixd); |
| wpl = pixGetWpl(pixd); |
| datamin = pixGetData(pixmin); |
| datamax = pixGetData(pixmax); |
| wplt = pixGetWpl(pixmin); |
| pixGetDimensions(pixmin, &wt, &ht, NULL); |
| for (i = 0; i < ht; i++) { |
| line = data + sy * i * wpl; |
| linemin = datamin + i * wplt; |
| linemax = datamax + i * wplt; |
| yoff = sy * i; |
| for (j = 0; j < wt; j++) { |
| xoff = sx * j; |
| minval = GET_DATA_BYTE(linemin, j); |
| maxval = GET_DATA_BYTE(linemax, j); |
| if (maxval == minval) { /* this is bad */ |
| /* fprintf(stderr, "should't happen! i,j = %d,%d, minval = %d\n", |
| i, j, minval); */ |
| continue; |
| } |
| ia = iaaGetLinearTRC(iaa, maxval - minval); |
| for (k = 0; k < sy && yoff + k < h; k++) { |
| tline = line + k * wpl; |
| for (m = 0; m < sx && xoff + m < w; m++) { |
| val = GET_DATA_BYTE(tline, xoff + m); |
| sval = val - minval; |
| sval = L_MAX(0, sval); |
| SET_DATA_BYTE(tline, xoff + m, ia[sval]); |
| } |
| } |
| } |
| } |
| |
| for (i = 0; i < 256; i++) |
| if (iaa[i]) FREE(iaa[i]); |
| FREE(iaa); |
| return pixd; |
| } |
| |
| |
| /*! |
| * iaaGetLinearTRC() |
| * |
| * Input: iaa (bare array of ptrs to l_int32) |
| * diff (between min and max pixel values that are |
| * to be mapped to 0 and 255) |
| * Return: ia (LUT with input (val - minval) and output a |
| * value between 0 and 255) |
| */ |
| static l_int32 * |
| iaaGetLinearTRC(l_int32 **iaa, |
| l_int32 diff) |
| { |
| l_int32 i; |
| l_int32 *ia; |
| l_float32 factor; |
| |
| PROCNAME("iaaGetLinearTRC"); |
| |
| if (!iaa) |
| return (l_int32 *)ERROR_PTR("iaa not defined", procName, NULL); |
| |
| if (iaa[diff] != NULL) /* already have it */ |
| return iaa[diff]; |
| |
| if ((ia = (l_int32 *)CALLOC(256, sizeof(l_int32))) == NULL) |
| return (l_int32 *)ERROR_PTR("ia not made", procName, NULL); |
| iaa[diff] = ia; |
| if (diff == 0) { /* shouldn't happen */ |
| for (i = 0; i < 256; i++) |
| ia[i] = 128; |
| } |
| else { |
| factor = 255. / (l_float32)diff; |
| for (i = 0; i < diff + 1; i++) |
| ia[i] = (l_int32)(factor * i + 0.5); |
| for (i = diff + 1; i < 256; i++) |
| ia[i] = 255; |
| } |
| |
| return ia; |
| } |
| |
| |