1 // This is contrib/brl/bbas/volm/pro/process/volm_dsm_ground_plane_estimation_process.cxx
2 #include <iostream>
3 #include <string>
4 #include <bprb/bprb_func_process.h>
5 //:
6 // \file
7 //     process to estimate a ground plane from original height maps
8 //
9 // \verbatim
10 //  Modifications
11 //    none yet
12 // \endverbatim
13 
14 #include "vil/vil_math.h"
15 #include <bsta/bsta_histogram.h>
16 #include <mbl/mbl_thin_plate_spline_3d.h>
17 #include "vil/vil_image_view.h"
18 #include "vgl/vgl_point_3d.h"
19 #include "vnl/vnl_math.h"
20 #include <vgl/algo/vgl_line_2d_regression.h>
21 #include "vil/vil_save.h"
22 
23 namespace volm_dsm_ground_plane_estimation_process_globals
24 {
25   constexpr unsigned int n_inputs_ = 4;
26   constexpr unsigned int n_outputs_ = 1;
27 }
28 
volm_dsm_ground_plane_estimation_process_cons(bprb_func_process & pro)29 bool volm_dsm_ground_plane_estimation_process_cons(bprb_func_process& pro)
30 {
31   using namespace volm_dsm_ground_plane_estimation_process_globals;
32   std::vector<std::string> input_types_(n_inputs_);
33   input_types_[0] = "vil_image_view_base_sptr";       // original height map image
34   input_types_[1] = "int";                            // number of height value estimations to use, bottom N, e.g. 100
35   input_types_[2] = "int";                            // window size to get minimum height value around the fitting points.
36   input_types_[3] = "float";                          // Invalid pixel value
37   std::vector<std::string> output_types_(n_outputs_);
38   output_types_[0] = "vil_image_view_base_sptr"; // output ground plane image
39   return pro.set_input_types(input_types_) && pro.set_output_types(output_types_);
40 }
41 
volm_dsm_ground_plane_estimation_process(bprb_func_process & pro)42 bool volm_dsm_ground_plane_estimation_process(bprb_func_process& pro)
43 {
44   using namespace volm_dsm_ground_plane_estimation_process_globals;
45   if (!pro.verify_inputs())  {
46     std::cerr << pro.name() << ": Wring inputs!!!\n";
47     return false;
48   }
49   // get inputs
50   unsigned in_i = 0;
51   vil_image_view_base_sptr img_res = pro.get_input<vil_image_view_base_sptr>(in_i++);
52   int N = pro.get_input<int>(in_i++);
53   int window_size = pro.get_input<int>(in_i++);
54   auto invalid_pixel = pro.get_input<float>(in_i++);
55 
56   auto* in_img = dynamic_cast<vil_image_view<float>*>(img_res.ptr());
57   if (!in_img) {
58     std::cerr << pro.name() << ": Unsupported image pixel format -- " << img_res->pixel_format() << ", only float is supported!\n";
59     return false;
60   }
61   int ni = static_cast<int>(in_img->ni());
62   int nj = static_cast<int>(in_img->nj());
63   auto* out = new vil_image_view<float>(ni, nj);
64 
65   std::vector<vgl_point_3d<double> > src_pts, dst_pts;
66 
67 #if 1
68   for (int i = N; i < ni-N; i+=N) {
69     int s_i = i - window_size;  int e_i = i + window_size;
70     if (s_i < 0)   s_i = 0;
71     if (e_i > ni)  e_i = ni;
72     for (int j = N; j < nj-N; j+=N) {
73       int s_j = j - window_size;  int e_j = j + window_size;
74       if (s_j < 0)    s_j = 0;
75       if (e_j > nj)   e_j = nj;
76       float min = 1000000.0f;
77       for (int ii = s_i; ii < e_i; ii++) {
78         for (int jj = s_j; jj < e_j; jj++) {
79           if ( std::abs((*in_img)(ii,jj)-invalid_pixel) < 1E-3 )
80             continue;
81           if ( (*in_img)(ii,jj) < min )
82             min = (*in_img)(ii,jj);
83         }
84       }
85       if (min < 1000000.0f) {
86         double img_val = (*in_img)(i,j);
87         vgl_point_3d<double> pt1(i,j,img_val);
88         vgl_point_3d<double>  pt(i,j,min);
89         src_pts.push_back(pt1);
90         dst_pts.push_back(pt);
91       }
92     }
93   }
94 #endif
95 #if 0
96   for (int i = 0; i < ni; i++)
97   {
98     int s_i = i - N;  int e_i = i + N;
99     if (s_i < 0)   s_i = 0;
100     if (e_i > ni)  e_i = ni;
101     for (int j = 0; j < nj; j++)
102     {
103       int s_j = j - N;  int e_j = j + N;
104       if (s_j < 0)    s_j = 0;
105       if (e_j > nj)   e_j = nj;
106       float min = 1000000.0f;
107       for (int ii = s_i; ii < e_i; ii++) {
108         for (int jj = s_j; jj < e_j; jj++) {
109           if ( std::abs((*in_img)(ii,jj)-invalid_pixel) < 1E-3 )
110             continue;
111           if ( (*in_img)(ii,jj) < min )
112             min = (*in_img)(ii,jj);
113         }
114       }
115       if (min < 1000000.0f) {
116         double img_val = (*in_img)(i,j);
117         vgl_point_3d<double> pt1(i,j,img_val);
118         vgl_point_3d<double>  pt(i,j,min);
119         src_pts.push_back(pt1);
120         dst_pts.push_back(pt);
121       }
122     }
123   }
124 #endif
125 #if 0
126   for (int j = N; j < nj-N; j += N)
127   {
128     for (int i = N; i < ni-N; i+=N)
129     {
130       // find the minimum in the neighborhood
131       float min = 10000000.0f;
132       bool found = false;
133       for (int k = -N; k < N; k++) {
134         for (int m = -N; m < N; m++) {
135           int uu = i+k;
136           int vv = j+m;
137           if ( std::abs((*in_img)(uu,vv)-invalid_pixel) < 1E-3 )
138             continue;
139           if ( (*in_img)(uu,vv) < min ) {
140             min = (*in_img)(uu,vv);
141             found = true;
142           }
143         }
144       }
145       if (found) {
146         double img_val = (*in_img)(i,j);
147         vgl_point_3d<double> pt1(i,j,img_val);
148         vgl_point_3d<double>  pt(i,j,min);
149         src_pts.push_back(pt1);
150         dst_pts.push_back(pt);
151       }
152     }
153   }
154 #endif
155 
156   std::cout << pro.name() << ": Total image pixels: " << ni*nj << ", size of source pts: " << src_pts.size() << std::endl;
157 
158   // construct spline object
159   mbl_thin_plate_spline_3d tps;
160   tps.build(src_pts, dst_pts);
161 
162   // apply to points
163   out->fill(invalid_pixel);
164   for (int i = 0; i < ni; i++) {
165     for (int j = 0; j < nj; j++) {
166       if (std::abs((*in_img)(i,j)-invalid_pixel) < 1E-3 )
167         continue;
168       vgl_point_3d<double> p(i,j,(*in_img)(i,j));
169       vgl_point_3d<double> new_p = tps(p);
170       (*out)(i,j) = new_p.z();
171     }
172   }
173 
174   pro.set_output_val<vil_image_view_base_sptr>(0, out);
175   return true;
176 
177 }
178 
179 
180 //: process to estimate a ground plane from original height maps using an edge map.  The edge map is used to control windows size
181 namespace volm_dsm_ground_plane_estimation_edge_process_globals
182 {
183   constexpr unsigned int n_inputs_ = 4;
184   constexpr unsigned int n_outputs_ = 1;
185 
186   int window_size_from_nearest_edge(vil_image_view<vxl_byte>* edge_img, int const& i, int const& j, int const& search_range);
187 }
188 
volm_dsm_ground_plane_estimation_edge_process_cons(bprb_func_process & pro)189 bool volm_dsm_ground_plane_estimation_edge_process_cons(bprb_func_process& pro)
190 {
191   using namespace volm_dsm_ground_plane_estimation_edge_process_globals;
192   std::vector<std::string> input_types_(n_inputs_);
193   input_types_[0] = "vil_image_view_base_sptr";       // original height map image
194   input_types_[1] = "vil_image_view_base_sptr";       // edge image used to obtain the search window size
195   input_types_[2] = "int";                            // number of height value estimations to use, bottom N, e.g. 100
196   input_types_[3] = "float";                          // invalid pixel value
197   std::vector<std::string> output_types_(n_outputs_);
198   output_types_[0] = "vil_image_view_base_sptr"; // output ground plane image
199   return pro.set_input_types(input_types_) && pro.set_output_types(output_types_);
200 }
201 
volm_dsm_ground_plane_estimation_edge_process(bprb_func_process & pro)202 bool volm_dsm_ground_plane_estimation_edge_process(bprb_func_process& pro)
203 {
204   using namespace volm_dsm_ground_plane_estimation_edge_process_globals;
205   if (!pro.verify_inputs())  {
206     std::cerr << pro.name() << ": Wring inputs!!!\n";
207     return false;
208   }
209   // get inputs
210   unsigned in_i = 0;
211   vil_image_view_base_sptr img_res = pro.get_input<vil_image_view_base_sptr>(in_i++);
212   vil_image_view_base_sptr edge_img_res = pro.get_input<vil_image_view_base_sptr>(in_i++);
213   int N = pro.get_input<int>(in_i++);
214   auto invalid_pixel = pro.get_input<float>(in_i++);
215 
216   auto* in_img = dynamic_cast<vil_image_view<float>*>(img_res.ptr());
217   if (!in_img) {
218     std::cerr << pro.name() << ": Unsupported image pixel format -- " << img_res->pixel_format() << ", only float is supported!\n";
219     return false;
220   }
221   auto* edge_img = dynamic_cast<vil_image_view<vxl_byte>*>(edge_img_res.ptr());
222   if (!edge_img) {
223     std::cerr << pro.name() << ": Unsupported edge image pixel format -- " << edge_img_res->pixel_format() << ", only byte is supported!\n";
224     return false;
225   }
226   int ni = static_cast<int>(in_img->ni());
227   int nj = static_cast<int>(in_img->nj());
228   if (ni != edge_img->ni() || nj != edge_img->nj()) {
229     std::cerr << pro.name() << ": height image and edge image size do not match!\n";
230     return false;
231   }
232   auto* out = new vil_image_view<float>(ni, nj);
233   int search_range = ni;
234   if (search_range > nj)
235     search_range = nj;
236   std::vector<vgl_point_3d<double> > src_pts, dst_pts;
237   for (int i = N; i < ni-N; i+=N)
238   {
239     for (int j = N; j < nj-N; j+=N)
240     {
241       // find appropriate window size for current pixel
242       int window_size = window_size_from_nearest_edge(edge_img, i, j, search_range);
243       int s_i = i - window_size;  int e_i = i + window_size;
244       if (s_i < 0)   s_i = 0;
245       if (e_i > ni)  e_i = ni;
246       int s_j = j - window_size;  int e_j = j + window_size;
247       if (s_j < 0)    s_j = 0;
248       if (e_j > nj)   e_j = nj;
249       float min = 1000000.0f;
250       for (int ii = s_i; ii < e_i; ii++) {
251         for (int jj = s_j; jj < e_j; jj++) {
252           if ( std::abs((*in_img)(ii,jj)-invalid_pixel) < 1E-3 )
253             continue;
254           if ( (*in_img)(ii,jj) < min )
255             min = (*in_img)(ii,jj);
256         }
257       }
258       if (min < 1000000.0f) {
259         double img_val = (*in_img)(i,j);
260         vgl_point_3d<double> pt1(i,j,img_val);
261         vgl_point_3d<double>  pt(i,j,min);
262         src_pts.push_back(pt1);
263         dst_pts.push_back(pt);
264       }
265     }
266   }
267   std::cout << pro.name() << ": Total image pixels: " << ni*nj << ", size of source pts: " << src_pts.size() << std::endl;
268   for (unsigned i = 0; i < src_pts.size(); i++) {
269     std::cout << "source: " << src_pts[i].x() << " " << src_pts[i].y() << " " << src_pts[i].z()
270              << "  dest: " << dst_pts[i].x() << " " << dst_pts[i].y() << " " << dst_pts[i].z() << std::endl;
271   }
272 
273   // construct spline object
274   mbl_thin_plate_spline_3d tps;
275   tps.build(src_pts, dst_pts);
276 
277   // apply to points
278   out->fill(invalid_pixel);
279   for (int i = 0; i < ni; i++) {
280     for (int j = 0; j < nj; j++) {
281       if (std::abs((*in_img)(i,j)-invalid_pixel) < 1E-3 )
282         continue;
283       vgl_point_3d<double> p(i,j,(*in_img)(i,j));
284       vgl_point_3d<double> new_p = tps(p);
285       (*out)(i,j) = new_p.z();
286     }
287   }
288 
289   pro.set_output_val<vil_image_view_base_sptr>(0, out);
290   return true;
291 }
292 
window_size_from_nearest_edge(vil_image_view<vxl_byte> * edge_img,int const & i,int const & j,int const & search_range)293 int volm_dsm_ground_plane_estimation_edge_process_globals::window_size_from_nearest_edge(vil_image_view<vxl_byte>* edge_img,
294                                                                                          int const& i, int const& j,
295                                                                                          int const& search_range)
296 {
297   // if the pixel itself is an edge, return an default search window
298   if ( (*edge_img)(i,j))
299     return 30;
300   bool found_neighbor = false;
301   int  edge_window_size = 0;
302   unsigned num_nbrs = 8;
303   unsigned ni = edge_img->ni();
304   unsigned nj = edge_img->nj();
305   // keep increasing search radius
306   for (int radius = 1; (radius < search_range && !found_neighbor); radius++)
307   {
308     int start_i = i - radius;  int end_i = i + radius;
309     int start_j = j - radius;  int end_j = j + radius;
310     for (int ii = start_i; (ii < end_i && !found_neighbor); ii++) {
311       for (int jj = start_j; (jj < end_j && !found_neighbor); jj++) {
312         if (ii < 0 || jj < 0 || ii >= (int)ni || jj >= (int)nj)
313           continue;
314         if ( (*edge_img)(ii, jj) != 0) {
315           found_neighbor = true;
316           edge_window_size = radius;
317         }
318       }
319     }
320   }
321   if (!edge_window_size)
322     return 30;
323   edge_window_size += 30;
324   return edge_window_size;
325 }
326 
327 // Process to perform ground filtering using MGF algorithm
328 // reference http://www.sciencedirect.com/science/article/pii/S0924271608000956
329 namespace volm_dsm_ground_filter_mgf_process_globals
330 {
331   constexpr unsigned n_inputs_ = 5;
332   constexpr unsigned n_outputs_ = 2;
333 
334   bool nearest_ground_elev(vil_image_view<float> const& img,
335                            vil_image_view<vxl_byte> const& grd_mask,
336                            unsigned const& i, unsigned const& j,
337                            float& ground_elev);
338   // perform linear region along the scan line and remove mis-labeled ground pixel if their STD is 3 times
339   bool ground_linear_regression(vil_image_view<float> const& img,
340                                 vil_image_view<vxl_byte>& grd_mask,
341                                 bool const& is_horizontal,
342                                 float const& neighbor_size = 100,
343                                 float const& rsm_thres = 0.2);
344 
345 }
346 
volm_dsm_ground_filter_mgf_process_cons(bprb_func_process & pro)347 bool volm_dsm_ground_filter_mgf_process_cons(bprb_func_process& pro)
348 {
349   using namespace volm_dsm_ground_filter_mgf_process_globals;
350   // this process takes 5 inputs
351   std::vector<std::string> input_types_(n_inputs_);
352   input_types_[0] = "vil_image_view_base_sptr";   // input DSM image
353   input_types_[1] = "float";                      // local window size
354   input_types_[2] = "float";                      // elevation threshold
355   input_types_[3] = "float";                      // slope threshold in degree
356   input_types_[4] = "float";                      // pixel resolution in meters.  default = 1.0 meter
357   std::vector<std::string> output_types_(n_outputs_);
358   output_types_[0] = "vil_image_view_base_sptr";  // filtered ground/non-ground image
359   output_types_[1] = "vil_image_view_base_sptr";  // interpolated ground digital elevation model
360   return pro.set_input_types(input_types_) && pro.set_output_types(output_types_);
361 }
362 
volm_dsm_ground_filter_mgf_process(bprb_func_process & pro)363 bool volm_dsm_ground_filter_mgf_process(bprb_func_process& pro)
364 {
365   using namespace volm_dsm_ground_filter_mgf_process_globals;
366   if (!pro.verify_inputs()) {
367     std::cerr << pro.name() << ": Wrong Inputs!\n";
368     return false;
369   }
370   // get the inputs
371   unsigned in_i = 0;
372   vil_image_view_base_sptr dsm_img_res = pro.get_input<vil_image_view_base_sptr>(in_i++);
373   auto window_size = pro.get_input<float>(in_i++);
374   auto elev_thres = pro.get_input<float>(in_i++);
375   auto slop_thres_deg = pro.get_input<float>(in_i++);
376   auto pixel_res = pro.get_input<float>(in_i++);
377 
378   // compute slope threshold
379   float slop_thres = std::tan(slop_thres_deg / vnl_math::deg_per_rad) * pixel_res;
380   // load the image
381   auto* in_img = dynamic_cast<vil_image_view<float>*>(dsm_img_res.ptr());
382   if (!in_img) {
383     std::cerr << pro.name() << ": Unsupported image pixel format -- " << dsm_img_res->pixel_format() << ", only float is supported!\n";
384     return false;
385   }
386   unsigned ni = in_img->ni();
387   unsigned nj = in_img->nj();
388   if (ni < 2 || nj < 2) {
389     std::cerr << pro.name() << ": Input image is too small -- ni: " << ni << ", nj: " << nj << "!\n";
390     return false;
391   }
392   // compute slope along 4 scan line direction
393   float invalid_slope = -1E3f;
394   vil_image_view<float> lr_img(ni, nj);  // slope value scan from left to right
395   vil_image_view<float> rl_img(ni, nj);  // slope value scan from right to left
396   vil_image_view<float> tb_img(ni, nj);  // slope value scan from top to bottom
397   vil_image_view<float> bt_img(ni, nj);  // slope value scan from bottom to top
398   lr_img.fill(invalid_slope);
399   rl_img.fill(invalid_slope);
400   tb_img.fill(invalid_slope);
401   bt_img.fill(invalid_slope);
402 
403   for (unsigned j = 0; j < nj; j++) {
404     for (unsigned i = 1; i < (ni-1); i++) {
405       lr_img(i,j) = (*in_img)(i,j) - (*in_img)(i-1,j);
406       rl_img(i,j) = (*in_img)(i,j) - (*in_img)(i+1,j);
407     }
408   }
409   for (unsigned j = 0; j < nj; j++) {
410     rl_img(0,j) = (*in_img)(0,j) - (*in_img)(1,j);
411     lr_img(ni-1, j) = (*in_img)(ni-1,j) - (*in_img)(ni-2,j);
412   }
413   for (unsigned i = 0; i < ni; i++) {
414     for (unsigned j = 1; j < (nj-1); j++) {
415       tb_img(i,j) = (*in_img)(i,j) - (*in_img)(i,j-1);
416       bt_img(i,j) = (*in_img)(i,j) - (*in_img)(i,j+1);
417     }
418   }
419   for (unsigned i = 0; i < ni; i++) {
420     tb_img(i, nj-1) = (*in_img)(i,nj-1) - (*in_img)(i,nj-2);
421     bt_img(i, 0) = (*in_img)(i,0) - (*in_img)(i,1);
422   }
423 
424   auto* grd_mask = new vil_image_view<vxl_byte>(ni, nj);
425   grd_mask->fill(0);
426 
427   // generate initial ground mask
428   auto nw_i = (unsigned)std::floor(ni/window_size + 0.5f);
429   auto nw_j = (unsigned)std::floor(nj/window_size + 0.5f);
430 
431   // find minimum as ground pixel for each window
432   std::map<std::pair<unsigned, unsigned>, float> local_grd_elev;
433   vil_image_view<float> grd_tmp(ni, nj);
434   grd_tmp.fill(1E6);
435   for (unsigned widx_i = 0; widx_i < nw_i; widx_i++) {
436     unsigned s_ni, e_ni;
437     s_ni = widx_i * window_size;
438     e_ni = (widx_i+1) * window_size;
439     if (e_ni > ni)
440       e_ni = ni;
441     for (unsigned widx_j = 0; widx_j < nw_j; widx_j++) {
442       unsigned s_nj, e_nj;
443       s_nj = widx_j * window_size;
444       e_nj = (widx_j+1) * window_size;
445       if (e_nj > nj)
446         e_nj = nj;
447       float min_elev = 1.0e5f;
448       unsigned min_i = ni;
449       unsigned min_j = nj;
450       for (unsigned i = s_ni; i < e_ni; i++) {
451         for (unsigned j = s_nj; j < e_nj; j++) {
452           if ( (*in_img)(i,j) < min_elev ) {
453             min_elev = (*in_img)(i,j);
454             min_i = i; min_j = j;
455           }
456         }
457       }
458       (*grd_mask)(min_i, min_j) = 127;  // ground
459       local_grd_elev.insert(std::pair<std::pair<unsigned, unsigned>, float>(std::pair<unsigned, unsigned>(widx_i, widx_j), min_elev));
460       for (unsigned i = s_ni; i < e_ni; i++)
461         for (unsigned j = s_nj; j < e_nj; j++)
462           grd_tmp(i,j) = min_elev;
463     }
464   }
465 
466   std::cout << "Start MGF ground filtering with elevation threshold: " << elev_thres
467            << ", slope threshold: " << slop_thres << "(" << slop_thres_deg << " degree), local neighbor size: "
468            << window_size << std::endl;
469   std::cout << "  scan from left to right..." << std::flush << std::endl;
470 
471   // start left to right scan
472   for (unsigned j = 0; j < nj; j++)
473   {
474     for (unsigned i = 0; i < ni; i++) {
475       float elev = (*in_img)(i, j);
476       float elev_diff = elev - grd_tmp(i,j);
477       if (elev_diff > elev_thres) {
478         (*grd_mask)(i,j) = 255;  // non-ground
479         continue;
480       }
481       // check slope value from left to right
482       float lr_slope = lr_img(i,j);
483       if (lr_slope < invalid_slope+1)
484         continue;
485       if (lr_slope > slop_thres) {
486         (*grd_mask)(i,j) = 255;  // non-ground
487       }
488       else if (lr_slope <= slop_thres && lr_slope >= 0) {
489         (*grd_mask)(i,j) = (*grd_mask)(i-1,j);  // copy status of previous points
490       }
491       else {
492         // find the nearest ground plane height
493         float grd_elev;
494         if (!nearest_ground_elev(*in_img, *grd_mask, i, j, grd_elev)) {
495           std::cerr << pro.name() << ": search nearest ground height value failed for pixel (" << i << ',' << j << ") as scanning from left to right!\n";
496           return false;
497         }
498         float elev_diff_nearest = elev - grd_elev;
499         if (elev_diff_nearest > elev_thres)
500           (*grd_mask)(i,j) = 255;  // non-ground
501         else
502           (*grd_mask)(i,j) = 127;  // ground
503       }
504     }
505   }
506 
507   // start right to left scan
508   std::cout << "  scan from right to left..." << std::flush << std::endl;
509   for (unsigned j = 0; j < nj; j++)
510   {
511     for (int i = ni-1; i >= 0; i--) {
512       float elev = (*in_img)(i, j);
513       float elev_diff = elev - grd_tmp(i,j);
514       if (elev_diff > elev_thres) {
515         (*grd_mask)(i,j) = 255;  // non-ground
516         continue;
517       }
518       // check slope value from right to left
519       float rl_slope = rl_img(i,j);
520       if (rl_slope < invalid_slope+1)  // keep previous status
521         continue;
522       if (rl_slope > slop_thres) {
523         (*grd_mask)(i,j) = 255;  // non-ground
524       }
525       else if (rl_slope >= 0 && rl_slope <= slop_thres) {
526         if ((*grd_mask)(i+1,j))
527           (*grd_mask)(i,j) = (*grd_mask)(i+1,j);  // copy status of previous points if previous point is not un-certain
528       }
529       else {
530         // find the nearest ground plane height
531         float grd_elev;
532         if (!nearest_ground_elev(*in_img, *grd_mask, i, j, grd_elev)) {
533           std::cerr << pro.name() << ": search nearest ground height value failed for pixel (" << i << ',' << j << ") as scanning from left to right!\n";
534           return false;
535         }
536         float elev_diff_nearest = elev - grd_elev;
537         if (elev_diff_nearest > elev_thres)
538           (*grd_mask)(i,j) = 255;  // non-ground
539         else
540           (*grd_mask)(i,j) = 127;  // ground
541       }
542     }
543   }
544 
545   // perform linear regression along horizontal scan line
546   std::cout << "  perform linear regression along image row..." << std::flush << std::endl;
547   unsigned n_size_horizontal = 100;
548   if (n_size_horizontal >= ni)
549     n_size_horizontal = ni;
550   ground_linear_regression(*in_img, *grd_mask, true, n_size_horizontal);
551 
552   // start top to bottom scan
553   std::cout << "  scan from top to bottom..." << std::flush << std::endl;
554   for (unsigned i = 0; i < ni; i++)
555   {
556     for (unsigned j = 0; j < nj; j++) {
557       float elev = (*in_img)(i, j);
558       float elev_diff = elev - grd_tmp(i,j);
559       if (elev_diff > elev_thres) {
560         (*grd_mask)(i,j) = 255;  // non-ground
561         continue;
562       }
563       // check slope value from top to bottom
564       float tb_slope = tb_img(i,j);
565       if (tb_slope < invalid_slope+1)  // keep previous status
566         continue;
567       if (tb_slope > slop_thres)  // non-ground (should be building edge)
568         (*grd_mask)(i,j) = 255;
569       else if (tb_slope >= 0 && tb_slope <= slop_thres) {
570         if ((*grd_mask)(i,j-1))
571           (*grd_mask)(i,j) = (*grd_mask)(i,j-1);  // copy status of previous point if previous point is not un-certain
572       }
573       else {
574         // find the nearest ground plane height
575         float grd_elev;
576         if (!nearest_ground_elev(*in_img, *grd_mask, i, j, grd_elev)) {
577           std::cerr << pro.name() << ": search nearest ground height value failed for pixel (" << i << ',' << j << ") as scanning from left to right!\n";
578           return false;
579         }
580         float elev_diff_nearest = elev - grd_elev;
581         if (elev_diff_nearest > elev_thres)
582           (*grd_mask)(i,j) = 255;  // non-ground
583         else
584           (*grd_mask)(i,j) = 127;  // ground
585       }
586     }
587   }
588 
589   // start top to bottom scan
590   std::cout << "  scan from bottom to top..." << std::flush << std::endl;
591   for (unsigned i = 0; i < ni; i++)
592   {
593     for (int j = nj-1; j >= 0; j--) {
594       float elev = (*in_img)(i, j);
595       float elev_diff = elev - grd_tmp(i,j);
596       if (elev_diff > elev_thres) {
597         (*grd_mask)(i,j) = 255;  // non-ground
598         continue;
599       }
600       // check slope value from top to bottom
601       float bt_slope = bt_img(i,j);
602       if (bt_slope > slop_thres)  // non-ground (should be building edge)
603         (*grd_mask)(i,j) = 255;
604       else if (bt_slope >= 0 && bt_slope <= slop_thres) {
605         if ((*grd_mask)(i,j+1))
606           (*grd_mask)(i,j) = (*grd_mask)(i,j+1);  // copy status of previous point if previous point is not un-certain
607       }
608       else {
609         // find the nearest ground plane height
610         float grd_elev;
611         if (!nearest_ground_elev(*in_img, *grd_mask, i, j, grd_elev)) {
612           std::cerr << pro.name() << ": search nearest ground height value failed for pixel (" << i << ',' << j << ") as scanning from left to right!\n";
613           return false;
614         }
615         float elev_diff_nearest = elev - grd_elev;
616         if (elev_diff_nearest > elev_thres)
617           (*grd_mask)(i,j) = 255;  // non-ground
618         else
619           (*grd_mask)(i,j) = 127;  // ground
620       }
621     }
622   }
623 
624   // perform linear regression along vertical scan line
625   std::cout << "  perform linear regression along image column..." << std::flush << std::endl;
626   unsigned n_size_vertical = 100;
627   if (n_size_vertical >= ni)
628     n_size_vertical = ni;
629   ground_linear_regression(*in_img, *grd_mask, false, n_size_vertical);
630 
631   std::cout << "Start to generate ground DEM tile from filtering..." << std::flush << std::endl;
632   // generate ground image from ground mask -- tale minimum elevation points as sample points every 10 pixels
633   vil_image_view<float> sample_img(ni, nj);
634   vil_image_view<vxl_byte> sample_grd(ni, nj);
635   sample_img.fill(0.0f);
636   sample_grd.fill(0);
637   int sample_size = (int)std::floor(10.0/pixel_res+0.5);
638   for (int r = sample_size; r < (int)(ni-sample_size); r += sample_size) {
639     for (int c = sample_size; c < (int)(nj-sample_size); c += sample_size) {
640       // find the minimum elevation among ground pixels
641       float min = 1E7f;
642       int pt_i, pt_j;
643       bool found = false;
644       for (int k = -sample_size; k < sample_size; k++) {
645         for (int m = -sample_size; m < sample_size; m++) {
646           int i = r + k;
647           int j = c + m;
648           if (i < 0 || j < 0 || i >= (int)ni || j >= (int)nj)
649             continue;
650           if ( (*grd_mask)(i,j) != 127)
651             continue;
652           if ( (*in_img)(i,j) < min) {
653             pt_i = i;  pt_j = j;
654             min = (*in_img)(i,j);
655             found = true;
656           }
657         }
658       }
659       if (found) {
660         sample_grd(r, c) = 127;
661         sample_img(r, c) = min;
662       }
663     }
664   }
665 
666   // perform linear regression again to remove any specious ground pixels
667   ground_linear_regression(sample_img, sample_grd, true,  ni, 0.5);
668   ground_linear_regression(sample_img, sample_grd, false, nj, 0.5);
669 
670   std::vector<vgl_point_3d<double> > src_pts, dst_pts;
671   for (unsigned i = 0; i < ni; i++)
672     for (unsigned j = 0; j < nj; j++)
673       if (sample_grd(i,j) == 127) {
674         vgl_point_3d<double> src_pt(i, j, (*in_img)(i,j));
675         vgl_point_3d<double> dst_pt(i, j, sample_img(i,j));
676         src_pts.push_back(src_pt);
677         dst_pts.push_back(dst_pt);
678       }
679   unsigned grd_pt_cnt = 0;
680   for (unsigned i = 0; i < ni; i++)
681     for (unsigned j = 0; j < nj; j++)
682       if ( (*grd_mask)(i,j) == 127 )
683         grd_pt_cnt += 1;
684   std::cout << "  Total image pixels: " << ni*nj << ", number of ground pixels: " << grd_pt_cnt
685             << ", number of sampled ground pixels: " << src_pts.size() << std::endl;
686 
687   // construct spline object
688   mbl_thin_plate_spline_3d tps;
689   tps.build(src_pts, dst_pts);
690 
691   // apply to other points
692   auto* grd_img = new vil_image_view<float>(ni, nj);
693   grd_img->fill(-1.0f);
694   for (unsigned i = 0; i < ni; i++) {
695     for (unsigned j = 0; j < nj; j++) {
696       vgl_point_3d<double> p(i,j,(*in_img)(i,j));
697       vgl_point_3d<double> new_p = tps(p);
698       (*grd_img)(i,j) = new_p.z();
699     }
700   }
701 
702   // output
703   pro.set_output_val<vil_image_view_base_sptr>(0, grd_mask);
704   pro.set_output_val<vil_image_view_base_sptr>(1, grd_img);
705   return true;
706 }
707 
nearest_ground_elev(vil_image_view<float> const & img,vil_image_view<vxl_byte> const & grd_mask,unsigned const & i,unsigned const & j,float & ground_elev)708 bool volm_dsm_ground_filter_mgf_process_globals::nearest_ground_elev(vil_image_view<float> const& img,
709                                                                      vil_image_view<vxl_byte> const& grd_mask,
710                                                                      unsigned const& i, unsigned const& j,
711                                                                      float& ground_elev)
712 {
713   if (img.ni() != grd_mask.ni() || img.nj() != grd_mask.nj())
714     return false;
715 
716   int ni = static_cast<int>(img.ni());
717   int nj = static_cast<int>(img.nj());
718   int search_range = ni;
719   if (search_range > nj)
720     search_range = nj;
721 
722   bool found = false;
723   for (int radius = 1; (radius < search_range); radius++)
724   {
725     int start_i = i - radius;  int end_i = i + radius;
726     int start_j = j - radius;  int end_j = j + radius;
727     for (int ii = start_i; (ii < end_i && !found); ii++) {
728       for (int jj = start_j; (jj < end_j && !found); jj++) {
729         if (ii < 0 || jj < 0 || ii >= ni || jj >= nj)
730           continue;
731         if ( grd_mask(ii,jj) == 127) {
732           found = true;
733           ground_elev = img(ii,jj);
734           return found;
735         }
736       }
737     }
738   }
739   return found;
740 }
741 
ground_linear_regression(vil_image_view<float> const & img,vil_image_view<vxl_byte> & grd_mask,bool const & is_horizontal,float const & neighbor_size,float const & rsm_thres)742 bool volm_dsm_ground_filter_mgf_process_globals::ground_linear_regression(vil_image_view<float> const& img,
743                                                                           vil_image_view<vxl_byte>& grd_mask,
744                                                                           bool const& is_horizontal,
745                                                                           float const& neighbor_size,
746                                                                           float const& rsm_thres)
747 {
748   unsigned ni = img.ni();
749   unsigned nj = img.nj();
750   if (ni != grd_mask.ni() || nj != grd_mask.nj())
751     return false;
752   unsigned nw;
753   unsigned num_lines;
754   unsigned num_pts;
755   if (is_horizontal) {
756     num_lines = nj;
757     num_pts = ni;
758     nw = (unsigned)std::floor(ni/neighbor_size + 0.5);
759   }
760   else {
761     num_lines = ni;
762     num_pts = nj;
763     nw = (unsigned)std::floor(nj/neighbor_size + 0.5);
764   }
765   for (unsigned j = 0; j < num_lines; j++)
766   {
767     if (j == 250)
768       unsigned stop_cnt = 1;
769     for (unsigned widx = 0; widx < nw; widx++) {
770       unsigned s_pt, e_pt;
771       s_pt = widx*neighbor_size; e_pt = (widx+1)*neighbor_size;
772       if (e_pt > num_pts) e_pt = num_pts;
773       // fit the line segment
774       vgl_line_2d_regression<float> reg;
775       std::vector<unsigned> grd_pts;
776       std::vector<float> grd_elevs;
777       for (unsigned i = s_pt; i < e_pt; i++) {
778         if (is_horizontal) {
779           if (grd_mask(i,j) == 127) {
780             reg.increment_partial_sums(i, img(i,j));
781             grd_pts.push_back(i);
782             grd_elevs.push_back(img(i,j));
783           }
784         } else {
785           if (grd_mask(j,i) == 127) {
786             reg.increment_partial_sums(i, img(j,i));
787             grd_pts.push_back(i);
788             grd_elevs.push_back(img(j,i));
789           }
790         }
791       }
792       reg.fit();
793       vgl_line_2d<float> line = reg.get_line();
794       double rms = reg.get_rms_error();
795       std::vector<unsigned> after_grd_pts;
796       std::vector<float> after_gre_elev;
797       // remove points that have more than three times of the standard deviation of the regression
798       for (unsigned int i : grd_pts) {
799         float elev;
800         if (is_horizontal) elev = img(i,j);
801         else               elev = img(j,i);
802         float elev_est = (-line.a()*i-line.c()) / line.b();
803         float elev_diff = elev-elev_est;
804         if ((elev-elev_est) >= rsm_thres*rms) {
805           if (is_horizontal) grd_mask(i,j) = 255;
806           else               grd_mask(j,i) = 255;
807         }
808       }
809     } // end of loop along current line
810   } // end of all scan lines
811 
812   return true;
813 }
814