Coverage Report

Created: 2025-12-31 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/leptonica/src/bilateral.c
Line
Count
Source
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 bilateral.c
29
 * <pre>
30
 *
31
 *     Top level approximate separable grayscale or color bilateral filtering
32
 *          PIX                 *pixBilateral()
33
 *          PIX                 *pixBilateralGray()
34
 *
35
 *     Implementation of approximate separable bilateral filter
36
 *          static L_BILATERAL  *bilateralCreate()
37
 *          static void         *bilateralDestroy()
38
 *          static PIX          *bilateralApply()
39
 *
40
 *     Slow, exact implementation of grayscale or color bilateral filtering
41
 *          PIX                 *pixBilateralExact()
42
 *          PIX                 *pixBilateralGrayExact()
43
 *          PIX                 *pixBlockBilateralExact()
44
 *
45
 *     Kernel helper function
46
 *          L_KERNEL            *makeRangeKernel()
47
 *
48
 *  This includes both a slow, exact implementation of the bilateral
49
 *  filter algorithm (given by Sylvain Paris and Frédo Durand),
50
 *  and a fast, approximate and separable implementation (following
51
 *  Yang, Tan and Ahuja).  See bilateral.h for algorithmic details.
52
 *
53
 *  The bilateral filter has the nice property of applying a gaussian
54
 *  filter to smooth parts of the image that don't vary too quickly,
55
 *  while at the same time preserving edges.  The filter is nonlinear
56
 *  and cannot be decomposed into two separable filters; however,
57
 *  there exists an approximate method that is separable.  To further
58
 *  speed up the separable implementation, you can generate the
59
 *  intermediate data at reduced resolution.
60
 *
61
 *  The full kernel is composed of two parts: a spatial gaussian filter
62
 *  and a nonlinear "range" filter that depends on the intensity difference
63
 *  between the reference pixel at the spatial kernel origin and any other
64
 *  pixel within the kernel support.
65
 *
66
 *  In our implementations, the range filter is a parameterized,
67
 *  one-sided, 256-element, monotonically decreasing gaussian function
68
 *  of the absolute value of the difference between pixel values; namely,
69
 *  abs(I2 - I1).  In general, any decreasing function can be used,
70
 *  and more generally,  any two-dimensional kernel can be used if
71
 *  you wish to relax the 'abs' condition.  (In that case, the range
72
 *  filter can be 256 x 256).
73
 * </pre>
74
 */
75
76
#ifdef HAVE_CONFIG_H
77
#include <config_auto.h>
78
#endif  /* HAVE_CONFIG_H */
79
80
#include <math.h>
81
#include "allheaders.h"
82
#include "bilateral.h"
83
84
static L_BILATERAL *bilateralCreate(PIX *pixs, l_float32 spatial_stdev,
85
                                    l_float32 range_stdev, l_int32 ncomps,
86
                                    l_int32 reduction);
87
static PIX *bilateralApply(L_BILATERAL *bil);
88
static void bilateralDestroy(L_BILATERAL **pbil);
89
90
91
#ifndef  NO_CONSOLE_IO
92
#define  DEBUG_BILATERAL    0
93
#endif  /* ~NO_CONSOLE_IO */
94
95
/*--------------------------------------------------------------------------*
96
 *  Top level approximate separable grayscale or color bilateral filtering  *
97
 *--------------------------------------------------------------------------*/
