You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

aruco_detector_node.py 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. #################################################################################
  2. # author: Friegel, Kowatsch, Brueckner
  3. # date: 27.02.2022
  4. # description: With this ROS2-Node can be determined the pose of different ArUco Markers.
  5. #
  6. #
  7. # Node parameters:
  8. # - marker_size:
  9. # size of the marker in meters
  10. # default: 0.1m (10cm)
  11. #
  12. # - aruco_dictionary_id:
  13. # Kind of ArUco Marker which is used. For instance DICT_4X4_50, DICT4X4_100, ...
  14. # For furhter information take a look into https://docs.opencv.org/3.4/d9/d6a/group__aruco.html
  15. # default: DICT_6X6_100
  16. #
  17. # - input_image_topic
  18. # Topic with the pictures. It must be a compressed Image.
  19. # default: "/image_raw/compressed"
  20. #
  21. # - url_yaml_file
  22. # Url where the yaml_file is stored. You get a yaml-file after camera calibration.
  23. # default: "file:///home/ros2/dev2_ws/src/aruco_detector/hd_pro_webcam_c920.yaml"
  24. #
  25. #
  26. # Outputs:
  27. # - topic: /aruco/aruco_poses
  28. # The aruco_detector_node publishes a custom ros2 message.
  29. #
  30. # - topic: /aruco/aruco_output_images/compressed
  31. # To this topic will be published the output images.
  32. # In this images the detected ArCuo Marker is highlighted and the pose is drawn using a coordinate system.
  33. #
  34. #################################################################################
  35. # Imports
  36. import rclpy
  37. from rclpy.node import Node
  38. from rclpy.qos import qos_profile_sensor_data
  39. from cv_bridge import CvBridge
  40. import cv2
  41. import numpy as np
  42. import math
  43. from transforms3d.euler import mat2euler
  44. from aruco_detector import yaml_handling
  45. from sensor_msgs.msg import CompressedImage
  46. from geometry_msgs.msg import Pose
  47. from aruco_interfaces.msg import ArucoMarkerPose
  48. class ArucoDetectorNode(rclpy.node.Node):
  49. def __init__(self):
  50. super().__init__('aruco_detector_node')
  51. # Declare parameters
  52. self.declare_parameter("marker_size", .1)
  53. self.declare_parameter("aruco_dictionary_id", "DICT_6X6_100")
  54. self.declare_parameter("input_image_topic", "/image_raw/compressed")
  55. self.declare_parameter("url_yaml_file", "file:///home/ros2/dev2_ws/src/aruco_detector/hd_pro_webcam_c920.yaml") # url to yaml file (calibration file)
  56. # Read parameters
  57. self.marker_size = self.get_parameter("marker_size").get_parameter_value().double_value
  58. dictionary_id_name = self.get_parameter("aruco_dictionary_id").get_parameter_value().string_value
  59. input_image_topic = self.get_parameter("input_image_topic").get_parameter_value().string_value
  60. url_yaml_file = self.get_parameter("url_yaml_file").get_parameter_value().string_value
  61. # check dictionary_id
  62. try:
  63. dictionary_id = cv2.aruco.__getattribute__(dictionary_id_name)
  64. if type(dictionary_id) != type(cv2.aruco.DICT_5X5_100):
  65. raise AttributeError
  66. except AttributeError:
  67. self.get_logger().error("bad aruco_dictionary_id: {}".format(dictionary_id_name))
  68. options = "\n".join([s for s in dir(cv2.aruco) if s.startswith("DICT")])
  69. self.get_logger().error("valid options: {}".format(options))
  70. # Set up subscriber
  71. # (images from the camera in wich the ArUco Marker will be detected)
  72. self.create_subscription(CompressedImage, input_image_topic, self.image_callback, qos_profile_sensor_data)
  73. # Set up publishers
  74. # - current poses
  75. # - images with the detected and highlighted ArUco-Tags
  76. self.publisher_marker_poses = self.create_publisher(ArucoMarkerPose, '/aruco/aruco_poses', 10)
  77. self.publisher_output_images = self.create_publisher(CompressedImage, '/aruco/aruco_output_images/compressed', 10)
  78. # Load the camera parameters from yaml-file
  79. camera_info_msg = yaml_handling.yaml_to_CameraInfo(url_yaml_file)
  80. if camera_info_msg is None:
  81. self.get_logger().warn("No valid camera info inside the yaml_file!")
  82. return
  83. # read and store camera parameters
  84. self.intrinsic_mat = np.reshape(np.array(camera_info_msg.k), (3, 3))
  85. self.distortion = np.array(camera_info_msg.d)
  86. # get the dictionary / parameters
  87. self.aruco_dictionary = cv2.aruco.Dictionary_get(dictionary_id)
  88. self.aruco_parameters = cv2.aruco.DetectorParameters_create()
  89. # Initialize the CvBridge to convert ROS Image messages to OpenCV images
  90. # For detailed informations:
  91. # http://docs.ros.org/en/lunar/api/cv_bridge/html/python/index.html
  92. self.bridge = CvBridge()
  93. def image_callback(self, img_msg):
  94. """
  95. image_callback(self, img_msg)
  96. Callback function from the image-subscriber.
  97. Is called if a new compressed image will be received.
  98. """
  99. # Convert the compressed input image into cv_image
  100. # This is done, because the folling functions needs the cv_image-type.
  101. cv_image = self.bridge.compressed_imgmsg_to_cv2(img_msg,desired_encoding='passthrough')
  102. # Basic marker detection
  103. # -corners: vector of detected marker corners
  104. # -marker_ids: ids read from the ArUco Marker
  105. corners, marker_ids, rejected = cv2.aruco.detectMarkers(cv_image,
  106. self.aruco_dictionary,
  107. parameters=self.aruco_parameters)
  108. #check if marker is detected, then estimate pose an publish
  109. if marker_ids is not None:
  110. # Pose estimation for single markers
  111. # -rvecs: array of output rotation vectors
  112. # -tvecs: array of output translation vectors
  113. rvecs, tvecs, _objPoints = cv2.aruco.estimatePoseSingleMarkers(corners,
  114. self.marker_size,
  115. self.intrinsic_mat,
  116. self.distortion)
  117. # Create the variable in which all poses and marker_ids are stored.
  118. # ArucoMarkerPose is an custom ros2 message, which is found inside the folder "aruco_interfaces"
  119. pose_array = ArucoMarkerPose()
  120. # iterate through all founded marker_ids and compute their poses
  121. for i, marker_id in enumerate(marker_ids):
  122. # store the calulated current position (translation vector)
  123. pose = Pose()
  124. pose.position.x = tvecs[i][0][0]
  125. pose.position.y = tvecs[i][0][1]
  126. pose.position.z = tvecs[i][0][2]
  127. # create the rotation matrix
  128. rot_matrix = np.eye(4)
  129. rot_matrix[0:3, 0:3] = cv2.Rodrigues(np.array(rvecs[i][0]))[0]
  130. # calculate the current angle
  131. angle_psi, angle_theta, angle_phi = mat2euler(rot_matrix)
  132. # calculate the quaternion
  133. # rotation axis: z => 2D detection
  134. # If 3D detection is required, the calculation can be adjusted here.
  135. pose.orientation.x = 0.0
  136. pose.orientation.y = 0.0
  137. pose.orientation.z = math.sin((angle_phi) / 2)
  138. pose.orientation.w = math.cos((angle_phi) / 2)
  139. # store the calculated pose and marker id in the output list
  140. pose_array.poses.append(pose)
  141. pose_array.marker_ids.append(marker_id[0])
  142. # publish the pose list when all markers are detected
  143. self.publisher_marker_poses.publish(pose_array)
  144. # Draw the Axis into the output picture
  145. output_img = cv2.aruco.drawAxis(cv_image,
  146. self.intrinsic_mat,
  147. self.distortion,
  148. rvecs,
  149. tvecs,
  150. 0.5
  151. )
  152. # Draw a rectangle around the detected markers to highlight them.
  153. output_img = cv2.aruco.drawDetectedMarkers(cv_image, corners, marker_ids)
  154. # Convert the cv-image to CompressedImage
  155. output_img_compressed = self.bridge.cv2_to_compressed_imgmsg(output_img, dst_format='jpg')
  156. # Publish the finished picture with the highlighted Markers and Axis
  157. self.publisher_output_images.publish(output_img_compressed)
  158. def main(args=None):
  159. rclpy.init(args=args)
  160. minimal_publisher = ArucoDetectorNode()
  161. rclpy.spin(minimal_publisher)
  162. minimal_publisher.destroy_node()
  163. rclpy.shutdown()
  164. if __name__ == '__main__':
  165. main()