Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/thirdparty/leptonica/src/adaptmap.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 1:1d09e1dec1d9 | 2:b50eed0cc0ef |
|---|---|
| 1 /*====================================================================* | |
| 2 - Copyright (C) 2001 Leptonica. All rights reserved. | |
| 3 - | |
| 4 - Redistribution and use in source and binary forms, with or without | |
| 5 - modification, are permitted provided that the following conditions | |
| 6 - are met: | |
| 7 - 1. Redistributions of source code must retain the above copyright | |
| 8 - notice, this list of conditions and the following disclaimer. | |
| 9 - 2. Redistributions in binary form must reproduce the above | |
| 10 - copyright notice, this list of conditions and the following | |
| 11 - disclaimer in the documentation and/or other materials | |
| 12 - provided with the distribution. | |
| 13 - | |
| 14 - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 15 - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 16 - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 17 - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY | |
| 18 - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| 19 - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| 20 - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| 21 - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
| 22 - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
| 23 - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| 24 - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 25 *====================================================================*/ | |
| 26 | |
| 27 /*! | |
| 28 * \file adaptmap.c | |
| 29 * <pre> | |
| 30 * | |
| 31 * ------------------------------------------------------------------- | |
| 32 * | |
| 33 * Image binarization algorithms are found in: | |
| 34 * grayquant.c: standard, simple, general grayscale quantization | |
| 35 * adaptmap.c: local adaptive; mostly gray-to-gray in preparation | |
| 36 * for binarization | |
| 37 * binarize.c: special binarization methods, locally adaptive. | |
| 38 * pageseg.c: locally adaptive cleaning operation with several options | |
| 39 * | |
| 40 * ------------------------------------------------------------------- | |
| 41 * | |
| 42 * Clean background to white using background normalization | |
| 43 * PIX *pixCleanBackgroundToWhite() | |
| 44 * | |
| 45 * Adaptive background normalization (top-level functions) | |
| 46 * PIX *pixBackgroundNormSimple() 8 and 32 bpp | |
| 47 * PIX *pixBackgroundNorm() 8 and 32 bpp | |
| 48 * PIX *pixBackgroundNormMorph() 8 and 32 bpp | |
| 49 * | |
| 50 * Arrays of inverted background values for normalization (16 bpp) | |
| 51 * l_int32 pixBackgroundNormGrayArray() 8 bpp input | |
| 52 * l_int32 pixBackgroundNormRGBArrays() 32 bpp input | |
| 53 * l_int32 pixBackgroundNormGrayArrayMorph() 8 bpp input | |
| 54 * l_int32 pixBackgroundNormRGBArraysMorph() 32 bpp input | |
| 55 * | |
| 56 * Measurement of local background | |
| 57 * l_int32 pixGetBackgroundGrayMap() 8 bpp | |
| 58 * l_int32 pixGetBackgroundRGBMap() 32 bpp | |
| 59 * l_int32 pixGetBackgroundGrayMapMorph() 8 bpp | |
| 60 * l_int32 pixGetBackgroundRGBMapMorph() 32 bpp | |
| 61 * l_int32 pixFillMapHoles() | |
| 62 * PIX *pixExtendByReplication() 8 bpp | |
| 63 * l_int32 pixSmoothConnectedRegions() 8 bpp | |
| 64 * | |
| 65 * Measurement of local foreground | |
| 66 * l_int32 pixGetForegroundGrayMap() 8 bpp | |
| 67 * | |
| 68 * Generate inverted background map for each component | |
| 69 * PIX *pixGetInvBackgroundMap() 16 bpp | |
| 70 * | |
| 71 * Apply inverse background map to image | |
| 72 * PIX *pixApplyInvBackgroundGrayMap() 8 bpp | |
| 73 * PIX *pixApplyInvBackgroundRGBMap() 32 bpp | |
| 74 * | |
| 75 * Apply variable map | |
| 76 * PIX *pixApplyVariableGrayMap() 8 bpp | |
| 77 * | |
| 78 * Non-adaptive (global) mapping | |
| 79 * PIX *pixGlobalNormRGB() 32 bpp or cmapped | |
| 80 * PIX *pixGlobalNormNoSatRGB() 32 bpp | |
| 81 * | |
| 82 * Adaptive threshold spread normalization | |
| 83 * l_int32 pixThresholdSpreadNorm() 8 bpp | |
| 84 * | |
| 85 * Adaptive background normalization (flexible adaptaption) | |
| 86 * PIX *pixBackgroundNormFlex() 8 bpp | |
| 87 * | |
| 88 * Adaptive contrast normalization | |
| 89 * PIX *pixContrastNorm() 8 bpp | |
| 90 * static l_int32 pixMinMaxTiles() | |
| 91 * static l_int32 pixSetLowContrast() | |
| 92 * static PIX *pixLinearTRCTiled() | |
| 93 * static l_int32 *iaaGetLinearTRC() | |
| 94 * | |
| 95 * Adaptive normalization with MinMax conversion of RGB to gray, | |
| 96 * contrast enhancement and optional 2x upscale binarization | |
| 97 * PIX *pixBackgroundNormTo1MinMax() | |
| 98 * PIX *pixConvertTo8MinMax() | |
| 99 * static l_int32 *pixSelectiveContrastMod() | |
| 100 * | |
| 101 * Background normalization is done by generating a reduced map (or set | |
| 102 * of maps) representing the estimated background value of the | |
| 103 * input image, and using this to shift the pixel values so that | |
| 104 * this background value is set to some constant value. | |
| 105 * | |
| 106 * Specifically, normalization has 3 steps: | |
| 107 * (1) Generate a background map at a reduced scale. | |
| 108 * (2) Make the array of inverted background values by inverting | |
| 109 * the map. The result is an array of local multiplicative factors. | |
| 110 * (3) Apply this inverse background map to the image | |
| 111 * | |
| 112 * The inverse background arrays can be generated in two different ways here: | |
| 113 * (1) Remove the 'foreground' pixels and average over the remaining | |
| 114 * pixels in each tile. Propagate values into tiles where | |
| 115 * values have not been assigned, either because there was not | |
| 116 * enough background in the tile or because the tile is covered | |
| 117 * by a foreground region described by an image mask. | |
| 118 * After the background map is made, the inverse map is generated by | |
| 119 * smoothing over some number of adjacent tiles | |
| 120 * (block convolution) and then inverting. | |
| 121 * (2) Remove the foreground pixels using a morphological closing | |
| 122 * on a subsampled version of the image. Propagate values | |
| 123 * into pixels covered by an optional image mask. Invert the | |
| 124 * background map without preconditioning by convolutional smoothing. | |
| 125 * | |
| 126 * Other methods for adaptively normalizing the image are also given here. | |
| 127 * | |
| 128 * (1) pixThresholdSpreadNorm() computes a local threshold over the image | |
| 129 * and normalizes the input pixel values so that this computed threshold | |
| 130 * is a constant across the entire image. | |
| 131 * | |
| 132 * (2) pixContrastNorm() computes and applies a local TRC so that the | |
| 133 * local dynamic range is expanded to the full 8 bits, where the | |
| 134 * darkest pixels are mapped to 0 and the lightest to 255. This is | |
| 135 * useful for improving the appearance of pages with very light | |
| 136 * foreground or very dark background, and where the local TRC | |
| 137 * function doesn't change rapidly with position. | |
| 138 * | |
| 139 * Adaptive binarization is done in two steps: | |
| 140 * (1) Background normalization by some method | |
| 141 * (2) Global thresholding with a value appropriate to the normalization. | |
| 142 * There are several high-level functions in leptonica for doing adaptive | |
| 143 * binarization on grayscale and color images, such as: | |
| 144 * * pixAdaptThresholdToBinary() (in grayquant.c) | |
| 145 * * pixConvertTo1Adaptive() (in pixconv.c) | |
| 146 * * pixCleanImage() (in pageseg.c) | |
| 147 * </pre> | |
| 148 */ | |
| 149 | |
| 150 #ifdef HAVE_CONFIG_H | |
| 151 #include <config_auto.h> | |
| 152 #endif /* HAVE_CONFIG_H */ | |
| 153 | |
| 154 #include "allheaders.h" | |
| 155 | |
| 156 /* Default input parameters for pixBackgroundNormSimple() | |
| 157 * Notes: | |
| 158 * (1) mincount must never exceed the tile area (width * height) | |
| 159 * (2) bgval must be sufficiently below 255 to avoid accidental | |
| 160 * saturation; otherwise it should be large to avoid | |
| 161 * shrinking the dynamic range | |
| 162 * (3) results should otherwise not be sensitive to these values | |
| 163 */ | |
| 164 static const l_int32 DefaultTileWidth = 10; /*!< default tile width */ | |
| 165 static const l_int32 DefaultTileHeight = 15; /*!< default tile height */ | |
| 166 static const l_int32 DefaultFgThreshold = 60; /*!< default fg threshold */ | |
| 167 static const l_int32 DefaultMinCount = 40; /*!< default minimum count */ | |
| 168 static const l_int32 DefaultBgVal = 200; /*!< default bg value */ | |
| 169 static const l_int32 DefaultXSmoothSize = 2; /*!< default x smooth size */ | |
| 170 static const l_int32 DefaultYSmoothSize = 1; /*!< default y smooth size */ | |
| 171 | |
| 172 static l_int32 pixMinMaxTiles(PIX *pixs, l_int32 sx, l_int32 sy, | |
| 173 l_int32 mindiff, l_int32 smoothx, l_int32 smoothy, | |
| 174 PIX **ppixmin, PIX **ppixmax); | |
| 175 static l_int32 pixSetLowContrast(PIX *pixs1, PIX *pixs2, l_int32 mindiff); | |
| 176 static PIX *pixLinearTRCTiled(PIX *pixd, PIX *pixs, l_int32 sx, l_int32 sy, | |
| 177 PIX *pixmin, PIX *pixmax); | |
| 178 static l_int32 *iaaGetLinearTRC(l_int32 **iaa, l_int32 diff); | |
| 179 | |
| 180 static l_ok pixSelectiveContrastMod(PIX *pixs, l_int32 contrast); | |
| 181 | |
| 182 #ifndef NO_CONSOLE_IO | |
| 183 #define DEBUG_GLOBAL 0 /*!< set to 1 to debug pixGlobalNormNoSatRGB() */ | |
| 184 #endif /* ~NO_CONSOLE_IO */ | |
| 185 | |
| 186 /*------------------------------------------------------------------* | |
| 187 * Clean background to white using background normalization * | |
| 188 *------------------------------------------------------------------*/ | |
| 189 /*! | |
| 190 * \brief pixCleanBackgroundToWhite() | |
| 191 * | |
| 192 * \param[in] pixs 8 bpp grayscale or 32 bpp rgb | |
| 193 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null | |
| 194 * \param[in] pixg [optional] 8 bpp grayscale version; can be null | |
| 195 * \param[in] gamma gamma correction; must be > 0.0; typically ~1.0 | |
| 196 * \param[in] blackval dark value to set to black (0) | |
| 197 * \param[in] whiteval light value to set to white (255) | |
| 198 * \return pixd 8 bpp or 32 bpp rgb, or NULL on error | |
| 199 * | |
| 200 * <pre> | |
| 201 * Notes: | |
| 202 * (1) This is a simplified interface for cleaning an image. | |
| 203 * For comparison, see pixAdaptThresholdToBinaryGen(). | |
| 204 * (2) The suggested default values for the input parameters are: | |
| 205 * gamma: 1.0 (reduce this to increase the contrast; e.g., | |
| 206 * for light text) | |
| 207 * blackval 70 (a bit more than 60) | |
| 208 * whiteval 190 (a bit less than 200) | |
| 209 * (3) Note: the whiteval must not exceed 200, which is the value | |
| 210 * that the background is set to in pixBackgroundNormSimple(). | |
| 211 * </pre> | |
| 212 */ | |
| 213 PIX * | |
| 214 pixCleanBackgroundToWhite(PIX *pixs, | |
| 215 PIX *pixim, | |
| 216 PIX *pixg, | |
| 217 l_float32 gamma, | |
| 218 l_int32 blackval, | |
| 219 l_int32 whiteval) | |
| 220 { | |
| 221 l_int32 d; | |
| 222 PIX *pixd; | |
| 223 | |
| 224 if (!pixs) | |
| 225 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 226 d = pixGetDepth(pixs); | |
| 227 if (d != 8 && d != 32) | |
| 228 return (PIX *)ERROR_PTR("depth not 8 or 32", __func__, NULL); | |
| 229 if (whiteval > 200) { | |
| 230 L_WARNING("white value %d must not exceed 200; reset to 190", | |
| 231 __func__, whiteval); | |
| 232 whiteval = 190; | |
| 233 } | |
| 234 | |
| 235 pixd = pixBackgroundNormSimple(pixs, pixim, pixg); | |
| 236 if (!pixd) | |
| 237 return (PIX *)ERROR_PTR("background norm failedd", __func__, NULL); | |
| 238 pixGammaTRC(pixd, pixd, gamma, blackval, whiteval); | |
| 239 return pixd; | |
| 240 } | |
| 241 | |
| 242 | |
| 243 /*------------------------------------------------------------------* | |
| 244 * Adaptive background normalization * | |
| 245 *------------------------------------------------------------------*/ | |
| 246 /*! | |
| 247 * \brief pixBackgroundNormSimple() | |
| 248 * | |
| 249 * \param[in] pixs 8 bpp grayscale or 32 bpp rgb | |
| 250 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null | |
| 251 * \param[in] pixg [optional] 8 bpp grayscale version; can be null | |
| 252 * \return pixd 8 bpp or 32 bpp rgb, or NULL on error | |
| 253 * | |
| 254 * <pre> | |
| 255 * Notes: | |
| 256 * (1) This is a simplified interface to pixBackgroundNorm(), | |
| 257 * where seven parameters are defaulted. | |
| 258 * (2) The input image is either grayscale or rgb. | |
| 259 * (3) See pixBackgroundNorm() for usage and function. | |
| 260 * </pre> | |
| 261 */ | |
| 262 PIX * | |
| 263 pixBackgroundNormSimple(PIX *pixs, | |
| 264 PIX *pixim, | |
| 265 PIX *pixg) | |
| 266 { | |
| 267 return pixBackgroundNorm(pixs, pixim, pixg, | |
| 268 DefaultTileWidth, DefaultTileHeight, | |
| 269 DefaultFgThreshold, DefaultMinCount, | |
| 270 DefaultBgVal, DefaultXSmoothSize, | |
| 271 DefaultYSmoothSize); | |
| 272 } | |
| 273 | |
| 274 | |
| 275 /*! | |
| 276 * \brief pixBackgroundNorm() | |
| 277 * | |
| 278 * \param[in] pixs 8 bpp grayscale or 32 bpp rgb | |
| 279 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null | |
| 280 * \param[in] pixg [optional] 8 bpp grayscale version; can be null | |
| 281 * \param[in] sx, sy tile size in pixels | |
| 282 * \param[in] thresh threshold for determining foreground | |
| 283 * \param[in] mincount min threshold on counts in a tile | |
| 284 * \param[in] bgval target bg val; typ. > 128 | |
| 285 * \param[in] smoothx half-width of block convolution kernel width | |
| 286 * \param[in] smoothy half-width of block convolution kernel height | |
| 287 * \return pixd 8 bpp or 32 bpp rgb, or NULL on error | |
| 288 * | |
| 289 * <pre> | |
| 290 * Notes: | |
| 291 * (1) This is a top-level interface for normalizing the image intensity | |
| 292 * by mapping the image so that the background is near the input | |
| 293 * value %bgval. | |
| 294 * (2) The input image is either grayscale or rgb. | |
| 295 * (3) For each component in the input image, the background value | |
| 296 * in each tile is estimated using the values in the tile that | |
| 297 * are not part of the foreground, where the foreground is | |
| 298 * determined by %thresh. | |
| 299 * (4) An optional binary mask can be specified, with the foreground | |
| 300 * pixels typically over image regions. The resulting background | |
| 301 * map values will be determined by surrounding pixels that are | |
| 302 * not under the mask foreground. The origin (0,0) of this mask | |
| 303 * is assumed to be aligned with the origin of the input image. | |
| 304 * This binary mask must not fully cover pixs, because then there | |
| 305 * will be no pixels in the input image available to compute | |
| 306 * the background. | |
| 307 * (5) An optional grayscale version of the input pixs can be supplied. | |
| 308 * The only reason to do this is if the input is RGB and this | |
| 309 * grayscale version can be used elsewhere. If the input is RGB | |
| 310 * and this is not supplied, it is made internally using only | |
| 311 * the green component, and destroyed after use. | |
| 312 * (6) The dimensions of the pixel tile (%sx, %sy) give the amount | |
| 313 * by which the map is reduced in size from the input image. | |
| 314 * (7) The input image is binarized using %thresh, in order to | |
| 315 * locate the foreground components. If this is set too low, | |
| 316 * some actual foreground may be used to determine the maps; | |
| 317 * if set too high, there may not be enough background | |
| 318 * to determine the map values accurately. Typically, it is | |
| 319 * better to err by setting the threshold too high. | |
| 320 * (8) A %mincount threshold is a minimum count of pixels in a | |
| 321 * tile for which a background reading is made, in order for that | |
| 322 * pixel in the map to be valid. This number should perhaps be | |
| 323 * at least 1/3 the size of the tile. | |
| 324 * (9) A %bgval target background value for the normalized image. This | |
| 325 * should be at least 128. If set too close to 255, some | |
| 326 * clipping will occur in the result. It is recommended to use | |
| 327 * %bgval = 200. | |
| 328 * (10) Two factors, %smoothx and %smoothy, are input for smoothing | |
| 329 * the map. Each low-pass filter kernel dimension is | |
| 330 * is 2 * (smoothing factor) + 1, so a | |
| 331 * value of 0 means no smoothing. A value of 1 or 2 is recommended. | |
| 332 * (11) See pixCleanBackgroundToWhite(). The recommended value for %bgval | |
| 333 * is 200. As done there, pixBackgroundNorm() is typically followed | |
| 334 * by pixGammaTRC(), where the maxval must not not exceed %bgval. | |
| 335 * </pre> | |
| 336 */ | |
| 337 PIX * | |
| 338 pixBackgroundNorm(PIX *pixs, | |
| 339 PIX *pixim, | |
| 340 PIX *pixg, | |
| 341 l_int32 sx, | |
| 342 l_int32 sy, | |
| 343 l_int32 thresh, | |
| 344 l_int32 mincount, | |
| 345 l_int32 bgval, | |
| 346 l_int32 smoothx, | |
| 347 l_int32 smoothy) | |
| 348 { | |
| 349 l_int32 d, allfg; | |
| 350 PIX *pixm, *pixmi, *pixd; | |
| 351 PIX *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi; | |
| 352 | |
| 353 if (!pixs) | |
| 354 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 355 d = pixGetDepth(pixs); | |
| 356 if (d != 8 && d != 32) | |
| 357 return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL); | |
| 358 if (sx < 4 || sy < 4) | |
| 359 return (PIX *)ERROR_PTR("sx and sy must be >= 4", __func__, NULL); | |
| 360 if (mincount > sx * sy) { | |
| 361 L_WARNING("mincount too large for tile size\n", __func__); | |
| 362 mincount = (sx * sy) / 3; | |
| 363 } | |
| 364 | |
| 365 /* If pixim exists, verify that it is not all foreground. */ | |
| 366 if (pixim) { | |
| 367 pixInvert(pixim, pixim); | |
| 368 pixZero(pixim, &allfg); | |
| 369 pixInvert(pixim, pixim); | |
| 370 if (allfg) | |
| 371 return (PIX *)ERROR_PTR("pixim all foreground", __func__, NULL); | |
| 372 } | |
| 373 | |
| 374 pixd = NULL; | |
| 375 if (d == 8) { | |
| 376 pixm = NULL; | |
| 377 pixGetBackgroundGrayMap(pixs, pixim, sx, sy, thresh, mincount, &pixm); | |
| 378 if (!pixm) { | |
| 379 L_WARNING("map not made; return a copy of the source\n", __func__); | |
| 380 return pixCopy(NULL, pixs); | |
| 381 } | |
| 382 | |
| 383 pixmi = pixGetInvBackgroundMap(pixm, bgval, smoothx, smoothy); | |
| 384 if (!pixmi) { | |
| 385 L_WARNING("pixmi not made; return a copy of source\n", __func__); | |
| 386 pixDestroy(&pixm); | |
| 387 return pixCopy(NULL, pixs); | |
| 388 } else { | |
| 389 pixd = pixApplyInvBackgroundGrayMap(pixs, pixmi, sx, sy); | |
| 390 } | |
| 391 | |
| 392 pixDestroy(&pixm); | |
| 393 pixDestroy(&pixmi); | |
| 394 } | |
| 395 else { | |
| 396 pixmr = pixmg = pixmb = NULL; | |
| 397 pixGetBackgroundRGBMap(pixs, pixim, pixg, sx, sy, thresh, | |
| 398 mincount, &pixmr, &pixmg, &pixmb); | |
| 399 if (!pixmr || !pixmg || !pixmb) { | |
| 400 pixDestroy(&pixmr); | |
| 401 pixDestroy(&pixmg); | |
| 402 pixDestroy(&pixmb); | |
| 403 L_WARNING("map not made; return a copy of the source\n", __func__); | |
| 404 return pixCopy(NULL, pixs); | |
| 405 } | |
| 406 | |
| 407 pixmri = pixGetInvBackgroundMap(pixmr, bgval, smoothx, smoothy); | |
| 408 pixmgi = pixGetInvBackgroundMap(pixmg, bgval, smoothx, smoothy); | |
| 409 pixmbi = pixGetInvBackgroundMap(pixmb, bgval, smoothx, smoothy); | |
| 410 if (!pixmri || !pixmgi || !pixmbi) { | |
| 411 L_WARNING("not all pixm*i are made; return src copy\n", __func__); | |
| 412 pixd = pixCopy(NULL, pixs); | |
| 413 } else { | |
| 414 pixd = pixApplyInvBackgroundRGBMap(pixs, pixmri, pixmgi, pixmbi, | |
| 415 sx, sy); | |
| 416 } | |
| 417 | |
| 418 pixDestroy(&pixmr); | |
| 419 pixDestroy(&pixmg); | |
| 420 pixDestroy(&pixmb); | |
| 421 pixDestroy(&pixmri); | |
| 422 pixDestroy(&pixmgi); | |
| 423 pixDestroy(&pixmbi); | |
| 424 } | |
| 425 | |
| 426 if (!pixd) | |
| 427 ERROR_PTR("pixd not made", __func__, NULL); | |
| 428 pixCopyResolution(pixd, pixs); | |
| 429 return pixd; | |
| 430 } | |
| 431 | |
| 432 | |
| 433 /*! | |
| 434 * \brief pixBackgroundNormMorph() | |
| 435 * | |
| 436 * \param[in] pixs 8 bpp grayscale or 32 bpp rgb | |
| 437 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null | |
| 438 * \param[in] reduction at which morph closings are done; between 2 and 16 | |
| 439 * \param[in] size of square Sel for the closing; use an odd number | |
| 440 * \param[in] bgval target bg val; typ. > 128 | |
| 441 * \return pixd 8 bpp, or NULL on error | |
| 442 * | |
| 443 * <pre> | |
| 444 * Notes: | |
| 445 * (1) This is a top-level interface for normalizing the image intensity | |
| 446 * by mapping the image so that the background is near the input | |
| 447 * value 'bgval'. | |
| 448 * (2) The input image is either grayscale or rgb. | |
| 449 * (3) For each component in the input image, the background value | |
| 450 * is estimated using a grayscale closing; hence the 'Morph' | |
| 451 * in the function name. | |
| 452 * (4) An optional binary mask can be specified, with the foreground | |
| 453 * pixels typically over image regions. The resulting background | |
| 454 * map values will be determined by surrounding pixels that are | |
| 455 * not under the mask foreground. The origin (0,0) of this mask | |
| 456 * is assumed to be aligned with the origin of the input image. | |
| 457 * This binary mask must not fully cover pixs, because then there | |
| 458 * will be no pixels in the input image available to compute | |
| 459 * the background. | |
| 460 * (5) The map is computed at reduced size (given by 'reduction') | |
| 461 * from the input pixs and optional pixim. At this scale, | |
| 462 * pixs is closed to remove the background, using a square Sel | |
| 463 * of odd dimension. The product of reduction * size should be | |
| 464 * large enough to remove most of the text foreground. | |
| 465 * (6) No convolutional smoothing needs to be done on the map before | |
| 466 * inverting it. | |
| 467 * (7) A 'bgval' target background value for the normalized image. This | |
| 468 * should be at least 128. If set too close to 255, some | |
| 469 * clipping will occur in the result. | |
| 470 * </pre> | |
| 471 */ | |
| 472 PIX * | |
| 473 pixBackgroundNormMorph(PIX *pixs, | |
| 474 PIX *pixim, | |
| 475 l_int32 reduction, | |
| 476 l_int32 size, | |
| 477 l_int32 bgval) | |
| 478 { | |
| 479 l_int32 d, allfg; | |
| 480 PIX *pixm, *pixmi, *pixd; | |
| 481 PIX *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi; | |
| 482 | |
| 483 if (!pixs) | |
| 484 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 485 d = pixGetDepth(pixs); | |
| 486 if (d != 8 && d != 32) | |
| 487 return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL); | |
| 488 if (reduction < 2 || reduction > 16) | |
| 489 return (PIX *)ERROR_PTR("reduction must be between 2 and 16", | |
| 490 __func__, NULL); | |
| 491 | |
| 492 /* If pixim exists, verify that it is not all foreground. */ | |
| 493 if (pixim) { | |
| 494 pixInvert(pixim, pixim); | |
| 495 pixZero(pixim, &allfg); | |
| 496 pixInvert(pixim, pixim); | |
| 497 if (allfg) | |
| 498 return (PIX *)ERROR_PTR("pixim all foreground", __func__, NULL); | |
| 499 } | |
| 500 | |
| 501 pixd = NULL; | |
| 502 if (d == 8) { | |
| 503 pixGetBackgroundGrayMapMorph(pixs, pixim, reduction, size, &pixm); | |
| 504 if (!pixm) | |
| 505 return (PIX *)ERROR_PTR("pixm not made", __func__, NULL); | |
| 506 pixmi = pixGetInvBackgroundMap(pixm, bgval, 0, 0); | |
| 507 if (!pixmi) | |
| 508 ERROR_PTR("pixmi not made", __func__, NULL); | |
| 509 else | |
| 510 pixd = pixApplyInvBackgroundGrayMap(pixs, pixmi, | |
| 511 reduction, reduction); | |
| 512 pixDestroy(&pixm); | |
| 513 pixDestroy(&pixmi); | |
| 514 } | |
| 515 else { /* d == 32 */ | |
| 516 pixmr = pixmg = pixmb = NULL; | |
| 517 pixGetBackgroundRGBMapMorph(pixs, pixim, reduction, size, | |
| 518 &pixmr, &pixmg, &pixmb); | |
| 519 if (!pixmr || !pixmg || !pixmb) { | |
| 520 pixDestroy(&pixmr); | |
| 521 pixDestroy(&pixmg); | |
| 522 pixDestroy(&pixmb); | |
| 523 return (PIX *)ERROR_PTR("not all pixm*", __func__, NULL); | |
| 524 } | |
| 525 | |
| 526 pixmri = pixGetInvBackgroundMap(pixmr, bgval, 0, 0); | |
| 527 pixmgi = pixGetInvBackgroundMap(pixmg, bgval, 0, 0); | |
| 528 pixmbi = pixGetInvBackgroundMap(pixmb, bgval, 0, 0); | |
| 529 if (!pixmri || !pixmgi || !pixmbi) | |
| 530 ERROR_PTR("not all pixm*i are made", __func__, NULL); | |
| 531 else | |
| 532 pixd = pixApplyInvBackgroundRGBMap(pixs, pixmri, pixmgi, pixmbi, | |
| 533 reduction, reduction); | |
| 534 | |
| 535 pixDestroy(&pixmr); | |
| 536 pixDestroy(&pixmg); | |
| 537 pixDestroy(&pixmb); | |
| 538 pixDestroy(&pixmri); | |
| 539 pixDestroy(&pixmgi); | |
| 540 pixDestroy(&pixmbi); | |
| 541 } | |
| 542 | |
| 543 if (!pixd) | |
| 544 ERROR_PTR("pixd not made", __func__, NULL); | |
| 545 pixCopyResolution(pixd, pixs); | |
| 546 return pixd; | |
| 547 } | |
| 548 | |
| 549 | |
| 550 /*-------------------------------------------------------------------------* | |
| 551 * Arrays of inverted background values for normalization * | |
| 552 *-------------------------------------------------------------------------* | |
| 553 * Notes for these four functions: * | |
| 554 * (1) They are useful if you need to save the actual mapping array. * | |
| 555 * (2) They could be used in the top-level functions but are * | |
| 556 * not because their use makes those functions less clear. * | |
| 557 * (3) Each component in the input pixs generates a 16 bpp pix array. * | |
| 558 *-------------------------------------------------------------------------*/ | |
| 559 /*! | |
| 560 * \brief pixBackgroundNormGrayArray() | |
| 561 * | |
| 562 * \param[in] pixs 8 bpp grayscale | |
| 563 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null | |
| 564 * \param[in] sx, sy tile size in pixels | |
| 565 * \param[in] thresh threshold for determining foreground | |
| 566 * \param[in] mincount min threshold on counts in a tile | |
| 567 * \param[in] bgval target bg val; typ. > 128 | |
| 568 * \param[in] smoothx half-width of block convolution kernel width | |
| 569 * \param[in] smoothy half-width of block convolution kernel height | |
| 570 * \param[out] ppixd 16 bpp array of inverted background value | |
| 571 * \return 0 if OK, 1 on error | |
| 572 * | |
| 573 * <pre> | |
| 574 * Notes: | |
| 575 * (1) See notes in pixBackgroundNorm(). | |
| 576 * (2) This returns a 16 bpp pix that can be used by | |
| 577 * pixApplyInvBackgroundGrayMap() to generate a normalized version | |
| 578 * of the input pixs. | |
| 579 * </pre> | |
| 580 */ | |
| 581 l_ok | |
| 582 pixBackgroundNormGrayArray(PIX *pixs, | |
| 583 PIX *pixim, | |
| 584 l_int32 sx, | |
| 585 l_int32 sy, | |
| 586 l_int32 thresh, | |
| 587 l_int32 mincount, | |
| 588 l_int32 bgval, | |
| 589 l_int32 smoothx, | |
| 590 l_int32 smoothy, | |
| 591 PIX **ppixd) | |
| 592 { | |
| 593 l_int32 allfg; | |
| 594 PIX *pixm; | |
| 595 | |
| 596 if (!ppixd) | |
| 597 return ERROR_INT("&pixd not defined", __func__, 1); | |
| 598 *ppixd = NULL; | |
| 599 if (!pixs || pixGetDepth(pixs) != 8) | |
| 600 return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1); | |
| 601 if (pixGetColormap(pixs)) | |
| 602 return ERROR_INT("pixs is colormapped", __func__, 1); | |
| 603 if (pixim && pixGetDepth(pixim) != 1) | |
| 604 return ERROR_INT("pixim not 1 bpp", __func__, 1); | |
| 605 if (sx < 4 || sy < 4) | |
| 606 return ERROR_INT("sx and sy must be >= 4", __func__, 1); | |
| 607 if (mincount > sx * sy) { | |
| 608 L_WARNING("mincount too large for tile size\n", __func__); | |
| 609 mincount = (sx * sy) / 3; | |
| 610 } | |
| 611 | |
| 612 /* If pixim exists, verify that it is not all foreground. */ | |
| 613 if (pixim) { | |
| 614 pixInvert(pixim, pixim); | |
| 615 pixZero(pixim, &allfg); | |
| 616 pixInvert(pixim, pixim); | |
| 617 if (allfg) | |
| 618 return ERROR_INT("pixim all foreground", __func__, 1); | |
| 619 } | |
| 620 | |
| 621 pixGetBackgroundGrayMap(pixs, pixim, sx, sy, thresh, mincount, &pixm); | |
| 622 if (!pixm) | |
| 623 return ERROR_INT("pixm not made", __func__, 1); | |
| 624 *ppixd = pixGetInvBackgroundMap(pixm, bgval, smoothx, smoothy); | |
| 625 pixCopyResolution(*ppixd, pixs); | |
| 626 pixDestroy(&pixm); | |
| 627 return 0; | |
| 628 } | |
| 629 | |
| 630 | |
| 631 /*! | |
| 632 * \brief pixBackgroundNormRGBArrays() | |
| 633 * | |
| 634 * \param[in] pixs 32 bpp rgb | |
| 635 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null | |
| 636 * \param[in] pixg [optional] 8 bpp grayscale version; can be null | |
| 637 * \param[in] sx, sy tile size in pixels | |
| 638 * \param[in] thresh threshold for determining foreground | |
| 639 * \param[in] mincount min threshold on counts in a tile | |
| 640 * \param[in] bgval target bg val; typ. > 128 | |
| 641 * \param[in] smoothx half-width of block convolution kernel width | |
| 642 * \param[in] smoothy half-width of block convolution kernel height | |
| 643 * \param[out] ppixr 16 bpp array of inverted R background value | |
| 644 * \param[out] ppixg 16 bpp array of inverted G background value | |
| 645 * \param[out] ppixb 16 bpp array of inverted B background value | |
| 646 * \return 0 if OK, 1 on error | |
| 647 * | |
| 648 * <pre> | |
| 649 * Notes: | |
| 650 * (1) See notes in pixBackgroundNorm(). | |
| 651 * (2) This returns a set of three 16 bpp pix that can be used by | |
| 652 * pixApplyInvBackgroundGrayMap() to generate a normalized version | |
| 653 * of each component of the input pixs. | |
| 654 * </pre> | |
| 655 */ | |
| 656 l_ok | |
| 657 pixBackgroundNormRGBArrays(PIX *pixs, | |
| 658 PIX *pixim, | |
| 659 PIX *pixg, | |
| 660 l_int32 sx, | |
| 661 l_int32 sy, | |
| 662 l_int32 thresh, | |
| 663 l_int32 mincount, | |
| 664 l_int32 bgval, | |
| 665 l_int32 smoothx, | |
| 666 l_int32 smoothy, | |
| 667 PIX **ppixr, | |
| 668 PIX **ppixg, | |
| 669 PIX **ppixb) | |
| 670 { | |
| 671 l_int32 allfg; | |
| 672 PIX *pixmr, *pixmg, *pixmb; | |
| 673 | |
| 674 if (!ppixr || !ppixg || !ppixb) | |
| 675 return ERROR_INT("&pixr, &pixg, &pixb not all defined", __func__, 1); | |
| 676 *ppixr = *ppixg = *ppixb = NULL; | |
| 677 if (!pixs) | |
| 678 return ERROR_INT("pixs not defined", __func__, 1); | |
| 679 if (pixGetDepth(pixs) != 32) | |
| 680 return ERROR_INT("pixs not 32 bpp", __func__, 1); | |
| 681 if (pixim && pixGetDepth(pixim) != 1) | |
| 682 return ERROR_INT("pixim not 1 bpp", __func__, 1); | |
| 683 if (sx < 4 || sy < 4) | |
| 684 return ERROR_INT("sx and sy must be >= 4", __func__, 1); | |
| 685 if (mincount > sx * sy) { | |
| 686 L_WARNING("mincount too large for tile size\n", __func__); | |
| 687 mincount = (sx * sy) / 3; | |
| 688 } | |
| 689 | |
| 690 /* If pixim exists, verify that it is not all foreground. */ | |
| 691 if (pixim) { | |
| 692 pixInvert(pixim, pixim); | |
| 693 pixZero(pixim, &allfg); | |
| 694 pixInvert(pixim, pixim); | |
| 695 if (allfg) | |
| 696 return ERROR_INT("pixim all foreground", __func__, 1); | |
| 697 } | |
| 698 | |
| 699 pixGetBackgroundRGBMap(pixs, pixim, pixg, sx, sy, thresh, mincount, | |
| 700 &pixmr, &pixmg, &pixmb); | |
| 701 if (!pixmr || !pixmg || !pixmb) { | |
| 702 pixDestroy(&pixmr); | |
| 703 pixDestroy(&pixmg); | |
| 704 pixDestroy(&pixmb); | |
| 705 return ERROR_INT("not all pixm* made", __func__, 1); | |
| 706 } | |
| 707 | |
| 708 *ppixr = pixGetInvBackgroundMap(pixmr, bgval, smoothx, smoothy); | |
| 709 *ppixg = pixGetInvBackgroundMap(pixmg, bgval, smoothx, smoothy); | |
| 710 *ppixb = pixGetInvBackgroundMap(pixmb, bgval, smoothx, smoothy); | |
| 711 pixDestroy(&pixmr); | |
| 712 pixDestroy(&pixmg); | |
| 713 pixDestroy(&pixmb); | |
| 714 return 0; | |
| 715 } | |
| 716 | |
| 717 | |
| 718 /*! | |
| 719 * \brief pixBackgroundNormGrayArrayMorph() | |
| 720 * | |
| 721 * \param[in] pixs 8 bpp grayscale | |
| 722 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null | |
| 723 * \param[in] reduction at which morph closings are done; between 2 and 16 | |
| 724 * \param[in] size of square Sel for the closing; use an odd number | |
| 725 * \param[in] bgval target bg val; typ. > 128 | |
| 726 * \param[out] ppixd 16 bpp array of inverted background value | |
| 727 * \return 0 if OK, 1 on error | |
| 728 * | |
| 729 * <pre> | |
| 730 * Notes: | |
| 731 * (1) See notes in pixBackgroundNormMorph(). | |
| 732 * (2) This returns a 16 bpp pix that can be used by | |
| 733 * pixApplyInvBackgroundGrayMap() to generate a normalized version | |
| 734 * of the input pixs. | |
| 735 * </pre> | |
| 736 */ | |
| 737 l_ok | |
| 738 pixBackgroundNormGrayArrayMorph(PIX *pixs, | |
| 739 PIX *pixim, | |
| 740 l_int32 reduction, | |
| 741 l_int32 size, | |
| 742 l_int32 bgval, | |
| 743 PIX **ppixd) | |
| 744 { | |
| 745 l_int32 allfg; | |
| 746 PIX *pixm; | |
| 747 | |
| 748 if (!ppixd) | |
| 749 return ERROR_INT("&pixd not defined", __func__, 1); | |
| 750 *ppixd = NULL; | |
| 751 if (!pixs) | |
| 752 return ERROR_INT("pixs not defined", __func__, 1); | |
| 753 if (pixGetDepth(pixs) != 8) | |
| 754 return ERROR_INT("pixs not 8 bpp", __func__, 1); | |
| 755 if (pixim && pixGetDepth(pixim) != 1) | |
| 756 return ERROR_INT("pixim not 1 bpp", __func__, 1); | |
| 757 if (reduction < 2 || reduction > 16) | |
| 758 return ERROR_INT("reduction must be between 2 and 16", __func__, 1); | |
| 759 | |
| 760 /* If pixim exists, verify that it is not all foreground. */ | |
| 761 if (pixim) { | |
| 762 pixInvert(pixim, pixim); | |
| 763 pixZero(pixim, &allfg); | |
| 764 pixInvert(pixim, pixim); | |
| 765 if (allfg) | |
| 766 return ERROR_INT("pixim all foreground", __func__, 1); | |
| 767 } | |
| 768 | |
| 769 pixGetBackgroundGrayMapMorph(pixs, pixim, reduction, size, &pixm); | |
| 770 if (!pixm) | |
| 771 return ERROR_INT("pixm not made", __func__, 1); | |
| 772 *ppixd = pixGetInvBackgroundMap(pixm, bgval, 0, 0); | |
| 773 pixCopyResolution(*ppixd, pixs); | |
| 774 pixDestroy(&pixm); | |
| 775 return 0; | |
| 776 } | |
| 777 | |
| 778 | |
| 779 /*! | |
| 780 * \brief pixBackgroundNormRGBArraysMorph() | |
| 781 * | |
| 782 * \param[in] pixs 32 bpp rgb | |
| 783 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null | |
| 784 * \param[in] reduction at which morph closings are done; between 2 and 16 | |
| 785 * \param[in] size of square Sel for the closing; use an odd number | |
| 786 * \param[in] bgval target bg val; typ. > 128 | |
| 787 * \param[out] ppixr 16 bpp array of inverted R background value | |
| 788 * \param[out] ppixg 16 bpp array of inverted G background value | |
| 789 * \param[out] ppixb 16 bpp array of inverted B background value | |
| 790 * \return 0 if OK, 1 on error | |
| 791 * | |
| 792 * <pre> | |
| 793 * Notes: | |
| 794 * (1) See notes in pixBackgroundNormMorph(). | |
| 795 * (2) This returns a set of three 16 bpp pix that can be used by | |
| 796 * pixApplyInvBackgroundGrayMap() to generate a normalized version | |
| 797 * of each component of the input pixs. | |
| 798 * </pre> | |
| 799 */ | |
| 800 l_ok | |
| 801 pixBackgroundNormRGBArraysMorph(PIX *pixs, | |
| 802 PIX *pixim, | |
| 803 l_int32 reduction, | |
| 804 l_int32 size, | |
| 805 l_int32 bgval, | |
| 806 PIX **ppixr, | |
| 807 PIX **ppixg, | |
| 808 PIX **ppixb) | |
| 809 { | |
| 810 l_int32 allfg; | |
| 811 PIX *pixmr, *pixmg, *pixmb; | |
| 812 | |
| 813 if (!ppixr || !ppixg || !ppixb) | |
| 814 return ERROR_INT("&pixr, &pixg, &pixb not all defined", __func__, 1); | |
| 815 *ppixr = *ppixg = *ppixb = NULL; | |
| 816 if (!pixs) | |
| 817 return ERROR_INT("pixs not defined", __func__, 1); | |
| 818 if (pixGetDepth(pixs) != 32) | |
| 819 return ERROR_INT("pixs not 32 bpp", __func__, 1); | |
| 820 if (pixim && pixGetDepth(pixim) != 1) | |
| 821 return ERROR_INT("pixim not 1 bpp", __func__, 1); | |
| 822 if (reduction < 2 || reduction > 16) | |
| 823 return ERROR_INT("reduction must be between 2 and 16", __func__, 1); | |
| 824 | |
| 825 /* If pixim exists, verify that it is not all foreground. */ | |
| 826 if (pixim) { | |
| 827 pixInvert(pixim, pixim); | |
| 828 pixZero(pixim, &allfg); | |
| 829 pixInvert(pixim, pixim); | |
| 830 if (allfg) | |
| 831 return ERROR_INT("pixim all foreground", __func__, 1); | |
| 832 } | |
| 833 | |
| 834 pixGetBackgroundRGBMapMorph(pixs, pixim, reduction, size, | |
| 835 &pixmr, &pixmg, &pixmb); | |
| 836 if (!pixmr || !pixmg || !pixmb) { | |
| 837 pixDestroy(&pixmr); | |
| 838 pixDestroy(&pixmg); | |
| 839 pixDestroy(&pixmb); | |
| 840 return ERROR_INT("not all pixm* made", __func__, 1); | |
| 841 } | |
| 842 | |
| 843 *ppixr = pixGetInvBackgroundMap(pixmr, bgval, 0, 0); | |
| 844 *ppixg = pixGetInvBackgroundMap(pixmg, bgval, 0, 0); | |
| 845 *ppixb = pixGetInvBackgroundMap(pixmb, bgval, 0, 0); | |
| 846 pixDestroy(&pixmr); | |
| 847 pixDestroy(&pixmg); | |
| 848 pixDestroy(&pixmb); | |
| 849 return 0; | |
| 850 } | |
| 851 | |
| 852 | |
| 853 /*------------------------------------------------------------------* | |
| 854 * Measurement of local background * | |
| 855 *------------------------------------------------------------------*/ | |
| 856 /*! | |
| 857 * \brief pixGetBackgroundGrayMap() | |
| 858 * | |
| 859 * \param[in] pixs 8 bpp grayscale; not cmapped | |
| 860 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null; | |
| 861 * it should not have only foreground pixels | |
| 862 * \param[in] sx, sy tile size in pixels | |
| 863 * \param[in] thresh threshold for determining foreground | |
| 864 * \param[in] mincount min threshold on counts in a tile | |
| 865 * \param[out] ppixd 8 bpp grayscale map | |
| 866 * \return 0 if OK, 1 on error | |
| 867 * | |
| 868 * <pre> | |
| 869 * Notes: | |
| 870 * (1) The background is measured in regions that don't have | |
| 871 * images. It is then propagated into the image regions, | |
| 872 * and finally smoothed in each image region. | |
| 873 * </pre> | |
| 874 */ | |
| 875 l_ok | |
| 876 pixGetBackgroundGrayMap(PIX *pixs, | |
| 877 PIX *pixim, | |
| 878 l_int32 sx, | |
| 879 l_int32 sy, | |
| 880 l_int32 thresh, | |
| 881 l_int32 mincount, | |
| 882 PIX **ppixd) | |
| 883 { | |
| 884 l_int32 w, h, wd, hd, wim, him, wpls, wplim, wpld, wplf; | |
| 885 l_int32 xim, yim, delx, nx, ny, i, j, k, m; | |
| 886 l_int32 count, sum, val8; | |
| 887 l_int32 empty, fgpixels; | |
| 888 l_uint32 *datas, *dataim, *datad, *dataf, *lines, *lineim, *lined, *linef; | |
| 889 l_float32 scalex, scaley; | |
| 890 PIX *pixd, *piximi, *pixb, *pixf, *pixims; | |
| 891 | |
| 892 if (!ppixd) | |
| 893 return ERROR_INT("&pixd not defined", __func__, 1); | |
| 894 *ppixd = NULL; | |
| 895 if (!pixs || pixGetDepth(pixs) != 8) | |
| 896 return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1); | |
| 897 if (pixGetColormap(pixs)) | |
| 898 return ERROR_INT("pixs is colormapped", __func__, 1); | |
| 899 if (pixim && pixGetDepth(pixim) != 1) | |
| 900 return ERROR_INT("pixim not 1 bpp", __func__, 1); | |
| 901 if (sx < 4 || sy < 4) | |
| 902 return ERROR_INT("sx and sy must be >= 4", __func__, 1); | |
| 903 if (mincount > sx * sy) { | |
| 904 L_WARNING("mincount too large for tile size\n", __func__); | |
| 905 mincount = (sx * sy) / 3; | |
| 906 } | |
| 907 | |
| 908 /* Evaluate the 'image' mask, pixim, and make sure | |
| 909 * it is not all fg. */ | |
| 910 fgpixels = 0; /* boolean for existence of fg pixels in the image mask. */ | |
| 911 if (pixim) { | |
| 912 piximi = pixInvert(NULL, pixim); /* set non-'image' pixels to 1 */ | |
| 913 pixZero(piximi, &empty); | |
| 914 pixDestroy(&piximi); | |
| 915 if (empty) | |
| 916 return ERROR_INT("pixim all fg; no background", __func__, 1); | |
| 917 pixZero(pixim, &empty); | |
| 918 if (!empty) /* there are fg pixels in pixim */ | |
| 919 fgpixels = 1; | |
| 920 } | |
| 921 | |
| 922 /* Generate the foreground mask, pixf, which is at | |
| 923 * full resolution. These pixels will be ignored when | |
| 924 * computing the background values. */ | |
| 925 pixb = pixThresholdToBinary(pixs, thresh); | |
| 926 pixf = pixMorphSequence(pixb, "d7.1 + d1.7", 0); | |
| 927 pixDestroy(&pixb); | |
| 928 if (!pixf) | |
| 929 return ERROR_INT("pixf not made", __func__, 1); | |
| 930 | |
| 931 | |
| 932 /* ------------- Set up the output map pixd --------------- */ | |
| 933 /* Generate pixd, which is reduced by the factors (sx, sy). */ | |
| 934 w = pixGetWidth(pixs); | |
| 935 h = pixGetHeight(pixs); | |
| 936 wd = (w + sx - 1) / sx; | |
| 937 hd = (h + sy - 1) / sy; | |
| 938 pixd = pixCreate(wd, hd, 8); | |
| 939 | |
| 940 /* Note: we only compute map values in tiles that are complete. | |
| 941 * In general, tiles at right and bottom edges will not be | |
| 942 * complete, and we must fill them in later. */ | |
| 943 nx = w / sx; | |
| 944 ny = h / sy; | |
| 945 wpls = pixGetWpl(pixs); | |
| 946 datas = pixGetData(pixs); | |
| 947 wpld = pixGetWpl(pixd); | |
| 948 datad = pixGetData(pixd); | |
| 949 wplf = pixGetWpl(pixf); | |
| 950 dataf = pixGetData(pixf); | |
| 951 for (i = 0; i < ny; i++) { | |
| 952 lines = datas + sy * i * wpls; | |
| 953 linef = dataf + sy * i * wplf; | |
| 954 lined = datad + i * wpld; | |
| 955 for (j = 0; j < nx; j++) { | |
| 956 delx = j * sx; | |
| 957 sum = 0; | |
| 958 count = 0; | |
| 959 for (k = 0; k < sy; k++) { | |
| 960 for (m = 0; m < sx; m++) { | |
| 961 if (GET_DATA_BIT(linef + k * wplf, delx + m) == 0) { | |
| 962 sum += GET_DATA_BYTE(lines + k * wpls, delx + m); | |
| 963 count++; | |
| 964 } | |
| 965 } | |
| 966 } | |
| 967 if (count >= mincount) { | |
| 968 val8 = sum / count; | |
| 969 SET_DATA_BYTE(lined, j, val8); | |
| 970 } | |
| 971 } | |
| 972 } | |
| 973 pixDestroy(&pixf); | |
| 974 | |
| 975 /* If there is an optional mask with fg pixels, erase the previous | |
| 976 * calculation for the corresponding map pixels, setting the | |
| 977 * map values to 0. Then, when all the map holes are filled, | |
| 978 * these erased pixels will be set by the surrounding map values. | |
| 979 * | |
| 980 * The calculation here is relatively efficient: for each pixel | |
| 981 * in pixd (which corresponds to a tile of mask pixels in pixim) | |
| 982 * we look only at the pixel in pixim that is at the center | |
| 983 * of the tile. If the mask pixel is ON, we reset the map | |
| 984 * pixel in pixd to 0, so that it can later be filled in. */ | |
| 985 pixims = NULL; | |
| 986 if (pixim && fgpixels) { | |
| 987 wim = pixGetWidth(pixim); | |
| 988 him = pixGetHeight(pixim); | |
| 989 dataim = pixGetData(pixim); | |
| 990 wplim = pixGetWpl(pixim); | |
| 991 for (i = 0; i < ny; i++) { | |
| 992 yim = i * sy + sy / 2; | |
| 993 if (yim >= him) | |
| 994 break; | |
| 995 lineim = dataim + yim * wplim; | |
| 996 for (j = 0; j < nx; j++) { | |
| 997 xim = j * sx + sx / 2; | |
| 998 if (xim >= wim) | |
| 999 break; | |
| 1000 if (GET_DATA_BIT(lineim, xim)) | |
| 1001 pixSetPixel(pixd, j, i, 0); | |
| 1002 } | |
| 1003 } | |
| 1004 } | |
| 1005 | |
| 1006 /* Fill all the holes in the map. */ | |
| 1007 if (pixFillMapHoles(pixd, nx, ny, L_FILL_BLACK)) { | |
| 1008 pixDestroy(&pixd); | |
| 1009 L_WARNING("can't make the map\n", __func__); | |
| 1010 return 1; | |
| 1011 } | |
| 1012 | |
| 1013 /* Finally, for each connected region corresponding to the | |
| 1014 * 'image' mask, reset all pixels to their average value. | |
| 1015 * Each of these components represents an image (or part of one) | |
| 1016 * in the input, and this smooths the background values | |
| 1017 * in each of these regions. */ | |
| 1018 if (pixim && fgpixels) { | |
| 1019 scalex = 1. / (l_float32)sx; | |
| 1020 scaley = 1. / (l_float32)sy; | |
| 1021 pixims = pixScaleBySampling(pixim, scalex, scaley); | |
| 1022 pixSmoothConnectedRegions(pixd, pixims, 2); | |
| 1023 pixDestroy(&pixims); | |
| 1024 } | |
| 1025 | |
| 1026 *ppixd = pixd; | |
| 1027 pixCopyResolution(*ppixd, pixs); | |
| 1028 return 0; | |
| 1029 } | |
| 1030 | |
| 1031 | |
| 1032 /*! | |
| 1033 * \brief pixGetBackgroundRGBMap() | |
| 1034 * | |
| 1035 * \param[in] pixs 32 bpp rgb | |
| 1036 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null; it | |
| 1037 * should not have all foreground pixels | |
| 1038 * \param[in] pixg [optional] 8 bpp grayscale version; can be null | |
| 1039 * \param[in] sx, sy tile size in pixels | |
| 1040 * \param[in] thresh threshold for determining foreground | |
| 1041 * \param[in] mincount min threshold on counts in a tile | |
| 1042 * \param[out] ppixmr red component map | |
| 1043 * \param[out] ppixmg green component map | |
| 1044 * \param[out] ppixmb blue component map | |
| 1045 * \return 0 if OK, 1 on error | |
| 1046 * | |
| 1047 * <pre> | |
| 1048 * Notes: | |
| 1049 * (1) If pixg, which is a grayscale version of pixs, is provided, | |
| 1050 * use this internally to generate the foreground mask. | |
| 1051 * Otherwise, a grayscale version of pixs will be generated | |
| 1052 * from the green component only, used, and destroyed. | |
| 1053 * </pre> | |
| 1054 */ | |
| 1055 l_ok | |
| 1056 pixGetBackgroundRGBMap(PIX *pixs, | |
| 1057 PIX *pixim, | |
| 1058 PIX *pixg, | |
| 1059 l_int32 sx, | |
| 1060 l_int32 sy, | |
| 1061 l_int32 thresh, | |
| 1062 l_int32 mincount, | |
| 1063 PIX **ppixmr, | |
| 1064 PIX **ppixmg, | |
| 1065 PIX **ppixmb) | |
| 1066 { | |
| 1067 l_int32 w, h, wm, hm, wim, him, wpls, wplim, wplf; | |
| 1068 l_int32 xim, yim, delx, nx, ny, i, j, k, m; | |
| 1069 l_int32 count, rsum, gsum, bsum, rval, gval, bval; | |
| 1070 l_int32 empty, fgpixels; | |
| 1071 l_uint32 pixel; | |
| 1072 l_uint32 *datas, *dataim, *dataf, *lines, *lineim, *linef; | |
| 1073 l_float32 scalex, scaley; | |
| 1074 PIX *piximi, *pixgc, *pixb, *pixf, *pixims; | |
| 1075 PIX *pixmr, *pixmg, *pixmb; | |
| 1076 | |
| 1077 if (!ppixmr || !ppixmg || !ppixmb) | |
| 1078 return ERROR_INT("&pixm* not all defined", __func__, 1); | |
| 1079 *ppixmr = *ppixmg = *ppixmb = NULL; | |
| 1080 if (!pixs) | |
| 1081 return ERROR_INT("pixs not defined", __func__, 1); | |
| 1082 if (pixGetDepth(pixs) != 32) | |
| 1083 return ERROR_INT("pixs not 32 bpp", __func__, 1); | |
| 1084 if (pixim && pixGetDepth(pixim) != 1) | |
| 1085 return ERROR_INT("pixim not 1 bpp", __func__, 1); | |
| 1086 if (sx < 4 || sy < 4) | |
| 1087 return ERROR_INT("sx and sy must be >= 4", __func__, 1); | |
| 1088 if (mincount > sx * sy) { | |
| 1089 L_WARNING("mincount too large for tile size\n", __func__); | |
| 1090 mincount = (sx * sy) / 3; | |
| 1091 } | |
| 1092 | |
| 1093 /* Evaluate the mask pixim and make sure it is not all foreground */ | |
| 1094 fgpixels = 0; /* boolean for existence of fg mask pixels */ | |
| 1095 if (pixim) { | |
| 1096 piximi = pixInvert(NULL, pixim); /* set non-'image' pixels to 1 */ | |
| 1097 pixZero(piximi, &empty); | |
| 1098 pixDestroy(&piximi); | |
| 1099 if (empty) | |
| 1100 return ERROR_INT("pixim all fg; no background", __func__, 1); | |
| 1101 pixZero(pixim, &empty); | |
| 1102 if (!empty) /* there are fg pixels in pixim */ | |
| 1103 fgpixels = 1; | |
| 1104 } | |
| 1105 | |
| 1106 /* Generate the foreground mask. These pixels will be | |
| 1107 * ignored when computing the background values. */ | |
| 1108 if (pixg) /* use the input grayscale version if it is provided */ | |
| 1109 pixgc = pixClone(pixg); | |
| 1110 else | |
| 1111 pixgc = pixConvertRGBToGrayFast(pixs); | |
| 1112 pixb = pixThresholdToBinary(pixgc, thresh); | |
| 1113 pixf = pixMorphSequence(pixb, "d7.1 + d1.7", 0); | |
| 1114 pixDestroy(&pixgc); | |
| 1115 pixDestroy(&pixb); | |
| 1116 | |
| 1117 /* Generate the output mask images */ | |
| 1118 w = pixGetWidth(pixs); | |
| 1119 h = pixGetHeight(pixs); | |
| 1120 wm = (w + sx - 1) / sx; | |
| 1121 hm = (h + sy - 1) / sy; | |
| 1122 pixmr = pixCreate(wm, hm, 8); | |
| 1123 pixmg = pixCreate(wm, hm, 8); | |
| 1124 pixmb = pixCreate(wm, hm, 8); | |
| 1125 | |
| 1126 /* ------------- Set up the mapping images --------------- */ | |
| 1127 /* Note: we only compute map values in tiles that are complete. | |
| 1128 * In general, tiles at right and bottom edges will not be | |
| 1129 * complete, and we must fill them in later. */ | |
| 1130 nx = w / sx; | |
| 1131 ny = h / sy; | |
| 1132 wpls = pixGetWpl(pixs); | |
| 1133 datas = pixGetData(pixs); | |
| 1134 wplf = pixGetWpl(pixf); | |
| 1135 dataf = pixGetData(pixf); | |
| 1136 for (i = 0; i < ny; i++) { | |
| 1137 lines = datas + sy * i * wpls; | |
| 1138 linef = dataf + sy * i * wplf; | |
| 1139 for (j = 0; j < nx; j++) { | |
| 1140 delx = j * sx; | |
| 1141 rsum = gsum = bsum = 0; | |
| 1142 count = 0; | |
| 1143 for (k = 0; k < sy; k++) { | |
| 1144 for (m = 0; m < sx; m++) { | |
| 1145 if (GET_DATA_BIT(linef + k * wplf, delx + m) == 0) { | |
| 1146 pixel = *(lines + k * wpls + delx + m); | |
| 1147 rsum += (pixel >> 24); | |
| 1148 gsum += ((pixel >> 16) & 0xff); | |
| 1149 bsum += ((pixel >> 8) & 0xff); | |
| 1150 count++; | |
| 1151 } | |
| 1152 } | |
| 1153 } | |
| 1154 if (count >= mincount) { | |
| 1155 rval = rsum / count; | |
| 1156 gval = gsum / count; | |
| 1157 bval = bsum / count; | |
| 1158 pixSetPixel(pixmr, j, i, rval); | |
| 1159 pixSetPixel(pixmg, j, i, gval); | |
| 1160 pixSetPixel(pixmb, j, i, bval); | |
| 1161 } | |
| 1162 } | |
| 1163 } | |
| 1164 pixDestroy(&pixf); | |
| 1165 | |
| 1166 /* If there is an optional mask with fg pixels, erase the previous | |
| 1167 * calculation for the corresponding map pixels, setting the | |
| 1168 * map values in each of the 3 color maps to 0. Then, when | |
| 1169 * all the map holes are filled, these erased pixels will | |
| 1170 * be set by the surrounding map values. */ | |
| 1171 if (pixim) { | |
| 1172 wim = pixGetWidth(pixim); | |
| 1173 him = pixGetHeight(pixim); | |
| 1174 dataim = pixGetData(pixim); | |
| 1175 wplim = pixGetWpl(pixim); | |
| 1176 for (i = 0; i < ny; i++) { | |
| 1177 yim = i * sy + sy / 2; | |
| 1178 if (yim >= him) | |
| 1179 break; | |
| 1180 lineim = dataim + yim * wplim; | |
| 1181 for (j = 0; j < nx; j++) { | |
| 1182 xim = j * sx + sx / 2; | |
| 1183 if (xim >= wim) | |
| 1184 break; | |
| 1185 if (GET_DATA_BIT(lineim, xim)) { | |
| 1186 pixSetPixel(pixmr, j, i, 0); | |
| 1187 pixSetPixel(pixmg, j, i, 0); | |
| 1188 pixSetPixel(pixmb, j, i, 0); | |
| 1189 } | |
| 1190 } | |
| 1191 } | |
| 1192 } | |
| 1193 | |
| 1194 /* ----------------- Now fill in the holes ----------------------- */ | |
| 1195 if (pixFillMapHoles(pixmr, nx, ny, L_FILL_BLACK) || | |
| 1196 pixFillMapHoles(pixmg, nx, ny, L_FILL_BLACK) || | |
| 1197 pixFillMapHoles(pixmb, nx, ny, L_FILL_BLACK)) { | |
| 1198 pixDestroy(&pixmr); | |
| 1199 pixDestroy(&pixmg); | |
| 1200 pixDestroy(&pixmb); | |
| 1201 L_WARNING("can't make the maps\n", __func__); | |
| 1202 return 1; | |
| 1203 } | |
| 1204 | |
| 1205 /* Finally, for each connected region corresponding to the | |
| 1206 * fg mask, reset all pixels to their average value. */ | |
| 1207 if (pixim && fgpixels) { | |
| 1208 scalex = 1. / (l_float32)sx; | |
| 1209 scaley = 1. / (l_float32)sy; | |
| 1210 pixims = pixScaleBySampling(pixim, scalex, scaley); | |
| 1211 pixSmoothConnectedRegions(pixmr, pixims, 2); | |
| 1212 pixSmoothConnectedRegions(pixmg, pixims, 2); | |
| 1213 pixSmoothConnectedRegions(pixmb, pixims, 2); | |
| 1214 pixDestroy(&pixims); | |
| 1215 } | |
| 1216 | |
| 1217 *ppixmr = pixmr; | |
| 1218 *ppixmg = pixmg; | |
| 1219 *ppixmb = pixmb; | |
| 1220 pixCopyResolution(*ppixmr, pixs); | |
| 1221 pixCopyResolution(*ppixmg, pixs); | |
| 1222 pixCopyResolution(*ppixmb, pixs); | |
| 1223 return 0; | |
| 1224 } | |
| 1225 | |
| 1226 | |
| 1227 /*! | |
| 1228 * \brief pixGetBackgroundGrayMapMorph() | |
| 1229 * | |
| 1230 * \param[in] pixs 8 bpp grayscale; not cmapped | |
| 1231 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null; it | |
| 1232 * should not have all foreground pixels | |
| 1233 * \param[in] reduction factor at which closing is performed | |
| 1234 * \param[in] size of square Sel for the closing; use an odd number | |
| 1235 * \param[out] ppixm grayscale map | |
| 1236 * \return 0 if OK, 1 on error | |
| 1237 */ | |
| 1238 l_ok | |
| 1239 pixGetBackgroundGrayMapMorph(PIX *pixs, | |
| 1240 PIX *pixim, | |
| 1241 l_int32 reduction, | |
| 1242 l_int32 size, | |
| 1243 PIX **ppixm) | |
| 1244 { | |
| 1245 l_int32 nx, ny, empty, fgpixels; | |
| 1246 l_float32 scale; | |
| 1247 PIX *pixm, *pix1, *pix2, *pix3, *pixims; | |
| 1248 | |
| 1249 if (!ppixm) | |
| 1250 return ERROR_INT("&pixm not defined", __func__, 1); | |
| 1251 *ppixm = NULL; | |
| 1252 if (!pixs || pixGetDepth(pixs) != 8) | |
| 1253 return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1); | |
| 1254 if (pixGetColormap(pixs)) | |
| 1255 return ERROR_INT("pixs is colormapped", __func__, 1); | |
| 1256 if (pixim && pixGetDepth(pixim) != 1) | |
| 1257 return ERROR_INT("pixim not 1 bpp", __func__, 1); | |
| 1258 | |
| 1259 /* Evaluate the mask pixim and make sure it is not all foreground. */ | |
| 1260 fgpixels = 0; /* boolean for existence of fg mask pixels */ | |
| 1261 if (pixim) { | |
| 1262 pixInvert(pixim, pixim); /* set background pixels to 1 */ | |
| 1263 pixZero(pixim, &empty); | |
| 1264 if (empty) | |
| 1265 return ERROR_INT("pixim all fg; no background", __func__, 1); | |
| 1266 pixInvert(pixim, pixim); /* revert to original mask */ | |
| 1267 pixZero(pixim, &empty); | |
| 1268 if (!empty) /* there are fg pixels in pixim */ | |
| 1269 fgpixels = 1; | |
| 1270 } | |
| 1271 | |
| 1272 /* Downscale as requested and do the closing to get the background. */ | |
| 1273 scale = 1. / (l_float32)reduction; | |
| 1274 pix1 = pixScaleBySampling(pixs, scale, scale); | |
| 1275 pix2 = pixCloseGray(pix1, size, size); | |
| 1276 pix3 = pixExtendByReplication(pix2, 1, 1); | |
| 1277 pixDestroy(&pix1); | |
| 1278 pixDestroy(&pix2); | |
| 1279 | |
| 1280 /* Downscale the image mask, if any, and remove it from the | |
| 1281 * background. These pixels will be filled in (twice). */ | |
| 1282 pixims = NULL; | |
| 1283 if (pixim) { | |
| 1284 pixims = pixScale(pixim, scale, scale); | |
| 1285 pixm = pixConvertTo8(pixims, FALSE); | |
| 1286 pixAnd(pixm, pixm, pix3); | |
| 1287 } | |
| 1288 else | |
| 1289 pixm = pixClone(pix3); | |
| 1290 pixDestroy(&pix3); | |
| 1291 | |
| 1292 /* Fill all the holes in the map. */ | |
| 1293 nx = pixGetWidth(pixs) / reduction; | |
| 1294 ny = pixGetHeight(pixs) / reduction; | |
| 1295 if (pixFillMapHoles(pixm, nx, ny, L_FILL_BLACK)) { | |
| 1296 pixDestroy(&pixm); | |
| 1297 pixDestroy(&pixims); | |
| 1298 L_WARNING("can't make the map\n", __func__); | |
| 1299 return 1; | |
| 1300 } | |
| 1301 | |
| 1302 /* Finally, for each connected region corresponding to the | |
| 1303 * fg mask, reset all pixels to their average value. */ | |
| 1304 if (pixim && fgpixels) | |
| 1305 pixSmoothConnectedRegions(pixm, pixims, 2); | |
| 1306 pixDestroy(&pixims); | |
| 1307 | |
| 1308 *ppixm = pixm; | |
| 1309 pixCopyResolution(*ppixm, pixs); | |
| 1310 return 0; | |
| 1311 } | |
| 1312 | |
| 1313 | |
| 1314 /*! | |
| 1315 * \brief pixGetBackgroundRGBMapMorph() | |
| 1316 * | |
| 1317 * \param[in] pixs 32 bpp rgb | |
| 1318 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null; it | |
| 1319 * should not have all foreground pixels | |
| 1320 * \param[in] reduction factor at which closing is performed | |
| 1321 * \param[in] size of square Sel for the closing; use an odd number | |
| 1322 * \param[out] ppixmr red component map | |
| 1323 * \param[out] ppixmg green component map | |
| 1324 * \param[out] ppixmb blue component map | |
| 1325 * \return 0 if OK, 1 on error | |
| 1326 */ | |
| 1327 l_ok | |
| 1328 pixGetBackgroundRGBMapMorph(PIX *pixs, | |
| 1329 PIX *pixim, | |
| 1330 l_int32 reduction, | |
| 1331 l_int32 size, | |
| 1332 PIX **ppixmr, | |
| 1333 PIX **ppixmg, | |
| 1334 PIX **ppixmb) | |
| 1335 { | |
| 1336 l_int32 nx, ny, empty, fgpixels; | |
| 1337 l_float32 scale; | |
| 1338 PIX *pixm, *pixmr, *pixmg, *pixmb, *pix1, *pix2, *pix3, *pixims; | |
| 1339 | |
| 1340 if (!ppixmr || !ppixmg || !ppixmb) | |
| 1341 return ERROR_INT("&pixm* not all defined", __func__, 1); | |
| 1342 *ppixmr = *ppixmg = *ppixmb = NULL; | |
| 1343 if (!pixs) | |
| 1344 return ERROR_INT("pixs not defined", __func__, 1); | |
| 1345 if (pixGetDepth(pixs) != 32) | |
| 1346 return ERROR_INT("pixs not 32 bpp", __func__, 1); | |
| 1347 if (pixim && pixGetDepth(pixim) != 1) | |
| 1348 return ERROR_INT("pixim not 1 bpp", __func__, 1); | |
| 1349 | |
| 1350 /* Evaluate the mask pixim and make sure it is not all foreground. */ | |
| 1351 fgpixels = 0; /* boolean for existence of fg mask pixels */ | |
| 1352 if (pixim) { | |
| 1353 pixInvert(pixim, pixim); /* set background pixels to 1 */ | |
| 1354 pixZero(pixim, &empty); | |
| 1355 if (empty) | |
| 1356 return ERROR_INT("pixim all fg; no background", __func__, 1); | |
| 1357 pixInvert(pixim, pixim); /* revert to original mask */ | |
| 1358 pixZero(pixim, &empty); | |
| 1359 if (!empty) /* there are fg pixels in pixim */ | |
| 1360 fgpixels = 1; | |
| 1361 } | |
| 1362 | |
| 1363 /* Generate an 8 bpp version of the image mask, if it exists */ | |
| 1364 scale = 1. / (l_float32)reduction; | |
| 1365 pixims = NULL; | |
| 1366 pixm = NULL; | |
| 1367 if (pixim) { | |
| 1368 pixims = pixScale(pixim, scale, scale); | |
| 1369 pixm = pixConvertTo8(pixims, FALSE); | |
| 1370 } | |
| 1371 | |
| 1372 /* Downscale as requested and do the closing to get the background. | |
| 1373 * Then remove the image mask pixels from the background. They | |
| 1374 * will be filled in (twice) later. Do this for all 3 components. */ | |
| 1375 pix1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_RED); | |
| 1376 pix2 = pixCloseGray(pix1, size, size); | |
| 1377 pix3 = pixExtendByReplication(pix2, 1, 1); | |
| 1378 if (pixim) | |
| 1379 pixmr = pixAnd(NULL, pixm, pix3); | |
| 1380 else | |
| 1381 pixmr = pixClone(pix3); | |
| 1382 pixDestroy(&pix1); | |
| 1383 pixDestroy(&pix2); | |
| 1384 pixDestroy(&pix3); | |
| 1385 | |
| 1386 pix1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_GREEN); | |
| 1387 pix2 = pixCloseGray(pix1, size, size); | |
| 1388 pix3 = pixExtendByReplication(pix2, 1, 1); | |
| 1389 if (pixim) | |
| 1390 pixmg = pixAnd(NULL, pixm, pix3); | |
| 1391 else | |
| 1392 pixmg = pixClone(pix3); | |
| 1393 pixDestroy(&pix1); | |
| 1394 pixDestroy(&pix2); | |
| 1395 pixDestroy(&pix3); | |
| 1396 | |
| 1397 pix1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_BLUE); | |
| 1398 pix2 = pixCloseGray(pix1, size, size); | |
| 1399 pix3 = pixExtendByReplication(pix2, 1, 1); | |
| 1400 if (pixim) | |
| 1401 pixmb = pixAnd(NULL, pixm, pix3); | |
| 1402 else | |
| 1403 pixmb = pixClone(pix3); | |
| 1404 pixDestroy(&pixm); | |
| 1405 pixDestroy(&pix1); | |
| 1406 pixDestroy(&pix2); | |
| 1407 pixDestroy(&pix3); | |
| 1408 | |
| 1409 /* Fill all the holes in the three maps. */ | |
| 1410 nx = pixGetWidth(pixs) / reduction; | |
| 1411 ny = pixGetHeight(pixs) / reduction; | |
| 1412 if (pixFillMapHoles(pixmr, nx, ny, L_FILL_BLACK) || | |
| 1413 pixFillMapHoles(pixmg, nx, ny, L_FILL_BLACK) || | |
| 1414 pixFillMapHoles(pixmb, nx, ny, L_FILL_BLACK)) { | |
| 1415 pixDestroy(&pixmr); | |
| 1416 pixDestroy(&pixmg); | |
| 1417 pixDestroy(&pixmb); | |
| 1418 pixDestroy(&pixims); | |
| 1419 L_WARNING("can't make the maps\n", __func__); | |
| 1420 return 1; | |
| 1421 } | |
| 1422 | |
| 1423 /* Finally, for each connected region corresponding to the | |
| 1424 * fg mask in each component, reset all pixels to their | |
| 1425 * average value. */ | |
| 1426 if (pixim && fgpixels) { | |
| 1427 pixSmoothConnectedRegions(pixmr, pixims, 2); | |
| 1428 pixSmoothConnectedRegions(pixmg, pixims, 2); | |
| 1429 pixSmoothConnectedRegions(pixmb, pixims, 2); | |
| 1430 pixDestroy(&pixims); | |
| 1431 } | |
| 1432 | |
| 1433 *ppixmr = pixmr; | |
| 1434 *ppixmg = pixmg; | |
| 1435 *ppixmb = pixmb; | |
| 1436 pixCopyResolution(*ppixmr, pixs); | |
| 1437 pixCopyResolution(*ppixmg, pixs); | |
| 1438 pixCopyResolution(*ppixmb, pixs); | |
| 1439 return 0; | |
| 1440 } | |
| 1441 | |
| 1442 | |
| 1443 /*! | |
| 1444 * \brief pixFillMapHoles() | |
| 1445 * | |
| 1446 * \param[in] pix 8 bpp; a map, with one pixel for each tile in | |
| 1447 * a larger image | |
| 1448 * \param[in] nx number of horizontal pixel tiles that are entirely | |
| 1449 * covered with pixels in the original source image | |
| 1450 * \param[in] ny ditto for the number of vertical pixel tiles | |
| 1451 * \param[in] filltype L_FILL_WHITE or L_FILL_BLACK | |
| 1452 * \return 0 if OK, 1 on error | |
| 1453 * | |
| 1454 * <pre> | |
| 1455 * Notes: | |
| 1456 * (1) This is an in-place operation on pix (the map). pix is | |
| 1457 * typically a low-resolution version of some other image | |
| 1458 * from which it was derived, where each pixel in pix | |
| 1459 * corresponds to a rectangular tile (say, m x n) of pixels | |
| 1460 * in the larger image. All we need to know about the larger | |
| 1461 * image is whether or not the rightmost column and bottommost | |
| 1462 * row of pixels in pix correspond to tiles that are | |
| 1463 * only partially covered by pixels in the larger image. | |
| 1464 * (2) Typically, some number of pixels in the input map are | |
| 1465 * not known, and their values must be determined by near | |
| 1466 * pixels that are known. These unknown pixels are the 'holes'. | |
| 1467 * They can take on only two values, 0 and 255, and the | |
| 1468 * instruction about which to fill is given by the filltype flag. | |
| 1469 * (3) The "holes" can come from two sources. The first is when there | |
| 1470 * are not enough foreground or background pixels in a tile; | |
| 1471 * the second is when a tile is at least partially covered | |
| 1472 * by an image mask. If we're filling holes in a fg mask, | |
| 1473 * the holes are initialized to black (0) and use L_FILL_BLACK. | |
| 1474 * For filling holes in a bg mask, initialize the holes to | |
| 1475 * white (255) and use L_FILL_WHITE. | |
| 1476 * (4) If w is the map width, nx = w or nx = w - 1; ditto for h and ny. | |
| 1477 * </pre> | |
| 1478 */ | |
| 1479 l_ok | |
| 1480 pixFillMapHoles(PIX *pix, | |
| 1481 l_int32 nx, | |
| 1482 l_int32 ny, | |
| 1483 l_int32 filltype) | |
| 1484 { | |
| 1485 l_int32 w, h, y, nmiss, goodcol, i, j, found, ival, valtest; | |
| 1486 l_uint32 val, lastval; | |
| 1487 NUMA *na; /* indicates if there is any data in the column */ | |
| 1488 | |
| 1489 if (!pix || pixGetDepth(pix) != 8) | |
| 1490 return ERROR_INT("pix not defined or not 8 bpp", __func__, 1); | |
| 1491 if (pixGetColormap(pix)) | |
| 1492 return ERROR_INT("pix is colormapped", __func__, 1); | |
| 1493 | |
| 1494 /* ------------- Fill holes in the mapping image columns ----------- */ | |
| 1495 pixGetDimensions(pix, &w, &h, NULL); | |
| 1496 na = numaCreate(0); /* holds flag for which columns have data */ | |
| 1497 nmiss = 0; | |
| 1498 valtest = (filltype == L_FILL_WHITE) ? 255 : 0; | |
| 1499 for (j = 0; j < nx; j++) { /* do it by columns */ | |
| 1500 found = FALSE; | |
| 1501 for (i = 0; i < ny; i++) { | |
| 1502 pixGetPixel(pix, j, i, &val); | |
| 1503 if (val != valtest) { | |
| 1504 y = i; | |
| 1505 found = TRUE; | |
| 1506 break; | |
| 1507 } | |
| 1508 } | |
| 1509 if (found == FALSE) { | |
| 1510 numaAddNumber(na, 0); /* no data in the column */ | |
| 1511 nmiss++; | |
| 1512 } | |
| 1513 else { | |
| 1514 numaAddNumber(na, 1); /* data in the column */ | |
| 1515 for (i = y - 1; i >= 0; i--) /* replicate upwards to top */ | |
| 1516 pixSetPixel(pix, j, i, val); | |
| 1517 pixGetPixel(pix, j, 0, &lastval); | |
| 1518 for (i = 1; i < h; i++) { /* set going down to bottom */ | |
| 1519 pixGetPixel(pix, j, i, &val); | |
| 1520 if (val == valtest) | |
| 1521 pixSetPixel(pix, j, i, lastval); | |
| 1522 else | |
| 1523 lastval = val; | |
| 1524 } | |
| 1525 } | |
| 1526 } | |
| 1527 | |
| 1528 if (nmiss == nx) { /* no data in any column! */ | |
| 1529 numaDestroy(&na); | |
| 1530 L_WARNING("no bg found; no data in any column\n", __func__); | |
| 1531 return 1; | |
| 1532 } | |
| 1533 | |
| 1534 /* ---------- Fill in missing columns by replication ----------- */ | |
| 1535 if (nmiss > 0) { /* replicate columns */ | |
| 1536 /* Find the first good column */ | |
| 1537 goodcol = 0; | |
| 1538 for (j = 0; j < w; j++) { | |
| 1539 numaGetIValue(na, j, &ival); | |
| 1540 if (ival == 1) { | |
| 1541 goodcol = j; | |
| 1542 break; | |
| 1543 } | |
| 1544 } | |
| 1545 if (goodcol > 0) { /* copy cols backward */ | |
| 1546 for (j = goodcol - 1; j >= 0; j--) | |
| 1547 pixRasterop(pix, j, 0, 1, h, PIX_SRC, pix, j + 1, 0); | |
| 1548 } | |
| 1549 for (j = goodcol + 1; j < w; j++) { /* copy cols forward */ | |
| 1550 numaGetIValue(na, j, &ival); | |
| 1551 if (ival == 0) { | |
| 1552 /* Copy the column to the left of j */ | |
| 1553 pixRasterop(pix, j, 0, 1, h, PIX_SRC, pix, j - 1, 0); | |
| 1554 } | |
| 1555 } | |
| 1556 } | |
| 1557 if (w > nx) { /* replicate the last column */ | |
| 1558 pixRasterop(pix, w - 1, 0, 1, h, PIX_SRC, pix, w - 2, 0); | |
| 1559 } | |
| 1560 | |
| 1561 numaDestroy(&na); | |
| 1562 return 0; | |
| 1563 } | |
| 1564 | |
| 1565 | |
| 1566 /*! | |
| 1567 * \brief pixExtendByReplication() | |
| 1568 * | |
| 1569 * \param[in] pixs 8 bpp | |
| 1570 * \param[in] addw number of extra pixels horizontally to add | |
| 1571 * \param[in] addh number of extra pixels vertically to add | |
| 1572 * \return pixd extended with replicated pixel values, or NULL on error | |
| 1573 * | |
| 1574 * <pre> | |
| 1575 * Notes: | |
| 1576 * (1) The pixel values are extended to the left and down, as required. | |
| 1577 * </pre> | |
| 1578 */ | |
| 1579 PIX * | |
| 1580 pixExtendByReplication(PIX *pixs, | |
| 1581 l_int32 addw, | |
| 1582 l_int32 addh) | |
| 1583 { | |
| 1584 l_int32 w, h, i, j; | |
| 1585 l_uint32 val; | |
| 1586 PIX *pixd; | |
| 1587 | |
| 1588 if (!pixs || pixGetDepth(pixs) != 8) | |
| 1589 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL); | |
| 1590 | |
| 1591 if (addw == 0 && addh == 0) | |
| 1592 return pixCopy(NULL, pixs); | |
| 1593 | |
| 1594 pixGetDimensions(pixs, &w, &h, NULL); | |
| 1595 if ((pixd = pixCreate(w + addw, h + addh, 8)) == NULL) | |
| 1596 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); | |
| 1597 pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0); | |
| 1598 | |
| 1599 if (addw > 0) { | |
| 1600 for (i = 0; i < h; i++) { | |
| 1601 pixGetPixel(pixd, w - 1, i, &val); | |
| 1602 for (j = 0; j < addw; j++) | |
| 1603 pixSetPixel(pixd, w + j, i, val); | |
| 1604 } | |
| 1605 } | |
| 1606 | |
| 1607 if (addh > 0) { | |
| 1608 for (j = 0; j < w + addw; j++) { | |
| 1609 pixGetPixel(pixd, j, h - 1, &val); | |
| 1610 for (i = 0; i < addh; i++) | |
| 1611 pixSetPixel(pixd, j, h + i, val); | |
| 1612 } | |
| 1613 } | |
| 1614 | |
| 1615 pixCopyResolution(pixd, pixs); | |
| 1616 return pixd; | |
| 1617 } | |
| 1618 | |
| 1619 | |
| 1620 /*! | |
| 1621 * \brief pixSmoothConnectedRegions() | |
| 1622 * | |
| 1623 * \param[in] pixs 8 bpp grayscale; no colormap | |
| 1624 * \param[in] pixm [optional] 1 bpp; if null, this is a no-op | |
| 1625 * \param[in] factor subsampling factor for getting average; >= 1 | |
| 1626 * \return 0 if OK, 1 on error | |
| 1627 * | |
| 1628 * <pre> | |
| 1629 * Notes: | |
| 1630 * (1) The pixels in pixs corresponding to those in each | |
| 1631 * 8-connected region in the mask are set to the average value. | |
| 1632 * (2) This is required for adaptive mapping to avoid the | |
| 1633 * generation of stripes in the background map, due to | |
| 1634 * variations in the pixel values near the edges of mask regions. | |
| 1635 * (3) This function is optimized for background smoothing, where | |
| 1636 * there are a relatively small number of components. It will | |
| 1637 * be inefficient if used where there are many small components. | |
| 1638 * </pre> | |
| 1639 */ | |
| 1640 l_ok | |
| 1641 pixSmoothConnectedRegions(PIX *pixs, | |
| 1642 PIX *pixm, | |
| 1643 l_int32 factor) | |
| 1644 { | |
| 1645 l_int32 empty, i, n, x, y; | |
| 1646 l_float32 aveval; | |
| 1647 BOXA *boxa; | |
| 1648 PIX *pixmc; | |
| 1649 PIXA *pixa; | |
| 1650 | |
| 1651 if (!pixs || pixGetDepth(pixs) != 8) | |
| 1652 return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1); | |
| 1653 if (pixGetColormap(pixs)) | |
| 1654 return ERROR_INT("pixs has colormap", __func__, 1); | |
| 1655 if (!pixm) { | |
| 1656 L_INFO("pixm not defined\n", __func__); | |
| 1657 return 0; | |
| 1658 } | |
| 1659 if (pixGetDepth(pixm) != 1) | |
| 1660 return ERROR_INT("pixm not 1 bpp", __func__, 1); | |
| 1661 pixZero(pixm, &empty); | |
| 1662 if (empty) { | |
| 1663 L_INFO("pixm has no fg pixels; nothing to do\n", __func__); | |
| 1664 return 0; | |
| 1665 } | |
| 1666 | |
| 1667 boxa = pixConnComp(pixm, &pixa, 8); | |
| 1668 n = boxaGetCount(boxa); | |
| 1669 for (i = 0; i < n; i++) { | |
| 1670 if ((pixmc = pixaGetPix(pixa, i, L_CLONE)) == NULL) { | |
| 1671 L_WARNING("missing pixmc!\n", __func__); | |
| 1672 continue; | |
| 1673 } | |
| 1674 boxaGetBoxGeometry(boxa, i, &x, &y, NULL, NULL); | |
| 1675 pixGetAverageMasked(pixs, pixmc, x, y, factor, L_MEAN_ABSVAL, &aveval); | |
| 1676 pixPaintThroughMask(pixs, pixmc, x, y, (l_int32)aveval); | |
| 1677 pixDestroy(&pixmc); | |
| 1678 } | |
| 1679 | |
| 1680 boxaDestroy(&boxa); | |
| 1681 pixaDestroy(&pixa); | |
| 1682 return 0; | |
| 1683 } | |
| 1684 | |
| 1685 | |
| 1686 /*------------------------------------------------------------------* | |
| 1687 * Measurement of local foreground * | |
| 1688 *------------------------------------------------------------------*/ | |
| 1689 #if 0 /* Not working properly: do not use */ | |
| 1690 | |
| 1691 /*! | |
| 1692 * \brief pixGetForegroundGrayMap() | |
| 1693 * | |
| 1694 * \param[in] pixs 8 bpp | |
| 1695 * \param[in] pixim [optional] 1 bpp 'image' mask; can be null | |
| 1696 * \param[in] sx, sy src tile size, in pixels | |
| 1697 * \param[in] thresh threshold for determining foreground | |
| 1698 * \param[out] ppixd 8 bpp grayscale map | |
| 1699 * \return 0 if OK, 1 on error | |
| 1700 * | |
| 1701 * <pre> | |
| 1702 * Notes: | |
| 1703 * (1) Each (sx, sy) tile of pixs gets mapped to one pixel in pixd. | |
| 1704 * (2) pixd is the estimate of the fg (darkest) value within each tile. | |
| 1705 * (3) All pixels in pixd that are in 'image' regions, as specified | |
| 1706 * by pixim, are given the background value 0. | |
| 1707 * (4) For pixels in pixd that can't directly be given a fg value, | |
| 1708 * the value is inferred by propagating from neighboring pixels. | |
| 1709 * (5) In practice, pixd can be used to normalize the fg, and | |
| 1710 * it can be done after background normalization. | |
| 1711 * (6) The overall procedure is: | |
| 1712 * ~ reduce 2x by sampling | |
| 1713 * ~ paint all 'image' pixels white, so that they don't | |
| 1714 * ~ participate in the Min reduction | |
| 1715 * ~ do a further (sx, sy) Min reduction -- think of | |
| 1716 * it as a large opening followed by subsampling by the | |
| 1717 * reduction factors | |
| 1718 * ~ threshold the result to identify fg, and set the | |
| 1719 * bg pixels to 255 (these are 'holes') | |
| 1720 * ~ fill holes by propagation from fg values | |
| 1721 * ~ replicatively expand by 2x, arriving at the final | |
| 1722 * resolution of pixd | |
| 1723 * ~ smooth with a 17x17 kernel | |
| 1724 * ~ paint the 'image' regions black | |
| 1725 * </pre> | |
| 1726 */ | |
| 1727 l_ok | |
| 1728 pixGetForegroundGrayMap(PIX *pixs, | |
| 1729 PIX *pixim, | |
| 1730 l_int32 sx, | |
| 1731 l_int32 sy, | |
| 1732 l_int32 thresh, | |
| 1733 PIX **ppixd) | |
| 1734 { | |
| 1735 l_int32 w, h, d, wd, hd; | |
| 1736 l_int32 empty, fgpixels; | |
| 1737 PIX *pixd, *piximi, *pixim2, *pixims, *pixs2, *pixb, *pixt1, *pixt2, *pixt3; | |
| 1738 | |
| 1739 if (!ppixd) | |
| 1740 return ERROR_INT("&pixd not defined", __func__, 1); | |
| 1741 *ppixd = NULL; | |
| 1742 if (!pixs) | |
| 1743 return ERROR_INT("pixs not defined", __func__, 1); | |
| 1744 pixGetDimensions(pixs, &w, &h, &d); | |
| 1745 if (d != 8) | |
| 1746 return ERROR_INT("pixs not 8 bpp", __func__, 1); | |
| 1747 if (pixim && pixGetDepth(pixim) != 1) | |
| 1748 return ERROR_INT("pixim not 1 bpp", __func__, 1); | |
| 1749 if (sx < 2 || sy < 2) | |
| 1750 return ERROR_INT("sx and sy must be >= 2", __func__, 1); | |
| 1751 | |
| 1752 /* Generate pixd, which is reduced by the factors (sx, sy). */ | |
| 1753 wd = (w + sx - 1) / sx; | |
| 1754 hd = (h + sy - 1) / sy; | |
| 1755 pixd = pixCreate(wd, hd, 8); | |
| 1756 *ppixd = pixd; | |
| 1757 | |
| 1758 /* Evaluate the 'image' mask, pixim. If it is all fg, | |
| 1759 * the output pixd has all pixels with value 0. */ | |
| 1760 fgpixels = 0; /* boolean for existence of fg pixels in the image mask. */ | |
| 1761 if (pixim) { | |
| 1762 piximi = pixInvert(NULL, pixim); /* set non-image pixels to 1 */ | |
| 1763 pixZero(piximi, &empty); | |
| 1764 pixDestroy(&piximi); | |
| 1765 if (empty) /* all 'image'; return with all pixels set to 0 */ | |
| 1766 return 0; | |
| 1767 pixZero(pixim, &empty); | |
| 1768 if (!empty) /* there are fg pixels in pixim */ | |
| 1769 fgpixels = 1; | |
| 1770 } | |
| 1771 | |
| 1772 /* 2x subsampling; paint white through 'image' mask. */ | |
| 1773 pixs2 = pixScaleBySampling(pixs, 0.5, 0.5); | |
| 1774 if (pixim && fgpixels) { | |
| 1775 pixim2 = pixReduceBinary2(pixim, NULL); | |
| 1776 pixPaintThroughMask(pixs2, pixim2, 0, 0, 255); | |
| 1777 pixDestroy(&pixim2); | |
| 1778 } | |
| 1779 | |
| 1780 /* Min (erosion) downscaling; total reduction (4 sx, 4 sy). */ | |
| 1781 pixt1 = pixScaleGrayMinMax(pixs2, sx, sy, L_CHOOSE_MIN); | |
| 1782 | |
| 1783 /* pixDisplay(pixt1, 300, 200); */ | |
| 1784 | |
| 1785 /* Threshold to identify fg; paint bg pixels to white. */ | |
| 1786 pixb = pixThresholdToBinary(pixt1, thresh); /* fg pixels */ | |
| 1787 pixInvert(pixb, pixb); | |
| 1788 pixPaintThroughMask(pixt1, pixb, 0, 0, 255); | |
| 1789 pixDestroy(&pixb); | |
| 1790 | |
| 1791 /* Replicative expansion by 2x to (sx, sy). */ | |
| 1792 pixt2 = pixExpandReplicate(pixt1, 2); | |
| 1793 | |
| 1794 /* pixDisplay(pixt2, 500, 200); */ | |
| 1795 | |
| 1796 /* Fill holes in the fg by propagation */ | |
| 1797 pixFillMapHoles(pixt2, w / sx, h / sy, L_FILL_WHITE); | |
| 1798 | |
| 1799 /* pixDisplay(pixt2, 700, 200); */ | |
| 1800 | |
| 1801 /* Smooth with 17x17 kernel. */ | |
| 1802 pixt3 = pixBlockconv(pixt2, 8, 8); | |
| 1803 pixRasterop(pixd, 0, 0, wd, hd, PIX_SRC, pixt3, 0, 0); | |
| 1804 | |
| 1805 /* Paint the image parts black. */ | |
| 1806 pixims = pixScaleBySampling(pixim, 1. / sx, 1. / sy); | |
| 1807 pixPaintThroughMask(pixd, pixims, 0, 0, 0); | |
| 1808 | |
| 1809 pixDestroy(&pixs2); | |
| 1810 pixDestroy(&pixt1); | |
| 1811 pixDestroy(&pixt2); | |
| 1812 pixDestroy(&pixt3); | |
| 1813 return 0; | |
| 1814 } | |
| 1815 #endif /* Not working properly: do not use */ | |
| 1816 | |
| 1817 | |
| 1818 /*------------------------------------------------------------------* | |
| 1819 * Generate inverted background map * | |
| 1820 *------------------------------------------------------------------*/ | |
| 1821 /*! | |
| 1822 * \brief pixGetInvBackgroundMap() | |
| 1823 * | |
| 1824 * \param[in] pixs 8 bpp grayscale; no colormap | |
| 1825 * \param[in] bgval target bg val; typ. > 128 | |
| 1826 * \param[in] smoothx half-width of block convolution kernel width | |
| 1827 * \param[in] smoothy half-width of block convolution kernel height | |
| 1828 * \return pixd 16 bpp, or NULL on error | |
| 1829 * | |
| 1830 * <pre> | |
| 1831 * Notes: | |
| 1832 * (1) bgval should typically be > 120 and < 240 | |
| 1833 * (2) pixd is a normalization image; the original image is | |
| 1834 * multiplied by pixd and the result is divided by 256. | |
| 1835 * </pre> | |
| 1836 */ | |
| 1837 PIX * | |
| 1838 pixGetInvBackgroundMap(PIX *pixs, | |
| 1839 l_int32 bgval, | |
| 1840 l_int32 smoothx, | |
| 1841 l_int32 smoothy) | |
| 1842 { | |
| 1843 l_int32 w, h, wplsm, wpld, i, j; | |
| 1844 l_int32 val, val16; | |
| 1845 l_uint32 *datasm, *datad, *linesm, *lined; | |
| 1846 PIX *pixsm, *pixd; | |
| 1847 | |
| 1848 if (!pixs || pixGetDepth(pixs) != 8) | |
| 1849 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL); | |
| 1850 if (pixGetColormap(pixs)) | |
| 1851 return (PIX *)ERROR_PTR("pixs has colormap", __func__, NULL); | |
| 1852 pixGetDimensions(pixs, &w, &h, NULL); | |
| 1853 if (w < 5 || h < 5) | |
| 1854 return (PIX *)ERROR_PTR("w and h must be >= 5", __func__, NULL); | |
| 1855 | |
| 1856 /* smooth the map image */ | |
| 1857 pixsm = pixBlockconv(pixs, smoothx, smoothy); | |
| 1858 datasm = pixGetData(pixsm); | |
| 1859 wplsm = pixGetWpl(pixsm); | |
| 1860 | |
| 1861 /* invert the map image, scaling up to preserve dynamic range */ | |
| 1862 pixd = pixCreate(w, h, 16); | |
| 1863 datad = pixGetData(pixd); | |
| 1864 wpld = pixGetWpl(pixd); | |
| 1865 for (i = 0; i < h; i++) { | |
| 1866 linesm = datasm + i * wplsm; | |
| 1867 lined = datad + i * wpld; | |
| 1868 for (j = 0; j < w; j++) { | |
| 1869 val = GET_DATA_BYTE(linesm, j); | |
| 1870 if (val > 0) | |
| 1871 val16 = (256 * bgval) / val; | |
| 1872 else { /* shouldn't happen */ | |
| 1873 L_WARNING("smoothed bg has 0 pixel!\n", __func__); | |
| 1874 val16 = bgval / 2; | |
| 1875 } | |
| 1876 SET_DATA_TWO_BYTES(lined, j, val16); | |
| 1877 } | |
| 1878 } | |
| 1879 | |
| 1880 pixDestroy(&pixsm); | |
| 1881 pixCopyResolution(pixd, pixs); | |
| 1882 return pixd; | |
| 1883 } | |
| 1884 | |
| 1885 | |
| 1886 /*------------------------------------------------------------------* | |
| 1887 * Apply background map to image * | |
| 1888 *------------------------------------------------------------------*/ | |
| 1889 /*! | |
| 1890 * \brief pixApplyInvBackgroundGrayMap() | |
| 1891 * | |
| 1892 * \param[in] pixs 8 bpp grayscale; no colormap | |
| 1893 * \param[in] pixm 16 bpp, inverse background map | |
| 1894 * \param[in] sx tile width in pixels | |
| 1895 * \param[in] sy tile height in pixels | |
| 1896 * \return pixd 8 bpp, or NULL on error | |
| 1897 */ | |
| 1898 PIX * | |
| 1899 pixApplyInvBackgroundGrayMap(PIX *pixs, | |
| 1900 PIX *pixm, | |
| 1901 l_int32 sx, | |
| 1902 l_int32 sy) | |
| 1903 { | |
| 1904 l_int32 w, h, wm, hm, wpls, wpld, i, j, k, m, xoff, yoff; | |
| 1905 l_int32 vals, vald; | |
| 1906 l_uint32 val16; | |
| 1907 l_uint32 *datas, *datad, *lines, *lined, *flines, *flined; | |
| 1908 PIX *pixd; | |
| 1909 | |
| 1910 if (!pixs || pixGetDepth(pixs) != 8) | |
| 1911 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL); | |
| 1912 if (pixGetColormap(pixs)) | |
| 1913 return (PIX *)ERROR_PTR("pixs has colormap", __func__, NULL); | |
| 1914 if (!pixm || pixGetDepth(pixm) != 16) | |
| 1915 return (PIX *)ERROR_PTR("pixm undefined or not 16 bpp", __func__, NULL); | |
| 1916 if (sx == 0 || sy == 0) | |
| 1917 return (PIX *)ERROR_PTR("invalid sx and/or sy", __func__, NULL); | |
| 1918 | |
| 1919 datas = pixGetData(pixs); | |
| 1920 wpls = pixGetWpl(pixs); | |
| 1921 pixGetDimensions(pixs, &w, &h, NULL); | |
| 1922 pixGetDimensions(pixm, &wm, &hm, NULL); | |
| 1923 if ((pixd = pixCreateTemplate(pixs)) == NULL) | |
| 1924 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); | |
| 1925 datad = pixGetData(pixd); | |
| 1926 wpld = pixGetWpl(pixd); | |
| 1927 for (i = 0; i < hm; i++) { | |
| 1928 lines = datas + sy * i * wpls; | |
| 1929 lined = datad + sy * i * wpld; | |
| 1930 yoff = sy * i; | |
| 1931 for (j = 0; j < wm; j++) { | |
| 1932 pixGetPixel(pixm, j, i, &val16); | |
| 1933 xoff = sx * j; | |
| 1934 for (k = 0; k < sy && yoff + k < h; k++) { | |
| 1935 flines = lines + k * wpls; | |
| 1936 flined = lined + k * wpld; | |
| 1937 for (m = 0; m < sx && xoff + m < w; m++) { | |
| 1938 vals = GET_DATA_BYTE(flines, xoff + m); | |
| 1939 vald = (vals * val16) / 256; | |
| 1940 vald = L_MIN(vald, 255); | |
| 1941 SET_DATA_BYTE(flined, xoff + m, vald); | |
| 1942 } | |
| 1943 } | |
| 1944 } | |
| 1945 } | |
| 1946 | |
| 1947 return pixd; | |
| 1948 } | |
| 1949 | |
| 1950 | |
| 1951 /*! | |
| 1952 * \brief pixApplyInvBackgroundRGBMap() | |
| 1953 * | |
| 1954 * \param[in] pixs 32 bpp rbg | |
| 1955 * \param[in] pixmr 16 bpp, red inverse background map | |
| 1956 * \param[in] pixmg 16 bpp, green inverse background map | |
| 1957 * \param[in] pixmb 16 bpp, blue inverse background map | |
| 1958 * \param[in] sx tile width in pixels | |
| 1959 * \param[in] sy tile height in pixels | |
| 1960 * \return pixd 32 bpp rbg, or NULL on error | |
| 1961 */ | |
| 1962 PIX * | |
| 1963 pixApplyInvBackgroundRGBMap(PIX *pixs, | |
| 1964 PIX *pixmr, | |
| 1965 PIX *pixmg, | |
| 1966 PIX *pixmb, | |
| 1967 l_int32 sx, | |
| 1968 l_int32 sy) | |
| 1969 { | |
| 1970 l_int32 w, h, wm, hm, wpls, wpld, i, j, k, m, xoff, yoff; | |
| 1971 l_int32 rvald, gvald, bvald; | |
| 1972 l_uint32 vals; | |
| 1973 l_uint32 rval16, gval16, bval16; | |
| 1974 l_uint32 *datas, *datad, *lines, *lined, *flines, *flined; | |
| 1975 PIX *pixd; | |
| 1976 | |
| 1977 if (!pixs) | |
| 1978 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 1979 if (pixGetDepth(pixs) != 32) | |
| 1980 return (PIX *)ERROR_PTR("pixs not 32 bpp", __func__, NULL); | |
| 1981 if (!pixmr || !pixmg || !pixmb) | |
| 1982 return (PIX *)ERROR_PTR("pix maps not all defined", __func__, NULL); | |
| 1983 if (pixGetDepth(pixmr) != 16 || pixGetDepth(pixmg) != 16 || | |
| 1984 pixGetDepth(pixmb) != 16) | |
| 1985 return (PIX *)ERROR_PTR("pix maps not all 16 bpp", __func__, NULL); | |
| 1986 if (sx == 0 || sy == 0) | |
| 1987 return (PIX *)ERROR_PTR("invalid sx and/or sy", __func__, NULL); | |
| 1988 | |
| 1989 datas = pixGetData(pixs); | |
| 1990 wpls = pixGetWpl(pixs); | |
| 1991 w = pixGetWidth(pixs); | |
| 1992 h = pixGetHeight(pixs); | |
| 1993 wm = pixGetWidth(pixmr); | |
| 1994 hm = pixGetHeight(pixmr); | |
| 1995 if ((pixd = pixCreateTemplate(pixs)) == NULL) | |
| 1996 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); | |
| 1997 datad = pixGetData(pixd); | |
| 1998 wpld = pixGetWpl(pixd); | |
| 1999 for (i = 0; i < hm; i++) { | |
| 2000 lines = datas + sy * i * wpls; | |
| 2001 lined = datad + sy * i * wpld; | |
| 2002 yoff = sy * i; | |
| 2003 for (j = 0; j < wm; j++) { | |
| 2004 pixGetPixel(pixmr, j, i, &rval16); | |
| 2005 pixGetPixel(pixmg, j, i, &gval16); | |
| 2006 pixGetPixel(pixmb, j, i, &bval16); | |
| 2007 xoff = sx * j; | |
| 2008 for (k = 0; k < sy && yoff + k < h; k++) { | |
| 2009 flines = lines + k * wpls; | |
| 2010 flined = lined + k * wpld; | |
| 2011 for (m = 0; m < sx && xoff + m < w; m++) { | |
| 2012 vals = *(flines + xoff + m); | |
| 2013 rvald = ((vals >> 24) * rval16) / 256; | |
| 2014 rvald = L_MIN(rvald, 255); | |
| 2015 gvald = (((vals >> 16) & 0xff) * gval16) / 256; | |
| 2016 gvald = L_MIN(gvald, 255); | |
| 2017 bvald = (((vals >> 8) & 0xff) * bval16) / 256; | |
| 2018 bvald = L_MIN(bvald, 255); | |
| 2019 composeRGBPixel(rvald, gvald, bvald, flined + xoff + m); | |
| 2020 } | |
| 2021 } | |
| 2022 } | |
| 2023 } | |
| 2024 | |
| 2025 return pixd; | |
| 2026 } | |
| 2027 | |
| 2028 | |
| 2029 /*------------------------------------------------------------------* | |
| 2030 * Apply variable map * | |
| 2031 *------------------------------------------------------------------*/ | |
| 2032 /*! | |
| 2033 * \brief pixApplyVariableGrayMap() | |
| 2034 * | |
| 2035 * \param[in] pixs 8 bpp | |
| 2036 * \param[in] pixg 8 bpp, variable map | |
| 2037 * \param[in] target typ. 128 for threshold | |
| 2038 * \return pixd 8 bpp, or NULL on error | |
| 2039 * | |
| 2040 * <pre> | |
| 2041 * Notes: | |
| 2042 * (1) Suppose you have an image that you want to transform based | |
| 2043 * on some photometric measurement at each point, such as the | |
| 2044 * threshold value for binarization. Representing the photometric | |
| 2045 * measurement as an image pixg, you can threshold in input image | |
| 2046 * using pixVarThresholdToBinary(). Alternatively, you can map | |
| 2047 * the input image pointwise so that the threshold over the | |
| 2048 * entire image becomes a constant, such as 128. For example, | |
| 2049 * if a pixel in pixg is 150 and the target is 128, the | |
| 2050 * corresponding pixel in pixs is mapped linearly to a value | |
| 2051 * (128/150) of the input value. If the resulting mapped image | |
| 2052 * pixd were then thresholded at 128, you would obtain the | |
| 2053 * same result as a direct binarization using pixg with | |
| 2054 * pixVarThresholdToBinary(). | |
| 2055 * (2) The sizes of pixs and pixg must be equal. | |
| 2056 * </pre> | |
| 2057 */ | |
| 2058 PIX * | |
| 2059 pixApplyVariableGrayMap(PIX *pixs, | |
| 2060 PIX *pixg, | |
| 2061 l_int32 target) | |
| 2062 { | |
| 2063 l_int32 i, j, w, h, d, wpls, wplg, wpld, vals, valg, vald; | |
| 2064 l_uint8 *lut; | |
| 2065 l_uint32 *datas, *datag, *datad, *lines, *lineg, *lined; | |
| 2066 l_float32 fval; | |
| 2067 PIX *pixd; | |
| 2068 | |
| 2069 if (!pixs) | |
| 2070 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 2071 if (!pixg) | |
| 2072 return (PIX *)ERROR_PTR("pixg not defined", __func__, NULL); | |
| 2073 if (!pixSizesEqual(pixs, pixg)) | |
| 2074 return (PIX *)ERROR_PTR("pix sizes not equal", __func__, NULL); | |
| 2075 pixGetDimensions(pixs, &w, &h, &d); | |
| 2076 if (d != 8) | |
| 2077 return (PIX *)ERROR_PTR("depth not 8 bpp", __func__, NULL); | |
| 2078 | |
| 2079 /* Generate a LUT for the mapping if the image is large enough | |
| 2080 * to warrant the overhead. The LUT is of size 2^16. For the | |
| 2081 * index to the table, get the MSB from pixs and the LSB from pixg. | |
| 2082 * Note: this LUT is bigger than the typical 32K L1 cache, so | |
| 2083 * we expect cache misses. L2 latencies are about 5ns. But | |
| 2084 * division is slooooow. For large images, this function is about | |
| 2085 * 4x faster when using the LUT. C'est la vie. */ | |
| 2086 lut = NULL; | |
| 2087 if (w * h > 100000) { /* more pixels than 2^16 */ | |
| 2088 lut = (l_uint8 *)LEPT_CALLOC(0x10000, sizeof(l_uint8)); | |
| 2089 for (i = 0; i < 256; i++) { | |
| 2090 for (j = 0; j < 256; j++) { | |
| 2091 fval = (l_float32)(i * target) / (j + 0.5); | |
| 2092 lut[(i << 8) + j] = L_MIN(255, (l_int32)(fval + 0.5)); | |
| 2093 } | |
| 2094 } | |
| 2095 } | |
| 2096 | |
| 2097 if ((pixd = pixCreate(w, h, 8)) == NULL) { | |
| 2098 LEPT_FREE(lut); | |
| 2099 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); | |
| 2100 } | |
| 2101 pixCopyResolution(pixd, pixs); | |
| 2102 datad = pixGetData(pixd); | |
| 2103 wpld = pixGetWpl(pixd); | |
| 2104 datas = pixGetData(pixs); | |
| 2105 wpls = pixGetWpl(pixs); | |
| 2106 datag = pixGetData(pixg); | |
| 2107 wplg = pixGetWpl(pixg); | |
| 2108 for (i = 0; i < h; i++) { | |
| 2109 lines = datas + i * wpls; | |
| 2110 lineg = datag + i * wplg; | |
| 2111 lined = datad + i * wpld; | |
| 2112 if (lut) { | |
| 2113 for (j = 0; j < w; j++) { | |
| 2114 vals = GET_DATA_BYTE(lines, j); | |
| 2115 valg = GET_DATA_BYTE(lineg, j); | |
| 2116 vald = lut[(vals << 8) + valg]; | |
| 2117 SET_DATA_BYTE(lined, j, vald); | |
| 2118 } | |
| 2119 } | |
| 2120 else { | |
| 2121 for (j = 0; j < w; j++) { | |
| 2122 vals = GET_DATA_BYTE(lines, j); | |
| 2123 valg = GET_DATA_BYTE(lineg, j); | |
| 2124 fval = (l_float32)(vals * target) / (valg + 0.5); | |
| 2125 vald = L_MIN(255, (l_int32)(fval + 0.5)); | |
| 2126 SET_DATA_BYTE(lined, j, vald); | |
| 2127 } | |
| 2128 } | |
| 2129 } | |
| 2130 | |
| 2131 LEPT_FREE(lut); | |
| 2132 return pixd; | |
| 2133 } | |
| 2134 | |
| 2135 | |
| 2136 /*------------------------------------------------------------------* | |
| 2137 * Non-adaptive (global) mapping * | |
| 2138 *------------------------------------------------------------------*/ | |
| 2139 /*! | |
| 2140 * \brief pixGlobalNormRGB() | |
| 2141 * | |
| 2142 * \param[in] pixd [optional] null, existing or equal to pixs | |
| 2143 * \param[in] pixs 32 bpp rgb, or colormapped | |
| 2144 * \param[in] rval, gval, bval pixel values in pixs that are | |
| 2145 * linearly mapped to mapval | |
| 2146 * \param[in] mapval use 255 for mapping to white | |
| 2147 * \return pixd 32 bpp rgb or colormapped, or NULL on error | |
| 2148 * | |
| 2149 * <pre> | |
| 2150 * Notes: | |
| 2151 * (1) The value of pixd determines if the results are written to a | |
| 2152 * new pix (use NULL), in-place to pixs (use pixs), or to some | |
| 2153 * other existing pix. | |
| 2154 * (2) This does a global normalization of an image where the | |
| 2155 * r,g,b color components are not balanced. Thus, white in pixs is | |
| 2156 * represented by a set of r,g,b values that are not all 255. | |
| 2157 * (3) The input values (rval, gval, bval) should be chosen to | |
| 2158 * represent the gray color (mapval, mapval, mapval) in src. | |
| 2159 * Thus, this function will map (rval, gval, bval) to that gray color. | |
| 2160 * (4) Typically, mapval = 255, so that (rval, gval, bval) | |
| 2161 * corresponds to the white point of src. In that case, these | |
| 2162 * parameters should be chosen so that few pixels have higher values. | |
| 2163 * (5) In all cases, we do a linear TRC separately on each of the | |
| 2164 * components, saturating at 255. | |
| 2165 * (6) If the input pix is 8 bpp without a colormap, you can get | |
| 2166 * this functionality with mapval = 255 by calling: | |
| 2167 * pixGammaTRC(pixd, pixs, 1.0, 0, bgval); | |
| 2168 * where bgval is the value you want to be mapped to 255. | |
| 2169 * Or more generally, if you want bgval to be mapped to mapval: | |
| 2170 * pixGammaTRC(pixd, pixs, 1.0, 0, 255 * bgval / mapval); | |
| 2171 * </pre> | |
| 2172 */ | |
| 2173 PIX * | |
| 2174 pixGlobalNormRGB(PIX *pixd, | |
| 2175 PIX *pixs, | |
| 2176 l_int32 rval, | |
| 2177 l_int32 gval, | |
| 2178 l_int32 bval, | |
| 2179 l_int32 mapval) | |
| 2180 { | |
| 2181 l_int32 w, h, d, i, j, ncolors, rv, gv, bv, wpl; | |
| 2182 l_int32 *rarray, *garray, *barray; | |
| 2183 l_uint32 *data, *line; | |
| 2184 NUMA *nar, *nag, *nab; | |
| 2185 PIXCMAP *cmap; | |
| 2186 | |
| 2187 if (!pixs) | |
| 2188 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 2189 cmap = pixGetColormap(pixs); | |
| 2190 pixGetDimensions(pixs, &w, &h, &d); | |
| 2191 if (!cmap && d != 32) | |
| 2192 return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", __func__, NULL); | |
| 2193 if (mapval <= 0) { | |
| 2194 L_WARNING("mapval must be > 0; setting to 255\n", __func__); | |
| 2195 mapval = 255; | |
| 2196 } | |
| 2197 | |
| 2198 /* Prepare pixd to be a copy of pixs */ | |
| 2199 if ((pixd = pixCopy(pixd, pixs)) == NULL) | |
| 2200 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); | |
| 2201 | |
| 2202 /* Generate the TRC maps for each component. Make sure the | |
| 2203 * upper range for each color is greater than zero. */ | |
| 2204 nar = numaGammaTRC(1.0, 0, L_MAX(1, 255 * rval / mapval)); | |
| 2205 nag = numaGammaTRC(1.0, 0, L_MAX(1, 255 * gval / mapval)); | |
| 2206 nab = numaGammaTRC(1.0, 0, L_MAX(1, 255 * bval / mapval)); | |
| 2207 | |
| 2208 /* Extract copies of the internal arrays */ | |
| 2209 rarray = numaGetIArray(nar); | |
| 2210 garray = numaGetIArray(nag); | |
| 2211 barray = numaGetIArray(nab); | |
| 2212 if (!nar || !nag || !nab || !rarray || !garray || !barray) { | |
| 2213 L_ERROR("allocation failure in arrays\n", __func__); | |
| 2214 goto cleanup_arrays; | |
| 2215 } | |
| 2216 | |
| 2217 if (cmap) { | |
| 2218 ncolors = pixcmapGetCount(cmap); | |
| 2219 for (i = 0; i < ncolors; i++) { | |
| 2220 pixcmapGetColor(cmap, i, &rv, &gv, &bv); | |
| 2221 pixcmapResetColor(cmap, i, rarray[rv], garray[gv], barray[bv]); | |
| 2222 } | |
| 2223 } | |
| 2224 else { | |
| 2225 data = pixGetData(pixd); | |
| 2226 wpl = pixGetWpl(pixd); | |
| 2227 for (i = 0; i < h; i++) { | |
| 2228 line = data + i * wpl; | |
| 2229 for (j = 0; j < w; j++) { | |
| 2230 extractRGBValues(line[j], &rv, &gv, &bv); | |
| 2231 composeRGBPixel(rarray[rv], garray[gv], barray[bv], line + j); | |
| 2232 } | |
| 2233 } | |
| 2234 } | |
| 2235 | |
| 2236 cleanup_arrays: | |
| 2237 numaDestroy(&nar); | |
| 2238 numaDestroy(&nag); | |
| 2239 numaDestroy(&nab); | |
| 2240 LEPT_FREE(rarray); | |
| 2241 LEPT_FREE(garray); | |
| 2242 LEPT_FREE(barray); | |
| 2243 return pixd; | |
| 2244 } | |
| 2245 | |
| 2246 | |
| 2247 /*! | |
| 2248 * \brief pixGlobalNormNoSatRGB() | |
| 2249 * | |
| 2250 * \param[in] pixd [optional] null, existing or equal to pixs | |
| 2251 * \param[in] pixs 32 bpp rgb | |
| 2252 * \param[in] rval, gval, bval pixel values in pixs that are | |
| 2253 * linearly mapped to mapval; but see below | |
| 2254 * \param[in] factor subsampling factor; integer >= 1 | |
| 2255 * \param[in] rank between 0.0 and 1.0; typ. use a value near 1.0 | |
| 2256 * \return pixd 32 bpp rgb, or NULL on error | |
| 2257 * | |
| 2258 * <pre> | |
| 2259 * Notes: | |
| 2260 * (1) This is a version of pixGlobalNormRGB(), where the output | |
| 2261 * intensity is scaled back so that a controlled fraction of | |
| 2262 * pixel components is allowed to saturate. See comments in | |
| 2263 * pixGlobalNormRGB(). | |
| 2264 * (2) The value of pixd determines if the results are written to a | |
| 2265 * new pix (use NULL), in-place to pixs (use pixs), or to some | |
| 2266 * other existing pix. | |
| 2267 * (3) This does a global normalization of an image where the | |
| 2268 * r,g,b color components are not balanced. Thus, white in pixs is | |
| 2269 * represented by a set of r,g,b values that are not all 255. | |
| 2270 * (4) The input values (rval, gval, bval) can be chosen to be the | |
| 2271 * color that, after normalization, becomes white background. | |
| 2272 * For images that are mostly background, the closer these values | |
| 2273 * are to the median component values, the closer the resulting | |
| 2274 * background will be to gray, becoming white at the brightest places. | |
| 2275 * (5) The mapval used in pixGlobalNormRGB() is computed here to | |
| 2276 * avoid saturation of any component in the image (save for a | |
| 2277 * fraction of the pixels given by the input rank value). | |
| 2278 * </pre> | |
| 2279 */ | |
| 2280 PIX * | |
| 2281 pixGlobalNormNoSatRGB(PIX *pixd, | |
| 2282 PIX *pixs, | |
| 2283 l_int32 rval, | |
| 2284 l_int32 gval, | |
| 2285 l_int32 bval, | |
| 2286 l_int32 factor, | |
| 2287 l_float32 rank) | |
| 2288 { | |
| 2289 l_int32 mapval; | |
| 2290 l_float32 rankrval, rankgval, rankbval; | |
| 2291 l_float32 rfract, gfract, bfract, maxfract; | |
| 2292 | |
| 2293 if (!pixs) | |
| 2294 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 2295 if (pixGetDepth(pixs) != 32) | |
| 2296 return (PIX *)ERROR_PTR("pixs not 32 bpp", __func__, NULL); | |
| 2297 if (factor < 1) | |
| 2298 return (PIX *)ERROR_PTR("sampling factor < 1", __func__, NULL); | |
| 2299 if (rank < 0.0 || rank > 1.0) | |
| 2300 return (PIX *)ERROR_PTR("rank not in [0.0 ... 1.0]", __func__, NULL); | |
| 2301 if (rval <= 0 || gval <= 0 || bval <= 0) | |
| 2302 return (PIX *)ERROR_PTR("invalid estim. color values", __func__, NULL); | |
| 2303 | |
| 2304 /* The max value for each component may be larger than the | |
| 2305 * input estimated background value. In that case, mapping | |
| 2306 * for those pixels would saturate. To prevent saturation, | |
| 2307 * we compute the fraction for each component by which we | |
| 2308 * would oversaturate. Then take the max of these, and | |
| 2309 * reduce, uniformly over all components, the output intensity | |
| 2310 * by this value. Then no component will saturate. | |
| 2311 * In practice, if rank < 1.0, a fraction of pixels | |
| 2312 * may have a component saturate. By keeping rank close to 1.0, | |
| 2313 * that fraction can be made arbitrarily small. */ | |
| 2314 pixGetRankValueMaskedRGB(pixs, NULL, 0, 0, factor, rank, &rankrval, | |
| 2315 &rankgval, &rankbval); | |
| 2316 rfract = rankrval / (l_float32)rval; | |
| 2317 gfract = rankgval / (l_float32)gval; | |
| 2318 bfract = rankbval / (l_float32)bval; | |
| 2319 maxfract = L_MAX(rfract, gfract); | |
| 2320 maxfract = L_MAX(maxfract, bfract); | |
| 2321 #if DEBUG_GLOBAL | |
| 2322 lept_stderr("rankrval = %7.2f, rankgval = %7.2f, rankbval = %7.2f\n", | |
| 2323 rankrval, rankgval, rankbval); | |
| 2324 lept_stderr("rfract = %7.4f, gfract = %7.4f, bfract = %7.4f\n", | |
| 2325 rfract, gfract, bfract); | |
| 2326 #endif /* DEBUG_GLOBAL */ | |
| 2327 | |
| 2328 mapval = (l_int32)(255. / maxfract); | |
| 2329 pixd = pixGlobalNormRGB(pixd, pixs, rval, gval, bval, mapval); | |
| 2330 return pixd; | |
| 2331 } | |
| 2332 | |
| 2333 | |
| 2334 /*------------------------------------------------------------------* | |
| 2335 * Adaptive threshold spread normalization * | |
| 2336 *------------------------------------------------------------------*/ | |
| 2337 /*! | |
| 2338 * \brief pixThresholdSpreadNorm() | |
| 2339 * | |
| 2340 * \param[in] pixs 8 bpp grayscale; not colormapped | |
| 2341 * \param[in] filtertype L_SOBEL_EDGE or L_TWO_SIDED_EDGE; | |
| 2342 * \param[in] edgethresh threshold on magnitude of edge filter; | |
| 2343 * typ 10-20 | |
| 2344 * \param[in] smoothx, smoothy half-width of convolution kernel applied to | |
| 2345 * spread threshold: use 0 for no smoothing | |
| 2346 * \param[in] gamma gamma correction; typ. about 0.7 | |
| 2347 * \param[in] minval input value that gives 0 for output; typ. -25 | |
| 2348 * \param[in] maxval input value that gives 255 for output; | |
| 2349 * typ. 255 | |
| 2350 * \param[in] targetthresh target threshold for normalization | |
| 2351 * \param[out] ppixth [optional] computed local threshold value | |
| 2352 * \param[out] ppixb [optional] thresholded normalized image | |
| 2353 * \param[out] ppixd [optional] normalized image | |
| 2354 * \return 0 if OK, 1 on error | |
| 2355 * | |
| 2356 * <pre> | |
| 2357 * Notes: | |
| 2358 * (1) The basis of this approach is the use of seed spreading | |
| 2359 * on a (possibly) sparse set of estimates for the local threshold. | |
| 2360 * The resulting dense estimates are smoothed by convolution | |
| 2361 * and used to either threshold the input image or normalize it | |
| 2362 * with a local transformation that linearly maps the pixels so | |
| 2363 * that the local threshold estimate becomes constant over the | |
| 2364 * resulting image. This approach is one of several that | |
| 2365 * have been suggested (and implemented) by Ray Smith. | |
| 2366 * (2) You can use either the Sobel or TwoSided edge filters. | |
| 2367 * The results appear to be similar, using typical values | |
| 2368 * of edgethresh in the rang 10-20. | |
| 2369 * (3) To skip the trc enhancement, use gamma = 1.0, minval = 0 | |
| 2370 * and maxval = 255. | |
| 2371 * (4) For the normalized image pixd, each pixel is linearly mapped | |
| 2372 * in such a way that the local threshold is equal to targetthresh. | |
| 2373 * (5) The full width and height of the convolution kernel | |
| 2374 * are (2 * smoothx + 1) and (2 * smoothy + 1). | |
| 2375 * (6) This function can be used with the pixtiling utility if the | |
| 2376 * images are too large. See pixOtsuAdaptiveThreshold() for | |
| 2377 * an example of this. | |
| 2378 * </pre> | |
| 2379 */ | |
| 2380 l_ok | |
| 2381 pixThresholdSpreadNorm(PIX *pixs, | |
| 2382 l_int32 filtertype, | |
| 2383 l_int32 edgethresh, | |
| 2384 l_int32 smoothx, | |
| 2385 l_int32 smoothy, | |
| 2386 l_float32 gamma, | |
| 2387 l_int32 minval, | |
| 2388 l_int32 maxval, | |
| 2389 l_int32 targetthresh, | |
| 2390 PIX **ppixth, | |
| 2391 PIX **ppixb, | |
| 2392 PIX **ppixd) | |
| 2393 { | |
| 2394 PIX *pixe, *pixet, *pixsd, *pixg1, *pixg2, *pixth; | |
| 2395 | |
| 2396 if (ppixth) *ppixth = NULL; | |
| 2397 if (ppixb) *ppixb = NULL; | |
| 2398 if (ppixd) *ppixd = NULL; | |
| 2399 if (!pixs || pixGetDepth(pixs) != 8) | |
| 2400 return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1); | |
| 2401 if (pixGetColormap(pixs)) | |
| 2402 return ERROR_INT("pixs is colormapped", __func__, 1); | |
| 2403 if (!ppixth && !ppixb && !ppixd) | |
| 2404 return ERROR_INT("no output requested", __func__, 1); | |
| 2405 if (filtertype != L_SOBEL_EDGE && filtertype != L_TWO_SIDED_EDGE) | |
| 2406 return ERROR_INT("invalid filter type", __func__, 1); | |
| 2407 | |
| 2408 /* Get the thresholded edge pixels. These are the ones | |
| 2409 * that have values in pixs near the local optimal fg/bg threshold. */ | |
| 2410 if (filtertype == L_SOBEL_EDGE) | |
| 2411 pixe = pixSobelEdgeFilter(pixs, L_VERTICAL_EDGES); | |
| 2412 else /* L_TWO_SIDED_EDGE */ | |
| 2413 pixe = pixTwoSidedEdgeFilter(pixs, L_VERTICAL_EDGES); | |
| 2414 pixet = pixThresholdToBinary(pixe, edgethresh); | |
| 2415 pixInvert(pixet, pixet); | |
| 2416 | |
| 2417 /* Build a seed image whose only nonzero values are those | |
| 2418 * values of pixs corresponding to pixels in the fg of pixet. */ | |
| 2419 pixsd = pixCreateTemplate(pixs); | |
| 2420 pixCombineMasked(pixsd, pixs, pixet); | |
| 2421 | |
| 2422 /* Spread the seed and optionally smooth to reduce noise */ | |
| 2423 pixg1 = pixSeedspread(pixsd, 4); | |
| 2424 pixg2 = pixBlockconv(pixg1, smoothx, smoothy); | |
| 2425 | |
| 2426 /* Optionally do a gamma enhancement */ | |
| 2427 pixth = pixGammaTRC(NULL, pixg2, gamma, minval, maxval); | |
| 2428 | |
| 2429 /* Do the mapping and thresholding */ | |
| 2430 if (ppixd) { | |
| 2431 *ppixd = pixApplyVariableGrayMap(pixs, pixth, targetthresh); | |
| 2432 if (ppixb) | |
| 2433 *ppixb = pixThresholdToBinary(*ppixd, targetthresh); | |
| 2434 } | |
| 2435 else if (ppixb) | |
| 2436 *ppixb = pixVarThresholdToBinary(pixs, pixth); | |
| 2437 | |
| 2438 if (ppixth) | |
| 2439 *ppixth = pixth; | |
| 2440 else | |
| 2441 pixDestroy(&pixth); | |
| 2442 | |
| 2443 pixDestroy(&pixe); | |
| 2444 pixDestroy(&pixet); | |
| 2445 pixDestroy(&pixsd); | |
| 2446 pixDestroy(&pixg1); | |
| 2447 pixDestroy(&pixg2); | |
| 2448 return 0; | |
| 2449 } | |
| 2450 | |
| 2451 | |
| 2452 /*------------------------------------------------------------------* | |
| 2453 * Adaptive background normalization (flexible adaptaption) * | |
| 2454 *------------------------------------------------------------------*/ | |
| 2455 /*! | |
| 2456 * \brief pixBackgroundNormFlex() | |
| 2457 * | |
| 2458 * \param[in] pixs 8 bpp grayscale; not colormapped | |
| 2459 * \param[in] sx, sy desired tile dimensions; size may vary; | |
| 2460 * use values between 3 and 10 | |
| 2461 * \param[in] smoothx, smoothy half-width of convolution kernel applied to | |
| 2462 * threshold array: use values between 1 and 3 | |
| 2463 * \param[in] delta difference parameter in basin filling; | |
| 2464 * use 0 to skip | |
| 2465 * \return pixd 8 bpp, background-normalized), or NULL on error | |
| 2466 * | |
| 2467 * <pre> | |
| 2468 * Notes: | |
| 2469 * (1) This does adaptation flexibly to a quickly varying background. | |
| 2470 * For that reason, all input parameters should be small. | |
| 2471 * (2) sx and sy give the tile size; they should be in [5 - 7]. | |
| 2472 * (3) The full width and height of the convolution kernel | |
| 2473 * are (2 * smoothx + 1) and (2 * smoothy + 1). They | |
| 2474 * should be in [1 - 2]. | |
| 2475 * (4) Basin filling is used to fill the large fg regions. The | |
| 2476 * parameter %delta measures the height that the black | |
| 2477 * background is raised from the local minima. By raising | |
| 2478 * the background, it is possible to threshold the large | |
| 2479 * fg regions to foreground. If %delta is too large, | |
| 2480 * bg regions will be lifted, causing thickening of | |
| 2481 * the fg regions. Use 0 to skip. | |
| 2482 * </pre> | |
| 2483 */ | |
| 2484 PIX * | |
| 2485 pixBackgroundNormFlex(PIX *pixs, | |
| 2486 l_int32 sx, | |
| 2487 l_int32 sy, | |
| 2488 l_int32 smoothx, | |
| 2489 l_int32 smoothy, | |
| 2490 l_int32 delta) | |
| 2491 { | |
| 2492 l_float32 scalex, scaley; | |
| 2493 PIX *pixt, *pixsd, *pixmin, *pixbg, *pixbgi, *pixd; | |
| 2494 | |
| 2495 if (!pixs || pixGetDepth(pixs) != 8) | |
| 2496 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, NULL); | |
| 2497 if (pixGetColormap(pixs)) | |
| 2498 return (PIX *)ERROR_PTR("pixs is colormapped", __func__, NULL); | |
| 2499 if (sx < 3 || sy < 3) | |
| 2500 return (PIX *)ERROR_PTR("sx and/or sy less than 3", __func__, NULL); | |
| 2501 if (sx > 10 || sy > 10) | |
| 2502 return (PIX *)ERROR_PTR("sx and/or sy exceed 10", __func__, NULL); | |
| 2503 if (smoothx < 1 || smoothy < 1) | |
| 2504 return (PIX *)ERROR_PTR("smooth params less than 1", __func__, NULL); | |
| 2505 if (smoothx > 3 || smoothy > 3) | |
| 2506 return (PIX *)ERROR_PTR("smooth params exceed 3", __func__, NULL); | |
| 2507 | |
| 2508 /* Generate the bg estimate using smoothed average with subsampling */ | |
| 2509 scalex = 1. / (l_float32)sx; | |
| 2510 scaley = 1. / (l_float32)sy; | |
| 2511 pixt = pixScaleSmooth(pixs, scalex, scaley); | |
| 2512 | |
| 2513 /* Do basin filling on the bg estimate if requested */ | |
| 2514 if (delta <= 0) | |
| 2515 pixsd = pixClone(pixt); | |
| 2516 else { | |
| 2517 pixLocalExtrema(pixt, 0, 0, &pixmin, NULL); | |
| 2518 pixsd = pixSeedfillGrayBasin(pixmin, pixt, delta, 4); | |
| 2519 pixDestroy(&pixmin); | |
| 2520 } | |
| 2521 pixbg = pixExtendByReplication(pixsd, 1, 1); | |
| 2522 | |
| 2523 /* Map the bg to 200 */ | |
| 2524 pixbgi = pixGetInvBackgroundMap(pixbg, 200, smoothx, smoothy); | |
| 2525 pixd = pixApplyInvBackgroundGrayMap(pixs, pixbgi, sx, sy); | |
| 2526 | |
| 2527 pixDestroy(&pixt); | |
| 2528 pixDestroy(&pixsd); | |
| 2529 pixDestroy(&pixbg); | |
| 2530 pixDestroy(&pixbgi); | |
| 2531 return pixd; | |
| 2532 } | |
| 2533 | |
| 2534 | |
| 2535 /*------------------------------------------------------------------* | |
| 2536 * Adaptive contrast normalization * | |
| 2537 *------------------------------------------------------------------*/ | |
| 2538 /*! | |
| 2539 * \brief pixContrastNorm() | |
| 2540 * | |
| 2541 * \param[in] pixd [optional] 8 bpp; null or equal to pixs | |
| 2542 * \param[in] pixs 8 bpp grayscale; not colormapped | |
| 2543 * \param[in] sx, sy tile dimensions | |
| 2544 * \param[in] mindiff minimum difference to accept as valid | |
| 2545 * \param[in] smoothx, smoothy half-width of convolution kernel applied to | |
| 2546 * min and max arrays: use 0 for no smoothing | |
| 2547 * \return pixd always | |
| 2548 * | |
| 2549 * <pre> | |
| 2550 * Notes: | |
| 2551 * (1) This function adaptively attempts to expand the contrast | |
| 2552 * to the full dynamic range in each tile. If the contrast in | |
| 2553 * a tile is smaller than %mindiff, it uses the min and max | |
| 2554 * pixel values from neighboring tiles. It also can use | |
| 2555 * convolution to smooth the min and max values from | |
| 2556 * neighboring tiles. After all that processing, it is | |
| 2557 * possible that the actual pixel values in the tile are outside | |
| 2558 * the computed [min ... max] range for local contrast | |
| 2559 * normalization. Such pixels are taken to be at either 0 | |
| 2560 * (if below the min) or 255 (if above the max). | |
| 2561 * (2) pixd can be equal to pixs (in-place operation) or | |
| 2562 * null (makes a new pixd). | |
| 2563 * (3) sx and sy give the tile size; they are typically at least 20. | |
| 2564 * (4) mindiff is used to eliminate results for tiles where it is | |
| 2565 * likely that either fg or bg is missing. A value around 50 | |
| 2566 * or more is reasonable. | |
| 2567 * (5) The full width and height of the convolution kernel | |
| 2568 * are (2 * smoothx + 1) and (2 * smoothy + 1). Some smoothing | |
| 2569 * is typically useful, and we limit the smoothing half-widths | |
| 2570 * to the range from 0 to 8. | |
| 2571 * (6) A linear TRC (gamma = 1.0) is applied to increase the contrast | |
| 2572 * in each tile. The result can subsequently be globally corrected, | |
| 2573 * by applying pixGammaTRC() with arbitrary values of gamma | |
| 2574 * and the 0 and 255 points of the mapping. | |
| 2575 * </pre> | |
| 2576 */ | |
| 2577 PIX * | |
| 2578 pixContrastNorm(PIX *pixd, | |
| 2579 PIX *pixs, | |
| 2580 l_int32 sx, | |
| 2581 l_int32 sy, | |
| 2582 l_int32 mindiff, | |
| 2583 l_int32 smoothx, | |
| 2584 l_int32 smoothy) | |
| 2585 { | |
| 2586 PIX *pixmin, *pixmax; | |
| 2587 | |
| 2588 if (!pixs || pixGetDepth(pixs) != 8) | |
| 2589 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, pixd); | |
| 2590 if (pixd && pixd != pixs) | |
| 2591 return (PIX *)ERROR_PTR("pixd not null or == pixs", __func__, pixd); | |
| 2592 if (pixGetColormap(pixs)) | |
| 2593 return (PIX *)ERROR_PTR("pixs is colormapped", __func__, pixd); | |
| 2594 if (sx < 5 || sy < 5) | |
| 2595 return (PIX *)ERROR_PTR("sx and/or sy less than 5", __func__, pixd); | |
| 2596 if (smoothx < 0 || smoothy < 0) | |
| 2597 return (PIX *)ERROR_PTR("smooth params less than 0", __func__, pixd); | |
| 2598 if (smoothx > 8 || smoothy > 8) | |
| 2599 return (PIX *)ERROR_PTR("smooth params exceed 8", __func__, pixd); | |
| 2600 | |
| 2601 /* Get the min and max pixel values in each tile, and represent | |
| 2602 * each value as a pixel in pixmin and pixmax, respectively. */ | |
| 2603 pixMinMaxTiles(pixs, sx, sy, mindiff, smoothx, smoothy, &pixmin, &pixmax); | |
| 2604 | |
| 2605 /* For each tile, do a linear expansion of the dynamic range | |
| 2606 * of pixels so that the min value is mapped to 0 and the | |
| 2607 * max value is mapped to 255. */ | |
| 2608 pixd = pixLinearTRCTiled(pixd, pixs, sx, sy, pixmin, pixmax); | |
| 2609 | |
| 2610 pixDestroy(&pixmin); | |
| 2611 pixDestroy(&pixmax); | |
| 2612 return pixd; | |
| 2613 } | |
| 2614 | |
| 2615 | |
| 2616 /*! | |
| 2617 * \brief pixMinMaxTiles() | |
| 2618 * | |
| 2619 * \param[in] pixs 8 bpp grayscale; not colormapped | |
| 2620 * \param[in] sx, sy tile dimensions | |
| 2621 * \param[in] mindiff minimum difference to accept as valid | |
| 2622 * \param[in] smoothx, smoothy half-width of convolution kernel applied to | |
| 2623 * min and max arrays: use 0 for no smoothing | |
| 2624 * \param[out] ppixmin tiled minima | |
| 2625 * \param[out] ppixmax tiled maxima | |
| 2626 * \return 0 if OK, 1 on error | |
| 2627 * | |
| 2628 * <pre> | |
| 2629 * Notes: | |
| 2630 * (1) This computes filtered and smoothed values for the min and | |
| 2631 * max pixel values in each tile of the image. | |
| 2632 * (2) See pixContrastNorm() for usage. | |
| 2633 * </pre> | |
| 2634 */ | |
| 2635 static l_ok | |
| 2636 pixMinMaxTiles(PIX *pixs, | |
| 2637 l_int32 sx, | |
| 2638 l_int32 sy, | |
| 2639 l_int32 mindiff, | |
| 2640 l_int32 smoothx, | |
| 2641 l_int32 smoothy, | |
| 2642 PIX **ppixmin, | |
| 2643 PIX **ppixmax) | |
| 2644 { | |
| 2645 l_int32 w, h; | |
| 2646 PIX *pixmin1, *pixmax1, *pixmin2, *pixmax2; | |
| 2647 | |
| 2648 if (ppixmin) *ppixmin = NULL; | |
| 2649 if (ppixmax) *ppixmax = NULL; | |
| 2650 if (!ppixmin || !ppixmax) | |
| 2651 return ERROR_INT("&pixmin or &pixmax undefined", __func__, 1); | |
| 2652 if (!pixs || pixGetDepth(pixs) != 8) | |
| 2653 return ERROR_INT("pixs undefined or not 8 bpp", __func__, 1); | |
| 2654 if (pixGetColormap(pixs)) | |
| 2655 return ERROR_INT("pixs is colormapped", __func__, 1); | |
| 2656 if (sx < 5 || sy < 5) | |
| 2657 return ERROR_INT("sx and/or sy less than 3", __func__, 1); | |
| 2658 if (smoothx < 0 || smoothy < 0) | |
| 2659 return ERROR_INT("smooth params less than 0", __func__, 1); | |
| 2660 if (smoothx > 5 || smoothy > 5) | |
| 2661 return ERROR_INT("smooth params exceed 5", __func__, 1); | |
| 2662 | |
| 2663 /* Get the min and max values in each tile */ | |
| 2664 pixmin1 = pixScaleGrayMinMax(pixs, sx, sy, L_CHOOSE_MIN); | |
| 2665 pixmax1 = pixScaleGrayMinMax(pixs, sx, sy, L_CHOOSE_MAX); | |
| 2666 | |
| 2667 pixmin2 = pixExtendByReplication(pixmin1, 1, 1); | |
| 2668 pixmax2 = pixExtendByReplication(pixmax1, 1, 1); | |
| 2669 pixDestroy(&pixmin1); | |
| 2670 pixDestroy(&pixmax1); | |
| 2671 | |
| 2672 /* Make sure no value is 0 */ | |
| 2673 pixAddConstantGray(pixmin2, 1); | |
| 2674 pixAddConstantGray(pixmax2, 1); | |
| 2675 | |
| 2676 /* Generate holes where the contrast is too small */ | |
| 2677 pixSetLowContrast(pixmin2, pixmax2, mindiff); | |
| 2678 | |
| 2679 /* Fill the holes (0 values) */ | |
| 2680 pixGetDimensions(pixmin2, &w, &h, NULL); | |
| 2681 pixFillMapHoles(pixmin2, w, h, L_FILL_BLACK); | |
| 2682 pixFillMapHoles(pixmax2, w, h, L_FILL_BLACK); | |
| 2683 | |
| 2684 /* Smooth if requested */ | |
| 2685 if (smoothx > 0 || smoothy > 0) { | |
| 2686 smoothx = L_MIN(smoothx, (w - 1) / 2); | |
| 2687 smoothy = L_MIN(smoothy, (h - 1) / 2); | |
| 2688 *ppixmin = pixBlockconv(pixmin2, smoothx, smoothy); | |
| 2689 *ppixmax = pixBlockconv(pixmax2, smoothx, smoothy); | |
| 2690 } | |
| 2691 else { | |
| 2692 *ppixmin = pixClone(pixmin2); | |
| 2693 *ppixmax = pixClone(pixmax2); | |
| 2694 } | |
| 2695 pixCopyResolution(*ppixmin, pixs); | |
| 2696 pixCopyResolution(*ppixmax, pixs); | |
| 2697 pixDestroy(&pixmin2); | |
| 2698 pixDestroy(&pixmax2); | |
| 2699 | |
| 2700 return 0; | |
| 2701 } | |
| 2702 | |
| 2703 | |
| 2704 /*! | |
| 2705 * \brief pixSetLowContrast() | |
| 2706 * | |
| 2707 * \param[in] pixs1 8 bpp | |
| 2708 * \param[in] pixs2 8 bpp | |
| 2709 * \param[in] mindiff minimum difference to accept as valid | |
| 2710 * \return 0 if OK; 1 if no pixel diffs are large enough, or on error | |
| 2711 * | |
| 2712 * <pre> | |
| 2713 * Notes: | |
| 2714 * (1) This compares corresponding pixels in pixs1 and pixs2. | |
| 2715 * When they differ by less than %mindiff, set the pixel | |
| 2716 * values to 0 in each. Each pixel typically represents a tile | |
| 2717 * in a larger image, and a very small difference between | |
| 2718 * the min and max in the tile indicates that the min and max | |
| 2719 * values are not to be trusted. | |
| 2720 * (2) If contrast (pixel difference) detection is expected to fail, | |
| 2721 * caller should check return value. | |
| 2722 * </pre> | |
| 2723 */ | |
| 2724 static l_ok | |
| 2725 pixSetLowContrast(PIX *pixs1, | |
| 2726 PIX *pixs2, | |
| 2727 l_int32 mindiff) | |
| 2728 { | |
| 2729 l_int32 i, j, w, h, d, wpl, val1, val2, found; | |
| 2730 l_uint32 *data1, *data2, *line1, *line2; | |
| 2731 | |
| 2732 if (!pixs1 || !pixs2) | |
| 2733 return ERROR_INT("pixs1 and pixs2 not both defined", __func__, 1); | |
| 2734 if (pixSizesEqual(pixs1, pixs2) == 0) | |
| 2735 return ERROR_INT("pixs1 and pixs2 not equal size", __func__, 1); | |
| 2736 pixGetDimensions(pixs1, &w, &h, &d); | |
| 2737 if (d != 8) | |
| 2738 return ERROR_INT("depth not 8 bpp", __func__, 1); | |
| 2739 if (mindiff > 254) return 0; | |
| 2740 | |
| 2741 data1 = pixGetData(pixs1); | |
| 2742 data2 = pixGetData(pixs2); | |
| 2743 wpl = pixGetWpl(pixs1); | |
| 2744 found = 0; /* init to not finding any diffs >= mindiff */ | |
| 2745 for (i = 0; i < h; i++) { | |
| 2746 line1 = data1 + i * wpl; | |
| 2747 line2 = data2 + i * wpl; | |
| 2748 for (j = 0; j < w; j++) { | |
| 2749 val1 = GET_DATA_BYTE(line1, j); | |
| 2750 val2 = GET_DATA_BYTE(line2, j); | |
| 2751 if (L_ABS(val1 - val2) >= mindiff) { | |
| 2752 found = 1; | |
| 2753 break; | |
| 2754 } | |
| 2755 } | |
| 2756 if (found) break; | |
| 2757 } | |
| 2758 if (!found) { | |
| 2759 L_WARNING("no pixel pair diffs as large as mindiff\n", __func__); | |
| 2760 pixClearAll(pixs1); | |
| 2761 pixClearAll(pixs2); | |
| 2762 return 1; | |
| 2763 } | |
| 2764 | |
| 2765 for (i = 0; i < h; i++) { | |
| 2766 line1 = data1 + i * wpl; | |
| 2767 line2 = data2 + i * wpl; | |
| 2768 for (j = 0; j < w; j++) { | |
| 2769 val1 = GET_DATA_BYTE(line1, j); | |
| 2770 val2 = GET_DATA_BYTE(line2, j); | |
| 2771 if (L_ABS(val1 - val2) < mindiff) { | |
| 2772 SET_DATA_BYTE(line1, j, 0); | |
| 2773 SET_DATA_BYTE(line2, j, 0); | |
| 2774 } | |
| 2775 } | |
| 2776 } | |
| 2777 | |
| 2778 return 0; | |
| 2779 } | |
| 2780 | |
| 2781 | |
| 2782 /*! | |
| 2783 * \brief pixLinearTRCTiled() | |
| 2784 * | |
| 2785 * \param[in] pixd [optional] 8 bpp | |
| 2786 * \param[in] pixs 8 bpp, not colormapped | |
| 2787 * \param[in] sx, sy tile dimensions | |
| 2788 * \param[in] pixmin pix of min values in tiles | |
| 2789 * \param[in] pixmax pix of max values in tiles | |
| 2790 * \return pixd always | |
| 2791 * | |
| 2792 * <pre> | |
| 2793 * Notes: | |
| 2794 * (1) pixd can be equal to pixs (in-place operation) or | |
| 2795 * null (makes a new pixd). | |
| 2796 * (2) sx and sy give the tile size; they are typically at least 20. | |
| 2797 * (3) pixmin and pixmax are generated by pixMinMaxTiles() | |
| 2798 * (4) For each tile, this does a linear expansion of the dynamic | |
| 2799 * range so that the min value in the tile becomes 0 and the | |
| 2800 * max value in the tile becomes 255. | |
| 2801 * (5) The LUTs that do the mapping are generated as needed | |
| 2802 * and stored for reuse in an integer array within the ptr array iaa[]. | |
| 2803 * </pre> | |
| 2804 */ | |
| 2805 static PIX * | |
| 2806 pixLinearTRCTiled(PIX *pixd, | |
| 2807 PIX *pixs, | |
| 2808 l_int32 sx, | |
| 2809 l_int32 sy, | |
| 2810 PIX *pixmin, | |
| 2811 PIX *pixmax) | |
| 2812 { | |
| 2813 l_int32 i, j, k, m, w, h, wt, ht, wpl, wplt, xoff, yoff; | |
| 2814 l_int32 minval, maxval, val, sval; | |
| 2815 l_int32 *ia; | |
| 2816 l_int32 **iaa; | |
| 2817 l_uint32 *data, *datamin, *datamax, *line, *tline, *linemin, *linemax; | |
| 2818 | |
| 2819 if (!pixs || pixGetDepth(pixs) != 8) | |
| 2820 return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", __func__, pixd); | |
| 2821 if (pixd && pixd != pixs) | |
| 2822 return (PIX *)ERROR_PTR("pixd not null or == pixs", __func__, pixd); | |
| 2823 if (pixGetColormap(pixs)) | |
| 2824 return (PIX *)ERROR_PTR("pixs is colormapped", __func__, pixd); | |
| 2825 if (!pixmin || !pixmax) | |
| 2826 return (PIX *)ERROR_PTR("pixmin & pixmax not defined", __func__, pixd); | |
| 2827 if (sx < 5 || sy < 5) | |
| 2828 return (PIX *)ERROR_PTR("sx and/or sy less than 5", __func__, pixd); | |
| 2829 | |
| 2830 iaa = (l_int32 **)LEPT_CALLOC(256, sizeof(l_int32 *)); | |
| 2831 if ((pixd = pixCopy(pixd, pixs)) == NULL) { | |
| 2832 LEPT_FREE(iaa); | |
| 2833 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); | |
| 2834 } | |
| 2835 pixGetDimensions(pixd, &w, &h, NULL); | |
| 2836 | |
| 2837 data = pixGetData(pixd); | |
| 2838 wpl = pixGetWpl(pixd); | |
| 2839 datamin = pixGetData(pixmin); | |
| 2840 datamax = pixGetData(pixmax); | |
| 2841 wplt = pixGetWpl(pixmin); | |
| 2842 pixGetDimensions(pixmin, &wt, &ht, NULL); | |
| 2843 for (i = 0; i < ht; i++) { | |
| 2844 line = data + sy * i * wpl; | |
| 2845 linemin = datamin + i * wplt; | |
| 2846 linemax = datamax + i * wplt; | |
| 2847 yoff = sy * i; | |
| 2848 for (j = 0; j < wt; j++) { | |
| 2849 xoff = sx * j; | |
| 2850 minval = GET_DATA_BYTE(linemin, j); | |
| 2851 maxval = GET_DATA_BYTE(linemax, j); | |
| 2852 if (maxval == minval) { | |
| 2853 L_ERROR("shouldn't happen! i,j = %d,%d, minval = %d\n", | |
| 2854 __func__, i, j, minval); | |
| 2855 continue; | |
| 2856 } | |
| 2857 if ((ia = iaaGetLinearTRC(iaa, maxval - minval)) == NULL) { | |
| 2858 L_ERROR("failure to make ia for j = %d!\n", __func__, j); | |
| 2859 continue; | |
| 2860 } | |
| 2861 for (k = 0; k < sy && yoff + k < h; k++) { | |
| 2862 tline = line + k * wpl; | |
| 2863 for (m = 0; m < sx && xoff + m < w; m++) { | |
| 2864 val = GET_DATA_BYTE(tline, xoff + m); | |
| 2865 sval = val - minval; | |
| 2866 sval = L_MAX(0, sval); | |
| 2867 SET_DATA_BYTE(tline, xoff + m, ia[sval]); | |
| 2868 } | |
| 2869 } | |
| 2870 } | |
| 2871 } | |
| 2872 | |
| 2873 for (i = 0; i < 256; i++) | |
| 2874 LEPT_FREE(iaa[i]); | |
| 2875 LEPT_FREE(iaa); | |
| 2876 return pixd; | |
| 2877 } | |
| 2878 | |
| 2879 | |
| 2880 /*! | |
| 2881 * \brief iaaGetLinearTRC() | |
| 2882 * | |
| 2883 * \param[in] iaa bare array of ptrs to l_int32 | |
| 2884 * \param[in] diff between min and max pixel values that are | |
| 2885 * to be mapped to 0 and 255 | |
| 2886 * \return ia LUT with input (val - minval) and output a | |
| 2887 * value between 0 and 255) | |
| 2888 */ | |
| 2889 static l_int32 * | |
| 2890 iaaGetLinearTRC(l_int32 **iaa, | |
| 2891 l_int32 diff) | |
| 2892 { | |
| 2893 l_int32 i; | |
| 2894 l_int32 *ia; | |
| 2895 l_float32 factor; | |
| 2896 | |
| 2897 if (!iaa) | |
| 2898 return (l_int32 *)ERROR_PTR("iaa not defined", __func__, NULL); | |
| 2899 | |
| 2900 if (iaa[diff] != NULL) /* already have it */ | |
| 2901 return iaa[diff]; | |
| 2902 | |
| 2903 ia = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); | |
| 2904 iaa[diff] = ia; | |
| 2905 if (diff == 0) { /* shouldn't happen */ | |
| 2906 for (i = 0; i < 256; i++) | |
| 2907 ia[i] = 128; | |
| 2908 } | |
| 2909 else { | |
| 2910 factor = 255. / (l_float32)diff; | |
| 2911 for (i = 0; i < diff + 1; i++) | |
| 2912 ia[i] = (l_int32)(factor * i + 0.5); | |
| 2913 for (i = diff + 1; i < 256; i++) | |
| 2914 ia[i] = 255; | |
| 2915 } | |
| 2916 | |
| 2917 return ia; | |
| 2918 } | |
| 2919 | |
| 2920 | |
| 2921 /*------------------------------------------------------------------* | |
| 2922 * Adaptive normalization with MinMax conversion of RGB to gray, * | |
| 2923 * contrast enhancement and optional 2x upscale binarization * | |
| 2924 *------------------------------------------------------------------*/ | |
| 2925 /*! | |
| 2926 * \brief pixBackgroundNormTo1MinMax() | |
| 2927 * | |
| 2928 * \param[in] pixs any depth, with or without colormap | |
| 2929 * \param[in] contrast 1 to 10: 1 reduces contrast; 10 is maximum | |
| 2930 * enhancement | |
| 2931 * \param[in] scalefactor 1 (no change); 2 (2x upscale) | |
| 2932 * \return 1 bpp pix if OK; NULL on error | |
| 2933 * | |
| 2934 * <pre> | |
| 2935 * Notes: | |
| 2936 * (1) This is a convenience binarization function that does four things: | |
| 2937 * * Generates a grayscale image with color enhancement to gray | |
| 2938 * * Background normalization | |
| 2939 * * Optional contrast enhancement | |
| 2940 * * Binarizes either at input resolution or with 2x upscaling | |
| 2941 * (2) If the %pixs is 1 bpp, returns a copy. | |
| 2942 * (3) The contrast increasing parameter %contrast takes values {1, ... 10}. | |
| 2943 * For decent scans, contrast = 1 is recommended. Use a larger | |
| 2944 * value if important details are lost in binarization. | |
| 2945 * (4) Valid values of %scalefactor are 1 and 2. | |
| 2946 * </pre> | |
| 2947 */ | |
| 2948 PIX * | |
| 2949 pixBackgroundNormTo1MinMax(PIX *pixs, | |
| 2950 l_int32 contrast, | |
| 2951 l_int32 scalefactor) | |
| 2952 { | |
| 2953 PIX *pix1, *pix2, *pixd; | |
| 2954 | |
| 2955 if (!pixs) | |
| 2956 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 2957 if (contrast < 1 || contrast > 10) | |
| 2958 return (PIX *)ERROR_PTR("contrast not in [1 ... 10]", __func__, NULL); | |
| 2959 if (scalefactor != 1 && scalefactor != 2) | |
| 2960 return (PIX *)ERROR_PTR("scalefactor not 1 or 2", __func__, NULL); | |
| 2961 | |
| 2962 if (pixGetDepth(pixs) == 1) { | |
| 2963 pixd = pixCopy(NULL, pixs); | |
| 2964 } else { | |
| 2965 pix1 = pixConvertTo8MinMax(pixs); | |
| 2966 pix2 = pixBackgroundNormSimple(pix1, NULL, NULL); | |
| 2967 pixSelectiveContrastMod(pix2, contrast); | |
| 2968 if (scalefactor == 1) | |
| 2969 pixd = pixThresholdToBinary(pix2, 180); | |
| 2970 else /* scalefactor == 2 */ | |
| 2971 pixd = pixScaleGray2xLIThresh(pix2, 180); | |
| 2972 pixDestroy(&pix1); | |
| 2973 pixDestroy(&pix2); | |
| 2974 } | |
| 2975 return pixd; | |
| 2976 } | |
| 2977 | |
| 2978 | |
| 2979 /*! | |
| 2980 * \brief pixConvertTo8MinMax() | |
| 2981 * | |
| 2982 * \param[in] pixs any depth, with or without colormap | |
| 2983 * \return 8 bpp pix if OK; NULL on error | |
| 2984 * | |
| 2985 * <pre> | |
| 2986 * Notes: | |
| 2987 * (1) This is a special version of pixConvert1To8() that removes any | |
| 2988 * existing colormap and uses pixConvertRGBToGrayMinMax() | |
| 2989 * to strongly render color into black. | |
| 2990 * </pre> | |
| 2991 */ | |
| 2992 PIX * | |
| 2993 pixConvertTo8MinMax(PIX *pixs) | |
| 2994 { | |
| 2995 l_int32 d; | |
| 2996 if (!pixs) | |
| 2997 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 2998 | |
| 2999 d = pixGetDepth(pixs); | |
| 3000 if (d == 1) { | |
| 3001 return pixConvert1To8(NULL, pixs, 255, 0); | |
| 3002 } else if (d == 2) { | |
| 3003 return pixConvert2To8(pixs, 0, 85, 170, 255, FALSE); | |
| 3004 } else if (d == 4) { | |
| 3005 return pixConvert4To8(pixs, FALSE); | |
| 3006 } else if (d == 8) { | |
| 3007 if (pixGetColormap(pixs) != NULL) | |
| 3008 return pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE); | |
| 3009 else | |
| 3010 return pixCopy(NULL, pixs); | |
| 3011 } else if (d == 16) { | |
| 3012 return pixConvert16To8(pixs, L_MS_BYTE); | |
| 3013 } else if (d == 32) { | |
| 3014 return pixConvertRGBToGrayMinMax(pixs, L_CHOOSE_MIN); | |
| 3015 } | |
| 3016 | |
| 3017 L_ERROR("Invalid depth d = %d\n", __func__, d); | |
| 3018 return NULL; | |
| 3019 } | |
| 3020 | |
| 3021 | |
| 3022 /*! | |
| 3023 * \brief pixSelectiveContrastMod() | |
| 3024 * | |
| 3025 * \param[in] pixs 8 bpp without colormap | |
| 3026 * \param[in] contrast 1 (default value) for some contrast reduction; | |
| 3027 * 10 for maximum contrast enhancement. | |
| 3028 * \return 0 if OK, 1 on error | |
| 3029 * | |
| 3030 * <pre> | |
| 3031 * Notes: | |
| 3032 * (1) This does in-place contrast enhancement on 8 bpp grayscale that | |
| 3033 * has been background normalized to 200. Therefore, there should | |
| 3034 * be no gray pixels above 200 in %pixs. For general contrast | |
| 3035 * enhancement on gray or color images, see pixContrastTRC(). | |
| 3036 * (2) Caller restricts %contrast to [1 ... 10]. | |
| 3037 * (3) Use %contrast = 1 for minimum contrast enhancement (which will | |
| 3038 * remove some speckle noise) and %contrast = 10 for maximum | |
| 3039 * darkening. | |
| 3040 * (4) We use 200 for the white point in all transforms. Using a | |
| 3041 * white point above 200 will darken all grayscale pixels. | |
| 3042 * </pre> | |
| 3043 */ | |
| 3044 static l_ok | |
| 3045 pixSelectiveContrastMod(PIX *pixs, | |
| 3046 l_int32 contrast) | |
| 3047 { | |
| 3048 if (!pixs || pixGetDepth(pixs) != 8) | |
| 3049 return ERROR_INT("pixs not defined or not 8 bpp", __func__, 1); | |
| 3050 | |
| 3051 if (contrast == 1) | |
| 3052 pixGammaTRC(pixs, pixs, 2.0, 50, 200); | |
| 3053 else if (contrast == 2) | |
| 3054 pixGammaTRC(pixs, pixs, 1.8, 60, 200); | |
| 3055 else if (contrast == 3) | |
| 3056 pixGammaTRC(pixs, pixs, 1.6, 70, 200); | |
| 3057 else if (contrast == 4) | |
| 3058 pixGammaTRC(pixs, pixs, 1.4, 80, 200); | |
| 3059 else if (contrast == 5) | |
| 3060 pixGammaTRC(pixs, pixs, 1.2, 90, 200); | |
| 3061 else if (contrast == 6) | |
| 3062 pixGammaTRC(pixs, pixs, 1.0, 100, 200); | |
| 3063 else if (contrast == 7) | |
| 3064 pixGammaTRC(pixs, pixs, 0.85, 110, 200); | |
| 3065 else if (contrast == 8) | |
| 3066 pixGammaTRC(pixs, pixs, 0.7, 120, 200); | |
| 3067 else if (contrast == 9) | |
| 3068 pixGammaTRC(pixs, pixs, 0.6, 130, 200); | |
| 3069 else /* contrast == 10 */ | |
| 3070 pixGammaTRC(pixs, pixs, 0.5, 140, 200); | |
| 3071 | |
| 3072 return 0; | |
| 3073 } | |
| 3074 |