98
/*!
99
 * \brief   pixBilateral()
100
 *
101
 * \param[in]    pixs            8 bpp gray or 32 bpp rgb, no colormap
102
 * \param[in]    spatial_stdev   of gaussian kernel; in pixels, > 0.5
103
 * \param[in]    range_stdev     of gaussian range kernel; > 5.0; typ. 50.0
104
 * \param[in]    ncomps          number of intermediate sums J(k,x);
105
 *                               in [4 ... 30]
106
 * \param[in]    reduction       1, 2 or 4
107
 * \return  pixd   bilateral filtered image, or NULL on error
108
 *
109
 * <pre>
110
 * Notes:
111
 *      (1) This performs a relatively fast, separable bilateral
112
 *          filtering operation.  The time is proportional to ncomps
113
 *          and varies inversely approximately as the cube of the
114
 *          reduction factor.  See bilateral.h for algorithm details.
115
 *      (2) We impose minimum values for range_stdev and ncomps to
116
 *          avoid nasty artifacts when either are too small.  We also
117
 *          impose a constraint on their product:
118
 *               ncomps * range_stdev >= 100.
119
 *          So for values of range_stdev >= 25, ncomps can be as small as 4.
120
 *          Here is a qualitative, intuitive explanation for this constraint.
121
 *          Call the difference in k values between the J(k) == 'delta', where
122
 *              'delta' ~ 200 / ncomps
123
 *          Then this constraint is roughly equivalent to the condition:
124
 *              'delta' < 2 * range_stdev
125
 *          Note that at an intensity difference of (2 * range_stdev), the
126
 *          range part of the kernel reduces the effect by the factor 0.14.
127
 *          This constraint requires that we have a sufficient number of
128
 *          PCBs (i.e, a small enough 'delta'), so that for any value of
129
 *          image intensity I, there exists a k (and a PCB, J(k), such that
130
 *              |I - k| < range_stdev
131
 *          Any fewer PCBs and we don't have enough to support this condition.
132
 *      (3) The upper limit of 30 on ncomps is imposed because the
133
 *          gain in accuracy is not worth the extra computation.
134
 *      (4) The size of the gaussian kernel is twice the spatial_stdev
135
 *          on each side of the origin.  The minimum value of
136
 *          spatial_stdev, 0.5, is required to have a finite sized
137
 *          spatial kernel.  In practice, a much larger value is used.
138
 *      (5) Computation of the intermediate images goes inversely
139
 *          as the cube of the reduction factor.  If you can use a
140
 *          reduction of 2 or 4, it is well-advised.
141
 *      (6) The range kernel is defined over the absolute value of pixel
142
 *          grayscale differences, and hence must have size 256 x 1.
143
 *          Values in the array represent the multiplying weight
144
 *          depending on the absolute gray value difference between
145
 *          the source pixel and the neighboring pixel, and should
146
 *          be monotonically decreasing.
147
 *      (7) Interesting observation.  Run this on prog/fish24.jpg, with
148
 *          range_stdev = 60, ncomps = 6, and spatial_dev = {10, 30, 50}.
149
 *          As spatial_dev gets larger, we get the counter-intuitive
150
 *          result that the body of the red fish becomes less blurry.
151
 *      (8) The image must be sufficiently big to get reasonable results.
152
 *          This requires the dimensions to be at least twice the filter size.
153
 *          Otherwise, return a copy of the input with warning.
154
 * </pre>
155
 */
156
PIX *
157
pixBilateral(PIX       *pixs,
158
             l_float32  spatial_stdev,
159
             l_float32  range_stdev,
160
             l_int32    ncomps,
161
             l_int32    reduction)
162
284
{
163
284
l_int32       w, h, d, filtersize;
164
284
l_float32     sstdev;  /* scaled spatial stdev */
165
284
PIX          *pixt, *pixr, *pixg, *pixb, *pixd;
166
167
284
    if (!pixs || pixGetColormap(pixs))
168
74
        return (PIX *)ERROR_PTR("pixs not defined or cmapped", __func__, NULL);
169
210
    pixGetDimensions(pixs, &w, &h, &d);
170
210
    if (d != 8 && d != 32)
171
8
        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL);
172
202
    if (reduction != 1 && reduction != 2 && reduction != 4)
173
0
        return (PIX *)ERROR_PTR("reduction invalid", __func__, NULL);
174
202
    filtersize = (l_int32)(2.0 * spatial_stdev + 1.0 + 0.5);
175
202
    if (w < 2 * filtersize || h < 2 * filtersize) {
176
77
        L_WARNING("w = %d, h = %d; w or h < 2 * filtersize = %d; "
177
77
                  "returning copy\n", __func__, w, h, 2 * filtersize);
178
77
        return pixCopy(NULL, pixs);
179
77
    }
180
125
    sstdev = spatial_stdev / (l_float32)reduction;  /* reduced spat. stdev */
181
125
    if (sstdev < 0.5)
182
0
        return (PIX *)ERROR_PTR("sstdev < 0.5", __func__, NULL);
183
125
    if (range_stdev <= 5.0)
184
0
        return (PIX *)ERROR_PTR("range_stdev <= 5.0", __func__, NULL);
185
125
    if (ncomps < 4 || ncomps > 30)
186
0
        return (PIX *)ERROR_PTR("ncomps not in [4 ... 30]", __func__, NULL);
187
125
    if (ncomps * range_stdev < 100.0)
188
0
        return (PIX *)ERROR_PTR("ncomps * range_stdev < 100.0", __func__, NULL);
189
190
125
    if (d == 8)
191
118
        return pixBilateralGray(pixs, spatial_stdev, range_stdev,
192
118
                                ncomps, reduction);
193
194
7
    pixt = pixGetRGBComponent(pixs, COLOR_RED);
195
7
    pixr = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps,
196
7
                            reduction);
197
7
    pixDestroy(&pixt);
198
7
    pixt = pixGetRGBComponent(pixs, COLOR_GREEN);
199
7
    pixg = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps,
200
7
                            reduction);
201
7
    pixDestroy(&pixt);
202
7
    pixt = pixGetRGBComponent(pixs, COLOR_BLUE);
203
7
    pixb = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps,
204
7
                            reduction);
205
7
    pixDestroy(&pixt);
206
7
    pixd = pixCreateRGBImage(pixr, pixg, pixb);
207
7
    pixDestroy(&pixr);
208
7
    pixDestroy(&pixg);
209
7
    pixDestroy(&pixb);
210
7
    return pixd;
