@@ -32,7 +32,10 @@ from std_msgs.msg import String
3232
3333
3434def _noisy (std_dev ):
35- return std_dev * np .random .randn ()
35+ # Use the standard deviation as the mean as well as the std deviation
36+ # This feels super hacky but for me better represents wheel encoder error
37+ # usually perceived distance travelled will be greater than actual distance
38+ return np .random .normal (std_dev , std_dev )
3639
3740
3841def _SE3_to_pose_msg (pose ):
@@ -70,8 +73,10 @@ class PoseNoisifier(object):
7073 self ._lock = threading .Lock ()
7174
7275 self ._start_pose = None
76+ self ._last_time = None
7377
74- self ._accumulated_error = np .eye (4 )
78+ self ._nodom = np .eye (4 )
79+ self ._initialised = False
7580 self ._last_vel = Twist ()
7681
7782 self ._tf = tf2_ros .TransformBroadcaster ()
@@ -88,25 +93,69 @@ class PoseNoisifier(object):
8893
8994 def pub (self , odom_msg ):
9095 with self ._lock :
96+
97+ current_time = rospy .get_time ()
98+ time_diff = current_time - self ._last_time
9199 # Bail if we don't have valid data to work with
92100 if self ._start_pose is None :
93101 return
94102
95- # Translate our clean odom into a noisy odom
96- noisy_odom = np .matmul (self ._accumulated_error ,
97- sp .pose_msg_to_SE3 (odom_msg .pose ))
103+ # Initialise that which needs initialisation
104+ if not self ._initialised :
105+ self ._nodom = sp .pose_msg_to_SE3 (odom_msg .pose )
106+ self ._last_odom = sp .pose_msg_to_SE3 (odom_msg .pose )
107+ self ._initialised = True
108+
109+ # Calculate the velocity of the last segment of time using odometery.
110+ # Note, previous version used command velocity but this differed too much
111+ # from "true" velocity
112+ # TODO enhancement would be to use actual wheel rotations and translate that into
113+ # an estimate of velocity but that is too much for now. Current version thinks
114+ # it is stationary even if the wheels keep spinning as the robot hits a wall
115+ current_odom = sp .pose_msg_to_SE3 (odom_msg .pose )
116+ odom_diff = np .matmul (np .linalg .inv (self ._last_odom ), current_odom )
117+ vel_angular = sp .rpy_from_SE3 (odom_diff )[2 ]/ time_diff # angular.z
118+ vel_linear = odom_diff [0 ,3 ]/ time_diff # linear.x
119+
120+ # Calculate current noisy pose from starting point (noisy odom pose)
121+ # Current velocity with some level of error transformed into an expected pose
122+ # Between last and current odom reading
123+ # NOTE error is now usually positive multiplier to better simulate wheel encoder
124+ # errors (always think you have travelled further than you have)
125+ lin_vel_noisy = vel_linear + \
126+ vel_linear * _noisy (self .noise_linear )
127+ ang_vel_noisy = vel_angular + \
128+ vel_angular * _noisy (self .noise_angular )
129+
130+ # Turn velocities back into distances
131+ lin_move = lin_vel_noisy * time_diff
132+ ang_move = ang_vel_noisy * time_diff
133+
134+ self ._nodom = np .matmul (
135+ self ._nodom ,
136+ np .matmul (
137+ sp .SE3_from_translation (lin_move ),
138+ sp .SE3_from_yaw (ang_move )),
139+ )
98140
99- # Figure out "localisation" transform from map to odom frame
141+ # Calculate the error between what we read here and the true odometry
142+ odom_error = np .matmul (sp .pose_msg_to_SE3 (odom_msg .pose ),
143+ np .linalg .inv (self ._nodom ))
144+
145+ # Calculate localisation transform from map to odom frame to accomodate for
146+ # calculated error
100147 localisation = np .matmul (self ._start_pose ,
101- np . linalg . inv ( self . _accumulated_error ) )
148+ odom_error )
102149
103150 # Publish our messages
104151 o = Odometry ()
105152 o .header .stamp = odom_msg .header .stamp
106153 o .header .frame_id = self .out_frame_odom
107154 o .child_frame_id = self .out_frame_robot
108- o .pose .pose = _SE3_to_pose_msg (noisy_odom )
155+ o .pose .pose = _SE3_to_pose_msg (self . _nodom )
109156 o .pose .covariance = odom_msg .pose .covariance
157+ # TODO establish whether this twist should be the cmd vel twist
158+ # or calculated "true" velocity
110159 o .twist .twist = self ._last_vel
111160 self ._pub_odom .publish (o )
112161
@@ -121,22 +170,19 @@ class PoseNoisifier(object):
121170 t .header .stamp = odom_msg .header .stamp
122171 t .header .frame_id = self .out_frame_odom
123172 t .child_frame_id = self .out_frame_robot
124- t .transform = _SE3_to_tf_msg (noisy_odom )
173+ t .transform = _SE3_to_tf_msg (self . _nodom )
125174 self ._tf .sendTransform (t )
126175
127- # Update accumulated noise
128- self ._accumulated_error = np .matmul (
129- self ._accumulated_error ,
130- np .matmul (
131- sp .SE3_from_translation (self ._last_vel .linear .x *
132- _noisy (self .noise_linear )),
133- sp .SE3_from_yaw (self ._last_vel .angular .z *
134- _noisy (self .noise_angular ))),
135- )
176+ # Update time and odom
177+ self ._last_time = current_time
178+ self ._last_odom = current_odom
179+
180+
136181
137182 def update_start_pose (self , start_pose_msg ):
138183 with self ._lock :
139- self ._accumulated_error = np .eye (4 )
184+ self ._initialised = False
185+ self ._last_time = rospy .get_time ()
140186 self ._start_pose = sp .wxyzXYZ_to_SE3 (* np .array ([
141187 float (x .strip ()) for x in start_pose_msg .data .replace (
142188 '[' , '' ).replace (']' , '' ).split (',' )
0 commit comments