licornea_tools
cg_straight_depths_from_disparity.cc
Go to the documentation of this file.
1 #include <iostream>
2 #include <utility>
3 #include <vector>
4 #include <cmath>
5 #include <algorithm>
6 #include <map>
8 #include "lib/feature_points.h"
10 #include "../lib/args.h"
11 #include "../lib/misc.h"
12 #include "../lib/intrinsics.h"
13 #include "../lib/json.h"
14 #include "../lib/eigen.h"
15 #include "../lib/assert.h"
16 #include <Eigen/Sparse>
17 #include <Eigen/IterativeLinearSolvers>
18 #include <Eigen/SparseLU>
19 #include <Eigen/SparseQR>
20 
21 using namespace tlz;
22 
23 constexpr bool verbose = false;
24 constexpr int min_position_pairs_count = 300;
26 constexpr bool make_visualizations = false;
27 
28 using view_feature_positions = std::map<view_index, vec2>;
29 
30 using view_feature_position_pair = std::pair<vec2, vec2>;
31 using view_feature_position_pairs = std::map<view_index, view_feature_position_pair>;
32 
33 
35  const view_feature_positions& views1, const view_feature_positions& views2
36 ) {
37  view_feature_position_pairs out_pairwise;
38  auto it1 = views1.begin();
39  auto it2 = views2.begin();
40  while(it1 != views1.end() && it2 != views2.end()) {
41  if(it1->first < it2->first) {
42  ++it1;
43  } else if(it2->first < it1->first) {
44  ++it2;
45  } else {
46  const view_index& idx = it1->first;
47  out_pairwise.emplace(idx, view_feature_position_pair(it1->second, it2->second));
48  ++it1;
49  ++it2;
50  }
51  }
52  return out_pairwise;
53 }
54 
55 
56 
58  real scale = NAN;
59  real weight = 0.0;
60 
62  estimate_relative_scale_result(real sc, real w) : scale(sc), weight(w) { }
63 
64  explicit operator bool () const { return ! std::isnan(scale); }
65 };
67  const view_feature_position_pairs& src_tg_positions,
68  const view_index& center_view = view_index()
69 ) {
70  // find scale s and translation t which maps source positions src_i to target positions tg_i:
71  // tg_i = s*src_i + t
72 
73  real scale;
74  vec2 translation;
75 
76  if(center_view) {
77  // center on center_view, and solve for scale only
78  vec2 src_center_position = src_tg_positions.at(center_view).first;
79  vec2 tg_center_position = src_tg_positions.at(center_view).second;
80 
81  real scales_sum = 0, scales_weights_sum = 0;
82  for(const auto& kv : src_tg_positions) {
83  const view_index& idx = kv.first;
84  if(idx == center_view) continue;
85  const view_feature_position_pair& p = kv.second;
86  const vec2& src_position = p.first;
87  const vec2& tg_position = p.second;
88 
89  const vec2& src_offset = src_position - src_center_position;
90  const vec2& tg_offset = tg_position - tg_center_position;
91 
92  real x_scale = tg_offset[0] / src_offset[0];
93  real x_weight = std::max({ sq(src_offset[0]), sq(tg_offset[0]) });
94  scales_sum += x_weight * x_scale;
95  scales_weights_sum += x_weight;
96 
97  real y_scale = tg_offset[1] / src_offset[1];
98  real y_weight = std::max({ sq(src_offset[1]), sq(tg_offset[1]) });
99  scales_sum += y_weight * y_scale;
100  scales_weights_sum += y_weight;
101  }
102 
103  scale = scales_sum / scales_weights_sum;
104  translation = tg_center_position - src_center_position*scale;
105 
106  } else {
107  // solve using linear least squares system Ax = b with x = [s, tx, ty]
108  std::size_t n = src_tg_positions.size();
109 
110  // fill matrices A, b
111  Eigen_matXn<3> A(2*n, 3);
112  Eigen_vecX b(2*n);
113  std::ptrdiff_t row = 0;
114  for(const auto& kv : src_tg_positions) {
115  const view_feature_position_pair& p = kv.second;
116  const vec2& src_position = p.first;
117  const vec2& tg_position = p.second;
118 
119  A(row, 0) = src_position[0];
120  A(row, 1) = 1.0;
121  A(row, 2) = 0.0;
122  b[row] = tg_position[0];
123  row++;
124 
125  A(row, 0) = src_position[1];
126  A(row, 1) = 0.0;
127  A(row, 2) = 1.0;
128  b[row] = tg_position[1];
129  row++;
130  }
131 
132  // solve using linear least squares
133  Eigen::ColPivHouseholderQR<Eigen_matXn<3>> solver(A);
134  if(solver.info() != Eigen::Success) return estimate_relative_scale_result();
135  Eigen_vec<3> x = solver.solve(b);
136  if(solver.info() != Eigen::Success) return estimate_relative_scale_result();
137 
138  scale = x[0];
139  translation = vec2(x[1], x[2]);
140  }
141 
142 
143 
144  // measure error of solution
145  real error = 0.0;
146  for(const auto& kv : src_tg_positions) {
147  const view_feature_position_pair& p = kv.second;
148  const vec2& src_position = p.first;
149  const vec2& tg_position = p.second;
150 
151  vec2 src_to_tg_position = scale*src_position + translation;
152 
153  vec2 diff = tg_position - src_to_tg_position;
154  error += sq(diff[0]) + sq(diff[1]);
155  }
156  error = std::sqrt(error / real(src_tg_positions.size()));
157 
158  //std::cout << "err=" << error << std::endl;
159 
161 
162  real weight = (center_view ? 10.0 : 1.0) * 1.0/sq(error);
163 
164  return estimate_relative_scale_result(scale, weight);
165 }
166 
167 
168 Eigen_vecX compute_global_ratios(const Eigen_matXX& ratios, const Eigen_matXX& weights) {
169  std::size_t ratios_count = 0;
170  std::size_t features_count = ratios.rows();
171  for(int ref = 0; ref < features_count; ++ref) for(int tg = ref+1; tg < features_count; ++tg)
172  if(weights(tg, ref) != 0.0) ++ratios_count;
173 
174  std::size_t rows = ratios_count + 1;
175  std::size_t cols = features_count;
176 
177  Eigen::SparseMatrix<real> A(rows, cols);
178  Eigen::SparseVector<real> b(rows);
179  std::ptrdiff_t row = 0;
180  for(int ref = 0; ref < features_count; ++ref) for(int tg = ref+1; tg < features_count; ++tg) {
181  real weight = weights(tg, ref);
182  if(weight == 0.0) continue;
183 
184  A.insert(row, tg) = ratios(tg, ref);
185  A.insert(row, ref) = -1.0;
186 
187  ++row;
188  }
189 
190  b.insert(row) = 1.0;
191  A.insert(row, 0) = 1.0;
192 
193  A.makeCompressed();
194 
195  Eigen::SparseQR<Eigen::SparseMatrix<real>, Eigen::COLAMDOrdering<int>> solver;
196 
197  solver.compute(A);
198  //if(solver.info() != Eigen::Success) throw std::runtime_error("solver.compute(A) failed");
199 
200  Eigen_vecX x = solver.solve(b);
201  //if(solver.info() != Eigen::Success) throw std::runtime_error("solver.solve(b) failed");
202 
203  Assert(x.rows() == cols);
204 
205  return x;
206 }
207 
208 
209 
210 Eigen_vecX complete_depths(const Eigen_vecX& known_depths, const Eigen_vecX& global_ratios) {
211  real ratios_sum = 0.0, known_sum = 0.0;
212 
213  for(std::ptrdiff_t i = 0; i < known_depths.rows(); ++i) {
214  if(known_depths[i] == 0) continue;
215  if(global_ratios[i] == 0) continue;
216 
217  ratios_sum += global_ratios[i];
218  known_sum += known_depths[i];
219  }
220  real scale = known_sum / ratios_sum;
221 
222  Eigen_vecX depths = scale * global_ratios;
223 
224  real rms_error = 0.0;
225  real count = 0.0;
226  for(std::ptrdiff_t i = 0; i < known_depths.rows(); ++i) {
227  if(known_depths[i] == 0) continue;
228  if(depths[i] == 0) continue;
229 
230  real known = known_depths[i];
231  real re_known = depths[i];
232 
233  rms_error += sq(known - re_known);
234  count += 1.0;
235  }
236  rms_error = std::sqrt(rms_error / count);
237  std::cout << "rms error: " << rms_error << std::endl;
238 
239  return depths;
240 }
241 
242 
243 
244 int main(int argc, const char* argv[]) {
245  get_args(argc, argv, "image_correspondences.json intrinsics.json R.json straight_depths.json out_straight_depths.json");
247  intrinsics intr = intrinsics_arg();
248  mat33 R = decode_mat(json_arg());
249  straight_depths in_depths = straight_depths_arg();
250  std::string out_depths_filename = out_filename_arg();
251 
252  std::cout << std::setprecision(10);
253  Eigen::initParallel();
254 
255  const int default_num_threads = Eigen::nbThreads();
256  Eigen::setNbThreads(0);
257 
258  mat33 unrotate = intr.K * R.t() * intr.K_inv;
259 
260  auto all_views = get_all_views(cors);
261  auto all_features = get_feature_names(cors);
262 
263  std::size_t features_count = all_features.size();
264  //features_count = 20;
265  std::cout << "feature count: " << features_count << std::endl;
266 
267  std::map<std::string, int> feature_indices;
268  for(std::ptrdiff_t i = 0; i < features_count; ++i)
269  feature_indices[all_features[i]] = i;
270 
271  // undistort & unrotate correspondences, and get view feature positions foreach feature
272  // all_view_feature_xy = points in normalized view spaces (v_z = 1)
273  std::cout << "undistorting correspondences" << std::endl;
274  image_correspondences undist_cors = undistort(cors, intr);
275  std::cout << "collecting unrotated view feature positions" << std::endl;
276  std::map<std::string, view_feature_positions> all_view_feature_xy;
277  for(const auto& kv : undist_cors.features) {
278  const std::string& feature_name = kv.first;
279  const image_correspondence_feature& feature = kv.second;
281  for(const auto& kv2 : feature.points) {
282  const view_index& idx = kv2.first;
283  const feature_point& fpoint = kv2.second;
284  vec2 unrotated_xy = mul_h(unrotate, fpoint.position);
285  all_view_feature_xy[feature_name][idx] = unrotated_xy;
286  }
287  }
288 
289  // calculate relative scale ratio for all feature pairs, using the image correspondences
290  Eigen_matXX scale_ratios(features_count, features_count);
291  Eigen_matXX weights(features_count, features_count);
292  scale_ratios.setZero();
293  weights.setZero();
294 
295 
296  std::cout << "estimating pairwise relative scales of disparities" << std::endl;
297  #pragma omp parallel for schedule(guided)
298  for(int ref = 0; ref < features_count; ++ref) for(int tg = ref+1; tg < features_count; ++tg) {
299  // get feature position pairs for source and target feature
300  // for views on which both features are present
301  const std::string& src_feature_name = all_features.at(ref);
302  const view_feature_positions& src_positions = all_view_feature_xy.at(src_feature_name);
303  const std::string& tg_feature_name = all_features.at(tg);
304  const view_feature_positions& tg_positions = all_view_feature_xy.at(tg_feature_name);
305 
306  auto src_tg_position_pairs = common_view_feature_positions(src_positions, tg_positions);
307  if(src_tg_position_pairs.size() < min_position_pairs_count) continue;
308 
309 
310  // estimate scale of target view feature positions, relative to corresponding reference view features
312  view_index src_reference = cors.features.at(src_feature_name).reference_view;
313  view_index tg_reference = cors.features.at(tg_feature_name).reference_view;
314  if(src_reference == tg_reference) result = estimate_relative_scale(src_tg_position_pairs, src_reference);
315  else result = estimate_relative_scale(src_tg_position_pairs);
316  if(! result) continue;
317 
318  scale_ratios(tg, ref) = result.scale;
319  weights(tg, ref) = result.weight;
320 
321  if(verbose)
322  std::cout << ref << "/" << (features_count-1) << " <--> " << tg << "/" << (features_count-1) << std::endl;
323  else
324  std::cout << '.' << std::flush;
325  }
326  std::cout << std::endl;
327 
328 
329  if(make_visualizations) {
330  cv::Mat_<real> weights_img;
331  cv::eigen2cv(weights, weights_img);
332  cv::Mat_<uchar> weights_img2;
333  cv::normalize(weights_img, weights_img2, 0, 255, cv::NORM_MINMAX, CV_8UC1);
334  cv::resize(weights_img2, weights_img2, cv::Size(0,0), 3, 3, cv::INTER_NEAREST);
335  cv::imwrite("pairwise_scale_weights.png", weights_img2, { CV_IMWRITE_PNG_COMPRESSION, 9 });
336 
337  cv::Mat_<real> ratios_img;
338  cv::eigen2cv(scale_ratios, ratios_img);
339  cv::Mat_<uchar> ratios_img2;
340  cv::normalize(ratios_img, ratios_img2, 0, 255, cv::NORM_MINMAX, CV_8UC1);
341  cv::resize(ratios_img2, ratios_img2, cv::Size(0,0), 5, 5, cv::INTER_NEAREST);
342  cv::imwrite("pairwise_scales.png", ratios_img2, { CV_IMWRITE_PNG_COMPRESSION, 9 });
343  }
344 
345 
346  std::cout << "calculating global scale ratios" << std::endl;
347  Eigen::setNbThreads(default_num_threads);
348  Eigen_vecX global_scale_ratios = compute_global_ratios(scale_ratios, weights);
349 
350 
351  std::cout << "calculating depths" << std::endl;
352  Eigen_vecX known_depths(features_count);
353  known_depths.setZero();
354  for(const auto& kv : in_depths) {
355  const std::string& feature_name = kv.first;
356  const straight_depth& sdepth = kv.second;
357  auto feature_index_it = feature_indices.find(feature_name);
358  if(feature_index_it != feature_indices.end()) {
359  int feature_index = feature_index_it->second;
360  known_depths[feature_index] = sdepth.depth;
361  }
362  }
363  Eigen_vecX depths = complete_depths(known_depths, global_scale_ratios);
364 
365 
366  Eigen_matXn<2> known_completed(features_count, 2);
367  known_completed.setZero();
368  known_completed.block(0, 0, features_count, 1) = known_depths;
369  known_completed.block(0, 1, features_count, 1) = depths;
370 
371  std::cout << std::setprecision(10);
372  std::cout << "known/completed:\n" << std::endl;
373  for(int row = 0; row < features_count; ++row) {
374  if(known_depths[row] == 0) continue;
375  if(depths[row] == 0) continue;
376  std::cout << known_completed(row, 0) << ";" << known_completed(row, 1) << "\n";
377  }
378 
379 
380  std::cout << "saving computed depths" << std::endl;
381  straight_depths out_straight_depths;
382  for(int row = 0; row < features_count; ++row) {
383  if(depths[row] == 0) continue;
384  real depth = depths[row];
385  out_straight_depths[all_features[row]] = depth;
386  }
387  export_json_file(encode_straight_depths(out_straight_depths), out_depths_filename);
388 }
estimate_relative_scale_result estimate_relative_scale(const view_feature_position_pairs &src_tg_positions, const view_index &center_view=view_index())
Numeric sq(Numeric n)
Compute square of a number.
Definition: misc.h:17
std::map< view_index, vec2 > view_feature_positions
json encode_straight_depths(const straight_depths &depths)
std::vector< vec2 > positions(const feature_points &fpoints)
Eigen::Matrix< real, Eigen::Dynamic, Eigen::Dynamic > Eigen_matXX
Definition: eigen.h:11
std::vector< view_index > get_all_views(const image_correspondences &cors)
view_feature_position_pairs common_view_feature_positions(const view_feature_positions &views1, const view_feature_positions &views2)
int rows
std::map< view_index, view_feature_position_pair > view_feature_position_pairs
Set of features, each on set of views.
Eigen::Matrix< real, Rows, 1 > Eigen_vec
Definition: eigen.h:19
constexpr real max_relative_scale_error
constexpr bool verbose
cv::Vec< real, 2 > vec2
Definition: common.h:22
Eigen::Matrix< real, Eigen::Dynamic, 1 > Eigen_vecX
Definition: eigen.h:18
vec3 mul_h(const mat44 &mat, const vec3 &vec)
Definition: common.h:29
cv::Mat_< real > decode_mat(const json &j)
Definition: json.cc:32
std::map< std::string, image_correspondence_feature > features
intrinsics intrinsics_arg()
Definition: intrinsics.cc:119
json json_arg()
Definition: json.h:34
constexpr bool make_visualizations
cv::Matx< real, 3, 3 > mat33
Definition: common.h:26
Eigen::Matrix< real, Eigen::Dynamic, Cols > Eigen_matXn
Definition: eigen.h:12
std::vector< std::string > get_feature_names(const image_correspondences &cors)
std::map< std::string, straight_depth > straight_depths
double real
Definition: common.h:16
void export_json_file(const json &j, const std::string &filename, bool compact)
Definition: json.cc:9
image_correspondences image_correspondences_arg()
feature_points undistort(const feature_points &dist_fpoints, const intrinsics &intr)
int cols
#define Assert
Definition: assert.h:40
straight_depths straight_depths_arg()
Eigen_vecX complete_depths(const Eigen_vecX &known_depths, const Eigen_vecX &global_ratios)
std::string out_filename_arg()
Definition: args.cc:104
Feature on set of views. Optionally one view is "reference".
int main(int argc, const char *argv[])
Eigen_vecX compute_global_ratios(const Eigen_matXX &ratios, const Eigen_matXX &weights)
std::map< view_index, feature_point > points
std::pair< vec2, vec2 > view_feature_position_pair
constexpr int min_position_pairs_count
void get_args(int argc, const char *argv[], const std::string &usage)
Definition: args.cc:49