Visual Servoing Platform  version 3.2.0 under development (2019-01-22)
testAprilTag.cpp
1 /****************************************************************************
2  *
3  * ViSP, open source Visual Servoing Platform software.
4  * Copyright (C) 2005 - 2019 by Inria. All rights reserved.
5  *
6  * This software is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * See the file LICENSE.txt at the root directory of this source
11  * distribution for additional information about the GNU GPL.
12  *
13  * For using ViSP with software that can not be combined with the GNU
14  * GPL, please contact Inria about acquiring a ViSP Professional
15  * Edition License.
16  *
17  * See http://visp.inria.fr for more information.
18  *
19  * This software was developed at:
20  * Inria Rennes - Bretagne Atlantique
21  * Campus Universitaire de Beaulieu
22  * 35042 Rennes Cedex
23  * France
24  *
25  * If you have questions regarding the use of this file, please contact
26  * Inria at visp@inria.fr
27  *
28  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
29  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
30  *
31  * Description:
32  * Test AprilTag detection.
33  *
34  *****************************************************************************/
41 #include <iostream>
42 #include <map>
43 #include <visp3/core/vpDisplay.h>
44 #include <visp3/core/vpIoTools.h>
45 #include <visp3/detection/vpDetectorAprilTag.h>
46 #include <visp3/gui/vpDisplayGDI.h>
47 #include <visp3/gui/vpDisplayOpenCV.h>
48 #include <visp3/gui/vpDisplayX.h>
49 #include <visp3/io/vpImageIo.h>
50 #include <visp3/io/vpParseArgv.h>
51 
52 #if defined(VISP_HAVE_APRILTAG)
53 
54 // List of allowed command line options
55 #define GETOPTARGS "cdi:p:C:T:h"
56 
57 namespace
58 {
59 /*
60  Print the program options.
61 
62  \param name : Program name.
63  \param badparam : Bad parameter name.
64  \param ipath: Input image path.
65  */
66 void usage(const char *name, const char *badparam, std::string ipath)
67 {
68  fprintf(stdout, "\n\
69  Test AprilTag detection.\n\
70  \n\
71  SYNOPSIS\n\
72  %s [-c] [-d] [-i <input image path>] [-p <personal image path>] \
73  [-C <tag color>] [-T <tag thickness>]\n\
74  [-h]\n \
75  ", name);
76 
77  fprintf(stdout, "\n\
78  OPTIONS: Default\n\
79  -i <input image path> %s\n\
80  Set image input path.\n\
81  From this path read \"AprilTag/AprilTag.pgm image.\n\
82  Setting the VISP_INPUT_IMAGE_PATH environment\n\
83  variable produces the same behaviour than using\n\
84  this option.\n\
85  \n\
86  -p <personal image path> \n\
87  Path to an image used to test image reading function.\n\
88  Example: -p /my_path_to/image.png\n\
89  \n\
90  -c \n\
91  Disable the mouse click. Useful to automate the \n\
92  execution of this program without human intervention.\n\
93  \n\
94  -d \n\
95  Turn off the display.\n\
96  \n\
97  -C <color (0, 1, ...)> \n\
98  Color for tag detection display.\n\
99  \n\
100  -T <thickness> \n\
101  Thickness for tag detection display.\n\
102  \n\
103  -h\n\
104  Print the help.\n\n", ipath.c_str());
105 
106  if (badparam)
107  fprintf(stdout, "\nERROR: Bad parameter [%s]\n", badparam);
108 }
109 
123 bool getOptions(int argc, const char **argv, std::string &ipath, std::string &ppath, bool &click_allowed, bool &display,
124  int &color_id, unsigned int &thickness)
125 {
126  const char *optarg_;
127  int c;
128  while ((c = vpParseArgv::parse(argc, argv, GETOPTARGS, &optarg_)) > 1) {
129 
130  switch (c) {
131  case 'i':
132  ipath = optarg_;
133  break;
134  case 'p':
135  ppath = optarg_;
136  break;
137  case 'h':
138  usage(argv[0], NULL, ipath);
139  return false;
140  break;
141  case 'c':
142  click_allowed = false;
143  break;
144  case 'd':
145  display = false;
146  break;
147  case 'C':
148  color_id = atoi(optarg_);
149  break;
150  case 'T':
151  thickness = (unsigned int) atoi(optarg_);
152  break;
153 
154  default:
155  usage(argv[0], optarg_, ipath);
156  return false;
157  break;
158  }
159  }
160 
161  if ((c == 1) || (c == -1)) {
162  // standalone param or error
163  usage(argv[0], NULL, ipath);
164  std::cerr << "ERROR: " << std::endl;
165  std::cerr << " Bad argument " << optarg_ << std::endl << std::endl;
166  return false;
167  }
168 
169  return true;
170 }
171 
172 struct TagGroundTruth {
173  std::string message;
174  std::vector<vpImagePoint> corners;
175 
176  TagGroundTruth(const std::string &msg, const std::vector<vpImagePoint> &c) : message(msg), corners(c) {}
177 
178  bool operator==(const TagGroundTruth &b) const
179  {
180  if (message != b.message || corners.size() != b.corners.size())
181  return false;
182 
183  for (size_t i = 0; i < corners.size(); i++) {
184  // Allow 0.5 pixel of difference
185  if (!vpMath::equal(corners[i].get_u(), b.corners[i].get_u(), 0.5) ||
186  !vpMath::equal(corners[i].get_v(), b.corners[i].get_v(), 0.5)) {
187  return false;
188  }
189  }
190 
191  return true;
192  }
193 
194  bool operator!=(const TagGroundTruth &b) const { return !(*this == b); }
195 };
196 
197 std::ostream &operator<<(std::ostream &os, TagGroundTruth &t)
198 {
199  os << t.message << std::endl;
200  for (size_t i = 0; i < t.corners.size(); i++)
201  os << t.corners[i] << std::endl;
202 
203  return os;
204 }
205 }
206 
207 int main(int argc, const char *argv[])
208 {
209  try {
210  std::string env_ipath;
211  std::string opt_ipath = "";
212  std::string opt_ppath = "";
213  std::string ipath = "";
214  std::string filename = "";
215  bool opt_click_allowed = true;
216  bool opt_display = true;
217  int opt_color_id = -1;
218  unsigned int opt_thickness = 2;
219 
220  // Get the visp-images-data package path or VISP_INPUT_IMAGE_PATH
221  // environment variable value
222  env_ipath = vpIoTools::getViSPImagesDataPath();
223 
224  // Set the default input path
225  if (!env_ipath.empty())
226  ipath = env_ipath;
227 
228  // Read the command line options
229  if (getOptions(argc, argv, opt_ipath, opt_ppath, opt_click_allowed, opt_display,
230  opt_color_id, opt_thickness) == false) {
231  exit(EXIT_FAILURE);
232  }
233 
234  // Get the option values
235  if (!opt_ipath.empty())
236  ipath = opt_ipath;
237 
238  // Compare ipath and env_ipath. If they differ, we take into account
239  // the input path comming from the command line option
240  if (!opt_ipath.empty() && !env_ipath.empty()) {
241  if (ipath != env_ipath) {
242  std::cout << std::endl << "WARNING: " << std::endl;
243  std::cout << " Since -i <visp image path=" << ipath << "> "
244  << " is different from VISP_IMAGE_PATH=" << env_ipath << std::endl
245  << " we skip the environment variable." << std::endl;
246  }
247  }
248 
249  //
250  // Here starts really the test
251  //
252 
254  if (opt_ppath.empty()) {
255  filename = vpIoTools::createFilePath(ipath, "AprilTag/AprilTag.pgm");
256  } else {
257  filename = opt_ppath;
258  }
259 
260  if (!vpIoTools::checkFilename(filename)) {
261  std::cerr << "Filename: " << filename << " does not exist." << std::endl;
262  return EXIT_SUCCESS;
263  }
264  vpImageIo::read(I, filename);
265 
266 #ifdef VISP_HAVE_X11
267  vpDisplayX d;
268 #elif defined(VISP_HAVE_GDI)
269  vpDisplayGDI d;
270 #elif defined(VISP_HAVE_OPENCV)
271  vpDisplayOpenCV d;
272 #else
273  opt_display = false;
274 #endif
275 
278  double tagSize = 0.053;
279  float quad_decimate = 1.0;
280  int nThreads = 1;
281  bool display_tag = true;
282 
283  vpDetectorBase *detector = new vpDetectorAprilTag(tagFamily);
284  dynamic_cast<vpDetectorAprilTag *>(detector)->setAprilTagQuadDecimate(quad_decimate);
285  dynamic_cast<vpDetectorAprilTag *>(detector)->setAprilTagPoseEstimationMethod(poseEstimationMethod);
286  dynamic_cast<vpDetectorAprilTag *>(detector)->setAprilTagNbThreads(nThreads);
287  dynamic_cast<vpDetectorAprilTag *>(detector)->setDisplayTag(display_tag,
288  opt_color_id < 0 ? vpColor::none : vpColor::getColor(opt_color_id),
289  opt_thickness);
290 
291  vpCameraParameters cam;
292  cam.initPersProjWithoutDistortion(615.1674805, 615.1675415, 312.1889954, 243.4373779);
293 
294  if (opt_display) {
295 #if defined(VISP_HAVE_X11) || defined(VISP_HAVE_GDI) || defined(VISP_HAVE_OPENCV)
296  d.init(I, 0, 0, "AprilTag detection");
297 #endif
299  }
300 
301  std::vector<vpHomogeneousMatrix> cMo_vec;
302  dynamic_cast<vpDetectorAprilTag *>(detector)->detect(I, tagSize, cam, cMo_vec);
303 
304  // Ground truth
305  std::map<std::string, TagGroundTruth> mapOfTagsGroundTruth;
306  bool use_detection_ground_truth = false;
307  {
308  std::string filename_ground_truth = vpIoTools::createFilePath(ipath, "AprilTag/ground_truth_detection.txt");
309  std::ifstream file_ground_truth(filename_ground_truth.c_str());
310  if (file_ground_truth.is_open() && opt_ppath.empty()) {
311  use_detection_ground_truth = true;
312 
313  std::string message = "";
314  double v1 = 0.0, v2 = 0.0, v3 = 0.0, v4 = 0.0;
315  double u1 = 0.0, u2 = 0.0, u3 = 0.0, u4 = 0.0;
316  while (file_ground_truth >> message >> v1 >> u1 >> v2 >> u2 >> v3 >> u3 >> v4 >> u4) {
317  std::vector<vpImagePoint> tagCorners(4);
318  tagCorners[0].set_ij(v1, u1);
319  tagCorners[1].set_ij(v2, u2);
320  tagCorners[2].set_ij(v3, u3);
321  tagCorners[3].set_ij(v4, u4);
322  mapOfTagsGroundTruth.insert(std::make_pair(message, TagGroundTruth(message, tagCorners)));
323  }
324  }
325  }
326 
327  std::map<std::string, vpPoseVector> mapOfPosesGroundTruth;
328  bool use_pose_ground_truth = false;
329  {
330  std::string filename_ground_truth = vpIoTools::createFilePath(ipath, "AprilTag/ground_truth_pose.txt");
331  std::ifstream file_ground_truth(filename_ground_truth.c_str());
332  if (file_ground_truth.is_open() && opt_ppath.empty()) {
333  use_pose_ground_truth = true;
334 
335  std::string message = "";
336  double tx = 0.0, ty = 0.0, tz = 0.0;
337  double tux = 0.0, tuy = 0.0, tuz = 0.0;
338  while (file_ground_truth >> message >> tx >> ty >> tz >> tux >> tuy >> tuz) {
339  mapOfPosesGroundTruth.insert(std::make_pair(message, vpPoseVector(tx, ty, tz, tux, tuy, tuz)));
340  }
341  }
342  }
343 
344  std::cout << "use_pose_ground_truth: " << use_pose_ground_truth << std::endl;
345 
346  for (size_t i = 0; i < detector->getNbObjects(); i++) {
347  std::vector<vpImagePoint> p = detector->getPolygon(i);
348 
349  if (use_detection_ground_truth) {
350  std::string message = detector->getMessage(i);
351  std::replace(message.begin(), message.end(), ' ', '_');
352  std::map<std::string, TagGroundTruth>::iterator it = mapOfTagsGroundTruth.find(message);
353  TagGroundTruth current(message, p);
354  if (it == mapOfTagsGroundTruth.end()) {
355  std::cerr << "Problem with tag decoding (tag_family or id): " << message << std::endl;
356  return EXIT_FAILURE;
357  } else if (it->second != current) {
358  std::cerr << "Problem, current detection:\n" << current << "\nGround truth:\n" << it->second << std::endl;
359  return EXIT_FAILURE;
360  }
361  }
362 
363  if (opt_display) {
364  vpRect bbox = detector->getBBox(i);
366  vpDisplay::displayText(I, (int)(bbox.getTop() - 10), (int)bbox.getLeft(), detector->getMessage(i),
367  vpColor::red);
368  }
369  }
370 
371  if (opt_display) {
372  vpDisplay::displayText(I, 20, 20, "Click to display tag poses", vpColor::red);
373  vpDisplay::flush(I);
374  if (opt_click_allowed)
376 
378  }
379 
380  for (size_t i = 0; i < cMo_vec.size(); i++) {
381  if (opt_display)
382  vpDisplay::displayFrame(I, cMo_vec[i], cam, tagSize / 2, vpColor::none, 3);
383 
384  if (use_pose_ground_truth) {
385  vpPoseVector pose_vec(cMo_vec[i]);
386 
387  std::string message = detector->getMessage(i);
388  std::replace(message.begin(), message.end(), ' ', '_');
389  std::map<std::string, vpPoseVector>::iterator it = mapOfPosesGroundTruth.find(message);
390  if (it == mapOfPosesGroundTruth.end()) {
391  std::cerr << "Problem with tag decoding (tag_family or id): " << message << std::endl;
392  return EXIT_FAILURE;
393  } else {
394  for (unsigned int cpt = 0; cpt < 6; cpt++) {
395  if (!vpMath::equal(it->second[cpt], pose_vec[cpt], 0.005)) {
396  std::cerr << "Problem, current pose: " << pose_vec.t() << "\nGround truth pose: " << it->second.t()
397  << std::endl;
398  return EXIT_FAILURE;
399  }
400  }
401  }
402  }
403  }
404 
405  if (opt_display) {
406  vpDisplay::displayText(I, 20, 20, "Click to quit.", vpColor::red);
407  vpDisplay::flush(I);
408  if (opt_click_allowed)
410  }
411 
412  delete detector;
413  } catch (const vpException &e) {
414  std::cerr << "Catch an exception: " << e.what() << std::endl;
415  return EXIT_FAILURE;
416  }
417 
418  std::cout << "\ntestAprilTag is ok." << std::endl;
419  return EXIT_SUCCESS;
420 }
421 #else
422 int main()
423 {
424  std::cout << "Need ViSP AprilTag." << std::endl;
425  return 0;
426 }
427 #endif
double getTop() const
Definition: vpRect.h:175
static bool getClick(const vpImage< unsigned char > &I, bool blocking=true)
static std::string getViSPImagesDataPath()
Definition: vpIoTools.cpp:1316
Display for windows using GDI (available on any windows 32 platform).
Definition: vpDisplayGDI.h:129
Class to define colors available for display functionnalities.
Definition: vpColor.h:120
static bool equal(double x, double y, double s=0.001)
Definition: vpMath.h:290
static void displayText(const vpImage< unsigned char > &I, const vpImagePoint &ip, const std::string &s, const vpColor &color)
Use the X11 console to display images on unix-like OS. Thus to enable this class X11 should be instal...
Definition: vpDisplayX.h:151
static const vpColor none
Definition: vpColor.h:192
error that can be emited by ViSP classes.
Definition: vpException.h:71
void init(vpImage< unsigned char > &I, int winx=-1, int winy=-1, const std::string &title="")
size_t getNbObjects() const
static const vpColor green
Definition: vpColor.h:183
static void flush(const vpImage< unsigned char > &I)
static bool parse(int *argcPtr, const char **argv, vpArgvInfo *argTable, int flags)
Definition: vpParseArgv.cpp:69
static const vpColor red
Definition: vpColor.h:180
void initPersProjWithoutDistortion(const double px, const double py, const double u0, const double v0)
static bool checkFilename(const char *filename)
Definition: vpIoTools.cpp:675
const char * what() const
static std::string createFilePath(const std::string &parent, const std::string &child)
Definition: vpIoTools.cpp:1541
static void display(const vpImage< unsigned char > &I)
The vpDisplayOpenCV allows to display image using the OpenCV library. Thus to enable this class OpenC...
Generic class defining intrinsic camera parameters.
static void displayRectangle(const vpImage< unsigned char > &I, const vpImagePoint &topLeft, unsigned int width, unsigned int height, const vpColor &color, bool fill=false, unsigned int thickness=1)
static void read(vpImage< unsigned char > &I, const std::string &filename)
Definition: vpImageIo.cpp:207
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))
Implementation of a pose vector and operations on poses.
Definition: vpPoseVector.h:92
Defines a rectangle in the plane.
Definition: vpRect.h:78
static vpColor getColor(const unsigned int &i)
Definition: vpColor.h:249
std::vector< std::string > & getMessage()
vpRect getBBox(size_t i) const
std::vector< std::vector< vpImagePoint > > & getPolygon()