211
125
}
212
213
214
/*!
215
 * \brief   pixBilateralGray()
216
 *
217
 * \param[in]    pixs             8 bpp gray
218
 * \param[in]    spatial_stdev    of gaussian kernel; in pixels, > 0.5
219
 * \param[in]    range_stdev      of gaussian range kernel; > 5.0; typ. 50.0
220
 * \param[in]    ncomps           number of intermediate sums J(k,x);
221
 *                                in [4 ... 30]
222
 * \param[in]    reduction        1, 2 or 4
223
 * \return  pixd   8 bpp bilateral filtered image, or NULL on error
224
 *
225
 * <pre>
226
 * Notes:
227
 *      (1) See pixBilateral() for constraints on the input parameters.
228
 *      (2) See pixBilateral() for algorithm details.
229
 * </pre>
230
 */
231
PIX *
232
pixBilateralGray(PIX       *pixs,
233
                 l_float32  spatial_stdev,
234
                 l_float32  range_stdev,
235
                 l_int32    ncomps,
236
                 l_int32    reduction)
237
139
{
238
139
l_float32     sstdev;  /* scaled spatial stdev */
239
139
PIX          *pixd;
240
139
L_BILATERAL  *bil;
241
242
139
    if (!pixs || pixGetColormap(pixs))
243
0
        return (PIX *)ERROR_PTR("pixs not defined or cmapped", __func__, NULL);
244
139
    if (pixGetDepth(pixs) != 8)
245
0
        return (PIX *)ERROR_PTR("pixs not 8 bpp gray", __func__, NULL);
246
139
    if (reduction != 1 && reduction != 2 && reduction != 4)
247
0
        return (PIX *)ERROR_PTR("reduction invalid", __func__, NULL);
248
139
    sstdev = spatial_stdev / (l_float32)reduction;  /* reduced spat. stdev */
249
139
    if (sstdev < 0.5)
250
0
        return (PIX *)ERROR_PTR("sstdev < 0.5", __func__, NULL);
251
139
    if (range_stdev <= 5.0)
252
0
        return (PIX *)ERROR_PTR("range_stdev <= 5.0", __func__, NULL);
253
139
    if (ncomps < 4 || ncomps > 30)
254
0
        return (PIX *)ERROR_PTR("ncomps not in [4 ... 30]", __func__, NULL);
255
139
    if (ncomps * range_stdev < 100.0)
256
0
        return (PIX *)ERROR_PTR("ncomps * range_stdev < 100.0", __func__, NULL);
257
258
139
    bil = bilateralCreate(pixs, spatial_stdev, range_stdev, ncomps, reduction);
259
139
    if (!bil) return (PIX *)ERROR_PTR("bil not made", __func__, NULL);
260
139
    pixd = bilateralApply(bil);
261
139
    bilateralDestroy(&bil);
262
139
    return pixd;
263
139
}
264
265
266
/*----------------------------------------------------------------------*
267
 *       Implementation of approximate separable bilateral filter       *
268
 *----------------------------------------------------------------------*/
269
/*!
270
 * \brief   bilateralCreate()
271
 *
272
 * \param[in]    pixs            8 bpp gray, no colormap
273
 * \param[in]    spatial_stdev   of gaussian kernel; in pixels, > 0.5
274
 * \param[in]    range_stdev     of gaussian range kernel; > 5.0; typ. 50.0
275
 * \param[in]    ncomps          number of intermediate sums J(k,x);
276
 *                               in [4 ... 30]
277
 * \param[in]    reduction       1, 2 or 4
278
 * \return  bil, or NULL on error
279
 *
280
 * <pre>
281
 * Notes:
282
 *      (1) This initializes a bilateral filtering operation, generating all
283
 *          the data required.  It takes most of the time in the bilateral
284
 *          filtering operation.
285
 *      (2) See bilateral.h for details of the algorithm.
286
 *      (3) See pixBilateral() for constraints on input parameters, which
287
 *          are not checked here.
288
 * </pre>
289
 */
290
static L_BILATERAL *
291
bilateralCreate(PIX       *pixs,
292
                l_float32  spatial_stdev,
293
                l_float32  range_stdev,
294
                l_int32    ncomps,
295
                l_int32    reduction)
