Part 6 Object Detection Node (Complete)¶
Here's a full example of the object_detection.py node that you should have developed during Part 6 Exercise 2. Also included here is an illustration of how to use the cv2.circle() method to create a marker on an image illustrating the centroid of the detected feature, as discussed here.
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
import cv2
from cv_bridge import CvBridge, CvBridgeError
from sensor_msgs.msg import Image
from pathlib import Path
class ObjectDetection(Node):
def __init__(self):
super().__init__("object_detection")
self.camera_sub = self.create_subscription(
msg_type=Image,
topic="/camera/image_raw",
callback=self.camera_callback,
qos_profile=10
)
self.waiting_for_image = True
def camera_callback(self, img_data):
cvbridge_interface = CvBridge()
try:
cv_img = cvbridge_interface.imgmsg_to_cv2(
img_data, desired_encoding="bgr8"
)
except CvBridgeError as e:
self.get_logger().warning(f"{e}")
if self.waiting_for_image:
height, width, channels = cv_img.shape
self.get_logger().info(
f"Obtained an image of height {height}px and width {width}px."
)
self.show_image(img=cv_img, img_name="step1_original")
crop_width = width - 400
crop_height = 400
crop_y0 = int((width / 2) - (crop_width / 2))
crop_z0 = int((height / 2) - (crop_height / 2))
cropped_img = cv_img[
crop_z0:crop_z0+crop_height,
crop_y0:crop_y0+crop_width
]
self.show_image(img=cropped_img, img_name="step2_cropping")
hsv_img = cv2.cvtColor(cropped_img, cv2.COLOR_BGR2HSV)
lower_threshold = (115, 225, 100)
upper_threshold = (130, 255, 255)
img_mask = cv2.inRange(hsv_img, lower_threshold, upper_threshold)
self.show_image(img=img_mask, img_name="step3_image_mask")
filtered_img = cv2.bitwise_and(cropped_img, cropped_img, mask = img_mask)
# Finding the Image Centroid: (1)
m = cv2.moments(img_mask) # (2)!
cy = m['m10'] / (m['m00'] + 1e-5)
cz = m['m01'] / (m['m00'] + 1e-5) # (3)!
cv2.circle(
filtered_img,
(int(cy), int(cz)),
10, (0, 0, 255), 2
) # (4)!
self.show_image(img=filtered_img, img_name="step4_filtered_image")
self.waiting_for_image = False
cv2.destroyAllWindows()
def show_image(self, img, img_name, save_img=True):
self.get_logger().info("Opening the image in a new window...")
cv2.imshow(img_name, img)
if save_img:
self.save_image(img, img_name)
self.get_logger().info(
"IMPORTANT: Close the image pop-up window to exit."
)
cv2.waitKey(0)
def save_image(self, img, img_name):
self.get_logger().info(f"Saving the image...")
base_image_path = Path.home().joinpath("myrosdata/object_detection/")
base_image_path.mkdir(parents=True, exist_ok=True)
full_image_path = base_image_path.joinpath(
f"{img_name}.jpg")
cv2.imwrite(str(full_image_path), img)
self.get_logger().info(
f"\nSaved an image to '{full_image_path}'\n"
f" - image dims: {img.shape[0]}x{img.shape[1]}px\n"
f" - file size: {full_image_path.stat().st_size} bytes"
)
def main(args=None):
rclpy.init(args=args)
node = ObjectDetection()
while node.waiting_for_image:
rclpy.spin_once(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
-
Everything here should be familiar to you from earlier in this exercise, except for this section...
-
Here, we obtain the moments of our colour blob by providing the boolean representation of it (i.e. the
img_mask) to thecv2.moments()function. -
Then, we are determining where the central point of this colour blob is located by calculating the
cyandczcoordinates of it. This provides us with pixel coordinates relative to the top left-hand corner of the image. -
Finally, this function allows us to draw a circle on our image at the centroid location so that we can visualise it. Into this function we pass:
- The image that we want the circle to be drawn on. In this case:
filtered_img. - The location that we want the circle to be placed, specifying the horizontal and vertical pixel coordinates respectively:
(int(cy), int(cz)). - How big we want the circle to be: here we specify a radius of 10 pixels.
- The colour of the circle, specifying this using a Blue-Green-Red colour space:
(0, 0, 255)(i.e.: pure red in this case) - Finally, the thickness of the line that will be used to draw the circle, in pixels.
- The image that we want the circle to be drawn on. In this case: