SLAM

Posted Real-Ying

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SLAM相关的知识,希望对你有一定的参考价值。

|__all together ship

|__SLAM__

                 |__Graph SLAM__

                                             |__完成约束

                                             |__完成Graph SLAM__

                                             |                                   |__完成约束(格子)

                                             |                                   |__加入地标         

                                             |                                                    

                                             |__ Ω和§

                                             |            |__编程(Init_pos,move1,move2)

                                             |                                                             |__加入检测(加入每次测量Z0,Z1,Z2)

                                             |__引入噪音

                                             |__完整的SLAM编程

PID:P的主要功能是让误差最小,因为转向角正比于误差。D使用得当能避免超调。系统漂移与偏置最好用I解决。

 

是时候了,put all together to make a system,这个系统采用车辆模型,并且使用了之前的robot类,得到连续空间里的最短路径

 

steering_noise = 0.1
distance_noise = 0.03
measurement_noise = 0.3

class robot:
     def __init__(self,length = 0.5):
          self.x = 0.0
          self.y = 0.0
          self.orientation = 0.0
          self.length = length
          self.steering_noise = 0.0
          self.distance_noise = 0.0
          self.measurement_noise = 0.0
          self.num_collisions = 0
          self.num_steps = 0

     def set(self,new_x,new_y,new_orientation):
           self_x = float(new_x)
           self_y = float(new_y)
           self_orientation = float(new_orientation) % (2.0*pi)

     def set_noise(self,new_s_noise,new_d_noise,new_m_noise):
           self.steering_noise = float(new_s_noise)
           self.distance_noise = float(new_d_noise)
           self.measurement_noise = float(new_m_noise)

     def check_collisions(self,grid):    #检查是否与网格碰撞
           for i in range(len(grid)):
                for j in range(len(grid[0])):
                     if grid[i][j] == 1:
                         dist = sqrt((self.x - float(i))**2 +
                                        (self.y - float(j) )**2)
                         if dist < 0.5:
                             num_collisions+=1
                         return False              
           return True

     def check_goal(self,goal,threshold = 1.0):    #检查是否到达目标(threshold阀值)
           dist = sqrt((float(i) - self.x)**2 +
                           (float(j) - self.y)**2 )
           return goal < threshold

     def move(self,grid,steering,distance
                    tolerance = 0.001,max_steering_angle = pi/4.0):
           if steering > max_steering_angle:
               steering = max_steering_angle
           if steering < -max_steering_angle:
                steering = -max_steering_angle         
           if distance < 0.0:
              distance = 0.0
     
           # make a new copy
           res = robot()
           res.length = self.length
           res.steering_noise = self.steering_noise
           res.distance_noise = self.distance_noise
           res.measurement_noise = self.measurement_noise
           res.num_collisions = self.num_collisions
           res.num.step = self.num_step + 1
     
           #应用 noise
           steering2 = random.guass(steering,self.steering_noise)
           distance2 = random.guass(distance,self.distance_noise)

           #执行motion
           turn = tan(steering2)*distance2 / res.length
           if abs(turn) < tolerance:
              res.x = self.x + (distance2*cos(self.orientation))
              res.y = self.y + (distance2*sin(self.orientation))
              res.orientation = (self.orientation + turn) % (2.0*pi)
           else:   
              radius = distance2 / turn
              cx = self.x-(sin(self.orientation) * radius)
              cy = self.x+(cos(self.orientation) * radius)
              res.orientation = (self.orientation + turn) % (2*pi)
              res.x = cx +(sin(res.orientation) * radius)
              res.y = cy - (cos(res.orientation) * radius)
          return res

     def sense(self):
           return [random.guass(self.x,self.measurement_noise),
                      random.guass(self.y,self.measurement_noise)]  

     def measurement_prob(self, measurement):
           error_x = measurement[0] - self.x
           error_y = measurement[1] - self.y 
           error = exp(- (error_x ** 2) / (self.measurement_noise ** 2) / 2.0) / sqrt(2.0 * pi * (sigma ** 2))       
           error*= exp(- (error_y ** 2) / (self.measurement_noise ** 2) / 2.0) / sqrt(2.0 * pi * (sigma ** 2)) 
     return error

     def __repr__(self):
         return \'[x=%.5s y=%.5s orient=%.5s]\' % (self.x,self.y,self.orientation)
 

#输入数据和参数
grid = [[0, 1, 0, 0, 0, 0],
           [0, 1, 0, 1, 1, 0],
           [0, 1, 0, 1, 0, 0],
           [0, 0, 0, 1, 0, 1],
           [0, 1, 0, 1, 0, 0]]
