-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Phase2 (b): Perception for TurtleBot 4 Open CV and MediaPipe: 3D Objects #12
base: main
Are you sure you want to change the base?
Changes from 11 commits
f2ede37
114a01f
88c068e
850f2ba
4f41ea1
a24f5f2
9621045
775264f
8b2f454
25ac127
cfd013e
cf190bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from launch import LaunchDescription | ||
from launch_ros.actions import Node | ||
|
||
def generate_launch_description(): | ||
return LaunchDescription([ | ||
Node( | ||
package='unit02_simulation_motion_planner', | ||
namespace='tb4_2D_perception', | ||
executable='tb4_2D_perception', | ||
name='turtlebot4_2D_perception' | ||
) | ||
]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import rclpy | ||
from rclpy.node import Node | ||
from sensor_msgs.msg import Image | ||
from cv_bridge import CvBridge | ||
import cv2 # OpenCV library | ||
import numpy as np | ||
from matplotlib import pyplot as plt | ||
|
||
class TurtlePerception(Node): | ||
""" | ||
- TurtlePerception class inherits from (or is a subclass of) Node attributes as a ROS Node | ||
that acts as a primary entrypoint in the ROS system for communication. It can be used to | ||
create ROS entities such as publishers, subscribers, services, etc. | ||
- This class is an implementation of the 2D object detection using classical openCV. | ||
Here, we use inbuilt opencv methods such as | ||
- cv2.findContours(): to retrieve contours from the binary image | ||
- cv2.approxPloyDP(): to approximate the shape detected | ||
- cv2.drawContours(): to draw bounding boxes for detected objects | ||
|
||
Attributes: | ||
Node: Is a class from rclpy.node.Node | ||
|
||
More Information at: https://docs.ros2.org/latest/api/rclpy/api/node.html | ||
|
||
Topics: | ||
- Publishers: None | ||
- Subscribers: | ||
- Topic name: /color/image | ||
- Topic type: sensor_msgs.msg/Image | ||
- Topic desciption: To subcribe image frames from OKA-D camera sensor of the TB4 | ||
""" | ||
def __init__(self): | ||
""" | ||
TurtlePerception class constructor to initialize nodes, | ||
subscribers, publishers and parameters | ||
""" | ||
super().__init__('tb4_perception') | ||
|
||
# subscriber to receive an Image from the /color/image topic. | ||
self.subscription = self.create_subscription(Image,'/color/image', | ||
self.perception_callback, 10) | ||
|
||
# Initialize bridge between ROS2 and OpenCV | ||
self.bridge = CvBridge() | ||
|
||
def perception_callback(self, frames_data): | ||
""" | ||
TurtlePerception class constructor to initialize nodes, | ||
subscribers, publishers and parameters | ||
|
||
Args: frames_data | ||
""" | ||
self.get_logger().info('Receiving Turtlebot4 visual frames_data') | ||
|
||
# current frame | ||
current_frame = self.bridge.imgmsg_to_cv2(frames_data) | ||
|
||
# Poping each and every frame | ||
cv2.imshow("TurtleBot4 Camera View", current_frame) | ||
|
||
process_frames = current_frame.copy() | ||
|
||
# converting image into grayscale image | ||
gray = cv2.cvtColor(process_frames, cv2.COLOR_BGR2GRAY) | ||
|
||
# setting threshold of gray image | ||
_, threshold = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) | ||
|
||
# using a findContours() function | ||
|
||
# The function retrieves contours from the binary image using the algorithm | ||
# # (Satoshi Suzuki and others.Topological structural analysis of digitized binary images | ||
# by border following Computer Vision, Graphics, and Image Processing, 30(1):32–46, 1985.) | ||
# The contours are a useful tool for shape analysis and object detection and recognition. | ||
|
||
contours, _ = cv2.findContours( | ||
threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) | ||
|
||
i = 0 | ||
|
||
# list for storing names of shapes | ||
for contour in contours: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're going to want to cap the number of contours you process here. You can get in a degenerate situation where you have a large number of "blobs". I would make that number a node parameter and only highlight the n-most largest blobs by area. |
||
|
||
# here we are ignoring first counter because | ||
# findcontour function detects whole image as shape | ||
if i == 0: | ||
i = 1 | ||
continue | ||
|
||
# cv2.approxPloyDP() function to approximate the shape | ||
approx = cv2.approxPolyDP( | ||
contour, 0.01 * cv2.arcLength(contour, True), True) | ||
|
||
# using drawContours() function | ||
cv2.drawContours(process_frames, [contour], 0, (0, 0, 255), 5) | ||
|
||
# finding center point of shape | ||
cv_moments = cv2.moments(contour) | ||
if cv_moments['m00'] != 0.0: | ||
x_point = int(cv_moments['m10']/cv_moments['m00']) | ||
y_point = int(cv_moments['m01']/cv_moments['m00']) | ||
|
||
# putting shape name at center of each shape | ||
if len(approx) == 3: | ||
cv2.putText(process_frames, 'Triangle', (x_point, y_point), | ||
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2) | ||
|
||
elif len(approx) == 4: | ||
cv2.putText(process_frames, 'Quadrilateral', (x_point, y_point), | ||
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2) | ||
|
||
elif len(approx) == 5: | ||
cv2.putText(process_frames, 'Pentagon', (x_point, y_point), | ||
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2) | ||
|
||
elif len(approx) == 6: | ||
cv2.putText(process_frames, 'Hexagon', (x_point, y_point), | ||
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2) | ||
|
||
else: | ||
cv2.putText(process_frames, 'circle', (x_point, y_point), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Screams in math. |
||
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2) | ||
|
||
# displaying the image after drawing contours | ||
cv2.imshow('Turtlebot 4 Simple Object Detection', process_frames) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like I said, I would publish this as a topic. |
||
cv2.waitKey(1) | ||
|
||
def main(args=None): | ||
""" | ||
Main method to instantiate ROS nodes and TurtlePerception class to publish image | ||
processed object detection | ||
|
||
Args: | ||
None | ||
""" | ||
rclpy.init(args=args) | ||
turtle_perception = TurtlePerception() | ||
rclpy.spin(turtle_perception) | ||
turtle_perception.destroy_node() | ||
rclpy.shutdown() | ||
|
||
if __name__ == '__main__': | ||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import rclpy | ||
from rclpy.node import Node | ||
from sensor_msgs.msg import Image | ||
from cv_bridge import CvBridge, CvBridgeError | ||
import cv2 # OpenCV library | ||
import mediapipe as mp | ||
import numpy as np | ||
from matplotlib import pyplot as plt | ||
import time | ||
|
||
class TurtlePerception3D(Node): | ||
""" | ||
- TurtlePerception3D class inherits from (or is a subclass of) Node attributes as a ROS Node | ||
that acts as a primary entrypoint in the ROS system for communication. It can be used to | ||
create ROS entities such as publishers, subscribers, services, etc. | ||
- This class is an implementation of the 3D object detection using openCV and MediaPipe. | ||
Here, we use inbuilt opencv methods such as | ||
- TODO | ||
|
||
|
||
Attributes: | ||
Node: Is a class from rclpy.node.Node | ||
|
||
More Information at: https://docs.ros2.org/latest/api/rclpy/api/node.html | ||
|
||
Topics: | ||
- Publishers: None | ||
- Subscribers: | ||
- Topic name: /color/image | ||
- Topic type: sensor_msgs.msg/Image | ||
- Topic desciption: To subcribe image frames from OKA-D camera sensor of the TB4 | ||
""" | ||
def __init__(self): | ||
""" | ||
TurtlePerception3D class constructor to initialize nodes, | ||
subscribers, publishers and parameters | ||
""" | ||
super().__init__('tb4_perception_3d') | ||
|
||
# subscriber to receive an Image from the /color/image topic. | ||
self.subscription = self.create_subscription(Image,'/color/image', | ||
self.perception_callback, 10) | ||
|
||
# Initialize bridge between ROS2 and OpenCV | ||
self.bridge = CvBridge() | ||
self.mp_objectron = mp.solutions.objectron | ||
self.mp_drawing = mp.solutions.drawing_utils | ||
|
||
def perception_callback(self, frames_data): | ||
""" | ||
TurtlePerception3D class constructor to initialize nodes, | ||
subscribers, publishers and parameters | ||
|
||
Args: frames_data | ||
""" | ||
try: | ||
self.get_logger().info('Receiving Turtlebot4 visual frames_data') | ||
|
||
# current frame | ||
current_frame = self.bridge.imgmsg_to_cv2(frames_data, desired_encoding="bgr8") | ||
|
||
resized_image = cv2.resize(current_frame, (720, 480)) | ||
|
||
# Poping each and every frame | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See previous comment. |
||
cv2.imshow("TurtleBot4 Camera View", resized_image) | ||
|
||
process_frames = resized_image.copy() | ||
|
||
with self.mp_objectron.Objectron(static_image_mode=False, | ||
max_num_objects=2, | ||
min_detection_confidence=0.5, | ||
min_tracking_confidence=0.8, | ||
model_name='Shoe') as objectron: | ||
|
||
start = time.time() | ||
|
||
#Convert the BGR image to RGB | ||
image = cv2.cvtColor(process_frames, cv2.COLOR_BGR2RGB) | ||
|
||
# Improving performace by marking the image as not writeable to pass by reference | ||
image.flags.writeable = False | ||
|
||
# 3D object detection | ||
detected_object_frame = objectron.process(image) | ||
|
||
image.flags.writeable = True | ||
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) | ||
|
||
if detected_object_frame.detected_objects: | ||
for detected_objects in detected_object_frame.detected_objects: | ||
self.mp_drawing.draw_landmarks(image, detected_objects.landmarks_2d, self.mp_objectron.BOX_CONNECTIONS) | ||
self.mp_drawing.draw_axis(image, detected_objects.rotation, detected_objects.translation) | ||
|
||
end = time.time() | ||
total_time = end - start | ||
|
||
fps = 1 / total_time | ||
cv2.putText(image, f'FPS: {int(fps)}', (20,70), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0,255,0), 2) | ||
# displaying the image after drawing contours | ||
cv2.imshow('Turtlebot 4 Simple Object Detection', image) | ||
cv2.waitKey(1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't do this. You shouldn't wait inside a node loop. Same goes for the other node. Just spit the data out to a topic. |
||
|
||
except CvBridgeError as e: | ||
print(e) | ||
|
||
def main(args=None): | ||
""" | ||
Main method to instantiate ROS nodes and TurtlePerception3D class to publish image | ||
processed object detection | ||
|
||
Args: | ||
None | ||
""" | ||
rclpy.init(args=args) | ||
turtle_perception_3d = TurtlePerception3D() | ||
rclpy.spin(turtle_perception_3d) | ||
turtle_perception_3d.destroy_node() | ||
rclpy.shutdown() | ||
|
||
if __name__ == '__main__': | ||
main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We really shouldn't do this. You should use RVIZ or RQT to view the image as a topic.