1 // This is brl/bbas/volm/pro/processes/volm_correct_rational_cameras_process.cxx
2 #include <iostream>
3 #include <sstream>
4 #include <fstream>
5 #include <iomanip>
6 #include <bprb/bprb_func_process.h>
7 //:
8 // \file
9 //         Take a list of rational cameras and a list of 2D image correspondences to geo-register all cameras
10 //         some of the correspondences may be very poor/wrong but a majority is of good quality (i.e. corresponds to the same 3D point)
11 //         Use a RANSAC scheme to find offsets for each camera using "inlier" correspondences
12 //         A track file that contains the name of all cameras and associated 2-d image correspondences is used as an input.  Format of the file is:
13 //           # The number of correspondences for each frame,
14 //           # [satellite camera name 1] [i_11] [j_11] [i_12] [j_12] ... [i_1n] [j_1n]
15 //           # [satellite camera name 2] [i_21] [j_21] [i_22] [j_22] ... [i_2n] [j_2n]
16 //           # [satellite camera name 3] [i_31] [j_31] [i_32] [j_32] ... [i_3n] [j_3n]
17 //           # ...
18 //         To improve the accuracy, a 3-d initial guessing point is given.  The initial point can be defined based on the overlapped region of cameras
19 //
20 // \verbatim
21 //  Modifications
22 //    none yet
23 // \endverbatim
24 //
25 #include <bprb/bprb_parameters.h>
26 #ifdef _MSC_VER
27 #  include "vcl_msvc_warnings.h"
28 #endif
29 #include "vpgl/vpgl_rational_camera.h"
30 #include "vpgl/vpgl_local_rational_camera.h"
31 #include "vul/vul_file.h"
32 #include "vul/vul_awk.h"
33 #include "vgl/vgl_point_2d.h"
34 #include "vgl/vgl_point_3d.h"
35 #include "vgl/vgl_box_2d.h"
36 #include "vgl/vgl_intersection.h"
37 #include <vpgl/algo/vpgl_rational_adjust_onept.h>
38 #include "vul/vul_file_iterator.h"
39 #include <volm/volm_satellite_resources.h>
40 #include <volm/volm_satellite_resources_sptr.h>
41 #include <volm/volm_io_tools.h>
42 #include "vil/vil_load.h"
43 #include <vil/file_formats/vil_nitf2_image.h>
44 #include <vpgl/file_formats/vpgl_nitf_rational_camera.h>
45 #include "vgl/vgl_distance.h"
46 #include <vpgl/algo/vpgl_rational_adjust_multipt.h>
47 
48 namespace volm_correct_rational_cameras_ransac_with_initial_process_globals
49 {
50   unsigned int n_inputs_  = 6;
51   unsigned int n_outputs_ = 0;
52 
53   //: return the overlapped region of multiple 2-d bounding box
54   vgl_box_2d<double> intersection(std::vector<vgl_box_2d<double> > const& boxes);
55 
56   //: load rational camera from nitf file path
57   vpgl_rational_camera<double>* load_cam_from_nitf(std::string const& nitf_img_path);
58 
59   //: calculate the relative diameter used in back-projection
60   //  Relative diameter is used to define the initial search range in Amoeba algorithm (check vnl/algo/vnl_amoeba.h for more details)
61   bool obtain_relative_diameter(volm_satellite_resources_sptr const& sat_res,
62                                 std::vector<std::string> const& camera_names,
63                                 double& relative_diameter);
64 
65   //: calculate the 3-d initial point from the overlapped region of satellite images
66   bool initial_point_by_overlap_region(volm_satellite_resources_sptr const& sat_res,
67                                        std::vector<std::string> const& camera_names,
68                                        std::string const& dem_folder,
69                                        vgl_point_3d<double>& init_pt,
70                                        double& zmin, double& zmax);
71 }
72 
volm_correct_rational_cameras_ransac_with_intial_process_cons(bprb_func_process & pro)73 bool volm_correct_rational_cameras_ransac_with_intial_process_cons(bprb_func_process& pro)
74 {
75   using namespace volm_correct_rational_cameras_ransac_with_initial_process_globals;
76   std::vector<std::string> input_types(n_inputs_);
77   input_types[0] = "volm_satellite_resources_sptr";  // satellite resource to fetch the full path of the satellite images
78   input_types[1] = "vcl_string";                     // a file that lists the name to a camera on each line and i and j coordinate of the 3D world point
79                                                      // format of the file:
80                                                      // n  # number of correspondences for each frame,
81                                                      // full_path_cam_name_1 i_11 j_11 i_12 j_12 ... i_1n j_1n
82                                                      // full_path_cam_name_2 i_21 j_21 i_22 j_22 ... i_2n j_2n
83   input_types[2] = "vcl_string";                     // ASTER DEM folder to retrieve the elevation values
84   input_types[3] = "vcl_string";                     // output folder
85   input_types[4] = "float";                          // pixel radius for the disagreement among inliers, e.g. 2 pixels
86   input_types[5] = "bool";                           // option to enforce having at least 2 existing corrected cameras
87   std::vector<std::string> output_types(n_outputs_);
88   return pro.set_input_types(input_types) && pro.set_output_types(output_types);
89 }
90 
91 //: execute the process
volm_correct_rational_cameras_ransac_with_intial_process(bprb_func_process & pro)92 bool volm_correct_rational_cameras_ransac_with_intial_process(bprb_func_process& pro)
93 {
94   using namespace volm_correct_rational_cameras_ransac_with_initial_process_globals;
95   if (!pro.verify_inputs()) {
96     std::cerr << pro.name() << ": Wrong Inputs!!!\n";
97     return false;
98   }
99   // get the inputs
100   unsigned in_i = 0;
101   volm_satellite_resources_sptr res = pro.get_input<volm_satellite_resources_sptr>(in_i++);
102   std::string input_txt   = pro.get_input<std::string>(in_i++);
103   std::string dem_folder  = pro.get_input<std::string>(in_i++);
104   std::string output_path = pro.get_input<std::string>(in_i++);
105   auto pix_rad          = pro.get_input<float>(in_i++);
106   bool enforce_existing  = pro.get_input<bool>(in_i++);
107 
108   if (enforce_existing)
109     std::cout << "!!!!!!! enforce to have at least 2 existing images!\n";
110   else
111     std::cout << "!!!!!!! DO NOT enforce to have at least 2 existing images!\n";
112 
113   // read the track file
114   std::ifstream ifs(input_txt.c_str());
115   if (!ifs) {
116     std::cerr << pro.name() << ": can not open file: " << input_txt << "!!\n";
117     return false;
118   }
119   unsigned n;
120   ifs >> n;
121   if (!n) {
122     std::cerr << pro.name() << ": 0 correspondences in file: " << input_txt << "! returning without correcting any cams!\n";
123     return false;
124   }
125 
126   std::cout << "will read: " << n << " correspondences for each frame from " << input_txt << std::endl;
127   std::vector<std::string> cam_names;
128   std::vector<std::string> in_cam_files;
129   std::vector<std::vector<vgl_point_2d<double> > > corrs;
130   std::vector<std::string> out_cam_files;
131 
132   while (!ifs.eof())
133   {
134     std::string cam_name;
135     ifs >> cam_name;
136     if (cam_name.size() < 2) break;
137     std::cout << "reading camera: " << cam_name << std::endl;
138     std::string out_cam_file = output_path + cam_name + "_corrected.rpb";
139     std::cout << "output camera file: " << out_cam_file << std::endl;
140 
141     // locate the original cameras in the resources
142     std::pair<std::string, std::string> img_path = res->full_path(cam_name);
143     if (img_path.first.compare("") == 0) {
144       std::cerr << pro.name() << ": Can not locate " << cam_name << " in satellite resources! exiting!\n";
145       return false;
146     }
147 
148     cam_names.push_back(cam_name);
149     in_cam_files.push_back(img_path.first);
150     out_cam_files.push_back(out_cam_file);
151 
152     std::vector<vgl_point_2d<double> > corrs_frame;
153     for (unsigned ii = 0; ii < n; ii++) {
154       double i, j;
155       ifs >> i;  ifs >> j;
156       vgl_point_2d<double> cor(i,j);
157       corrs_frame.push_back(cor);
158     }
159     corrs.push_back(corrs_frame);
160   }
161   ifs.close();
162 
163   // define camera weights
164   std::vector<float> cam_weights;
165   std::vector<vpgl_rational_camera<double> > cams;
166   std::vector<std::vector<vgl_point_2d<double> > > new_corrs;
167 
168   unsigned cnt_exist = 0;
169 
170   // now determine which cameras already exist
171   for (unsigned i = 0; i < out_cam_files.size(); i++) {
172     if (vul_file::exists(out_cam_files[i]))
173     {
174       cnt_exist++;
175       // load corrected camera
176       vpgl_rational_camera<double> *ratcam = read_rational_camera<double>(out_cam_files[i]);
177       if (!ratcam) {
178         std::cerr << pro.name() << ": Failed to load rational camera from file " << out_cam_files[i] << "\n";
179         return false;
180       }
181       cams.push_back(*ratcam);
182       cam_weights.push_back(1.0f);
183       new_corrs.push_back(corrs[i]);
184     }
185     else // load original camera and start correction
186     {
187       vpgl_rational_camera<double> *ratcam = load_cam_from_nitf(in_cam_files[i]);
188       if (!ratcam) {
189         std::cerr << pro.name() << ": Failed to load original camera from original image path " << in_cam_files[i] << "\n";
190         return false;
191       }
192       cams.push_back(*ratcam);
193       cam_weights.push_back(0.0f);
194       new_corrs.push_back(corrs[i]);
195     }
196   }
197   if (enforce_existing && cnt_exist < 2) {
198     std::cerr << pro.name() << ": Enforcing condition to have 2 pre-existing corrected cameras! EXITING since there is: " << cnt_exist << " cameras.\n";
199     return false;
200   }
201 
202   // re-distribute weight if there is no corrected camera
203   if (cnt_exist == 0) {
204     cam_weights.assign(cam_weights.size(), 1.0f/cams.size());
205   }
206   else {
207     if (cnt_exist < 2) {
208       std::cerr << pro.name() << ": If pre-existing camera exists, there should be at least 2 corrected cameras!\n";
209       return false;
210     }
211   }
212   std::cout << " assigned camera weights: \n";
213   for (unsigned i = 0; i < cams.size(); i++)
214     std::cout << cam_names[i] << " weight: " << cam_weights[i] << std::endl;
215   std::cout << "camera size: " << cams.size() << " corrs size: " << new_corrs.size() << std::endl;
216   std::cout.flush();
217 
218   // calculate the initial guessing point
219   vgl_point_3d<double> initial_pt;
220   double zmin, zmax;
221   if (!initial_point_by_overlap_region(res, cam_names, dem_folder, initial_pt, zmin, zmax)) {
222     std::cerr << pro.name() << ": Evaluating initial point failed!\n";
223     return false;
224   }
225   // calculate the relative diameter given the fact that all correspondence must be inside the overlapped region of all cameras
226   double relative_diameter;
227   if (!obtain_relative_diameter(res, cam_names, relative_diameter)) {
228     std::cerr << pro.name() << ": Evaluating relative diameter failed!\n";
229     return false;
230   }
231 
232   // adjust using each correspondence and save the offsets
233   std::cout << "Executing adjust image offsets..." << std::endl;
234   std::cout << "initial 3-d point for back projection: " << initial_pt << std::endl;
235   std::cout << "height range: [" << zmin << ',' << zmax << ']' << std::endl;
236   std::cout << "relative diameter: " << relative_diameter << std::endl;
237   std::vector<std::vector<vgl_vector_2d<double> > > cam_trans;
238   std::vector<unsigned> corrs_ids;
239   for (unsigned i = 0; i < n; i++)
240   {
241     // rearrange the correspondence
242     std::vector<vgl_point_2d<double> > corrs_i;
243     corrs_i.reserve(new_corrs.size());
244 for (auto & new_corr : new_corrs)
245       corrs_i.push_back(new_corr[i]);
246     std::vector<vgl_vector_2d<double> > cam_trans_i;
247     vgl_point_3d<double> intersection;
248     if (!vpgl_rational_adjust_onept::adjust_with_weights(cams, cam_weights, corrs_i, initial_pt, zmin, zmax, cam_trans_i, intersection, relative_diameter))
249     {
250 #if 1
251       std::cout << "correspondence adjustment failed for correspondence: " << std::endl;
252       for (auto & ii : corrs_i)
253         std::cout << "[" << ii.x() << "," << ii.y() << "]\t";
254       std::cout << '\n';
255 #endif
256       continue;
257     }
258     cam_trans.push_back(cam_trans_i);
259     corrs_ids.push_back(i);
260 
261 #if 1
262     std::cout << i << " --> correspondence: ";
263     for (auto & i : corrs_i) {
264         std::cout << "[" << i.x() << "," << i.y() << "]\t";
265     }
266     std::cout << " --> project to 3D intersection point: [" << std::setprecision(12) << intersection.y()
267                                                          << "," << std::setprecision(12) << intersection.x()
268                                                          << "," << std::setprecision(12) << intersection.z()
269                                                          << "], giving offset: ";
270     std::cout << " --> camera translation: ";
271     for (auto & i : cam_trans_i) {
272       std::cout << "[" << i.x() << "," << i.y() << "]\t";
273     }
274     std::cout << '\n';
275 #endif
276   }
277   std::cout << "out of " << n << " correspondences " << cam_trans.size() << " of them back-projected to 3-d world point successfully:";
278   for (unsigned int corrs_id : corrs_ids)
279     std::cout << ' ' << corrs_id;
280   std::cout << '\n';
281 
282   if (!cam_trans.size()) {
283     std::cout << "out of " << n << " correspondences " << cam_trans.size() << " of them yielded corrections! exit without any correction!\n";
284     return false;
285   }
286 
287   // find the inliers
288   std::vector<unsigned> inlier_cnts(cam_trans.size(), 0);
289   std::vector<std::vector<unsigned> > inliers;
290   for (unsigned i = 0; i < cam_trans.size(); i++) {  // for each correction find how many inliers are there for it
291     std::vector<unsigned> inliers_i;
292     inliers_i.push_back(corrs_ids[i]); // first push itself
293     inlier_cnts[i]++;
294     for (unsigned j = 0; j < cam_trans.size(); j++) {
295       if (i == j) continue;
296       double dif = 0;
297       for (unsigned k = 0; k < cam_trans[i].size(); k++) {
298         vgl_point_2d<double> trans1(cam_trans[i][k].x(), cam_trans[i][k].y());
299         vgl_point_2d<double> trans2(cam_trans[j][k].x(), cam_trans[j][k].y());
300         dif += vgl_distance(trans1, trans2);
301       }
302       dif /= cam_trans[i].size();
303       if (dif < pix_rad) {
304         inlier_cnts[i]++;
305         inliers_i.push_back(corrs_ids[j]);
306       }
307     }
308     inliers.push_back(inliers_i);
309   }
310   unsigned max = 0;
311   unsigned max_i = 0;
312   for (unsigned i = 0; i < cam_trans.size(); i++) {
313     if (max < inlier_cnts[i]) {
314       max = inlier_cnts[i];
315       max_i = i;
316     }
317   }
318   std::cout << "out of " << cam_trans.size() << " valid correspondences, " << max << " of them yield constant translations using " << pix_rad << " pixel radius" << std::endl;
319   // check whether the inliers count is sufficient
320   double inlier_ratio = (double)max / cam_trans.size();
321   if (inlier_ratio < 0.1) {
322     std::cout << pro.name() << ": less than 10% of correspondence yield constant translations due to bad correspondence, correction failed" << std::endl;
323     return false;
324   }
325 
326 #if 1
327   std::cout << "correspondence that provides inliers: " << std::endl;
328   for (unsigned int j : inliers[max_i])
329     std::cout << j << ' ';
330   std::cout << '\n';
331 #endif
332   // use the correspondence with the most number of inliers to correct the cameras
333   std::cout << "correction offset: " << std::endl;
334   for (unsigned k = 0; k < cams.size(); k++)
335     std::cout << "camera " << k << " --> offset_u: " << cam_trans[max_i][k].x() << " offset_v: " << cam_trans[max_i][k].y() << std::endl;
336   for (unsigned k = 0; k < cams.size(); k++) {
337     double u_off, v_off;
338     cams[k].image_offset(u_off, v_off);
339     cams[k].set_image_offset(u_off + cam_trans[max_i][k].x(), v_off + cam_trans[max_i][k].y());
340   }
341 
342   // refine the cameras using all the inliers of this correspondence
343   std::vector<std::vector<vgl_point_2d<double> > > corrs_inliers;
344   for (auto & new_corr : new_corrs) {
345     std::vector<vgl_point_2d<double> > vec;
346     for (unsigned int j : inliers[max_i])
347       vec.push_back(new_corr[j]);
348     corrs_inliers.push_back(vec);
349   }
350   std::vector<vgl_vector_2d<double> > cam_trans_inliers;
351   std::vector<vgl_point_3d<double> > intersections;
352   //std::vector<float> cam_weights_equal(cams.size(), 1.0f/cams.size());
353   //if (!vpgl_rational_adjust_multiple_pts::adjust_lev_marq(cams, cam_weights_equal, corrs_inliers, cam_trans_inliers, intersections))
354   //if (!vpgl_rational_adjust_multiple_pts::adjust_lev_marq(cams, cam_weights, corrs_inliers, cam_trans_inliers, intersections))
355   if (!vpgl_rational_adjust_multiple_pts::adjust_lev_marq(cams, cam_weights, corrs_inliers, initial_pt, zmin, zmax, cam_trans_inliers, intersections, relative_diameter))
356   {
357     std::cerr << "In vpgl_correct_rational_cameras_process - adjustment failed\n";
358     return false;
359   }
360 
361 #if 1
362   std::cout << " after refinement: \n";
363   for (auto & intersection : intersections)
364     std::cout << "after adjustment 3D intersection point: " << std::setprecision(12) << intersection.y() << "," << std::setprecision(12) << intersection.x()
365                                                            << "," << std::setprecision(12) << intersection.z()
366                                                            << std::endl;
367 #endif
368   std::cout << "correction offset from refinement:" << std::endl;
369   for (auto & cam_trans_inlier : cam_trans_inliers)   // for each correction find how many inliers are there for it
370     std::cout << "offset_u: " << cam_trans_inlier.x() << " v: " << cam_trans_inlier.y() << std::endl;
371 
372   for (unsigned i = 0; i < cams.size(); i++) {
373     double u_off,v_off;
374     cams[i].image_offset(u_off,v_off);
375     cams[i].set_image_offset(u_off + cam_trans_inliers[i].x(), v_off + cam_trans_inliers[i].y());
376     cams[i].save(out_cam_files[i]);
377   }
378 
379   return true;
380 }
381 
load_cam_from_nitf(std::string const & nitf_img_path)382 vpgl_rational_camera<double>* volm_correct_rational_cameras_ransac_with_initial_process_globals::load_cam_from_nitf(std::string const& nitf_img_path)
383 {
384   vil_image_resource_sptr image = vil_load_image_resource(nitf_img_path.c_str());
385   if (!image)
386   {
387     return nullptr;
388   }
389   std::string format = image->file_format();
390   std::string prefix = format.substr(0,4);
391   if (prefix != "nitf") {
392     return nullptr;
393   }
394   // cast to an nitf2_image
395   auto *nitf_image = static_cast<vil_nitf2_image*>(image.ptr());
396   auto *nitf_cam = new vpgl_nitf_rational_camera(nitf_image, true);
397   return dynamic_cast<vpgl_rational_camera<double>*>(nitf_cam);
398 }
399 
obtain_relative_diameter(volm_satellite_resources_sptr const & sat_res,std::vector<std::string> const & camera_names,double & relative_diameter)400 bool volm_correct_rational_cameras_ransac_with_initial_process_globals::obtain_relative_diameter(volm_satellite_resources_sptr const& sat_res,
401                                                                                                  std::vector<std::string> const& camera_names,
402                                                                                                  double& relative_diameter)
403 {
404   relative_diameter = 1.0;
405   // obtain the overlap region
406   std::vector<vgl_box_2d<double> > img_footprints;
407   auto cit = camera_names.begin();
408   for (; cit != camera_names.end(); ++cit)
409   {
410     std::pair<std::string, std::string> img_path = sat_res->full_path(*cit);
411     if (img_path.first.compare("") == 0)
412       return false;
413     brad_image_metadata meta(img_path.first);
414     double ll_lon = meta.lower_left_.x();
415     double ll_lat = meta.lower_left_.y();
416     double ur_lon = meta.upper_right_.x();
417     double ur_lat = meta.upper_right_.y();
418     vgl_box_2d<double> img_box(ll_lon, ur_lon, ll_lat, ur_lat);
419     img_footprints.push_back(img_box);
420   }
421 
422   vgl_box_2d<double> overlap_region = volm_correct_rational_cameras_ransac_with_initial_process_globals::intersection(img_footprints);
423   double width  = overlap_region.width();
424   double height = overlap_region.height();
425   // calculate the diameter by the diagonal
426   double diagonal = std::sqrt(width*width + height*height);
427   if (overlap_region.centroid_x() > overlap_region.centroid_y())
428     relative_diameter = 0.5*diagonal/overlap_region.centroid_y();
429   else
430     relative_diameter = 0.5*diagonal/overlap_region.centroid_x();
431   return true;
432 }
433 
initial_point_by_overlap_region(volm_satellite_resources_sptr const & sat_res,std::vector<std::string> const & camera_names,std::string const & dem_folder,vgl_point_3d<double> & init_pt,double & zmin,double & zmax)434 bool volm_correct_rational_cameras_ransac_with_initial_process_globals::initial_point_by_overlap_region(volm_satellite_resources_sptr const& sat_res,
435                                                                                                         std::vector<std::string> const& camera_names,
436                                                                                                         std::string const& dem_folder,
437                                                                                                         vgl_point_3d<double>& init_pt,
438                                                                                                         double& zmin, double& zmax)
439 {
440   std::vector<volm_img_info> infos;
441   volm_io_tools::load_aster_dem_imgs(dem_folder, infos);
442   if (infos.empty()) {
443     std::cerr << "can not find any height map in the DEM folder " << dem_folder << '\n';
444     return false;
445   }
446   // obtain the overlap region
447   std::vector<vgl_box_2d<double> > img_footprints;
448   auto cit = camera_names.begin();
449   for (; cit != camera_names.end(); ++cit)
450   {
451     std::pair<std::string, std::string> img_path = sat_res->full_path(*cit);
452     if (img_path.first.compare("") == 0)
453       return false;
454     brad_image_metadata meta(img_path.first);
455     double ll_lon = meta.lower_left_.x();
456     double ll_lat = meta.lower_left_.y();
457     double ur_lon = meta.upper_right_.x();
458     double ur_lat = meta.upper_right_.y();
459     vgl_box_2d<double> img_box(ll_lon, ur_lon, ll_lat, ur_lat);
460     img_footprints.push_back(img_box);
461   }
462   vgl_box_2d<double> overlap_region = volm_correct_rational_cameras_ransac_with_initial_process_globals::intersection(img_footprints);
463 
464   // find the min and max height of the overlapped region
465   double min_elev = 10000.0, max_elev = -10000.0;
466   vgl_point_2d<double> lower_left = overlap_region.min_point();
467   vgl_point_2d<double> upper_right = overlap_region.max_point();
468   if (!volm_io_tools::find_min_max_height(lower_left, upper_right, infos, min_elev, max_elev)){
469     std::cerr << "can not find elevation for the overlap region " << overlap_region.min_point() << ", " << overlap_region.max_point() << '\n';
470     return false;
471   }
472   zmin = min_elev - 10;
473   zmax = max_elev + 10;
474   init_pt.set(overlap_region.centroid_x(), overlap_region.centroid_y(), zmin);
475   return true;
476 }
477 
intersection(std::vector<vgl_box_2d<double>> const & boxes)478 vgl_box_2d<double> volm_correct_rational_cameras_ransac_with_initial_process_globals::intersection(std::vector<vgl_box_2d<double> > const& boxes)
479 {
480   if (boxes.size() == 2) {
481     return vgl_intersection(boxes[0], boxes[1]);
482   }
483   std::vector<vgl_box_2d<double> > new_boxes;
484   vgl_box_2d<double> box = vgl_intersection(boxes[0], boxes[1]);
485   if (box.is_empty())
486     return box;
487   new_boxes.push_back(box);
488   for (unsigned i = 2; i < boxes.size(); i++)
489     new_boxes.push_back(boxes[i]);
490   return volm_correct_rational_cameras_ransac_with_initial_process_globals::intersection(new_boxes);
491 }
492