296
139
{
297
139
l_int32       w, ws, wd, h, hs, hd, i, j, k, index;
298
139
l_int32       border, minval, maxval, spatial_size;
299
139
l_int32       halfwidth, wpls, wplt, wpld, kval, nval, dval;
300
139
l_float32     sstdev, fval1, fval2, denom, sum, norm, kern;
301
139
l_int32      *nc, *kindex;
302
139
l_float32    *kfract, *range, *spatial;
303
139
l_uint32     *datas, *datat, *datad, *lines, *linet, *lined;
304
139
L_BILATERAL  *bil;
305
139
PIX          *pix1, *pix2, *pixt, *pixsc, *pixd;
306
139
PIXA         *pixac;
307
308
139
    if (reduction == 1) {
309
139
        pix1 = pixClone(pixs);
310
139
    } else if (reduction == 2) {
311
0
        pix1 = pixScaleAreaMap2(pixs);
312
0
    } else {  /* reduction == 4) */
313
0
        pix2 = pixScaleAreaMap2(pixs);
314
0
        pix1 = pixScaleAreaMap2(pix2);
315
0
        pixDestroy(&pix2);
316
0
    }
317
139
    if (!pix1)
318
0
        return (L_BILATERAL *)ERROR_PTR("pix1 not made", __func__, NULL);
319
320
139
    sstdev = spatial_stdev / (l_float32)reduction;  /* reduced spat. stdev */
321
139
    border = (l_int32)(2 * sstdev + 1);
322
139
    pixsc = pixAddMirroredBorder(pix1, border, border, border, border);
323
139
    pixGetExtremeValue(pix1, 1, L_SELECT_MIN, NULL, NULL, NULL, &minval);
324
139
    pixGetExtremeValue(pix1, 1, L_SELECT_MAX, NULL, NULL, NULL, &maxval);
325
139
    pixDestroy(&pix1);
326
139
    if (!pixsc)
327
0
        return (L_BILATERAL *)ERROR_PTR("pixsc not made", __func__, NULL);
328
329
139
    bil = (L_BILATERAL *)LEPT_CALLOC(1, sizeof(L_BILATERAL));
330
139
    bil->spatial_stdev = sstdev;
331
139
    bil->range_stdev = range_stdev;
332
139
    bil->reduction = reduction;
333
139
    bil->ncomps = ncomps;
334
139
    bil->minval = minval;
335
139
    bil->maxval = maxval;
336
139
    bil->pixsc = pixsc;
337
139
    bil->pixs = pixClone(pixs);
338
339
    /* -------------------------------------------------------------------- *
340
     * Generate arrays for interpolation of J(k,x):
341
     *  (1.0 - kfract[.]) * J(kindex[.], x) + kfract[.] * J(kindex[.] + 1, x),
342
     * where I(x) is the index into kfract[] and kindex[],
343
     * and x is an index into the 2D image array.
344
     * -------------------------------------------------------------------- */
345
        /* nc is the set of k values to be used in J(k,x) */
346
139
    nc = (l_int32 *)LEPT_CALLOC(ncomps, sizeof(l_int32));
347
1.52k
    for (i = 0; i < ncomps; i++)
348
1.39k
        nc[i] = minval + i * (maxval - minval) / (ncomps - 1);
349
139
    bil->nc = nc;
350
351
        /* kindex maps from intensity I(x) to the lower k index for J(k,x) */
352
139
    kindex = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
353
1.39k
    for (i = minval, k = 0; i <= maxval && k < ncomps - 1; k++) {
354
1.25k
        fval2 = nc[k + 1];
355
31.7k
        while (i < fval2) {
356
30.5k
            kindex[i] = k;
357
30.5k
            i++;
358
30.5k
        }
359
1.25k
    }
360
139
    kindex[maxval] = ncomps - 2;
361
139
    bil->kindex = kindex;
362
363
        /* kfract maps from intensity I(x) to the fraction of J(k+1,x) used */
364
139
    kfract = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32));  /* from lower */
365
1.39k
    for (i = minval, k = 0; i <= maxval && k < ncomps - 1; k++) {
366
1.25k
        fval1 = nc[k];
367
1.25k
        fval2 = nc[k + 1];
368
31.7k
        while (i < fval2) {
369
30.5k
            kfract[i] = (l_float32)(i - fval1) / (l_float32)(fval2 - fval1);
370
30.5k
            i++;
371
30.5k
        }
372
1.25k
    }
373
139
    kfract[maxval] = 1.0;
374
139
    bil->kfract = kfract;
375
376
#if  DEBUG_BILATERAL
377
    for (i = minval; i <= maxval; i++)
378
      lept_stderr("kindex[%d] = %d; kfract[%d] = %5.3f\n",
379
                  i, kindex[i], i, kfract[i]);
380
    for (i = 0; i < ncomps; i++)
381
      lept_stderr("nc[%d] = %d\n", i, nc[i]);
382
#endif  /* DEBUG_BILATERAL */
383
384
    /* -------------------------------------------------------------------- *
385
     *             Generate 1-D kernel arrays (spatial and range)           *
386
     * -------------------------------------------------------------------- */
387
139
    spatial_size = 2 * sstdev + 1;  /* same as the added border */
388
139
    spatial = (l_float32 *)LEPT_CALLOC(spatial_size, sizeof(l_float32));
389
139
    denom = 2. * sstdev * sstdev;
390
1.66k
    for (i = 0; i < spatial_size; i++)
391
1.52k
        spatial[i] = expf(-(l_float32)(i * i) / denom);
392
139
    bil->spatial = spatial;
393
394
139
    range = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32));
395
139
    denom = 2. * range_stdev * range_stdev;
396
35.7k
    for (i = 0; i < 256; i++)
397
35.5k
        range[i] = expf(-(l_float32)(i * i) / denom);
398
139
    bil->range = range;
399
400
    /* -------------------------------------------------------------------- *
401
     *            Generate principal bilateral component images             *
402
     * -------------------------------------------------------------------- */
