Logo Search packages:      
Sourcecode: leptonlib version File versions  Download package

adaptmap.c

/*====================================================================*
 -  Copyright (C) 2001 Leptonica.  All rights reserved.
 -  This software is distributed in the hope that it will be
 -  useful, but with NO WARRANTY OF ANY KIND.
 -  No author or distributor accepts responsibility to anyone for the
 -  consequences of using this software, or for whether it serves any
 -  particular purpose or works at all, unless he or she says so in
 -  writing.  Everyone is granted permission to copy, modify and
 -  redistribute this source code, for commercial or non-commercial
 -  purposes, with the following restrictions: (1) the origin of this
 -  source code must not be misrepresented; (2) modified versions must
 -  be plainly marked as such; and (3) this notice may not be removed
 -  or altered from any source or modified source distribution.
 *====================================================================*/

/*
 *  adaptmap.c
 *                     
 *      Adaptive background normalization
 *          PIX       *pixBackgroundNorm()         8 and 32 bpp
 *          PIX       *pixBackgroundNormMorph()    8 and 32 bpp
 *
 *      Measurement of local background
 *          l_int32    pixGetBackgroundGrayMap()        8 bpp
 *          l_int32    pixGetBackgroundRGBMap()         32 bpp
 *          l_int32    pixGetBackgroundGrayMapMorph()   8 bpp
 *          l_int32    pixGetBackgroundRGBMapMorph()    32 bpp
 *          l_int32    pixFillMapHoles()
 *          PIX       *pixExtendByReplication()         8 bpp
 *
 *      Generate inverted background map
 *          PIX       *pixGetInvBackgroundMap()
 *
 *      Apply background map to image
 *          PIX       *pixApplyInvBackgroundGrayMap()  8 bpp
 *          PIX       *pixApplyInvBackgroundRGBMap()   32 bpp
 *
 *  Normalization is done by generating a reduced map (or set
 *  of maps) representing the estimated background value of the
 *  input image, and using this to shift the pixel values so that
 *  this background value is set to some constant value.
 *
 *  Specifically, normalization has 3 steps:
 *    (1) Generate a background map at a reduced scale
 *    (2) Invert the map to use as a local multiplicative factor
 *    (3) Apply this inverse map to the image
 *
 *  The background map can be generated in two different ways here:
 *    (1) Remove the 'foreground' pixels and average over the remaining
 *        pixels in each tile.  Propagate values into tiles where
 *        values have not been assigned, either because there was not
 *        enough background in the tile or because the tile is covered
 *        by a foreground region described by an image mask.
 *        After the map is made, the inverse map is generated by
 *        smoothing over some number of adjacent tiles
 *        (block convolution) and then inverting.
 *    (2) Remove the foreground pixels using a morphological closing
 *        on a subsampled version of the image.  Propagate values
 *        into pixels covered by an optional image mask.  Invert the
 *        mask without preconditioning by convolutional smoothing.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include "allheaders.h"


/*------------------------------------------------------------------*
 *                Adaptive background normalization                 *
 *------------------------------------------------------------------*/
/*!
 *  pixBackgroundNorm()
 *
 *      Input:  pixs (8 bpp grayscale or 32 bpp rgb)
 *              pixim (<optional> 1 bpp 'image' mask; can be null)
 *              sx, sy (tile size in pixels)
 *              thresh (threshold for determining foreground)
 *              mincount (min threshold on counts in a tile)
 *              bgval (target bg val; typ. > 128)
 *              smoothx (half-width of block convolution kernel width)
 *              smoothy (half-width of block convolution kernel height)
 *      Return: pixd (8 bpp or 32 bpp rgb), or null on error
 *
 *  Notes:
 *    (1) The input image is either grayscale or rgb.
 *    (2) An optional binary mask can be specified, with the foreground
 *        pixels typically over image regions.  The resulting background
 *        map values will be determined by surrounding pixels that are
 *        not under the mask foreground.  The origin (0,0) of this mask
 *        is assumed to be aligned with the origin of the input image.
 *        This binary mask must not fully cover pixs, because then there
 *        will be no pixels in the input image available to compute
 *        the background.
 *    (3) The dimensions of the pixel tile (sx, sy) give the amound by
 *        by which the map is reduced in size from the input image.
 *    (4) The threshold is used to binarize the input image, in order to
 *        locate the foreground components.  If this is set too low,
 *        some actual foreground may be used to determine the maps;
 *        if set too high, there may not be enough background
 *        to determine the map values accurately.  Typically, it's
 *        better to err by setting the threshold too high.
 *    (5) A 'mincount' threshold is a minimum count of pixels in a
 *        tile for which a background reading is made, in order for that
 *        pixel in the map to be valid.  This number should perhaps be
 *        at least 1/3 the size of the tile.
 *    (6) A 'bgval' target background value for the normalized image.  This
 *        should be at least 128.  If set too close to 255, some
 *        clipping will occur in the result.
 *    (7) Two factors, 'smoothx' and 'smoothy', are input for smoothing
 *        the map.  Each low-pass filter kernel dimension is
 *        is 2 * (smoothing factor) + 1, so a
 *        value of 0 means no smoothing. A value of 1 or 2 is recommended.
 */
