Skip to content

Commit 5c66631

Browse files
committed
Display multiple video feeds and poses alongside robot info
1 parent 9d76722 commit 5c66631

File tree

1 file changed

+124
-49
lines changed

1 file changed

+124
-49
lines changed

turtlebot4_vision_tutorials/turtlebot4_vision_tutorials/pose_display.py

Lines changed: 124 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
# @author Hilary Luo (hluo@clearpathrobotics.com)
1818

1919
import cv2
20+
from functools import partial
2021
import numpy as np
2122

2223
from cv_bridge import CvBridge
@@ -25,7 +26,7 @@
2526
from rclpy.node import Node
2627
from rclpy.qos import qos_profile_sensor_data
2728
from std_msgs.msg import String as string_msg
28-
from sensor_msgs.msg import Image
29+
from sensor_msgs.msg import BatteryState, Image
2930

3031
from turtlebot4_vision_tutorials.MovenetDepthaiEdge import Body
3132

@@ -35,37 +36,74 @@
3536
[6, 12], [12, 11], [11, 5],
3637
[12, 14], [14, 16], [11, 13], [13, 15]]
3738

38-
39-
class PoseDetection(Node):
39+
class PoseDisplay(Node):
4040
lights_on_ = False
41-
frame = None
42-
body = Body()
41+
frame = [ None, None, None, None, None, None, None]
42+
body = [ None, None, None, None, None, None, None]
43+
percentage = [ None, None, None, None, None, None, None]
44+
4345

4446
def __init__(self):
4547
super().__init__('pose_display')
48+
self.declare_parameter('tile_x', 3)
49+
self.declare_parameter('tile_y', 2)
50+
self.declare_parameter('namespaces', ['tb11', 'tb12'])
51+
self.declare_parameter('image_height', 432)
52+
self.declare_parameter('image_width', 768)
53+
54+
self.tile_x = self.get_parameter('tile_x').get_parameter_value().integer_value
55+
self.tile_y = self.get_parameter('tile_y').get_parameter_value().integer_value
56+
self.namespaces = self.get_parameter('namespaces').get_parameter_value().string_array_value
57+
self.image_height = self.get_parameter('image_height').get_parameter_value().integer_value
58+
self.image_width = self.get_parameter('image_width').get_parameter_value().integer_value
59+
60+
self.full_frame = np.zeros((self.image_height*self.tile_y,
61+
self.image_width*self.tile_x, 3), np.uint8)
4662

4763
self.output = None
4864

49-
# Subscribe to the /semaphore_flags topic
50-
self.semaphore_flag_subscriber = self.create_subscription(
51-
string_msg,
52-
'semaphore_flag',
53-
self.semaphore_flag_callback,
54-
qos_profile_sensor_data)
55-
56-
# Subscribe to the ffmpeg_decoded topic
57-
self.body_pose_subscriber = self.create_subscription(
58-
PoseArray,
59-
'body_pose',
60-
self.body_pose_callback,
61-
qos_profile_sensor_data)
62-
63-
# Subscribe to the ffmpeg_decoded topic
64-
self.ffmpeg_subscriber = self.create_subscription(
65-
Image,
66-
'oakd/rgb/preview/ffmpeg_decoded',
67-
self.frame_callback,
68-
qos_profile_sensor_data)
65+
# # Subscribe to the /semaphore_flags topic
66+
# self.semaphore_flag_subscriber = self.create_subscription(
67+
# string_msg,
68+
# 'semaphore_flag',
69+
# self.semaphore_flag_callback,
70+
# qos_profile_sensor_data)
71+
72+
self.body_pose_subscribers = []
73+
self.ffmpeg_subscribers = []
74+
self.battery_subscribers = []
75+
76+
# Naming a window
77+
cv2.namedWindow("Movenet", cv2.WINDOW_NORMAL)
78+
79+
# Subscribe to the pose topics
80+
for i, ns in enumerate(self.namespaces):
81+
subscriber = self.create_subscription(
82+
PoseArray,
83+
f'/{ns}/body_pose',
84+
partial(self.body_pose_callback, num = i),
85+
qos_profile_sensor_data)
86+
self.body_pose_subscribers.append(subscriber)
87+
88+
# Subscribe to the ffmpeg_decoded topics
89+
subscriber = self.create_subscription(
90+
Image,
91+
f'/{ns}/oakd/rgb/preview/ffmpeg_decoded',
92+
partial(self.frame_callback, num = i),
93+
qos_profile_sensor_data)
94+
self.ffmpeg_subscribers.append(subscriber)
95+
96+
# Subscribe to the battery topics
97+
subscriber = self.create_subscription(
98+
BatteryState,
99+
f'/{ns}/battery_state',
100+
partial(self.battery_callback, num = i),
101+
qos_profile_sensor_data)
102+
self.battery_subscribers.append(subscriber)
103+
104+
105+
timer_period = 0.0833 # seconds
106+
self.timer = self.create_timer(timer_period, self.updateDisplay)
69107

70108
self.bridge = CvBridge()
71109

@@ -82,48 +120,83 @@ def semaphore_flag_callback(self, letter_msg: string_msg):
82120
(0, 190, 255),
83121
3)
84122

