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