PIX *
pixBackgroundNorm(PIX     *pixs,
                  PIX     *pixim,
                  l_int32  sx,
                  l_int32  sy,
              l_int32  thresh,
              l_int32  mincount,
                  l_int32  bgval,
              l_int32  smoothx,
              l_int32  smoothy)
{
l_int32  d, allfg;
PIX     *pixm, *pixmi, *pixd;
PIX     *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi;

    PROCNAME("pixBackgroundNorm");

    if (!pixs)
      return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    d = pixGetDepth(pixs);
    if (d != 8 && d != 32)
      return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
    if (sx < 4 || sy < 4)
      return (PIX *)ERROR_PTR("sx and sy must be >= 4", procName, NULL);
    if (mincount > sx * sy) {
        L_WARNING("mincount too large for tile size", procName);
      mincount = (sx * sy) / 3;
    }

        /* If pixim exists, verify that it is not all foreground. */
    if (pixim) {
        pixInvert(pixim, pixim);
        pixZero(pixim, &allfg);
        pixInvert(pixim, pixim);
        if (allfg)
            return (PIX *)ERROR_PTR("pixim all foreground", procName, NULL);
    }

    pixd = NULL;
    if (d == 8) {
        pixm = NULL;
      pixGetBackgroundGrayMap(pixs, pixim, sx, sy, thresh, mincount, &pixm);
        if (!pixm)
          return (PIX *)ERROR_PTR("pixm not made", procName, NULL);

      pixmi = pixGetInvBackgroundMap(pixm, bgval, smoothx, smoothy);
      if (!pixmi)
          ERROR_PTR("pixmi not made", procName, NULL);
        else
          pixd = pixApplyInvBackgroundGrayMap(pixs, pixmi, sx, sy);

      pixDestroy(&pixm);
      pixDestroy(&pixmi);
    }
    else {
      pixmr = pixmg = pixmb = NULL;
      pixGetBackgroundRGBMap(pixs, pixim, sx, sy, thresh, mincount, &pixmr, 
                             &pixmg, &pixmb);
        if (!pixmr || !pixmg || !pixmb) {
          pixDestroy(&pixmr);
          pixDestroy(&pixmg);
          pixDestroy(&pixmb);
          return (PIX *)ERROR_PTR("not all pixm*", procName, NULL);
      }

      pixmri = pixGetInvBackgroundMap(pixmr, bgval, smoothx, smoothy);
      pixmgi = pixGetInvBackgroundMap(pixmg, bgval, smoothx, smoothy);
      pixmbi = pixGetInvBackgroundMap(pixmb, bgval, smoothx, smoothy);
      if (!pixmri || !pixmgi || !pixmbi)
          ERROR_PTR("not all pixm*i are made", procName, NULL);
      else
          pixd = pixApplyInvBackgroundRGBMap(pixs, pixmri, pixmgi, pixmbi,
                                             sx, sy);

      pixDestroy(&pixmr);
      pixDestroy(&pixmg);
      pixDestroy(&pixmb);
      pixDestroy(&pixmri);
      pixDestroy(&pixmgi);
      pixDestroy(&pixmbi);
    }

    if (!pixd)
      ERROR_PTR("pixd not made", procName, NULL);
    return pixd;
}


/*!
 *  pixBackgroundNormMorph()
 *
 *      Input:  pixs (8 bpp grayscale or 32 bpp rgb)
 *              pixim (<optional> 1 bpp 'image' mask; can be null)
 *              reduction (at which morph closings are done)
 *              size (of square Sel for the closing; use an odd number)
 *              bgval (target bg val; typ. > 128)
 *      Return: pixd (8 bpp), or null on error
 *
 *  Notes:
 *    (1) The input image is either grayscale or rgb.
 *    (2) An optional binary mask can be specified, with the foreground
 *        pixels typically over image regions.  The resulting background
 *        map values will be determined by surrounding pixels that are
 *        not under the mask foreground.  The origin (0,0) of this mask
 *        is assumed to be aligned with the origin of the input image.
 *        This binary mask must not fully cover pixs, because then there
 *        will be no pixels in the input image available to compute
 *        the background.
 *    (3) The map is computed at reduced size (given by 'reduction')
 *        from the input pixs and optional pixim.  At this scale,
 *        pixs is closed to remove the background, using a square Sel
 *        of odd dimension.  The product of reduction * size should be
 *        large enough to remove most of the text foreground.
 *    (4) No convolutional smoothing needs to be done on the map before
 *        inverting it.
 *    (5) A 'bgval' target background value for the normalized image.  This
 *        should be at least 128.  If set too close to 255, some
 *        clipping will occur in the result.
 */
