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.

229 lines
8.7 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. ## Example 11
  2. This tutorial highlights how to create a [PointCloud](http://docs.ros.org/en/melodic/api/sensor_msgs/html/msg/PointCloud.html) message from the data of a [PointCloud2](http://docs.ros.org/en/noetic/api/sensor_msgs/html/msg/PointCloud2.html) message type, then transform the PointCloud's reference link to a different frame. The data published by RealSense is referencing its `camera_color_optical_frame` link, and we will be changing its reference to the `base_link`.
  3. Begin by starting up the stretch driver launch file.
  4. ```{.bash .shell-prompt}
  5. roslaunch stretch_core stretch_driver.launch
  6. ```
  7. To activate the [RealSense camera](https://www.intelrealsense.com/depth-camera-d435i/) and publish topics to be visualized, run the following launch file in a new terminal.
  8. ```{.bash .shell-prompt}
  9. roslaunch stretch_core d435i_low_resolution.launch
  10. ```
  11. Then run the [pointCloud_transformer.py](https://github.com/hello-robot/stretch_tutorials/blob/noetic/src/pointcloud_transformer.py) node. In a new terminal, execute:
  12. ```{.bash .shell-prompt}
  13. cd catkin_ws/src/stretch_tutorials/src/
  14. python3 pointcloud_transformer.py
  15. ```
  16. Within this tutorial package, there is an [RViz config file](https://github.com/hello-robot/stretch_tutorials/blob/noetic/rviz/PointCloud_transformer_example.rviz) with the `PointCloud` in the Display tree. You can visualize this topic and the robot model by running the command below in a new terminal.
  17. ```{.bash .shell-prompt}
  18. rosrun rviz rviz -d /home/hello-robot/catkin_ws/src/stretch_tutorials/rviz/PointCloud_transformer_example.rviz
  19. ```
  20. The GIF below visualizes what happens when running the previous node.
  21. <p align="center">
  22. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/noetic/images/PointCloud_transformer.gif"/>
  23. </p>
  24. ### The Code
  25. ```python
  26. #!/usr/bin/env python3
  27. import rospy
  28. import tf
  29. import sensor_msgs.point_cloud2 as pc2
  30. from sensor_msgs.msg import PointCloud2, PointCloud
  31. from geometry_msgs.msg import Point32
  32. from std_msgs.msg import Header
  33. class PointCloudTransformer:
  34. """
  35. A class that takes in a PointCloud2 message and stores its points into a
  36. PointCloud message. Then that PointCloud is transformed to reference the
  37. 'base_link' frame.
  38. """
  39. def __init__(self):
  40. """
  41. Function that initializes the subscriber, publisher, and other variables.
  42. :param self: The self reference.
  43. """
  44. self.pointcloud2_sub = rospy.Subscriber("/camera/depth/color/points", PointCloud2, self.callback_pcl2, queue_size=1)
  45. self.pointcloud_pub = rospy.Publisher("/camera_cloud", PointCloud, queue_size=1)
  46. self.pcl2_cloud = None
  47. self.listener = tf.TransformListener(True, rospy.Duration(10.0))
  48. rospy.loginfo('Publishing transformed PointCloud. Use RViz to visualize')
  49. def callback_pcl2(self,msg):
  50. """
  51. Callback function that stores the PointCloud2 message.
  52. :param self: The self reference.
  53. :param msg: The PointCloud2 message type.
  54. """
  55. self.pcl2_cloud = msg
  56. def pcl_transformer(self):
  57. """
  58. A function that extracts the points from the stored PointCloud2 message
  59. and appends those points to a PointCloud message. Then the function transforms
  60. the PointCloud from its the header frame id, 'camera_color_optical_frame'
  61. to the 'base_link' frame.
  62. :param self: The self reference.
  63. """
  64. temp_cloud = PointCloud()
  65. temp_cloud.header = self.pcl2_cloud.header
  66. for data in pc2.read_points(self.pcl2_cloud, skip_nans=True):
  67. temp_cloud.points.append(Point32(data[0],data[1],data[2]))
  68. transformed_cloud = self.transform_pointcloud(temp_cloud)
  69. self.pointcloud_pub.publish(transformed_cloud)
  70. def transform_pointcloud(self,msg):
  71. """
  72. Function that stores the PointCloud2 message.
  73. :param self: The self reference.
  74. :param msg: The PointCloud message.
  75. :returns new_cloud: The transformed PointCloud message.
  76. """
  77. while not rospy.is_shutdown():
  78. try:
  79. new_cloud = self.listener.transformPointCloud("base_link" ,msg)
  80. return new_cloud
  81. if new_cloud:
  82. break
  83. except (tf.LookupException, tf.ConnectivityException,tf.ExtrapolationException):
  84. pass
  85. if __name__=="__main__":
  86. rospy.init_node('pointcloud_transformer',anonymous=True)
  87. PCT = PointCloudTransformer()
  88. rate = rospy.Rate(1)
  89. rospy.sleep(1)
  90. while not rospy.is_shutdown():
  91. PCT.pcl_transformer()
  92. rate.sleep()
  93. ```
  94. ### The Code Explained
  95. Now let's break the code down.
  96. ```python
  97. #!/usr/bin/env python3
  98. ```
  99. Every Python ROS [Node](http://wiki.ros.org/Nodes) will have this declaration at the top. The first line makes sure your script is executed as a Python3 script.
  100. ```python
  101. import rospy
  102. import tf
  103. import sensor_msgs.point_cloud2 as pc2
  104. from sensor_msgs.msg import PointCloud2, PointCloud
  105. from geometry_msgs.msg import Point32
  106. from std_msgs.msg import Header
  107. ```
  108. You need to import `rospy` if you are writing a ROS [Node](http://wiki.ros.org/Nodes). Import `tf` to utilize the `transformPointCloud` function. Import various message types from `sensor_msgs`.
  109. ```python
  110. self.pointcloud2_sub = rospy.Subscriber("/camera/depth/color/points", PointCloud2, self.callback_pcl2, queue_size=1)
  111. ```
  112. Set up a subscriber. We're going to subscribe to the topic `/camera/depth/color/points`, looking for `PointCloud2` message. When a message comes in, ROS is going to pass it to the function `callback_pcl2()` automatically.
  113. ```python
  114. self.pointcloud_pub = rospy.Publisher("/camera_cloud", PointCloud, queue_size=1)
  115. ```
  116. This section of code defines the talker's interface to the rest of ROS. `self.pointcloud_pub = rospy.Publisher("/camera_cloud", PointCloud, queue_size=1)` declares that your node is publishing to the `/camera_cloud` topic using the message type `PointCloud`.
  117. ```python
  118. self.pcl2_cloud = None
  119. self.listener = tf.TransformListener(True, rospy.Duration(10.0))
  120. ```
  121. The first line of code initializes `self.pcl2_cloud` to store the `PointCloud2` message. The second line creates a `tf.TransformListener` object. Once the listener is created, it starts receiving tf transformations and buffers them for up to 10 seconds.
  122. ```python
  123. def callback_pcl2(self,msg):
  124. """
  125. Callback function that stores the PointCloud2 message.
  126. :param self: The self reference.
  127. :param msg: The PointCloud2 message type.
  128. """
  129. self.pcl2_cloud = msg
  130. ```
  131. The callback function then stores the `PointCloud2` message.
  132. ```python
  133. temp_cloud = PointCloud()
  134. temp_cloud.header = self.pcl2_cloud.header
  135. ```
  136. Create a `PointCloud` for temporary use. Set the temporary PointCloud header to the stored `PointCloud2` header.
  137. ```python
  138. for data in pc2.read_points(self.pcl2_cloud, skip_nans=True):
  139. temp_cloud.points.append(Point32(data[0],data[1],data[2]))
  140. ```
  141. Use a for loop to extract `PointCloud2` data into a list of x, y, and z points and append those values to the `PointCloud` message, `temp_cloud`.
  142. ```python
  143. transformed_cloud = self.transform_pointcloud(temp_cloud)
  144. ```
  145. Utilize the `transform_pointcloud` function to transform the points in the `PointCloud` message to reference the `base_link`
  146. ```python
  147. while not rospy.is_shutdown():
  148. try:
  149. new_cloud = self.listener.transformPointCloud("base_link" ,msg)
  150. return new_cloud
  151. if new_cloud:
  152. break
  153. except (tf.LookupException, tf.ConnectivityException,tf.ExtrapolationException):
  154. pass
  155. ```
  156. Try to look up and transform the `PointCloud` input. Use a try-except block, since it may fail on any single call, due to internal timing issues in the transform publishers. Transform the point cloud data from `camera_color_optical_frame` to `base_link` with the `transformPointCloud()` function.
  157. ```python
  158. self.pointcloud_pub.publish(transformed_cloud)
  159. ```
  160. Publish the new transformed `PointCloud`.
  161. ```python
  162. rospy.init_node('pointcloud_transformer',anonymous=True)
  163. PCT = PointCloudTransformer()
  164. ```
  165. The next line, `rospy.init_node(NAME, ...)`, is very important as it tells rospy the name of your node -- until rospy has this information, it cannot start communicating with the ROS Master.
  166. !!! note
  167. The name must be a base name, i.e. it cannot contain any slashes "/".
  168. Declare a `PointCloudTransformer` object.
  169. ```python
  170. rate = rospy.Rate(1)
  171. rospy.sleep(1)
  172. ```
  173. The first line gives the listener some time to accumulate transforms. The second line is the rate at which the node is going to publish information (1 Hz).
  174. ```python
  175. while not rospy.is_shutdown():
  176. PCT.pcl_transformer()
  177. rate.sleep()
  178. ```
  179. Run a while loop until the node is shut down. Within the while loop run the `pcl_transformer()` method.