init = [0,0]
goal = [len(gird) - 1,len(grid[0])-1]

myrobot = robot()
myrobot.set_noise(steering_noise,distance_noise,measurement_noise)

while not myrobot.check_goal(goal):
    theta = atan2(goal[1] - myrobot.y,goal[0] - myrobot.x) -myrobot.orientation
    myrobot = myrobot.move(grib,theta,0.1)
if not myrobot.check_collision(grib):
    print ####Collision####
          

 

main()会调用A*、路径平滑算法和在run里面的控制器,控制器里有粒子滤波算法。这里会有一个循环来计算轨迹误差CTE,只用了PD

 

theta和P(x,y)是粒子滤波器输出的定位,cte是误差,U是行驶的路程可由点积求出,然后求的小u是用分母归一化后的结果,只要大于1就说明超过了线段,到了下一个线段(每一小步的线段)。CTE同样方式得出。然后程序化这些数学公式,这里设置了一个index变量,当U超过1时,index就要加1,以防超过线段,下面的程序输出一段有效的,不会碰撞的路径,返回值有是否成功、碰撞次数、步数。有时候也会极少发生碰撞,因为障碍物较多,系统噪音的影响。

from math import *
import random

steering_noise    = 0.1
distance_noise    = 0.03
measurement_noise = 0.3

#------------------------------------------------
#
# this is the plan class 
class plan:
    # init:creates an empty plan
    def __init__(self, grid, init, goal, cost = 1):
        self.cost = cost
        self.grid = grid
        self.init = init
        self.goal = goal
        self.make_heuristic(grid, goal, self.cost)
        self.path = []
        self.spath = []

    #-------------------------------------------------------
    # make heuristic function for a grid
    def make_heuristic(self, grid, goal, cost):
        self.heuristic = [[0 for row in range(len(grid[0]))] 
                          for col in range(len(grid))]
        for i in range(len(self.grid)):    
            for j in range(len(self.grid[0])):
                self.heuristic[i][j] = abs(i - self.goal[0]) + \\
                    abs(j - self.goal[1])

    #--------------------------------------------------------
    # A* for searching a path to the goal
    def astar(self):
        if self.heuristic == []:
            raise ValueError, "Heuristic must be defined to run A*"

        # internal motion parameters
        delta = [[-1,  0], # go up
                 [ 0,  -1], # go left
                 [ 1,  0], # go down
                 [ 0,  1]] # do right


        # open list elements are of the type: [f, g, h, x, y]
        closed = [[0 for row in range(len(self.grid[0]))] 
                  for col in range(len(self.grid))]
        action = [[0 for row in range(len(self.grid[0]))] 
                  for col in range(len(self.grid))]

        closed[self.init[0]][self.init[1]] = 1


        x = self.init[0]
        y = self.init[1]
        h = self.heuristic[x][y]
        g = 0
        f = g + h

        open = [[f, g, h, x, y]]

        found  = False # flag that is set when search complete
        resign = False # flag set if we can\'t find expand
        count  = 0

        while not found and not resign:
            # check if we still have elements on the open list
            if len(open) == 0:
                resign = True
                print \'###### Search terminated without success\'
            else:
                # remove node from list
                open.sort()
                open.reverse()
                next = open.pop()
                x = next[3]
                y = next[4]
                g = next[1]

            # check if we are done
            if x == goal[0] and y == goal[1]:
                found = True
                # print \'###### A* search successful\'
            else:
                # expand winning element and add to new open list
                for i in range(len(delta)):
                    x2 = x + delta[i][0]
                    y2 = y + delta[i][1]
                    if x2 >= 0 and x2 < len(self.grid) and y2 >= 0 \\
                            and y2 < len(self.grid[0]):
                        if closed[x2][y2] == 0 and self.grid[x2][y2] == 0:
                            g2 = g + self.cost
                            h2 = self.heuristic[x2][y2]
                            f2 = g2 + h2
                            open.append([f2, g2, h2, x2, y2])
                            closed[x2][y2] = 1
                            action[x2][y2] = i

            count += 1

        # extract the path
        invpath = []
        x = self.goal[0]
        y = self.goal[1]
        invpath.append([x, y])
        while x != self.init[0] or y != self.init[1]:
            x2 = x - delta[action[x][y]][0]
            y2 = y - delta[action[x][y]][1]
            x = x2
            y = y2
            invpath.append([x, y])

        self.path = []
        for i in range(len(invpath)):
            self.path.append(invpath[len(invpath) - 1 - i])


    # -----------------------------------------------------
    # this is the smoothing function
    def smooth(self, weight_data = 0.1, weight_smooth = 0.1, 
               tolerance = 0.000001):

        if self.path == []:
            raise ValueError, "Run A* first before smoothing path"

        self.spath = [[0 for row in range(len(self.path[0]))] \\
                           for col in range(len(self.path))]
        for i in range(len(self.path)):
            for j in range(len(self.path[0])):
                self.spath[i][j] = self.path[i][j]

        change = tolerance
        while change >= tolerance:
            change = 0.0
            for i in range(1, len(self.path)-1):
                for j in range(len(self.path[0])):
                    aux = self.spath[i][j]
                    
                    self.spath[i][j] += weight_data * \\
                        (self.path[i][j] - self.spath[i][j])
                    
                    self.spath[i][j] += weight_smooth * \\
                        (self.spath[i-1][j] + self.spath[i+1][j] 
                         - (2.0 * self.spath[i][j]))
                    if i >= 2:
                        self.spath[i][j] += 0.5 * weight_smooth * \\
                            (2.0 * self.spath[i-1][j] - self.spath[i-2][j] 
                             - self.spath[i][j])
                    if i <= len(self.path) - 3:
                        self.spath[i][j] += 0.5 * weight_smooth * \\
                            (2.0 * self.spath[i+1][j] - self.spath[i+2][j] 
                             - self.spath[i][j])
                
            change += abs(aux - self.spath[i][j])
            
                