PIX *
pixBackgroundNormMorph(PIX     *pixs,
                       PIX     *pixim,
                       l_int32  reduction,
                       l_int32  size,
                       l_int32  bgval)
{
l_int32    d, nx, ny, allfg;
l_float32  scale;
PIX       *pixm, *pixmi, *pixd;
PIX       *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi;

    PROCNAME("pixBackgroundNormMorph");

    if (!pixs)
      return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    d = pixGetDepth(pixs);
    if (d != 8 && d != 32)
      return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
    if (reduction < 2 || reduction > 16)
      return (PIX *)ERROR_PTR("reduction must be between 2 and 16",
                                procName, NULL);

        /* If pixim exists, verify that it is not all foreground. */
    if (pixim) {
        pixInvert(pixim, pixim);
        pixZero(pixim, &allfg);
        pixInvert(pixim, pixim);
        if (allfg)
            return (PIX *)ERROR_PTR("pixim all foreground", procName, NULL);
    }

    scale = 1. / (l_float32)reduction;
    nx = pixGetWidth(pixs) / reduction;
    ny = pixGetHeight(pixs) / reduction;
    pixd = NULL;
    if (d == 8) {
        pixGetBackgroundGrayMapMorph(pixs, pixim, reduction, size, &pixm);
        if (!pixm)
          return (PIX *)ERROR_PTR("pixm not made", procName, NULL);
      pixmi = pixGetInvBackgroundMap(pixm, bgval, 0, 0);
      if (!pixmi)
          ERROR_PTR("pixmi not made", procName, NULL);
        else
          pixd = pixApplyInvBackgroundGrayMap(pixs, pixmi, reduction, reduction);
      pixDestroy(&pixm);
      pixDestroy(&pixmi);
    }
    else {  /* d == 32 */
      pixmr = pixmg = pixmb = NULL;
        pixGetBackgroundRGBMapMorph(pixs, pixim, reduction, size,
                                    &pixmr, &pixmg, &pixmb);
        if (!pixmr || !pixmg || !pixmb) {
          pixDestroy(&pixmr);
          pixDestroy(&pixmg);
          pixDestroy(&pixmb);
          return (PIX *)ERROR_PTR("not all pixm*", procName, NULL);
      }

      pixmri = pixGetInvBackgroundMap(pixmr, bgval, 0, 0);
      pixmgi = pixGetInvBackgroundMap(pixmg, bgval, 0, 0);
      pixmbi = pixGetInvBackgroundMap(pixmb, bgval, 0, 0);
      if (!pixmri || !pixmgi || !pixmbi)
          ERROR_PTR("not all pixm*i are made", procName, NULL);
      else
          pixd = pixApplyInvBackgroundRGBMap(pixs, pixmri, pixmgi, pixmbi,
                                             reduction, reduction);

      pixDestroy(&pixmr);
      pixDestroy(&pixmg);
      pixDestroy(&pixmb);
      pixDestroy(&pixmri);
      pixDestroy(&pixmgi);
      pixDestroy(&pixmbi);
    }

    if (!pixd)
      ERROR_PTR("pixd not made", procName, NULL);
    return pixd;
}


/*------------------------------------------------------------------*
 *                 Measurement of local background                  *
 *------------------------------------------------------------------*/
/*!
 *  pixGetBackgroundGrayMap()
 *
 *      Input:  pixs (8 bpp)
 *              pixim (<optional> 1 bpp 'image' mask; can be null; it
 *                     should not have all foreground pixels)
 *              sx, sy (tile size in pixels)
 *              thresh (threshold for determining foreground)
 *              mincount (min threshold on counts in a tile)
 *              &pixd (<return> 8 bpp grayscale map)
 *      Return: 0 if OK, 1 on error
 */
l_int32
pixGetBackgroundGrayMap(PIX     *pixs,
                        PIX     *pixim,
                        l_int32  sx,
                        l_int32  sy,
                    l_int32  thresh,
                    l_int32  mincount,
                        PIX    **ppixd)
{
l_int32    w, h, wd, hd, wim, him, wpls, wplim, wpld, wplf;
l_int32    xim, yim, delx, nx, ny, i, j, k, m;
l_int32    count, sum, val8;
l_uint32  *datas, *dataim, *datad, *dataf, *lines, *lineim, *lined, *linef;
PIX       *pixd, *pixb, *pixf, *pixt;
SEL       *sel_7h, *sel_7v;

    PROCNAME("pixGetBackgroundGrayMap");

    if (!ppixd)
      return ERROR_INT("&pixd not defined", procName, 1);
    *ppixd = NULL;
    if (!pixs)
      return ERROR_INT("pixs not defined", procName, 1);
    if (pixGetDepth(pixs) != 8)
      return ERROR_INT("pixs not 8 bpp", procName, 1);
    if (sx < 4 || sy < 4)
      return ERROR_INT("sx and sy must be >= 4", procName, 1);
    if (mincount > sx * sy) {
        L_WARNING("mincount too large for tile size", procName);
      mincount = (sx * sy) / 3;
    }

        /* Generate the foreground mask.  These pixels will be
         * ignored when computing the background values. */
    pixb = pixThresholdToBinary(pixs, thresh);
    sel_7h = selCreateBrick(1, 7, 0, 3, SEL_HIT);
    sel_7v = selCreateBrick(7, 1, 3, 0, SEL_HIT);
    pixt = pixDilate(NULL, pixb, sel_7h);
    pixf = pixDilate(NULL, pixt, sel_7v);
    pixDestroy(&pixt);
    pixDestroy(&pixb);
    selDestroy(&sel_7h);
    selDestroy(&sel_7v);

        /* Generate the output mask image */
    w = pixGetWidth(pixs);
    h = pixGetHeight(pixs);
    wd = (w + sx - 1) / sx;
    hd = (h + sy - 1) / sy;
    pixd = pixCreate(wd, hd, 8);

    /* ------------- Set up the mapping image --------------- */
        /* Note: we only compute map values in tiles that are complete.
       * In general, tiles at right and bottom edges will not be
       * complete, and we must fill them in later. */
    nx = w / sx;
    ny = h / sy;
    wpls = pixGetWpl(pixs);
    datas = pixGetData(pixs);
    wpld = pixGetWpl(pixd);
    datad = pixGetData(pixd);
    wplf = pixGetWpl(pixf);
    dataf = pixGetData(pixf);
    for (i = 0; i < ny; i++) {
        lines = datas + sy * i * wpls;
      linef = dataf + sy * i * wplf;
      lined = datad + i * wpld;
        for (j = 0; j < nx; j++) {
          delx = j * sx;
          sum = 0;
          count = 0;
          for (k = 0; k < sy; k++) {
            for (m = 0; m < sx; m++) {
                if (GET_DATA_BIT(linef + k * wplf, delx + m) == 0) {
                        sum += GET_DATA_BYTE(lines + k * wpls, delx + m);
                        count++;
                    }
            }
          }
          if (count >= mincount) {
            val8 = sum / count;
            SET_DATA_BYTE(lined, j, val8);
          }
      }
    }
    pixDestroy(&pixf);

        /* Apply the optional image mask, with clipping.  This erases
         * the previous calculation for the corresponding map pixels.
         * Then, when the holes are filled, these erased pixels are
         * set by the surrounding map values. */
    if (pixim) {
        wim = pixGetWidth(pixim);
        him = pixGetHeight(pixim);
        dataim = pixGetData(pixim);
        wplim = pixGetWpl(pixim);
        for (i = 0; i < ny; i++) {
            yim = i * sy + sy / 2;
            if (yim >= him)
                break;
            lineim = dataim + yim * wplim;
            for (j = 0; j < nx; j++) {
                xim = j * sx + sx / 2;
                if (xim >= wim)
                    break;
                if (GET_DATA_BIT(lineim, xim))
                    pixSetPixel(pixd, j, i, 0);
            }
        }
    }

    if (pixFillMapHoles(pixd, nx, ny)) {
      pixDestroy(&pixd);
      return ERROR_INT("fill error", procName, 1);
    }

    *ppixd = pixd;
    return 0;
}


