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