/*====================================================================* - Copyright (C) 2001 Leptonica. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials - provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *====================================================================*/ /*! * \file bilateral.c *
 *
 *     Top level approximate separable grayscale or color bilateral filtering
 *          PIX                 *pixBilateral()
 *          PIX                 *pixBilateralGray()
 *
 *     Implementation of approximate separable bilateral filter
 *          static L_BILATERAL  *bilateralCreate()
 *          static void         *bilateralDestroy()
 *          static PIX          *bilateralApply()
 *
 *     Slow, exact implementation of grayscale or color bilateral filtering
 *          PIX                 *pixBilateralExact()
 *          PIX                 *pixBilateralGrayExact()
 *          PIX                 *pixBlockBilateralExact()
 *
 *     Kernel helper function
 *          L_KERNEL            *makeRangeKernel()
 *
 *  This includes both a slow, exact implementation of the bilateral
 *  filter algorithm (given by Sylvain Paris and Frédo Durand),
 *  and a fast, approximate and separable implementation (following
 *  Yang, Tan and Ahuja).  See bilateral.h for algorithmic details.
 *
 *  The bilateral filter has the nice property of applying a gaussian
 *  filter to smooth parts of the image that don't vary too quickly,
 *  while at the same time preserving edges.  The filter is nonlinear
 *  and cannot be decomposed into two separable filters; however,
 *  there exists an approximate method that is separable.  To further
 *  speed up the separable implementation, you can generate the
 *  intermediate data at reduced resolution.
 *
 *  The full kernel is composed of two parts: a spatial gaussian filter
 *  and a nonlinear "range" filter that depends on the intensity difference
 *  between the reference pixel at the spatial kernel origin and any other
 *  pixel within the kernel support.
 *
 *  In our implementations, the range filter is a parameterized,
 *  one-sided, 256-element, monotonically decreasing gaussian function
 *  of the absolute value of the difference between pixel values; namely,
 *  abs(I2 - I1).  In general, any decreasing function can be used,
 *  and more generally,  any two-dimensional kernel can be used if
 *  you wish to relax the 'abs' condition.  (In that case, the range
 *  filter can be 256 x 256).
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include #include "allheaders.h" #include "bilateral.h" static L_BILATERAL *bilateralCreate(PIX *pixs, l_float32 spatial_stdev, l_float32 range_stdev, l_int32 ncomps, l_int32 reduction); static PIX *bilateralApply(L_BILATERAL *bil); static void bilateralDestroy(L_BILATERAL **pbil); #ifndef NO_CONSOLE_IO #define DEBUG_BILATERAL 0 #endif /* ~NO_CONSOLE_IO */ /*--------------------------------------------------------------------------* * Top level approximate separable grayscale or color bilateral filtering * *--------------------------------------------------------------------------*/ /*! * \brief pixBilateral() * * \param[in] pixs 8 bpp gray or 32 bpp rgb, no colormap * \param[in] spatial_stdev of gaussian kernel; in pixels, > 0.5 * \param[in] range_stdev of gaussian range kernel; > 5.0; typ. 50.0 * \param[in] ncomps number of intermediate sums J(k,x); * in [4 ... 30] * \param[in] reduction 1, 2 or 4 * \return pixd bilateral filtered image, or NULL on error * *
 * Notes:
 *      (1) This performs a relatively fast, separable bilateral
 *          filtering operation.  The time is proportional to ncomps
 *          and varies inversely approximately as the cube of the
 *          reduction factor.  See bilateral.h for algorithm details.
 *      (2) We impose minimum values for range_stdev and ncomps to
 *          avoid nasty artifacts when either are too small.  We also
 *          impose a constraint on their product:
 *               ncomps * range_stdev >= 100.
 *          So for values of range_stdev >= 25, ncomps can be as small as 4.
 *          Here is a qualitative, intuitive explanation for this constraint.
 *          Call the difference in k values between the J(k) == 'delta', where
 *              'delta' ~ 200 / ncomps
 *          Then this constraint is roughly equivalent to the condition:
 *              'delta' < 2 * range_stdev
 *          Note that at an intensity difference of (2 * range_stdev), the
 *          range part of the kernel reduces the effect by the factor 0.14.
 *          This constraint requires that we have a sufficient number of
 *          PCBs (i.e, a small enough 'delta'), so that for any value of
 *          image intensity I, there exists a k (and a PCB, J(k), such that
 *              |I - k| < range_stdev
 *          Any fewer PCBs and we don't have enough to support this condition.
 *      (3) The upper limit of 30 on ncomps is imposed because the
 *          gain in accuracy is not worth the extra computation.
 *      (4) The size of the gaussian kernel is twice the spatial_stdev
 *          on each side of the origin.  The minimum value of
 *          spatial_stdev, 0.5, is required to have a finite sized
 *          spatial kernel.  In practice, a much larger value is used.
 *      (5) Computation of the intermediate images goes inversely
 *          as the cube of the reduction factor.  If you can use a
 *          reduction of 2 or 4, it is well-advised.
 *      (6) The range kernel is defined over the absolute value of pixel
 *          grayscale differences, and hence must have size 256 x 1.
 *          Values in the array represent the multiplying weight
 *          depending on the absolute gray value difference between
 *          the source pixel and the neighboring pixel, and should
 *          be monotonically decreasing.
 *      (7) Interesting observation.  Run this on prog/fish24.jpg, with
 *          range_stdev = 60, ncomps = 6, and spatial_dev = {10, 30, 50}.
 *          As spatial_dev gets larger, we get the counter-intuitive
 *          result that the body of the red fish becomes less blurry.
 *      (8) The image must be sufficiently big to get reasonable results.
 *          This requires the dimensions to be at least twice the filter size.
 *          Otherwise, return a copy of the input with warning.
 * 
*/ PIX * pixBilateral(PIX *pixs, l_float32 spatial_stdev, l_float32 range_stdev, l_int32 ncomps, l_int32 reduction) { l_int32 w, h, d, filtersize; l_float32 sstdev; /* scaled spatial stdev */ PIX *pixt, *pixr, *pixg, *pixb, *pixd; PROCNAME("pixBilateral"); if (!pixs || pixGetColormap(pixs)) return (PIX *)ERROR_PTR("pixs not defined or cmapped", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 8 && d != 32) return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL); if (reduction != 1 && reduction != 2 && reduction != 4) return (PIX *)ERROR_PTR("reduction invalid", procName, NULL); filtersize = (l_int32)(2.0 * spatial_stdev + 1.0 + 0.5); if (w < 2 * filtersize || h < 2 * filtersize) { L_WARNING("w = %d, h = %d; w or h < 2 * filtersize = %d; " "returning copy\n", procName, w, h, 2 * filtersize); return pixCopy(NULL, pixs); } sstdev = spatial_stdev / (l_float32)reduction; /* reduced spat. stdev */ if (sstdev < 0.5) return (PIX *)ERROR_PTR("sstdev < 0.5", procName, NULL); if (range_stdev <= 5.0) return (PIX *)ERROR_PTR("range_stdev <= 5.0", procName, NULL); if (ncomps < 4 || ncomps > 30) return (PIX *)ERROR_PTR("ncomps not in [4 ... 30]", procName, NULL); if (ncomps * range_stdev < 100.0) return (PIX *)ERROR_PTR("ncomps * range_stdev < 100.0", procName, NULL); if (d == 8) return pixBilateralGray(pixs, spatial_stdev, range_stdev, ncomps, reduction); pixt = pixGetRGBComponent(pixs, COLOR_RED); pixr = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps, reduction); pixDestroy(&pixt); pixt = pixGetRGBComponent(pixs, COLOR_GREEN); pixg = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps, reduction); pixDestroy(&pixt); pixt = pixGetRGBComponent(pixs, COLOR_BLUE); pixb = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps, reduction); pixDestroy(&pixt); pixd = pixCreateRGBImage(pixr, pixg, pixb); pixDestroy(&pixr); pixDestroy(&pixg); pixDestroy(&pixb); return pixd; } /*! * \brief pixBilateralGray() * * \param[in] pixs 8 bpp gray * \param[in] spatial_stdev of gaussian kernel; in pixels, > 0.5 * \param[in] range_stdev of gaussian range kernel; > 5.0; typ. 50.0 * \param[in] ncomps number of intermediate sums J(k,x); * in [4 ... 30] * \param[in] reduction 1, 2 or 4 * \return pixd 8 bpp bilateral filtered image, or NULL on error * *
 * Notes:
 *      (1) See pixBilateral() for constraints on the input parameters.
 *      (2) See pixBilateral() for algorithm details.
 * 
*/ PIX * pixBilateralGray(PIX *pixs, l_float32 spatial_stdev, l_float32 range_stdev, l_int32 ncomps, l_int32 reduction) { l_float32 sstdev; /* scaled spatial stdev */ PIX *pixd; L_BILATERAL *bil; PROCNAME("pixBilateralGray"); if (!pixs || pixGetColormap(pixs)) return (PIX *)ERROR_PTR("pixs not defined or cmapped", procName, NULL); if (pixGetDepth(pixs) != 8) return (PIX *)ERROR_PTR("pixs not 8 bpp gray", procName, NULL); if (reduction != 1 && reduction != 2 && reduction != 4) return (PIX *)ERROR_PTR("reduction invalid", procName, NULL); sstdev = spatial_stdev / (l_float32)reduction; /* reduced spat. stdev */ if (sstdev < 0.5) return (PIX *)ERROR_PTR("sstdev < 0.5", procName, NULL); if (range_stdev <= 5.0) return (PIX *)ERROR_PTR("range_stdev <= 5.0", procName, NULL); if (ncomps < 4 || ncomps > 30) return (PIX *)ERROR_PTR("ncomps not in [4 ... 30]", procName, NULL); if (ncomps * range_stdev < 100.0) return (PIX *)ERROR_PTR("ncomps * range_stdev < 100.0", procName, NULL); bil = bilateralCreate(pixs, spatial_stdev, range_stdev, ncomps, reduction); if (!bil) return (PIX *)ERROR_PTR("bil not made", procName, NULL); pixd = bilateralApply(bil); bilateralDestroy(&bil); return pixd; } /*----------------------------------------------------------------------* * Implementation of approximate separable bilateral filter * *----------------------------------------------------------------------*/ /*! * \brief bilateralCreate() * * \param[in] pixs 8 bpp gray, no colormap * \param[in] spatial_stdev of gaussian kernel; in pixels, > 0.5 * \param[in] range_stdev of gaussian range kernel; > 5.0; typ. 50.0 * \param[in] ncomps number of intermediate sums J(k,x); * in [4 ... 30] * \param[in] reduction 1, 2 or 4 * \return bil, or NULL on error * *
 * Notes:
 *      (1) This initializes a bilateral filtering operation, generating all
 *          the data required.  It takes most of the time in the bilateral
 *          filtering operation.
 *      (2) See bilateral.h for details of the algorithm.
 *      (3) See pixBilateral() for constraints on input parameters, which
 *          are not checked here.
 * 
*/ static L_BILATERAL * bilateralCreate(PIX *pixs, l_float32 spatial_stdev, l_float32 range_stdev, l_int32 ncomps, l_int32 reduction) { l_int32 w, ws, wd, h, hs, hd, i, j, k, index; l_int32 border, minval, maxval, spatial_size; l_int32 halfwidth, wpls, wplt, wpld, kval, nval, dval; l_float32 sstdev, fval1, fval2, denom, sum, norm, kern; l_int32 *nc, *kindex; l_float32 *kfract, *range, *spatial; l_uint32 *datas, *datat, *datad, *lines, *linet, *lined; L_BILATERAL *bil; PIX *pix1, *pix2, *pixt, *pixsc, *pixd; PIXA *pixac; PROCNAME("bilateralCreate"); if (reduction == 1) { pix1 = pixClone(pixs); } else if (reduction == 2) { pix1 = pixScaleAreaMap2(pixs); } else { /* reduction == 4) */ pix2 = pixScaleAreaMap2(pixs); pix1 = pixScaleAreaMap2(pix2); pixDestroy(&pix2); } if (!pix1) return (L_BILATERAL *)ERROR_PTR("pix1 not made", procName, NULL); sstdev = spatial_stdev / (l_float32)reduction; /* reduced spat. stdev */ border = (l_int32)(2 * sstdev + 1); pixsc = pixAddMirroredBorder(pix1, border, border, border, border); pixGetExtremeValue(pix1, 1, L_SELECT_MIN, NULL, NULL, NULL, &minval); pixGetExtremeValue(pix1, 1, L_SELECT_MAX, NULL, NULL, NULL, &maxval); pixDestroy(&pix1); if (!pixsc) return (L_BILATERAL *)ERROR_PTR("pixsc not made", procName, NULL); bil = (L_BILATERAL *)LEPT_CALLOC(1, sizeof(L_BILATERAL)); bil->spatial_stdev = sstdev; bil->range_stdev = range_stdev; bil->reduction = reduction; bil->ncomps = ncomps; bil->minval = minval; bil->maxval = maxval; bil->pixsc = pixsc; bil->pixs = pixClone(pixs); /* -------------------------------------------------------------------- * * Generate arrays for interpolation of J(k,x): * (1.0 - kfract[.]) * J(kindex[.], x) + kfract[.] * J(kindex[.] + 1, x), * where I(x) is the index into kfract[] and kindex[], * and x is an index into the 2D image array. * -------------------------------------------------------------------- */ /* nc is the set of k values to be used in J(k,x) */ nc = (l_int32 *)LEPT_CALLOC(ncomps, sizeof(l_int32)); for (i = 0; i < ncomps; i++) nc[i] = minval + i * (maxval - minval) / (ncomps - 1); bil->nc = nc; /* kindex maps from intensity I(x) to the lower k index for J(k,x) */ kindex = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); for (i = minval, k = 0; i <= maxval && k < ncomps - 1; k++) { fval2 = nc[k + 1]; while (i < fval2) { kindex[i] = k; i++; } } kindex[maxval] = ncomps - 2; bil->kindex = kindex; /* kfract maps from intensity I(x) to the fraction of J(k+1,x) used */ kfract = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32)); /* from lower */ for (i = minval, k = 0; i <= maxval && k < ncomps - 1; k++) { fval1 = nc[k]; fval2 = nc[k + 1]; while (i < fval2) { kfract[i] = (l_float32)(i - fval1) / (l_float32)(fval2 - fval1); i++; } } kfract[maxval] = 1.0; bil->kfract = kfract; #if DEBUG_BILATERAL for (i = minval; i <= maxval; i++) lept_stderr("kindex[%d] = %d; kfract[%d] = %5.3f\n", i, kindex[i], i, kfract[i]); for (i = 0; i < ncomps; i++) lept_stderr("nc[%d] = %d\n", i, nc[i]); #endif /* DEBUG_BILATERAL */ /* -------------------------------------------------------------------- * * Generate 1-D kernel arrays (spatial and range) * * -------------------------------------------------------------------- */ spatial_size = 2 * sstdev + 1; /* same as the added border */ spatial = (l_float32 *)LEPT_CALLOC(spatial_size, sizeof(l_float32)); denom = 2. * sstdev * sstdev; for (i = 0; i < spatial_size; i++) spatial[i] = expf(-(l_float32)(i * i) / denom); bil->spatial = spatial; range = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32)); denom = 2. * range_stdev * range_stdev; for (i = 0; i < 256; i++) range[i] = expf(-(l_float32)(i * i) / denom); bil->range = range; /* -------------------------------------------------------------------- * * Generate principal bilateral component images * * -------------------------------------------------------------------- */ pixac = pixaCreate(ncomps); pixGetDimensions(pixsc, &ws, &hs, NULL); datas = pixGetData(pixsc); wpls = pixGetWpl(pixsc); pixGetDimensions(pixs, &w, &h, NULL); wd = (w + reduction - 1) / reduction; hd = (h + reduction - 1) / reduction; halfwidth = (l_int32)(2.0 * sstdev); for (index = 0; index < ncomps; index++) { pixt = pixCopy(NULL, pixsc); datat = pixGetData(pixt); wplt = pixGetWpl(pixt); kval = nc[index]; /* Separable convolutions: horizontal first */ for (i = 0; i < hd; i++) { lines = datas + (border + i) * wpls; linet = datat + (border + i) * wplt; for (j = 0; j < wd; j++) { sum = 0.0; norm = 0.0; for (k = -halfwidth; k <= halfwidth; k++) { nval = GET_DATA_BYTE(lines, border + j + k); kern = spatial[L_ABS(k)] * range[L_ABS(kval - nval)]; sum += kern * nval; norm += kern; } if (norm > 0.0) { dval = (l_int32)((sum / norm) + 0.5); SET_DATA_BYTE(linet, border + j, dval); } } } /* Vertical convolution */ pixd = pixCreate(wd, hd, 8); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); for (i = 0; i < hd; i++) { linet = datat + (border + i) * wplt; lined = datad + i * wpld; for (j = 0; j < wd; j++) { sum = 0.0; norm = 0.0; for (k = -halfwidth; k <= halfwidth; k++) { nval = GET_DATA_BYTE(linet + k * wplt, border + j); kern = spatial[L_ABS(k)] * range[L_ABS(kval - nval)]; sum += kern * nval; norm += kern; } if (norm > 0.0) dval = (l_int32)((sum / norm) + 0.5); else dval = GET_DATA_BYTE(linet, border + j); SET_DATA_BYTE(lined, j, dval); } } pixDestroy(&pixt); pixaAddPix(pixac, pixd, L_INSERT); } bil->pixac = pixac; bil->lineset = (l_uint32 ***)pixaGetLinePtrs(pixac, NULL); return bil; } /*! * \brief bilateralApply() * * \param[in] bil * \return pixd */ static PIX * bilateralApply(L_BILATERAL *bil) { l_int32 i, j, k, ired, jred, w, h, wpls, wpld, ncomps, reduction; l_int32 vals, vald, lowval, hival; l_int32 *kindex; l_float32 fract; l_float32 *kfract; l_uint32 *lines, *lined, *datas, *datad; l_uint32 ***lineset = NULL; /* for set of PBC */ PIX *pixs, *pixd; PIXA *pixac; PROCNAME("bilateralApply"); if (!bil) return (PIX *)ERROR_PTR("bil not defined", procName, NULL); pixs = bil->pixs; ncomps = bil->ncomps; kindex = bil->kindex; kfract = bil->kfract; reduction = bil->reduction; pixac = bil->pixac; lineset = bil->lineset; if (pixaGetCount(pixac) != ncomps) return (PIX *)ERROR_PTR("PBC images do not exist", procName, NULL); if ((pixd = pixCreateTemplate(pixs)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); pixGetDimensions(pixs, &w, &h, NULL); for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; ired = i / reduction; for (j = 0; j < w; j++) { jred = j / reduction; vals = GET_DATA_BYTE(lines, j); k = kindex[vals]; lowval = GET_DATA_BYTE(lineset[k][ired], jred); hival = GET_DATA_BYTE(lineset[k + 1][ired], jred); fract = kfract[vals]; vald = (l_int32)((1.0 - fract) * lowval + fract * hival + 0.5); SET_DATA_BYTE(lined, j, vald); } } return pixd; } /*! * \brief bilateralDestroy() * * \param[in,out] pbil will be set to null before returning */ static void bilateralDestroy(L_BILATERAL **pbil) { l_int32 i; L_BILATERAL *bil; PROCNAME("bilateralDestroy"); if (pbil == NULL) { L_WARNING("ptr address is null!\n", procName); return; } if ((bil = *pbil) == NULL) return; pixDestroy(&bil->pixs); pixDestroy(&bil->pixsc); pixaDestroy(&bil->pixac); LEPT_FREE(bil->spatial); LEPT_FREE(bil->range); LEPT_FREE(bil->nc); LEPT_FREE(bil->kindex); LEPT_FREE(bil->kfract); for (i = 0; i < bil->ncomps; i++) LEPT_FREE(bil->lineset[i]); LEPT_FREE(bil->lineset); LEPT_FREE(bil); *pbil = NULL; } /*----------------------------------------------------------------------* * Exact implementation of grayscale or color bilateral filtering * *----------------------------------------------------------------------*/ /*! * \brief pixBilateralExact() * * \param[in] pixs 8 bpp gray or 32 bpp rgb * \param[in] spatial_kel gaussian kernel * \param[in] range_kel [optional] 256 x 1, monotonically decreasing * \return pixd 8 bpp bilateral filtered image * *
 * Notes:
 *      (1) The spatial_kel is a conventional smoothing kernel, typically a
 *          2-d Gaussian kernel or other block kernel.  It can be either
 *          normalized or not, but must be everywhere positive.
 *      (2) The range_kel is defined over the absolute value of pixel
 *          grayscale differences, and hence must have size 256 x 1.
 *          Values in the array represent the multiplying weight for each
 *          gray value difference between the target pixel and center of the
 *          kernel, and should be monotonically decreasing.
 *      (3) If range_kel == NULL, a constant weight is applied regardless
 *          of the range value difference.  This degenerates to a regular
 *          pixConvolve() with a normalized kernel.
 * 