# ------------------------------------------------
# 
# this is the robot class
class robot:
    # init: creates robot and initializes location/orientation to 0, 0, 0
    def __init__(self, length = 0.5):
        self.x = 0.0
        self.y = 0.0
        self.orientation = 0.0
        self.length = length
        self.steering_noise    = 0.0
        self.distance_noise    = 0.0
        self.measurement_noise = 0.0
        self.num_collisions    = 0
        self.num_steps         = 0
        
    # set: sets a robot coordinate
    def set(self, new_x, new_y, new_orientation):

        self.x = float(new_x)
        self.y = float(new_y)
        self.orientation = float(new_orientation) % (2.0 * pi)

    # set_noise: sets the noise parameters
    def set_noise(self, new_s_noise, new_d_noise, new_m_noise):
        # makes it possible to change the noise parameters
        # this is often useful in particle filters
        self.steering_noise     = float(new_s_noise)
        self.distance_noise    = float(new_d_noise)
        self.measurement_noise = float(new_m_noise)

   
    # check: checks of the robot pose collides with an obstacle, or is too far outside the plane
    def check_collision(self, grid):
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == 1:
                    dist = sqrt((self.x - float(i)) ** 2 + 
                                (self.y - float(j)) ** 2)
                    if dist < 0.5:
                        self.num_collisions += 1
                        return False
        return True
        
    def check_goal(self, goal, threshold = 1.0):
        dist =  sqrt((float(goal[0]) - self.x) ** 2 + (float(goal[1]) - self.y) ** 2)
        return dist < threshold
        
    # move: steering = front wheel steering angle, limited by max_steering_angle
    #       distance = total distance driven, most be non-negative
    def move(self, grid, steering, distance, 
             tolerance = 0.001, max_steering_angle = pi / 4.0):
        if steering > max_steering_angle:
            steering = max_steering_angle
        if steering < -max_steering_angle:
            steering = -max_steering_angle
        if distance < 0.0:
            distance = 0.0
            
        # make a new copy
        res = robot()
        res.length            = self.length
        res.steering_noise    = self.steering_noise
        res.distance_noise    = self.distance_noise
        res.measurement_noise = self.measurement_noise
        res.num_collisions    = self.num_collisions
        res.num_steps         = self.num_steps + 1

        # apply noise
        steering2 = random.gauss(steering, self.steering_noise)
        distance2 = random.gauss(distance, self.distance_noise)

        # Execute motion
        turn = tan(steering2) * distance2 / res.length

        if abs(turn) < tolerance:
            # approximate by straight line motion
            res.x = self.x + (distance2 * cos(self.orientation))
            res.y = self.y + (distance2 * sin(self.orientation))
            res.orientation = (self.orientation + turn) % (2.0 * pi)
        else:
            # approximate bicycle model for motion
            radius = distance2 / turn
            cx = self.x - (sin(self.orientation) * radius)
            cy = self.y + (cos(self.orientation) * radius)
            res.orientation = (self.orientation + turn) % (2.0 * pi)
            res.x = cx + (sin(res.orientation) * radius)
            res.y = cy - (cos(res.orientation) * radius)

        # check for collision
        # res.check_collision(grid)
        return res

  
    # sense: 
    def sense(self):
        return [random.gauss(self.x, self.measurement_noise),
                random.gauss(self.y, self.measurement_noise)]

  
    # measurement_prob: computes the probability of a measurement
    def measurement_prob(self, measurement):
        # compute errors
        error_x = measurement[0] - self.x
        error_y = measurement[1] - self.y

        # calculate Gaussian
        error = exp(- (error_x ** 2) / (self.measurement_noise ** 2) / 2.0) \\
            / sqrt(2.0 * pi * (self.measurement_noise ** 2))
        error *= exp(- (error_y ** 2) / (self.measurement_noise ** 2) / 2.0) \\
            / sqrt(2.0 * pi * (self.measurement_noise ** 2))
        
        return error

    def __repr__(self):
        # return \'[x=%.5f y=%.5f orient=%.5f]\'  % (self.x, self.y, self.orientation)
        return \'[%.5f, %.5f]\'  % (self.x, self.y)

