Visual Servoing Platform  version 3.6.1 under development (2024-12-17)
tutorial-mb-generic-tracker-rgbd-blender.cpp
1 #include <iostream>
3 
4 #include <visp3/core/vpConfig.h>
5 #include <visp3/core/vpDisplay.h>
6 #include <visp3/core/vpIoTools.h>
7 #include <visp3/core/vpXmlParserCamera.h>
8 #include <visp3/gui/vpDisplayGDI.h>
9 #include <visp3/gui/vpDisplayOpenCV.h>
10 #include <visp3/gui/vpDisplayX.h>
11 #include <visp3/io/vpImageIo.h>
12 #include <visp3/mbt/vpMbGenericTracker.h>
13 
14 #if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGCODECS) && defined(VISP_HAVE_PUGIXML)
15 #ifdef ENABLE_VISP_NAMESPACE
16 using namespace VISP_NAMESPACE_NAME;
17 #endif
18 
19 namespace
20 {
21 bool read_data(unsigned int cpt, const std::string &video_color_images, const std::string &video_depth_images,
22  bool disable_depth, const std::string &video_ground_truth,
24  unsigned int &depth_width, unsigned int &depth_height,
25  std::vector<vpColVector> &pointcloud, const vpCameraParameters &cam_depth,
26  vpHomogeneousMatrix &cMo_ground_truth)
27 {
28  char buffer[FILENAME_MAX];
29  // Read color
30  snprintf(buffer, FILENAME_MAX, video_color_images.c_str(), cpt);
31  std::string filename_color = buffer;
32 
33  if (!vpIoTools::checkFilename(filename_color)) {
34  std::cerr << "Cannot read: " << filename_color << std::endl;
35  return false;
36  }
37  vpImageIo::read(I, filename_color);
38 
39  if (!disable_depth) {
40  // Read depth
41  snprintf(buffer, FILENAME_MAX, video_depth_images.c_str(), cpt);
42  std::string filename_depth = buffer;
43 
44  if (!vpIoTools::checkFilename(filename_depth)) {
45  std::cerr << "Cannot read: " << filename_depth << std::endl;
46  return false;
47  }
48  cv::Mat depth_raw = cv::imread(filename_depth, cv::IMREAD_ANYDEPTH | cv::IMREAD_ANYCOLOR);
49  if (depth_raw.empty()) {
50  std::cerr << "Cannot read: " << filename_depth << std::endl;
51  return false;
52  }
53 
54  depth_width = static_cast<unsigned int>(depth_raw.cols);
55  depth_height = static_cast<unsigned int>(depth_raw.rows);
56  I_depth_raw.resize(depth_height, depth_width);
57  pointcloud.resize(depth_width * depth_height);
58 
59  for (int i = 0; i < depth_raw.rows; i++) {
60  for (int j = 0; j < depth_raw.cols; j++) {
61  I_depth_raw[i][j] = static_cast<uint16_t>(32767.5f * depth_raw.at<cv::Vec3f>(i, j)[0]);
62  double x = 0.0, y = 0.0;
63  // Manually limit the field of view of the depth camera
64  double Z = depth_raw.at<cv::Vec3f>(i, j)[0] > 2.0f ? 0.0 : static_cast<double>(depth_raw.at<cv::Vec3f>(i, j)[0]);
65  vpPixelMeterConversion::convertPoint(cam_depth, j, i, x, y);
66  size_t idx = static_cast<size_t>(i * depth_raw.cols + j);
67  pointcloud[idx].resize(3);
68  pointcloud[idx][0] = x * Z;
69  pointcloud[idx][1] = y * Z;
70  pointcloud[idx][2] = Z;
71  }
72  }
73  }
74 
75  // Read ground truth
76  snprintf(buffer, FILENAME_MAX, video_ground_truth.c_str(), cpt);
77  std::string filename_pose = buffer;
78 
79  cMo_ground_truth.load(filename_pose);
80 
81  return true;
82 }
83 } // namespace
84 
85 void usage(const char **argv, int error, const std::string &data_path, const std::string &model_path, int first_frame)
86 {
87  std::cout << "Synopsis" << std::endl
88  << " " << argv[0]
89  << " [--data-path <path>] [--model-path <path>] [--first-frame <index>] [--depth-dense-mode <0|1>] "
90  << " [--depth-normals-mode <0|1>] [--me-mode <0|1>] [--klt-mode <0|1>] [--step-by-step] [--display-ground-truth] [--help, -h]" << std::endl
91  << std::endl;
92  std::cout << "Description" << std::endl
93  << " --data-path <path> Path to the data generated by Blender get_camera_pose_teabox.py" << std::endl
94  << " Python script."
95  << " Default: " << data_path << std::endl
96  << std::endl
97  << " --model-path <path> Path to the cad model and tracker settings." << std::endl
98  << " Default: " << model_path << std::endl
99  << std::endl
100  << " --first-frame <index> First frame number to process." << std::endl
101  << " Default: " << first_frame << std::endl
102  << std::endl
103 
104  << " --depth-dense-mode Whether to use dense depth features (0 = off, 1 = on). default: 1" << std::endl
105  << std::endl
106  << " --depth-normals-mode Whether to use normal depth features (0 = off, 1 = on). default: 0" << std::endl
107  << std::endl
108  << " --me-mode Whether to use moving edge features (0 = off, 1 = on). default: 1" << std::endl
109  << std::endl
110  << " --klt-mode Whether to use KLT features (0 = off, 1 = on). Requires OpenCV. default: 1" << std::endl
111  << std::endl
112 
113  << " --step-by-step Flag to enable step by step mode." << std::endl
114  << std::endl
115  << " --display-ground-truth Flag to enable displaying ground truth." << std::endl
116  << " When this flag is enabled, there is no tracking. This flag is useful" << std::endl
117  << " to validate the ground truth over the rendered images." << std::endl
118  << std::endl
119  << " --help, -h Print this helper message." << std::endl
120  << std::endl;
121  if (error) {
122  std::cout << "Error" << std::endl
123  << " "
124  << "Unsupported parameter " << argv[error] << std::endl;
125  }
126 }
127 
128 int main(int argc, const char **argv)
129 {
130  std::string opt_data_path = "data/teabox";
131  std::string opt_model_path = "model/teabox";
132  unsigned int opt_first_frame = 1;
133  int opt_meMode = 1, opt_kltMode = 1, opt_normalsMode = 0, opt_denseMode = 1;
134 
135  bool disable_depth = false;
136  bool opt_disable_klt = false;
137 
138  bool opt_display_ground_truth = false;
139  bool opt_step_by_step = false;
140 
141  for (int i = 1; i < argc; i++) {
142  if (std::string(argv[i]) == "--data-path" && i + 1 < argc) {
143  opt_data_path = std::string(argv[i + 1]);
144  i++;
145  }
146  else if (std::string(argv[i]) == "--model-path" && i + 1 < argc) {
147  opt_model_path = std::string(argv[i + 1]);
148  i++;
149  }
150  else if (std::string(argv[i]) == "--depth-dense-mode" && i + 1 < argc) {
151  opt_denseMode = static_cast<unsigned int>(atoi(argv[i + 1]));
152  if (opt_denseMode < 0 || opt_denseMode > 1) {
153  usage(argv, 0, opt_data_path, opt_model_path, opt_first_frame);
154  return EXIT_FAILURE;
155  }
156  i++;
157  }
158  else if (std::string(argv[i]) == "--depth-normals-mode" && i + 1 < argc) {
159  opt_normalsMode = static_cast<unsigned int>(atoi(argv[i + 1]));
160  if (opt_normalsMode < 0 || opt_normalsMode > 1) {
161  usage(argv, 0, opt_data_path, opt_model_path, opt_first_frame);
162  return EXIT_FAILURE;
163  }
164  i++;
165  }
166  else if (std::string(argv[i]) == "--me-mode" && i + 1 < argc) {
167  opt_meMode = static_cast<unsigned int>(atoi(argv[i + 1]));
168  if (opt_meMode < 0 || opt_meMode > 1) {
169  usage(argv, 0, opt_data_path, opt_model_path, opt_first_frame);
170  return EXIT_FAILURE;
171  }
172  i++;
173  }
174  else if (std::string(argv[i]) == "--klt-mode" && i + 1 < argc) {
175  opt_kltMode = static_cast<unsigned int>(atoi(argv[i + 1]));
176  if (opt_kltMode < 0 || opt_kltMode > 1) {
177  usage(argv, 0, opt_data_path, opt_model_path, opt_first_frame);
178  return EXIT_FAILURE;
179  }
180  i++;
181  }
182  else if (std::string(argv[i]) == "--display-ground-truth") {
183  opt_display_ground_truth = true;
184  }
185  else if (std::string(argv[i]) == "--step-by-step") {
186  opt_step_by_step = true;
187  }
188  else if (std::string(argv[i]) == "--first-frame" && i + 1 < argc) {
189  opt_first_frame = static_cast<unsigned int>(atoi(argv[i + 1]));
190  i++;
191  }
192  else if (std::string(argv[i]) == "--help" || std::string(argv[i]) == "-h") {
193  usage(argv, 0, opt_data_path, opt_model_path, opt_first_frame);
194  return EXIT_SUCCESS;
195  }
196  else {
197  usage(argv, i, opt_data_path, opt_model_path, opt_first_frame);
198  return EXIT_FAILURE;
199  }
200  }
201 
202  disable_depth = opt_denseMode == 0 && opt_normalsMode == 0;
203 
204  std::string video_color_images = vpIoTools::createFilePath(opt_data_path, "color/%04d_L.jpg");
205  std::string video_depth_images = vpIoTools::createFilePath(opt_data_path, "depth/Image%04d_R.exr");
206  std::string ground_truth = vpIoTools::createFilePath(opt_data_path, "ground-truth/Camera_L_%04d.txt");
207  std::string extrinsic_file = vpIoTools::createFilePath(opt_data_path, "depth_M_color.txt");
208  std::string color_camera_name = "Camera_L";
209  std::string depth_camera_name = "Camera_R";
210  std::string color_intrinsic_file = vpIoTools::createFilePath(opt_data_path, color_camera_name + ".xml");
211  std::string depth_intrinsic_file = vpIoTools::createFilePath(opt_data_path, depth_camera_name + ".xml");
212  std::string mbt_config_color = vpIoTools::createFilePath(opt_model_path, "teabox_color.xml");
213  std::string mbt_config_depth = vpIoTools::createFilePath(opt_model_path, "teabox_depth.xml");
214  std::string mbt_cad_model = vpIoTools::createFilePath(opt_model_path, "teabox.cao");
215  std::string mbt_init_file = vpIoTools::createFilePath(opt_model_path, "teabox.init");
216 
217  std::cout << "Input data" << std::endl;
218  std::cout << " Color images : " << video_color_images << std::endl;
219  std::cout << " Depth images : " << (disable_depth ? "Disabled" : video_depth_images) << std::endl;
220  std::cout << " Extrinsics : " << (disable_depth ? "Disabled" : extrinsic_file) << std::endl;
221  std::cout << " Color intrinsics: " << color_intrinsic_file << std::endl;
222  std::cout << " Depth intrinsics: " << (disable_depth ? "Disabled" : depth_intrinsic_file) << std::endl;
223  std::cout << " Ground truth : " << ground_truth << std::endl;
224  std::cout << "Tracker settings" << std::endl;
225  std::cout << " Color config : " << mbt_config_color << std::endl;
226  std::cout << " Depth config : " << mbt_config_depth << std::endl;
227  std::cout << " CAD model : " << mbt_cad_model << std::endl;
228  std::cout << " First frame : " << opt_first_frame << std::endl;
229  std::cout << " Step by step : " << opt_step_by_step << std::endl;
230  if (opt_display_ground_truth) {
231  std::cout << " Ground truth is used to project the cad model (no tracking)" << std::endl;
232  }
233  else {
234  std::cout << " Init file : " << mbt_init_file << std::endl;
235  std::cout << " Features : moving-edges " << (opt_disable_klt ? "" : "+ keypoints") << (disable_depth ? "" : " + depth") << std::endl;
236  }
237 
238 
239  std::vector<int> tracker_types;
240  int colorTracker = 0;
241 
242 
243  if (opt_meMode == 1) {
244  colorTracker |= vpMbGenericTracker::EDGE_TRACKER;
245  }
246  if (opt_kltMode == 1) {
247 #if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_IMGPROC) && defined(HAVE_OPENCV_VIDEO)
248  colorTracker |= vpMbGenericTracker::KLT_TRACKER;
249 #else
250  std::cerr << "Warning: keypoints cannot be used as features since ViSP is not built with OpenCV 3rd party" << std::endl;
251 #endif
252  }
253 
254  if (colorTracker == 0) {
255  std::cerr << "You should use at least one type of color feature. If OpenCV is not installed, KLT features are disabled" << std::endl;
256  return EXIT_FAILURE;
257  }
258 
259  tracker_types.push_back(colorTracker);
260 
261  if (!disable_depth) {
262  int depthTracker = 0;
263  if (opt_denseMode == 1) {
265  }
266  if (opt_normalsMode == 1) {
268  }
269 
270  tracker_types.push_back(depthTracker);
271  }
272 
273  vpMbGenericTracker tracker(tracker_types);
274  if (!disable_depth) {
275  tracker.loadConfigFile(mbt_config_color, mbt_config_depth, true);
276  }
277  else {
278  tracker.loadConfigFile(mbt_config_color);
279 
280  }
281  tracker.loadModel(mbt_cad_model);
282  vpCameraParameters cam_color, cam_depth;
283 
284  // Update intrinsics camera parameters from Blender generated data
286  if (p.parse(cam_color, color_intrinsic_file, color_camera_name, vpCameraParameters::perspectiveProjWithoutDistortion)
288  std::cout << "Cannot found intrinsics for camera " << color_camera_name << std::endl;
289  }
290  if (p.parse(cam_depth, depth_intrinsic_file, depth_camera_name, vpCameraParameters::perspectiveProjWithoutDistortion)
292  std::cout << "Cannot found intrinsics for camera " << depth_camera_name << std::endl;
293  }
294 
295  if (!disable_depth)
296  tracker.setCameraParameters(cam_color, cam_depth);
297  else
298  tracker.setCameraParameters(cam_color);
299 
300  // Reload intrinsics from tracker (useless)
301  if (!disable_depth)
302  tracker.getCameraParameters(cam_color, cam_depth);
303  else
304  tracker.getCameraParameters(cam_color);
305  tracker.setDisplayFeatures(true);
306  std::cout << "cam_color:\n" << cam_color << std::endl;
307 
308  if (!disable_depth)
309  std::cout << "cam_depth:\n" << cam_depth << std::endl;
310 
311  vpImage<uint16_t> I_depth_raw;
312  vpImage<unsigned char> I, I_depth;
313  unsigned int depth_width = 0, depth_height = 0;
314  std::vector<vpColVector> pointcloud;
315  vpHomogeneousMatrix cMo_ground_truth;
316 
317  unsigned int frame_cpt = opt_first_frame;
318  read_data(frame_cpt, video_color_images, video_depth_images, disable_depth, ground_truth,
319  I, I_depth_raw, depth_width, depth_height, pointcloud, cam_depth, cMo_ground_truth);
320  vpImageConvert::createDepthHistogram(I_depth_raw, I_depth);
321 
322 #if defined(VISP_HAVE_X11)
323  vpDisplayX d1, d2;
324 #elif defined(VISP_HAVE_GDI)
325  vpDisplayGDI d1, d2;
326 #elif defined (HAVE_OPENCV_HIGHGUI)
327  vpDisplayOpenCV d1, d2;
328 #endif
329 
330  d1.init(I, 0, 0, "Color image");
331  if (!disable_depth) {
332  d2.init(I_depth, static_cast<int>(I.getWidth()), 0, "Depth image");
333  }
334 
335  vpHomogeneousMatrix depth_M_color;
336  if (!disable_depth) {
337  depth_M_color.load(extrinsic_file);
338  tracker.setCameraTransformationMatrix("Camera2", depth_M_color);
339  std::cout << "depth_M_color:\n" << depth_M_color << std::endl;
340  }
341 
342  if (opt_display_ground_truth) {
343  tracker.initFromPose(I, cMo_ground_truth); // I and I_depth must be the same size when using depth features!
344  }
345  else {
346  tracker.initClick(I, mbt_init_file, true); // I and I_depth must be the same size when using depth features!
347  }
348 
349  try {
350  bool quit = false;
351  while (!quit && read_data(frame_cpt, video_color_images, video_depth_images, disable_depth,
352  ground_truth, I, I_depth_raw, depth_width, depth_height, pointcloud, cam_depth,
353  cMo_ground_truth)) {
354  vpImageConvert::createDepthHistogram(I_depth_raw, I_depth);
356  vpDisplay::display(I_depth);
357 
358  if (opt_display_ground_truth) {
359  tracker.initFromPose(I, cMo_ground_truth); // I and I_depth must be the same size when using depth features!
360  }
361  else {
362  if (!disable_depth) {
363  std::map<std::string, const vpImage<unsigned char> *> mapOfImages;
364  std::map<std::string, const std::vector<vpColVector> *> mapOfPointClouds;
365  std::map<std::string, unsigned int> mapOfPointCloudWidths;
366  std::map<std::string, unsigned int> mapOfPointCloudHeights;
367 
368  mapOfImages["Camera1"] = &I;
369  mapOfPointClouds["Camera2"] = &pointcloud;
370  mapOfPointCloudWidths["Camera2"] = depth_width;
371  mapOfPointCloudHeights["Camera2"] = depth_height;
372  tracker.track(mapOfImages, mapOfPointClouds, mapOfPointCloudWidths, mapOfPointCloudHeights);
373  }
374  else {
375  tracker.track(I);
376  }
377  }
378 
379  vpHomogeneousMatrix cMo = tracker.getPose();
380  std::cout << "\nFrame: " << frame_cpt << std::endl;
381  if (!opt_display_ground_truth)
382  std::cout << "cMo:\n" << cMo << std::endl;
383  std::cout << "cMo ground truth:\n" << cMo_ground_truth << std::endl;
384  if (!disable_depth) {
385  tracker.display(I, I_depth, cMo, depth_M_color * cMo, cam_color, cam_depth, vpColor::red, 2);
386  vpDisplay::displayFrame(I_depth, depth_M_color * cMo, cam_depth, 0.05, vpColor::none, 2);
387  }
388  else {
389  tracker.display(I, cMo, cam_color, vpColor::red, 2);
390  }
391 
392  vpDisplay::displayFrame(I, cMo, cam_color, 0.05, vpColor::none, 2);
393  std::ostringstream oss;
394  oss << "Frame: " << frame_cpt;
395  vpDisplay::setTitle(I, oss.str());
396  if (opt_step_by_step) {
397  vpDisplay::displayText(I, 20, 10, "Left click to trigger next step", vpColor::red);
398  vpDisplay::displayText(I, 40, 10, "Right click to quit step-by-step mode", vpColor::red);
399  }
400  else {
401  vpDisplay::displayText(I, 20, 10, "Left click to trigger step-by-step mode", vpColor::red);
402  vpDisplay::displayText(I, 40, 10, "Right click to exit...", vpColor::red);
403  }
404  if (!opt_display_ground_truth) {
405  {
406  std::stringstream ss;
407  ss << "Nb features: " << tracker.getError().size();
408  vpDisplay::displayText(I, I.getHeight() - 50, 20, ss.str(), vpColor::red);
409  }
410  {
411  std::stringstream ss;
412  ss << "Features: edges " << tracker.getNbFeaturesEdge() << ", klt " << tracker.getNbFeaturesKlt()
413  << ", dense depth " << tracker.getNbFeaturesDepthDense() << ", depth normals " << tracker.getNbFeaturesDepthNormal();
414  vpDisplay::displayText(I, I.getHeight() - 30, 20, ss.str(), vpColor::red);
415  }
416  }
417 
418  vpDisplay::flush(I);
419  vpDisplay::flush(I_depth);
420 
421  // Button 1: start step by step if not enabled from command line option
422  // Button 2: enables step by step mode
423  // Button 3: ends step by step mode if enabled
424  // quit otherwise
426  if (vpDisplay::getClick(I, button, opt_step_by_step)) {
427  if (button == vpMouseButton::button1 && opt_step_by_step == false) {
428  opt_step_by_step = true;
429  }
430  else if (button == vpMouseButton::button3 && opt_step_by_step == true) {
431  opt_step_by_step = false;
432  }
433  else if (button == vpMouseButton::button3 && opt_step_by_step == false) {
434  quit = true;
435  }
436  else if (button == vpMouseButton::button2) {
437  opt_step_by_step = true;
438  }
439  }
440 
441  frame_cpt++;
442  }
443 
444  vpDisplay::flush(I);
446  }
447  catch (std::exception &e) {
448  std::cerr << "Catch exception: " << e.what() << std::endl;
449  }
450 
451  return EXIT_SUCCESS;
452 }
453 #else
454 int main()
455 {
456  std::cout << "To run this tutorial, ViSP should be built with OpenCV and pugixml libraries." << std::endl;
457  return EXIT_SUCCESS;
458 }
459 #endif
Generic class defining intrinsic camera parameters.
@ perspectiveProjWithoutDistortion
Perspective projection without distortion model.
static const vpColor red
Definition: vpColor.h:217
static const vpColor none
Definition: vpColor.h:229
Display for windows using GDI (available on any windows 32 platform).
Definition: vpDisplayGDI.h:130
The vpDisplayOpenCV allows to display image using the OpenCV library. Thus to enable this class OpenC...
void init(vpImage< unsigned char > &I, int winx=-1, int winy=-1, const std::string &title="") VP_OVERRIDE
static bool getClick(const vpImage< unsigned char > &I, bool blocking=true)
static void display(const vpImage< unsigned char > &I)
static void displayFrame(const vpImage< unsigned char > &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, double size, const vpColor &color=vpColor::none, unsigned int thickness=1, const vpImagePoint &offset=vpImagePoint(0, 0), const std::string &frameName="", const vpColor &textColor=vpColor::black, const vpImagePoint &textOffset=vpImagePoint(15, 15))
static void setTitle(const vpImage< unsigned char > &I, const std::string &windowtitle)
static void flush(const vpImage< unsigned char > &I)
static void displayText(const vpImage< unsigned char > &I, const vpImagePoint &ip, const std::string &s, const vpColor &color)
Implementation of an homogeneous matrix and operations on such kind of matrices.
void load(std::ifstream &f)
static void createDepthHistogram(const vpImage< uint16_t > &src_depth, vpImage< vpRGBa > &dest_rgba)
static void read(vpImage< unsigned char > &I, const std::string &filename, int backend=IO_DEFAULT_BACKEND)
Definition: vpImageIo.cpp:147
unsigned int getWidth() const
Definition: vpImage.h:242
void resize(unsigned int h, unsigned int w)
resize the image : Image initialization
Definition: vpImage.h:544
unsigned int getHeight() const
Definition: vpImage.h:181
static bool checkFilename(const std::string &filename)
Definition: vpIoTools.cpp:786
static std::string createFilePath(const std::string &parent, const std::string &child)
Definition: vpIoTools.cpp:1427
Real-time 6D object pose tracking using its CAD model.
static void convertPoint(const vpCameraParameters &cam, const double &u, const double &v, double &x, double &y)
XML parser to load and save intrinsic camera parameters.
vpCameraParameters getCameraParameters() const
int parse(vpCameraParameters &cam, const std::string &filename, const std::string &camera_name, const vpCameraParameters::vpCameraParametersProjType &projModel, unsigned int image_width=0, unsigned int image_height=0, bool verbose=true)