/*!
 *  pixGetBackgroundRGBMap()
 *
 *      Input:  pixs (32 bpp)
 *              pixim (<optional> 1 bpp 'image' mask; can be null; it
 *                     should not have all foreground pixels)
 *              sx, sy (tile size in pixels)
 *              thresh (threshold for determining foreground)
 *              mincount (min threshold on counts in a tile)
 *              &pixmr, &pixmg, &pixmb (<return> rgb maps)
 *      Return: 0 if OK, 1 on error
 */
l_int32
pixGetBackgroundRGBMap(PIX     *pixs,
                       PIX     *pixim,
                       l_int32  sx,
                       l_int32  sy,
                   l_int32  thresh,
                   l_int32  mincount,
                   PIX    **ppixmr,
                   PIX    **ppixmg,
                   PIX    **ppixmb)
{
l_int32    w, h, wm, hm, wim, him, wpls, wplim, wplf;
l_int32    xim, yim, delx, nx, ny, i, j, k, m;
l_int32    rcount, gcount, bcount, rsum, gsum, bsum, rval, gval, bval;
l_uint32   pixel;
l_uint32  *datas, *dataim, *dataf, *lines, *lineim, *linef;
PIX       *pixg, *pixb, *pixf, *pixt;
PIX       *pixmr, *pixmg, *pixmb;
SEL       *sel_7h, *sel_7v;

    PROCNAME("pixGetBackgroundRGBMap");

    if (!ppixmr || !ppixmg || !ppixmb)
      return ERROR_INT("&pixm* not all defined", procName, 1);
    *ppixmr = *ppixmg = *ppixmb = NULL;
    if (!pixs)
      return ERROR_INT("pixs not defined", procName, 1);
    if (pixGetDepth(pixs) != 32)
      return ERROR_INT("pixs not 32 bpp", procName, 1);
    if (sx < 4 || sy < 4)
      return ERROR_INT("sx and sy must be >= 4", procName, 1);
    if (mincount > sx * sy) {
        L_WARNING("mincount too large for tile size", procName);
      mincount = (sx * sy) / 3;
    }

        /* Generate the foreground mask.  These pixels will be
         * ignored when computing the background values. */
    pixg = pixConvertRGBToGray(pixs, 0.33, 0.34, 0.33);
    pixb = pixThresholdToBinary(pixg, thresh);
    sel_7h = selCreateBrick(1, 7, 0, 3, SEL_HIT);
    sel_7v = selCreateBrick(7, 1, 3, 0, SEL_HIT);
    pixt = pixDilate(NULL, pixb, sel_7h);
    pixf = pixDilate(NULL, pixt, sel_7v);
    pixDestroy(&pixg);
    pixDestroy(&pixt);
    pixDestroy(&pixb);
    selDestroy(&sel_7h);
    selDestroy(&sel_7v);

        /* Generate the output mask images */
    w = pixGetWidth(pixs);
    h = pixGetHeight(pixs);
    wm = (w + sx - 1) / sx;
    hm = (h + sy - 1) / sy;
    pixmr = pixCreate(wm, hm, 8);
    pixmg = pixCreate(wm, hm, 8);
    pixmb = pixCreate(wm, hm, 8);
    *ppixmr = pixmr;
    *ppixmg = pixmg;
    *ppixmb = pixmb;

    /* ------------- Set up the mapping images --------------- */
        /* Note: we only compute map values in tiles that are complete.
       * In general, tiles at right and bottom edges will not be
       * complete, and we must fill them in later. */
    nx = w / sx;
    ny = h / sy;
    wpls = pixGetWpl(pixs);
    datas = pixGetData(pixs);
    wplf = pixGetWpl(pixf);
    dataf = pixGetData(pixf);
    for (i = 0; i < ny; i++) {
        lines = datas + sy * i * wpls;
      linef = dataf + sy * i * wplf;
        for (j = 0; j < nx; j++) {
          delx = j * sx;
          rsum = gsum = bsum = 0;
          rcount = gcount = bcount = 0;
          for (k = 0; k < sy; k++) {
            for (m = 0; m < sx; m++) {
                if (GET_DATA_BIT(linef + k * wplf, delx + m) == 0) {
                    pixel = *(lines + k * wpls + delx + m);
                    rsum += (pixel >> 24);
                  rcount++;
                    gsum += ((pixel >> 16) & 0xff);
                  gcount++;
                    bsum += ((pixel >> 8) & 0xff);
                  bcount++;
                    }
            }
          }
          if (rcount >= mincount) {
            rval = rsum / rcount;
            pixSetPixel(pixmr, j, i, rval);
          }
          if (gcount >= mincount) {
            gval = gsum / gcount;
            pixSetPixel(pixmg, j, i, gval);
          }
          if (bcount >= mincount) {
            bval = bsum / bcount;
            pixSetPixel(pixmb, j, i, bval);
          }
      }
    }
    pixDestroy(&pixf);

        /* Apply the optional image mask, with clipping.  This erases
         * the previous calculation for the corresponding map pixels.
         * Then, when the holes are filled, these erased pixels are
         * set by the surrounding map values. */
    if (pixim) {
        wim = pixGetWidth(pixim);
        him = pixGetHeight(pixim);
        dataim = pixGetData(pixim);
        wplim = pixGetWpl(pixim);
        for (i = 0; i < ny; i++) {
            yim = i * sy + sy / 2;
            if (yim >= him)
                break;
            lineim = dataim + yim * wplim;
            for (j = 0; j < nx; j++) {
                xim = j * sx + sx / 2;
                if (xim >= wim)
                    break;
                if (GET_DATA_BIT(lineim, xim)) {
                    pixSetPixel(pixmr, j, i, 0);
                    pixSetPixel(pixmg, j, i, 0);
                    pixSetPixel(pixmb, j, i, 0);
                }
            }
        }
    }

    /* ----------------- Now fill in the holes ----------------------- */
    if (pixFillMapHoles(pixmr, nx, ny) ||
        pixFillMapHoles(pixmg, nx, ny) ||
        pixFillMapHoles(pixmb, nx, ny))
      return ERROR_INT("fill error", procName, 1);
    else
        return 0;
}


