1 // This is contrib/brl/bbas/volm/volm_refine_height_map.cxx
2 //:
3 // \file
4 // \brief executable to refine height map using land classification information
5 //        Note in this executable, 'road' and 'parks' are used to evaluate the ground level height
6 // \author Yi Dong
7 // \date Dec 05, 2013
8 // \verbatim
9 //  Modifications
10 //   <none yet>
11 // \endverbatim
12 //
13 
14 #include <algorithm>
15 #include <iostream>
16 #include <utility>
17 #include <vil/algo/vil_region_finder.h>
18 #include "vil/vil_image_view.h"
19 #include "vil/vil_load.h"
20 #include "vil/vil_save.h"
21 #include <volm/volm_category_io.h>
22 #include <volm/volm_geo_index2.h>
23 #include <volm/volm_io.h>
24 #include <volm/volm_io_tools.h>
25 #include <volm/volm_osm_objects.h>
26 #include "vul/vul_arg.h"
27 #include "vul/vul_file.h"
28 #ifdef _MSC_VER
29 #  include "vcl_msvc_warnings.h"
30 #endif
31 #include "vul/vul_file_iterator.h"
32 
33 
error(std::string log_file,const std::string & msg)34 static void error(std::string log_file, const std::string& msg)
35 {
36   std::cerr << msg;
37   volm_io::write_post_processing_log(std::move(log_file), msg);
38 }
39 
float_equal(float const & a,float const & b)40 static bool float_equal(float const& a, float const& b)
41 {
42   return ((a-b)*(a-b) < 1E-8);
43 }
44 
average(std::vector<float> const & values)45 static float average(std::vector<float> const& values)
46 {
47   float sum = 0.0f;
48   for (float value : values)
49     sum += value;
50   return sum/values.size();
51 }
52 
find_nearest_building(unsigned const & i,unsigned const & j,int const & radius,std::map<unsigned,std::pair<std::vector<unsigned>,std::vector<unsigned>>> const & buildings,unsigned & building_id)53 static bool find_nearest_building(unsigned const& i, unsigned const& j,
54                                   int const& radius,
55                                   std::map<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > > const& buildings,
56                                   unsigned & building_id)
57 {
58   unsigned num_nbrs = 8;
59 
60   auto mit = buildings.begin();
61   for( ; mit != buildings.end(); ++mit)
62   {
63     std::vector<std::pair<int, int> > b_pixels;
64     auto num_pixels = (unsigned)mit->second.second.size();
65     for (unsigned p_idx = 0; p_idx < num_pixels; p_idx++)
66       b_pixels.emplace_back(mit->second.first[p_idx], mit->second.second[p_idx]);
67 
68     for (int r_idx = 1; r_idx < radius; r_idx++)
69     {
70       int nbrs8_delta[8][2] = { { r_idx, 0}, { r_idx,-r_idx}, { 0,-r_idx}, {-r_idx,-r_idx},
71                                 {-r_idx, 0}, {-r_idx, r_idx}, { 0, r_idx}, { r_idx, r_idx} };
72       for (unsigned c = 0; c < num_nbrs; c++) {
73         int nbr_i = i + nbrs8_delta[c][0];
74         int nbr_j = j + nbrs8_delta[c][1];
75         if (std::find(b_pixels.begin(), b_pixels.end(), std::pair<int,int>(nbr_i, nbr_j)) != b_pixels.end())
76         {
77           building_id = mit->first;
78           return true;
79         }
80       }
81     }
82   }
83   building_id = 0;
84   return false;
85 }
86 
standard_devation(std::vector<float> const & values,float const & mean)87 static float standard_devation(std::vector<float> const& values, float const& mean)
88 {
89   float dev_sum = 0.0f;
90   for (float value : values)
91     dev_sum += (value - mean) * (value - mean);
92   return std::sqrt(dev_sum/values.size());
93 }
94 
95 //: obtain the building pixels inside a window from the classification image (forward declaration)
96 static bool obtain_buildings(vil_image_view<vxl_byte> const& c_img,
97                              unsigned const& start_ni, unsigned const& start_nj,
98                              unsigned const& end_ni,   unsigned const& end_nj,
99                              std::map<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > >& buildings,
100                              std::map<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > >& flat_regions,
101                              std::vector<std::pair<unsigned, unsigned> >& non_buildings);
102 
103 static bool refine_building_by_median(vil_image_view<float> const& h_img,
104                                       std::map<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > > const& buildings,
105                                       vil_image_view<float>& refined_img);
106 
107 static bool refine_building_by_median_divided(vil_image_view<float> const& h_img,
108                                               vil_image_view<vxl_byte> const& c_img,
109                                               std::map<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > > const& buildings,
110                                               unsigned const& size,
111                                               vil_image_view<float>& refined_img);
112 
113 static bool refine_building(float const& ratio,
114                             float const& height_threshold,
115                             vil_image_view<float> const& h_img,
116                             vil_image_view<vxl_byte> & refined_c_img,
117                             vil_image_view<float>& refined_h_img);
118 
119 static float neighbor_height(std::map<std::pair<unsigned, unsigned>, float> const& window_height,
120                              unsigned const& w_idx_i,
121                              unsigned const& w_idx_j,
122                              unsigned const& w_size);
123 
124 
125 #if 1
126 // refine satellite height map using building footprint obtained both from satellite classification map and OSM buildings/roads
127 // the ground height here is defined by road obtained from osm
main(int argc,char ** argv)128 int main(int argc, char** argv)
129 {
130   vul_arg<std::string> height_img_name("-height", "height map about to be refined", "");
131   vul_arg<std::string> height_cam_name("-height-cam", "tfw camera for height map", "");
132   vul_arg<std::string> out_height("-out-height", "output filename of the refined height map", "");
133   vul_arg<std::string> out_class("-out-class", "output filename of the refined class map", "");
134   vul_arg<unsigned> dx("-x", "refined window size in pixel unit", 60);
135   vul_arg<unsigned> dy("-y", "refined window size in pixel unit", 60);
136   vul_arg<unsigned> iteration("-iter","number of refinement iteration", 100);
137   vul_arg<float> ratio("-ratio", "shape factor to control morphological operation", 0.5);
138   vul_arg<float> height_threshold("-thres", "threshold in height difference", 5);
139   vul_arg<float> tall_building_thres("-tall","height threshold for tall building category",60);
140   vul_arg<std::string> class_map_folder("-2d-class-root", "folder where all generated 2d map stored","");
141   vul_arg<std::string> out_class_color("-out-class-color","output color class map","");
142   vul_arg_parse(argc, argv);
143 
144 
145   if (height_img_name().compare("") == 0 || height_cam_name().compare("") == 0 ||
146       out_height().compare("") == 0 || out_class().compare("") == 0 || class_map_folder().compare("") == 0)
147   {
148     vul_arg_display_usage_and_exit();
149     return false;
150   }
151   std::stringstream log_file;
152   std::stringstream log;
153   log_file << out_height() << "/log_refine_height_map_scene.xml";
154 
155   // load height map image (float format)
156   if (!vul_file::exists(height_img_name())) {
157     log << "error: can not find height map image: " << height_img_name() << '\n';
158     error(log_file.str(), log.str());
159     return volm_io::EXE_ARGUMENT_ERROR;
160   }
161   vil_image_view<float> h_img = vil_load(height_img_name().c_str());
162 
163   // image size needs to be same
164   unsigned ni = h_img.ni();
165   unsigned nj = h_img.nj();
166   unsigned num_w_i = ni/dx() + 1;
167   unsigned num_w_j = nj/dx() + 1;
168 
169   // load height map camera
170   if (!vul_file::exists(height_cam_name())) {
171     log << "ERROR: can not find tfw camera file for height map: " << height_cam_name() << '\n';
172     error(log_file.str(), log.str());
173     return volm_io::EXE_ARGUMENT_ERROR;
174   }
175   vpgl_lvcs_sptr dummy_lvcs = new vpgl_lvcs;
176   vpgl_geo_camera* h_cam;
177   vpgl_geo_camera::init_geo_camera(height_cam_name(), dummy_lvcs, 0, 0, h_cam);
178 
179   std::cout << "Start refined the height map..." << std::endl;
180   std::cout << "  img size: " << ni << 'x' << nj << std::endl;
181   std::cout << "  window size: " << dx() << 'x' << dy() << std::endl;
182   std::cout << "  window num: " << num_w_i << 'x' << num_w_j << std::endl;
183 
184   // read all the geo_index tree for class map
185   double class_map_min_size;
186   std::vector<std::string> class_map_texts;
187   std::string file_glob = class_map_folder() + "/p1b_wr*_tile*.txt";
188   std::vector<volm_geo_index2_node_sptr> class_roots;
189   for (vul_file_iterator fn = file_glob.c_str(); fn; ++fn) {
190     volm_geo_index2_node_sptr class_root = volm_geo_index2::read_and_construct<volm_osm_object_ids_sptr>(fn(), class_map_min_size);
191     class_roots.push_back(class_root);
192     std::cout << " geo index is created from file " << fn() << std::endl;
193   }
194 
195   // find the overlap resources
196   std::vector<volm_geo_index2_node_sptr> class_leaves;
197   double h_lowerleft_lon, h_lowerleft_lat;
198   h_cam->img_to_global(0, nj-1, h_lowerleft_lon, h_lowerleft_lat);
199   double h_upperrght_lon, h_upperrght_lat;
200   h_cam->img_to_global(ni-1, 0, h_upperrght_lon, h_upperrght_lat);
201   vgl_box_2d<double> h_bbox(h_lowerleft_lon, h_upperrght_lon, h_lowerleft_lat, h_upperrght_lat);
202   for (const auto & class_root : class_roots) {
203     std::vector<volm_geo_index2_node_sptr> leaves;
204     leaves.clear();
205     volm_geo_index2::get_leaves(class_root, leaves, h_bbox);
206     for (const auto & leave : leaves)
207       class_leaves.push_back(leave);
208   }
209 
210   if (class_leaves.empty()) {
211     log << "ERROR: can not find any overlapped 2d class map for current height map: " << height_img_name() << " -- " << h_bbox << '\n';
212     error(log_file.str(), log.str());
213     return volm_io::EXE_ARGUMENT_ERROR;
214   }
215 
216   unsigned num_leaves = class_leaves.size();
217   std::cout << "there are " << num_leaves << " class maps intersect with height map " << h_bbox << std::endl;
218   for (unsigned l_idx = 0; l_idx < num_leaves; l_idx++)
219     std::cout <<"\t " << class_leaves[l_idx]->get_string() << " --- " << class_leaves[l_idx]->extent_ << std::endl;
220 
221   // load the associated image resource
222   std::map<std::string, vil_image_view<vxl_byte> > class_img_map;
223   std::map<std::string, vpgl_geo_camera*> class_cam_map;
224   for (unsigned l_idx = 0; l_idx < num_leaves; l_idx++) {
225     double lon_min, lat_min, lon_max, lat_max;
226     volm_geo_index2_node_sptr leaf = class_leaves[l_idx];
227     lon_min = leaf->extent_.min_x();  lat_min = leaf->extent_.min_y();
228     lon_max = leaf->extent_.max_x();  lat_max = leaf->extent_.max_y();
229     double scale_x = lon_max-lon_min;
230     double scale_y = lat_max-lat_min;
231     std::string hemisphere, direction;
232     if (lon_min < 0)  direction  = "W";
233     else              direction  = "E";
234     if (lat_min < 0)  hemisphere = "S";
235     else              hemisphere = "N";
236     std::stringstream img_name;
237     img_name << class_map_folder() << "/Sat2dMap_" << hemisphere << std::setprecision(12) << lat_min
238                                    << direction << std::setprecision(12) << lon_min
239                                    << "_S" << scale_x << 'x' << scale_y << ".tif";
240     std::stringstream cam_name;
241     cam_name << class_map_folder() << "/Sat2dMap_" << hemisphere << std::setprecision(12) << lat_min
242                                    << direction << std::setprecision(12) << lon_min
243                                    << "_S" << scale_x << 'x' << scale_y << ".tfw";
244     if (!vul_file::exists(img_name.str())) {
245       log << "ERROR: can not find class 2d map image resource: " << img_name.str() << '\n';  error(log_file.str(), log.str());
246       return volm_io::EXE_ARGUMENT_ERROR;
247     }
248     if (!vul_file::exists(cam_name.str())) {
249       log << "ERROR: can not find class 2d map camera tfw file: " << cam_name.str() << '\n';  error(log_file.str(), log.str());
250       return volm_io::EXE_ARGUMENT_ERROR;
251     }
252 
253     volm_img_info info;
254     volm_io_tools::load_lidar_img(img_name.str(), info, true, false, true, cam_name.str());
255     //volm_io_tools::load_geotiff_image(img_name.str(), info, true);
256     auto* img_src = dynamic_cast<vil_image_view<vxl_byte>*>(info.img_r.ptr());
257     class_img_map.insert(std::pair<std::string, vil_image_view<vxl_byte> >(leaf->get_string(), *img_src));
258     class_cam_map.insert(std::pair<std::string, vpgl_geo_camera*>(leaf->get_string(), info.cam));
259   }
260 
261   // create a class image from overlapped class map
262   vil_image_view<vxl_byte> c_img(ni, nj);
263   c_img.fill(0);
264   for (unsigned i = 0; i < ni; i++) {
265     for (unsigned j = 0; j < nj; j++) {
266       double lon, lat;
267       h_cam->img_to_global(i,j,lon,lat);
268       for (unsigned l_idx = 0; l_idx < num_leaves; l_idx++) {
269         //if (!class_leaves[l_idx]->extent_.contains(lon, lat))
270         //  continue;
271         double u, v;
272         class_cam_map[class_leaves[l_idx]->get_string()]->global_to_img(lon, lat, 0.0, u, v);
273         auto uu = (unsigned)std::floor(u+0.5);
274         auto vv = (unsigned)std::floor(v+0.5);
275         if (uu > 0 && vv > 0 && uu < class_img_map[class_leaves[l_idx]->get_string()].ni() && vv < class_img_map[class_leaves[l_idx]->get_string()].nj())
276           c_img(i,j) = (class_img_map[class_leaves[l_idx]->get_string()])(uu,vv);
277       }
278     }
279   }
280 
281   // start to refine the building
282   vil_image_view<vxl_byte> refined_c_img;
283   refined_c_img.deep_copy(c_img);
284   std::string refined_c_img_name = out_class();
285   vil_image_view<float> refined_h_img(ni, nj);
286   refined_h_img.deep_copy(h_img);
287   std::string refined_h_img_name = out_height();
288 
289 #if 0
290   std::string c_img_before_refine = vul_file::strip_extension(refined_c_img_name) + "_cropped.tif";
291   vil_image_view<vil_rgb<vxl_byte> > out_class_img(ni, nj, 1);
292   out_class_img.fill(volm_osm_category_io::volm_land_table[0].color_);
293   for (unsigned i = 0; i < ni; i++) {
294     for (unsigned j = 0; j < nj; j++) {
295       out_class_img(i,j) = volm_osm_category_io::volm_land_table[c_img(i,j)].color_;
296     }
297   }
298   vil_save(out_class_img, c_img_before_refine.c_str());
299 #endif
300 
301   // maps to record the ground height of each window (key is its window id)
302   std::map<std::pair<unsigned, unsigned>, float> window_min_height;
303   window_min_height.clear();
304 
305   // map to record the non-building pixels of each window
306   std::map<std::pair<unsigned, unsigned>, std::vector<std::pair<unsigned, unsigned> > > non_building_regions;
307 
308   // record all the building median heights in the height image
309   std::vector<float> all_buidling_heights;
310 
311   std::cout << "Start to refine buildings";
312   for (unsigned w_idx_i = 0; w_idx_i < num_w_i; w_idx_i++)
313   {
314     unsigned start_ni, end_ni;
315     start_ni = w_idx_i*dx(); end_ni = (w_idx_i+1)*dx();
316     for (unsigned w_idx_j = 0; w_idx_j < num_w_j; w_idx_j++)
317     {
318       std::cout << '.';
319       std::cout.flush();
320       std::pair<unsigned,unsigned> window_key(w_idx_i, w_idx_j);
321       // obtain window pixels
322       unsigned start_nj, end_nj;
323       start_nj = w_idx_j*dy(); end_nj = (w_idx_j+1)*dy();
324       // obtain building pixels, road pixels and non-building pixels (key is a unique value from upper left of the pixel)
325       std::map<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > > buildings;
326       std::map<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > > flat_regions;
327       std::vector<std::pair<unsigned, unsigned> > non_buildings;
328       if (!obtain_buildings(c_img, start_ni, start_nj, end_ni, end_nj, buildings, flat_regions, non_buildings)) {
329         log << "error: building extraction failed for window (" << start_ni << 'x' << start_nj << ") (to " << end_ni << 'x' << end_nj << ")\n";
330         error(log_file.str(), log.str());
331         return volm_io::EXE_ARGUMENT_ERROR;
332       }
333 
334       // flatten the building height by their median
335       // flat building super pixels values (use one of the refinement scheme below)
336 #if 0
337       // refine the whole building superpixel by a constant median value
338       if (!refine_building_by_median(h_img, buildings, refined_h_img))
339       {
340         log << "error: refine height image using median height failed\n";
341         error(log_file.str(), log.str());
342         return volm_io::EXE_ARGUMENT_ERROR;
343       }
344 #endif
345 #if 1
346       // refine the building roof by dividing building super pixels into small region
347       unsigned sub_pixel_size = 10;  // a 5x5 pixel window to split the building super pixels
348       if (!refine_building_by_median_divided(h_img, c_img, buildings, sub_pixel_size, refined_h_img)) {
349         log << "error: refine height image using median height failed\n";
350         error(log_file.str(), log.str());
351         return volm_io::EXE_ARGUMENT_ERROR;
352       }
353 
354 #endif
355 
356       std::pair<std::pair<unsigned, unsigned>, std::vector<std::pair<unsigned, unsigned> > > tmp_pair(std::pair<unsigned, unsigned>(w_idx_i, w_idx_j), non_buildings);
357       non_building_regions.insert(tmp_pair);
358 
359       // obtain the ground height from flat region -- using the minimum height for current window
360       std::vector<float> flat_heights;
361       for (auto & flat_region : flat_regions)
362       {
363         auto num_pts = (unsigned)flat_region.second.first.size();
364         for (unsigned ri = 0; ri < num_pts; ri++)
365           flat_heights.push_back(h_img(flat_region.second.first[ri], flat_region.second.second[ri]));
366       }
367       if (flat_heights.size() != 0) {
368         std::sort(flat_heights.begin(), flat_heights.end());
369         //float height_median = flat_heights[flat_heights.size()/2];
370         float height_median = flat_heights[0];
371         window_min_height.insert(std::pair<std::pair<unsigned, unsigned>, float>(std::pair<unsigned, unsigned>(w_idx_i, w_idx_j), height_median));
372       }
373     } // end of loop over each window
374   } // end of loop over each window
375 
376    // if there is no region can be used to define ground plane height, do not refine ground region
377   // at this stage, only building roof being flattened, class map remain same, building boundary remain as before
378   if (window_min_height.empty()) {
379     log << "warning: no road/park is available to refine ground plane height, ground refinement ignored\n";
380     error(log_file.str(), log.str());
381     vil_save(refined_h_img, refined_h_img_name.c_str());
382     vil_save(refined_c_img, refined_c_img_name.c_str());
383     return volm_io::SUCCESS;
384   }
385 
386   // ingest ground level height using height median retrieved from road and park region (class map doesn't change
387   unsigned window_size = num_w_i;
388   if (window_size > num_w_j) window_size = num_w_j;
389   std::cout << "\nStart to refine ground elevation";
390   for (unsigned w_idx_i = 0; w_idx_i < num_w_i; w_idx_i++)
391   {
392     for (unsigned w_idx_j = 0; w_idx_j < num_w_j; w_idx_j++)
393     {
394       std::cout << '.';
395       std::cout.flush();
396 
397       // obtain the window height
398       float grd_height = 0.0f;
399       auto mit = window_min_height.find(std::pair<unsigned,unsigned>(w_idx_i, w_idx_j));
400       if (mit == window_min_height.end())
401         // search neighbors to obtain height
402         grd_height = neighbor_height(window_min_height, w_idx_i, w_idx_j, window_size);
403       else
404         grd_height = mit->second;
405       // refine the ground height
406       auto r_mit = non_building_regions.find(std::pair<unsigned,unsigned>(w_idx_i, w_idx_j));
407       if (r_mit != non_building_regions.end()) {
408         std::vector<std::pair<unsigned, unsigned> > region_pixels = r_mit ->second;
409         for (auto & region_pixel : region_pixels) {
410           refined_h_img(region_pixel.first,region_pixel.second) = grd_height;
411         }
412       }
413     }
414   }
415 
416   // refine the building boundary using morphological operation (class map will be updated here to accept new building pixels)
417   // height threshold used to divide neighbor buildings having different height
418   std::cout << "\nStart to refine building boundary";
419   for (unsigned i = 0; i < iteration(); i++) {
420     std::cout << '.';
421     std::cout.flush();
422     if(!refine_building(ratio(), height_threshold(), h_img, refined_c_img, refined_h_img)) {
423       log << "error: refining building failed at ratio " << ratio() << '\n';
424       error(log_file.str(), log.str());
425       return volm_io::EXE_ARGUMENT_ERROR;
426     }
427   }
428 
429   // refine once again with high ratio and no height threshold to get rid of white noise (spikes)
430   if(!refine_building(0.6f, 1000, h_img, refined_c_img, refined_h_img)) {
431     log << "error: refining building failed at ratio " << ratio() << '\n';
432     error(log_file.str(), log.str());
433     return volm_io::EXE_ARGUMENT_ERROR;
434   }
435   std::cout << std::endl;
436 
437   // re-check class image
438   for (unsigned w_idx_i = 0; w_idx_i < num_w_i; w_idx_i++)
439   {
440     unsigned start_ni, end_ni;
441     for (unsigned w_idx_j = 0; w_idx_j < num_w_j; w_idx_j++)
442     {
443       std::cout << '.';
444       std::cout.flush();
445       std::pair<unsigned,unsigned> window_key(w_idx_i, w_idx_j);
446       // obtain window pixels
447       unsigned start_nj, end_nj;
448       start_ni = w_idx_i*dx(); end_ni = (w_idx_i+1)*dx();
449       start_nj = w_idx_j*dy(); end_nj = (w_idx_j+1)*dy();
450       // obtain the ground height for current window
451       float grd_height = 0.0f;
452       auto mit = window_min_height.find(std::pair<unsigned,unsigned>(w_idx_i, w_idx_j));
453       if (mit == window_min_height.end())
454         // search neighbors to obtain height
455         grd_height = neighbor_height(window_min_height, w_idx_i, w_idx_j, window_size);
456       else
457         grd_height = mit->second;
458 
459       // re-label building as tall building
460       for (unsigned i = start_ni; i < end_ni; i++)
461       {
462         for (unsigned j = start_nj; j < end_nj; j++)
463         {
464           if ( i >= ni || j >= nj)
465             continue;
466           if ((refined_h_img(i,j) - grd_height) > 0)
467             refined_c_img(i,j) = volm_osm_category_io::volm_land_table_name["building"].id_;
468         }
469       }
470     }
471   }
472 
473   // re-label to get tall building the tall building
474   // for building with height larger than given tall_building_thres, they are relabeled as tall building
475   for (unsigned w_idx_i = 0; w_idx_i < num_w_i; w_idx_i++)
476   {
477     unsigned start_ni, end_ni;
478     for (unsigned w_idx_j = 0; w_idx_j < num_w_j; w_idx_j++)
479     {
480       std::cout << '.';
481       std::cout.flush();
482       std::pair<unsigned,unsigned> window_key(w_idx_i, w_idx_j);
483       // obtain window pixels
484       unsigned start_nj, end_nj;
485       start_ni = w_idx_i*dx(); end_ni = (w_idx_i+1)*dx();
486       start_nj = w_idx_j*dy(); end_nj = (w_idx_j+1)*dy();
487       // obtain the ground height for current window
488       float grd_height = 0.0f;
489       auto mit = window_min_height.find(std::pair<unsigned,unsigned>(w_idx_i, w_idx_j));
490       if (mit == window_min_height.end())
491         // search neighbors to obtain height
492         grd_height = neighbor_height(window_min_height, w_idx_i, w_idx_j, window_size);
493       else
494         grd_height = mit->second;
495 
496       // re-label building as tall building
497       for (unsigned i = start_ni; i < end_ni; i++)
498       {
499         for (unsigned j = start_nj; j < end_nj; j++)
500         {
501           if ( i >= ni || j >= nj)
502             continue;
503           if ((refined_h_img(i,j) - grd_height) > tall_building_thres() &&  refined_c_img(i,j) == volm_osm_category_io::volm_land_table_name["building"].id_)
504             refined_c_img(i,j) = volm_osm_category_io::volm_land_table_name["tall_building"].id_;
505         }
506       }
507     }
508   }
509 
510   //output the result
511   vil_save(refined_c_img, refined_c_img_name.c_str());
512   vil_save(refined_h_img, refined_h_img_name.c_str());
513 
514 #if 1
515   // define a color image for debugging purpose
516   std::string out_folder = vul_file::dirname(refined_c_img_name);
517   vil_image_view<vil_rgb<vxl_byte> > out_color_img(ni, nj, 1);
518   out_color_img.fill(volm_osm_category_io::volm_land_table[0].color_);
519   for (unsigned i = 0; i < ni; i++) {
520     for (unsigned j = 0; j < nj; j++) {
521       out_color_img(i,j) = volm_osm_category_io::volm_land_table[refined_c_img(i,j)].color_;
522     }
523   }
524   vil_save(out_color_img, out_class_color().c_str());
525 #endif
526 
527   return volm_io::SUCCESS;
528 }
529 #endif
530 
obtain_buildings(vil_image_view<vxl_byte> const & c_img,unsigned const & start_ni,unsigned const & start_nj,unsigned const & end_ni,unsigned const & end_nj,std::map<unsigned,std::pair<std::vector<unsigned>,std::vector<unsigned>>> & buildings,std::map<unsigned,std::pair<std::vector<unsigned>,std::vector<unsigned>>> & flat_regions,std::vector<std::pair<unsigned,unsigned>> & non_buildings)531 static bool obtain_buildings(vil_image_view<vxl_byte> const& c_img,
532                              unsigned const& start_ni, unsigned const& start_nj,
533                              unsigned const& end_ni,   unsigned const& end_nj,
534                              std::map<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > >& buildings,
535                              std::map<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > >& flat_regions,
536                              std::vector<std::pair<unsigned, unsigned> >& non_buildings)
537 {
538   buildings.clear();
539   flat_regions.clear();
540   non_buildings.clear();
541   unsigned ni = c_img.ni();  unsigned nj = c_img.nj();
542   vil_region_finder<vxl_byte> region_finder(c_img, vil_region_finder_8_conn);
543   for (unsigned i = start_ni; i < end_ni; i++)
544   {
545     for (unsigned j = start_nj; j < end_nj; j++)
546     {
547       if ( i >= ni || j >= nj)
548         continue;
549       if (c_img(i,j) == volm_osm_category_io::volm_land_table_name["building"].id_)
550       {
551         std::vector<unsigned> ri;  std::vector<unsigned> rj;
552         ri.resize(0);  rj.resize(0);
553         region_finder.same_int_region(i, j, ri, rj);
554         //ignore regions that are smaller than 10 pixels
555         if (ri.size() >= 10) {
556           unsigned key = (i+j)*(i+j+1)/2 + j;
557           std::pair<std::vector<unsigned>, std::vector<unsigned> > tmp(ri, rj);
558           buildings.insert(std::pair<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > >(key, tmp));
559         }
560       }
561       else if (c_img(i,j) == volm_osm_category_io::volm_land_table_name["roads"].id_)
562       {
563         std::vector<unsigned> ri;  std::vector<unsigned> rj;
564         ri.resize(0);  rj.resize(0);
565         region_finder.same_int_region(i, j, ri, rj);
566         //ignore regions that are smaller than 10 pixels
567         if (ri.size()) {
568           unsigned key = (i+j)*(i+j+1)/2 + j;
569           std::pair<std::vector<unsigned>, std::vector<unsigned> > tmp(ri, rj);
570           flat_regions.insert(std::pair<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > >(key, tmp));
571         }
572       }
573       else
574         continue;
575     }
576   }
577   // obtain non-building region
578   for (unsigned i = start_ni; i < end_ni; i++) {
579     for (unsigned j = start_nj; j < end_nj; j++)
580     {
581       if ( i >= ni || j >= nj)
582         continue;
583       // check whether pixel (i,j) is inside any building
584       bool is_inside_building = false;
585       for (auto mit = buildings.begin();
586           (mit != buildings.end() && !is_inside_building); ++mit) {
587         if (mit->second.first.size() != mit->second.second.size())
588           return false;
589         for (unsigned b_idx = 0; (b_idx < mit->second.first.size() && !is_inside_building); b_idx++) {
590           if ( i == mit->second.first[b_idx] && j == mit->second.second[b_idx] )
591             is_inside_building = true;
592         }
593       }
594       // add non_building pixels
595       if (!is_inside_building)
596         non_buildings.emplace_back(i,j);
597     }
598   }
599   return true;
600 }
601 
refine_building_by_median(vil_image_view<float> const & h_img,std::map<unsigned,std::pair<std::vector<unsigned>,std::vector<unsigned>>> const & buildings,vil_image_view<float> & refined_img)602 static bool refine_building_by_median(vil_image_view<float> const& h_img,
603                                       std::map<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > > const& buildings,
604                                       vil_image_view<float>& refined_img)
605 {
606   // refine each building with its median height
607   for (const auto & building : buildings)
608   {
609     if (building.second.first.size() < 10)
610       continue;
611     auto num_pts = (unsigned)building.second.first.size();
612     // find the median
613     std::vector<float> height_values;
614     for (unsigned p_idx = 0; p_idx < num_pts; p_idx++)
615       height_values.push_back(h_img(building.second.first[p_idx], building.second.second[p_idx]));
616     std::sort(height_values.begin(), height_values.end());
617     float median = height_values[height_values.size()/2];
618 
619     // refine the height using median
620     for (unsigned p_idx = 0; p_idx < num_pts; p_idx++) {
621       unsigned int img_i = building.second.first[p_idx];
622       unsigned int img_j = building.second.second[p_idx];
623       refined_img(img_i, img_j) = median;
624     }
625 
626     // compute the mean height and the std
627     float mean = average(height_values);
628     float variance = standard_devation(height_values, mean);
629     std::vector<float> height_data;
630     height_data.push_back(median);
631     height_data.push_back(mean);
632     height_data.push_back(variance);
633   }
634 
635 #if 0
636   // refine ground using road median
637   // find the road median
638   std::vector<float> road_heights;
639   for (std::map<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > >::const_iterator mit = roads.begin();
640        mit != roads.end(); ++mit)
641   {
642     //if (mit->second.first.size() < 10)
643     //  continue;
644     unsigned num_pts = mit->second.first.size();
645     for (unsigned ri = 0; ri < num_pts; ri++)
646       road_heights.push_back(h_img(mit->second.first[ri], mit->second.second[ri]));
647   }
648   if (road_heights.size() == 0)
649     return false;
650   std::sort(road_heights.begin(), road_heights.end());
651   float road_median = road_heights[road_heights.size()/2];
652   // fill the road median
653   unsigned num_grd_pixel = non_buildings.size();
654   for (unsigned gi = 0; gi < num_grd_pixel; gi++) {
655     refined_img(non_buildings[gi].first, non_buildings[gi].second) = road_median;
656   }
657 #endif
658 
659   return true;
660 }
661 
refine_building_by_median_divided(vil_image_view<float> const & h_img,vil_image_view<vxl_byte> const & c_img,std::map<unsigned,std::pair<std::vector<unsigned>,std::vector<unsigned>>> const & buildings,unsigned const & w_size,vil_image_view<float> & refined_img)662 static bool refine_building_by_median_divided(vil_image_view<float> const& h_img,
663                                               vil_image_view<vxl_byte> const& c_img,
664                                               std::map<unsigned, std::pair<std::vector<unsigned>, std::vector<unsigned> > > const& buildings,
665                                               unsigned const& w_size,
666                                               vil_image_view<float>& refined_img)
667 {
668   // loop over each super pixels and divide them with a given window size
669   for (const auto & building : buildings)
670   {
671     if (building.second.first.size() < 10)  // ignore super pixel with less than 10 pixels
672       continue;
673     std::vector<std::pair<unsigned, unsigned> > super_pixel;
674     auto num_pts = (unsigned)building.second.first.size();
675     for (unsigned p_idx = 0; p_idx < num_pts; p_idx++)
676       super_pixel.emplace_back(building.second.first[p_idx], building.second.second[p_idx]);
677     // form the bbox of super pixels
678     vgl_box_2d<unsigned> bbox;
679     for (unsigned p_idx = 0; p_idx < num_pts; p_idx++)
680       bbox.add(vgl_point_2d<unsigned>(super_pixel[p_idx].first, super_pixel[p_idx].second));
681     // refine the each window inside the bbox
682     unsigned ni = bbox.max_x() - bbox.min_x();
683     unsigned nj = bbox.max_y() - bbox.min_y();
684     unsigned num_w_i = ni/w_size + 1;
685     unsigned num_w_j = nj/w_size + 1;
686     // obtain the building height for each window
687     for (unsigned int w_idx_i = 0; w_idx_i < num_w_i; ++w_idx_i) {
688       for (unsigned int w_idx_j = 0; w_idx_j < num_w_j; ++w_idx_j) {
689         unsigned int s_ni = bbox.min_x() + w_idx_i*w_size;
690         unsigned int e_ni = bbox.min_x() + (w_idx_i+1)*w_size;
691         unsigned int s_nj = bbox.min_y() + w_idx_j*w_size;
692         unsigned int e_nj = bbox.min_y() + (w_idx_j+1)*w_size;
693         std::vector<float> h_values;
694         for (unsigned int i = s_ni; i < e_ni; ++i) {
695           for (unsigned int j = s_nj; j < e_nj; ++j) {
696             if ( i > bbox.max_x() && j > bbox.max_y() )  // outside the bounding box
697               continue;
698             if ( c_img(i,j) == volm_osm_category_io::volm_land_table_name["building"].id_)
699               h_values.push_back(h_img(i,j));
700           }
701         }
702         // ignore the window without any building pixel inside
703         if (h_values.empty())
704           continue;
705         std::sort(h_values.begin(), h_values.end());
706         float median = h_values[h_values.size()/2];
707         // refine the window using median height
708         for (unsigned i = s_ni; i < e_ni; i++) {
709           for (unsigned j = s_nj; j < e_nj; j++) {
710             if ( i > bbox.max_x() && j > bbox.max_y() )  continue;
711             if ( c_img(i,j) == volm_osm_category_io::volm_land_table_name["building"].id_)
712               refined_img(i,j) = median;
713           }
714         }
715       }
716     }
717   }
718   return true;
719 }
720 
neighbor_height(std::map<std::pair<unsigned,unsigned>,float> const & window_height,unsigned const & w_idx_i,unsigned const & w_idx_j,unsigned const & w_size)721 static float neighbor_height(std::map<std::pair<unsigned, unsigned>, float> const& window_height, unsigned const& w_idx_i, unsigned const& w_idx_j, unsigned const& w_size)
722 {
723   bool found_neigh_height = false;
724 
725   unsigned num_nbrs = 8;
726   std::vector<float> neigh_heights;
727   for (int radius = 1; (radius < (int)w_size && !found_neigh_height); radius++)
728   {
729     neigh_heights.clear();
730     int nbrs8_delta[8][2] = { { radius, 0}, { radius,-radius}, { 0,-radius}, {-radius,-radius},
731                               {-radius, 0}, {-radius, radius}, { 0, radius}, { radius, radius} };
732     for (unsigned c = 0; c < num_nbrs; c++)
733     {
734       int nbr_i = (int)w_idx_i + nbrs8_delta[c][0];
735       int nbr_j = (int)w_idx_j + nbrs8_delta[c][1];
736       auto mit = window_height.find(std::pair<int,int>(nbr_i, nbr_j));
737       if (mit != window_height.end()) {
738         neigh_heights.push_back(mit->second);
739       }
740     }
741     if (neigh_heights.size() != 0)
742       found_neigh_height = true;
743   }
744 
745   if (neigh_heights.empty())
746     return 0.0f;
747 
748   float grd_height = 0.0f;
749   for (float neigh_height : neigh_heights)
750     grd_height += neigh_height;
751 
752   return grd_height/neigh_heights.size();
753 
754 }
755 
refine_building(float const & ratio,float const & height_threshold,vil_image_view<float> const & h_img,vil_image_view<vxl_byte> & refined_c_img,vil_image_view<float> & refined_h_img)756 static bool refine_building(float const& ratio,
757                             float const& height_threshold,
758                             vil_image_view<float> const& h_img,
759                             vil_image_view<vxl_byte> & refined_c_img,
760                             vil_image_view<float>& refined_h_img)
761 {
762   // refine the building by checking its first nearest neighbor
763   int ni = refined_h_img.ni();
764   int nj = refined_h_img.nj();
765   if (ni != (int)refined_c_img.ni() || nj != (int)refined_c_img.nj())
766     return false;
767   // define the first nearest neighbor list
768   int nbrs8_delta[8][2] = { { 1, 0}, { 1,-1}, { 0,-1}, {-1,-1},
769                             {-1, 0}, {-1, 1}, { 0, 1}, { 1, 1} };
770   unsigned num_nbrs = 8;
771   for (int i = 0; i < ni; i++) {
772     for (int j = 0; j < nj; j++) {
773       // ignore the tree and palm tree pixels
774       if (refined_c_img(i,j) == volm_osm_category_io::volm_land_table_name["palm_tree"].id_ || refined_c_img(i,j) == volm_osm_category_io::volm_land_table_name["tree"].id_)
775         continue;
776       // no need to correct building pixel
777       if (refined_c_img(i,j) == volm_osm_category_io::volm_land_table_name["building"].id_)
778         continue;
779       std::vector<float> neigh_heights;
780       // obtain the height values of its neighbor pixel which has building land class from refined land class map
781       for (unsigned c = 0; c < num_nbrs; c++) {
782         int nbr_i = i + nbrs8_delta[c][0];
783         int nbr_j = j + nbrs8_delta[c][1];
784         if (nbr_i >= 0 && nbr_j >=0 && nbr_i < ni && nbr_j < nj)
785           if (refined_c_img(nbr_i,nbr_j) == volm_osm_category_io::volm_land_table_name["building"].id_)
786             neigh_heights.push_back(refined_h_img(nbr_i,nbr_j));
787       }
788       if (neigh_heights.empty())
789         continue;
790 
791       // obtain the most duplicated height and number of its occurrence
792       float curr_height;
793       float curr_ratio;
794       if (neigh_heights.size() == 1) {
795         curr_height = neigh_heights[0];
796         curr_ratio = 1.0f/(float)(num_nbrs);
797       }
798       else {
799         std::sort(neigh_heights.begin(), neigh_heights.end());
800         std::vector<float> repeat;
801         std::vector<unsigned> occurance;
802         for (unsigned n_idx = 0; n_idx < neigh_heights.size()-1; n_idx++) {
803           if (float_equal(neigh_heights[n_idx], neigh_heights[n_idx+1]))
804           {
805             unsigned cnt = 1;
806             repeat.push_back(neigh_heights[n_idx]);
807             while ( (n_idx < neigh_heights.size()-1) && float_equal(neigh_heights[n_idx], neigh_heights[n_idx+1]) ){
808               n_idx++;  cnt++;
809             }
810             occurance.push_back(cnt);
811           }
812         }
813         if (repeat.size() != 0) {
814           std::map<unsigned, float> occ_h_map;
815           for (unsigned r_idx = 0; r_idx < repeat.size(); r_idx++)
816             occ_h_map.insert(std::pair<unsigned, float>(occurance[r_idx], repeat[r_idx]));
817           auto mit = occ_h_map.end();
818           mit--;
819           curr_height = mit->second;
820           curr_ratio  = (float)mit->first / (float)(num_nbrs);
821         }
822         else {
823           curr_height = 0.0f;
824           curr_ratio  = 0.0f;
825         }
826       }
827 #if 0
828       if (i == 427 && j == 391) {
829         std::cout << " --------- " << std::endl;
830         std::cout << "i = " << i << " j = " << j << " height = " << curr_height << " ratio = " << curr_ratio
831                  << " origin height = " << h_img(i,j) << std::endl;
832       }
833 #endif
834       // update the pixel by checking whether there are enough building neighbor pixels having same height
835       if (curr_ratio > ratio) {
836         if ( (curr_height-h_img(i,j))*(curr_height-h_img(i,j)) < height_threshold*height_threshold ) {
837           refined_h_img(i,j) = curr_height;
838           refined_c_img(i,j) = volm_osm_category_io::volm_land_table_name["building"].id_;
839         }
840       }
841 
842     }
843   }
844   return true;
845 }
846