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.

216 lines
8.6 KiB

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/melodic/api/sensor_msgs/html/msg/PointCloud2.html) message type, then transform the PointCloud's reference link to a different frame. The data published by the 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
  5. # Terminal 1
  6. roslaunch stretch_core stretch_driver.launch
  7. ```
  8. 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.
  9. ```bash
  10. # Terminal 2
  11. roslaunch stretch_core d435i_low_resolution.launch
  12. ```
  13. Then run the [pointCloud_transformer.py](https://github.com/hello-robot/stretch_tutorials/blob/main/src/pointcloud_transformer.py) node.
  14. ```bash
  15. # Terminal 3
  16. cd catkin_ws/src/stretch_tutorials/src/
  17. python pointcloud_transformer.py
  18. ```
  19. Within this tutorial package, there is an [RViz config file](https://github.com/hello-robot/stretch_tutorials/blob/main/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.
  20. ```bash
  21. # Terminal 4
  22. rosrun rviz rviz -d /home/hello-robot/catkin_ws/src/stretch_tutorials/rviz/PointCloud_transformer_example.rviz
  23. ```
  24. The gif below visualizes what happens when running the previous node.
  25. <p align="center">
  26. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/main/images/PointCloud_transformer.gif"/>
  27. </p>
  28. ### The Code
  29. ```python
  30. #!/usr/bin/env python
  31. import rospy
  32. import tf
  33. import sensor_msgs.point_cloud2 as pc2
  34. from sensor_msgs.msg import PointCloud2, PointCloud
  35. from geometry_msgs.msg import Point32
  36. from std_msgs.msg import Header
  37. class PointCloudTransformer:
  38. """
  39. A class that takes in a PointCloud2 message and stores its points into a
  40. PointCloud message. Then that PointCloud is transformed to reference the
  41. 'base_link' frame.
  42. """
  43. def __init__(self):
  44. """
  45. Function that initializes the subscriber, publisher, and other variables.
  46. :param self: The self reference.
  47. """
  48. self.pointcloud2_sub = rospy.Subscriber("/camera/depth/color/points", PointCloud2, self.callback_pcl2, queue_size=1)
  49. self.pointcloud_pub = rospy.Publisher("/camera_cloud", PointCloud, queue_size=1)
  50. self.pcl2_cloud = None
  51. self.listener = tf.TransformListener(True, rospy.Duration(10.0))
  52. rospy.loginfo('Publishing transformed PointCloud. Use RViz to visualize')
  53. def callback_pcl2(self,msg):
  54. """
  55. Callback function that stores the PointCloud2 message.
  56. :param self: The self reference.
  57. :param msg: The PointCloud2 message type.
  58. """
  59. self.pcl2_cloud = msg
  60. def pcl_transformer(self):
  61. """
  62. A function that extracts the points from the stored PointCloud2 message
  63. and appends those points to a PointCloud message. Then the function transforms
  64. the PointCloud from its the header frame id, 'camera_color_optical_frame'
  65. to the 'base_link' frame.
  66. :param self: The self reference.
  67. """
  68. temp_cloud = PointCloud()
  69. temp_cloud.header = self.pcl2_cloud.header
  70. for data in pc2.read_points(self.pcl2_cloud, skip_nans=True):
  71. temp_cloud.points.append(Point32(data[0],data[1],data[2]))
  72. transformed_cloud = self.transform_pointcloud(temp_cloud)
  73. self.pointcloud_pub.publish(transformed_cloud)
  74. def transform_pointcloud(self,msg):
  75. """
  76. Function that stores the PointCloud2 message.
  77. :param self: The self reference.
  78. :param msg: The PointCloud message.
  79. :returns new_cloud: The transformed PointCloud message.
  80. """
  81. while not rospy.is_shutdown():
  82. try:
  83. new_cloud = self.listener.transformPointCloud("base_link" ,msg)
  84. return new_cloud
  85. if new_cloud:
  86. break
  87. except (tf.LookupException, tf.ConnectivityException,tf.ExtrapolationException):
  88. pass
  89. if __name__=="__main__":
  90. rospy.init_node('pointcloud_transformer',anonymous=True)
  91. PCT = PointCloudTransformer()
  92. rate = rospy.Rate(1)
  93. rospy.sleep(1)
  94. while not rospy.is_shutdown():
  95. PCT.pcl_transformer()
  96. rate.sleep()
  97. ```
  98. ### The Code Explained
  99. Now let's break the code down.
  100. ```python
  101. #!/usr/bin/env python
  102. ```
  103. 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 Python script.
  104. ```python
  105. import rospy
  106. import tf
  107. import sensor_msgs.point_cloud2 as pc2
  108. from sensor_msgs.msg import PointCloud2, PointCloud
  109. from geometry_msgs.msg import Point32
  110. from std_msgs.msg import Header
  111. ```
  112. 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 the message types from `sensor_msgs`.
  113. ```python
  114. self.pointcloud2_sub = rospy.Subscriber("/camera/depth/color/points", PointCloud2, self.callback_pcl2, queue_size=1)
  115. ```
  116. 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.
  117. ```python
  118. self.pointcloud_pub = rospy.Publisher("/camera_cloud", PointCloud, queue_size=1)
  119. ```
  120. 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`.
  121. ```python
  122. self.pcl2_cloud = None
  123. self.listener = tf.TransformListener(True, rospy.Duration(10.0))
  124. ```
  125. 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 over the wire, and buffers them for up to 10 seconds.
  126. ```python
  127. def callback_pcl2(self,msg):
  128. """
  129. Callback function that stores the PointCloud2 message.
  130. :param self: The self reference.
  131. :param msg: The PointCloud2 message type.
  132. """
  133. self.pcl2_cloud = msg
  134. ```
  135. The callback function that stores the the `PointCloud2` message.
  136. ```python
  137. temp_cloud = PointCloud()
  138. temp_cloud.header = self.pcl2_cloud.header
  139. ```
  140. Create a `PointCloud` for temporary use. Set the temporary PointCloud's header to the stored `PointCloud2` header.
  141. ```python
  142. for data in pc2.read_points(self.pcl2_cloud, skip_nans=True):
  143. temp_cloud.points.append(Point32(data[0],data[1],data[2]))
  144. ```
  145. Use a for loop to extract `PointCloud2` data into a list of x, y, z points and append those values to the `PointCloud` message, *temp_cloud*.
  146. ```python
  147. transformed_cloud = self.transform_pointcloud(temp_cloud)
  148. ```
  149. Utilize the `transform_pointcloud` function to transform the points in the `PointCloud` message to reference the *base_link*
  150. ```python
  151. while not rospy.is_shutdown():
  152. try:
  153. new_cloud = self.listener.transformPointCloud("base_link" ,msg)
  154. return new_cloud
  155. if new_cloud:
  156. break
  157. except (tf.LookupException, tf.ConnectivityException,tf.ExtrapolationException):
  158. pass
  159. ```
  160. 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.
  161. ```python
  162. self.pointcloud_pub.publish(transformed_cloud)
  163. ```
  164. Publish the new transformed `PointCloud`.
  165. ```python
  166. rospy.init_node('pointcloud_transformer',anonymous=True)
  167. PCT = PointCloudTransformer()
  168. ```
  169. 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. **NOTE:** the name must be a base name, i.e. it cannot contain any slashes "/".
  170. Declare a `PointCloudTransformer` object.
  171. ```python
  172. rate = rospy.Rate(1)
  173. rospy.sleep(1)
  174. ```
  175. The first line gives the listener some time to accumulate transforms. The second line is the rate the node is going to publish information (1 Hz).
  176. ```python
  177. while not rospy.is_shutdown():
  178. PCT.pcl_transformer()
  179. rate.sleep()
  180. ```
  181. Run a while loop until the node is shutdown. Within the while loop run the `pcl_transformer()` method.