/*!
 *  pixGetBackgroundGrayMapMorph()
 *
 *      Input:  pixs (8 bpp)
 *              pixim (<optional> 1 bpp 'image' mask; can be null; it
 *                     should not have all foreground pixels)
 *              reduction (factor at which closing is performed)
 *              size (of square Sel for the closing; use an odd number)
 *              &pixm (<return> grayscale map)
 *      Return: 0 if OK, 1 on error
 */
l_int32
pixGetBackgroundGrayMapMorph(PIX     *pixs,
                             PIX     *pixim,
                             l_int32  reduction,
                             l_int32  size,
                             PIX    **ppixm)
{
l_int32    nx, ny;
l_float32  scale;
PIX       *pixm, *pixt1, *pixt2, *pixt3, *pixt4;

    PROCNAME("pixGetBackgroundGrayMapMorph");

    if (!pixs)
      return ERROR_INT("pixs not defined", procName, 1);
    if (!ppixm)
      return ERROR_INT("&pixm not defined", procName, 1);
    *ppixm = NULL;

    scale = 1. / (l_float32)reduction;
    pixt1 = pixScaleBySampling(pixs, scale, scale);
    pixt2 = pixCloseGray(pixt1, size, MORPH_SQUARE);
    pixt3 = pixExtendByReplication(pixt2, 1, 1);
    if (pixim) {
        pixt4 = pixScale(pixim, scale, scale);
        pixm = pixConvertTo8(pixt4);
        pixAnd(pixm, pixm, pixt3);
        pixDestroy(&pixt4);
    } 
    else
        pixm = pixClone(pixt3);

    nx = pixGetWidth(pixs) / reduction;
    ny = pixGetHeight(pixs) / reduction;
    if (pixFillMapHoles(pixm, nx, ny)) {
        pixDestroy(&pixm);
        return ERROR_INT("fill error", procName, 1);
    }
    *ppixm = pixm;

    pixDestroy(&pixt1);
    pixDestroy(&pixt2);
    pixDestroy(&pixt3);
    return 0;
}



/*!
 *  pixGetBackgroundRGBMapMorph()
 *
 *      Input:  pixs (8 bpp)
 *              pixim (<optional> 1 bpp 'image' mask; can be null; it
 *                     should not have all foreground pixels)
 *              reduction (factor at which closing is performed)
 *              size (of square Sel for the closing; use an odd number)
 *              &pixmr (<return> red component map)
 *              &pixmg (<return> green component map)
 *              &pixmb (<return> blue component map)
 *      Return: 0 if OK, 1 on error
 */