403
139
    pixac = pixaCreate(ncomps);
404
139
    pixGetDimensions(pixsc, &ws, &hs, NULL);
405
139
    datas = pixGetData(pixsc);
406
139
    wpls = pixGetWpl(pixsc);
407
139
    pixGetDimensions(pixs, &w, &h, NULL);
408
139
    wd = (w + reduction - 1) / reduction;
409
139
    hd = (h + reduction - 1) / reduction;
410
139
    halfwidth = (l_int32)(2.0 * sstdev);
411
1.52k
    for (index = 0; index < ncomps; index++) {
412
1.39k
        pixt = pixCopy(NULL, pixsc);
413
1.39k
        datat = pixGetData(pixt);
414
1.39k
        wplt = pixGetWpl(pixt);
415
1.39k
        kval = nc[index];
416
            /* Separable convolutions: horizontal first */
417
89.8k
        for (i = 0; i < hd; i++) {
418
88.4k
            lines = datas + (border + i) * wpls;
419
88.4k
            linet = datat + (border + i) * wplt;
420
8.40M
            for (j = 0; j < wd; j++) {
421
8.31M
                sum = 0.0;
422
8.31M
                norm = 0.0;
423
182M
                for (k = -halfwidth; k <= halfwidth; k++) {
424
174M
                    nval = GET_DATA_BYTE(lines, border + j + k);
425
174M
                    kern = spatial[L_ABS(k)] * range[L_ABS(kval - nval)];
426
174M
                    sum += kern * nval;
427
174M
                    norm += kern;
428
174M
                }
429
8.31M
                if (norm > 0.0) {
430
7.02M
                    dval = (l_int32)((sum / norm) + 0.5);
431
7.02M
                    SET_DATA_BYTE(linet, border + j, dval);
432
7.02M
                }
433
8.31M
            }
434
88.4k
        }
435
            /* Vertical convolution */
436
1.39k
        pixd = pixCreate(wd, hd, 8);
437
1.39k
        datad = pixGetData(pixd);
438
1.39k
        wpld = pixGetWpl(pixd);
439
89.8k
        for (i = 0; i < hd; i++) {
440
88.4k
            linet = datat + (border + i) * wplt;
441
88.4k
            lined = datad + i * wpld;
442
8.40M
            for (j = 0; j < wd; j++) {
443
8.31M
                sum = 0.0;
444
8.31M
                norm = 0.0;
445
182M
                for (k = -halfwidth; k <= halfwidth; k++) {
446
174M
                    nval = GET_DATA_BYTE(linet + k * wplt, border + j);
447
174M
                    kern = spatial[L_ABS(k)] * range[L_ABS(kval - nval)];
448
174M
                    sum += kern * nval;
449
174M
                    norm += kern;
450
174M
                }
451
8.31M
                if (norm > 0.0)
452
8.26M
                    dval = (l_int32)((sum / norm) + 0.5);
453
50.2k
                else
454
50.2k
                    dval = GET_DATA_BYTE(linet, border + j);
455
8.31M
                SET_DATA_BYTE(lined, j, dval);
456
8.31M
            }
457
88.4k
        }
458
1.39k
        pixDestroy(&pixt);
459
1.39k
        pixaAddPix(pixac, pixd, L_INSERT);
460
1.39k
    }
461
139
    bil->pixac = pixac;
462
139
    bil->lineset = (l_uint32 ***)pixaGetLinePtrs(pixac, NULL);
463
139
    return bil;
464
139
}
465
466
467
/*!
468
 * \brief   bilateralApply()
469
 *
470
 * \param[in]    bil
471
 * \return  pixd
472
 */
473
static PIX *
474
bilateralApply(L_BILATERAL  *bil)
475
139
{
476
139
l_int32      i, j, k, ired, jred, w, h, wpls, wpld, ncomps, reduction;
477
139
l_int32      vals, vald, lowval, hival;
478
139
l_int32     *kindex;
479
139
l_float32    fract;
480
139
l_float32   *kfract;
481
139
l_uint32    *lines, *lined, *datas, *datad;
482
139
l_uint32  ***lineset = NULL;  /* for set of PBC */
483
139
PIX         *pixs, *pixd;
484
139
PIXA        *pixac;
485
486
139
    if (!bil)
487
0
        return (PIX *)ERROR_PTR("bil not defined", __func__, NULL);
488
139
    pixs = bil->pixs;
489
139
    ncomps = bil->ncomps;
490
139
    kindex = bil->kindex;
491
139
    kfract = bil->kfract;
492
139
    reduction = bil->reduction;
493
139
    pixac = bil->pixac;
494
139
    lineset = bil->lineset;
495
139
    if (pixaGetCount(pixac) != ncomps)
496
0
        return (PIX *)ERROR_PTR("PBC images do not exist", __func__, NULL);
497
498
139
    if ((pixd = pixCreateTemplate(pixs)) == NULL)
499
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
500
139
    datas = pixGetData(pixs);
501
139
    wpls = pixGetWpl(pixs);
502
139
    datad = pixGetData(pixd);
503
139
    wpld = pixGetWpl(pixd);
504
139
    pixGetDimensions(pixs, &w, &h, NULL);
505
8.98k
    for (i = 0; i < h; i++) {
506
8.84k
        lines = datas + i * wpls;
507
8.84k
        lined = datad + i * wpld;
508
8.84k
        ired = i / reduction;
509
840k
        for (j = 0; j < w; j++) {
510
831k
            jred = j / reduction;
511
831k
            vals = GET_DATA_BYTE(lines, j);
512
831k
            k = kindex[vals];
513
831k
            lowval = GET_DATA_BYTE(lineset[k][ired], jred);
514
831k
            hival = GET_DATA_BYTE(lineset[k + 1][ired], jred);
515
831k
            fract = kfract[vals];
516
831k
            vald = (l_int32)((1.0 - fract) * lowval + fract * hival + 0.5);
517
831k
            SET_DATA_BYTE(lined, j, vald);
518
831k
        }
519
8.84k
    }
520
521
139
    return pixd;
522
139
}
523
524
525
/*!
526
 * \brief   bilateralDestroy()
527
 *
528
 * \param[in,out]   pbil    will be set to null before returning
529
 */
