-
Notifications
You must be signed in to change notification settings - Fork 23
/
bilateralFilter.m
208 lines (171 loc) · 6.75 KB
/
bilateralFilter.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
%
% output = bilateralFilter( data, edge, ...
% edgeMin, edgeMax, ...
% sigmaSpatial, sigmaRange, ...
% samplingSpatial, samplingRange )
%
% Bilateral and Cross-Bilateral Filter using the Bilateral Grid.
%
% Bilaterally filters the image 'data' using the edges in the image 'edge'.
% If 'data' == 'edge', then it the standard bilateral filter.
% Otherwise, it is the 'cross' or 'joint' bilateral filter.
% For convenience, you can also pass in [] for 'edge' for the normal
% bilateral filter.
%
% Note that for the cross bilateral filter, data does not need to be
% defined everywhere. Undefined values can be set to 'NaN'. However, edge
% *does* need to be defined everywhere.
%
% data and edge should be of the greyscale, double-precision floating point
% matrices of the same size (i.e. they should be [ height x width ])
%
% data is the only required argument
%
% edgeMin and edgeMax specifies the min and max values of 'edge' (or 'data'
% for the normal bilateral filter) and is useful when the input is in a
% range that's not between 0 and 1. For instance, if you are filtering the
% L channel of an image that ranges between 0 and 100, set edgeMin to 0 and
% edgeMax to 100.
%
% edgeMin defaults to min( edge( : ) ) and edgeMax defaults to max( edge(:)).
% This is probably *not* what you want, since the input may not span the
% entire range.
%
% sigmaSpatial and sigmaRange specifies the standard deviation of the space
% and range gaussians, respectively.
% sigmaSpatial defaults to min( width, height ) / 16
% sigmaRange defaults to ( edgeMax - edgeMin ) / 10.
%
% samplingSpatial and samplingRange specifies the amount of downsampling
% used for the approximation. Higher values use less memory but are also
% less accurate. The default and recommended values are:
%
% samplingSpatial = sigmaSpatial
% samplingRange = sigmaRange
%
function output = bilateralFilter( data, edge, edgeMin, edgeMax,...
sigmaSpatial, sigmaRange, samplingSpatial, samplingRange )
if( ndims( data ) > 2 ),
error( 'data must be a greyscale image with size [ height, width ]' );
end
if( ~isa( data, 'double' ) ),
error( 'data must be of class "double"' );
end
if ~exist( 'edge', 'var' ),
edge = data;
elseif isempty( edge ),
edge = data;
end
if( ndims( edge ) > 2 ),
error( 'edge must be a greyscale image with size [ height, width ]' );
end
if( ~isa( edge, 'double' ) ),
error( 'edge must be of class "double"' );
end
inputHeight = size( data, 1 );
inputWidth = size( data, 2 );
if ~exist( 'edgeMin', 'var' ),
edgeMin = min( edge( : ) );
%warning( 'edgeMin not set! Defaulting to: %f\n', edgeMin );
end
if ~exist( 'edgeMax', 'var' ),
edgeMax = max( edge( : ) );
%warning( 'edgeMax not set! Defaulting to: %f\n', edgeMax );
end
edgeDelta = edgeMax - edgeMin;
if ~exist( 'sigmaSpatial', 'var' ),
sigmaSpatial = min( inputWidth, inputHeight ) / 16;
%fprintf( 'Using default sigmaSpatial of: %f\n', sigmaSpatial );
end
if ~exist( 'sigmaRange', 'var' ),
sigmaRange = 0.1 * edgeDelta;
%fprintf( 'Using default sigmaRange of: %f\n', sigmaRange );
end
if ~exist( 'samplingSpatial', 'var' ),
samplingSpatial = sigmaSpatial;
end
if ~exist( 'samplingRange', 'var' ),
samplingRange = sigmaRange;
end
if size( data ) ~= size( edge ),
error( 'data and edge must be of the same size' );
end
% parameters
derivedSigmaSpatial = sigmaSpatial / samplingSpatial;
derivedSigmaRange = sigmaRange / samplingRange;
paddingXY = floor( 2 * derivedSigmaSpatial ) + 1;
paddingZ = floor( 2 * derivedSigmaRange ) + 1;
% allocate 3D grid
downsampledWidth = floor( ( inputWidth - 1 ) / samplingSpatial )...
+ 1 + 2 * paddingXY;
downsampledHeight = floor( ( inputHeight - 1 ) / samplingSpatial )...
+ 1 + 2 * paddingXY;
downsampledDepth = floor( edgeDelta / samplingRange ) + 1 + 2 * paddingZ;
gridData = zeros( downsampledHeight, downsampledWidth, downsampledDepth );
gridWeights = zeros( downsampledHeight, downsampledWidth, downsampledDepth );
% compute downsampled indices
[ jj, ii ] = meshgrid( 0 : inputWidth - 1, 0 : inputHeight - 1 );
% ii =
% 0 0 0 0 0
% 1 1 1 1 1
% 2 2 2 2 2
% jj =
% 0 1 2 3 4
% 0 1 2 3 4
% 0 1 2 3 4
% so when iterating over ii( k ), jj( k )
% get: ( 0, 0 ), ( 1, 0 ), ( 2, 0 ), ... (down columns first)
di = round( ii / samplingSpatial ) + paddingXY + 1;
dj = round( jj / samplingSpatial ) + paddingXY + 1;
dz = round( ( edge - edgeMin ) / samplingRange ) + paddingZ + 1;
% perform scatter (there's probably a faster way than this)
% normally would do downsampledWeights( di, dj, dk ) = 1, but we have to
% perform a summation to do box downsampling
for k = 1 : numel( dz ),
dataZ = data( k ); % traverses the image column wise, same as di( k )
if ~isnan( dataZ ),
dik = di( k );
djk = dj( k );
dzk = dz( k );
gridData( dik, djk, dzk ) = gridData( dik, djk, dzk ) + dataZ;
gridWeights( dik, djk, dzk ) = gridWeights( dik, djk, dzk ) + 1;
end
end
% make gaussian kernel
kernelWidth = 2 * derivedSigmaSpatial + 1;
kernelHeight = kernelWidth;
kernelDepth = 2 * derivedSigmaRange + 1;
halfKernelWidth = floor( kernelWidth / 2 );
halfKernelHeight = floor( kernelHeight / 2 );
halfKernelDepth = floor( kernelDepth / 2 );
[gridX, gridY, gridZ] = meshgrid( 0 : kernelWidth - 1,...
0 : kernelHeight - 1, 0 : kernelDepth - 1 );
gridX = gridX - halfKernelWidth;
gridY = gridY - halfKernelHeight;
gridZ = gridZ - halfKernelDepth;
gridRSquared = ( gridX .* gridX + gridY .* gridY ) /...
( derivedSigmaSpatial * derivedSigmaSpatial ) +...
( gridZ .* gridZ ) / ( derivedSigmaRange * derivedSigmaRange );
kernel = exp( -0.5 * gridRSquared );
% convolve
blurredGridData = convn( gridData, kernel, 'same' );
blurredGridWeights = convn( gridWeights, kernel, 'same' );
% divide
% avoid divide by 0, won't read there anyway
blurredGridWeights( blurredGridWeights == 0 ) = -2;
normalizedBlurredGrid = blurredGridData ./ blurredGridWeights;
% put 0s where it's undefined
normalizedBlurredGrid( blurredGridWeights < -1 ) = 0;
% for debugging
% blurredGridWeights( blurredGridWeights < -1 ) = 0; % put zeros back
% upsample
% meshgrid does x, then y, so output arguments need to be reversed
[ jj, ii ] = meshgrid( 0 : inputWidth - 1, 0 : inputHeight - 1 );
% no rounding
di = ( ii / samplingSpatial ) + paddingXY + 1;
dj = ( jj / samplingSpatial ) + paddingXY + 1;
dz = ( edge - edgeMin ) / samplingRange + paddingZ + 1;
% interpn takes rows, then cols, etc
% i.e. size(v,1), then size(v,2), ...
output = interpn( normalizedBlurredGrid, di, dj, dz );
end