*/ PIX * pixBilateralExact(PIX *pixs, L_KERNEL *spatial_kel, L_KERNEL *range_kel) { l_int32 d; PIX *pixt, *pixr, *pixg, *pixb, *pixd; PROCNAME("pixBilateralExact"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetColormap(pixs) != NULL) return (PIX *)ERROR_PTR("pixs is cmapped", procName, NULL); d = pixGetDepth(pixs); if (d != 8 && d != 32) return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL); if (!spatial_kel) return (PIX *)ERROR_PTR("spatial_ke not defined", procName, NULL); if (d == 8) { return pixBilateralGrayExact(pixs, spatial_kel, range_kel); } else { /* d == 32 */ pixt = pixGetRGBComponent(pixs, COLOR_RED); pixr = pixBilateralGrayExact(pixt, spatial_kel, range_kel); pixDestroy(&pixt); pixt = pixGetRGBComponent(pixs, COLOR_GREEN); pixg = pixBilateralGrayExact(pixt, spatial_kel, range_kel); pixDestroy(&pixt); pixt = pixGetRGBComponent(pixs, COLOR_BLUE); pixb = pixBilateralGrayExact(pixt, spatial_kel, range_kel); pixDestroy(&pixt); pixd = pixCreateRGBImage(pixr, pixg, pixb); pixDestroy(&pixr); pixDestroy(&pixg); pixDestroy(&pixb); return pixd; } } /*! * \brief pixBilateralGrayExact() * * \param[in] pixs 8 bpp gray * \param[in] spatial_kel gaussian kernel * \param[in] range_kel [optional] 256 x 1, monotonically decreasing * \return pixd 8 bpp bilateral filtered image * *
 * Notes:
 *      (1) See pixBilateralExact().
 * 
*/ PIX * pixBilateralGrayExact(PIX *pixs, L_KERNEL *spatial_kel, L_KERNEL *range_kel) { l_int32 i, j, id, jd, k, m, w, h, d, sx, sy, cx, cy, wplt, wpld; l_int32 val, center_val; l_uint32 *datat, *datad, *linet, *lined; l_float32 sum, weight_sum, weight; L_KERNEL *keli; PIX *pixt, *pixd; PROCNAME("pixBilateralGrayExact"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 8) return (PIX *)ERROR_PTR("pixs must be gray", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (!spatial_kel) return (PIX *)ERROR_PTR("spatial kel not defined", procName, NULL); kernelGetParameters(spatial_kel, &sy, &sx, NULL, NULL); if (w < 2 * sx + 1 || h < 2 * sy + 1) { L_WARNING("w = %d < 2 * sx + 1 = %d, or h = %d < 2 * sy + 1 = %d; " "returning copy\n", procName, w, 2 * sx + 1, h, 2 * sy + 1); return pixCopy(NULL, pixs); } if (!range_kel) return pixConvolve(pixs, spatial_kel, 8, 1); if (range_kel->sx != 256 || range_kel->sy != 1) return (PIX *)ERROR_PTR("range kel not {256 x 1", procName, NULL); keli = kernelInvert(spatial_kel); kernelGetParameters(keli, &sy, &sx, &cy, &cx); if ((pixt = pixAddMirroredBorder(pixs, cx, sx - cx, cy, sy - cy)) == NULL) { kernelDestroy(&keli); return (PIX *)ERROR_PTR("pixt not made", procName, NULL); } pixd = pixCreate(w, h, 8); datat = pixGetData(pixt); datad = pixGetData(pixd); wplt = pixGetWpl(pixt); wpld = pixGetWpl(pixd); for (i = 0, id = 0; id < h; i++, id++) { lined = datad + id * wpld; for (j = 0, jd = 0; jd < w; j++, jd++) { center_val = GET_DATA_BYTE(datat + (i + cy) * wplt, j + cx); weight_sum = 0.0; sum = 0.0; for (k = 0; k < sy; k++) { linet = datat + (i + k) * wplt; for (m = 0; m < sx; m++) { val = GET_DATA_BYTE(linet, j + m); weight = keli->data[k][m] * range_kel->data[0][L_ABS(center_val - val)]; weight_sum += weight; sum += val * weight; } } SET_DATA_BYTE(lined, jd, (l_int32)(sum / weight_sum + 0.5)); } } kernelDestroy(&keli); pixDestroy(&pixt); return pixd; } /*! * \brief pixBlockBilateralExact() * * \param[in] pixs 8 bpp gray or 32 bpp rgb * \param[in] spatial_stdev must be > 0.0 * \param[in] range_stdev must be > 0.0 * \return pixd 8 bpp or 32 bpp bilateral filtered image * *
 * Notes:
 *      (1) See pixBilateralExact().  This provides an interface using
 *          the standard deviations of the spatial and range filters.
 *      (2) The convolution window halfwidth is 2 * spatial_stdev,
 *          and the square filter size is 4 * spatial_stdev + 1.
 *          The kernel captures 95% of total energy.  This is compensated
 *          by normalization.
 *      (3) The range_stdev is analogous to spatial_halfwidth in the
 *          grayscale domain [0...255], and determines how much damping of the
 *          smoothing operation is applied across edges.  The larger this
 *          value is, the smaller the damping.  The smaller the value, the
 *          more edge details are preserved.  These approximations are useful
 *          for deciding the appropriate cutoff.
 *              kernel[1 * stdev] ~= 0.6  * kernel[0]
 *              kernel[2 * stdev] ~= 0.14 * kernel[0]
 *              kernel[3 * stdev] ~= 0.01 * kernel[0]
 *          If range_stdev is infinite there is no damping, and this
 *          becomes a conventional gaussian smoothing.
 *          This value does not affect the run time.
 *      (4) If range_stdev is negative or zero, the range kernel is
 *          ignored and this degenerates to a straight gaussian convolution.
 *      (5) This is very slow for large spatial filters.  The time
 *          on a 3GHz pentium is roughly
 *             T = 1.2 * 10^-8 * (A * sh^2)  sec
 *          where A = # of pixels, sh = spatial halfwidth of filter.
 * 
*/ PIX* pixBlockBilateralExact(PIX *pixs, l_float32 spatial_stdev, l_float32 range_stdev) { l_int32 d, halfwidth; L_KERNEL *spatial_kel, *range_kel; PIX *pixd; PROCNAME("pixBlockBilateralExact"); 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 (pixGetColormap(pixs) != NULL) return (PIX *)ERROR_PTR("pixs is cmapped", procName, NULL); if (spatial_stdev <= 0.0) return (PIX *)ERROR_PTR("invalid spatial stdev", procName, NULL); if (range_stdev <= 0.0) return (PIX *)ERROR_PTR("invalid range stdev", procName, NULL); halfwidth = 2 * spatial_stdev; spatial_kel = makeGaussianKernel(halfwidth, halfwidth, spatial_stdev, 1.0); range_kel = makeRangeKernel(range_stdev); pixd = pixBilateralExact(pixs, spatial_kel, range_kel); kernelDestroy(&spatial_kel); kernelDestroy(&range_kel); return pixd; } /*----------------------------------------------------------------------* * Kernel helper function * *----------------------------------------------------------------------*/ /*! * \brief makeRangeKernel() * * \param[in] range_stdev must be > 0.0 * \return kel, or NULL on error * *
 * Notes:
 *      (1) Creates a one-sided Gaussian kernel with the given
 *          standard deviation.  At grayscale difference of one stdev,
 *          the kernel falls to 0.6, and to 0.01 at three stdev.
 *      (2) A typical input number might be 20.  Then pixels whose
 *          value differs by 60 from the center pixel have their
 *          weight in the convolution reduced by a factor of about 0.01.
 * 
*/ L_KERNEL * makeRangeKernel(l_float32 range_stdev) { l_int32 x; l_float32 val, denom; L_KERNEL *kel; PROCNAME("makeRangeKernel"); if (range_stdev <= 0.0) return (L_KERNEL *)ERROR_PTR("invalid stdev <= 0", procName, NULL); denom = 2. * range_stdev * range_stdev; if ((kel = kernelCreate(1, 256)) == NULL) return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL); kernelSetOrigin(kel, 0, 0); for (x = 0; x < 256; x++) { val = expf(-(l_float32)(x * x) / denom); kernelSetElement(kel, 0, x, val); } return kel; }