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.

282 lines
10 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
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 7
  2. In this example, we will review the [image_view](http://wiki.ros.org/image_view?distro=melodic) ROS package and a Python script that captures an image from the [RealSense camera](https://www.intelrealsense.com/depth-camera-d435i/).
  3. <p align="center">
  4. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/noetic/images/camera_image.jpeg"/>
  5. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/noetic/images/camera_image_edge_detection.jpeg"/>
  6. </p>
  7. Begin by running the stretch `driver.launch` file.
  8. ```{.bash .shell-prompt}
  9. roslaunch stretch_core stretch_driver.launch
  10. ```
  11. 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.
  12. ```{.bash .shell-prompt}
  13. roslaunch stretch_core d435i_low_resolution.launch
  14. ```
  15. 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.
  16. ```{.bash .shell-prompt}
  17. rosrun rviz rviz -d /home/hello-robot/catkin_ws/src/stretch_tutorials/rviz/perception_example.rviz
  18. ```
  19. ## Capture Image with image_view
  20. There are a couple of methods to save an image using the [image_view](http://wiki.ros.org/image_view) package.
  21. **OPTION 1:** Use the `image_view` node to open a simple image viewer for ROS *sensor_msgs/image* topics. In a new terminal, execute:
  22. ```{.bash .shell-prompt}
  23. rosrun image_view image_view image:=/camera/color/image_raw_upright_view
  24. ```
  25. Then you can save the current image by right-clicking on the display window. By default, images will be saved as frame000.jpg, frame000.jpg, etc. Note, that the image will be saved to the terminal's current work directory.
  26. **OPTION 2:** Use the `image_saver` node to save an image to the terminal's current work directory. In a new terminal, execute:
  27. ```{.bash .shell-prompt}
  28. rosrun image_view image_saver image:=/camera/color/image_raw_upright_view
  29. ```
  30. ## Capture Image with Python Script
  31. 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/noetic/src/capture_image.py) node to save a .jpeg image of the image topic `/camera/color/image_raw_upright_view`. In a terminal, execute:
  32. ```{.bash .shell-prompt}
  33. cd ~/catkin_ws/src/stretch_tutorials/src
  34. python3 capture_image.py
  35. ```
  36. An image named **camera_image.jpeg** is saved in the **stored_data** folder in this package.
  37. ### The Code
  38. ```python
  39. #!/usr/bin/env python3
  40. import rospy
  41. import sys
  42. import os
  43. import cv2
  44. from sensor_msgs.msg import Image
  45. from cv_bridge import CvBridge, CvBridgeError
  46. class CaptureImage:
  47. """
  48. A class that converts a subscribed ROS image to a OpenCV image and saves
  49. the captured image to a predefined directory.
  50. """
  51. def __init__(self):
  52. """
  53. A function that initializes a CvBridge class, subscriber, and save path.
  54. :param self: The self reference.
  55. """
  56. self.bridge = CvBridge()
  57. self.sub = rospy.Subscriber('/camera/color/image_raw', Image, self.callback, queue_size=1)
  58. self.save_path = '/home/hello-robot/catkin_ws/src/stretch_tutorials/stored_data'
  59. def callback(self, msg):
  60. """
  61. A callback function that converts the ROS image to a CV2 image and stores the
  62. image.
  63. :param self: The self reference.
  64. :param msg: The ROS image message type.
  65. """
  66. try:
  67. image = self.bridge.imgmsg_to_cv2(msg, 'bgr8')
  68. except CvBridgeError as e:
  69. rospy.logwarn('CV Bridge error: {0}'.format(e))
  70. file_name = 'camera_image.jpeg'
  71. completeName = os.path.join(self.save_path, file_name)
  72. cv2.imwrite(completeName, image)
  73. rospy.signal_shutdown("done")
  74. sys.exit(0)
  75. if __name__ == '__main__':
  76. rospy.init_node('capture_image', argv=sys.argv)
  77. CaptureImage()
  78. rospy.spin()
  79. ```
  80. ### The Code Explained
  81. Now let's break the code down.
  82. ```python
  83. #!/usr/bin/env python3
  84. ```
  85. 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.
  86. ```python
  87. import rospy
  88. import sys
  89. import os
  90. import cv2
  91. ```
  92. You need to import `rospy` if you are writing a ROS [Node](http://wiki.ros.org/Nodes). 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/).
  93. ```python
  94. from sensor_msgs.msg import Image
  95. from cv_bridge import CvBridge, CvBridgeError
  96. ```
  97. 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.
  98. ```python
  99. def __init__(self):
  100. """
  101. A function that initializes a CvBridge class, subscriber, and save path.
  102. :param self: The self reference.
  103. """
  104. self.bridge = CvBridge()
  105. self.sub = rospy.Subscriber('/camera/color/image_raw', Image, self.callback, queue_size=1)
  106. self.save_path = '/home/hello-robot/catkin_ws/src/stretch_tutorials/stored_data'
  107. ```
  108. Initialize the CvBridge class, the subscriber, and the directory where the captured image will be stored.
  109. ```python
  110. def callback(self, msg):
  111. """
  112. A callback function that converts the ROS image to a cv2 image and stores the
  113. image.
  114. :param self: The self reference.
  115. :param msg: The ROS image message type.
  116. """
  117. try:
  118. image = self.bridge.imgmsg_to_cv2(msg, 'bgr8')
  119. except CvBridgeError as e:
  120. rospy.logwarn('CV Bridge error: {0}'.format(e))
  121. ```
  122. Try to convert the ROS Image message to a cv2 Image message using the `imgmsg_to_cv2()` function.
  123. ```python
  124. file_name = 'camera_image.jpeg'
  125. completeName = os.path.join(self.save_path, file_name)
  126. cv2.imwrite(completeName, image)
  127. ```
  128. Join the directory and file name using the `path.join()` function. Then use the `imwrite()` function to save the image.
  129. ```python
  130. rospy.signal_shutdown("done")
  131. sys.exit(0)
  132. ```
  133. The first line of code initiates a clean shutdown of ROS. The second line of code exits the Python interpreter.
  134. ```python
  135. rospy.init_node('capture_image', argv=sys.argv)
  136. CaptureImage()
  137. ```
  138. 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.
  139. !!! note
  140. The name must be a base name, i.e. it cannot contain any slashes "/".
  141. Instantiate the `CaptureImage()` class.
  142. ```python
  143. rospy.spin()
  144. ```
  145. 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.
  146. ## Edge Detection
  147. 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:
  148. ```{.bash .shell-prompt}
  149. cd ~/catkin_ws/src/stretch_tutorials/src
  150. python3 edge_detection.py
  151. ```
  152. 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.
  153. <p align="center">
  154. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/noetic/images/camera_image_edge_detection.gif"/>
  155. </p>
  156. ### The Code
  157. ```python
  158. #!/usr/bin/env python3
  159. import rospy
  160. import sys
  161. import os
  162. import cv2
  163. from sensor_msgs.msg import Image
  164. from cv_bridge import CvBridge, CvBridgeError
  165. class EdgeDetection:
  166. """
  167. A class that converts a subscribed ROS image to a OpenCV image and saves
  168. the captured image to a predefined directory.
  169. """
  170. def __init__(self):
  171. """
  172. A function that initializes a CvBridge class, subscriber, and other
  173. parameter values.
  174. :param self: The self reference.
  175. """
  176. self.bridge = CvBridge()
  177. self.sub = rospy.Subscriber('/camera/color/image_raw', Image, self.callback, queue_size=1)
  178. self.pub = rospy.Publisher('/image_edge_detection', Image, queue_size=1)
  179. self.save_path = '/home/hello-robot/catkin_ws/src/stretch_tutorials/stored_data'
  180. self.lower_thres = 100
  181. self.upper_thres = 200
  182. rospy.loginfo("Publishing the CV2 Image. Use RViz to visualize.")
  183. def callback(self, msg):
  184. """
  185. A callback function that converts the ROS image to a CV2 image and goes
  186. through the Canny Edge filter in OpenCV for edge detection. Then publishes
  187. that filtered image to be visualized in RViz.
  188. :param self: The self reference.
  189. :param msg: The ROS image message type.
  190. """
  191. try:
  192. image = self.bridge.imgmsg_to_cv2(msg, 'bgr8')
  193. except CvBridgeError as e:
  194. rospy.logwarn('CV Bridge error: {0}'.format(e))
  195. image = cv2.Canny(image, self.lower_thres, self.upper_thres)
  196. image_msg = self.bridge.cv2_to_imgmsg(image, 'passthrough')
  197. image_msg.header = msg.header
  198. self.pub.publish(image_msg)
  199. if __name__ == '__main__':
  200. rospy.init_node('edge_detection', argv=sys.argv)
  201. EdgeDetection()
  202. rospy.spin()
  203. ```
  204. ### The Code Explained
  205. Since there are similarities in the capture image node, we will only break down the different components of the edge detection node.
  206. Define the lower and upper bounds of the Hysteresis Thresholds.
  207. ```python
  208. image = cv2.Canny(image, self.lower_thres, self.upper_thres)
  209. ```
  210. Run the Canny Edge function to detect edges from the cv2 image.
  211. ```python
  212. image_msg = self.bridge.cv2_to_imgmsg(image, 'passthrough')
  213. ```
  214. Convert the cv2 image back to a ROS image so it can be published.
  215. ```python
  216. image_msg.header = msg.header
  217. self.pub.publish(image_msg)
  218. ```
  219. Publish the ROS image with the same header as the subscribed ROS message.