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 rospy
from pathlib import Path
import cv2
from cv_bridge import CvBridge, CvBridgeError
from sensor_msgs.msg import Image
node_name = "object_detection"
rospy.init_node(node_name)
print(f"Launched the '{node_name}' node. Currently waiting for an image...")
rate = rospy.Rate(5)
base_image_path = Path.home().joinpath("myrosdata/object_detection/")
base_image_path.mkdir(parents=True, exist_ok=True)
cvbridge_interface = CvBridge()
waiting_for_image = True
def show_and_save_image(img, img_name):
full_image_path = base_image_path.joinpath(f"{img_name}.jpg")
print("Opening the image in a new window...")
cv2.imshow(img_name, img)
print(f"Saving the image to '{full_image_path}'...")
cv2.imwrite(str(full_image_path), img)
print(f"Saved 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")
print("**IMPORTANT: Close the image pop-up window to continue!**")
cv2.waitKey(0)
def camera_cb(img_data):
global waiting_for_image
try:
cv_img = cvbridge_interface.imgmsg_to_cv2(img_data, desired_encoding="bgr8")
except CvBridgeError as e:
print(e)
if waiting_for_image == True:
height, width, channels = cv_img.shape
print(f"Obtained an image of height {height}px and width {width}px.")
show_and_save_image(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]
show_and_save_image(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)
show_and_save_image(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)!
show_and_save_image(filtered_img, img_name = "step4_filtered_image")
waiting_for_image = False
rospy.Subscriber("/camera/rgb/image_raw", Image, camera_cb)
while waiting_for_image:
rate.sleep()
cv2.destroyAllWindows()
-
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: