Visual Servoing Platform  version 3.2.0 under development (2019-01-22)
vpDetectorAprilTag.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  * Base class for April Tag detection.
33  *
34  *****************************************************************************/
35 #include <visp3/core/vpConfig.h>
36 
37 #ifdef VISP_HAVE_APRILTAG
38 #include <map>
39 
40 #include <apriltag.h>
41 #include <common/homography.h>
42 #include <tag16h5.h>
43 #include <tag25h7.h>
44 #include <tag25h9.h>
45 #include <tag36artoolkit.h>
46 #include <tag36h10.h>
47 #include <tag36h11.h>
48 
49 #include <visp3/core/vpDisplay.h>
50 #include <visp3/core/vpPixelMeterConversion.h>
51 #include <visp3/core/vpPoint.h>
52 #include <visp3/detection/vpDetectorAprilTag.h>
53 #include <visp3/vision/vpPose.h>
54 
55 #ifndef DOXYGEN_SHOULD_SKIP_THIS
56 class vpDetectorAprilTag::Impl
57 {
58 public:
59  Impl(const vpAprilTagFamily &tagFamily, const vpPoseEstimationMethod &method)
60  : m_cam(), m_poseEstimationMethod(method), m_tagFamily(tagFamily), m_tagPoses(), m_tagSize(1.0), m_td(NULL),
61  m_tf(NULL), m_detections(NULL), m_zAlignedWithCameraFrame(false)
62  {
63  switch (m_tagFamily) {
64  case TAG_36h11:
65  m_tf = tag36h11_create();
66  break;
67 
68  case TAG_36h10:
69  m_tf = tag36h10_create();
70  break;
71 
72  case TAG_36ARTOOLKIT:
73  m_tf = tag36artoolkit_create();
74  break;
75 
76  case TAG_25h9:
77  m_tf = tag25h9_create();
78  break;
79 
80  case TAG_25h7:
81  m_tf = tag25h7_create();
82  break;
83 
84  case TAG_16h5:
85  m_tf = tag16h5_create();
86  break;
87 
88  default:
89  throw vpException(vpException::fatalError, "Unknow Tag family!");
90  break;
91  }
92 
93  m_td = apriltag_detector_create();
94  apriltag_detector_add_family(m_td, m_tf);
95 
96  m_mapOfCorrespondingPoseMethods[DEMENTHON_VIRTUAL_VS] = vpPose::DEMENTHON;
97  m_mapOfCorrespondingPoseMethods[LAGRANGE_VIRTUAL_VS] = vpPose::LAGRANGE;
98  }
99 
100  ~Impl()
101  {
102  apriltag_detector_destroy(m_td);
103 
104  switch (m_tagFamily) {
105  case TAG_36h11:
106  tag36h11_destroy(m_tf);
107  break;
108 
109  case TAG_36h10:
110  tag36h10_destroy(m_tf);
111  break;
112 
113  case TAG_36ARTOOLKIT:
114  tag36artoolkit_destroy(m_tf);
115  break;
116 
117  case TAG_25h9:
118  tag25h9_destroy(m_tf);
119  break;
120 
121  case TAG_25h7:
122  tag25h7_destroy(m_tf);
123  break;
124 
125  case TAG_16h5:
126  tag16h5_destroy(m_tf);
127  break;
128 
129  default:
130  break;
131  }
132 
133  if (m_detections) {
134  apriltag_detections_destroy(m_detections);
135  m_detections = NULL;
136  }
137  }
138 
139  bool detect(const vpImage<unsigned char> &I, std::vector<std::vector<vpImagePoint> > &polygons,
140  std::vector<std::string> &messages, const bool computePose, const bool displayTag,
141  const vpColor color, const unsigned int thickness)
142  {
143  std::vector<vpHomogeneousMatrix> tagPosesPrev = m_tagPoses;
144  m_tagPoses.clear();
145 
146  image_u8_t im = {/*.width =*/(int32_t)I.getWidth(),
147  /*.height =*/(int32_t)I.getHeight(),
148  /*.stride =*/(int32_t)I.getWidth(),
149  /*.buf =*/I.bitmap};
150 
151  if (m_detections) {
152  apriltag_detections_destroy(m_detections);
153  m_detections = NULL;
154  }
155 
156  m_detections = apriltag_detector_detect(m_td, &im);
157  int nb_detections = zarray_size(m_detections);
158  bool detected = nb_detections > 0;
159 
160  polygons.resize((size_t)nb_detections);
161  messages.resize((size_t)nb_detections);
162 
163  for (int i = 0; i < zarray_size(m_detections); i++) {
164  apriltag_detection_t *det;
165  zarray_get(m_detections, i, &det);
166 
167  std::vector<vpImagePoint> polygon;
168  for (int j = 0; j < 4; j++) {
169  polygon.push_back(vpImagePoint(det->p[j][1], det->p[j][0]));
170  }
171  polygons[i] = polygon;
172  std::stringstream ss;
173  ss << m_tagFamily << " id: " << det->id;
174  messages[i] = ss.str();
175 
176  if (displayTag) {
177  vpColor Ox = (color == vpColor::none) ? vpColor::red : color;
178  vpColor Oy = (color == vpColor::none) ? vpColor::green : color;
179  vpColor Ox2 = (color == vpColor::none) ? vpColor::yellow : color;
180  vpColor Oy2 = (color == vpColor::none) ? vpColor::blue : color;
181 
182  vpDisplay::displayLine(I, (int)det->p[0][1], (int)det->p[0][0], (int)det->p[1][1], (int)det->p[1][0],
183  Ox, thickness);
184  vpDisplay::displayLine(I, (int)det->p[0][1], (int)det->p[0][0], (int)det->p[3][1], (int)det->p[3][0],
185  Oy, thickness);
186  vpDisplay::displayLine(I, (int)det->p[1][1], (int)det->p[1][0], (int)det->p[2][1], (int)det->p[2][0],
187  Ox2, thickness);
188  vpDisplay::displayLine(I, (int)det->p[2][1], (int)det->p[2][0], (int)det->p[3][1], (int)det->p[3][0],
189  Oy2, thickness);
190  }
191 
192  if (computePose) {
194  {
195  if ((int)tagPosesPrev.size() > i) {
196  cMo = tagPosesPrev[i];
197  }
198  }
199  if (getPose(i, m_tagSize, m_cam, cMo)) {
200  m_tagPoses.push_back(cMo);
201  }
202  // else case should never happen
203  }
204  }
205 
206  return detected;
207  }
208 
209  bool getPose(size_t tagIndex, const double tagSize, const vpCameraParameters &cam, vpHomogeneousMatrix &cMo) {
210  if (m_detections == NULL) {
211  throw(vpException(vpException::fatalError, "Cannot get tag index=%d pose: detection empty", tagIndex));
212  }
213  apriltag_detection_t *det;
214  zarray_get(m_detections, static_cast<int>(tagIndex), &det);
215 
216  int nb_detections = zarray_size(m_detections);
217  if (tagIndex >= (size_t)nb_detections) {
218  return false;
219  }
220 
223  double fx = cam.get_px(), fy = cam.get_py();
224  double cx = cam.get_u0(), cy = cam.get_v0();
225 
226  matd_t *M = homography_to_pose(det->H, fx, fy, cx, cy, tagSize / 2);
227 
228  for (int i = 0; i < 3; i++) {
229  for (int j = 0; j < 3; j++) {
230  cMo[i][j] = MATD_EL(M, i, j);
231  }
232  cMo[i][3] = MATD_EL(M, i, 3);
233  }
234 
235  matd_destroy(M);
236 
239  // Apply a rotation of 180deg around x axis
240  oMo[0][0] = 1; oMo[0][1] = 0; oMo[0][2] = 0;
241  oMo[1][0] = 0; oMo[1][1] = -1; oMo[1][2] = 0;
242  oMo[2][0] = 0; oMo[2][1] = 0; oMo[2][2] = -1;
243  cMo = cMo*oMo;
244  }
245  }
246 
247  // Add marker object points
248  vpPose pose;
250  vpPoint pt;
251 
252  vpImagePoint imPt;
253  double x = 0.0, y = 0.0;
254  std::vector<vpPoint> pts(4);
256  pt.setWorldCoordinates(-tagSize / 2.0, tagSize / 2.0, 0.0);
257  }
258  else {
259  pt.setWorldCoordinates(-tagSize / 2.0, -tagSize / 2.0, 0.0);
260  }
261  imPt.set_uv(det->p[0][0], det->p[0][1]);
262  vpPixelMeterConversion::convertPoint(cam, imPt, x, y);
263  pt.set_x(x);
264  pt.set_y(y);
265  pts[0] = pt;
266 
268  pt.setWorldCoordinates(tagSize / 2.0, tagSize / 2.0, 0.0);
269  }
270  else {
271  pt.setWorldCoordinates(tagSize / 2.0, -tagSize / 2.0, 0.0);
272  }
273  imPt.set_uv(det->p[1][0], det->p[1][1]);
274  vpPixelMeterConversion::convertPoint(cam, imPt, x, y);
275  pt.set_x(x);
276  pt.set_y(y);
277  pts[1] = pt;
278 
280  pt.setWorldCoordinates(tagSize / 2.0, -tagSize / 2.0, 0.0);
281  }
282  else {
283  pt.setWorldCoordinates(tagSize / 2.0, tagSize / 2.0, 0.0);
284  }
285  imPt.set_uv(det->p[2][0], det->p[2][1]);
286  vpPixelMeterConversion::convertPoint(cam, imPt, x, y);
287  pt.set_x(x);
288  pt.set_y(y);
289  pts[2] = pt;
290 
292  pt.setWorldCoordinates(-tagSize / 2.0, -tagSize / 2.0, 0.0);
293  }
294  else {
295  pt.setWorldCoordinates(-tagSize / 2.0, tagSize / 2.0, 0.0);
296  }
297  imPt.set_uv(det->p[3][0], det->p[3][1]);
298  vpPixelMeterConversion::convertPoint(cam, imPt, x, y);
299  pt.set_x(x);
300  pt.set_y(y);
301  pts[3] = pt;
302 
303  pose.addPoints(pts);
304  }
305 
308  vpHomogeneousMatrix cMo_dementhon, cMo_lagrange, cMo_homography = cMo;
309 
310  double residual_dementhon = std::numeric_limits<double>::max(),
311  residual_lagrange = std::numeric_limits<double>::max();
312  double residual_homography = pose.computeResidual(cMo_homography);
313 
314  if (pose.computePose(vpPose::DEMENTHON, cMo_dementhon)) {
315  residual_dementhon = pose.computeResidual(cMo_dementhon);
316  }
317 
318  if (pose.computePose(vpPose::LAGRANGE, cMo_lagrange)) {
319  residual_lagrange = pose.computeResidual(cMo_lagrange);
320  }
321 
322  if (residual_dementhon < residual_lagrange) {
323  if (residual_dementhon < residual_homography) {
324  cMo = cMo_dementhon;
325  } else {
326  cMo = cMo_homography;
327  }
328  } else if (residual_lagrange < residual_homography) {
329  cMo = cMo_lagrange;
330  } else {
331  // cMo = cMo_homography; //already the case
332  }
333  } else {
334  pose.computePose(m_mapOfCorrespondingPoseMethods[m_poseEstimationMethod], cMo);
335  }
336  }
337 
339  // Compute final pose using VVS
340  pose.computePose(vpPose::VIRTUAL_VS, cMo);
341  }
342 
343  return true;
344  }
345 
346  void getTagPoses(std::vector<vpHomogeneousMatrix> &tagPoses) const { tagPoses = m_tagPoses; }
347 
348  void setCameraParameters(const vpCameraParameters &cam) { m_cam = cam; }
349 
350  void setNbThreads(const int nThreads) { m_td->nthreads = nThreads; }
351 
352  void setQuadDecimate(const float quadDecimate) { m_td->quad_decimate = quadDecimate; }
353 
354  void setQuadSigma(const float quadSigma) { m_td->quad_sigma = quadSigma; }
355 
356  void setRefineDecode(const bool refineDecode) { m_td->refine_decode = refineDecode ? 1 : 0; }
357 
358  void setRefineEdges(const bool refineEdges) { m_td->refine_edges = refineEdges ? 1 : 0; }
359 
360  void setRefinePose(const bool refinePose) { m_td->refine_pose = refinePose ? 1 : 0; }
361 
362  void setTagSize(const double tagSize) { m_tagSize = tagSize; }
363 
364  void setPoseEstimationMethod(const vpPoseEstimationMethod &method) { m_poseEstimationMethod = method; }
365 
366  void setZAlignedWithCameraAxis(bool zAlignedWithCameraFrame) { m_zAlignedWithCameraFrame = zAlignedWithCameraFrame; }
367 
368 protected:
369  vpCameraParameters m_cam;
370  std::map<vpPoseEstimationMethod, vpPose::vpPoseMethodType> m_mapOfCorrespondingPoseMethods;
373  std::vector<vpHomogeneousMatrix> m_tagPoses;
374  double m_tagSize;
375  apriltag_detector_t *m_td;
376  apriltag_family_t *m_tf;
377  zarray_t *m_detections;
379 };
380 #endif // DOXYGEN_SHOULD_SKIP_THIS
381 
386  const vpPoseEstimationMethod &poseEstimationMethod)
388  m_poseEstimationMethod(poseEstimationMethod), m_tagFamily(tagFamily), m_zAlignedWithCameraFrame(false),
389  m_impl(new Impl(tagFamily, poseEstimationMethod))
390 {
391 }
392 
397 
406 {
407  m_message.clear();
408  m_polygon.clear();
409  m_nb_objects = 0;
410 
411  bool detected = m_impl->detect(I, m_polygon, m_message, false, m_displayTag,
413  m_nb_objects = m_message.size();
414 
415  return detected;
416 }
417 
432 bool vpDetectorAprilTag::detect(const vpImage<unsigned char> &I, const double tagSize, const vpCameraParameters &cam,
433  std::vector<vpHomogeneousMatrix> &cMo_vec)
434 {
435  m_message.clear();
436  m_polygon.clear();
437  m_nb_objects = 0;
438 
439  m_impl->setTagSize(tagSize);
440  m_impl->setCameraParameters(cam);
441  bool detected = m_impl->detect(I, m_polygon, m_message, true, m_displayTag,
443  m_nb_objects = m_message.size();
444  m_impl->getTagPoses(cMo_vec);
445 
446  return detected;
447 }
448 
475 bool vpDetectorAprilTag::getPose(size_t tagIndex, const double tagSize, const vpCameraParameters &cam, vpHomogeneousMatrix &cMo)
476 {
477  return (m_impl->getPose(tagIndex, tagSize, cam, cMo));
478 }
479 
486 {
487  if (nThreads > 0)
488  m_impl->setNbThreads(nThreads);
489 }
490 
497 {
498  m_poseEstimationMethod = poseEstimationMethod;
499  m_impl->setPoseEstimationMethod(poseEstimationMethod);
500 }
501 
514 void vpDetectorAprilTag::setAprilTagQuadDecimate(const float quadDecimate) { m_impl->setQuadDecimate(quadDecimate); }
515 
528 void vpDetectorAprilTag::setAprilTagQuadSigma(const float quadSigma) { m_impl->setQuadSigma(quadSigma); }
529 
542 void vpDetectorAprilTag::setAprilTagRefineDecode(const bool refineDecode) { m_impl->setRefineDecode(refineDecode); }
543 
558 void vpDetectorAprilTag::setAprilTagRefineEdges(const bool refineEdges) { m_impl->setRefineEdges(refineEdges); }
559 
576 void vpDetectorAprilTag::setAprilTagRefinePose(const bool refinePose) { m_impl->setRefinePose(refinePose); }
577 
583 void vpDetectorAprilTag::setZAlignedWithCameraAxis(bool zAlignedWithCameraFrame)
584 {
585  m_zAlignedWithCameraFrame = zAlignedWithCameraFrame;
586  m_impl->setZAlignedWithCameraAxis(zAlignedWithCameraFrame);
587 }
588 
589 #elif !defined(VISP_BUILD_SHARED_LIBS)
590 // Work arround to avoid warning: libvisp_core.a(vpDetectorAprilTag.cpp.o) has
591 // no symbols
592 void dummy_vpDetectorAprilTag() {}
593 #endif
void setAprilTagQuadDecimate(const float quadDecimate)
void setAprilTagRefineEdges(const bool refineEdges)
bool computePose(vpPoseMethodType method, vpHomogeneousMatrix &cMo, bool(*func)(const vpHomogeneousMatrix &)=NULL)
Definition: vpPose.cpp:362
double get_u0() const
void setAprilTagPoseEstimationMethod(const vpPoseEstimationMethod &poseEstimationMethod)
unsigned int getWidth() const
Definition: vpImage.h:239
Implementation of an homogeneous matrix and operations on such kind of matrices.
Type * bitmap
points toward the bitmap
Definition: vpImage.h:133
Class to define colors available for display functionnalities.
Definition: vpColor.h:120
void addPoints(const std::vector< vpPoint > &lP)
Definition: vpPose.cpp:152
static const vpColor none
Definition: vpColor.h:192
error that can be emited by ViSP classes.
Definition: vpException.h:71
void set_x(const double x)
Set the point x coordinate in the image plane.
Definition: vpPoint.cpp:470
static void convertPoint(const vpCameraParameters &cam, const double &u, const double &v, double &x, double &y)
double get_py() const
static const vpColor green
Definition: vpColor.h:183
static const vpColor red
Definition: vpColor.h:180
Class that defines what is a point.
Definition: vpPoint.h:58
bool getPose(size_t tagIndex, const double tagSize, const vpCameraParameters &cam, vpHomogeneousMatrix &cMo)
size_t m_nb_objects
Number of detected objects.
double get_v0() const
void setZAlignedWithCameraAxis(bool zAlignedWithCameraFrame)
Class used for pose computation from N points (pose from point only). Some of the algorithms implemen...
Definition: vpPose.h:78
Generic class defining intrinsic camera parameters.
void set_y(const double y)
Set the point y coordinate in the image plane.
Definition: vpPoint.cpp:472
double get_px() const
vpPoseEstimationMethod m_poseEstimationMethod
void setWorldCoordinates(const double oX, const double oY, const double oZ)
Definition: vpPoint.cpp:113
void setAprilTagRefineDecode(const bool refineDecode)
std::vector< std::vector< vpImagePoint > > m_polygon
vpDetectorAprilTag(const vpAprilTagFamily &tagFamily=TAG_36h11, const vpPoseEstimationMethod &poseEstimationMethod=HOMOGRAPHY_VIRTUAL_VS)
unsigned int getHeight() const
Definition: vpImage.h:178
void set_uv(const double u, const double v)
Definition: vpImagePoint.h:248
void setAprilTagQuadSigma(const float quadSigma)
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
Definition: vpImagePoint.h:88
static void displayLine(const vpImage< unsigned char > &I, const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, unsigned int thickness=1)
static const vpColor yellow
Definition: vpColor.h:188
unsigned int m_displayTagThickness
std::vector< std::string > m_message
Message attached to each object.
vpAprilTagFamily m_tagFamily
void setAprilTagRefinePose(const bool refinePose)
void setAprilTagNbThreads(const int nThreads)
double computeResidual(const vpHomogeneousMatrix &cMo) const
Compute and return the sum of squared residuals expressed in meter^2 for the pose matrix cMo...
Definition: vpPose.cpp:324
static const vpColor blue
Definition: vpColor.h:186
bool detect(const vpImage< unsigned char > &I)