1717# @author Hilary Luo (hluo@clearpathrobotics.com)
1818
1919import cv2
20+ from functools import partial
2021import numpy as np
2122
2223from cv_bridge import CvBridge
2526from rclpy .node import Node
2627from rclpy .qos import qos_profile_sensor_data
2728from std_msgs .msg import String as string_msg
28- from sensor_msgs .msg import Image
29+ from sensor_msgs .msg import BatteryState , Image
2930
3031from turtlebot4_vision_tutorials .MovenetDepthaiEdge import Body
3132
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
138213def 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