# ----------------------------------------------------------------
# 
# this is the particle filter class
class particles:
    # init: creates particle set with given initial position
    def __init__(self, x, y, theta, 
                 steering_noise, distance_noise, measurement_noise, N = 100):
        self.N = N
        self.steering_noise    = steering_noise
        self.distance_noise    = distance_noise
        self.measurement_noise = measurement_noise
        
        self.data = []
        for i in range(self.N):
            r = robot()
            r.set(x, y, theta)
            r.set_noise(steering_noise, distance_noise, measurement_noise)
            self.data.append(r)
            
    # extract position from a particle set
    def get_position(self):
        x = 0.0
        y = 0.0
        orientation = 0.0

        for i in range(self.N):
            x += self.data[i].x
            y += self.data[i].y
            # orientation is tricky because it is cyclic. By normalizing
            # around the first particle we are somewhat more robust to
            # the 0=2pi problem
            orientation += (((self.data[i].orientation
                              - self.data[0].orientation + pi) % (2.0 * pi)) 
                            + self.data[0].orientation - pi)
        return [x / self.N, y / self.N, orientation / self.N]

    # motion of the particles
    def move(self, grid, steer, speed):
        newdata = []

        for i in range(self.N):
            r = self.data[i].move(grid, steer, speed)
            newdata.append(r)
        self.data = newdata

    # sensing and resampling
    def sense(self, Z):
        w = []
        for i in range(self.N):
            w.append(self.data[i].measurement_prob(Z))

        # resampling (注意这是浅拷贝)
        p3 = []
        index = int(random.random() * self.N)
        beta = 0.0
        mw = max(w)

        for i in range(self.N):
            beta += random.random() * 2.0 * mw
            while beta > w[index]:
                beta -= w[index]
                index = (index + 1) % self.N
            p3.append(self.data[index])
        self.data = p3

    
# --------------------------------------------------
# run:  runs control program for the robot
def run(grid, goal, spath, params, printflag = False, speed = 0.1, timeout = 1000):

    myrobot = robot()
    myrobot.set(0., 0., 0.)
    myrobot.set_noise(steering_noise, distance_noise, measurement_noise)
    filter = particles(myrobot.x, myrobot.y, myrobot.orientation,
                       steering_noise, distance_noise, measurement_noise)

    cte  = 0.0
    err  = 0.0
    N    = 0

    index = 0 # index into the path
    
    while not myrobot.check_goal(goal) and N < timeout:

        diff_cte = - cte

        # compute the CTE

        # start with the present robot estimate
        estimate = filter.get_position()
        