530
static void
531
bilateralDestroy(L_BILATERAL  **pbil)
532
139
{
533
139
l_int32       i;
534
139
L_BILATERAL  *bil;
535
536
139
    if (pbil == NULL) {
537
0
        L_WARNING("ptr address is null!\n", __func__);
538
0
        return;
539
0
    }
540
541
139
    if ((bil = *pbil) == NULL)
542
0
        return;
543
544
139
    pixDestroy(&bil->pixs);
545
139
    pixDestroy(&bil->pixsc);
546
139
    pixaDestroy(&bil->pixac);
547
139
    LEPT_FREE(bil->spatial);
548
139
    LEPT_FREE(bil->range);
549
139
    LEPT_FREE(bil->nc);
550
139
    LEPT_FREE(bil->kindex);
551
139
    LEPT_FREE(bil->kfract);
552
1.52k
    for (i = 0; i < bil->ncomps; i++)
553
1.39k
        LEPT_FREE(bil->lineset[i]);
554
139
    LEPT_FREE(bil->lineset);
555
139
    LEPT_FREE(bil);
556
139
    *pbil = NULL;
557
139
}
558
559
560
/*----------------------------------------------------------------------*
561
 *    Exact implementation of grayscale or color bilateral filtering    *
562
 *----------------------------------------------------------------------*/
563
/*!
564
 * \brief   pixBilateralExact()
565
 *
566
 * \param[in]    pixs          8 bpp gray or 32 bpp rgb
567
 * \param[in]    spatial_kel   gaussian kernel
568
 * \param[in]    range_kel     [optional] 256 x 1, monotonically decreasing
569
 * \return  pixd   8 bpp bilateral filtered image
570
 *
571
 * <pre>
572
 * Notes:
573
 *      (1) The spatial_kel is a conventional smoothing kernel, typically a
574
 *          2-d Gaussian kernel or other block kernel.  It can be either
575
 *          normalized or not, but must be everywhere positive.
576
 *      (2) The range_kel is defined over the absolute value of pixel
577
 *          grayscale differences, and hence must have size 256 x 1.
578
 *          Values in the array represent the multiplying weight for each
579
 *          gray value difference between the target pixel and center of the
580
 *          kernel, and should be monotonically decreasing.
581
 *      (3) If range_kel == NULL, a constant weight is applied regardless
582
 *          of the range value difference.  This degenerates to a regular
583
 *          pixConvolve() with a normalized kernel.
584
 * </pre>
585
 */
586
PIX *
587
pixBilateralExact(PIX       *pixs,
588
                  L_KERNEL  *spatial_kel,
589
                  L_KERNEL  *range_kel)
590
202
{
591
202
l_int32  d;
592
202
PIX     *pixt, *pixr, *pixg, *pixb, *pixd;
593
594
202
    if (!pixs)
595
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
596
202
    if (pixGetColormap(pixs) != NULL)
597
0
        return (PIX *)ERROR_PTR("pixs is cmapped", __func__, NULL);
598
202
    d = pixGetDepth(pixs);
599
202
    if (d != 8 && d != 32)
600
0
        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL);
601
202
    if (!spatial_kel)
602
0
        return (PIX *)ERROR_PTR("spatial_ke not defined", __func__, NULL);
