1 #include <cmath>
2 #include <iostream>
3 #include <algorithm>
4 #include <volm/conf/volm_conf_query.h>
5 //:
6 // \file
7 #include <cassert>
8 #ifdef _MSC_VER
9 #  include "vcl_msvc_warnings.h"
10 #endif
11 #include <bpgl/bpgl_camera_utils.h>
12 #include "vul/vul_file.h"
13 #include "vil/vil_load.h"
14 #include "vil/vil_save.h"
15 #include <vsph/vsph_spherical_coord.h>
16 #include <vsph/vsph_sph_point_3d.h>
17 #include "vgl/vgl_polygon_scan_iterator.h"
18 
19 // default constructor
volm_conf_query()20 volm_conf_query::volm_conf_query()
21 {
22   ni_ = 0;  nj_ = 0;  ncam_ = 0;  nobj_ = 0;  nref_ = 0;
23   dm_ = nullptr;
24   cameras_.clear();  camera_strings_.clear();
25   ref_obj_name_.clear();  conf_objects_.clear();
26   tol_in_pixel_ = 0;
27 }
28 
29 // create query from labeled depth map scene
volm_conf_query(const volm_camera_space_sptr & cam_space,const depth_map_scene_sptr & depth_scene,int const & tol_in_pixel)30 volm_conf_query::volm_conf_query(const volm_camera_space_sptr& cam_space, const depth_map_scene_sptr& depth_scene, int const& tol_in_pixel)
31 {
32   tol_in_pixel_ = tol_in_pixel;
33   dm_ = depth_scene;
34   ni_ = dm_->ni();  nj_ = dm_->nj();
35   altitude_ = cam_space->altitude();
36   // create reference objects from depth scene
37   bool success = this->parse_ref_object(dm_);
38   assert(success && "volm_conf_query: parse reference object list from depth scene failed");
39   nref_ = (unsigned)this->ref_obj_name_.size();
40   std::cout << nref_ << " reference configurational objects are loaded: ";
41   for (unsigned i = 0; i < nref_; i++)
42     std::cout << ref_obj_name_[i] << ' ';
43   std::cout << '\n';
44   // create cameras
45   success = this->create_perspective_cameras(cam_space);
46   assert(success && "volm_conf_query: construct perspective cameras from camera space failed");
47   ncam_ = (unsigned)this->cameras_.size();
48   std::cout << ncam_ << " cameras are created: " << std::endl;
49   // construct configurational object from 3d polygons
50   success  = this->create_conf_object();
51   assert(success && "volm_conf_query: construct configurational objects failed");
52 }
53 
parse_ref_object(const depth_map_scene_sptr & dm)54 bool volm_conf_query::parse_ref_object(const depth_map_scene_sptr& dm)
55 {
56   ref_obj_name_.clear();
57   if (dm == nullptr)
58     return false;
59   // note that sky object should never be reference object
60   if (dm->ground_plane().size())
61     for (unsigned i = 0; i < dm->ground_plane().size(); i++)
62       if ( dm->ground_plane()[i]->is_ref() && dm->ground_plane()[i]->active())
63         ref_obj_name_.push_back(dm->ground_plane()[i]->name());
64   if (dm->scene_regions().size())
65     for (unsigned i = 0; i < dm->scene_regions().size(); i++)
66       if ( dm->scene_regions()[i]->is_ref() && dm->scene_regions()[i]->active())
67         ref_obj_name_.push_back(dm->scene_regions()[i]->name());
68   if (ref_obj_name_.empty())
69     return false;
70   return true;
71 }
72 
create_perspective_cameras(const volm_camera_space_sptr & cam_space)73 bool volm_conf_query::create_perspective_cameras(const volm_camera_space_sptr& cam_space)
74 {
75   // iterate over valid cameras in the camera space
76   // Note that ground plane construct has been applied on camera space
77   cameras_.clear();  camera_strings_.clear();  camera_angles_.clear();
78   std::vector<unsigned> const& valid_cams = cam_space->valid_indices();
79   for (unsigned int valid_cam : valid_cams) {
80     vpgl_perspective_camera<double> cam = cam_space->camera(valid_cam);
81     std::string cam_str = cam_space->get_string(valid_cam);
82     cameras_.push_back(cam);
83     camera_strings_.push_back(cam_str);
84     camera_angles_.push_back(cam_space->camera_angles(valid_cam));
85   }
86   return true;
87 }
88 
89 
create_conf_object()90 bool volm_conf_query::create_conf_object()
91 {
92   conf_objects_.clear();
93   // loop over each calibrated camera to construct the list of configurational objects
94   for (unsigned cam_id =0;  cam_id < ncam_;  cam_id++)
95   {
96     std::cout << "camera: " << this->camera_strings_[cam_id] << std::endl;
97     vpgl_perspective_camera<double> pcam = cameras_[cam_id];
98     // create a map of volm_conf_object
99     std::map<std::string, volm_conf_object_sptr> conf_object;
100     std::map<std::string, std::pair<unsigned, unsigned> > conf_pixels;
101     // only consider non-planar objects
102     std::vector<depth_map_region_sptr> regions = dm_->scene_regions();
103     for (unsigned r_idx = 0; r_idx < regions.size(); r_idx++)
104     {
105       if (!regions[r_idx]->active()) {
106         std::cout << "\t\t region " << r_idx << " " << regions[r_idx]->name() << " is not active, IGNORED" << std::endl;
107         continue;
108       }
109       //std::cout << "\t\t projecting " << regions[r_idx]->name() << "..." << std::flush << std::endl;
110       vgl_polygon<double> poly = bsol_algs::vgl_from_poly(regions[r_idx]->region_2d());
111       float theta = -1.0f, dist = -1.0f, height = -1.0f;
112       unsigned i, j;
113       // project all ground vertices on the polygon to 3-d world points if the vertex is under the horizon
114       this->project(pcam, poly, dist, theta, i, j);
115       // here to use the distance from depth map scene
116       dist   = regions[r_idx]->min_depth();
117       height = regions[r_idx]->height();
118       //std::cout << "\t\t min_dist: " << dist << ", phi: " << theta << ", pixel: " << i << "x" << j << std::flush << std::endl;
119       if (theta < 0)
120         continue;
121       // create a configurational object for it
122       unsigned char land_id = regions[r_idx]->land_id();
123       volm_conf_object_sptr conf_obj = new volm_conf_object(theta, dist, height, land_id);
124       //std::cout << "conf_obj: ";  conf_obj->print(std::cout);
125       conf_object.insert(std::pair<std::string, volm_conf_object_sptr>(regions[r_idx]->name(), conf_obj));
126       std::pair<unsigned, unsigned> tmp_pair(i,j);
127       conf_pixels.insert(std::pair<std::string, std::pair<unsigned, unsigned> >(regions[r_idx]->name(), tmp_pair));
128     }
129     // update the configurational object for current camera
130     conf_objects_.push_back(conf_object);
131     conf_objects_pixels_.push_back(conf_pixels);
132   }
133 
134   if (tol_in_pixel_ == 0)
135   {
136     for (unsigned cam_id =0;  cam_id < ncam_;  cam_id++)
137     {
138       std::map<std::string, volm_conf_object_sptr> conf_objs = conf_objects_[cam_id];
139       std::map<std::string, std::pair<unsigned, unsigned> > conf_pixels = conf_objects_pixels_[cam_id];
140       std::map<std::string, std::pair<float, float> > conf_dist_tol;
141       for (auto & conf_pixel : conf_pixels)
142       {
143         float min_dist = conf_objs[conf_pixel.first]->dist()*0.8;
144         float max_dist = conf_objs[conf_pixel.first]->dist()*1.2;
145         conf_dist_tol.insert(std::pair<std::string, std::pair<float, float> >(conf_pixel.first, std::pair<float, float>(min_dist, max_dist)));
146       }
147       conf_objects_d_tol_.push_back(conf_dist_tol);
148     }
149   }
150   else
151   {
152     // calculate the distance tolerance for each configuration object
153     int nbrs4_delta[4][2] = {  {-1*tol_in_pixel_,    tol_in_pixel_},
154                                {   tol_in_pixel_,    tol_in_pixel_},
155                                {   tol_in_pixel_, -1*tol_in_pixel_},
156                                {-1*tol_in_pixel_, -1*tol_in_pixel_}
157                             };
158     unsigned num_nbrs = 4;
159     for (unsigned cam_id =0;  cam_id < ncam_;  cam_id++)
160     {
161       vpgl_perspective_camera<double> pcam = cameras_[cam_id];
162       std::map<std::string, volm_conf_object_sptr> conf_objs = conf_objects_[cam_id];
163       std::map<std::string, std::pair<unsigned, unsigned> > conf_pixels = conf_objects_pixels_[cam_id];
164       std::map<std::string, std::pair<float, float> > conf_dist_tol;
165       for (auto & conf_pixel : conf_pixels)
166       {
167         unsigned i = conf_pixel.second.first;  unsigned j = conf_pixel.second.second;
168         float min_dist = conf_objs[conf_pixel.first]->dist();
169         float max_dist = min_dist;
170         for (unsigned k = 0; k < num_nbrs; k++) {
171           int nbr_i = i + nbrs4_delta[k][0];
172           int nbr_j = j + nbrs4_delta[k][1];
173           float dist, phi;
174           this->project(pcam, nbr_i, nbr_j, dist, phi);
175           if (dist < 0 || phi < 0)
176             continue;
177           if (dist <= min_dist)  min_dist = dist;
178           if (dist >= max_dist)  max_dist = dist;
179           //std::cout << "\t pixel " << nbr_i << ", " << nbr_j << ", dist: " << dist << ", min_dist: " << min_dist << ", max_dist: " << max_dist << std::endl;
180         }
181         conf_dist_tol.insert(std::pair<std::string, std::pair<float, float> >(conf_pixel.first, std::pair<float, float>(min_dist, max_dist)));
182       }
183       conf_objects_d_tol_.push_back(conf_dist_tol);
184     }
185   }
186 
187   return true;
188 }
189 
190 // calculate the minimum distance, the average angle values from the label boundary and the pixel associated with it
project(vpgl_perspective_camera<double> const & cam,vgl_polygon<double> const & poly,float & min_dist,float & phi,unsigned & i,unsigned & j)191 void volm_conf_query::project(vpgl_perspective_camera<double> const& cam,
192                               vgl_polygon<double> const& poly,
193                               float& min_dist, float& phi, unsigned& i, unsigned& j)
194 {
195   min_dist = -1.0f;  phi = -1.0f;  i = 0;  j=0;
196   // only consider the first sheet
197   unsigned n_vertices = poly[0].size();
198   std::map<float, float> pt_pairs;
199   std::map<float, std::pair<unsigned, unsigned> > pt_pixels;
200   // calculate the angle value from all polygons
201   std::set<float> dist_values;
202   std::vector<float> phi_values;
203   std::vector<std::pair<double, double> > pixel_values;
204   for (unsigned v_idx = 0; v_idx < n_vertices; v_idx++)
205   {
206     double x = poly[0][v_idx].x();
207     double y = poly[0][v_idx].y();
208     if (x < 0 || x >= ni_ || y < 0 || y >= nj_)
209       continue;
210     float dist, phi;
211     this->project(cam, x, y, dist, phi);
212     if (dist < 0)
213       continue;
214     dist_values.insert(dist);
215     phi_values.push_back(phi);
216     pixel_values.emplace_back(x,y);
217   }
218   if (phi_values.empty())  // no vertices projects to ground
219     return;
220   min_dist = *(dist_values.begin());
221   // calculate the average angle
222   float phi_value = 0.0f;
223   double xi = 0.0, yi = 0.0;
224   for (float pi : phi_values)
225     phi_value += pi;
226   for (auto & pixel_value : pixel_values) {
227     xi += pixel_value.first;
228     yi += pixel_value.second;
229   }
230   phi_value /= phi_values.size();
231   xi /= pixel_values.size();
232   yi /= pixel_values.size();
233 
234   phi = phi_value;
235   i = (unsigned)(std::floor(xi+0.5));
236   j = (unsigned)(std::floor(yi+0.5));
237   return;
238 
239 # if 0
240   for (unsigned v_idx = 0; v_idx < n_vertices; v_idx++) {
241     double x = poly[0][v_idx].x();
242     double y = poly[0][v_idx].y();
243     float dist, phi;
244     this->project(cam, x, y, dist, phi);
245     if (dist < 0)
246       continue;
247     pt_pairs.insert(std::pair<float, float>(dist, phi));
248 #if 0
249     std::cout << "\t\tpixel: " << x << "x" << y << " is under horizon, has ray " << cp
250              << " and spherical coords: " << sp << " dist: " << dist << ", theta: " << sp.phi_ << std::flush << std::endl;
251 #endif
252 
253 
254     pt_pixels.insert(std::pair<float, std::pair<unsigned, unsigned> >(dist, std::pair<unsigned, unsigned>((unsigned)x,(unsigned)y)));
255   }
256   if (pt_pairs.empty())  // no vertices projects to ground
257     return;
258   min_dist = pt_pairs.begin()->first;
259   phi = pt_pairs[min_dist];
260 
261   i = pt_pixels[min_dist].first;
262   j = pt_pixels[min_dist].second;
263   //std::cout << "\t\tmin_dist: " << min_dist << ", phi: " << phi << ", pixel: " << i << "x" << j << std::flush << std::endl;
264   return;
265 #endif
266 
267 }
268 
project(vpgl_perspective_camera<double> const & cam,double const & pixel_i,double const & pixel_j,float & dist,float & phi)269 void volm_conf_query::project(vpgl_perspective_camera<double> const& cam,
270                               double const& pixel_i, double const& pixel_j,
271                               float& dist, float& phi)
272 {
273   dist = -1.0; phi = -1.0;
274   vgl_line_2d<double> horizon = bpgl_camera_utils::horizon(cam);
275   if (pixel_i < 0 || pixel_i >= ni_ || pixel_j < 0 || pixel_j >= nj_)
276     return;
277   double yl = line_coord(horizon, pixel_i);
278   if (pixel_j < yl)  // given img point is above the horizon
279     return;
280   // obtain the back project ray to calculate the distance
281   vgl_ray_3d<double> ray = cam.backproject(pixel_i, pixel_j);
282   // obtain the angular relative to camera x axis
283   vgl_point_3d<double> cp(ray.direction().x(), ray.direction().y(), ray.direction().z());
284   vsph_spherical_coord sph_coord;
285   vsph_sph_point_3d sp;
286   sph_coord.spherical_coord(cp, sp);
287   // calculate the distance
288   dist = std::tan(vnl_math::pi - sp.theta_)*altitude_;
289   phi = sp.phi_;
290   return;
291 }
292 
line_coord(vgl_line_2d<double> const & line,double const & x)293 double volm_conf_query::line_coord(vgl_line_2d<double> const& line, double const& x)
294 {
295   if (line.b() == 0)
296     return 0.0;
297   else
298     return -line.a()/line.b()*x - line.c()/line.b();
299 }
300 
301 
visualize_ref_objs(std::string const & in_file,std::string const & out_folder)302 bool volm_conf_query::visualize_ref_objs(std::string const& in_file, std::string const& out_folder)
303 {
304   if (!vul_file::exists(in_file))
305     return false;
306   vil_image_view<vil_rgb<vxl_byte> > src_img = vil_load(in_file.c_str());
307   std::vector<depth_map_region_sptr> regions = dm_->scene_regions();
308 
309   for (unsigned cam_id = 0; cam_id < ncam_; cam_id++) {
310     vil_image_view<vil_rgb<vxl_byte> > img;
311     img.deep_copy(src_img);
312     // plot horizontal line
313     vgl_line_2d<double> h_line = bpgl_camera_utils::horizon(cameras_[cam_id]);
314     std::vector<vgl_point_2d<double> > h_line_pixels;
315     h_line_pixels.clear();
316     for (unsigned x = 0; x < ni_; x++) {
317       double y = (std::floor)(line_coord(h_line, x));
318       h_line_pixels.emplace_back((double)x, y);
319     }
320     this->plot_line_into_image(img, h_line_pixels, 0, 0, 0, 6);
321     // plot the non-ground depth map scenes first
322     for (auto & region : regions) {
323       // not plot the object that is not projected based on current camera
324       if (conf_objects_[cam_id].find(region->name()) == conf_objects_[cam_id].end())
325         continue;
326       vgl_polygon<double> poly = bsol_algs::vgl_from_poly(region->region_2d());
327       poly[0].push_back(poly[0][0]);
328       unsigned char r,g,b;
329       double width;
330       if (std::find(ref_obj_name_.begin(), ref_obj_name_.end(), region->name()) == ref_obj_name_.end()) {
331         r = volm_osm_category_io::volm_land_table[region->land_id()].color_.r;
332         g = volm_osm_category_io::volm_land_table[region->land_id()].color_.g;
333         b = volm_osm_category_io::volm_land_table[region->land_id()].color_.b;
334         width = 5.0;
335       } else {
336         r = 255; g = 0; b = 0; width = 7.0;
337       }
338       //std::cout << "object: " << regions[i]->name() << ", land: " << (int)regions[i]->land_id() << ", color: " << (int)r << "," << (int)g << "," << (int)b << std::endl;
339       this->plot_line_into_image(img, poly[0],r,g,b,width);
340     }
341     // plot the configurational object
342     for (auto mit = conf_objects_pixels_[cam_id].begin(); mit != conf_objects_pixels_[cam_id].end(); ++mit ) {
343       unsigned char land_id = conf_objects_[cam_id][mit->first]->land();
344       unsigned char r,g,b;
345       double width;
346       if (std::find(ref_obj_name_.begin(), ref_obj_name_.end(), mit->first) == ref_obj_name_.end()) {
347         r = volm_osm_category_io::volm_land_table[land_id].color_.r;
348         g = volm_osm_category_io::volm_land_table[land_id].color_.g;
349         b = volm_osm_category_io::volm_land_table[land_id].color_.b;
350         width = 20.0;
351       }else {
352         r = 255; g = 255; b = 255; width = 30.0;
353       }
354       this->plot_dot_into_image(img, vgl_point_2d<double>((double)mit->second.first, (double)mit->second.second),r,g,b,width);
355     }
356     std::string filename = vul_file::strip_extension(vul_file::strip_directory(in_file));
357     std::string out_file = out_folder + "/" + filename + "_" + camera_strings_[cam_id] + ".tif";
358     vil_save(img, out_file.c_str());
359   }
360   return true;
361 
362 }
363 
364 #if 0
365 bool volm_conf_query::generate_top_views(std::string const& out_folder, std::string const& filename_pre)
366 {
367   // ensure a maximum image size
368   unsigned half_ni = 0;  unsigned half_nj = 0;
369   for (unsigned cam_id = 0; cam_id < ncam_; cam_id++)
370   {
371     std::map<std::string, volm_conf_object_sptr> conf_obj = conf_objects_[cam_id];
372     for (std::map<std::string, volm_conf_object_sptr>::iterator mit = conf_obj.begin();  mit != conf_obj.end();  ++mit) {
373       float x = mit->second->dist() * std::cos(mit->second->theta());
374       float y = mit->second->dist() * std::sin(mit->second->theta());
375       if (half_ni < std::ceil(x))  half_ni = std::ceil(x);
376       if (half_nj < std::ceil(y))  half_nj = std::ceil(y);
377       //std::cout << "obj " << mit->first << " has distance " << mit->second->dist() << " and angle " << mit->second->theta()
378       //         << ", pixel " << std::ceil(x) << "x" << std::ceil(y)
379       //         << ", img size" << 2*half_ni << "x" << 2*half_nj << std::endl;
380     }
381   }
382   unsigned ni,nj;
383   ni = 2*half_ni;
384   nj = 2*half_nj;
385   for (unsigned cam_id = 0; cam_id < ncam_; cam_id++)
386   {
387     std::string cam_string = camera_strings_[cam_id];
388     vil_image_view<vil_rgb<vxl_byte> > img(ni, nj);
389     img.fill(vil_rgb<vxl_byte>(127,127,127));
390     // perform the coordinate transformation (putting camera center to the image center)
391     float xo = half_ni, yo = half_nj;
392     // plot camera center
393     this->plot_dot_into_image(img, vgl_point_2d<double>(xo,yo), 0, 0, 0, 5.0);
394 
395     std::map<std::string, volm_conf_object_sptr> conf_obj = conf_objects_[cam_id];
396     for (std::map<std::string, volm_conf_object_sptr>::iterator mit = conf_obj.begin();  mit != conf_obj.end();  ++mit) {
397       float xc = mit->second->dist() * std::cos(mit->second->theta());
398       float yc = mit->second->dist() * std::sin(mit->second->theta());
399       unsigned char r, g, b;  double width;
400       unsigned char land_id = mit->second->land();
401       if (std::find(ref_obj_name_.begin(), ref_obj_name_.end(), mit->first) == ref_obj_name_.end()) {
402         r = volm_osm_category_io::volm_land_table[land_id].color_.r;
403         g = volm_osm_category_io::volm_land_table[land_id].color_.g;
404         b = volm_osm_category_io::volm_land_table[land_id].color_.b;
405         width = 10.0;
406       }else {
407         r = 255; g = 255; b = 255; width = 25.0;
408       }
409       //std::cout << "(" << xc << "," << yc << "), (" << xo << "," << yo << ") --> (" << xc+xo << "," << yo-yc << ")" << std::endl;
410       this->plot_dot_into_image(img, vgl_point_2d<double>(xc+xo,yo-yc), r, g, b, width);
411     }
412     std::string out_file = out_folder + "/" + filename_pre + "_" + cam_string + ".tif";
413     //std::cout << "save image to " << out_file << std::endl;
414     vil_save(img, out_file.c_str());
415   }
416   return true;
417 }
418 #endif
419 
plot_line_into_image(vil_image_view<vil_rgb<vxl_byte>> & image,std::vector<vgl_point_2d<double>> const & line,unsigned char const & r,unsigned char const & g,unsigned char const & b,double const & width)420 void volm_conf_query::plot_line_into_image(vil_image_view<vil_rgb<vxl_byte> >& image,
421                                            std::vector<vgl_point_2d<double> > const& line,
422                                            unsigned char const& r, unsigned char const& g, unsigned char const& b,
423                                            double const& width)
424 {
425   vgl_polygon<double> img_poly;
426   volm_io_tools::expend_line(line, width, img_poly);
427   vgl_polygon_scan_iterator<double> it(img_poly, true);
428   for (it.reset(); it.next();  ) {
429     int y = it.scany();
430     for (int x = it.startx(); x <= it.endx(); ++x) {
431       if ( x >= 0 && y >= 0 && x < (int)image.ni() && y < (int)image.nj()) {
432         image(x, y) = vil_rgb<vxl_byte>(r, g, b);
433       }
434     }
435   }
436 }
437 
plot_dot_into_image(vil_image_view<vil_rgb<vxl_byte>> & image,vgl_point_2d<double> const & pt,unsigned char const & r,unsigned char const & g,unsigned char const & b,double const & radius)438 void volm_conf_query::plot_dot_into_image(vil_image_view<vil_rgb<vxl_byte> >& image,
439                                           vgl_point_2d<double> const& pt,
440                                           unsigned char const& r, unsigned char const& g, unsigned char const& b,
441                                           double const& radius)
442 {
443   vgl_polygon<float> img_poly;
444   img_poly.new_sheet();
445   std::set<float> angles;
446   for (unsigned i = 0; i <= 36; i++)
447     angles.insert(i*10.0f);
448   angles.insert(45.0f);  angles.insert(135.0f);  angles.insert(225.0f);  angles.insert(315.0f);
449   for (const auto & angle : angles) {
450     float x = pt.x() + radius*std::cos(angle*vnl_math::pi_over_180);
451     float y = pt.y() + radius*std::sin(angle*vnl_math::pi_over_180);
452     img_poly.push_back(x, y);
453   }
454   vgl_polygon_scan_iterator<float> it(img_poly, true);
455   for (it.reset(); it.next();  ) {
456     int y = it.scany();
457     for (int x = it.startx(); x <= it.endx(); ++x) {
458       if ( x>=0 && y>=0 && x<(int)image.ni() && y<(int)image.nj())
459         image(x,y) = vil_rgb<vxl_byte>(r, g, b);
460     }
461   }
462 }
463