l_int32
pixGetBackgroundRGBMapMorph(PIX     *pixs,
                            PIX     *pixim,
                            l_int32  reduction,
                            l_int32  size,
                            PIX    **ppixmr,
                            PIX    **ppixmg,
                            PIX    **ppixmb)
{
l_int32    nx, ny;
l_float32  scale;
PIX       *pixm, *pixmr, *pixmg, *pixmb, *pixt1, *pixt2, *pixt3;

    PROCNAME("pixGetBackgroundRGBMapMorph");

    if (!pixs)
      return ERROR_INT("pixs not defined", procName, 1);
    if (!ppixmr || !ppixmg || !ppixmb)
      return ERROR_INT("&pixm* not all defined", procName, 1);
    *ppixmr = *ppixmg = *ppixmb = NULL;

        /* Generate an 8 bpp version of the image mask, if it exists */
    scale = 1. / (l_float32)reduction;
    if (pixim) {
        pixt1 = pixScale(pixim, scale, scale);
        pixm = pixConvertTo8(pixt1);
        pixDestroy(&pixt1);
    }

    pixt1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_RED);
    pixt2 = pixCloseGray(pixt1, size, MORPH_SQUARE);
    pixt3 = pixExtendByReplication(pixt2, 1, 1);
    if (pixim)
        pixmr = pixAnd(NULL, pixm, pixt3);
    else
        pixmr = pixClone(pixt3);
    *ppixmr = pixmr;
    pixDestroy(&pixt1);
    pixDestroy(&pixt2);
    pixDestroy(&pixt3);

    pixt1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_GREEN);
    pixt2 = pixCloseGray(pixt1, size, MORPH_SQUARE);
    pixt3 = pixExtendByReplication(pixt2, 1, 1);
    if (pixim)
        pixmg = pixAnd(NULL, pixm, pixt3);
    else
        pixmg = pixClone(pixt3);
    *ppixmg = pixmg;
    pixDestroy(&pixt1);
    pixDestroy(&pixt2);
    pixDestroy(&pixt3);

    pixt1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_BLUE);
    pixt2 = pixCloseGray(pixt1, size, MORPH_SQUARE);
    pixt3 = pixExtendByReplication(pixt2, 1, 1);
    if (pixim)
        pixmb = pixAnd(NULL, pixm, pixt3);
    else
        pixmb = pixClone(pixt3);
    *ppixmb = pixmb;
    pixDestroy(&pixm);
    pixDestroy(&pixt1);
    pixDestroy(&pixt2);
    pixDestroy(&pixt3);

    nx = pixGetWidth(pixs) / reduction;
    ny = pixGetHeight(pixs) / reduction;
    pixFillMapHoles(pixmr, nx, ny);
    pixFillMapHoles(pixmg, nx, ny);
    pixFillMapHoles(pixmb, nx, ny);

    return 0;
}


/*!
 *  pixFillMapHoles()
 *
 *      Input:  pix (8 bpp)
 *              nx (number of horizontal pixel tiles that entirely cover
 *                  pixels in the original source image)
 *              ny (ditto for the number of vertical pixel tiles)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      - in-place operation on pix (the map)
 *      - If w is the map width, nx = w or nx = w - 1; ditto for h and ny.
 */
l_int32
pixFillMapHoles(PIX     *pix,
                l_int32  nx,
                l_int32  ny)
{
l_int32   w, h, y, nmiss, goodcol, i, j, found, ival;
l_uint32  val, lastval;
NUMA     *na;
PIX      *pixt;

    PROCNAME("pixFillMapHoles");

    if (!pix)
      return ERROR_INT("pix not defined", procName, 1);
    if (pixGetDepth(pix) != 8)
      return ERROR_INT("pix not 8 bpp", procName, 1);
    w = pixGetWidth(pix);
    h = pixGetHeight(pix);

    /* ------------- Fill holes in the mapping image columns ----------- */
    na = numaCreate(0);  /* holds flag for which columns have data */
    nmiss = 0;
    for (j = 0; j < nx; j++) {  /* do it by columns */
      found = FALSE;
      for (i = 0; i < ny; i++) {
          pixGetPixel(pix, j, i, &val);
          if (val != 0) {
            y = i;
            found = TRUE;
            break;
          }
      }
      if (found == FALSE) {
          numaAddNumber(na, 0);  /* no data in the column */
          nmiss++;
      }
        else {
          numaAddNumber(na, 1);
          for (i = y - 1; i >= 0; i--)  /* fill up to top */
              pixSetPixel(pix, j, i, val);
          pixGetPixel(pix, j, 0, &lastval);
            for (i = 1; i < h; i++) {  /* set going down to bottom */
              pixGetPixel(pix, j, i, &val);
            if (val == 0)
                pixSetPixel(pix, j, i, lastval);
                else
                lastval = val;
          }
      }
    }
    numaAddNumber(na, 0);  /* last column */
    
    if (nmiss == nx) {  /* no data in any column! */
      numaDestroy(&na);
      return ERROR_INT("no background found", procName, 1);
    }

    /* ---------- Fill in missing columns by replication ----------- */
    if (nmiss > 0) {  /* replicate columns */
        pixt = pixCopy(NULL, pix);
          /* find the first good column */
      goodcol = 0;
      for (j = 0; j < w; j++) {
          numaGetIValue(na, j, &ival);
          if (ival == 1) {
              goodcol = j;
            break;
          }
      }
      if (goodcol > 0) {  /* copy cols backward */
          for (j = goodcol - 1; j >= 0; j--) {
            pixRasterop(pix, j, 0, 1, h, PIX_SRC, pixt, j + 1, 0);
            pixRasterop(pixt, j, 0, 1, h, PIX_SRC, pix, j, 0);
          }
      }
      for (j = goodcol + 1; j < w; j++) {   /* copy cols forward */
          numaGetIValue(na, j, &ival);
          if (ival == 0) {
                  /* copy the column to the left of j */
            pixRasterop(pix, j, 0, 1, h, PIX_SRC, pixt, j - 1, 0);
            pixRasterop(pixt, j, 0, 1, h, PIX_SRC, pix, j, 0);
          }
      }
      pixDestroy(&pixt);
    }
    if (w > nx) {  /* replicate the last column */
        for (i = 0; i < h; i++) {
            pixGetPixel(pix, w - 2, i, &val);
            pixSetPixel(pix, w - 1, i, val);
        }
    }
    
    numaDestroy(&na);
          
    return 0;
}