#-------------------------------------------------- # some basic vector calculations
dx = spath[index+1][0] ­ spath[index][0]
dy = spath[index+1][1] ­ spath[index][1]
drx = estimate[0] ­ spath[index][0]
dry = estimate[1] ­ spath[index][1]
# u is the robot estimate projected into the path segment
u = (drx * dx + dry * dy)/(dx * dx + dy * dy)
#the cte is the estimate projected onto the normal of the path segment
cte = (dry * dx ­ drx * dy)/(dx * dx + dy * dy)
if u > 1:
index += 1
# ---------------------------------------- diff_cte += cte steer = - params[0] * cte - params[1] * diff_cte myrobot = myrobot.move(grid, steer, speed) filter.move(grid, steer, speed) Z = myrobot.sense() filter.sense(Z) if not myrobot.check_collision(grid): print \'##### Collision ####\' err += (cte ** 2) N += 1 if printflag: print myrobot, cte, index, u return [myrobot.check_goal(goal), myrobot.num_collisions, myrobot.num_steps] # ------------------------------------------------------------------- # # this is our main routine def main(grid, init, goal, steering_noise, distance_noise, measurement_noise, weight_data, weight_smooth, p_gain, d_gain): path = plan(grid, init, goal) path.astar() path.smooth(weight_data, weight_smooth) return run(grid, goal, path.spath, [p_gain, d_gain]) # ------------------------------------------------ # # input data and parameters grid = [[0, 1, 0, 0, 0, 0], [0, 1, 0, 1, 1, 0], [0, 1, 0, 1, 0, 0], [0, 0, 0, 1, 0, 1], [0, 1, 0, 1, 0, 0]] init = [0, 0] goal = [len(grid)-1, len(grid[0])-1] #以下这些参数可以调着玩,特别是p、d的权重,用优化函数可能得不到返回,自己尝试出好的值 steering_noise = 0.1 distance_noise = 0.03 measurement_noise = 0.3 weight_data = 0.1 weight_smooth = 0.2 p_gain = 2.0 d_gain = 6.0 print main(grid, init, goal, steering_noise, distance_noise, measurement_noise, weight_data, weight_smooth, p_gain, d_gain) #---------------------------------------- # 参数优化 def twiddle(init_params): n_params = len(init_params) dparams = [1.0 for row in range(n_params)] params = [0.0 for row in range(n_params)] K = 10 for i in range(n_params): params[i] = init_params[i] best_error = 0.0; for k in range(K): ret = main(grid, init, goal, steering_noise, distance_noise, measurement_noise, params[0], params[1], params[2], params[3]) if ret[0]: best_error += ret[1] * 100 + ret[2] else: best_error += 99999 best_error = float(best_error) / float(k+1) print best_error n = 0 while sum(dparams) > 0.0000001: for i in range(len(params)): params[i] += dparams[i] err = 0 for k in range(K): ret = main(grid, init, goal, steering_noise, distance_noise, measurement_noise, params[0], params[1], params[2], params[3], best_error) if ret[0]: err += ret[1] * 100 + ret[2] else: err += 99999 print float(err) / float(k+1) if err < best_error: best_error = float(err) / float(k+1) dparams[i] *= 1.1 else: params[i] -= 2.0 * dparams[i] err = 0 for k in range(K): ret = main(grid, init, goal, steering_noise, distance_noise, measurement_noise, params[0], params[1], params[2], params[3], best_error) if ret[0]: err += ret[1] * 100 + ret[2] else: err += 99999 print float(err) / float(k+1) if err < best_error: best_error = float(err) / float(k+1) dparams[i] *= 1.1 else: params[i] += dparams[i] dparams[i] *= 0.5 n += 1 print \'Twiddle #\', n, params, \' -> \', best_error print \' \' return params #twiddle([weight_data, weight_smooth, p_gain, d_gain])

 

SLAM是一种建图方法,是同时定位和建图的总称。

当移动机器人在环境中建图时,因为移动的不确定性迫使我们去定位。比如一个机器人从X0(0,0)沿x轴运动10个单位到X1,不能以X1=X0+10去表示移动后机器人的位置,而是用关于两个参数的高斯分布来表示,y方向同样。这两个高斯函数就成了约束条件,Graph SLAM就是利用一系列这样的约束来定义概率。(这是相对约束)

一个机器人的移动过程,每个点X1~X4都是(x,y,orientation)的三维向量,每个点时刻都会测量一次与地标的距离(测量结果z用高斯表示),这样会有三个约束出现:初始位置约束、相对约束(两点之间的相对位置)、相对地标位置约束。

 

完成Graph SLAM

为了完成Graph SLAM,一个矩阵和一个向量被引入,要把所有的姿势坐标和地标都填到这个二维矩阵里,

 

x0—>x1  5,那么x0+5=x1,x0+(-1*x1)=-5,就是第一行。然后反过来x1+(-1*x0)=5,就是第二行。

举一反三,倒回,x2—>x1 -4 ,x1-4=x2,x2+(-x1)=-4,这就是第三行。然后x1+(-x2)=4,加到第二行,最后结果如图,以此类推填充矩阵。

同理填入地标格子,没测地标的检测点空(x与L对应的空格),临近的两个检测点才测互相的空格。

 Ω和§

 然后将这两个矩阵经过简单的数学运算就能找到所有世界坐标的最佳解,即最好估计值μ。

这就是SLAM Graph方法,只要每次看到约束的时候就把这些数字填到这两个矩阵当中,然后只需一个简单的运算,机器人的位置最佳坐标就出来了。