603
604
202
    if (d == 8) {
605
161
        return pixBilateralGrayExact(pixs, spatial_kel, range_kel);
606
161
    } else {  /* d == 32 */
607
41
        pixt = pixGetRGBComponent(pixs, COLOR_RED);
608
41
        pixr = pixBilateralGrayExact(pixt, spatial_kel, range_kel);
609
41
        pixDestroy(&pixt);
610
41
        pixt = pixGetRGBComponent(pixs, COLOR_GREEN);
611
41
        pixg = pixBilateralGrayExact(pixt, spatial_kel, range_kel);
612
41
        pixDestroy(&pixt);
613
41
        pixt = pixGetRGBComponent(pixs, COLOR_BLUE);
614
41
        pixb = pixBilateralGrayExact(pixt, spatial_kel, range_kel);
615
41
        pixDestroy(&pixt);
616
41
        pixd = pixCreateRGBImage(pixr, pixg, pixb);
617
618
41
        pixDestroy(&pixr);
619
41
        pixDestroy(&pixg);
620
41
        pixDestroy(&pixb);
621
41
        return pixd;
622
41
    }
623
202
}
624
625
626
/*!
627
 * \brief   pixBilateralGrayExact()
628
 *
629
 * \param[in]    pixs          8 bpp gray
630
 * \param[in]    spatial_kel   gaussian kernel
631
 * \param[in]    range_kel     [optional] 256 x 1, monotonically decreasing
632
 * \return  pixd   8 bpp bilateral filtered image
633
 *
634
 * <pre>
635
 * Notes:
636
 *      (1) See pixBilateralExact().
637
 * </pre>
638
 */
639
PIX *
640
pixBilateralGrayExact(PIX       *pixs,
641
                      L_KERNEL  *spatial_kel,
642
                      L_KERNEL  *range_kel)
643
284
{
644
284
l_int32    i, j, id, jd, k, m, w, h, d, sx, sy, cx, cy, wplt, wpld;
645
284
l_int32    val, center_val;
646
284
l_uint32  *datat, *datad, *linet, *lined;
647
284
l_float32  sum, weight_sum, weight;
648
284
L_KERNEL  *keli;
649
284
PIX       *pixt, *pixd;
650
651
284
    if (!pixs)
652
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
653
284
    if (pixGetDepth(pixs) != 8)
654
0
        return (PIX *)ERROR_PTR("pixs must be gray", __func__, NULL);
655
284
    pixGetDimensions(pixs, &w, &h, &d);
656
284
    if (!spatial_kel)
657
0
        return (PIX *)ERROR_PTR("spatial kel not defined", __func__, NULL);
658
284
    kernelGetParameters(spatial_kel, &sy, &sx, NULL, NULL);
659
284
    if (w < 2 * sx + 1 || h < 2 * sy + 1) {
660
267
        L_WARNING("w = %d < 2 * sx + 1 = %d, or h = %d < 2 * sy + 1 = %d; "
661
267
                  "returning copy\n", __func__, w, 2 * sx + 1, h, 2 * sy + 1);
662
267
        return pixCopy(NULL, pixs);
663
267
    }
664
17
    if (!range_kel)
665
0
        return pixConvolve(pixs, spatial_kel, 8, 1);
666
17
    if (range_kel->sx != 256 || range_kel->sy != 1)
667
0
        return (PIX *)ERROR_PTR("range kel not {256 x 1", __func__, NULL);
668
669
17
    keli = kernelInvert(spatial_kel);
670
17
    kernelGetParameters(keli, &sy, &sx, &cy, &cx);
671
17
    if ((pixt = pixAddMirroredBorder(pixs, cx, sx - cx, cy, sy - cy)) == NULL) {
672
0
        kernelDestroy(&keli);
673
0
        return (PIX *)ERROR_PTR("pixt not made", __func__, NULL);
674
0
    }
675
676
17
    pixd = pixCreate(w, h, 8);
677
17
    datat = pixGetData(pixt);
678
17
    datad = pixGetData(pixd);
679
17
    wplt = pixGetWpl(pixt);
680
17
    wpld = pixGetWpl(pixd);
681
2.70k
    for (i = 0, id = 0; id < h; i++, id++) {
682
2.68k
        lined = datad + id * wpld;
683
475k
        for (j = 0, jd = 0; jd < w; j++, jd++) {
684
472k
            center_val = GET_DATA_BYTE(datat + (i + cy) * wplt, j + cx);
685
472k
            weight_sum = 0.0;
686
472k
            sum = 0.0;
687
19.8M
            for (k = 0; k < sy; k++) {
688
19.3M
                linet = datat + (i + k) * wplt;
689
813M
                for (m = 0; m < sx; m++) {
690
793M
                    val = GET_DATA_BYTE(linet, j + m);
691
793M
                    weight = keli->data[k][m] *
692
793M
                        range_kel->data[0][L_ABS(center_val - val)];
693
793M
                    weight_sum += weight;
694
793M
                    sum += val * weight;
695
793M
                }
696
19.3M
            }
697
472k
            SET_DATA_BYTE(lined, jd, (l_int32)(sum / weight_sum + 0.5));
698
472k
        }
699
2.68k
    }
700
701
17
    kernelDestroy(&keli);
702
17
    pixDestroy(&pixt);
703
17
    return pixd;
704
17
}
705
706
707
/*!
708
 * \brief   pixBlockBilateralExact()
709
 *
710
 * \param[in]    pixs             8 bpp gray or 32 bpp rgb
711
 * \param[in]    spatial_stdev    must be > 0.0
712
 * \param[in]    range_stdev      must be > 0.0
713
 * \return  pixd   8 bpp or 32 bpp bilateral filtered image
714
 *
715
 * <pre>
716
 * Notes:
717
 *      (1) See pixBilateralExact().  This provides an interface using
718
 *          the standard deviations of the spatial and range filters.
719
 *      (2) The convolution window halfwidth is 2 * spatial_stdev,
720
 *          and the square filter size is 4 * spatial_stdev + 1.
721
 *          The kernel captures 95% of total energy.  This is compensated
722
 *          by normalization.
723
 *      (3) The range_stdev is analogous to spatial_halfwidth in the
724
 *          grayscale domain [0...255], and determines how much damping of the
725
 *          smoothing operation is applied across edges.  The larger this
726
 *          value is, the smaller the damping.  The smaller the value, the
727
 *          more edge details are preserved.  These approximations are useful
728
 *          for deciding the appropriate cutoff.
729
 *              kernel[1 * stdev] ~= 0.6  * kernel[0]
730
 *              kernel[2 * stdev] ~= 0.14 * kernel[0]
731
 *              kernel[3 * stdev] ~= 0.01 * kernel[0]
732
 *          If range_stdev is infinite there is no damping, and this
733
 *          becomes a conventional gaussian smoothing.
734
 *          This value does not affect the run time.
735
 *      (4) If range_stdev is negative or zero, the range kernel is
736
 *          ignored and this degenerates to a straight gaussian convolution.
737
 *      (5) This is very slow for large spatial filters.  The time
738
 *          on a 3GHz pentium is roughly
739
 *             T = 1.2 * 10^-8 * (A * sh^2)  sec
740
 *          where A = # of pixels, sh = spatial halfwidth of filter.
741
 * </pre>
742
 */
