aruco_detect.cpp
Go to the documentation of this file.
1 /*
2  * Detecting and pose estimation of ArUco markers
3  * Copyright (C) 2018 Copter Express Technologies
4  *
5  * Author: Oleg Kalachev <okalachev@gmail.com>
6  *
7  * Distributed under MIT License (available at https://opensource.org/licenses/MIT).
8  * The above copyright notice and this permission notice shall be included in all
9  * copies or substantial portions of the Software.
10  */
11 
12 /*
13  * Code is based on https://github.com/UbiquityRobotics/fiducials, which is distributed
14  * under the BSD license.
15  */
16 
17 #include <math.h>
18 #include <vector>
19 #include <string>
20 #include <map>
21 #include <unordered_map>
22 #include <unordered_set>
23 #include <ros/ros.h>
24 #include <nodelet/nodelet.h>
26 #include <tf/transform_datatypes.h>
27 #include <tf2_ros/buffer.h>
32 #include <cv_bridge/cv_bridge.h>
33 #include <dynamic_reconfigure/server.h>
34 #include <geometry_msgs/Vector3.h>
35 #include <geometry_msgs/Pose.h>
36 #include <geometry_msgs/PoseStamped.h>
37 #include <geometry_msgs/PoseWithCovarianceStamped.h>
38 #include <geometry_msgs/TransformStamped.h>
39 #include <visualization_msgs/Marker.h>
40 #include <visualization_msgs/MarkerArray.h>
41 
42 #include <opencv2/opencv.hpp>
43 #include <opencv2/highgui.hpp>
44 #include <opencv2/aruco.hpp>
45 #include <opencv2/imgproc/imgproc.hpp>
46 #include <opencv2/calib3d/calib3d.hpp>
47 
48 #include <aruco_pose/Marker.h>
49 #include <aruco_pose/MarkerArray.h>
50 #include <aruco_pose/DetectorConfig.h>
51 
52 #include "utils.h"
53 #include <memory>
54 #include <functional>
55 
56 using std::vector;
57 using cv::Mat;
58 
59 class ArucoDetect : public nodelet::Nodelet {
60 private:
61  std::unique_ptr<tf2_ros::TransformBroadcaster> br_;
62  std::unique_ptr<tf2_ros::Buffer> tf_buffer_;
63  std::unique_ptr<tf2_ros::TransformListener> tf_listener_;
64  std::shared_ptr<dynamic_reconfigure::Server<aruco_pose::DetectorConfig>> dyn_srv_;
65  bool enabled_ = true;
66  cv::Ptr<cv::aruco::Dictionary> dictionary_;
67  cv::Ptr<cv::aruco::DetectorParameters> parameters_;
73  double length_;
74  std::unordered_map<int, double> length_override_;
77  aruco_pose::MarkerArray array_;
78  std::unordered_set<int> map_markers_ids_;
79  visualization_msgs::MarkerArray vis_array_;
80 
81 public:
82  virtual void onInit()
83  {
86 
87  br_.reset(new tf2_ros::TransformBroadcaster());
88  tf_buffer_.reset(new tf2_ros::Buffer());
89  tf_listener_.reset(new tf2_ros::TransformListener(*tf_buffer_, nh_));
90 
91  int dictionary;
92  dictionary = nh_priv_.param("dictionary", 2);
93  estimate_poses_ = nh_priv_.param("estimate_poses", true);
94  send_tf_ = nh_priv_.param("send_tf", true);
95  if (estimate_poses_ && !nh_priv_.getParam("length", length_)) {
96  NODELET_FATAL("can't estimate marker's poses as ~length parameter is not defined");
97  ros::shutdown();
98  }
99  readLengthOverride(nh_priv_);
100 
101  known_tilt_ = nh_priv_.param<std::string>("known_tilt", "");
102  auto_flip_ = nh_priv_.param("auto_flip", false);
103 
104  frame_id_prefix_ = nh_priv_.param<std::string>("frame_id_prefix", "aruco_");
105 
106  camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F);
107 
108  dictionary_ = cv::aruco::getPredefinedDictionary(static_cast<cv::aruco::PREDEFINED_DICTIONARY_NAME>(dictionary));
110 
112  image_transport::ImageTransport it_priv(nh_priv_);
113 
114  map_markers_sub_ = nh_.subscribe("map_markers", 1, &ArucoDetect::mapMarkersCallback, this);
115  debug_pub_ = it_priv.advertise("debug", 1);
116  markers_pub_ = nh_priv_.advertise<aruco_pose::MarkerArray>("markers", 1);
117  vis_markers_pub_ = nh_priv_.advertise<visualization_msgs::MarkerArray>("visualization", 1);
118  img_sub_ = it.subscribeCamera("image_raw", 1, &ArucoDetect::imageCallback, this);
119 
120  dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<aruco_pose::DetectorConfig>>(nh_priv_);
121  dynamic_reconfigure::Server<aruco_pose::DetectorConfig>::CallbackType cb;
122 
123  cb = std::bind(&ArucoDetect::paramCallback, this, std::placeholders::_1, std::placeholders::_2);
124  dyn_srv_->setCallback(cb);
125 
126  NODELET_INFO("ready");
127  }
128 
129 private:
130  void imageCallback(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr &cinfo)
131  {
132  if (!enabled_) return;
133 
134  Mat image = cv_bridge::toCvShare(msg, "bgr8")->image;
135 
136  vector<int> ids;
137  vector<vector<cv::Point2f>> corners, rejected;
138  vector<cv::Vec3d> rvecs, tvecs;
139  vector<cv::Point3f> obj_points;
140  geometry_msgs::TransformStamped snap_to;
141 
142  // Detect markers
143  cv::aruco::detectMarkers(image, dictionary_, corners, ids, parameters_, rejected);
144 
145  array_.header.stamp = msg->header.stamp;
146  array_.header.frame_id = msg->header.frame_id;
147  array_.markers.clear();
148 
149  if (ids.size() != 0) {
150  parseCameraInfo(cinfo, camera_matrix_, dist_coeffs_);
151 
152  // Estimate individual markers' poses
153  if (estimate_poses_) {
154  cv::aruco::estimatePoseSingleMarkers(corners, length_, camera_matrix_, dist_coeffs_,
155  rvecs, tvecs);
156 
157  // process length override, TODO: efficiency
158  if (!length_override_.empty()) {
159  for (unsigned int i = 0; i < ids.size(); i++) {
160  int id = ids[i];
161  auto item = length_override_.find(id);
162  if (item != length_override_.end()) { // found override
163  vector<cv::Vec3d> rvecs_current, tvecs_current;
164  vector<vector<cv::Point2f>> corners_current;
165  corners_current.push_back(corners[i]);
166  cv::aruco::estimatePoseSingleMarkers(corners_current, item->second,
167  camera_matrix_, dist_coeffs_,
168  rvecs_current, tvecs_current);
169  rvecs[i] = rvecs_current[0];
170  tvecs[i] = tvecs_current[0];
171  }
172  }
173  }
174 
175  if (!known_tilt_.empty()) {
176  try {
177  snap_to = tf_buffer_->lookupTransform(msg->header.frame_id, known_tilt_,
178  msg->header.stamp, ros::Duration(0.02));
179  } catch (const tf2::TransformException& e) {
180  NODELET_WARN_THROTTLE(5, "can't snap: %s", e.what());
181  }
182  }
183  }
184 
185  array_.markers.reserve(ids.size());
186  aruco_pose::Marker marker;
187  geometry_msgs::TransformStamped transform;
188  transform.header.stamp = msg->header.stamp;
189  transform.header.frame_id = msg->header.frame_id;
190 
191  for (unsigned int i = 0; i < ids.size(); i++) {
192  marker.id = ids[i];
193  marker.length = getMarkerLength(marker.id);
194  fillCorners(marker, corners[i]);
195 
196  if (estimate_poses_) {
197  fillPose(marker.pose, rvecs[i], tvecs[i]);
198 
199  // snap orientation (if enabled and snap frame available)
200  if (!known_tilt_.empty() && !snap_to.header.frame_id.empty()) {
201  snapOrientation(marker.pose.orientation, snap_to.transform.rotation, auto_flip_);
202  }
203 
204  // TODO: check IDs are unique
205  if (send_tf_) {
206  transform.child_frame_id = getChildFrameId(ids[i]);
207 
208  // check if such static transform is in the map
209  if (map_markers_ids_.find(ids[i]) == map_markers_ids_.end()) {
210  transform.transform.rotation = marker.pose.orientation;
211  fillTranslation(transform.transform.translation, tvecs[i]);
212  br_->sendTransform(transform);
213  }
214  }
215  }
216  array_.markers.push_back(marker);
217  }
218  }
219 
220  markers_pub_.publish(array_);
221 
222  // Publish visualization markers
223  if (estimate_poses_ && vis_markers_pub_.getNumSubscribers() != 0) {
224  // Delete all markers
225  visualization_msgs::Marker vis_marker;
226  vis_marker.action = visualization_msgs::Marker::DELETEALL;
227  vis_array_.markers.clear();
228  vis_array_.markers.reserve(ids.size() + 1);
229  vis_array_.markers.push_back(vis_marker);
230 
231  for (unsigned int i = 0; i < ids.size(); i++)
232  pushVisMarkers(msg->header.frame_id, msg->header.stamp, array_.markers[i].pose,
233  getMarkerLength(ids[i]), ids[i], i);
234 
235  vis_markers_pub_.publish(vis_array_);
236  }
237 
238  // Publish debug image
239  if (debug_pub_.getNumSubscribers() != 0) {
240  Mat debug = image.clone();
241  cv::aruco::drawDetectedMarkers(debug, corners, ids); // draw markers
242  if (estimate_poses_)
243  for (unsigned int i = 0; i < ids.size(); i++)
244  cv::aruco::drawAxis(debug, camera_matrix_, dist_coeffs_,
245  rvecs[i], tvecs[i], getMarkerLength(ids[i]));
246 
247  cv_bridge::CvImage out_msg;
248  out_msg.header.frame_id = msg->header.frame_id;
249  out_msg.header.stamp = msg->header.stamp;
251  out_msg.image = debug;
252  debug_pub_.publish(out_msg.toImageMsg());
253  }
254  }
255 
256  inline void fillCorners(aruco_pose::Marker& marker, const vector<cv::Point2f>& corners) const
257  {
258  marker.c1.x = corners[0].x;
259  marker.c2.x = corners[1].x;
260  marker.c3.x = corners[2].x;
261  marker.c4.x = corners[3].x;
262  marker.c1.y = corners[0].y;
263  marker.c2.y = corners[1].y;
264  marker.c3.y = corners[2].y;
265  marker.c4.y = corners[3].y;
266  }
267 
268  inline void fillPose(geometry_msgs::Pose& pose, const cv::Vec3d& rvec, const cv::Vec3d& tvec) const
269  {
270  pose.position.x = tvec[0];
271  pose.position.y = tvec[1];
272  pose.position.z = tvec[2];
273 
274  double angle = norm(rvec);
275  cv::Vec3d axis = rvec / angle;
276 
277  tf2::Quaternion q;
278  q.setRotation(tf2::Vector3(axis[0], axis[1], axis[2]), angle);
279 
280  pose.orientation.w = q.w();
281  pose.orientation.x = q.x();
282  pose.orientation.y = q.y();
283  pose.orientation.z = q.z();
284  }
285 
286  inline void fillTranslation(geometry_msgs::Vector3& translation, const cv::Vec3d& tvec) const
287  {
288  translation.x = tvec[0];
289  translation.y = tvec[1];
290  translation.z = tvec[2];
291  }
292 
293  void pushVisMarkers(const std::string& frame_id, const ros::Time& stamp,
294  const geometry_msgs::Pose &pose, double length, int id, int index)
295  {
296  visualization_msgs::Marker marker;
297  marker.header.frame_id = frame_id;
298  marker.header.stamp = stamp;
299  marker.action = visualization_msgs::Marker::ADD;
300  marker.id = index;
301 
302  // Marker
303  marker.ns = "aruco_marker";
304  marker.type = visualization_msgs::Marker::CUBE;
305  marker.scale.x = length;
306  marker.scale.y = length;
307  marker.scale.z = 0.001;
308  marker.color.r = 1;
309  marker.color.g = 1;
310  marker.color.b = 1;
311  marker.color.a = 0.9;
312  marker.pose = pose;
313  vis_array_.markers.push_back(marker);
314 
315  // Label
316  marker.ns = "aruco_marker_label";
317  marker.type = visualization_msgs::Marker::TEXT_VIEW_FACING;
318  marker.scale.z = length * 0.6;
319  marker.color.r = 0;
320  marker.color.g = 0;
321  marker.color.b = 0;
322  marker.color.a = 1;
323  marker.text = std::to_string(id);
324  marker.pose = pose;
325  vis_array_.markers.push_back(marker);
326  }
327 
328  inline std::string getChildFrameId(int id) const
329  {
330  return frame_id_prefix_ + std::to_string(id);
331  }
332 
334  {
335  std::map<std::string, double> length_override;
336  nh.getParam("length_override", length_override);
337  for (auto const& item : length_override) {
338  length_override_[std::stoi(item.first)] = item.second;
339  }
340  }
341 
342  inline double getMarkerLength(int id)
343  {
344  auto item = length_override_.find(id);
345  if (item != length_override_.end()) {
346  return item->second;
347  } else {
348  return length_;
349  }
350  }
351 
352  void mapMarkersCallback(const aruco_pose::MarkerArray& msg)
353  {
354  map_markers_ids_.clear();
355  for (auto const& marker : msg.markers) {
356  map_markers_ids_.insert(marker.id);
357  }
358  }
359 
360  void paramCallback(aruco_pose::DetectorConfig &config, uint32_t level)
361  {
362  enabled_ = config.enabled;
363  parameters_->adaptiveThreshConstant = config.adaptiveThreshConstant;
364  parameters_->adaptiveThreshWinSizeMin = config.adaptiveThreshWinSizeMin;
365  parameters_->adaptiveThreshWinSizeMax = config.adaptiveThreshWinSizeMax;
366  parameters_->adaptiveThreshWinSizeStep = config.adaptiveThreshWinSizeStep;
367  parameters_->cornerRefinementMaxIterations = config.cornerRefinementMaxIterations;
368  parameters_->cornerRefinementMethod = config.cornerRefinementMethod;
369  parameters_->cornerRefinementMinAccuracy = config.cornerRefinementMinAccuracy;
370  parameters_->cornerRefinementWinSize = config.cornerRefinementWinSize;
371 #if ((CV_VERSION_MAJOR == 3) && (CV_VERSION_MINOR >= 4) && (CV_VERSION_REVISION >= 7)) || (CV_VERSION_MAJOR > 3)
372  parameters_->detectInvertedMarker = config.detectInvertedMarker;
373 #endif
374  parameters_->errorCorrectionRate = config.errorCorrectionRate;
375  parameters_->minCornerDistanceRate = config.minCornerDistanceRate;
376  parameters_->markerBorderBits = config.markerBorderBits;
377  parameters_->maxErroneousBitsInBorderRate = config.maxErroneousBitsInBorderRate;
378  parameters_->minDistanceToBorder = config.minDistanceToBorder;
379  parameters_->minMarkerDistanceRate = config.minMarkerDistanceRate;
380  parameters_->minMarkerPerimeterRate = config.minMarkerPerimeterRate;
381  parameters_->maxMarkerPerimeterRate = config.maxMarkerPerimeterRate;
382  parameters_->minOtsuStdDev = config.minOtsuStdDev;
383  parameters_->perspectiveRemoveIgnoredMarginPerCell = config.perspectiveRemoveIgnoredMarginPerCell;
384  parameters_->perspectiveRemovePixelPerCell = config.perspectiveRemovePixelPerCell;
385  parameters_->polygonalApproxAccuracyRate = config.polygonalApproxAccuracyRate;
386 #if ((CV_VERSION_MAJOR == 3) && (CV_VERSION_MINOR >= 4) && (CV_VERSION_REVISION >= 2)) || (CV_VERSION_MAJOR > 3)
387  parameters_->aprilTagQuadDecimate = config.aprilTagQuadDecimate;
388  parameters_->aprilTagQuadSigma = config.aprilTagQuadSigma;
389 #endif
390  }
391 };
392 
CvImageConstPtr toCvShare(const sensor_msgs::ImageConstPtr &source, const std::string &encoding=std::string())
void fillCorners(aruco_pose::Marker &marker, const vector< cv::Point2f > &corners) const
aruco_pose::MarkerArray array_
void snapOrientation(geometry_msgs::Quaternion &to, const geometry_msgs::Quaternion &from, bool auto_flip=false)
Definition: utils.h:110
std::unique_ptr< tf2_ros::TransformBroadcaster > br_
visualization_msgs::MarkerArray vis_array_
void imageCallback(const sensor_msgs::ImageConstPtr &msg, const sensor_msgs::CameraInfoConstPtr &cinfo)
std::string frame_id_prefix_
image_transport::Publisher debug_pub_
ros::Publisher markers_pub_
double getMarkerLength(int id)
uint32_t getNumSubscribers() const
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
CameraSubscriber subscribeCamera(const std::string &base_topic, uint32_t queue_size, const CameraSubscriber::Callback &callback, const ros::VoidPtr &tracked_object=ros::VoidPtr(), const TransportHints &transport_hints=TransportHints())
cv::Ptr< cv::aruco::Dictionary > dictionary_
ros::NodeHandle & getNodeHandle() const
cv::Ptr< cv::aruco::DetectorParameters > parameters_
double length_
std::string encoding
Publisher advertise(const std::string &base_topic, uint32_t queue_size, bool latch=false)
ros::NodeHandle & getPrivateNodeHandle() const
static void parseCameraInfo(const sensor_msgs::CameraInfoConstPtr &cinfo, cv::Mat &matrix, cv::Mat &dist)
Definition: utils.h:33
length
Definition: genmap.py:42
void pushVisMarkers(const std::string &frame_id, const ros::Time &stamp, const geometry_msgs::Pose &pose, double length, int id, int index)
void fillTranslation(geometry_msgs::Vector3 &translation, const cv::Vec3d &tvec) const
TFSIMD_FORCE_INLINE tfScalar angle(const Quaternion &q1, const Quaternion &q2)
CV_EXPORTS Ptr< Dictionary > getPredefinedDictionary(PREDEFINED_DICTIONARY_NAME name)
Returns one of the predefined dictionaries defined in PREDEFINED_DICTIONARY_NAME. ...
Definition: dictionary.cpp:301
bool estimate_poses_
std::string known_tilt_
std::unordered_map< int, double > length_override_
sensor_msgs::ImagePtr toImageMsg() const
bool param(const std::string &param_name, T &param_val, const T &default_val) const
void readLengthOverride(ros::NodeHandle &nh)
void publish(const boost::shared_ptr< M > &message) const
#define NODELET_WARN_THROTTLE(rate,...)
bool getParam(const std::string &key, std::string &s) const
void mapMarkersCallback(const aruco_pose::MarkerArray &msg)
void fillPose(geometry_msgs::Pose &pose, const cv::Vec3d &rvec, const cv::Vec3d &tvec) const
virtual void onInit()
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
std::unordered_set< int > map_markers_ids_
void publish(const sensor_msgs::Image &message) const
ros::Subscriber map_markers_sub_
void setRotation(const Vector3 &axis, const tf2Scalar &angle)
ros::Publisher vis_markers_pub_
std::shared_ptr< dynamic_reconfigure::Server< aruco_pose::DetectorConfig > > dyn_srv_
#define NODELET_INFO(...)
std::string getChildFrameId(int id) const
image_transport::CameraSubscriber img_sub_
CV_EXPORTS_W void drawDetectedMarkers(InputOutputArray image, InputArrayOfArrays corners, InputArray ids=noArray(), Scalar borderColor=Scalar(0, 255, 0))
Draw detected markers in image.
Definition: aruco.cpp:1702
InputArrayOfArrays const Ptr< CharucoBoard > Size InputOutputArray InputOutputArray OutputArrayOfArrays rvecs
Definition: charuco.hpp:245
CV_EXPORTS_W void estimatePoseSingleMarkers(InputArrayOfArrays corners, float markerLength, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvecs, OutputArray tvecs, OutputArray _objPoints=noArray())
Pose estimation for single markers.
Definition: aruco.cpp:1222
static CV_WRAP Ptr< DetectorParameters > create()
Create a new set of DetectorParameters with default values.
Definition: aruco.cpp:96
InputArray ids
Definition: aruco.hpp:569
ROSCPP_DECL void shutdown()
#define NODELET_FATAL(...)
InputArrayOfArrays const Ptr< CharucoBoard > Size InputOutputArray InputOutputArray OutputArrayOfArrays OutputArrayOfArrays tvecs
Definition: charuco.hpp:245
uint32_t getNumSubscribers() const
CV_EXPORTS_W void detectMarkers(InputArray image, const Ptr< Dictionary > &dictionary, OutputArrayOfArrays corners, OutputArray ids, const Ptr< DetectorParameters > &parameters=DetectorParameters::create(), OutputArrayOfArrays rejectedImgPoints=noArray(), InputArray cameraMatrix=noArray(), InputArray distCoeff=noArray())
Basic marker detection.
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
std::unique_ptr< tf2_ros::TransformListener > tf_listener_
void paramCallback(aruco_pose::DetectorConfig &config, uint32_t level)
NodeHandlePtr nh_
std::unique_ptr< tf2_ros::Buffer > tf_buffer_
std_msgs::Header header
CV_EXPORTS_W void drawAxis(InputOutputArray image, InputArray cameraMatrix, InputArray distCoeffs, InputArray rvec, InputArray tvec, float length)
Draw coordinate system axis from pose estimation.
Definition: aruco.cpp:1749


aruco_pose
Author(s): Oleg Kalachev
autogenerated on Mon Feb 28 2022 22:08:24