85-
def body_pose_callback(self, pose_msg: PoseArray):
86-
# self.get_logger().info('body_pose_callback')
123+
def body_pose_callback(self, pose_msg: PoseArray, num: int):
124+
self.get_logger().info(f'Body_pose_callback {num} - start')
87125
temp_keypoints = []
88126
temp_scores = []
89127
for i, point in enumerate(pose_msg.poses):
90128
temp_keypoints.append((int(point.position.x), int(point.position.y)))
91129
temp_scores.append(point.position.z)
92-
self.body.keypoints = np.array(temp_keypoints)
93-
self.body.scores = np.array(temp_scores)
94-
95-
def frame_callback(self, image_msg: Image):
96-
# self.get_logger().info('frame_callback')
130+
b = Body()
131+
b.keypoints = np.array(temp_keypoints)
132+
b.scores = np.array(temp_scores)
133+
self.body[num] = b
134+
# self.get_logger().info(f'Body_pose_callback {num} - end')
135+
136+
def frame_callback(self, image_msg: Image, num: int):
137+
self.get_logger().info(f'Frame_callback {num} - start')
97138
if image_msg.data is None:
98139
return
99-
self.frame = self.bridge.imgmsg_to_cv2(image_msg, "bgr8")
100-
if self.body.keypoints is not None:
101-
self.draw()
102-
self.waitKey()
103-
104-
def draw(self):
105-
lines = [np.array([self.body.keypoints[point] for point in line])
106-
for line in LINES_BODY if self.body.scores[line[0]] > SCORE_THRESH and
107-
self.body.scores[line[1]] > SCORE_THRESH]
140+
self.frame[num] = self.bridge.imgmsg_to_cv2(image_msg, "bgr8")
141+
if self.body[num] is not None and self.body[num].keypoints is not None:
142+
self.draw(num)
143+
self.updateFrame(num)
144+
# self.get_logger().info(f'Frame_callback {num} - end')
145+
146+
def draw(self, num: int):
147+
lines = [np.array([self.body[num].keypoints[point] for point in line])
148+
for line in LINES_BODY if self.body[num].scores[line[0]] > SCORE_THRESH and
149+
self.body[num].scores[line[1]] > SCORE_THRESH]
108150
if lines is not None:
109-
cv2.polylines(self.frame, lines, False, (255, 180, 90), 2, cv2.LINE_AA)
151+
cv2.polylines(self.frame[num], lines, False, (255, 180, 90), 2, cv2.LINE_AA)
110152

111-
for i, x_y in enumerate(self.body.keypoints):
112-
if self.body.scores[i] > SCORE_THRESH:
153+
for i, x_y in enumerate(self.body[num].keypoints):
154+
if self.body[num].scores[i] > SCORE_THRESH:
113155
if i % 2 == 1:
114156
color = (0, 255, 0)
115157
elif i == 0:
116158
color = (0, 255, 255)
117159
else:
118160
color = (0, 0, 255)
119-
cv2.circle(self.frame, (x_y[0], x_y[1]), 4, color, -11)
161+
cv2.circle(self.frame[num], (x_y[0], x_y[1]), 4, color, -11)
162+
163+
def updateFrame(self, num: int):
164+
self.get_logger().info(f'Updated frame {num}')
165+
x = num%self.tile_x
166+
y = int((num - x)/self.tile_y)
167+
cv2.putText(self.frame[num],
168+
f'{self.namespaces[num]}',
169+
(50, 50),
170+
# (self.frame[num].shape[1] // 2, 100),
171+
cv2.FONT_HERSHEY_PLAIN,
172+
2,
173+
(0, 0, 255),
174+
2)
175+
if self.percentage[num]:
176+
cv2.putText(self.frame[num],
177+
f'{self.percentage[num]:.1f}%',
178+
(self.frame[num].shape[1] - 120, self.frame[num].shape[0] - 20),
179+
cv2.FONT_HERSHEY_PLAIN,
180+
2,
181+
(0, 0, 255),
182+
2)
183+
184+
self.full_frame[
185+
int(y*self.image_height):int((y+1)*self.image_height),
186+
int(x*self.image_width):int((x+1)*self.image_width),
187+
0:3] = self.frame[num]
188+
189+
def updateDisplay(self):
190+
self.get_logger().info(f'Updated display')
191+
cv2.imshow("Movenet", self.full_frame)
192+
cv2.waitKey(1)
120193

121-
def waitKey(self, delay=1):
194+
def waitKey(self, delay=0.1):
122195
# if self.show_fps:
123196
# self.pose.fps.draw(self.frame, orig=(50,50), size=1, color=(240,180,100))
124-
cv2.imshow("Movenet", self.frame)
197+
cv2.imshow("Movenet", self.full_frame)
125198
if self.output:
126-
self.output.write(self.frame)
199+
self.output.write(self.full_frame)
127200
key = cv2.waitKey(delay)
128201
if key == 32:
129202
# Pause on space bar
@@ -134,10 +207,12 @@ def waitKey(self, delay=1):
134207
self.show_crop = not self.show_crop
135208
return key
136209

210+
def battery_callback(self, batt_msg: BatteryState, num: int):
211+
self.percentage[num] = batt_msg.percentage * 100
137212

138213
def main(args=None):
139214
rclpy.init(args=args)
140-
node = PoseDetection()
215+
node = PoseDisplay()
141216
rclpy.spin(node)
142217
node.destroy_node()
143218
rclpy.shutdown()

0 commit comments

Comments
 (0)