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