743
PIX*
744
pixBlockBilateralExact(PIX       *pixs,
745
                       l_float32  spatial_stdev,
746
                       l_float32  range_stdev)
747
284
{
748
284
l_int32    d, halfwidth;
749
284
L_KERNEL  *spatial_kel, *range_kel;
750
284
PIX       *pixd;
751
752
284
    if (!pixs)
753
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
754
284
    d = pixGetDepth(pixs);
755
284
    if (d != 8 && d != 32)
756
67
        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL);
757
217
    if (pixGetColormap(pixs) != NULL)
758
15
        return (PIX *)ERROR_PTR("pixs is cmapped", __func__, NULL);
759
202
    if (spatial_stdev <= 0.0)
760
0
        return (PIX *)ERROR_PTR("invalid spatial stdev", __func__, NULL);
761
202
    if (range_stdev <= 0.0)
762
0
        return (PIX *)ERROR_PTR("invalid range stdev", __func__, NULL);
763
764
202
    halfwidth = 2 * spatial_stdev;
765
202
    spatial_kel = makeGaussianKernel(halfwidth, halfwidth, spatial_stdev, 1.0);
766
202
    range_kel = makeRangeKernel(range_stdev);
767
202
    pixd = pixBilateralExact(pixs, spatial_kel, range_kel);
768
202
    kernelDestroy(&spatial_kel);
769
202
    kernelDestroy(&range_kel);
770
202
    return pixd;
771
202
}
772
773
774
/*----------------------------------------------------------------------*
775
 *                         Kernel helper function                       *
776
 *----------------------------------------------------------------------*/
777
/*!
778
 * \brief   makeRangeKernel()
779
 *
780
 * \param[in]    range_stdev   must be > 0.0
781
 * \return  kel, or NULL on error
782
 *
783
 * <pre>
784
 * Notes:
785
 *      (1) Creates a one-sided Gaussian kernel with the given
786
 *          standard deviation.  At grayscale difference of one stdev,
787
 *          the kernel falls to 0.6, and to 0.01 at three stdev.
788
 *      (2) A typical input number might be 20.  Then pixels whose
789
 *          value differs by 60 from the center pixel have their
790
 *          weight in the convolution reduced by a factor of about 0.01.
791
 * </pre>
792
 */
793
L_KERNEL *
794
makeRangeKernel(l_float32  range_stdev)
795
202
{
796
202
l_int32    x;
797
202
l_float32  val, denom;
798
202
L_KERNEL  *kel;
799
800
202
    if (range_stdev <= 0.0)
801
0
        return (L_KERNEL *)ERROR_PTR("invalid stdev <= 0", __func__, NULL);
802
803
202
    denom = 2. * range_stdev * range_stdev;
804
202
    if ((kel = kernelCreate(1, 256)) == NULL)
805
0
        return (L_KERNEL *)ERROR_PTR("kel not made", __func__, NULL);
806
202
    kernelSetOrigin(kel, 0, 0);
807
51.9k
    for (x = 0; x < 256; x++) {
808
51.7k
        val = expf(-(l_float32)(x * x) / denom);
809
51.7k
        kernelSetElement(kel, 0, x, val);
810
51.7k
    }
811
202
    return kel;
812
202
}