├── README.md ├── consolidator.m ├── createDSM.m ├── createNet.m ├── demo_smrf.m ├── hillshade2.m ├── inpaint_nans.m ├── ir2xiyi.m ├── license.txt ├── progressiveFilter.m ├── smrf.m ├── vcs-smrf-samp11-demview.png └── vcs-smrf-samp11-dotview.png /README.md: -------------------------------------------------------------------------------- 1 | # smrf 2 | smrf - A Simple Morphological Filter for Ground Identification of LIDAR Data 3 | 4 | SMRF is designed to apply a series of opening operations against a digital surface model derived from a LIDAR point cloud, with the dual purpose of creating a gridded model of the ground surface and a vector of boolean values for each tuple (x,y,z) describing it as either ground (0) or object (1). 5 | 6 | SMRF must be minimally called with x,y,z (all vectors of the same length) as well as a cellsize (c), a slope threshold value (s), and a maximum window size (w). The slope threshold value governs the identification process, and roughly corresponds to the maximum slope of the terrain you are working with. The maximum window size defines a window radius (in map units), and corresponds to the size of largest feature to be removed. 7 | 8 | SMRF was tested against the ISPRS LIDAR reference data set, assembled by Sithole and Vosselman (2003). It achieved a mean total error rate of 2.97% and a mean Cohen's Kappa score of 90.02%. 9 | 10 | Pingel, T. J., Clarke K. C., & McBride, W. A. (2013). An Improved Simple Morphological Filter for the Terrain Classification of Airborne LIDAR Data. ISPRS Journal of Photogrammetry and Remote Sensing, 77, 31-30. http://dx.doi.org/10.1016/j.isprsjprs.2012.12.002 11 | -------------------------------------------------------------------------------- /consolidator.m: -------------------------------------------------------------------------------- 1 | function [xcon,ycon,ind] = consolidator(x,y,aggregation_mode,tol) 2 | % consolidator: consolidate "replicates" in x, also aggregate corresponding y 3 | % usage: [xcon,ycon,ind] = consolidator(x,y,aggregation_mode,tol) 4 | % 5 | % arguments: (input) 6 | % x - rectangular array of data to be consolidated. If multiple (p) 7 | % columns, then each row of x is interpreted as a single point in a 8 | % p-dimensional space. (x may be character, in which case xcon will 9 | % be returned as a character array.) 10 | % 11 | % x CANNOT be complex. If you do have complex data, split it into 12 | % real and imaginary components as columns of an array. 13 | % 14 | % If x and y are both ROW vctors, they will be transposed and 15 | % treated as single columns. 16 | % 17 | % y - outputs to be aggregated. If y is not supplied (is left empty) 18 | % then consolidator is similar to unique(x,'rows'), but with a 19 | % tolerance on which points are distinct. (y may be complex.) 20 | % 21 | % y MUST have the same number of rows as x unless y is empty. 22 | % 23 | % aggregation_mode - (OPTIONAL) - an aggregation function, either 24 | % in the form of a function name, such as 'mean' or 'sum', or as 25 | % a function handle, i.e., @mean or @std. An inline function would 26 | % also work, for those users of older matlab releases. 27 | % 28 | % DEFAULT: 'mean' 29 | % 30 | % Aggregation_mode may also be the string 'count', in which case 31 | % a count is made of the replicates found. Ycon will only have 32 | % one column in this case. 33 | % 34 | % The function supplied MUST have the property that it operates 35 | % on the the first dimension of its input by default. 36 | % 37 | % Common functions one might use here are: 38 | % 'mean', 'sum', 'median', 'min', 'max', 'std', 'var', 'prod' 39 | % 'geomean', 'harmmean'. 40 | % 41 | % These last two examples would utilize the statistics toolbox, 42 | % however, these means can be generated using a function 43 | % handle easily enough if that toolbox is not available: 44 | % 45 | % fun = @(x) 1./mean(1./x) % harmonic mean 46 | % fun = @(x) exp(mean(log(x))) % geometric mean 47 | % 48 | % tol - (OPTIONAL) tolerance to identify replicate elements of x. If 49 | % x has multiple columns, then the same (absolute) tolerance is 50 | % applied to all columns of x. 51 | % 52 | % DEFAULT: 0 53 | % 54 | % arguments: (output) 55 | % xcon - consolidated x. Replicates wthin the tolerance are removed. 56 | % if no y was specified, then consolidation is still done on x. 57 | % 58 | % ycon - aggregated value as specified by the aggregation_mode. 59 | % 60 | % ind - vector - denotes the elements of the original array which 61 | % were consolidated into each element of the result. 62 | % 63 | % 64 | % Example 1: 65 | % 66 | % Group means: (using a function handle for the aggregation) 67 | % x = round(rand(1000,1)*5); 68 | % y = x+randn(size(x)); 69 | % [xg,yg] = consolidator(x,y,@mean); 70 | % [xg,yg] 71 | % ans = 72 | % 0 0.1668 73 | % 1.0000 0.9678 74 | % 2.0000 2.0829 75 | % 3.0000 2.9688 76 | % 4.0000 4.0491 77 | % 5.0000 4.8852 78 | % 79 | % Example 2: 80 | % 81 | % Group counts on x 82 | % x = round(randn(100000,1)); 83 | % [xg,c] = consolidator(x,[],'count'); 84 | % [xg,c] 85 | % ans = 86 | % -4 26 87 | % -3 633 88 | % -2 5926 89 | % -1 24391 90 | % 0 38306 91 | % 1 24156 92 | % 2 5982 93 | % 3 559 94 | % 4 21 95 | % 96 | % Example 3: 97 | % 98 | % Unique(x,'rows'), but with a tolerance 99 | % x = rand(100,2); 100 | % xc = consolidator(x,[],[],.05); 101 | % size(xc) 102 | % ans = 103 | % 62 2 104 | % 105 | % See also: unique 106 | % 107 | % Author: John D'Errico 108 | % e-mail address: woodchips@rochester.rr.com 109 | % Release: 3 110 | % Release date: 5/2/06 111 | 112 | % is it a character array? 113 | if ischar(x) 114 | charflag = 1; 115 | x=double(x); 116 | else 117 | charflag = 0; 118 | end 119 | 120 | % check for/supply defaults 121 | if (nargin<4) || isempty(tol) 122 | tol = 0; 123 | end 124 | if (tol<0) 125 | error 'Tolerance must be non-negative.' 126 | end 127 | tol = tol*(1+10*eps); 128 | 129 | % ------------------------------------------------------- 130 | % DETERMINE AGGREGATION MODE AND CREATE A FUNCTION HANDLE 131 | % ------------------------------------------------------- 132 | if (nargin < 3) || isempty(aggregation_mode) 133 | % use default function 134 | fun=@mean; 135 | aggregation_mode='mean'; 136 | 137 | elseif ischar(aggregation_mode) 138 | aggregation_mode=lower(aggregation_mode); 139 | 140 | k=strmatch(aggregation_mode,'count'); 141 | if ~isempty(k) 142 | fun=@(x) x; 143 | else 144 | fun=str2func(aggregation_mode); 145 | end 146 | 147 | elseif isa(aggregation_mode,'inline') 148 | fun=aggregation_mode; 149 | am = struct(fun); 150 | aggregation_mode=am.expr; 151 | 152 | else 153 | fun=aggregation_mode; 154 | aggregation_mode=func2str(fun); 155 | 156 | end 157 | % ------------------------------------------------------- 158 | 159 | % was y supplied, or empty? 160 | [n,p] = size(x); 161 | if (nargin<2) || isempty(y) 162 | y = zeros(n,0); 163 | fun = @(x) x; 164 | aggregation_mode = 'count'; 165 | end 166 | % check for mismatch between x and y 167 | [junk,q] = size(y); 168 | if n~=junk 169 | error 'y must have the same number of rows as x.' 170 | end 171 | 172 | % are both x and y row vectors? 173 | if (n == 1) 174 | x=x'; 175 | n = length(x); 176 | p = 1; 177 | 178 | if ~isempty(y) 179 | y=y'; 180 | else 181 | y=zeros(n,0); 182 | end 183 | 184 | q = size(y,2); 185 | end 186 | 187 | if isempty(y) 188 | aggregation_mode = 'count'; 189 | end 190 | 191 | % consolidate elements of x. 192 | % first shift, scale, and then ceil. 193 | if tol>0 194 | xhat = x - repmat(min(x,[],1),n,1)+tol*eps; 195 | xhat = ceil(xhat/tol); 196 | else 197 | xhat = x; 198 | end 199 | [xhat,tags] = sortrows(xhat); 200 | x=x(tags,:); 201 | y=y(tags,:); 202 | 203 | % count the replicates 204 | iu = [true;any(diff(xhat),2)]; 205 | eb = cumsum(iu); 206 | 207 | % which original elements went where? 208 | if nargout>2 209 | ind = eb; 210 | ind(tags) = ind; 211 | end 212 | 213 | % count is the vector of counts for the consolidated 214 | % x values 215 | if issparse(eb) 216 | eb = full(eb); 217 | end 218 | count=accumarray(eb,1).'; 219 | % ec is the expanded counts, i.e., counts for the 220 | % unconsolidated x 221 | ec = count(eb); 222 | 223 | % special case for aggregation_mode of 'count', 224 | % but we still need to aggregate (using the mean) on x 225 | if strcmp(aggregation_mode,'count') 226 | ycon = count.'; 227 | q = 0; % turn off aggregation on y 228 | else 229 | ycon = zeros(length(count),q); 230 | end 231 | 232 | % loop over the different replicate counts, aggregate x and y 233 | ucount = unique(count); 234 | xcon = repmat(NaN,[length(count),p]); 235 | fullx = ~issparse(x); 236 | fully = ~issparse(y); 237 | for k=ucount 238 | if k==1 239 | xcon(count==1,:) = x(ec==1,:); 240 | else 241 | if fullx 242 | v=permute(x(ec==k,:),[3 2 1]); 243 | else 244 | v=permute(full(x(ec==k,:)),[3 2 1]); 245 | end 246 | v=reshape(v,p,k,[]); 247 | v=permute(v,[2 1 3]); 248 | xcon(count==k,:)=reshape(mean(v),p,[]).'; 249 | end 250 | 251 | if q>0 252 | % aggregate y as specified 253 | if k==1 254 | switch aggregation_mode 255 | case {'std' 'var'} 256 | ycon(count==1,:) = 0; 257 | otherwise 258 | ycon(count==1,:) = y(ec==1,:); 259 | end 260 | else 261 | if fully 262 | v=permute(y(ec==k,:),[3 2 1]); 263 | else 264 | v=permute(full(y(ec==k,:)),[3 2 1]); 265 | end 266 | v=reshape(v,q,k,[]); 267 | v=permute(v,[2 1 3]); 268 | 269 | % aggregate using the appropriate function 270 | ycon(count==k,:)=reshape(fun(v),q,[]).'; 271 | 272 | end 273 | end 274 | end 275 | 276 | % was it originally a character array? 277 | if charflag 278 | xcon=char(xcon); 279 | end 280 | 281 | 282 | -------------------------------------------------------------------------------- /createDSM.m: -------------------------------------------------------------------------------- 1 | % createDSM 2 | % Simple utility to construct a Digital Surface Model from LIDAR data. 3 | % 4 | % 5 | % Syntax 6 | % [DSM R isEmptyCell xi yi] = createDSM(x,y,z,varargin); 7 | % 8 | % 9 | % Description 10 | % createDSM takes as input a three dimensional point cloud (usually LIDAR 11 | % data) and creates an initial ground surface, useful for further 12 | % processing routes that can extract ground and identify objects in 13 | % scene. Required input (in addition to the point cloud) is a cellSize 14 | % (in map coordinates). The user may, instead, optionally specify xi and 15 | % yi, two vectors to which the data will are then snapped. By default 16 | % createDSM creates a minimum surface, in which duplicate cell values are 17 | % reduced to their minimum. However, the user may specify other values 18 | % (e.g., 'mean', 'max') if desired. 19 | % 20 | % 21 | % Requirements 22 | % Requires John D'Errico's consolidator.m and inpaint_nans.m files. 23 | % These are available via the Mathworks File Exchange Program at: 24 | % http://www.mathworks.com/matlabcentral/fileexchange/8354 25 | % http://mathworks.com/matlabcentral/fileexchange/4551 26 | % 27 | % Also requires that the Mathworks Mapping Toolbox is installed. 28 | % 29 | % 30 | % Input Parameters 31 | % x,y,z - Equally sized vectors defining the points in the cloud 32 | % 33 | % 'c',c - Cell size, in map units, for the final grid. The cell size 34 | % should generally be close to the mean x,y density of your 35 | % point cloud. 36 | % 37 | % 'xi',xi - Alternatively, the user can supply a vector of values to 38 | % 'yi',yi define the grid 39 | % 40 | % 'type',t String value or function handle specifying the basis for 41 | % consolidation. Possible values include 'min', 'median', 42 | % 'mean', 'max'. See consolidator.m for all possible inputs. 43 | % 44 | % 'inpaintMethod',ipm 45 | % If this parameter is supplied, it controls the argument 46 | % passed to D'Errico's inpaint_nans method. The default 47 | % value is 4. 48 | % 49 | % 50 | % Output Parameters 51 | % 52 | % DSM A digital surface model (DSM) of the ground. 53 | % 54 | % R A referencing matrix that relates the image file (ZIfin) to 55 | % map coordinates. See worldfileread for more information. 56 | % 57 | % isEmptyCell An image mask describing whether the DSM was empty or not 58 | % at that location. 59 | % 60 | % xi,yi Vectors describing the range of the image. 61 | % 62 | % 63 | % Examples: 64 | % % Download reference LIDAR data 65 | % url = 'http://www.itc.nl/isprswgIII-3/filtertest/Reference.zip'; 66 | % fn = 'Reference.zip'; 67 | % urlwrite(url,[tempdir,'\',fn]); 68 | % unzip([tempdir,'\',fn], tempdir); 69 | % 70 | % % Read data 71 | % M = dlmread([tempdir,'\samp11.txt']); 72 | % x = M(:,1); 73 | % y = M(:,2); 74 | % z = M(:,3); 75 | % gobs = M(:,4); % 0 is Ground, 1 is Object 76 | % clear M; 77 | % 78 | % % Create a minimum surface 79 | % [ZImin R isEmptyCell] = createDSM(x,y,z,'c',1,'min'); 80 | % 81 | % % Write data to geotiff 82 | % imwrite(ZImin,'samp11.tif','tif'); 83 | % worldfilewrite(R,'samp11.tfw'); 84 | % 85 | % 86 | % Author: 87 | % Thomas J. Pingel 88 | % Department of Geography 89 | % University of California, Santa Barbara 90 | % pingel@geog.ucsb.edu 91 | % 92 | % 93 | % License 94 | % Copyright (c) 2011, Thomas J. Pingel 95 | % All rights reserved. 96 | % 97 | % Redistribution and use in source and binary forms, with or without 98 | % modification, are permitted provided that the following conditions are 99 | % met: 100 | % 101 | % * Redistributions of source code must retain the above copyright 102 | % notice, this list of conditions and the following disclaimer. 103 | % * Redistributions in binary form must reproduce the above copyright 104 | % notice, this list of conditions and the following disclaimer in 105 | % the documentation and/or other materials provided with the distribution 106 | % 107 | % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 108 | % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 109 | % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 110 | % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 111 | % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 112 | % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 113 | % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 114 | % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 115 | % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 116 | % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 117 | % POSSIBILITY OF SUCH DAMAGE. 118 | % 119 | % See Also: 120 | % worldfilewrite.m, 121 | 122 | function [DSM,R,isEmptyCell xi yi] = createDSM(x,y,z,varargin) 123 | 124 | % Define inputs 125 | 126 | cellSize = []; 127 | inpaintMethod = []; 128 | xi = []; 129 | yi = []; 130 | cType = []; 131 | 132 | % Define outputs 133 | 134 | DSM = []; 135 | R = []; 136 | isEmptyCell = []; 137 | 138 | %% Process supplied arguments 139 | 140 | i = 1; 141 | while i<=length(varargin) 142 | if isstr(varargin{i}) 143 | switchstr = lower(varargin{i}); 144 | switch switchstr 145 | case 'c' 146 | cellSize = varargin{i+1}; 147 | i = i + 2; 148 | case 'inpaintmethod' 149 | inpaintMethod = varargin{i+1}; 150 | i = i + 2; 151 | case 'xi' 152 | xi = varargin{i+1}; 153 | i = i + 2; 154 | case 'yi' 155 | yi = varargin{i+1}; 156 | i = i + 2; 157 | case 'type' 158 | cType = varargin{i+1}; 159 | i = i + 2; 160 | otherwise 161 | i = i + 1; 162 | end 163 | else 164 | i = i + 1; 165 | end 166 | end 167 | 168 | 169 | if isempty(cType) 170 | cType = 'min'; 171 | end 172 | if isempty(inpaintMethod) 173 | inpaintMethod = 4; % Springs as default method 174 | end 175 | 176 | % define cellsize from xi and yi if they were not defined 177 | if ~isempty(xi) & ~isempty(yi) 178 | cellSize = abs(xi(2) - xi(1)); 179 | end 180 | 181 | if isempty(cellSize) 182 | error('Cell size must be declared.'); 183 | end 184 | 185 | % Define xi and yi if they were not supplied 186 | if isempty(xi) & isempty(yi) 187 | xi = ceil2(min(x),cellSize):cellSize:floor2(max(x),cellSize); 188 | yi = floor2(max(y),cellSize):-cellSize:ceil2(min(y),cellSize); 189 | end 190 | 191 | % Define meshgrids and referencing matrix 192 | [XI YI] = meshgrid(xi,yi); 193 | R = makerefmat(xi(1),yi(1),xi(2) - xi(1),yi(2) - yi(1)); 194 | 195 | % Create gridded values 196 | xs = round3(x,xi); 197 | ys = round3(y,yi); 198 | 199 | % Translate (xr,yr) to pixels (r,c) 200 | [r c] = map2pix(R,xs,ys); 201 | r = round(r); % Fix any numerical irregularities 202 | c = round(c); % Fix any numerical irregularities 203 | % Translate pixels to single vector of indexed values 204 | idx = sub2ind([length(yi) length(xi)],r,c); 205 | 206 | % Consolidate those values according to minimum 207 | [xcon Z] = consolidator(idx,z,cType); 208 | 209 | % Remove any NaN entries. How these get there, I'm not sure. 210 | Z(isnan(xcon)) = []; 211 | xcon(isnan(xcon)) = []; 212 | 213 | % Construct image 214 | DSM = nan(length(yi),length(xi)); 215 | DSM(xcon) = Z; 216 | 217 | isEmptyCell = logical(isnan(DSM)); 218 | 219 | % Inpaint NaNs 220 | if (inpaintMethod~=-1 & any(isnan(DSM(:)))) 221 | DSM = inpaint_nans(DSM,inpaintMethod); 222 | end 223 | 224 | % tStop = tStart - tStop; 225 | 226 | end 227 | 228 | 229 | 230 | 231 | function xr2 = floor2(x,xint) 232 | xr2 = floor(x/xint)*xint; 233 | end 234 | 235 | function xr2 = ceil2(x,xint) 236 | xr2 = ceil(x/xint)*xint; 237 | end 238 | 239 | function xs = round3(x,xi) % Snap vector of x to vector xi 240 | dx = abs(xi(2) - xi(1)); 241 | minxi = min(xi); 242 | maxxi = max(xi); 243 | 244 | %% Perform rounding on interval dx 245 | xs = (dx * round((x - minxi)/dx)) + minxi; 246 | 247 | %% Outside the range of xi is marked NaN 248 | % Fix edge cases 249 | xs((xs==minxi-dx) & (x > (minxi - (dx)))) = minxi; 250 | xs((xs==maxxi+dx) & (x < (maxxi + (dx)))) = maxxi; 251 | % Make NaNs 252 | xs(xs < minxi) = NaN; 253 | xs(xs > maxxi) = NaN; 254 | end -------------------------------------------------------------------------------- /createNet.m: -------------------------------------------------------------------------------- 1 | % createNet 2 | % Simple utility to cut a "net" of background values into a digital surface model 3 | % 4 | % Syntax 5 | % [ZInet isNetCell] = createNet(ZI,cellSize,netWidth) 6 | % 7 | % 8 | % Description 9 | % createNet removes columns and rows from ZI according to the spacing 10 | % specified in gridSpacing, which is a spacing specified according to 11 | % map coordinates (not necessarily pixels). These columns and rows are 12 | % refilled according to background values calculated from an image 13 | % opening operation with a disk-shaped structuring element with a radius 14 | % twice the size of netWidth. Cutting in a net into a DSM helps to keep 15 | % the size of the filter window to a manageable size. A net is typically 16 | % useful when the buildings can not be entirely removed with a 20 meter 17 | % window radius. 18 | % 19 | % 20 | % Requirements 21 | % Image Processing Toolbox 22 | % 23 | % 24 | % Input Parameters 25 | % ZI - A two dimensional image 26 | % 27 | % cellSize - The spacing (in map units) between cells 28 | % 29 | % netWidth - The size (in map units) for the net 30 | % 31 | % 32 | % Output Parameters 33 | % 34 | % ZInet - ZI, with the net cut in 35 | % 36 | % isNetCell - A logical image mask of the net 37 | % 38 | % 39 | % 40 | % 41 | % 42 | % Author: 43 | % Thomas J. Pingel 44 | % Department of Geography 45 | % University of California, Santa Barbara 46 | % pingel@geog.ucsb.edu 47 | % 48 | % 49 | % 50 | % License 51 | % Copyright (c) 2011, Thomas J. Pingel 52 | % All rights reserved. 53 | % 54 | % Redistribution and use in source and binary forms, with or without 55 | % modification, are permitted provided that the following conditions are 56 | % met: 57 | % 58 | % * Redistributions of source code must retain the above copyright 59 | % notice, this list of conditions and the following disclaimer. 60 | % * Redistributions in binary form must reproduce the above copyright 61 | % notice, this list of conditions and the following disclaimer in 62 | % the documentation and/or other materials provided with the distribution 63 | % 64 | % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 65 | % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 66 | % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 67 | % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 68 | % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 69 | % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 70 | % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 71 | % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 72 | % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 73 | % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 74 | % POSSIBILITY OF SUCH DAMAGE. 75 | 76 | 77 | 78 | function [ZInet isNetCell] = createNet(ZImin,cellSize,gridSize) 79 | 80 | bigOpen = imopen(ZImin,strel('disk',2*ceil(gridSize/cellSize))); 81 | isNetCell = logical(zeros(size(ZImin))); 82 | isNetCell(:,1:ceil(gridSize/cellSize):end) = 1; 83 | isNetCell(1:ceil(gridSize/cellSize):end,:) = 1; 84 | 85 | ZInet = ZImin; 86 | ZInet(isNetCell) = bigOpen(isNetCell); 87 | -------------------------------------------------------------------------------- /demo_smrf.m: -------------------------------------------------------------------------------- 1 | % Test SMRF against ISPRS data set. 2 | 3 | % Download reference LIDAR data if not in temp or current dir 4 | if ~exist([tempdir,'\','samp11.txt']) & ~exist('samp11.txt') 5 | disp('Downloading data.'); 6 | url = 'http://www.itc.nl/isprswgIII-3/filtertest/Reference.zip'; 7 | fn = 'Reference.zip'; 8 | urlwrite(url,[tempdir,'\',fn]); 9 | unzip([tempdir,'\',fn], tempdir); 10 | end 11 | 12 | %% 13 | % Read data 14 | M = dlmread([tempdir,'\samp11.txt']); 15 | x = M(:,1); 16 | y = M(:,2); 17 | z = M(:,3); 18 | gobs = M(:,4); % 0 is Ground, 1 is Object 19 | clear M; 20 | 21 | % Declare parameters for this sample (Pingel et al., 2011) 22 | c = 1; 23 | s = .2; 24 | w = 16; 25 | et = .45; 26 | es = 1.2; 27 | 28 | % Run filter 29 | [ZI R gest] = smrf(x,y,z,'c',c,'s',s,'w',w,'et',et,'es',es); 30 | 31 | % Report results 32 | ct = crosstab(gobs,gest) 33 | 34 | %% 35 | ti = 50; 36 | hfig = figure; 37 | plot3(x,y,z,'.','markersize',4); 38 | axis equal vis3d 39 | % axis([min(x) max(x) min(y) max(y) min(z) max(z)]); 40 | daspect([1 1 1]); 41 | set(gca,'xtick',[min(x):ti:max(x)]); 42 | set(gca,'ytick',[min(y):ti:max(y)]); 43 | set(gca,'ztick',[min(z):ti:max(z)]); 44 | set(gca,'xticklabel',{[0:50:max(x)-min(x)]}); 45 | set(gca,'yticklabel',{[0:50:max(y)-min(y)]}); 46 | set(gca,'zticklabel',{[0:50:max(z)-min(z)]}); 47 | figDPI = '600'; 48 | figW = 5; 49 | figH = 5; 50 | grid on 51 | set(gca,'fontsize',8) 52 | set(hfig,'PaperUnits','inches'); 53 | set(hfig,'PaperPosition',[0 0 figW figH]); 54 | fileout = ['vcs-smrf-samp11-dotview.']; 55 | print(hfig,[fileout,'tif'],'-r600','-dtiff'); 56 | print(hfig,[fileout,'png'],'-r600','-dpng'); 57 | %% 58 | ti = 50; 59 | hfig = figure; 60 | colormap gray; 61 | [xi yi] = ir2xiyi(ZI,R); 62 | [XI YI] = meshgrid(xi,yi); 63 | surf(XI,YI,ZI,hillshade2(ZI),'edgecolor','none'); 64 | % surf(ZI,hillshade2(ZI),'edgecolor','none'); 65 | axis equal vis3d; 66 | % axis([min(x) max(x) min(y) max(y) min(z) max(z)]); 67 | daspect([1 1 1]); 68 | set(gca,'xtick',[min(x):ti:max(x)]); 69 | set(gca,'ytick',[min(y):ti:max(y)]); 70 | set(gca,'ztick',[min(z):ti:max(z)]); 71 | set(gca,'xticklabel',{[0:50:max(x)-min(x)]}); 72 | set(gca,'yticklabel',{[0:50:max(y)-min(y)]}); 73 | set(gca,'zticklabel',{[0:50:max(z)-min(z)]}); 74 | view(3); 75 | set(hfig,'PaperUnits','inches'); 76 | set(hfig,'PaperPosition',[0 0 figW figH]); 77 | set(hfig,'PaperPosition',[0 0 figW figH]); 78 | set(gca,'fontsize',8) 79 | fileout = ['vcs-smrf-samp11-demview.']; 80 | print(hfig,[fileout,'tif'],'-r600','-dtiff'); 81 | print(hfig,[fileout,'png'],'-r600','-dpng'); 82 | -------------------------------------------------------------------------------- /hillshade2.m: -------------------------------------------------------------------------------- 1 | function h = hillshade2(dem,R,varargin) 2 | % PUPROSE: Calculate hillshade for a digital elevation model (DEM) 3 | % ------------------------------------------------------------------- 4 | % USAGE: h = hillshade(dem,X,Y,varagin) 5 | % where: dem is the DEM to calculate hillshade for 6 | % X and Y are the DEM coordinate vectors 7 | % varargin are parameters options 8 | % 9 | % OPTIONS: 10 | % 'azimuth' is the direction of lighting in deg (default 315) 11 | % 'altitude' is the altitude of the lighting source in 12 | % in degrees above horizontal (default 45) 13 | % 'zfactor' is the DEM altitude scaling z-factor (default 1) 14 | % 'plotit' creates a simple plot of the hillshade 15 | % 16 | % EXAMPLE: 17 | % h=hillshade(peaks(50),1:50,1:50,'azimuth',45,'altitude',100,'plotit') 18 | % - calculates the hillshade for an example 50x50 peak surface. 19 | % - changes the default settings for azimuth and altitude. 20 | % - creates an output hillshade plot 21 | 22 | % See also: GRADIENT, CART2POL 23 | % 24 | % Note: Uses simple unweighted gradient of 4 nearest neighbours for slope 25 | % calculation (instead of Horn's method) with ESRIs hillshade 26 | % algorithm. 27 | % 28 | % Felix Hebeler, Dept. of Geography, University Zurich, February 2007. 29 | % modified by Andrew Stevens (astevens@usgs.gov), 5/04/2007 30 | 31 | %% configure inputs 32 | %default parameters 33 | 34 | sz = size(dem); 35 | X = 1:sz(2); 36 | Y = sz(1):-1:1; 37 | 38 | 39 | azimuth=315; 40 | altitude=45; 41 | zf=1; 42 | plotit=0; 43 | 44 | %parse inputs 45 | if isempty(varargin)~=1 % check if any arguments are given 46 | [m1,n1]=size(varargin); 47 | opts={'azimuth';'altitude';'zfactor';'plotit'}; 48 | for i=1:n1; % check which parameters are given 49 | indi=strcmpi(varargin{i},opts); 50 | ind=find(indi==1); 51 | if isempty(ind)~=1 52 | switch ind 53 | case 1 54 | azimuth=varargin{i+1}; 55 | case 2 56 | altitude=varargin{i+1}; 57 | case 3 58 | zf=varargin{i+1}; 59 | case 4 60 | plotit=1; 61 | end 62 | end 63 | end 64 | end 65 | 66 | %% Initialize paramaters 67 | dx=abs(X(2)-X(1)); % get cell spacing in x and y direction 68 | dy=abs(Y(2)-Y(1)); % from coordinate vectors 69 | 70 | % lighting azimuth 71 | azimuth = 360.0-azimuth+90; %convert to mathematic unit 72 | azimuth(azimuth>=360)=azimuth-360; 73 | azimuth = azimuth * (pi/180); % convert to radians 74 | 75 | %lighting altitude 76 | altitude = (90-altitude) * (pi/180); % convert to zenith angle in radians 77 | 78 | %% calc slope and aspect (radians) 79 | [fx,fy] = gradient(dem,dx,dy); % uses simple, unweighted gradient of immediate neighbours 80 | [asp,grad]=cart2pol(fy,fx); % convert to carthesian coordinates 81 | %grad = grad/d; % multiply w cellsize 82 | grad=atan(zf*grad); %steepest slope 83 | % convert asp 84 | asp(asp (DEFAULT) see method 1, but % this method does not build as large of a % linear system in the case of only a few % NaNs in a large array. % Extrapolation behavior is linear. % % method == 1 --> simple approach, applies del^2 % over the entire array, then drops those parts % of the array which do not have any contact with % NaNs. Uses a least squares approach, but it % does not modify known values. % In the case of small arrays, this method is % quite fast as it does very little extra work. % Extrapolation behavior is linear. % % method == 2 --> uses del^2, but solving a direct % linear system of equations for nan elements. % This method will be the fastest possible for % large systems since it uses the sparsest % possible system of equations. Not a least % squares approach, so it may be least robust % to noise on the boundaries of any holes. % This method will also be least able to % interpolate accurately for smooth surfaces. % Extrapolation behavior is linear. % % method == 3 --+ See method 0, but uses del^4 for % the interpolating operator. This may result % in more accurate interpolations, at some cost % in speed. % % method == 4 --+ Uses a spring metaphor. Assumes % springs (with a nominal length of zero) % connect each node with every neighbor % (horizontally, vertically and diagonally) % Since each node tries to be like its neighbors, % extrapolation is as a constant function where % this is consistent with the neighboring nodes. % % method == 5 --+ See method 2, but use an average % of the 8 nearest neighbors to any element. % This method is NOT recommended for use. % % % arguments (output): % B - nxm array with NaNs replaced % % % Example: % [x,y] = meshgrid(0:.01:1); % z0 = exp(x+y); % znan = z0; % znan(20:50,40:70) = NaN; % znan(30:90,5:10) = NaN; % znan(70:75,40:90) = NaN; % % z = inpaint_nans(znan); % % % See also: griddata, interp1 % % Author: John D'Errico % e-mail address: woodchips@rochester.rr.com % Release: 2 % Release date: 4/15/06 % I always need to know which elements are NaN, % and what size the array is for any method [n,m]=size(A); A=A(:); nm=n*m; k=isnan(A(:)); % list the nodes which are known, and which will % be interpolated nan_list=find(k); known_list=find(~k); % how many nans overall nan_count=length(nan_list); % convert NaN indices to (r,c) form % nan_list==find(k) are the unrolled (linear) indices % (row,column) form [nr,nc]=ind2sub([n,m],nan_list); % both forms of index in one array: % column 1 == unrolled index % column 2 == row index % column 3 == column index nan_list=[nan_list,nr,nc]; % supply default method if (nargin<2) || isempty(method) method = 0; elseif ~ismember(method,0:5) error 'If supplied, method must be one of: {0,1,2,3,4,5}.' end % for different methods switch method case 0 % The same as method == 1, except only work on those % elements which are NaN, or at least touch a NaN. % horizontal and vertical neighbors only talks_to = [-1 0;0 -1;1 0;0 1]; neighbors_list=identify_neighbors(n,m,nan_list,talks_to); % list of all nodes we have identified all_list=[nan_list;neighbors_list]; % generate sparse array with second partials on row % variable for each element in either list, but only % for those nodes which have a row index > 1 or < n L = find((all_list(:,2) > 1) & (all_list(:,2) < n)); nl=length(L); if nl>0 fda=sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3)+repmat([-1 0 1],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); else fda=spalloc(n*m,n*m,size(all_list,1)*5); end % 2nd partials on column index L = find((all_list(:,3) > 1) & (all_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3)+repmat([-n 0 n],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); k=find(any(fda(:,nan_list(:,1)),2)); % and solve... B=A; B(nan_list(:,1))=fda(k,nan_list(:,1))\rhs(k); case 1 % least squares approach with del^2. Build system % for every array element as an unknown, and then % eliminate those which are knowns. % Build sparse matrix approximating del^2 for % every element in A. % Compute finite difference for second partials % on row variable first [i,j]=ndgrid(2:(n-1),1:m); ind=i(:)+(j(:)-1)*n; np=(n-2)*m; fda=sparse(repmat(ind,1,3),[ind-1,ind,ind+1], ... repmat([1 -2 1],np,1),n*m,n*m); % now second partials on column variable [i,j]=ndgrid(1:n,2:(m-1)); ind=i(:)+(j(:)-1)*n; np=n*(m-2); fda=fda+sparse(repmat(ind,1,3),[ind-n,ind,ind+n], ... repmat([1 -2 1],np,1),nm,nm); % eliminate knowns rhs=-fda(:,known_list)*A(known_list); k=find(any(fda(:,nan_list),2)); % and solve... B=A; B(nan_list(:,1))=fda(k,nan_list(:,1))\rhs(k); case 2 % Direct solve for del^2 BVP across holes % generate sparse array with second partials on row % variable for each nan element, only for those nodes % which have a row index > 1 or < n L = find((nan_list(:,2) > 1) & (nan_list(:,2) < n)); nl=length(L); if nl>0 fda=sparse(repmat(nan_list(L,1),1,3), ... repmat(nan_list(L,1),1,3)+repmat([-1 0 1],nl,1), ... repmat([1 -2 1],nl,1),n*m,n*m); else fda=spalloc(n*m,n*m,size(nan_list,1)*5); end % 2nd partials on column index L = find((nan_list(:,3) > 1) & (nan_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,3), ... repmat(nan_list(L,1),1,3)+repmat([-n 0 n],nl,1), ... repmat([1 -2 1],nl,1),n*m,n*m); end % fix boundary conditions at extreme corners % of the array in case there were nans there if ismember(1,nan_list(:,1)) fda(1,[1 2 n+1])=[-2 1 1]; end if ismember(n,nan_list(:,1)) fda(n,[n, n-1,n+n])=[-2 1 1]; end if ismember(nm-n+1,nan_list(:,1)) fda(nm-n+1,[nm-n+1,nm-n+2,nm-n])=[-2 1 1]; end if ismember(nm,nan_list(:,1)) fda(nm,[nm,nm-1,nm-n])=[-2 1 1]; end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); % and solve... B=A; k=nan_list(:,1); B(k)=fda(k,k)\rhs(k); case 3 % The same as method == 0, except uses del^4 as the % interpolating operator. % del^4 template of neighbors talks_to = [-2 0;-1 -1;-1 0;-1 1;0 -2;0 -1; ... 0 1;0 2;1 -1;1 0;1 1;2 0]; neighbors_list=identify_neighbors(n,m,nan_list,talks_to); % list of all nodes we have identified all_list=[nan_list;neighbors_list]; % generate sparse array with del^4, but only % for those nodes which have a row & column index % >= 3 or <= n-2 L = find( (all_list(:,2) >= 3) & ... (all_list(:,2) <= (n-2)) & ... (all_list(:,3) >= 3) & ... (all_list(:,3) <= (m-2))); nl=length(L); if nl>0 % do the entire template at once fda=sparse(repmat(all_list(L,1),1,13), ... repmat(all_list(L,1),1,13) + ... repmat([-2*n,-n-1,-n,-n+1,-2,-1,0,1,2,n-1,n,n+1,2*n],nl,1), ... repmat([1 2 -8 2 1 -8 20 -8 1 2 -8 2 1],nl,1),nm,nm); else fda=spalloc(n*m,n*m,size(all_list,1)*5); end % on the boundaries, reduce the order around the edges L = find((((all_list(:,2) == 2) | ... (all_list(:,2) == (n-1))) & ... (all_list(:,3) >= 2) & ... (all_list(:,3) <= (m-1))) | ... (((all_list(:,3) == 2) | ... (all_list(:,3) == (m-1))) & ... (all_list(:,2) >= 2) & ... (all_list(:,2) <= (n-1)))); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,5), ... repmat(all_list(L,1),1,5) + ... repmat([-n,-1,0,+1,n],nl,1), ... repmat([1 1 -4 1 1],nl,1),nm,nm); end L = find( ((all_list(:,2) == 1) | ... (all_list(:,2) == n)) & ... (all_list(:,3) >= 2) & ... (all_list(:,3) <= (m-1))); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3) + ... repmat([-n,0,n],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); end L = find( ((all_list(:,3) == 1) | ... (all_list(:,3) == m)) & ... (all_list(:,2) >= 2) & ... (all_list(:,2) <= (n-1))); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3) + ... repmat([-1,0,1],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); k=find(any(fda(:,nan_list(:,1)),2)); % and solve... B=A; B(nan_list(:,1))=fda(k,nan_list(:,1))\rhs(k); case 4 % Spring analogy % interpolating operator. % list of all springs between a node and a horizontal % or vertical neighbor hv_list=[-1 -1 0;1 1 0;-n 0 -1;n 0 1]; hv_springs=[]; for i=1:4 hvs=nan_list+repmat(hv_list(i,:),nan_count,1); k=(hvs(:,2)>=1) & (hvs(:,2)<=n) & (hvs(:,3)>=1) & (hvs(:,3)<=m); hv_springs=[hv_springs;[nan_list(k,1),hvs(k,1)]]; end % delete replicate springs hv_springs=unique(sort(hv_springs,2),'rows'); % build sparse matrix of connections, springs % connecting diagonal neighbors are weaker than % the horizontal and vertical springs nhv=size(hv_springs,1); springs=sparse(repmat((1:nhv)',1,2),hv_springs, ... repmat([1 -1],nhv,1),nhv,nm); % eliminate knowns rhs=-springs(:,known_list)*A(known_list); % and solve... B=A; B(nan_list(:,1))=springs(:,nan_list(:,1))\rhs; case 5 % Average of 8 nearest neighbors % generate sparse array to average 8 nearest neighbors % for each nan element, be careful around edges fda=spalloc(n*m,n*m,size(nan_list,1)*9); % -1,-1 L = find((nan_list(:,2) > 1) & (nan_list(:,3) > 1)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-n-1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % 0,-1 L = find(nan_list(:,3) > 1); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-n, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % +1,-1 L = find((nan_list(:,2) < n) & (nan_list(:,3) > 1)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-n+1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % -1,0 L = find(nan_list(:,2) > 1); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % +1,0 L = find(nan_list(:,2) < n); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % -1,+1 L = find((nan_list(:,2) > 1) & (nan_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([n-1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % 0,+1 L = find(nan_list(:,3) < m); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([n, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % +1,+1 L = find((nan_list(:,2) < n) & (nan_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([n+1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); % and solve... B=A; k=nan_list(:,1); B(k)=fda(k,k)\rhs(k); end % all done, make sure that B is the same shape as % A was when we came in. B=reshape(B,n,m); % ==================================================== % end of main function % ==================================================== % ==================================================== % begin subfunctions % ==================================================== function neighbors_list=identify_neighbors(n,m,nan_list,talks_to) % identify_neighbors: identifies all the neighbors of % those nodes in nan_list, not including the nans % themselves % % arguments (input): % n,m - scalar - [n,m]=size(A), where A is the % array to be interpolated % nan_list - array - list of every nan element in A % nan_list(i,1) == linear index of i'th nan element % nan_list(i,2) == row index of i'th nan element % nan_list(i,3) == column index of i'th nan element % talks_to - px2 array - defines which nodes communicate % with each other, i.e., which nodes are neighbors. % % talks_to(i,1) - defines the offset in the row % dimension of a neighbor % talks_to(i,2) - defines the offset in the column % dimension of a neighbor % % For example, talks_to = [-1 0;0 -1;1 0;0 1] % means that each node talks only to its immediate % neighbors horizontally and vertically. % % arguments(output): % neighbors_list - array - list of all neighbors of % all the nodes in nan_list if ~isempty(nan_list) % use the definition of a neighbor in talks_to nan_count=size(nan_list,1); talk_count=size(talks_to,1); nn=zeros(nan_count*talk_count,2); j=[1,nan_count]; for i=1:talk_count nn(j(1):j(2),:)=nan_list(:,2:3) + ... repmat(talks_to(i,:),nan_count,1); j=j+nan_count; end % drop those nodes which fall outside the bounds of the % original array L = (nn(:,1)<1)|(nn(:,1)>n)|(nn(:,2)<1)|(nn(:,2)>m); nn(L,:)=[]; % form the same format 3 column array as nan_list neighbors_list=[sub2ind([n,m],nn(:,1),nn(:,2)),nn]; % delete replicates in the neighbors list neighbors_list=unique(neighbors_list,'rows'); % and delete those which are also in the list of NaNs. neighbors_list=setdiff(neighbors_list,nan_list,'rows'); else neighbors_list=[]; end -------------------------------------------------------------------------------- /ir2xiyi.m: -------------------------------------------------------------------------------- 1 | function [xi yi] = ir2xiyi(I,R) 2 | r = size(I,1); 3 | c = size(I,2); 4 | [xb yb] = pix2map(R,[1 r],[1 c]); 5 | xi = xb(1):R(2):xb(2); 6 | yi = yb(1):R(4):yb(2); 7 | end 8 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, John D'Errico 2 | 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 are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the distribution 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /progressiveFilter.m: -------------------------------------------------------------------------------- 1 | % progressiveFilter 2 | % Removes high points from a Digital Surface Model by progressive 3 | % morphological filtering. 4 | % 5 | % Syntax 6 | % function [isObjectCell] = progressiveFilter(ZI,varargin) 7 | % 8 | % 9 | % Description 10 | % The progressive morphological filter is the heart of the SMRF lidar 11 | % ground filtering package. Given an image, a cellsize, a slope 12 | % threshold, and a maximum window size, the filter uses an image opening 13 | % operation of iteratively increasing size to locate all "objects" - high 14 | % points in the image like trees or buildings. 15 | % 16 | % The progressiveFilter must be minimally called with an image (a digital 17 | % surface model) as well as a cellsize (c), a slope threshold value (s), 18 | % and a maximum window size (w). The slope threshold value governs the 19 | % identification process, and roughly corresponds to the maximum slope of 20 | % the terrain you are working with. The maximum window size defines a 21 | % window radius (in map units), and corresponds to the size of largest 22 | % feature to be removed. 23 | % 24 | % 25 | % Requirements 26 | % Image Processing Toolbox 27 | % progressiveFilter requires John D'Errico's inpaint_nans.m file. 28 | % http://mathworks.com/matlabcentral/fileexchange/4551 29 | % 30 | % 31 | % Input Parameters 32 | % ZI - A digital surface model, preferably a minimum surface 33 | % 34 | % 'c',c - Cell size, in map units 35 | % 36 | % 's',s - Defines the maximum expected slope of the ground surface 37 | % Values are given in dz/dx, so most slope values will be in 38 | % the range of .05 to .30. 39 | % 40 | % 'w',w - Defines the filter's maximum window radius. 41 | % 'w',[0 w] Alternatively, the user can supply his or her own vector of 42 | % 'w',[1:5:w] window sizes to control the open process. 43 | % 44 | % 'inpaintMethod',ipm 45 | % If this parameter is supplied, it controls the argument 46 | % passed to D'Errico's inpaint_nans method. The default 47 | % value is 4. 48 | % 49 | % 'cutNet',netSize 50 | % Cuts a net of spacing netSize (map coordinates) into ZI 51 | % before further processing. This can help to remove large 52 | % buildings without the need for extremely large filter 53 | % windows. Generally, netSize should be set to the largest 54 | % window radius used (w). 55 | % 56 | % 57 | % Output Parameters 58 | % 59 | % isObjectCell 60 | % A logical image mask indicating cells flagged as objects. 61 | % 62 | % 63 | % Author: 64 | % Thomas J. Pingel 65 | % Department of Geography 66 | % University of California, Santa Barbara 67 | % pingel@geog.ucsb.edu 68 | % 69 | % 70 | % 71 | % License 72 | % Copyright (c) 2011, Thomas J. Pingel 73 | % All rights reserved. 74 | % 75 | % Redistribution and use in source and binary forms, with or without 76 | % modification, are permitted provided that the following conditions are 77 | % met: 78 | % 79 | % * Redistributions of source code must retain the above copyright 80 | % notice, this list of conditions and the following disclaimer. 81 | % * Redistributions in binary form must reproduce the above copyright 82 | % notice, this list of conditions and the following disclaimer in 83 | % the documentation and/or other materials provided with the distribution 84 | % 85 | % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 86 | % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 87 | % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 88 | % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 89 | % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 90 | % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 91 | % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 92 | % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 93 | % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 94 | % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 95 | % POSSIBILITY OF SUCH DAMAGE. 96 | 97 | 98 | 99 | function [isObjectCell lastSurface thisSurface] = progressiveFilter(lastSurface,varargin) 100 | 101 | % Define required input parameters 102 | cellSize = []; 103 | slopeThreshold = []; 104 | wkmax = []; 105 | 106 | % Define optional input parameters 107 | inpaintMethod = []; 108 | strelShape = []; 109 | 110 | % Define output parameters 111 | isObjectCell = []; 112 | 113 | 114 | %% Process supplied arguments 115 | 116 | i = 1; 117 | while i<=length(varargin) 118 | if isstr(varargin{i}) 119 | switchstr = lower(varargin{i}); 120 | switch switchstr 121 | case 'c' 122 | cellSize = varargin{i+1}; 123 | i = i + 2; 124 | case 's' 125 | slopeThreshold = varargin{i+1}; 126 | i = i + 2; 127 | case 'w' 128 | wkmax = varargin{i+1}; 129 | i = i + 2; 130 | case 'inpaintmethod' 131 | inpaintMethod = varargin{i+1}; 132 | i = i + 2; 133 | case 'shape' 134 | strelShape = varargin{i+1}; 135 | i = i + 2; 136 | otherwise 137 | i = i + 1; 138 | end 139 | else 140 | i = i + 1; 141 | end 142 | end 143 | 144 | 145 | %% Catch some errors 146 | 147 | if isempty(cellSize) 148 | error('Cell size must be specified.'); 149 | end 150 | if isempty(wkmax) 151 | error('Maximum window size must be specified'); 152 | end 153 | if isempty(slopeThreshold) 154 | error('Slope threshold value must be specified.'); 155 | end 156 | 157 | 158 | %% Define some default parameters 159 | 160 | if isempty(inpaintMethod) 161 | inpaintMethod = 4; % Springs 162 | end 163 | 164 | if isempty(strelShape) 165 | strelShape = 'disk'; 166 | end 167 | 168 | 169 | %% Convert wkmax to a vector of window sizes (radii) defined in pixels. 170 | % If w was supplied as a vector, use those values as the basis; otherwise, 171 | % use 1:1:wkmax 172 | 173 | if numel(wkmax)~=1 174 | wk = ceil(wkmax / cellSize); 175 | else 176 | wk = 1 : ceil(wkmax / cellSize); 177 | end 178 | 179 | % wk = wkmax; 180 | %% Define elevation thresholds based on supplied slope tolerance 181 | 182 | eThresh = slopeThreshold * (wk * cellSize); 183 | 184 | 185 | %% Perform iterative filtering. 186 | 187 | isObjectCell = logical(zeros(size(lastSurface))); 188 | 189 | for i = 1:length(wk) 190 | thisSurface = imopen(lastSurface,strel(strelShape,wk(i))); 191 | isObjectCell = isObjectCell | (lastSurface - thisSurface > eThresh(i)); 192 | lastSurface = thisSurface; 193 | end 194 | 195 | -------------------------------------------------------------------------------- /smrf.m: -------------------------------------------------------------------------------- 1 | % smrf 2 | % A Simple Morphological Filter for Ground Identification of LIDAR point 3 | % clouds. 4 | % 5 | % 6 | % Syntax 7 | % [ZIfin R isObject ZIpro ZImin isObjectCell] = smrf(x,y,z,'c',c,'s',s,'w',w); 8 | % 9 | % 10 | % Description 11 | % SMRF is designed to apply a series of opening operations against a 12 | % digital surface model derived from a LIDAR point cloud, with the dual 13 | % purpose of creating a gridded model of the ground surface (ZIfin) (and 14 | % its referencing matrix R) and a vector of boolean values for each tuple 15 | % (x,y,z) describing it as either ground (0) or object (1). 16 | % 17 | % SMRF must be minimally called with x,y,z (all vectors of the same 18 | % length) as well as a cellsize (c), a slope threshold value (s), and a 19 | % maximum window size (w). The slope threshold value governs the 20 | % identification process, and roughly corresponds to the maximum slope of 21 | % the terrain you are working with. The maximum window size defines a 22 | % window radius (in map units), and corresponds to the size of largest 23 | % feature to be removed. 24 | % 25 | % 26 | % Requirements 27 | % SMRF requires John D'Errico's consolidator.m and inpaint_nans.m files. 28 | % These are available via the Mathworks File Exchange Program at: 29 | % http://www.mathworks.com/matlabcentral/fileexchange/8354 30 | % http://mathworks.com/matlabcentral/fileexchange/4551 31 | % 32 | % SMRF also requires two subfunctions, createDSM.m and 33 | % progressiveFilter.m. These were written as subfunctions for 34 | % pedagogical purposes. If the optional 'net cutting' feature is used to 35 | % remove large buildings with smaller windows, createNet.m must be 36 | % accessible as well. 37 | % 38 | % Finally, SMRF also requires that the Mathworks Mapping Toolbox is 39 | % installed. 40 | % 41 | % 42 | % Input Parameters 43 | % x,y,z - Equally sized vectors defining the points in the cloud 44 | % 45 | % 'c',c - Cell size, in map units, for the final grid. The cell size 46 | % should generally be close to the mean x,y density of your 47 | % point cloud. 48 | % 49 | % 'xi',xi - Alternatively, the user can supply a vector of values to 50 | % 'yi',yi define the grid 51 | % 52 | % 's',s - Defines the maximum expected slope of the ground surface 53 | % Values are given in dz/dx, so most slope values will be in 54 | % the range of .05 to .30. 55 | % 56 | % 'w',w - Defines the filter's maximum window radius. 57 | % 'w',[0 w] Alternatively, the user can supply his or her own vector of 58 | % 'w',[1:5:w] window sizes to control the open process. 59 | % 60 | % 'et',et - Defines the elevation threshold that expresses the maximum 61 | % vertical distance that a point may be above the prospective 62 | % ground surface created after the opening operation is 63 | % completed. These values are typically in the range of 0.25 64 | % to 1.0 meter. An elevation threshold must be supplied in 65 | % order for SMRF to return an isObject vector. 66 | % 67 | % 'es',es - Elevation scaling factor that scales the elevation 68 | % threshold (et) depending on the slope of the prospective 69 | % digital surface model (ZIpro) created after the smrf filter 70 | % has identified all nonground points in the minimum surface. 71 | % Elevation scaling factors generally range from 0.0 to 2.5, 72 | % with 1.25 a good starting value. If no es parameter is 73 | % supplied, the value of es is set to zero. 74 | % 75 | % 'inpaintMethod',ipm 76 | % If this parameter is supplied, it controls the argument 77 | % passed to D'Errico's inpaint_nans method. The default 78 | % value is 4. 79 | % 80 | % 'cutNet',netSize 81 | % Cuts a net of spacing netSize (map coordinates) into ZImin 82 | % before further processing. This can help to remove large 83 | % buildings without the need for extremely large filter 84 | % windows. Generally, netSize should be set to the largest 85 | % window radius used (w). 86 | % 87 | % 88 | % Output Parameters 89 | % 90 | % ZIfin A digital surface model (DSM) of the ground. If an 91 | % elevation threshold is not provided, the final DSM is set 92 | % equal to the prospective DSM (see below). 93 | % 94 | % R A referencing matrix that relates the image file (ZIfin) to 95 | % map coordinates. See worldfileread for more information. 96 | % 97 | % isObject A logical vector, equal in length to (x,y,z), that 98 | % describes whether each tuple is ground (0) or nonground (1) 99 | % 100 | % ZIpro The prospective ground surface created after the smrf 101 | % algorithm has identified nonground cells in the initial 102 | % minimum surface (ZImin). It is created by inpainting all 103 | % empty, outlier, or nonground cells from the minimum 104 | % surface. 105 | % 106 | % ZImin The initial minimum surface after smrf internally calls 107 | % createDSM.m. 108 | % 109 | % isObjectCell 110 | % Cells in ZImin that were classified as empty,outliers,or 111 | % objects during SMRF's run. 112 | % 113 | % Examples 114 | % 115 | % % Test SMRF against ISPRS data set. 116 | % 117 | % % Download reference LIDAR data 118 | % url = 'http://www.itc.nl/isprswgIII-3/filtertest/Reference.zip'; 119 | % fn = 'Reference.zip'; 120 | % urlwrite(url,[tempdir,'\',fn]); 121 | % unzip([tempdir,'\',fn], tempdir); 122 | % 123 | % % Read data 124 | % M = dlmread([tempdir,'\samp11.txt']); 125 | % x = M(:,1); 126 | % y = M(:,2); 127 | % z = M(:,3); 128 | % gobs = M(:,4); % 0 is Ground, 1 is Object 129 | % clear M; 130 | % 131 | % % Declare parameters for this sample (Pingel et al., 2011) 132 | % c = 1; 133 | % s = .2; 134 | % w = 16; 135 | % et = .45; 136 | % es = 1.2; 137 | % 138 | % % Run filter 139 | % [ZI R gest] = smrf(x,y,z,'c',c,'s',s,'w',w,'et',et,'es',es); 140 | % 141 | % % Report results 142 | % ct = crosstab(gobs,gest) 143 | % 144 | % % View surface 145 | % figure; 146 | % surf(ZI,'edgecolor','none'); axis equal vis3d 147 | % 148 | % References: The filter was succesfully tested against the Sithole and 149 | % Vosselman's (2003) ISPRS LIDAR Dataset 150 | % (http://www.itc.nl/isprswgIII-3/filtertest/). 151 | % 152 | % 153 | %% 154 | % 155 | % Author: 156 | % Thomas J. Pingel 157 | % Department of Geography 158 | % University of California, Santa Barbara 159 | % pingel@geog.ucsb.edu 160 | % 161 | % 162 | % 163 | % License 164 | % Copyright (c) 2011, Thomas J. Pingel 165 | % All rights reserved. 166 | % 167 | % Redistribution and use in source and binary forms, with or without 168 | % modification, are permitted provided that the following conditions are 169 | % met: 170 | % 171 | % * Redistributions of source code must retain the above copyright 172 | % notice, this list of conditions and the following disclaimer. 173 | % * Redistributions in binary form must reproduce the above copyright 174 | % notice, this list of conditions and the following disclaimer in 175 | % the documentation and/or other materials provided with the distribution 176 | % 177 | % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 178 | % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 179 | % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 180 | % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 181 | % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 182 | % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 183 | % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 184 | % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 185 | % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 186 | % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 187 | % POSSIBILITY OF SUCH DAMAGE. 188 | % 189 | % 190 | % See Also: 191 | % worldfilewrite.m, 192 | 193 | 194 | 195 | function [ZIfin R isObject ZIpro ZImin isObjectCell] = smrf(x,y,z,varargin) 196 | 197 | if nargin < 9 198 | error('Not enough arguments. Minimum call: smrf(x,y,z,''c'',c,''s'',s,''w'',w)'); 199 | end 200 | 201 | dependencyCheck = [exist('consolidator') exist('inpaint_nans')]; 202 | if any(dependencyCheck==0) 203 | disp('The smrf algorithm requires that consolidator.m and inpaint_nans.m are accessible.'); 204 | disp('Both of these functions were written by John D''Errico and are available through the'); 205 | disp('Mathworks file exchange.'); 206 | disp('consolidator is located at http://www.mathworks.com/matlabcentral/fileexchange/8354'); 207 | disp('inpaint_nans is located at http://mathworks.com/matlabcentral/fileexchange/4551'); 208 | error('Please acquire these files before attempting to run smrf again.'); 209 | end 210 | % Initialize possible input values 211 | cellSize = []; 212 | slopeThreshold = []; 213 | wkmax = []; 214 | xi = []; 215 | yi = []; 216 | elevationThreshold = []; 217 | elevationScaler = []; 218 | 219 | % Initialize output values 220 | ZIfin = []; 221 | R = []; 222 | isObject = []; 223 | 224 | % Declare other global variables 225 | inpaintMethod = 4; % Springs 226 | cutNetSize = []; 227 | isNetCell = []; 228 | 229 | 230 | %% Process extra arguments 231 | 232 | i = 1; 233 | while i<=length(varargin) 234 | if isstr(varargin{i}) 235 | switchstr = lower(varargin{i}); 236 | switch switchstr 237 | case 'c' % Cell size (required, or xi and yi must be supplied) 238 | cellSize = varargin{i+1}; 239 | i = i + 2; 240 | case 's' % Slope tolerance (required) 241 | slopeThreshold = varargin{i+1}; 242 | i = i + 2; 243 | case 'w' % Maximum window size, in map units (required) 244 | wkmax = varargin{i+1}; 245 | i = i + 2; 246 | case 'et' % Elevation Threshold (optional) 247 | elevationThreshold = varargin{i+1}; 248 | i = i + 2; 249 | case 'es' % Elevation Scaling Factor (optional) 250 | elevationScaler = varargin{i+1}; 251 | i = i + 2; 252 | case 'xi' % A supplied vector for x 253 | xi = varargin{i+1}; 254 | i = i + 2; 255 | case 'yi' % A supplied vector for y 256 | yi = varargin{i+1}; 257 | i = i + 2; 258 | case 'inpaintmethod' % Argument to pass to inpaint_nans.m 259 | inpaintMethod = varargin{i+1}; 260 | i = i + 2; 261 | case 'cutnet' % Support to a cut a grid into large datasets 262 | cutNetSize = varargin{i+1}; 263 | i = i + 2; 264 | case 'objectMask' 265 | objectMask = varargin{i+1}; 266 | i = i + 2; 267 | otherwise 268 | i = i + 1; 269 | end 270 | else 271 | i = i + 1; 272 | end 273 | end 274 | 275 | 276 | %% Check for a few error conditions 277 | 278 | if isempty(slopeThreshold) 279 | error('Slope threshold must be supplied.'); 280 | end 281 | 282 | if isempty(wkmax) 283 | error('Maximum window size must be supplied.'); 284 | end 285 | 286 | if isempty(cellSize) && isempty(xi) && isempty(yi) 287 | error('Cell size or (xi AND yi) must be supplied.'); 288 | end 289 | 290 | if isempty(xi) && ~isempty(yi) 291 | error('If yi is defined, xi must also be defined.'); 292 | end 293 | 294 | if ~isempty(xi) && isempty(yi) 295 | error('If xi is defined, yi must also be defined.'); 296 | end 297 | 298 | if ~isempty(xi) && ~isvector(xi) 299 | error('xi must be a vector'); 300 | end 301 | 302 | if ~isempty(yi) && ~isvector(yi) 303 | error('yi must be a vector'); 304 | end 305 | 306 | if ~isempty(xi) && (abs(xi(2) - xi(1)) ~= abs(yi(2) - yi(1))) 307 | error('xi and yi must be incremented identically'); 308 | end 309 | 310 | if isempty(cellSize) && ~isempty(xi) && ~isempty(yi) 311 | cellSize = abs(xi(2) - xi(1)); 312 | end 313 | 314 | if ~isempty(elevationThreshold) && isempty(elevationScaler) 315 | elevationScaler = 0; 316 | end 317 | 318 | %% Create Digital Surface Model 319 | if isempty(xi) 320 | [ZImin R isEmptyCell xi yi] = createDSM(x,y,z,'c',cellSize,'type','min','inpaintMethod',inpaintMethod); 321 | else 322 | [ZImin R isEmptyCell] = createDSM(x,y,z,'xi',xi,'yi',yi,'type','min','inpaintMethod',inpaintMethod); 323 | end 324 | 325 | %% Detect outliers 326 | 327 | [isLowOutlierCell] = progressiveFilter(-ZImin,'c',cellSize,'s',5,'w',1); 328 | 329 | %% Cut a mesh into Zmin, if desired 330 | if ~isempty(cutNetSize) 331 | [ZInet isNetCell] = createNet(ZImin,cellSize,cutNetSize); 332 | else 333 | ZInet = ZImin; 334 | isNetCell = logical(zeros(size(ZImin))); 335 | end 336 | 337 | %% Detect objects 338 | 339 | [isObjectCell] = progressiveFilter(ZInet,'c',cellSize,'s',slopeThreshold,'w',wkmax); 340 | 341 | %% Construct a prospective ground surface 342 | 343 | ZIpro = ZImin; 344 | ZIpro(isEmptyCell | isLowOutlierCell | isObjectCell | isNetCell) = NaN; 345 | ZIpro = inpaint_nans(ZIpro,inpaintMethod); 346 | isObjectCell = isEmptyCell | isLowOutlierCell | isObjectCell | isNetCell; 347 | 348 | 349 | %% Identify ground ... 350 | % based on elevationThreshold and elevationScaler, if provided 351 | 352 | if ~isempty(elevationThreshold) && ~isempty(elevationScaler) 353 | 354 | % Identify Objects 355 | % Calculate slope 356 | [gx gy] = gradient(ZIpro / cellSize); 357 | gsurfs = sqrt(gx.^2 + gy.^2); % Slope of final estimated ground surface 358 | clear gx gy 359 | 360 | % Get Zpro height and slope at each x,y point 361 | iType = 'spline'; 362 | [r c] = map2pix(R,x,y); 363 | ez = interp2(ZIpro,c,r,iType); 364 | SI = interp2(gsurfs,c,r,iType); 365 | clear r c 366 | 367 | requiredValue = elevationThreshold + (elevationScaler * SI); 368 | isObject = abs(ez-z) > requiredValue; 369 | clear ez SI requiredValue 370 | 371 | 372 | % Interpolate final ZI 373 | F = TriScatteredInterp(x(~isObject),y(~isObject),z(~isObject),'natural'); 374 | [XI,YI] = meshgrid(xi,yi); 375 | ZIfin = F(XI,YI); 376 | else 377 | warning('Since elevation threshold and elevation scaling factor were not provided for ground identification, ZIfin is equal to ZIpro.'); 378 | ZIfin = ZIpro; 379 | end 380 | 381 | 382 | 383 | end % Function end -------------------------------------------------------------------------------- /vcs-smrf-samp11-demview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomaspingel/smrf-matlab/2da901bb6d1356f084ea3a814e615b03a7086a49/vcs-smrf-samp11-demview.png -------------------------------------------------------------------------------- /vcs-smrf-samp11-dotview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomaspingel/smrf-matlab/2da901bb6d1356f084ea3a814e615b03a7086a49/vcs-smrf-samp11-dotview.png --------------------------------------------------------------------------------