/*!
 *  pixExtendByReplication()
 *
 *      Input:  pixs (8 bpp)
 *              addw (number of extra pixels horizontally to add)
 *              addh (number of extra pixels vertically to add)
 *      Return: pixd (extended with replicated pixel values), or null on error
 *
 *  Notes:
 *      - the pixel values are extended to the left and down, as required
 */
PIX *
pixExtendByReplication(PIX     *pixs,
                       l_int32  addw,
                       l_int32  addh)
{
l_int32   w, h, i, j;
l_uint32  val;
PIX      *pixd;

    PROCNAME("pixExtendByReplication");

    if (!pixs)
      return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    if (pixGetDepth(pixs) != 8)
      return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);

    if (addw == 0 && addh == 0)
        return pixCopy(NULL, pixs);

    w = pixGetWidth(pixs);
    h = pixGetHeight(pixs);
    if ((pixd = pixCreate(w + addw, h + addh, 8)) == NULL)
      return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
    pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0);

    if (addw > 0) {
        for (i = 0; i < h; i++) {
            pixGetPixel(pixd, w - 1, i, &val);
            for (j = 0; j < addw; j++)
                pixSetPixel(pixd, w + j, i, val);
        }
    }

    if (addh > 0) {
        for (j = 0; j < w + addw; j++) {
            pixGetPixel(pixd, j, h - 1, &val);
            for (i = 0; i < addh; i++)
                pixSetPixel(pixd, j, h + i, val);
        }
    }

    return pixd;
}


/*------------------------------------------------------------------*
 *                  Generate inverted background map                *
 *------------------------------------------------------------------*/
/*!
 *  pixGetInvBackground()
 *
 *      Input:  pixs (8 bpp)
 *              bgval (target bg val; typ. > 128)
 *              smoothx (half-width of block convolution kernel width)
 *              smoothy (half-width of block convolution kernel height)
 *      Return: pixd (16 bpp), or null on error
 *
 *  Note:
 *     - bgval should typically be > 120 and < 240
 *     - pixd is a normalization image; the original image is
 *       multiplied by pixd and the result is divided by 256.
 */
PIX *
pixGetInvBackgroundMap(PIX     *pixs,
                       l_int32  bgval,
                   l_int32  smoothx,
                   l_int32  smoothy)
{
l_int32    w, h, wplsm, wpld, i, j;
l_int32    val, val16;
l_uint32  *datasm, *datad, *linesm, *lined;
PIX       *pixsm, *pixd;

    PROCNAME("pixGetInvBackgroundMap");

    if (!pixs)
      return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    if (pixGetDepth(pixs) != 8)
      return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
    w = pixGetWidth(pixs);
    h = pixGetHeight(pixs);
    if (w < 5 || h < 5)
      return (PIX *)ERROR_PTR("w and h must be >= 5", procName, NULL);

        /* smooth the map image */
    pixsm = pixBlockconv(pixs, smoothx, smoothy);
    datasm = pixGetData(pixsm);
    wplsm = pixGetWpl(pixsm);

        /* invert the map image, scaling up to preserve dynamic range */
    pixd = pixCreate(w, h, 16);
    datad = pixGetData(pixd);
    wpld = pixGetWpl(pixd);
    for (i = 0; i < h; i++) {
        linesm = datasm + i * wplsm;
        lined = datad + i * wpld;
      for (j = 0; j < w; j++) {
          val = GET_DATA_BYTE(linesm, j);
          if (val > 0)
              val16 = (256 * bgval) / val;
          else {  /* shouldn't happen */
            L_WARNING("smoothed bg has 0 pixel!", procName);
              val16 = bgval / 2;
          }
          SET_DATA_TWO_BYTES(lined, j, val16);
      }
    }

    pixDestroy(&pixsm);
    return pixd;
}


/*------------------------------------------------------------------*
 *                    Apply background map to image                 *
 *------------------------------------------------------------------*/
/*!
 *  pixApplyInvBackgroundGrayMap()
 *
 *      Input:  pixs (8 bpp)
 *              pixm (16 bpp, inverse background map)
 *              sx (tile width in pixels)
 *              sy (tile height in pixels)
 *      Return: pixd (8 bpp), or null on error
 */
