1 /**
2 * @file fastbilateral.cpp
3 * @brief Fast bilateral filtering
4 *
5 *
6 * This file is a part of PFSTMO package.
7 * ----------------------------------------------------------------------
8 * Copyright (C) 2003,2004 Grzegorz Krawczyk
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 * ----------------------------------------------------------------------
24 *
25 * @author Grzegorz Krawczyk, <krawczyk@mpi-sb.mpg.de>
26 *
27 * $Id: fastbilateral.cpp,v 1.5 2008/09/09 18:10:49 rafm Exp $
28 */
29
30 #include <config.h>
31
32 #include <iostream>
33
34 #include <fftw3.h>
35 #include <math.h>
36
37 #include "pfstmo.h"
38
39
40 using namespace std;
41
42
max(float a,float b)43 inline float max( float a, float b )
44 {
45 return a > b ? a : b;
46 }
47
min(float a,float b)48 inline float min( float a, float b )
49 {
50 return a < b ? a : b;
51 }
52
53
54 // TODO: use spatial convolution rather than FFT, should be much
55 // faster except for a very large kernels
56 class GaussianBlur
57 {
58 float* source;
59 fftwf_complex* freq;
60 fftwf_plan fplan_fw;
61 fftwf_plan fplan_in;
62
63 float sigma;
64
65 public:
GaussianBlur(int nx,int ny,float sigma)66 GaussianBlur( int nx, int ny, float sigma ) : sigma( sigma )
67 {
68 int ox = nx;
69 int oy = ny/2 + 1; // saves half of the data
70 const int osize = ox * oy;
71 source = (float*)fftwf_malloc(sizeof(float) * nx * 2 * (ny/2+1) );
72 freq = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * osize);
73 // if( source == NULL || freq == NULL )
74 //TODO: throw exception
75 fplan_fw = fftwf_plan_dft_r2c_2d(nx, ny, source, freq, FFTW_ESTIMATE);
76 fplan_in = fftwf_plan_dft_c2r_2d(nx, ny, freq, source, FFTW_ESTIMATE);
77 }
78
79
blur(const pfstmo::Array2D * I,pfstmo::Array2D * J)80 void blur( const pfstmo::Array2D *I, pfstmo::Array2D *J )
81 {
82 int i,x,y;
83
84 int nx = I->getCols();
85 int ny = I->getRows();
86 int nsize = nx * ny;
87
88 int ox = nx;
89 int oy = ny/2 + 1; // saves half of the data
90 int osize = ox * oy;
91
92 for( y=0 ; y<ny ; y++ )
93 for( x=0 ; x<nx ; x++ )
94 source[x*ny+y] = (*I)(x,y);
95
96 fftwf_execute(fplan_fw);
97
98 // filter
99 float sig = nx/(2.0f*sigma);
100 float sig2 = 2.0f*sig*sig;
101 for( x=0 ; x<ox/2 ; x++ )
102 for( y=0 ; y<oy ; y++ )
103 {
104 float d2 = x*x + y*y;
105 float kernel = exp( -d2 / sig2 );
106
107 freq[x*oy+y][0] *= kernel;
108 freq[x*oy+y][1] *= kernel;
109 freq[(ox-x-1)*oy+y][0] *= kernel;
110 freq[(ox-x-1)*oy+y][1] *= kernel;
111 }
112
113 fftwf_execute(fplan_in);
114
115 for( x=0 ; x<nx ; x++ )
116 for( y=0 ; y<ny ; y++ )
117 (*J)(x,y) = source[x*ny+y] / nsize;
118 }
119
~GaussianBlur()120 ~GaussianBlur()
121 {
122 fftwf_free(source);
123 fftwf_free(freq);
124 fftwf_destroy_plan(fplan_fw);
125 fftwf_destroy_plan(fplan_in);
126 }
127
128
129 };
130
131
132 // According to the original paper, downsampling can be used to speed
133 // up computation. However, downsampling cannot be mathematically
134 // justified and introduces large errors (mostly excessive bluring) in
135 // the result of the bilateral filter. Therefore, in this
136 // implementation, downsampling is disabled.
137
138 #if 0
139 /**
140 * @brief upsampling and downsampling
141 *
142 * original code from pfssize
143 */
144 void upsampleArray( const pfstmo::Array2D *in, pfstmo::Array2D *out )
145 {
146 float dx = (float)in->getCols() / (float)out->getCols();
147 float dy = (float)in->getRows() / (float)out->getRows();
148
149 float pad;
150
151 float filterSamplingX = max( modff( dx, &pad ), 0.01f );
152 float filterSamplingY = max( modff( dy, &pad ), 0.01f );
153
154 const int outRows = out->getRows();
155 const int outCols = out->getCols();
156
157 const float inRows = in->getRows();
158 const float inCols = in->getCols();
159
160 const float filterSize = 1;
161
162 float sx, sy;
163 int x, y;
164 for( y = 0, sy = -0.5 + dy/2; y < outRows; y++, sy += dy )
165 for( x = 0, sx = -0.5 + dx/2; x < outCols; x++, sx += dx ) {
166
167 float pixVal = 0;
168 float weight = 0;
169
170 for( float ix = max( 0, ceilf( sx-filterSize ) ); ix <= min( floorf(sx+filterSize), inCols-1 ); ix++ )
171 for( float iy = max( 0, ceilf( sy-filterSize ) ); iy <= min( floorf( sy+filterSize), inRows-1 ); iy++ ) {
172 float fx = fabs( sx - ix );
173 float fy = fabs( sy - iy );
174
175 const float fval = (1.0f-fx)*(1.0f-fy);
176
177 pixVal += (*in)( (int)ix, (int)iy ) * fval;
178 weight += fval;
179 }
180
181 if( weight == 0 ) {
182 fprintf( stderr, "%g %g %g %g\n", sx, sy, dx, dy );
183 }
184 // assert( weight != 0 );
185 (*out)(x,y) = pixVal / weight;
186
187 }
188 }
189
190 void downsampleArray( const pfstmo::Array2D *in, pfstmo::Array2D *out )
191 {
192 const float inRows = in->getRows();
193 const float inCols = in->getCols();
194
195 const int outRows = out->getRows();
196 const int outCols = out->getCols();
197
198 const float dx = (float)in->getCols() / (float)out->getCols();
199 const float dy = (float)in->getRows() / (float)out->getRows();
200
201 const float filterSize = 0.5;
202
203 float sx, sy;
204 int x, y;
205
206 for( y = 0, sy = dy/2-0.5; y < outRows; y++, sy += dy )
207 for( x = 0, sx = dx/2-0.5; x < outCols; x++, sx += dx ) {
208
209 float pixVal = 0;
210 float w = 0;
211 for( float ix = max( 0, ceilf( sx-dx*filterSize ) ); ix <= min( floorf( sx+dx*filterSize ), inCols-1 ); ix++ )
212 for( float iy = max( 0, ceilf( sy-dx*filterSize ) ); iy <= min( floorf( sy+dx*filterSize), inRows-1 ); iy++ ) {
213 pixVal += (*in)( (int)ix, (int)iy );
214 w += 1;
215 }
216 (*out)(x,y) = pixVal/w;
217 }
218 }
219
220 #endif
221
222 /*
223 Pseudocode from the paper:
224
225 PiecewiseBilateral (Image I, spatial kernel fs , intensity influence gr )
226 J=0 // set the output to zero
227 for j=0..NB SEGMENTS
228 ij= minI+j.*(max(I)-min(I))/NB SEGMENTS
229 Gj=gr (I - ij ) // evaluate gr at each pixel
230 Kj=Gj x fs // normalization factor
231 Hj=Gj .* I // compute H for each pixel
232 Hj=Hj x fs
233 Jj=Hj ./ Kj // normalize
234 J=J+Jj .* InterpolationWeight(I, ij )
235 */
236
fastBilateralFilter(const pfstmo::Array2D * I,pfstmo::Array2D * J,float sigma_s,float sigma_r,int downsample,pfstmo_progress_callback progress_cb)237 void fastBilateralFilter( const pfstmo::Array2D *I,
238 pfstmo::Array2D *J, float sigma_s, float sigma_r, int downsample,
239 pfstmo_progress_callback progress_cb )
240 {
241 int i;
242 int w = I->getCols();
243 int h = I->getRows();
244 int size = w * h;
245
246 // find range of values in the input array
247 float maxI = (*I)(0);
248 float minI = (*I)(0);
249 for(i=0 ; i<size ; i++)
250 {
251 float v = (*I)(i);
252 if( unlikely( v>maxI ) ) maxI = v;
253 if( unlikely( v<minI ) ) minI = v;
254 (*J)(i) = 0.0f; // zero output
255 }
256
257 pfstmo::Array2D* JJ;
258 // if( downsample != 1 )
259 // JJ = new pfstmo::Array2D(w,h);
260
261 // w /= downsample;
262 // h /= downsample;
263
264 int sizeZ = w*h;
265 // pfstmo::Array2D* Iz = new pfstmo::Array2D(w,h);
266 // downsampleArray(I,Iz);
267 const pfstmo::Array2D* Iz = I;
268 // sigma_s /= downsample;
269
270 pfstmo::Array2D* jJ = new pfstmo::Array2D(w,h);
271 pfstmo::Array2D* jG = new pfstmo::Array2D(w,h);
272 pfstmo::Array2D* jK = new pfstmo::Array2D(w,h);
273 pfstmo::Array2D* jH = new pfstmo::Array2D(w,h);
274
275 const int NB_SEGMENTS = (int)ceil((maxI-minI)/sigma_r);
276 float stepI = (maxI-minI)/NB_SEGMENTS;
277
278 GaussianBlur gaussian_blur( w, h, sigma_s );
279
280 // piecewise bilateral
281 for( int j=0 ; j<NB_SEGMENTS ; j++ )
282 {
283 progress_cb( j * 100 / NB_SEGMENTS );
284 float jI = minI + j*stepI; // current intensity value
285
286 for( i=0 ; i<sizeZ ; i++ )
287 {
288 float dI = (*Iz)(i)-jI;
289 (*jG)(i) = exp( -(dI*dI) / (sigma_r*sigma_r) );
290 (*jH)(i) = (*jG)(i) * (*I)(i);
291 }
292
293 gaussian_blur.blur( jG, jK );
294 gaussian_blur.blur( jH, jH );
295
296 // convolveArray(jG, sigma_s, jK);
297 // convolveArray(jH, sigma_s, jH);
298
299 for( i=0 ; i<sizeZ ; i++ )
300 if( likely((*jK)(i)!=0.0f) )
301 (*jJ)(i) = (*jH)(i) / (*jK)(i);
302 else
303 (*jJ)(i) = 0.0f;
304
305 // if( downsample == 1 )
306 JJ = jJ; // No upsampling is necessary
307 // else
308 // upsampleArray(jJ,JJ);
309
310 if( j == 0 ) {
311 // if the first segment - to account for the range boundary
312 for( i=0 ; i<size ; i++ )
313 {
314 if( likely( (*I)(i) > jI + stepI ) )
315 continue; // wi = 0;
316 if( likely( (*I)(i) > jI ) ) {
317 float wi = (stepI - ((*I)(i)-jI)) / stepI;
318 (*J)(i) += (*JJ)(i)*wi;
319 } else
320 (*J)(i) += (*JJ)(i);
321 }
322 } else if( j == NB_SEGMENTS-1 ) {
323 // if the last segment - to account for the range boundary
324 for( i=0 ; i<size ; i++ )
325 {
326 if( likely( (*I)(i) < jI - stepI ) )
327 continue; // wi = 0;
328 if( likely( (*I)(i) < jI ) ) {
329 float wi = (stepI - (jI-(*I)(i))) / stepI;
330 (*J)(i) += (*JJ)(i)*wi;
331 } else
332 (*J)(i) += (*JJ)(i);
333 }
334 } else {
335 for( i=0 ; i<size ; i++ )
336 {
337 float wi = (stepI - fabs( (*I)(i)-jI )) / stepI;
338 if( unlikely( wi>0.0f ) )
339 (*J)(i) += (*JJ)(i)*wi;
340 }
341 }
342 }
343
344 // delete Iz;
345 // if( downsample != 1 )
346 // delete JJ;
347 delete jJ;
348 delete jG;
349 delete jK;
350 delete jH;
351 }
352