calibrate.py
Go to the documentation of this file.
00001 #! /usr/bin/python
00002 #***********************************************************
00003 # Software License Agreement (BSD License)
00004 #
00005 # Copyright (c) 2008, Willow Garage, Inc.
00006 # All rights reserved.
00007 #
00008 # Redistribution and use in source and binary forms, with or without
00009 # modification, are permitted provided that the following conditions
00010 # are met:
00011 #
00012 #  * Redistributions of source code must retain the above copyright
00013 #    notice, this list of conditions and the following disclaimer.
00014 #  * Redistributions in binary form must reproduce the above
00015 #    copyright notice, this list of conditions and the following
00016 #    disclaimer in the documentation and/or other materials provided
00017 #    with the distribution.
00018 #  * Neither the name of the Willow Garage nor the names of its
00019 #    contributors may be used to endorse or promote products derived
00020 #    from this software without specific prior written permission.
00021 #
00022 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00023 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00024 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00025 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00026 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00027 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00028 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00029 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00030 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00032 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00033 # POSSIBILITY OF SUCH DAMAGE.
00034 #
00035 
00036 # Based on turtlebot_calibration by Wim Meeussen
00037 
00038 from __future__ import with_statement
00039 
00040 import roslib; roslib.load_manifest('elektron_calibration')
00041 import yaml
00042 import PyKDL
00043 import rospy
00044 from sensor_msgs.msg import Imu
00045 from nav_msgs.msg import Odometry
00046 from geometry_msgs.msg import Twist
00047 from elektron_calibration.msg import ScanDistAngle
00048 from math import *
00049 import threading
00050 
00051 
00052 def quat_to_angle(quat):
00053     rot = PyKDL.Rotation.Quaternion(quat.x, quat.y, quat.z, quat.w)
00054     return rot.GetRPY()[2]
00055         
00056 def normalize_angle(angle):
00057     res = angle
00058     while res > pi:
00059         res -= 2.0*pi
00060     while res < -pi:
00061         res += 2.0*pi
00062     return res
00063 
00064 
00065 class CalibrateRobot:
00066     def __init__(self):
00067         self.lock = threading.Lock()
00068         self.sub_imu  = rospy.Subscriber('imu/data', Imu, self.imu_cb)
00069         self.sub_odom = rospy.Subscriber('odom', Odometry, self.odom_cb)
00070         self.sub_scan = rospy.Subscriber('scan_dist_angle', ScanDistAngle, self.scan_cb)
00071         self.cmd_pub = rospy.Publisher('cmd_vel', Twist)
00072         self.imu_time = rospy.Time()
00073         self.odom_time = rospy.Time()
00074         self.scan_time = rospy.Time()
00075         
00076         # params
00077         self.inital_wall_angle = rospy.get_param("inital_wall_angle", 0.1)
00078         self.inital_wall_dist = rospy.get_param("inital_wall_angle", 3.0)
00079         self.imu_calibrate_time = rospy.get_param("imu_calibrate_time", 10.0)
00080         
00081 
00082 
00083     def calibrate_rot(self, speed, imu_drift=0):
00084         # rotate 360 degrees
00085         (imu_start_angle, odom_start_angle, scan_start_angle, 
00086          imu_start_time, odom_start_time, scan_start_time,
00087          xx, yy, dd) = self.sync_timestamps()
00088         last_angle = odom_start_angle
00089         turn_angle = 0
00090         while turn_angle < 15.0/8*pi:
00091             if rospy.is_shutdown():
00092                 return
00093             cmd = Twist()
00094             cmd.angular.z = speed
00095             self.cmd_pub.publish(cmd)
00096             rospy.sleep(0.025)
00097             with self.lock:
00098                 delta_angle = normalize_angle(self.odom_angle - last_angle)
00099             turn_angle += delta_angle
00100             last_angle = self.odom_angle
00101         self.cmd_pub.publish(Twist())
00102 
00103         rospy.sleep(1.0)
00104 
00105         (imu_end_angle, odom_end_angle, scan_end_angle,
00106          imu_end_time, odom_end_time, scan_end_time,
00107          xx, yy, dd) = self.sync_timestamps()
00108         imu_delta = 2*pi + normalize_angle(imu_end_angle - imu_start_angle) - imu_drift*(imu_end_time - imu_start_time).to_sec()
00109         odom_delta = 2*pi + normalize_angle(odom_end_angle - odom_start_angle)
00110         scan_delta = 2*pi + normalize_angle(scan_end_angle - scan_start_angle)
00111         rospy.loginfo('Imu error: %f percent'%(100.0*((imu_delta/scan_delta)-1.0)))
00112         rospy.loginfo('Odom error: %f percent'%(100.0*((odom_delta/scan_delta)-1.0)))
00113         return (imu_delta/scan_delta, odom_delta/scan_delta)
00114     
00115     def calibrate_lin(self, speed):
00116         # rotate 360 degrees
00117         (imu_end_angle, odom_end_angle, scan_end_angle,
00118          imu_end_time, odom_end_time, scan_end_time,
00119          xx, yy, dd) = self.sync_timestamps()
00120         last_dist = dd
00121         dist = 0.0
00122         last_x = xx
00123         last_y = yy
00124         to_travel = dd - 0.5
00125         while dist < to_travel:
00126             if rospy.is_shutdown():
00127                 return
00128             cmd = Twist()
00129             cmd.linear.x = speed
00130             self.cmd_pub.publish(cmd)
00131             rospy.sleep(0.025)
00132             with self.lock:
00133                 dx = self.odom_x - last_x
00134                 dy = self.odom_y - last_y
00135                 last_x = self.odom_x
00136                 last_y = self.odom_y
00137                 delta_dist = sqrt(dx*dx+dy*dy)
00138             dist += delta_dist
00139         self.cmd_pub.publish(Twist())
00140 
00141         rospy.sleep(1.0)
00142 
00143         (imu_end_angle, odom_end_angle, scan_end_angle,
00144          imu_end_time, odom_end_time, scan_end_time,
00145          e_xx, e_yy, e_dd) = self.sync_timestamps()
00146         scan_delta = dd - e_dd
00147         sx = e_xx - xx
00148         sy = e_yy - yy
00149         odom_delta = sqrt(sx*sx+sy*sy)
00150         rospy.loginfo('Odom linear error: %f percent'%(100.0*((odom_delta/scan_delta)-1.0)))
00151         return odom_delta/scan_delta
00152 
00153 
00154 
00155 
00156 
00157     def imu_drift(self):
00158         # estimate imu drift
00159         rospy.loginfo('Estimating imu drift')
00160         (imu_start_angle, odom_start_angle, scan_start_angle, 
00161          imu_start_time, odom_start_time, scan_start_time,
00162          xx, yy, dd) = self.sync_timestamps()
00163         rospy.sleep(self.imu_calibrate_time)
00164         (imu_end_angle, odom_end_angle, scan_end_angle,
00165          imu_end_time, odom_end_time, scan_end_time,
00166          xx, yy, dd) = self.sync_timestamps()
00167         imu_drift = normalize_angle(imu_end_angle - imu_start_angle) / ((imu_end_time - imu_start_time).to_sec())
00168         rospy.loginfo(' ... imu drift is %f degrees per second'%(imu_drift*180.0/pi))
00169         return imu_drift
00170 
00171 
00172     def align(self, scale = 1.0):
00173         rospy.loginfo("Aligning base with wall")
00174         with self.lock:
00175             angle = self.scan_angle
00176         cmd = Twist()
00177 
00178         while angle < -self.inital_wall_angle*scale or angle > self.inital_wall_angle*scale:
00179             if rospy.is_shutdown():
00180                 exit(0)
00181             if angle > 0:
00182                 cmd.angular.z = -0.1*scale
00183             else:
00184                 cmd.angular.z = 0.1*scale
00185             self.cmd_pub.publish(cmd)
00186             rospy.sleep(0.025)
00187             with self.lock:
00188                 angle = self.scan_angle
00189                 
00190     def go_back(self):
00191         rospy.loginfo("Aligning base with wall")
00192         with self.lock:
00193             dist = self.scan_dist
00194         cmd = Twist()
00195 
00196         while dist < self.inital_wall_dist:
00197             if rospy.is_shutdown():
00198                 exit(0)
00199             
00200             if (self.inital_wall_dist - dist > 0.5):
00201                 cmd.linear.x = -0.15
00202             else:
00203                 cmd.linear.x = -0.05
00204                 
00205             self.cmd_pub.publish(cmd)
00206             rospy.sleep(0.025)
00207             with self.lock:
00208                 dist = self.scan_dist
00209 
00210 
00211 
00212 
00213 
00214     def sync_timestamps(self, start_time=None):
00215         if not start_time:
00216             start_time = rospy.Time.now() + rospy.Duration(0.5)
00217         while not rospy.is_shutdown():
00218             rospy.sleep(0.3)
00219             with self.lock:
00220                 if self.imu_time < start_time:
00221                     rospy.loginfo("Still waiting for imu")
00222                 elif self.odom_time < start_time:
00223                     rospy.loginfo("Still waiting for odom")
00224                 elif self.scan_time < start_time:
00225                     rospy.loginfo("Still waiting for scan")
00226                 else:
00227                     return (self.imu_angle, self.odom_angle, self.scan_angle,
00228                             self.imu_time, self.odom_time, self.scan_time,
00229                             self.odom_x, self.odom_y, self.scan_dist)
00230         exit(0)
00231         
00232 
00233     def imu_cb(self, msg):
00234         with self.lock:
00235             angle = quat_to_angle(msg.orientation)
00236             self.imu_angle = angle
00237             self.imu_time = msg.header.stamp
00238 
00239     def odom_cb(self, msg):
00240         with self.lock:
00241             angle = quat_to_angle(msg.pose.pose.orientation)
00242             self.odom_angle = angle
00243             self.odom_x = msg.pose.pose.position.x
00244             self.odom_y = msg.pose.pose.position.y
00245             self.odom_time = msg.header.stamp
00246 
00247     def scan_cb(self, msg):
00248         with self.lock:
00249             self.scan_angle = msg.angle
00250             self.scan_dist = msg.dist
00251             self.scan_time = msg.header.stamp
00252 
00253 
00254 
00255 def main():
00256     rospy.init_node('scan_to_angle')
00257     robot = CalibrateRobot()
00258     
00259     imu_drift = robot.imu_drift()
00260     imu_corr = []
00261     odom_corr = []
00262     odom_lin_corr = []
00263     for speed in (0.3, 0.7, 1.0):
00264         robot.align()
00265         rospy.sleep(1.0)
00266         (imu, odom) = robot.calibrate_rot(speed, imu_drift)
00267         imu_corr.append(imu)
00268         odom_corr.append(odom)
00269         
00270     for speed in (0.05, 0.1, 0.2):
00271         robot.align(0.3)
00272         rospy.sleep(1.0)
00273         robot.go_back()
00274         rospy.sleep(2.0)
00275         odom_lin = robot.calibrate_lin(speed)
00276         odom_lin_corr.append(odom_lin)
00277         
00278     imu_res = 1.0/(sum(imu_corr)/len(imu_corr))
00279     odom_res = 1.0/(sum(odom_corr)/len(odom_corr))
00280     odom_lin_res = 1.0/(sum(odom_lin_corr)/len(odom_lin_corr))
00281     rospy.loginfo("Multiply the 'gyroscope/rot_scale' parameter with %f"%imu_res)
00282     rospy.loginfo("Multiply the 'elektron_base_node/rot_scale' parameter with %f"%odom_res)
00283     rospy.loginfo("Multiply the 'elektron_base_node/lin_scale' parameter with %f"%odom_lin_res)
00284 
00285 
00286 if __name__ == '__main__':
00287     main()


elektron_calibration
Author(s): Maciej StefaƄczyk
autogenerated on Sun Oct 5 2014 23:43:58