PIX *
pixApplyInvBackgroundGrayMap(PIX     *pixs,
                             PIX     *pixm,
                             l_int32  sx,
                             l_int32  sy)
{
l_int32    w, h, wm, hm, wpls, wpld, i, j, k, m, xoff, yoff;
l_int32    vals, vald;
l_uint32   val16;
l_uint32  *datas, *datad, *lines, *lined, *flines, *flined;
PIX       *pixd;

    PROCNAME("pixApplyInvBackgroundGrayMap");

    if (!pixs)
      return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    if (pixGetDepth(pixs) != 8)
      return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
    if (!pixm)
      return (PIX *)ERROR_PTR("pixm not defined", procName, NULL);
    if (pixGetDepth(pixm) != 16)
      return (PIX *)ERROR_PTR("pixm not 16 bpp", procName, NULL);
    if (sx == 0 || sy == 0)
      return (PIX *)ERROR_PTR("invalid sx and/or sy", procName, NULL);

    datas = pixGetData(pixs);
    wpls = pixGetWpl(pixs);
    w = pixGetWidth(pixs);
    h = pixGetHeight(pixs);
    wm = pixGetWidth(pixm);
    hm = pixGetHeight(pixm);
    pixd = pixCreateTemplate(pixs);
    datad = pixGetData(pixd);
    wpld = pixGetWpl(pixd);
    for (i = 0; i < hm; i++) {
        lines = datas + sy * i * wpls;
        lined = datad + sy * i * wpld;
      yoff = sy * i;
      for (j = 0; j < wm; j++) {
          pixGetPixel(pixm, j, i, &val16);
          xoff = sx * j;
          for (k = 0; k < sy && yoff + k < h; k++) {
              flines = lines + k * wpls;
              flined = lined + k * wpld;
            for (m = 0; m < sx && xoff + m < w; m++) {
                vals = GET_DATA_BYTE(flines, xoff + m);
                vald = (vals * val16) / 256;
                vald = L_MIN(vald, 255);
                SET_DATA_BYTE(flined, xoff + m, vald);
            }
          }
      }
    }

    return pixd;
}


/*!
 *  pixApplyInvBackgroundRGBMap()
 *
 *      Input:  pixs (32 bpp rbg)
 *              pixmr (16 bpp, red inverse background map)
 *              pixmg (16 bpp, green inverse background map)
 *              pixmb (16 bpp, blue inverse background map)
 *              sx (tile width in pixels)
 *              sy (tile height in pixels)
 *      Return: pixd (32 bpp rbg), or null on error
 */
PIX *
pixApplyInvBackgroundRGBMap(PIX     *pixs,
                            PIX     *pixmr,
                            PIX     *pixmg,
                            PIX     *pixmb,
                            l_int32  sx,
                            l_int32  sy)
{
l_int32    w, h, wm, hm, wpls, wpld, i, j, k, m, xoff, yoff;
l_int32    rvald, gvald, bvald;
l_uint32   vals, vald;
l_uint32   rval16, gval16, bval16;
l_uint32  *datas, *datad, *lines, *lined, *flines, *flined;
PIX       *pixd;

    PROCNAME("pixApplyInvBackgroundRGBMap");

    if (!pixs)
      return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    if (pixGetDepth(pixs) != 32)
      return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
    if (!pixmr || !pixmg || !pixmb)
      return (PIX *)ERROR_PTR("pix maps not all defined", procName, NULL);
    if (pixGetDepth(pixmr) != 16 || pixGetDepth(pixmg) != 16 ||
        pixGetDepth(pixmb) != 16)
      return (PIX *)ERROR_PTR("pix maps not all 16 bpp", procName, NULL);
    if (sx == 0 || sy == 0)
      return (PIX *)ERROR_PTR("invalid sx and/or sy", procName, NULL);

    datas = pixGetData(pixs);
    wpls = pixGetWpl(pixs);
    w = pixGetWidth(pixs);
    h = pixGetHeight(pixs);
    wm = pixGetWidth(pixmr);
    hm = pixGetHeight(pixmr);
    pixd = pixCreateTemplate(pixs);
    datad = pixGetData(pixd);
    wpld = pixGetWpl(pixd);
    for (i = 0; i < hm; i++) {
        lines = datas + sy * i * wpls;
        lined = datad + sy * i * wpld;
      yoff = sy * i;
      for (j = 0; j < wm; j++) {
          pixGetPixel(pixmr, j, i, &rval16);
          pixGetPixel(pixmg, j, i, &gval16);
          pixGetPixel(pixmb, j, i, &bval16);
          xoff = sx * j;
          for (k = 0; k < sy && yoff + k < h; k++) {
              flines = lines + k * wpls;
              flined = lined + k * wpld;
            for (m = 0; m < sx && xoff + m < w; m++) {
                vals = *(flines + xoff + m);
                rvald = ((vals >> 24) * rval16) / 256;
                rvald = L_MIN(rvald, 255);
                gvald = (((vals >> 16) & 0xff) * gval16) / 256;
                gvald = L_MIN(gvald, 255);
                bvald = (((vals >> 8) & 0xff) * bval16) / 256;
                bvald = L_MIN(bvald, 255);
                vald = (rvald << 24 | gvald << 16 | bvald << 8);
                *(flined + xoff + m) = vald;
            }
          }
      }
    }

    return pixd;
}


Generated by  Doxygen 1.6.0   Back to index