13
13
14
14
from typing import Union , List
15
15
16
- import actionlib
17
16
from move_base_msgs .msg import MoveBaseAction , MoveBaseActionGoal , MoveBaseGoal
18
17
from actionlib_msgs .msg import GoalStatus
18
+ from sensor_msgs .msg import PointCloud2
19
19
20
20
import rosservice
21
21
import tf2_ros as tf
22
22
import tf2_geometry_msgs # type: ignore
23
23
from tf2_geometry_msgs .tf2_geometry_msgs import do_transform_pose
24
24
from nav_msgs .srv import GetPlan
25
- from nav_msgs .msg import Path
25
+ from lasr_vision_msgs .srv import DetectWave
26
+ from play_motion_msgs .msg import PlayMotionAction
27
+ from play_motion_msgs .msg import PlayMotionGoal
26
28
29
+ from lasr_vision_msgs .srv import DetectWaveRequest
27
30
28
31
from math import atan2
29
32
import numpy as np
38
41
39
42
40
43
class PersonFollower :
41
-
42
44
# Parameters
43
45
_start_following_radius : float
44
46
_start_following_angle : float
@@ -71,17 +73,19 @@ def __init__(
71
73
self ,
72
74
start_following_radius : float = 2.0 ,
73
75
start_following_angle : float = 45.0 ,
74
- n_secs_static_finished : float = 10 .0 ,
75
- n_secs_static_plan_close : float = 5 .0 ,
76
- new_goal_threshold : float = 0.5 ,
76
+ n_secs_static_finished : float = 8 .0 ,
77
+ n_secs_static_plan_close : float = 10 .0 ,
78
+ new_goal_threshold : float = 2.0 ,
77
79
stopping_distance : float = 1.0 ,
80
+ static_speed : float = 0.0015 ,
78
81
):
79
82
self ._start_following_radius = start_following_radius
80
83
self ._start_following_angle = start_following_angle
81
84
self ._n_secs_static_finished = n_secs_static_finished
82
85
self ._n_secs_static_plan_close = n_secs_static_plan_close
83
86
self ._new_goal_threshold = new_goal_threshold
84
87
self ._stopping_distance = stopping_distance
88
+ self ._static_speed = static_speed
85
89
86
90
self ._track_id = None
87
91
@@ -120,6 +124,16 @@ def __init__(
120
124
if not self ._transcribe_speech_client_available :
121
125
rospy .logwarn ("Transcribe speech client not available" )
122
126
127
+ self ._detect_wave = rospy .ServiceProxy ("/detect_wave" , DetectWave )
128
+ if not self ._detect_wave .wait_for_service (rospy .Duration .from_sec (10.0 )):
129
+ rospy .logwarn ("Detect wave service not available" )
130
+
131
+ self ._play_motion = actionlib .SimpleActionClient (
132
+ "play_motion" , PlayMotionAction
133
+ )
134
+ if not self ._play_motion .wait_for_server (rospy .Duration .from_sec (10.0 )):
135
+ rospy .logwarn ("Play motion client not available" )
136
+
123
137
def _tf_pose (self , pose : PoseStamped , target_frame : str ):
124
138
trans = self ._buffer .lookup_transform (
125
139
target_frame , pose .header .frame_id , rospy .Time (0 ), rospy .Duration (1.0 )
@@ -129,7 +143,7 @@ def _tf_pose(self, pose: PoseStamped, target_frame: str):
129
143
def _robot_pose_in_odom (self ) -> Union [PoseStamped , None ]:
130
144
try :
131
145
current_pose : PoseWithCovarianceStamped = rospy .wait_for_message (
132
- "/amcl_pose " , PoseWithCovarianceStamped
146
+ "/robot_pose " , PoseWithCovarianceStamped
133
147
)
134
148
except AttributeError :
135
149
return None
@@ -203,8 +217,11 @@ def begin_tracking(self, ask: bool = False) -> bool:
203
217
rospy .loginfo (f"Tracking person with ID { self ._track_id } " )
204
218
return True
205
219
206
- def _recover_track (self ) -> bool :
207
- if self ._tts_client_available :
220
+ def _recover_track (self , say ) -> bool :
221
+ if not say :
222
+ self ._tts ("I SAW A person waving" , wait = True )
223
+
224
+ if self ._tts_client_available and say :
208
225
self ._tts ("I lost track of you, please come back" , wait = True )
209
226
210
227
while not self .begin_tracking (ask = True ) and not rospy .is_shutdown ():
@@ -213,6 +230,58 @@ def _recover_track(self) -> bool:
213
230
214
231
return True
215
232
233
+ # recover with vision, look up and check if person is waving
234
+ def _recover_vision (self , prev_pose : PoseStamped ) -> bool :
235
+ # look up with playmotion and detect wave service
236
+ # if detected, begin tracking
237
+
238
+ # use play motion to look up
239
+ self ._cancel_goal ()
240
+
241
+ goal = PlayMotionGoal ()
242
+ goal .motion_name = "look_centre"
243
+ self ._play_motion .send_goal_and_wait (goal )
244
+
245
+ self ._tts ("Can you wave at me so that i can try to find you easily" , wait = True )
246
+
247
+ # detect wave
248
+ try :
249
+ pcl = rospy .wait_for_message ("/xtion/depth_registered/points" , PointCloud2 )
250
+ req = DetectWaveRequest ()
251
+ req .pcl_msg = pcl
252
+ req .dataset = "resnet50"
253
+ req .confidence = 0.1
254
+ response = self ._detect_wave (req )
255
+ if response .wave_detected :
256
+ rospy .loginfo ("Wave detected, beginning tracking" )
257
+ if np .isnan (response .wave_position .point .x ) or np .isnan (
258
+ response .wave_position .point .y
259
+ ):
260
+ return False
261
+ goal_pose = self ._tf_pose (
262
+ PoseStamped (
263
+ pose = Pose (
264
+ position = Point (
265
+ x = response .wave_position .point .x ,
266
+ y = response .wave_position .point .y ,
267
+ z = response .wave_position .point .z ,
268
+ ),
269
+ orientation = Quaternion (0 , 0 , 0 , 1 ),
270
+ ),
271
+ header = pcl .header ,
272
+ ),
273
+ "map" ,
274
+ )
275
+ rospy .loginfo (goal_pose .pose .position )
276
+ goal_pose .pose .orientation = self ._compute_face_quat (
277
+ prev_pose .pose , goal_pose .pose
278
+ )
279
+ self ._move_base (goal_pose )
280
+ return True
281
+ except rospy .ServiceException as e :
282
+ rospy .loginfo (f"Error detecting wave: { e } " )
283
+ return False
284
+
216
285
def _euclidian_distance (self , p1 : Pose , p2 : Pose ) -> float :
217
286
return np .linalg .norm (
218
287
np .array ([p1 .position .x , p1 .position .y ])
@@ -314,8 +383,10 @@ def follow(self) -> None:
314
383
person_trajectory : PoseArray = PoseArray ()
315
384
person_trajectory .header .frame_id = "odom"
316
385
prev_track : Union [None , Person ] = None
386
+ prev_goal : Union [None , PoseStamped ] = None
317
387
last_goal_time : Union [None , rospy .Time ] = None
318
388
going_to_person : bool = False
389
+ track_vels : [float ] = []
319
390
320
391
while not rospy .is_shutdown ():
321
392
@@ -326,15 +397,38 @@ def follow(self) -> None:
326
397
filter (lambda track : track .id == self ._track_id , tracks .people ),
327
398
None ,
328
399
)
400
+ # keep a sliding window of the tracks velocity
401
+ if track is not None :
402
+ track_vels .append ((track .vel_x , track .vel_y ))
403
+ if len (track_vels ) > 10 :
404
+ track_vels .pop (0 )
329
405
330
406
if track is None :
331
407
rospy .loginfo ("Lost track of person, recovering..." )
332
- self ._cancel_goal ()
333
408
person_trajectory = PoseArray ()
334
- self ._recover_track ()
409
+ recovery = self ._recover_vision (prev_goal )
410
+ self ._recover_track (say = not recovery )
335
411
prev_track = None
336
412
continue
337
413
414
+ if prev_track is None :
415
+ robot_pose : PoseStamped = self ._robot_pose_in_odom ()
416
+
417
+ goal_pose = self ._tf_pose (
418
+ PoseStamped (
419
+ pose = Pose (
420
+ position = track .pose .position ,
421
+ orientation = robot_pose .pose .orientation ,
422
+ ),
423
+ header = tracks .header ,
424
+ ),
425
+ "map" ,
426
+ )
427
+ self ._move_base (goal_pose )
428
+ prev_goal = goal_pose
429
+ last_goal_time = rospy .Time .now ()
430
+ prev_track = track
431
+
338
432
# Distance to the previous pose
339
433
dist_to_prev = (
340
434
self ._euclidian_distance (track .pose , prev_track .pose )
@@ -350,42 +444,28 @@ def follow(self) -> None:
350
444
"map" ,
351
445
)
352
446
self ._move_base (goal_pose )
447
+ prev_goal = goal_pose
353
448
prev_track = track
354
449
last_goal_time = rospy .Time .now ()
355
- person_trajectory .poses .append (track .pose )
356
- person_trajectory .header .stamp = rospy .Time .now ()
357
- self ._person_trajectory_pub .publish (person_trajectory )
358
- going_to_person = False
359
- elif last_goal_time is not None :
360
- delta_t : float = (rospy .Time .now () - last_goal_time ).to_sec ()
361
- if (
362
- self ._n_secs_static_plan_close
363
- <= delta_t
364
- < self ._n_secs_static_finished
365
- ) and not going_to_person :
366
- self ._cancel_goal ()
367
- goal_pose = self ._get_pose_on_path (
368
- self ._tf_pose (self ._robot_pose_in_odom (), "map" ),
369
- self ._tf_pose (
370
- PoseStamped (pose = track .pose , header = tracks .header ),
371
- "map" ,
372
- ),
373
- self ._stopping_distance ,
374
- )
375
- if goal_pose is not None :
376
- self ._move_base (goal_pose )
377
- going_to_person = True
378
- else :
379
- rospy .logwarn ("Could not find a path to the person" )
380
- elif delta_t >= self ._n_secs_static_finished :
381
- rospy .loginfo (
382
- "Person has been static for too long, checking if we are finished"
383
- )
384
- self ._move_base_client .wait_for_result ()
385
- if self ._check_finished ():
386
- rospy .loginfo ("Finished following person" )
387
- break
388
- else :
389
- rospy .loginfo ("Person is not finished, continuing" )
390
- last_goal_time = None
391
- continue
450
+ elif (
451
+ self ._move_base_client .get_state () in [GoalStatus .ABORTED ]
452
+ and prev_goal is not None
453
+ ):
454
+ rospy .logwarn ("Goal was aborted, retrying" )
455
+ rospy .logwarn ((rospy .Time .now () - last_goal_time ).to_sec ())
456
+ rospy .logwarn (track .pose == prev_track .pose )
457
+ rospy .logwarn ("" )
458
+ self ._move_base (prev_goal )
459
+ elif (
460
+ np .mean ([np .linalg .norm (vel ) for vel in track_vels ])
461
+ < self ._static_speed
462
+ ):
463
+
464
+ rospy .logwarn ("Person has been static for too long, stopping" )
465
+ self ._cancel_goal ()
466
+ if self ._check_finished ():
467
+ rospy .loginfo ("Finished following person" )
468
+ break
469
+ rospy .loginfo ("" )
470
+ rospy .loginfo (np .mean ([np .linalg .norm (vel ) for vel in track_vels ]))
471
+ rospy .loginfo ("" )
0 commit comments