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.

277 lines
9.8 KiB

  1. # Capture Image
  2. ## Example 7
  3. In this example, we will review a Python script that captures an image from the [RealSense camera](https://www.intelrealsense.com/depth-camera-d435i/).
  4. <p align="center">
  5. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/noetic/images/camera_image.jpeg"/>
  6. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/noetic/images/camera_image_edge_detection.jpeg"/>
  7. </p>
  8. Begin by running the stretch `driver.launch` file.
  9. ```{.bash .shell-prompt}
  10. ros2 launch stretch_core stretch_driver.launch.py
  11. ```
  12. 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.
  13. ```{.bash .shell-prompt}
  14. ros2 launch stretch_core d435i_low_resolution.launch.py
  15. ```
  16. Within this tutorial package, there is an RViz config file with the topics for perception already in the Display tree. You can visualize these topics and the robot model by running the command below in a new terminal.
  17. ```{.bash .shell-prompt}
  18. ros2 run rviz2 rviz2 -d /home/hello-robot/ament_ws/src/stretch_tutorials/rviz/perception_example.rviz
  19. ```
  20. ## Capture Image with Python Script
  21. In this section, we will use a Python node to capture an image from the [RealSense camera](https://www.intelrealsense.com/depth-camera-d435i/). Execute the [capture_image.py](https://github.com/hello-robot/stretch_tutorials/blob/humble/stretch_ros_tutorials/capture_image.py) node to save a .jpeg image of the image topic `/camera/color/image_raw`. In a terminal, execute:
  22. ```{.bash .shell-prompt}
  23. cd ~/ament_ws/src/stretch_tutorials/stretch_ros_tutorials
  24. python3 capture_image.py
  25. ```
  26. An image named **camera_image.jpeg** is saved in the **stored_data** folder in this package, if you don't have this folder you can create it yourself.
  27. ### The Code
  28. ```python
  29. #!/usr/bin/env python3
  30. import rclpy
  31. import sys
  32. import os
  33. import cv2
  34. from rclpy.node import Node
  35. from sensor_msgs.msg import Image
  36. from cv_bridge import CvBridge, CvBridgeError
  37. class CaptureImage(Node):
  38. """
  39. A class that converts a subscribed ROS image to a OpenCV image and saves
  40. the captured image to a predefined directory.
  41. """
  42. def __init__(self):
  43. """
  44. A function that initializes a CvBridge class, subscriber, and save path.
  45. :param self: The self reference.
  46. """
  47. super().__init__('stretch_capture_image')
  48. self.bridge = CvBridge()
  49. self.sub = self.create_subscription(Image, '/camera/color/image_raw', self.image_callback, 10)
  50. self.save_path = '/home/hello-robot/ament_ws/src/stretch_tutorials/stored_data'
  51. self.br = CvBridge()
  52. def image_callback(self, msg):
  53. """
  54. A callback function that converts the ROS image to a CV2 image and stores the
  55. image.
  56. :param self: The self reference.
  57. :param msg: The ROS image message type.
  58. """
  59. try:
  60. image = self.bridge.imgmsg_to_cv2(msg, 'bgr8')
  61. except CvBridgeError as e:
  62. self.get_logger().warn('CV Bridge error: {0}'.format(e))
  63. file_name = 'camera_image.jpeg'
  64. completeName = os.path.join(self.save_path, file_name)
  65. cv2.imwrite(completeName, image)
  66. rclpy.shutdown()
  67. sys.exit(0)
  68. def main(args=None):
  69. rclpy.init(args=args)
  70. capture_image = CaptureImage()
  71. rclpy.spin(capture_image)
  72. if __name__ == '__main__':
  73. main()
  74. ```
  75. ### The Code Explained
  76. Now let's break the code down.
  77. ```python
  78. #!/usr/bin/env python3
  79. ```
  80. Every Python ROS [Node](http://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Nodes/Understanding-ROS2-Nodes.html) will have this declaration at the top. The first line makes sure your script is executed as a Python3 script.
  81. ```python
  82. import rclpy
  83. import sys
  84. import os
  85. import cv2
  86. ```
  87. You need to import `rclpy` if you are writing a ROS [Node](http://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Nodes/Understanding-ROS2-Nodes.html). There are functions from `sys`, `os`, and `cv2` that are required within this code. `cv2` is a library of Python functions that implements computer vision algorithms. Further information about cv2 can be found here: [OpenCV Python](https://www.geeksforgeeks.org/opencv-python-tutorial/).
  88. ```python
  89. from rclpy.node import Node
  90. from sensor_msgs.msg import Image
  91. from cv_bridge import CvBridge, CvBridgeError
  92. ```
  93. The `sensor_msgs.msg` is imported so that we can subscribe to ROS `Image` messages. Import [CvBridge](http://wiki.ros.org/cv_bridge) to convert between ROS `Image` messages and OpenCV images and the `Node` is neccesary to create a node in ROS2.
  94. ```python
  95. def __init__(self):
  96. """
  97. A function that initializes a CvBridge class, subscriber, and save path.
  98. :param self: The self reference.
  99. """
  100. super().__init__('stretch_capture_image')
  101. self.bridge = CvBridge()
  102. self.sub = self.create_subscription(Image, '/camera/color/image_raw', self.image_callback, 10)
  103. self.save_path = '/home/hello-robot/ament_ws/src/stretch_tutorials/stored_data'
  104. self.br = CvBridge()
  105. ```
  106. Initialize the node, CvBridge class, the subscriber, and the directory where the captured image will be stored.
  107. ```python
  108. def image_callback(self, msg):
  109. """
  110. A callback function that converts the ROS image to a cv2 image and stores the
  111. image.
  112. :param self: The self reference.
  113. :param msg: The ROS image message type.
  114. """
  115. try:
  116. image = self.bridge.imgmsg_to_cv2(msg, 'bgr8')
  117. except CvBridgeError as e:
  118. rospy.logwarn('CV Bridge error: {0}'.format(e))
  119. ```
  120. Try to convert the ROS Image message to a cv2 Image message using the `imgmsg_to_cv2()` function.
  121. ```python
  122. file_name = 'camera_image.jpeg'
  123. completeName = os.path.join(self.save_path, file_name)
  124. cv2.imwrite(completeName, image)
  125. ```
  126. Join the directory and file name using the `path.join()` function. Then use the `imwrite()` function to save the image.
  127. ```python
  128. rclpy.shutdown()
  129. sys.exit(0)
  130. ```
  131. The first line of code initiates a clean shutdown of ROS. The second line of code exits the Python interpreter.
  132. ```python
  133. rclpy.init(args=args)
  134. capture_image = CaptureImage()
  135. ```
  136. The next line, rclpy.init_node initializes the node. In this case, your node will take on the name 'stretch_capture_image'. Also setup CaptureImage class with `capture_image = CaptureImage()`.
  137. !!! note
  138. The name must be a base name, i.e. it cannot contain any slashes "/".
  139. ```python
  140. rclpy.spin(capture_image)
  141. ```
  142. Give control to ROS. This will allow the callback to be called whenever new messages come in. If we don't put this line in, then the node will not work, and ROS will not process any messages.
  143. ## Edge Detection
  144. In this section, we highlight a node that utilizes the [Canny Edge filter](https://www.geeksforgeeks.org/python-opencv-canny-function/) algorithm to detect the edges from an image and convert it back as a ROS image to be visualized in RViz. In a terminal, execute:
  145. ```{.bash .shell-prompt}
  146. cd ~/ament_ws/src/stretch_tutorials/stretch_ros_tutorials
  147. python3 edge_detection.py
  148. ```
  149. The node will publish a new Image topic named `/image_edge_detection`. This can be visualized in RViz and a gif is provided below for reference.
  150. <p align="center">
  151. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/noetic/images/camera_image_edge_detection.gif"/>
  152. </p>
  153. ### The Code
  154. ```python
  155. #!/usr/bin/env python3
  156. import rclpy
  157. from rclpy.node import Node
  158. import cv2
  159. from sensor_msgs.msg import Image
  160. from cv_bridge import CvBridge, CvBridgeError
  161. class EdgeDetection(Node):
  162. """
  163. A class that converts a subscribed ROS image to a OpenCV image and saves
  164. the captured image to a predefined directory.
  165. """
  166. def __init__(self):
  167. """
  168. A function that initializes a CvBridge class, subscriber, and other
  169. parameter values.
  170. :param self: The self reference.
  171. """
  172. super().__init__('stretch_edge_detection')
  173. self.bridge = CvBridge()
  174. self.sub = self.create_subscription(Image, '/camera/color/image_raw', self.callback, 1)
  175. self.pub = self.create_publisher(Image, '/image_edge_detection', 1)
  176. self.save_path = '/home/hello-robot/catkin_ws/src/stretch_tutorials/stored_data'
  177. self.lower_thres = 100
  178. self.upper_thres = 200
  179. self.get_logger().info("Publishing the CV2 Image. Use RViz to visualize.")
  180. def callback(self, msg):
  181. """
  182. A callback function that converts the ROS image to a CV2 image and goes
  183. through the Canny Edge filter in OpenCV for edge detection. Then publishes
  184. that filtered image to be visualized in RViz.
  185. :param self: The self reference.
  186. :param msg: The ROS image message type.
  187. """
  188. try:
  189. image = self.bridge.imgmsg_to_cv2(msg, 'bgr8')
  190. except CvBridgeError as e:
  191. self.get_logger().warn('CV Bridge error: {0}'.format(e))
  192. image = cv2.Canny(image, self.lower_thres, self.upper_thres)
  193. image_msg = self.bridge.cv2_to_imgmsg(image, 'passthrough')
  194. image_msg.header = msg.header
  195. self.pub.publish(image_msg)
  196. def main(args=None):
  197. rclpy.init(args=args)
  198. edge_detection = EdgeDetection()
  199. rclpy.spin(edge_detection)
  200. if __name__ == '__main__':
  201. main()
  202. ```
  203. ### The Code Explained
  204. Since there are similarities in the capture image node, we will only break down the different components of the edge detection node.
  205. Define the lower and upper bounds of the Hysteresis Thresholds.
  206. ```python
  207. image = cv2.Canny(image, self.lower_thres, self.upper_thres)
  208. ```
  209. Run the Canny Edge function to detect edges from the cv2 image.
  210. ```python
  211. image_msg = self.bridge.cv2_to_imgmsg(image, 'passthrough')
  212. ```
  213. Convert the cv2 image back to a ROS image so it can be published.
  214. ```python
  215. image_msg.header = msg.header
  216. self.pub.publish(image_msg)
  217. ```
  218. Publish the ROS image with the same header as the subscribed ROS message.