comparison mupdf-source/thirdparty/leptonica/src/bilateral.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 /*====================================================================*
2 - Copyright (C) 2001 Leptonica. All rights reserved.
3 -
4 - Redistribution and use in source and binary forms, with or without
5 - modification, are permitted provided that the following conditions
6 - are met:
7 - 1. Redistributions of source code must retain the above copyright
8 - notice, this list of conditions and the following disclaimer.
9 - 2. Redistributions in binary form must reproduce the above
10 - copyright notice, this list of conditions and the following
11 - disclaimer in the documentation and/or other materials
12 - provided with the distribution.
13 -
14 - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17 - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
18 - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 *====================================================================*/
26
27 /*!
28 * \file 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 {
163 l_int32 w, h, d, filtersize;
164 l_float32 sstdev; /* scaled spatial stdev */
165 PIX *pixt, *pixr, *pixg, *pixb, *pixd;
166
167 if (!pixs || pixGetColormap(pixs))
168 return (PIX *)ERROR_PTR("pixs not defined or cmapped", __func__, NULL);
169 pixGetDimensions(pixs, &w, &h, &d);
170 if (d != 8 && d != 32)
171 return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL);
172 if (reduction != 1 && reduction != 2 && reduction != 4)
173 return (PIX *)ERROR_PTR("reduction invalid", __func__, NULL);
174 filtersize = (l_int32)(2.0 * spatial_stdev + 1.0 + 0.5);
175 if (w < 2 * filtersize || h < 2 * filtersize) {
176 L_WARNING("w = %d, h = %d; w or h < 2 * filtersize = %d; "
177 "returning copy\n", __func__, w, h, 2 * filtersize);
178 return pixCopy(NULL, pixs);
179 }
180 sstdev = spatial_stdev / (l_float32)reduction; /* reduced spat. stdev */
181 if (sstdev < 0.5)
182 return (PIX *)ERROR_PTR("sstdev < 0.5", __func__, NULL);
183 if (range_stdev <= 5.0)
184 return (PIX *)ERROR_PTR("range_stdev <= 5.0", __func__, NULL);
185 if (ncomps < 4 || ncomps > 30)
186 return (PIX *)ERROR_PTR("ncomps not in [4 ... 30]", __func__, NULL);
187 if (ncomps * range_stdev < 100.0)
188 return (PIX *)ERROR_PTR("ncomps * range_stdev < 100.0", __func__, NULL);
189
190 if (d == 8)
191 return pixBilateralGray(pixs, spatial_stdev, range_stdev,
192 ncomps, reduction);
193
194 pixt = pixGetRGBComponent(pixs, COLOR_RED);
195 pixr = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps,
196 reduction);
197 pixDestroy(&pixt);
198 pixt = pixGetRGBComponent(pixs, COLOR_GREEN);
199 pixg = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps,
200 reduction);
201 pixDestroy(&pixt);
202 pixt = pixGetRGBComponent(pixs, COLOR_BLUE);
203 pixb = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps,
204 reduction);
205 pixDestroy(&pixt);
206 pixd = pixCreateRGBImage(pixr, pixg, pixb);
207 pixDestroy(&pixr);
208 pixDestroy(&pixg);
209 pixDestroy(&pixb);
210 return pixd;
211 }
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 {
238 l_float32 sstdev; /* scaled spatial stdev */
239 PIX *pixd;
240 L_BILATERAL *bil;
241
242 if (!pixs || pixGetColormap(pixs))
243 return (PIX *)ERROR_PTR("pixs not defined or cmapped", __func__, NULL);
244 if (pixGetDepth(pixs) != 8)
245 return (PIX *)ERROR_PTR("pixs not 8 bpp gray", __func__, NULL);
246 if (reduction != 1 && reduction != 2 && reduction != 4)
247 return (PIX *)ERROR_PTR("reduction invalid", __func__, NULL);
248 sstdev = spatial_stdev / (l_float32)reduction; /* reduced spat. stdev */
249 if (sstdev < 0.5)
250 return (PIX *)ERROR_PTR("sstdev < 0.5", __func__, NULL);
251 if (range_stdev <= 5.0)
252 return (PIX *)ERROR_PTR("range_stdev <= 5.0", __func__, NULL);
253 if (ncomps < 4 || ncomps > 30)
254 return (PIX *)ERROR_PTR("ncomps not in [4 ... 30]", __func__, NULL);
255 if (ncomps * range_stdev < 100.0)
256 return (PIX *)ERROR_PTR("ncomps * range_stdev < 100.0", __func__, NULL);
257
258 bil = bilateralCreate(pixs, spatial_stdev, range_stdev, ncomps, reduction);
259 if (!bil) return (PIX *)ERROR_PTR("bil not made", __func__, NULL);
260 pixd = bilateralApply(bil);
261 bilateralDestroy(&bil);
262 return pixd;
263 }
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 {
297 l_int32 w, ws, wd, h, hs, hd, i, j, k, index;
298 l_int32 border, minval, maxval, spatial_size;
299 l_int32 halfwidth, wpls, wplt, wpld, kval, nval, dval;
300 l_float32 sstdev, fval1, fval2, denom, sum, norm, kern;
301 l_int32 *nc, *kindex;
302 l_float32 *kfract, *range, *spatial;
303 l_uint32 *datas, *datat, *datad, *lines, *linet, *lined;
304 L_BILATERAL *bil;
305 PIX *pix1, *pix2, *pixt, *pixsc, *pixd;
306 PIXA *pixac;
307
308 if (reduction == 1) {
309 pix1 = pixClone(pixs);
310 } else if (reduction == 2) {
311 pix1 = pixScaleAreaMap2(pixs);
312 } else { /* reduction == 4) */
313 pix2 = pixScaleAreaMap2(pixs);
314 pix1 = pixScaleAreaMap2(pix2);
315 pixDestroy(&pix2);
316 }
317 if (!pix1)
318 return (L_BILATERAL *)ERROR_PTR("pix1 not made", __func__, NULL);
319
320 sstdev = spatial_stdev / (l_float32)reduction; /* reduced spat. stdev */
321 border = (l_int32)(2 * sstdev + 1);
322 pixsc = pixAddMirroredBorder(pix1, border, border, border, border);
323 pixGetExtremeValue(pix1, 1, L_SELECT_MIN, NULL, NULL, NULL, &minval);
324 pixGetExtremeValue(pix1, 1, L_SELECT_MAX, NULL, NULL, NULL, &maxval);
325 pixDestroy(&pix1);
326 if (!pixsc)
327 return (L_BILATERAL *)ERROR_PTR("pixsc not made", __func__, NULL);
328
329 bil = (L_BILATERAL *)LEPT_CALLOC(1, sizeof(L_BILATERAL));
330 bil->spatial_stdev = sstdev;
331 bil->range_stdev = range_stdev;
332 bil->reduction = reduction;
333 bil->ncomps = ncomps;
334 bil->minval = minval;
335 bil->maxval = maxval;
336 bil->pixsc = pixsc;
337 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 nc = (l_int32 *)LEPT_CALLOC(ncomps, sizeof(l_int32));
347 for (i = 0; i < ncomps; i++)
348 nc[i] = minval + i * (maxval - minval) / (ncomps - 1);
349 bil->nc = nc;
350
351 /* kindex maps from intensity I(x) to the lower k index for J(k,x) */
352 kindex = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
353 for (i = minval, k = 0; i <= maxval && k < ncomps - 1; k++) {
354 fval2 = nc[k + 1];
355 while (i < fval2) {
356 kindex[i] = k;
357 i++;
358 }
359 }
360 kindex[maxval] = ncomps - 2;
361 bil->kindex = kindex;
362
363 /* kfract maps from intensity I(x) to the fraction of J(k+1,x) used */
364 kfract = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32)); /* from lower */
365 for (i = minval, k = 0; i <= maxval && k < ncomps - 1; k++) {
366 fval1 = nc[k];
367 fval2 = nc[k + 1];
368 while (i < fval2) {
369 kfract[i] = (l_float32)(i - fval1) / (l_float32)(fval2 - fval1);
370 i++;
371 }
372 }
373 kfract[maxval] = 1.0;
374 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 spatial_size = 2 * sstdev + 1; /* same as the added border */
388 spatial = (l_float32 *)LEPT_CALLOC(spatial_size, sizeof(l_float32));
389 denom = 2. * sstdev * sstdev;
390 for (i = 0; i < spatial_size; i++)
391 spatial[i] = expf(-(l_float32)(i * i) / denom);
392 bil->spatial = spatial;
393
394 range = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32));
395 denom = 2. * range_stdev * range_stdev;
396 for (i = 0; i < 256; i++)
397 range[i] = expf(-(l_float32)(i * i) / denom);
398 bil->range = range;
399
400 /* -------------------------------------------------------------------- *
401 * Generate principal bilateral component images *
402 * -------------------------------------------------------------------- */
403 pixac = pixaCreate(ncomps);
404 pixGetDimensions(pixsc, &ws, &hs, NULL);
405 datas = pixGetData(pixsc);
406 wpls = pixGetWpl(pixsc);
407 pixGetDimensions(pixs, &w, &h, NULL);
408 wd = (w + reduction - 1) / reduction;
409 hd = (h + reduction - 1) / reduction;
410 halfwidth = (l_int32)(2.0 * sstdev);
411 for (index = 0; index < ncomps; index++) {
412 pixt = pixCopy(NULL, pixsc);
413 datat = pixGetData(pixt);
414 wplt = pixGetWpl(pixt);
415 kval = nc[index];
416 /* Separable convolutions: horizontal first */
417 for (i = 0; i < hd; i++) {
418 lines = datas + (border + i) * wpls;
419 linet = datat + (border + i) * wplt;
420 for (j = 0; j < wd; j++) {
421 sum = 0.0;
422 norm = 0.0;
423 for (k = -halfwidth; k <= halfwidth; k++) {
424 nval = GET_DATA_BYTE(lines, border + j + k);
425 kern = spatial[L_ABS(k)] * range[L_ABS(kval - nval)];
426 sum += kern * nval;
427 norm += kern;
428 }
429 if (norm > 0.0) {
430 dval = (l_int32)((sum / norm) + 0.5);
431 SET_DATA_BYTE(linet, border + j, dval);
432 }
433 }
434 }
435 /* Vertical convolution */
436 pixd = pixCreate(wd, hd, 8);
437 datad = pixGetData(pixd);
438 wpld = pixGetWpl(pixd);
439 for (i = 0; i < hd; i++) {
440 linet = datat + (border + i) * wplt;
441 lined = datad + i * wpld;
442 for (j = 0; j < wd; j++) {
443 sum = 0.0;
444 norm = 0.0;
445 for (k = -halfwidth; k <= halfwidth; k++) {
446 nval = GET_DATA_BYTE(linet + k * wplt, border + j);
447 kern = spatial[L_ABS(k)] * range[L_ABS(kval - nval)];
448 sum += kern * nval;
449 norm += kern;
450 }
451 if (norm > 0.0)
452 dval = (l_int32)((sum / norm) + 0.5);
453 else
454 dval = GET_DATA_BYTE(linet, border + j);
455 SET_DATA_BYTE(lined, j, dval);
456 }
457 }
458 pixDestroy(&pixt);
459 pixaAddPix(pixac, pixd, L_INSERT);
460 }
461 bil->pixac = pixac;
462 bil->lineset = (l_uint32 ***)pixaGetLinePtrs(pixac, NULL);
463 return bil;
464 }
465
466
467 /*!
468 * \brief bilateralApply()
469 *
470 * \param[in] bil
471 * \return pixd
472 */
473 static PIX *
474 bilateralApply(L_BILATERAL *bil)
475 {
476 l_int32 i, j, k, ired, jred, w, h, wpls, wpld, ncomps, reduction;
477 l_int32 vals, vald, lowval, hival;
478 l_int32 *kindex;
479 l_float32 fract;
480 l_float32 *kfract;
481 l_uint32 *lines, *lined, *datas, *datad;
482 l_uint32 ***lineset = NULL; /* for set of PBC */
483 PIX *pixs, *pixd;
484 PIXA *pixac;
485
486 if (!bil)
487 return (PIX *)ERROR_PTR("bil not defined", __func__, NULL);
488 pixs = bil->pixs;
489 ncomps = bil->ncomps;
490 kindex = bil->kindex;
491 kfract = bil->kfract;
492 reduction = bil->reduction;
493 pixac = bil->pixac;
494 lineset = bil->lineset;
495 if (pixaGetCount(pixac) != ncomps)
496 return (PIX *)ERROR_PTR("PBC images do not exist", __func__, NULL);
497
498 if ((pixd = pixCreateTemplate(pixs)) == NULL)
499 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
500 datas = pixGetData(pixs);
501 wpls = pixGetWpl(pixs);
502 datad = pixGetData(pixd);
503 wpld = pixGetWpl(pixd);
504 pixGetDimensions(pixs, &w, &h, NULL);
505 for (i = 0; i < h; i++) {
506 lines = datas + i * wpls;
507 lined = datad + i * wpld;
508 ired = i / reduction;
509 for (j = 0; j < w; j++) {
510 jred = j / reduction;
511 vals = GET_DATA_BYTE(lines, j);
512 k = kindex[vals];
513 lowval = GET_DATA_BYTE(lineset[k][ired], jred);
514 hival = GET_DATA_BYTE(lineset[k + 1][ired], jred);
515 fract = kfract[vals];
516 vald = (l_int32)((1.0 - fract) * lowval + fract * hival + 0.5);
517 SET_DATA_BYTE(lined, j, vald);
518 }
519 }
520
521 return pixd;
522 }
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 {
533 l_int32 i;
534 L_BILATERAL *bil;
535
536 if (pbil == NULL) {
537 L_WARNING("ptr address is null!\n", __func__);
538 return;
539 }
540
541 if ((bil = *pbil) == NULL)
542 return;
543
544 pixDestroy(&bil->pixs);
545 pixDestroy(&bil->pixsc);
546 pixaDestroy(&bil->pixac);
547 LEPT_FREE(bil->spatial);
548 LEPT_FREE(bil->range);
549 LEPT_FREE(bil->nc);
550 LEPT_FREE(bil->kindex);
551 LEPT_FREE(bil->kfract);
552 for (i = 0; i < bil->ncomps; i++)
553 LEPT_FREE(bil->lineset[i]);
554 LEPT_FREE(bil->lineset);
555 LEPT_FREE(bil);
556 *pbil = NULL;
557 }
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 {
591 l_int32 d;
592 PIX *pixt, *pixr, *pixg, *pixb, *pixd;
593
594 if (!pixs)
595 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
596 if (pixGetColormap(pixs) != NULL)
597 return (PIX *)ERROR_PTR("pixs is cmapped", __func__, NULL);
598 d = pixGetDepth(pixs);
599 if (d != 8 && d != 32)
600 return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL);
601 if (!spatial_kel)
602 return (PIX *)ERROR_PTR("spatial_ke not defined", __func__, NULL);
603
604 if (d == 8) {
605 return pixBilateralGrayExact(pixs, spatial_kel, range_kel);
606 } else { /* d == 32 */
607 pixt = pixGetRGBComponent(pixs, COLOR_RED);
608 pixr = pixBilateralGrayExact(pixt, spatial_kel, range_kel);
609 pixDestroy(&pixt);
610 pixt = pixGetRGBComponent(pixs, COLOR_GREEN);
611 pixg = pixBilateralGrayExact(pixt, spatial_kel, range_kel);
612 pixDestroy(&pixt);
613 pixt = pixGetRGBComponent(pixs, COLOR_BLUE);
614 pixb = pixBilateralGrayExact(pixt, spatial_kel, range_kel);
615 pixDestroy(&pixt);
616 pixd = pixCreateRGBImage(pixr, pixg, pixb);
617
618 pixDestroy(&pixr);
619 pixDestroy(&pixg);
620 pixDestroy(&pixb);
621 return pixd;
622 }
623 }
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 {
644 l_int32 i, j, id, jd, k, m, w, h, d, sx, sy, cx, cy, wplt, wpld;
645 l_int32 val, center_val;
646 l_uint32 *datat, *datad, *linet, *lined;
647 l_float32 sum, weight_sum, weight;
648 L_KERNEL *keli;
649 PIX *pixt, *pixd;
650
651 if (!pixs)
652 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
653 if (pixGetDepth(pixs) != 8)
654 return (PIX *)ERROR_PTR("pixs must be gray", __func__, NULL);
655 pixGetDimensions(pixs, &w, &h, &d);
656 if (!spatial_kel)
657 return (PIX *)ERROR_PTR("spatial kel not defined", __func__, NULL);
658 kernelGetParameters(spatial_kel, &sy, &sx, NULL, NULL);
659 if (w < 2 * sx + 1 || h < 2 * sy + 1) {
660 L_WARNING("w = %d < 2 * sx + 1 = %d, or h = %d < 2 * sy + 1 = %d; "
661 "returning copy\n", __func__, w, 2 * sx + 1, h, 2 * sy + 1);
662 return pixCopy(NULL, pixs);
663 }
664 if (!range_kel)
665 return pixConvolve(pixs, spatial_kel, 8, 1);
666 if (range_kel->sx != 256 || range_kel->sy != 1)
667 return (PIX *)ERROR_PTR("range kel not {256 x 1", __func__, NULL);
668
669 keli = kernelInvert(spatial_kel);
670 kernelGetParameters(keli, &sy, &sx, &cy, &cx);
671 if ((pixt = pixAddMirroredBorder(pixs, cx, sx - cx, cy, sy - cy)) == NULL) {
672 kernelDestroy(&keli);
673 return (PIX *)ERROR_PTR("pixt not made", __func__, NULL);
674 }
675
676 pixd = pixCreate(w, h, 8);
677 datat = pixGetData(pixt);
678 datad = pixGetData(pixd);
679 wplt = pixGetWpl(pixt);
680 wpld = pixGetWpl(pixd);
681 for (i = 0, id = 0; id < h; i++, id++) {
682 lined = datad + id * wpld;
683 for (j = 0, jd = 0; jd < w; j++, jd++) {
684 center_val = GET_DATA_BYTE(datat + (i + cy) * wplt, j + cx);
685 weight_sum = 0.0;
686 sum = 0.0;
687 for (k = 0; k < sy; k++) {
688 linet = datat + (i + k) * wplt;
689 for (m = 0; m < sx; m++) {
690 val = GET_DATA_BYTE(linet, j + m);
691 weight = keli->data[k][m] *
692 range_kel->data[0][L_ABS(center_val - val)];
693 weight_sum += weight;
694 sum += val * weight;
695 }
696 }
697 SET_DATA_BYTE(lined, jd, (l_int32)(sum / weight_sum + 0.5));
698 }
699 }
700
701 kernelDestroy(&keli);
702 pixDestroy(&pixt);
703 return pixd;
704 }
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 {
748 l_int32 d, halfwidth;
749 L_KERNEL *spatial_kel, *range_kel;
750 PIX *pixd;
751
752 if (!pixs)
753 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
754 d = pixGetDepth(pixs);
755 if (d != 8 && d != 32)
756 return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", __func__, NULL);
757 if (pixGetColormap(pixs) != NULL)
758 return (PIX *)ERROR_PTR("pixs is cmapped", __func__, NULL);
759 if (spatial_stdev <= 0.0)
760 return (PIX *)ERROR_PTR("invalid spatial stdev", __func__, NULL);
761 if (range_stdev <= 0.0)
762 return (PIX *)ERROR_PTR("invalid range stdev", __func__, NULL);
763
764 halfwidth = 2 * spatial_stdev;
765 spatial_kel = makeGaussianKernel(halfwidth, halfwidth, spatial_stdev, 1.0);
766 range_kel = makeRangeKernel(range_stdev);
767 pixd = pixBilateralExact(pixs, spatial_kel, range_kel);
768 kernelDestroy(&spatial_kel);
769 kernelDestroy(&range_kel);
770 return pixd;
771 }
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 {
796 l_int32 x;
797 l_float32 val, denom;
798 L_KERNEL *kel;
799
800 if (range_stdev <= 0.0)
801 return (L_KERNEL *)ERROR_PTR("invalid stdev <= 0", __func__, NULL);
802
803 denom = 2. * range_stdev * range_stdev;
804 if ((kel = kernelCreate(1, 256)) == NULL)
805 return (L_KERNEL *)ERROR_PTR("kel not made", __func__, NULL);
806 kernelSetOrigin(kel, 0, 0);
807 for (x = 0; x < 256; x++) {
808 val = expf(-(l_float32)(x * x) / denom);
809 kernelSetElement(kel, 0, x, val);
810 }
811 return kel;
812 }