licornea_tools
cg_optical_flow_cors.cc
Go to the documentation of this file.
1 #include <iostream>
2 #include <fstream>
3 #include <utility>
4 #include <random>
5 #include <vector>
6 #include <string>
7 #include <mutex>
8 #include <cmath>
9 #include <map>
10 #include <format.h>
11 #include "lib/feature_points.h"
13 #include "../lib/args.h"
14 #include "../lib/json.h"
15 #include "../lib/filesystem.h"
16 #include "../lib/dataset.h"
17 #include "../lib/opencv.h"
18 #include "../lib/image_io.h"
19 
20 using namespace tlz;
21 
22 constexpr bool verbose = false;
23 
24 constexpr real max_flow_err = 4.0;
26 constexpr int max_pyramid_level = 3;
27 const cv::Size horizontal_optical_flow_window_size(20, 20);
28 const cv::Size vertical_optical_flow_window_size(20, 20);
29 
30 struct flow_state {
32  std::vector<cv::Mat_<uchar>> image_pyramid;
33  std::vector<cv::Point2f> feature_positions;
34  std::vector<uchar> feature_status;
35 
36  flow_state() = default;
37  flow_state(const view_index& idx, const cv::Mat_<uchar>& img, std::size_t features_count) :
38  view_idx(idx),
39  image_pyramid({img}),
40  feature_positions(features_count),
41  feature_status(features_count) { }
42 
43  std::size_t features_count() const { return feature_positions.size(); }
44  std::size_t valid_features_count() const;
45  bool is_valid() const { return view_idx.is_valid(); }
46 };
47 
48 std::size_t flow_state::valid_features_count() const {
49  std::size_t count = 0;
50  for(uchar status : feature_status) if(status) ++count;
51  return count;
52 }
53 
56 
57 using local_feature_index = std::ptrdiff_t;
58 using correspondence_key_type = std::pair<local_feature_index, view_index>;
59 using correspondences_type = std::map<correspondence_key_type, vec2>;
60 
62  return std::make_pair(i, idx);
63 }
64 
65 
66 void print_flow_indicator(const view_index& origin_idx, const view_index& dest_idx) {
67  char dir = '?';
68  if(origin_idx.y < dest_idx.y) dir = '^';
69  else if(origin_idx.y > dest_idx.y) dir = 'v';
70  else if(origin_idx.x < dest_idx.x) dir = '>';
71  else if(origin_idx.x > dest_idx.x) dir = '<';
72 
73  if(verbose) std::cout << origin_idx << " --" << dir << "-- " << dest_idx << std::endl;
74  else std::cout << dir << std::flush;
75 }
76 
77 
79  std::size_t count = state.feature_positions.size();
80  for(local_feature_index i = 0; i < count; ++i) {
81  if(state.feature_status[i]) {
82  vec2 pos = point2f_to_vec2(state.feature_positions[i]);
83  cors[std::make_pair(i, state.view_idx)] = pos;
84  }
85  }
86 }
87 
88 
89 cv::Mat_<uchar> load_image(const dataset_group& datag, const view_index& idx, bool must_exist) {
90  static std::mutex disk_read_lock;
91  //std::lock_guard<std::mutex> lock(disk_read_lock);
92  std::string image_filename = datag.view(idx).image_filename();
93  if(file_exists(image_filename)) {
94  cv::Mat_<cv::Vec3b> col_img = load_texture(image_filename);
95  cv::Mat_<uchar> gray_img;
96  cv::cvtColor(col_img, gray_img, CV_BGR2GRAY);
97  return gray_img;
98  } else {
99  if(must_exist) throw std::runtime_error("image for " + encode_view_index(idx) + " must exist, but does not");
100  else return cv::Mat_<uchar>();
101  }
102 }
103 
104 
105 
106 flow_state flow_to(flow_state& origin_state, view_index dest_idx, const dataset_group& datag, const cv::Size& window_size, bool may_skip) {
107  std::size_t features_count = origin_state.features_count();
108 
109  if(origin_state.image_pyramid.size() <= max_pyramid_level) {
110  cv::Mat_<uchar> orig_img;
111  origin_state.image_pyramid.front().copyTo(orig_img);
112  origin_state.image_pyramid.clear();
113  cv::buildOpticalFlowPyramid(
114  orig_img,
115  origin_state.image_pyramid,
116  window_size,
118  );
119  }
120 
121  cv::Mat_<uchar> dest_img = load_image(datag, dest_idx, !may_skip);
122  if(may_skip && dest_img.empty()) {
123  return flow_state();
124  }
125 
126  flow_state dest_state(dest_idx, dest_img, features_count);
127  if(max_pyramid_level > 0) {
128  dest_state.image_pyramid.clear();
129  cv::buildOpticalFlowPyramid(
130  dest_img,
131  dest_state.image_pyramid,
132  window_size,
134  );
135  }
136 
137  std::vector<cv::Point2f>& dest_positions = dest_state.feature_positions;
138  std::vector<uchar>& dest_status = dest_state.feature_status;
139  std::vector<float> dest_errs(features_count);
140 
141  cv::TermCriteria term(cv::TermCriteria::COUNT | cv::TermCriteria::EPS, 30, 0.01);
142  cv::calcOpticalFlowPyrLK(
143  origin_state.image_pyramid,
144  dest_state.image_pyramid,
145  origin_state.feature_positions,
146  dest_positions,
147  dest_status,
148  dest_errs,
149  window_size,
151  term
152  );
153 
154 
155  auto position_ok = [&](const cv::Point2f& pos) -> bool {
156  return (pos.x > 0.0) && (pos.y > 0.0) && (pos.x < dest_img.cols) && (pos.y < dest_img.rows);
157  };
158 
159  for(std::ptrdiff_t feature = 0; feature < features_count; ++feature) {
160  bool status =
161  origin_state.feature_status[feature] &&
162  dest_status[feature] &&
163  position_ok(dest_positions[feature]) &&
164  dest_errs[feature] <= max_flow_err;
165  dest_status[feature] = status;
166  }
167 
168  return dest_state;
169 }
170 
171 
172 void do_horizontal_optical_flow(correspondences_type& cors, const view_index& reference_idx, const flow_state& mid_x_state, const dataset_group& datag, bool verb = false) {
173  const dataset& datas = datag.set();
174  int x_min = std::max(datas.x_min(), reference_idx.x - horizontal_outreach);
175  int x_max = std::min(datas.x_max(), reference_idx.x + horizontal_outreach);
176 
177  flow_state state = mid_x_state;
178  state.image_pyramid = { state.image_pyramid.front() }; // let flow_to() recalculate pyramid, because window size if different
179 
180  if(verb) std::cout << "horizontal optical flow by increasing x starting at mid_x..." << std::endl;
181  for(int x = mid_x_state.view_idx.x + datas.x_step(); x <= x_max; x += datas.x_step()) {
182  view_index idx(x, mid_x_state.view_idx.y);
183  print_flow_indicator(state.view_idx, idx);
184  flow_state new_state = flow_to(state, idx, datag, horizontal_optical_flow_window_size, true);
185  if(new_state.is_valid()) {
186  add_correspondences(cors, new_state);
187  state = std::move(new_state);
188  }
189  if(state.valid_features_count() == 0) break;
190  }
191 
192  if(verb) std::cout << "\nhorizontal optical flow by decreasing x starting at mid_x..." << std::endl;
193  state = mid_x_state;
194  for(int x = mid_x_state.view_idx.x - datas.x_step(); x >= x_min; x -= datas.x_step()) {
195  view_index idx(x, mid_x_state.view_idx.y);
196  print_flow_indicator(state.view_idx, idx);
197  flow_state new_state = flow_to(state, idx, datag, horizontal_optical_flow_window_size, true);
198  if(new_state.is_valid()) {
199  add_correspondences(cors, new_state);
200  state = std::move(new_state);
201  }
202  if(state.valid_features_count() == 0) break;
203  }
204 }
205 
206 
207 correspondences_type do_2d_optical_flow(const dataset_group& datag, const view_index& reference_idx, const std::vector<vec2>& reference_points) {
208  const dataset& datas = datag.set();
209  int y_min = std::max(datas.y_min(), reference_idx.y - vertical_outreach);
210  int y_max = std::min(datas.y_max(), reference_idx.y + vertical_outreach);
211 
212  cv::Mat_<uchar> center_image = load_image(datag, reference_idx, true);
213 
214  std::vector<cv::Point2f> center_positions = vec2_to_point2f(reference_points);
215  std::size_t features_count = center_positions.size();
216 
217  flow_state center_state(reference_idx, center_image, features_count);
218  center_state.feature_positions = center_positions;
219  center_state.feature_status.assign(features_count, 1);
220 
222  add_correspondences(cors, center_state);
223 
224  if(datas.is_2d()) {
225  // 2D MODE
226  std::vector<flow_state> vertical_origins;
227  vertical_origins.push_back(center_state);
228 
229  std::cout << "vertical optical flow by increasing y starting at mid_y..." << std::endl;
230  flow_state state = center_state;
231  for(int y = reference_idx.y + datas.y_step(); y <= y_max; y += datas.y_step()) {
232  view_index idx(reference_idx.x, y);
233  print_flow_indicator(state.view_idx, idx);
234  flow_state new_state = flow_to(state, idx, datag, vertical_optical_flow_window_size, false);
235  add_correspondences(cors, new_state);
236  vertical_origins.push_back(new_state);
237  state = std::move(new_state);
238  if(state.valid_features_count() == 0) break;
239  }
240 
241 
242  std::cout << "\nvertical optical flow by decreasing y starting at mid_y..." << std::endl;
243  state = center_state;
244  for(int y = reference_idx.y - datas.y_step(); y >= y_min; y -= datas.y_step()) {
245  view_index idx(reference_idx.x, y);
246  print_flow_indicator(state.view_idx, idx);
247  flow_state new_state = flow_to(state, idx, datag, vertical_optical_flow_window_size, false);
248  add_correspondences(cors, new_state);
249  vertical_origins.push_back(new_state);
250  state = std::move(new_state);
251  if(state.valid_features_count() == 0) break;
252  }
253 
254  std::cout << "\nnow doing horizontal flows..." << std::endl;
255  int done = 0;
256  #pragma omp parallel for
257  for(std::ptrdiff_t i = 0; i < vertical_origins.size(); i++) {
258  correspondences_type hcors;
259 
260  const flow_state& origin_state = vertical_origins[i];
261  do_horizontal_optical_flow(hcors, reference_idx, origin_state, datag);
262 
263  #pragma omp critical
264  {
265  ++done;
266  std::cout << '\n' << done << " of " << vertical_origins.size() << std::endl;
267 
268  cors.insert(hcors.begin(), hcors.end());
269  }
270  }
271 
272 
273  } else {
274  // 1D MODE
275 
276  do_horizontal_optical_flow(cors, reference_idx, center_state, datag, true);
277  }
278 
279  return cors;
280 }
281 
282 
283 int main(int argc, const char* argv[]) {
284  get_args(argc, argv, "dataset_parameters.json reference_fpoints.json horiz_outreach vert_outreach out_cors.json [dataset_group]");
285  dataset datas = dataset_arg();
286  feature_points reference_fpoints = feature_points_arg();
289  std::string out_cors_filename = out_filename_arg();
290  std::string dataset_group_name = string_opt_arg("");
291 
292  dataset_group datag = datas.group(dataset_group_name);
293 
294  view_index reference_idx = reference_fpoints.view_idx;
295 
296  std::cout << "loading reference points" << std::endl;
297  std::vector<vec2> reference_feature_points;
298  std::map<local_feature_index, std::string> feature_names;
299  for(const auto& kv : reference_fpoints.points) {
300  const std::string& feature_name = kv.first;
301  const feature_point& fpoint = kv.second;
302  feature_names[reference_feature_points.size()] = feature_name;
303  reference_feature_points.push_back(fpoint.position);
304  }
305 
306 
307  std::cout << "doing optical flow from reference view " << reference_idx << std::endl;
308  correspondences_type cors = do_2d_optical_flow(datag, reference_idx, reference_feature_points);
309 
310 
311  std::cout << "\nsaving image correspondences" << std::endl;
312  image_correspondences out_cors;
313  out_cors.dataset_group = dataset_group_name;
314  for(const auto& kv : cors) {
315  const local_feature_index& local_feature_idx = kv.first.first;
316  const view_index& idx = kv.first.second;
317  const vec2& position = kv.second;
318 
319  std::string& feature_name = feature_names[local_feature_idx];
320 
321  image_correspondence_feature& feature = out_cors.features[feature_name];
322  feature.reference_view = reference_idx;
323  feature.points[idx].position = position;
324  }
325  export_image_corresponcences(out_cors, out_cors_filename);
326 
327  std::cout << "done" << std::endl;
328 }
329 
bool file_exists(const std::string &filename)
int x_step() const
Definition: dataset.cc:173
std::string image_filename() const
Definition: dataset.cc:64
std::string encode_view_index(view_index idx)
Definition: dataset.cc:275
bool is_2d() const
Definition: dataset.cc:137
int x_max() const
Definition: dataset.cc:169
const cv::Size horizontal_optical_flow_window_size(20, 20)
int x_min() const
Definition: dataset.cc:165
constexpr bool verbose
Set of features, each on set of views.
void add_correspondences(correspondences_type &cors, const flow_state &state)
bool is_valid() const
Definition: common.h:117
dataset_view view(int x) const
Definition: dataset.cc:105
Points of different features, on one view.
std::vector< cv::Mat_< uchar > > image_pyramid
cv::Vec< real, 2 > vec2
Definition: common.h:22
constexpr real max_flow_err
cv::Mat_< cv::Vec3b > load_texture(const std::string &filename)
Definition: image_io.cc:6
std::pair< local_feature_index, view_index > correspondence_key_type
std::map< std::string, feature_point > points
std::size_t valid_features_count() const
int vertical_outreach
std::map< std::string, image_correspondence_feature > features
bool is_valid() const
std::vector< cv::Point2f > feature_positions
const cv::Size vertical_optical_flow_window_size(20, 20)
correspondence_key_type cor_key(local_feature_index i, const view_index &idx)
dataset dataset_arg()
Definition: dataset.cc:297
std::size_t features_count() const
std::vector< uchar > feature_status
double real
Definition: common.h:16
int main(int argc, const char *argv[])
long int_arg()
Definition: args.cc:138
void print_flow_indicator(const view_index &origin_idx, const view_index &dest_idx)
int horizontal_outreach
int y_step() const
Definition: dataset.cc:206
flow_state flow_to(flow_state &origin_state, view_index dest_idx, const dataset_group &datag, const cv::Size &window_size, bool may_skip)
constexpr real min_distance_between_features
std::map< correspondence_key_type, vec2 > correspondences_type
cv::Mat_< uchar > load_image(const dataset_group &datag, const view_index &idx, bool must_exist)
std::ptrdiff_t local_feature_index
flow_state(const view_index &idx, const cv::Mat_< uchar > &img, std::size_t features_count)
std::string out_filename_arg()
Definition: args.cc:104
Feature on set of views. Optionally one view is "reference".
int y_max() const
Definition: dataset.cc:201
void do_horizontal_optical_flow(correspondences_type &cors, const view_index &reference_idx, const flow_state &mid_x_state, const dataset_group &datag, bool verb=false)
constexpr int max_pyramid_level
void export_image_corresponcences(const image_correspondences &cors, const std::string &filename)
correspondences_type do_2d_optical_flow(const dataset_group &datag, const view_index &reference_idx, const std::vector< vec2 > &reference_points)
feature_points feature_points_arg()
std::map< view_index, feature_point > points
int y_min() const
Definition: dataset.cc:196
dataset_group group(const std::string &grp) const
Definition: dataset.cc:265
view_index view_idx
const dataset & set() const
Definition: dataset.h:58
std::string string_opt_arg(const std::string &def="")
Definition: args.h:36
void get_args(int argc, const char *argv[], const std::string &usage)
Definition: args.cc:49