Andrew Tu
2019-06-11 03:20:59 -07:00
20 changed files with 1566 additions and 394 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
patreon: myenigma
custom: https://www.paypal.me/myenigmapay/

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
*.csv
*.gif
*.g2o
# Byte-compiled / optimized / DLL files
__pycache__/

View File

@@ -6,17 +6,22 @@ author: Atsushi Sakai (@Atsushi_twi)
"""
import numpy as np
import math
import numpy as np
import matplotlib.pyplot as plt
# Estimation parameter of EKF
Q = np.diag([0.1, 0.1, np.deg2rad(1.0), 1.0])**2 # predict state covariance
# Covariance for EKF simulation
Q = np.diag([
0.1, # variance of location on x-axis
0.1, # variance of location on y-axis
np.deg2rad(1.0), # variance of yaw angle
1.0 # variance of velocity
])**2 # predict state covariance
R = np.diag([1.0, 1.0])**2 # Observation x,y position covariance
# Simulation parameter
Qsim = np.diag([1.0, np.deg2rad(30.0)])**2
Rsim = np.diag([0.5, 0.5])**2
INPUT_NOISE = np.diag([1.0, np.deg2rad(30.0)])**2
GPS_NOISE = np.diag([0.5, 0.5])**2
DT = 0.1 # time tick [s]
SIM_TIME = 50.0 # simulation time [s]
@@ -27,7 +32,7 @@ show_animation = True
def calc_input():
v = 1.0 # [m/s]
yawrate = 0.1 # [rad/s]
u = np.array([[v, yawrate]]).T
u = np.array([[v], [yawrate]])
return u
@@ -36,14 +41,10 @@ def observation(xTrue, xd, u):
xTrue = motion_model(xTrue, u)
# add noise to gps x-y
zx = xTrue[0, 0] + np.random.randn() * Rsim[0, 0]
zy = xTrue[1, 0] + np.random.randn() * Rsim[1, 1]
z = np.array([[zx, zy]]).T
z = observation_model(xTrue) + GPS_NOISE @ np.random.randn(2, 1)
# add noise to input
ud1 = u[0, 0] + np.random.randn() * Qsim[0, 0]
ud2 = u[1, 0] + np.random.randn() * Qsim[1, 1]
ud = np.array([[ud1, ud2]]).T
ud = u + INPUT_NOISE @ np.random.randn(2, 1)
xd = motion_model(xd, ud)
@@ -62,19 +63,18 @@ def motion_model(x, u):
[0.0, DT],
[1.0, 0.0]])
x = F@x + B@u
x = F @ x + B @ u
return x
def observation_model(x):
# Observation Model
H = np.array([
[1, 0, 0, 0],
[0, 1, 0, 0]
])
z = H@x
z = H @ x
return z

View File

@@ -11,13 +11,18 @@ import scipy.linalg
import math
import matplotlib.pyplot as plt
# Estimation parameter of UKF
Q = np.diag([0.1, 0.1, np.deg2rad(1.0), 1.0])**2
R = np.diag([1.0, np.deg2rad(40.0)])**2
# Covariance for UKF simulation
Q = np.diag([
0.1, # variance of location on x-axis
0.1, # variance of location on y-axis
np.deg2rad(1.0), # variance of yaw angle
1.0 # variance of velocity
])**2 # predict state covariance
R = np.diag([1.0, 1.0])**2 # Observation x,y position covariance
# Simulation parameter
Qsim = np.diag([0.5, 0.5])**2
Rsim = np.diag([1.0, np.deg2rad(30.0)])**2
INPUT_NOISE = np.diag([1.0, np.deg2rad(30.0)])**2
GPS_NOISE = np.diag([0.5, 0.5])**2
DT = 0.1 # time tick [s]
SIM_TIME = 50.0 # simulation time [s]
@@ -42,14 +47,10 @@ def observation(xTrue, xd, u):
xTrue = motion_model(xTrue, u)
# add noise to gps x-y
zx = xTrue[0, 0] + np.random.randn() * Qsim[0, 0]
zy = xTrue[1, 0] + np.random.randn() * Qsim[1, 1]
z = np.array([[zx, zy]]).T
z = observation_model(xTrue) + GPS_NOISE @ np.random.randn(2, 1)
# add noise to input
ud1 = u[0] + np.random.randn() * Rsim[0, 0]
ud2 = u[1] + np.random.randn() * Rsim[1, 1]
ud = np.array([ud1, ud2])
ud = u + INPUT_NOISE @ np.random.randn(2, 1)
xd = motion_model(xd, ud)
@@ -100,7 +101,9 @@ def generate_sigma_points(xEst, PEst, gamma):
def predict_sigma_motion(sigma, u):
# Sigma Points prediction with motion model
"""
Sigma Points prediction with motion model
"""
for i in range(sigma.shape[1]):
sigma[:, i:i + 1] = motion_model(sigma[:, i:i + 1], u)
@@ -108,7 +111,9 @@ def predict_sigma_motion(sigma, u):
def predict_sigma_observation(sigma):
# Sigma Points prediction with observation model
"""
Sigma Points prediction with observation model
"""
for i in range(sigma.shape[1]):
sigma[0:2, i] = observation_model(sigma[:, i])

View File

@@ -73,7 +73,7 @@ def update_clusters(clusters):
mind = min(dlist)
min_id = dlist.index(mind)
clusters.labels[ip] = min_id
cost += min_id
cost += mind
return clusters, cost

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

View File

@@ -0,0 +1,154 @@
0.008450416037156572,0.5335
0.046902201120156306,0.5345
0.08508127850753233,0.537
0.1979822644959155,0.2605
0.21189035697274505,0.2625
0.2587960806200922,0.26475
0.3043382657893199,0.2675
0.34660795861105775,0.27075
0.43632879047139106,0.59
0.4739624524675188,0.60025
0.5137777760286397,0.611
0.5492297764597742,0.6265
0.5895905154121426,0.64
0.6253152235389017,0.6565
0.6645851317087743,0.676
0.6997644244442851,0.6975
0.7785769484796541,0.3345
0.7772134100015329,0.74575
0.8652979956881222,0.3315
0.8996591653367609,0.31775
0.9397471965935056,0.31275
0.9847439663714841,0.31125
1.0283771976713423,0.31325
1.0641019057981014,0.31975
1.1009174447073562,0.3335
1.2012738766970301,0.92275
1.2397256617800307,0.95325
1.2779047391674068,0.9865
1.316629231946031,1.01775
1.3561718478115274,1.011
1.3948963405901518,1.0055
1.4330754179775278,1.00225
1.4731634492342724,0.99975
1.5113425266216485,0.9975
1.5517032655740168,1.001
1.5896096352657691,1.00275
1.6288795434356418,1.008
1.6684221593011381,1.0135
1.7066012366885142,1.022
1.7453257294671385,1.02875
1.7862318838107551,0.9935
1.8257744996762515,1.0025
1.8661352386286207,0.96075
1.9045870237116205,0.92125
1.9465840088377355,0.8855
1.986944747790103,0.85725
2.025669240568728,0.832
2.065757271825472,0.80675
2.1066634261690886,0.78875
2.1464787497302105,0.7705
2.1865667809869542,0.75625
2.2261093968524506,0.74475
2.2683790896741876,0.68275
2.3090125363221823,0.6375
2.3510095214482956,0.59925
2.3916429680962885,0.5665
2.4330945378311526,0.538
2.4783640153047557,0.50825
2.5203610004308707,0.4875
2.562903400948233,0.46825
2.599173524466238,0.45
2.642806755766097,0.4355
2.685076448587836,0.42275
2.722437402888339,0.4125
2.766888757275069,0.40125
2.8007045115324587,0.39525
2.841883373571701,0.385
2.8819714048284446,0.3805
2.922332143780814,0.38575
2.9637837135156797,0.38425
3.0005992524249336,0.36575
3.0401418682904318,0.3765
3.079957191851552,0.3915
3.115409192282687,0.408
3.154679100452558,0.4265
3.184949654666836,0.447
3.2242195628367085,0.4715
3.2574899017028507,0.49875
3.2959416867858504,0.52875
3.3292120256519926,0.564
3.3665729799524957,0.6055
3.4031158111661277,0.6515
3.438022396206014,0.70675
3.4756560582021407,0.771
3.513562427893893,0.77075
3.5522869206725183,0.7785
3.621827383056667,0.79575
3.65918833735717,0.8045
3.697367414744546,0.81725
3.7377281536969154,0.83325
3.775634523388667,0.8415
3.8135408930804187,0.85575
3.8522653858590425,0.87325
3.8898990478551703,0.88725
3.9299870791119154,0.906
3.9665299103255465,0.9265
4.006072526191043,0.94575
4.043978895882795,0.97175
4.081885265574547,1.02075
4.1206097583531704,1.046
4.1587888357405465,1.07025
4.196422497736674,1.097
4.234874282819675,1.12575
4.286688744988257,0.73475
4.324322406984384,0.72225
4.364410438241129,0.731
4.405862007975994,0.7405
4.44267754688525,0.749
4.484129116620115,0.76025
4.522853609398739,0.76825
4.560759979090491,0.77125
4.5989390564778665,0.77725
4.640117918517108,0.782
4.679115118991357,0.78425
4.717294196378733,0.789
4.757109519939853,0.78825
4.796652135805349,0.7855
4.8337403824102285,0.786
4.871646752101981,0.78275
4.9109166602718535,0.7785
4.950186568441726,0.7635
4.990274599698471,0.74725
5.028180969390222,0.737
5.0677235852557185,0.72575
5.109720570381833,0.71525
5.149263186247329,0.70625
5.1863514328522085,0.69975
5.230530079543315,0.693
5.269799987713188,0.68925
5.307979065100563,0.68425
5.347248973270435,0.68275
5.386518881440308,0.68075
5.426606912697053,0.68175
5.465604113171301,0.67825
5.502419652080556,0.6835
5.543871221815422,0.6885
5.580959468420302,0.67925
5.624319992024535,0.6555
5.660044700151294,0.639
5.700950854494911,0.623
5.740220762664784,0.6075
5.783581286269018,0.59475
5.820124117482649,0.58475
5.861848394913139,0.57325
5.899209349213642,0.565
5.938751965079138,0.55525
5.9782945809446355,0.55175
6.017564489114507,0.546
6.059288766544997,0.5405
6.097467843932373,0.53825
6.139464829058487,0.534
6.176825783358991,0.5325
6.215822983833238,0.53125
6.252911230438118,0.53075
1 0.008450416037156572 0.5335
2 0.046902201120156306 0.5345
3 0.08508127850753233 0.537
4 0.1979822644959155 0.2605
5 0.21189035697274505 0.2625
6 0.2587960806200922 0.26475
7 0.3043382657893199 0.2675
8 0.34660795861105775 0.27075
9 0.43632879047139106 0.59
10 0.4739624524675188 0.60025
11 0.5137777760286397 0.611
12 0.5492297764597742 0.6265
13 0.5895905154121426 0.64
14 0.6253152235389017 0.6565
15 0.6645851317087743 0.676
16 0.6997644244442851 0.6975
17 0.7785769484796541 0.3345
18 0.7772134100015329 0.74575
19 0.8652979956881222 0.3315
20 0.8996591653367609 0.31775
21 0.9397471965935056 0.31275
22 0.9847439663714841 0.31125
23 1.0283771976713423 0.31325
24 1.0641019057981014 0.31975
25 1.1009174447073562 0.3335
26 1.2012738766970301 0.92275
27 1.2397256617800307 0.95325
28 1.2779047391674068 0.9865
29 1.316629231946031 1.01775
30 1.3561718478115274 1.011
31 1.3948963405901518 1.0055
32 1.4330754179775278 1.00225
33 1.4731634492342724 0.99975
34 1.5113425266216485 0.9975
35 1.5517032655740168 1.001
36 1.5896096352657691 1.00275
37 1.6288795434356418 1.008
38 1.6684221593011381 1.0135
39 1.7066012366885142 1.022
40 1.7453257294671385 1.02875
41 1.7862318838107551 0.9935
42 1.8257744996762515 1.0025
43 1.8661352386286207 0.96075
44 1.9045870237116205 0.92125
45 1.9465840088377355 0.8855
46 1.986944747790103 0.85725
47 2.025669240568728 0.832
48 2.065757271825472 0.80675
49 2.1066634261690886 0.78875
50 2.1464787497302105 0.7705
51 2.1865667809869542 0.75625
52 2.2261093968524506 0.74475
53 2.2683790896741876 0.68275
54 2.3090125363221823 0.6375
55 2.3510095214482956 0.59925
56 2.3916429680962885 0.5665
57 2.4330945378311526 0.538
58 2.4783640153047557 0.50825
59 2.5203610004308707 0.4875
60 2.562903400948233 0.46825
61 2.599173524466238 0.45
62 2.642806755766097 0.4355
63 2.685076448587836 0.42275
64 2.722437402888339 0.4125
65 2.766888757275069 0.40125
66 2.8007045115324587 0.39525
67 2.841883373571701 0.385
68 2.8819714048284446 0.3805
69 2.922332143780814 0.38575
70 2.9637837135156797 0.38425
71 3.0005992524249336 0.36575
72 3.0401418682904318 0.3765
73 3.079957191851552 0.3915
74 3.115409192282687 0.408
75 3.154679100452558 0.4265
76 3.184949654666836 0.447
77 3.2242195628367085 0.4715
78 3.2574899017028507 0.49875
79 3.2959416867858504 0.52875
80 3.3292120256519926 0.564
81 3.3665729799524957 0.6055
82 3.4031158111661277 0.6515
83 3.438022396206014 0.70675
84 3.4756560582021407 0.771
85 3.513562427893893 0.77075
86 3.5522869206725183 0.7785
87 3.621827383056667 0.79575
88 3.65918833735717 0.8045
89 3.697367414744546 0.81725
90 3.7377281536969154 0.83325
91 3.775634523388667 0.8415
92 3.8135408930804187 0.85575
93 3.8522653858590425 0.87325
94 3.8898990478551703 0.88725
95 3.9299870791119154 0.906
96 3.9665299103255465 0.9265
97 4.006072526191043 0.94575
98 4.043978895882795 0.97175
99 4.081885265574547 1.02075
100 4.1206097583531704 1.046
101 4.1587888357405465 1.07025
102 4.196422497736674 1.097
103 4.234874282819675 1.12575
104 4.286688744988257 0.73475
105 4.324322406984384 0.72225
106 4.364410438241129 0.731
107 4.405862007975994 0.7405
108 4.44267754688525 0.749
109 4.484129116620115 0.76025
110 4.522853609398739 0.76825
111 4.560759979090491 0.77125
112 4.5989390564778665 0.77725
113 4.640117918517108 0.782
114 4.679115118991357 0.78425
115 4.717294196378733 0.789
116 4.757109519939853 0.78825
117 4.796652135805349 0.7855
118 4.8337403824102285 0.786
119 4.871646752101981 0.78275
120 4.9109166602718535 0.7785
121 4.950186568441726 0.7635
122 4.990274599698471 0.74725
123 5.028180969390222 0.737
124 5.0677235852557185 0.72575
125 5.109720570381833 0.71525
126 5.149263186247329 0.70625
127 5.1863514328522085 0.69975
128 5.230530079543315 0.693
129 5.269799987713188 0.68925
130 5.307979065100563 0.68425
131 5.347248973270435 0.68275
132 5.386518881440308 0.68075
133 5.426606912697053 0.68175
134 5.465604113171301 0.67825
135 5.502419652080556 0.6835
136 5.543871221815422 0.6885
137 5.580959468420302 0.67925
138 5.624319992024535 0.6555
139 5.660044700151294 0.639
140 5.700950854494911 0.623
141 5.740220762664784 0.6075
142 5.783581286269018 0.59475
143 5.820124117482649 0.58475
144 5.861848394913139 0.57325
145 5.899209349213642 0.565
146 5.938751965079138 0.55525
147 5.9782945809446355 0.55175
148 6.017564489114507 0.546
149 6.059288766544997 0.5405
150 6.097467843932373 0.53825
151 6.139464829058487 0.534
152 6.176825783358991 0.5325
153 6.215822983833238 0.53125
154 6.252911230438118 0.53075

View File

@@ -0,0 +1,220 @@
"""
LIDAR to 2D grid map example
author: Erno Horvath, Csaba Hajdu based on Atsushi Sakai's scripts (@Atsushi_twi)
"""
import math
import numpy as np
import matplotlib.pyplot as plt
from collections import deque
EXTEND_AREA = 1.0
def file_read(f):
"""
Reading LIDAR laser beams (angles and corresponding distance data)
"""
measures = [line.split(",") for line in open(f)]
angles = []
distances = []
for measure in measures:
angles.append(float(measure[0]))
distances.append(float(measure[1]))
angles = np.array(angles)
distances = np.array(distances)
return angles, distances
def bresenham(start, end):
"""
Implementation of Bresenham's line drawing algorithm
See en.wikipedia.org/wiki/Bresenham's_line_algorithm
Bresenham's Line Algorithm
Produces a np.array from start and end (original from roguebasin.com)
>>> points1 = bresenham((4, 4), (6, 10))
>>> print(points1)
np.array([[4,4], [4,5], [5,6], [5,7], [5,8], [6,9], [6,10]])
"""
# setup initial conditions
x1, y1 = start
x2, y2 = end
dx = x2 - x1
dy = y2 - y1
is_steep = abs(dy) > abs(dx) # determine how steep the line is
if is_steep: # rotate line
x1, y1 = y1, x1
x2, y2 = y2, x2
swapped = False # swap start and end points if necessary and store swap state
if x1 > x2:
x1, x2 = x2, x1
y1, y2 = y2, y1
swapped = True
dx = x2 - x1 # recalculate differentials
dy = y2 - y1 # recalculate differentials
error = int(dx / 2.0) # calculate error
ystep = 1 if y1 < y2 else -1
# iterate over bounding box generating points between start and end
y = y1
points = []
for x in range(x1, x2 + 1):
coord = [y, x] if is_steep else (x, y)
points.append(coord)
error -= abs(dy)
if error < 0:
y += ystep
error += dx
if swapped: # reverse the list if the coordinates were swapped
points.reverse()
points = np.array(points)
return points
def calc_grid_map_config(ox, oy, xyreso):
"""
Calculates the size, and the maximum distances according to the the measurement center
"""
minx = round(min(ox) - EXTEND_AREA / 2.0)
miny = round(min(oy) - EXTEND_AREA / 2.0)
maxx = round(max(ox) + EXTEND_AREA / 2.0)
maxy = round(max(oy) + EXTEND_AREA / 2.0)
xw = int(round((maxx - minx) / xyreso))
yw = int(round((maxy - miny) / xyreso))
print("The grid map is ", xw, "x", yw, ".")
return minx, miny, maxx, maxy, xw, yw
def atan_zero_to_twopi(y, x):
angle = math.atan2(y, x)
if angle < 0.0:
angle += math.pi * 2.0
return angle
def init_floodfill(cpoint, opoints, xypoints, mincoord, xyreso):
"""
cpoint: center point
opoints: detected obstacles points (x,y)
xypoints: (x,y) point pairs
"""
centix, centiy = cpoint
prev_ix, prev_iy = centix - 1, centiy
ox, oy = opoints
xw, yw = xypoints
minx, miny = mincoord
pmap = (np.ones((xw, yw))) * 0.5
for (x, y) in zip(ox, oy):
ix = int(round((x - minx) / xyreso)) # x coordinate of the the occupied area
iy = int(round((y - miny) / xyreso)) # y coordinate of the the occupied area
free_area = bresenham((prev_ix, prev_iy), (ix, iy))
for fa in free_area:
pmap[fa[0]][fa[1]] = 0 # free area 0.0
prev_ix = ix
prev_iy = iy
return pmap
def flood_fill(cpoint, pmap):
"""
cpoint: starting point (x,y) of fill
pmap: occupancy map generated from Bresenham ray-tracing
"""
# Fill empty areas with queue method
sx, sy = pmap.shape
fringe = deque()
fringe.appendleft(cpoint)
while fringe:
n = fringe.pop()
nx, ny = n
# West
if nx > 0:
if pmap[nx - 1, ny] == 0.5:
pmap[nx - 1, ny] = 0.0
fringe.appendleft((nx - 1, ny))
# East
if nx < sx - 1:
if pmap[nx + 1, ny] == 0.5:
pmap[nx + 1, ny] = 0.0
fringe.appendleft((nx + 1, ny))
# North
if ny > 0:
if pmap[nx, ny - 1] == 0.5:
pmap[nx, ny - 1] = 0.0
fringe.appendleft((nx, ny - 1))
# South
if ny < sy - 1:
if pmap[nx, ny + 1] == 0.5:
pmap[nx, ny + 1] = 0.0
fringe.appendleft((nx, ny + 1))
def generate_ray_casting_grid_map(ox, oy, xyreso, breshen=True):
"""
The breshen boolean tells if it's computed with bresenham ray casting (True) or with flood fill (False)
"""
minx, miny, maxx, maxy, xw, yw = calc_grid_map_config(ox, oy, xyreso)
pmap = np.ones((xw, yw))/2 # default 0.5 -- [[0.5 for i in range(yw)] for i in range(xw)]
centix = int(round(-minx / xyreso)) # center x coordinate of the grid map
centiy = int(round(-miny / xyreso)) # center y coordinate of the grid map
# occupancy grid computed with bresenham ray casting
if breshen:
for (x, y) in zip(ox, oy):
ix = int(round((x - minx) / xyreso)) # x coordinate of the the occupied area
iy = int(round((y - miny) / xyreso)) # y coordinate of the the occupied area
laser_beams = bresenham((centix, centiy), (ix, iy)) # line form the lidar to the cooupied point
for laser_beam in laser_beams:
pmap[laser_beam[0]][laser_beam[1]] = 0.0 # free area 0.0
pmap[ix][iy] = 1.0 # occupied area 1.0
pmap[ix+1][iy] = 1.0 # extend the occupied area
pmap[ix][iy+1] = 1.0 # extend the occupied area
pmap[ix+1][iy+1] = 1.0 # extend the occupied area
# occupancy grid computed with with flood fill
else:
pmap = init_floodfill((centix, centiy), (ox, oy), (xw, yw), (minx, miny), xyreso)
flood_fill((centix, centiy), pmap)
pmap = np.array(pmap, dtype=np.float)
for (x, y) in zip(ox, oy):
ix = int(round((x - minx) / xyreso))
iy = int(round((y - miny) / xyreso))
pmap[ix][iy] = 1.0 # occupied area 1.0
pmap[ix+1][iy] = 1.0 # extend the occupied area
pmap[ix][iy+1] = 1.0 # extend the occupied area
pmap[ix+1][iy+1] = 1.0 # extend the occupied area
return pmap, minx, maxx, miny, maxy, xyreso
def main():
"""
Example usage
"""
print(__file__, "start")
xyreso = 0.02 # x-y grid resolution
ang, dist = file_read("lidar01.csv")
ox = np.sin(ang) * dist
oy = np.cos(ang) * dist
pmap, minx, maxx, miny, maxy, xyreso = generate_ray_casting_grid_map(ox, oy, xyreso, True)
xyres = np.array(pmap).shape
plt.figure(1, figsize=(10,4))
plt.subplot(122)
plt.imshow(pmap, cmap="PiYG_r") # cmap = "binary" "PiYG_r" "PiYG_r" "bone" "bone_r" "RdYlGn_r"
plt.clim(-0.4, 1.4)
plt.gca().set_xticks(np.arange(-.5, xyres[1], 1), minor=True)
plt.gca().set_yticks(np.arange(-.5, xyres[0], 1), minor=True)
plt.grid(True, which="minor", color="w", linewidth=0.6, alpha=0.5)
plt.colorbar()
plt.subplot(121)
plt.plot([oy, np.zeros(np.size(oy))], [ox, np.zeros(np.size(oy))], "ro-")
plt.axis("equal")
plt.plot(0.0, 0.0, "ob")
plt.gca().set_aspect("equal", "box")
bottom, top = plt.ylim() # return the current ylim
plt.ylim((top, bottom)) # rescale y axis, to match the grid orientation
plt.grid(True)
plt.show()
if __name__ == '__main__':
main()

File diff suppressed because one or more lines are too long

View File

@@ -14,172 +14,191 @@ import math
show_animation = True
class AStarPlanner:
class Node:
def __init__(self, ox, oy, reso, rr):
"""
Intialize map for a star planning
def __init__(self, x, y, cost, pind):
self.x = x
self.y = y
self.cost = cost
self.pind = pind
ox: x position list of Obstacles [m]
oy: y position list of Obstacles [m]
reso: grid resolution [m]
rr: robot radius[m]
"""
def __str__(self):
return str(self.x) + "," + str(self.y) + "," + str(self.cost) + "," + str(self.pind)
self.reso = reso
self.rr = rr
self.calc_obstacle_map(ox, oy)
self.motion = self.get_motion_model()
class Node:
def __init__(self, x, y, cost, pind):
self.x = x # index of grid
self.y = y # index of grid
self.cost = cost
self.pind = pind
def calc_final_path(ngoal, closedset, reso):
# generate final course
rx, ry = [ngoal.x * reso], [ngoal.y * reso]
pind = ngoal.pind
while pind != -1:
n = closedset[pind]
rx.append(n.x * reso)
ry.append(n.y * reso)
pind = n.pind
def __str__(self):
return str(self.x) + "," + str(self.y) + "," + str(self.cost) + "," + str(self.pind)
return rx, ry
def planning(self, sx, sy, gx, gy):
"""
A star path search
input:
sx: start x position [m]
sy: start y position [m]
gx: goal x position [m]
gx: goal x position [m]
def a_star_planning(sx, sy, gx, gy, ox, oy, reso, rr):
"""
gx: goal x position [m]
gx: goal x position [m]
ox: x position list of Obstacles [m]
oy: y position list of Obstacles [m]
reso: grid resolution [m]
rr: robot radius[m]
"""
output:
rx: x position list of the final path
ry: y position list of the final path
"""
nstart = Node(round(sx / reso), round(sy / reso), 0.0, -1)
ngoal = Node(round(gx / reso), round(gy / reso), 0.0, -1)
ox = [iox / reso for iox in ox]
oy = [ioy / reso for ioy in oy]
nstart = self.Node(self.calc_xyindex(sx, self.minx),
self.calc_xyindex(sy, self.miny), 0.0, -1)
ngoal = self.Node(self.calc_xyindex(gx, self.minx),
self.calc_xyindex(gy, self.miny), 0.0, -1)
obmap, minx, miny, maxx, maxy, xw, yw = calc_obstacle_map(ox, oy, reso, rr)
openset, closedset = dict(), dict()
openset[self.calc_index(nstart)] = nstart
motion = get_motion_model()
while 1:
c_id = min(
openset, key=lambda o: openset[o].cost + self.calc_heuristic(ngoal, openset[o]))
current = openset[c_id]
openset, closedset = dict(), dict()
openset[calc_index(nstart, xw, minx, miny)] = nstart
# show graph
if show_animation: # pragma: no cover
plt.plot(self.calc_position(current.x, self.minx),
self.calc_position(current.y, self.miny), "xc")
if len(closedset.keys()) % 10 == 0:
plt.pause(0.001)
while 1:
c_id = min(
openset, key=lambda o: openset[o].cost + calc_heuristic(ngoal, openset[o]))
current = openset[c_id]
if current.x == ngoal.x and current.y == ngoal.y:
print("Find goal")
ngoal.pind = current.pind
ngoal.cost = current.cost
break
# show graph
if show_animation: # pragma: no cover
plt.plot(current.x * reso, current.y * reso, "xc")
if len(closedset.keys()) % 10 == 0:
plt.pause(0.001)
# Remove the item from the open set
del openset[c_id]
if current.x == ngoal.x and current.y == ngoal.y:
print("Find goal")
ngoal.pind = current.pind
ngoal.cost = current.cost
break
# Add it to the closed set
closedset[c_id] = current
# Remove the item from the open set
del openset[c_id]
# Add it to the closed set
closedset[c_id] = current
# expand search grid based on motion model
for i, _ in enumerate(self.motion):
node = self.Node(current.x + self.motion[i][0],
current.y + self.motion[i][1],
current.cost + self.motion[i][2], c_id)
n_id = self.calc_index(node)
# expand search grid based on motion model
for i, _ in enumerate(motion):
node = Node(current.x + motion[i][0],
current.y + motion[i][1],
current.cost + motion[i][2], c_id)
n_id = calc_index(node, xw, minx, miny)
if n_id in closedset:
continue
if n_id in closedset:
continue
if not self.verify_node(node):
continue
if not verify_node(node, obmap, minx, miny, maxx, maxy):
continue
if n_id not in openset:
openset[n_id] = node # Discover a new node
else:
if openset[n_id].cost >= node.cost:
# This path is the best until now. record it!
openset[n_id] = node
if n_id not in openset:
openset[n_id] = node # Discover a new node
else:
if openset[n_id].cost >= node.cost:
# This path is the best until now. record it!
openset[n_id] = node
rx, ry = self.calc_final_path(ngoal, closedset)
rx, ry = calc_final_path(ngoal, closedset, reso)
return rx, ry
return rx, ry
def calc_final_path(self, ngoal, closedset):
# generate final course
rx, ry = [self.calc_position(ngoal.x, self.minx)], [
self.calc_position(ngoal.y, self.miny)]
pind = ngoal.pind
while pind != -1:
n = closedset[pind]
rx.append(self.calc_position(n.x, self.minx))
ry.append(self.calc_position(n.y, self.miny))
pind = n.pind
return rx, ry
def calc_heuristic(n1, n2):
w = 1.0 # weight of heuristic
d = w * math.sqrt((n1.x - n2.x)**2 + (n1.y - n2.y)**2)
return d
def calc_heuristic(self, n1, n2):
w = 1.0 # weight of heuristic
d = w * math.sqrt((n1.x - n2.x)**2 + (n1.y - n2.y)**2)
return d
def calc_position(self, index, minp):
pos = index*self.reso+minp
return pos
def verify_node(node, obmap, minx, miny, maxx, maxy):
def calc_xyindex(self, position, minp):
return round((position - minp)/self.reso)
if node.x < minx:
return False
elif node.y < miny:
return False
elif node.x >= maxx:
return False
elif node.y >= maxy:
return False
def calc_index(self, node):
return (node.y - self.miny) * self.xwidth + (node.x - self.minx)
if obmap[node.x][node.y]:
return False
def verify_node(self, node):
px = self.calc_position(node.x, self.minx)
py = self.calc_position(node.y, self.miny)
return True
if px < self.minx:
return False
elif py < self.miny:
return False
elif px >= self.maxx:
return False
elif py >= self.maxy:
return False
if self.obmap[node.x][node.y]:
return False
def calc_obstacle_map(ox, oy, reso, vr):
return True
minx = round(min(ox))
miny = round(min(oy))
maxx = round(max(ox))
maxy = round(max(oy))
# print("minx:", minx)
# print("miny:", miny)
# print("maxx:", maxx)
# print("maxy:", maxy)
def calc_obstacle_map(self, ox, oy):
xwidth = round(maxx - minx)
ywidth = round(maxy - miny)
# print("xwidth:", xwidth)
# print("ywidth:", ywidth)
self.minx = round(min(ox))
self.miny = round(min(oy))
self.maxx = round(max(ox))
self.maxy = round(max(oy))
print("minx:", self.minx)
print("miny:", self.miny)
print("maxx:", self.maxx)
print("maxy:", self.maxy)
# obstacle map generation
obmap = [[False for i in range(ywidth)] for i in range(xwidth)]
for ix in range(xwidth):
x = ix + minx
for iy in range(ywidth):
y = iy + miny
# print(x, y)
for iox, ioy in zip(ox, oy):
d = math.sqrt((iox - x)**2 + (ioy - y)**2)
if d <= vr / reso:
obmap[ix][iy] = True
break
self.xwidth = round((self.maxx - self.minx)/self.reso)
self.ywidth = round((self.maxy - self.miny)/self.reso)
print("xwidth:", self.xwidth)
print("ywidth:", self.ywidth)
return obmap, minx, miny, maxx, maxy, xwidth, ywidth
# obstacle map generation
self.obmap = [[False for i in range(self.ywidth)]
for i in range(self.xwidth)]
for ix in range(self.xwidth):
x = self.calc_position(ix, self.minx)
for iy in range(self.ywidth):
y = self.calc_position(iy, self.miny)
for iox, ioy in zip(ox, oy):
d = math.sqrt((iox - x)**2 + (ioy - y)**2)
if d <= self.rr:
self.obmap[ix][iy] = True
break
def get_motion_model(self):
# dx, dy, cost
motion = [[1, 0, 1],
[0, 1, 1],
[-1, 0, 1],
[0, -1, 1],
[-1, -1, math.sqrt(2)],
[-1, 1, math.sqrt(2)],
[1, -1, math.sqrt(2)],
[1, 1, math.sqrt(2)]]
def calc_index(node, xwidth, xmin, ymin):
return (node.y - ymin) * xwidth + (node.x - xmin)
def get_motion_model():
# dx, dy, cost
motion = [[1, 0, 1],
[0, 1, 1],
[-1, 0, 1],
[0, -1, 1],
[-1, -1, math.sqrt(2)],
[-1, 1, math.sqrt(2)],
[1, -1, math.sqrt(2)],
[1, 1, math.sqrt(2)]]
return motion
return motion
def main():
@@ -190,38 +209,39 @@ def main():
sy = 10.0 # [m]
gx = 50.0 # [m]
gy = 50.0 # [m]
grid_size = 1.0 # [m]
robot_size = 1.0 # [m]
grid_size = 2.0 # [m]
robot_radius = 1.0 # [m]
# set obstable positions
ox, oy = [], []
for i in range(60):
for i in range(-10, 60):
ox.append(i)
oy.append(0.0)
for i in range(60):
oy.append(-10.0)
for i in range(-10, 60):
ox.append(60.0)
oy.append(i)
for i in range(61):
for i in range(-10, 61):
ox.append(i)
oy.append(60.0)
for i in range(61):
ox.append(0.0)
for i in range(-10, 61):
ox.append(-10.0)
oy.append(i)
for i in range(40):
for i in range(-10, 40):
ox.append(20.0)
oy.append(i)
for i in range(40):
for i in range(0, 40):
ox.append(40.0)
oy.append(60.0 - i)
if show_animation: # pragma: no cover
plt.plot(ox, oy, ".k")
plt.plot(sx, sy, "xr")
plt.plot(sx, sy, "og")
plt.plot(gx, gy, "xb")
plt.grid(True)
plt.axis("equal")
rx, ry = a_star_planning(sx, sy, gx, gy, ox, oy, grid_size, robot_size)
a_star = AStarPlanner(ox, oy, grid_size, robot_radius)
rx, ry = a_star.planning(sx, sy, gx, gy)
if show_animation: # pragma: no cover
plt.plot(rx, ry, "-r")

View File

@@ -1,7 +1,9 @@
"""
Dijkstra grid based planning
Grid based Dijkstra planning
author: Atsushi Sakai(@Atsushi_twi)
"""
import matplotlib.pyplot as plt
@@ -9,210 +11,235 @@ import math
show_animation = True
class Dijkstra:
class Node:
def __init__(self, ox, oy, reso, rr):
"""
Initialize map for a star planning
def __init__(self, x, y, cost, pind):
self.x = x
self.y = y
self.cost = cost
self.pind = pind
ox: x position list of Obstacles [m]
oy: y position list of Obstacles [m]
reso: grid resolution [m]
rr: robot radius[m]
"""
def __str__(self):
return str(self.x) + "," + str(self.y) + "," + str(self.cost) + "," + str(self.pind)
self.reso = reso
self.rr = rr
self.calc_obstacle_map(ox, oy)
self.motion = self.get_motion_model()
class Node:
def __init__(self, x, y, cost, pind):
self.x = x # index of grid
self.y = y # index of grid
self.cost = cost
self.pind = pind
def dijkstra_planning(sx, sy, gx, gy, ox, oy, reso, rr):
"""
gx: goal x position [m]
gx: goal x position [m]
ox: x position list of Obstacles [m]
oy: y position list of Obstacles [m]
reso: grid resolution [m]
rr: robot radius[m]
"""
def __str__(self):
return str(self.x) + "," + str(self.y) + "," + str(self.cost) + "," + str(self.pind)
nstart = Node(round(sx / reso), round(sy / reso), 0.0, -1)
ngoal = Node(round(gx / reso), round(gy / reso), 0.0, -1)
ox = [iox / reso for iox in ox]
oy = [ioy / reso for ioy in oy]
def planning(self, sx, sy, gx, gy):
"""
dijkstra path search
obmap, minx, miny, maxx, maxy, xw, yw = calc_obstacle_map(ox, oy, reso, rr)
input:
sx: start x position [m]
sy: start y position [m]
gx: goal x position [m]
gx: goal x position [m]
motion = get_motion_model()
output:
rx: x position list of the final path
ry: y position list of the final path
"""
openset, closedset = dict(), dict()
openset[calc_index(nstart, xw, minx, miny)] = nstart
nstart = self.Node(self.calc_xyindex(sx, self.minx),
self.calc_xyindex(sy, self.miny), 0.0, -1)
ngoal = self.Node(self.calc_xyindex(gx, self.minx),
self.calc_xyindex(gy, self.miny), 0.0, -1)
while 1:
c_id = min(openset, key=lambda o: openset[o].cost)
current = openset[c_id]
# print("current", current)
openset, closedset = dict(), dict()
openset[self.calc_index(nstart)] = nstart
# show graph
if show_animation:
plt.plot(current.x * reso, current.y * reso, "xc")
if len(closedset.keys()) % 10 == 0:
plt.pause(0.001)
while 1:
c_id = min(openset, key=lambda o: openset[o].cost)
current = openset[c_id]
if current.x == ngoal.x and current.y == ngoal.y:
print("Find goal")
ngoal.pind = current.pind
ngoal.cost = current.cost
break
# show graph
if show_animation: # pragma: no cover
plt.plot(self.calc_position(current.x, self.minx),
self.calc_position(current.y, self.miny), "xc")
if len(closedset.keys()) % 10 == 0:
plt.pause(0.001)
# Remove the item from the open set
del openset[c_id]
# Add it to the closed set
closedset[c_id] = current
if current.x == ngoal.x and current.y == ngoal.y:
print("Find goal")
ngoal.pind = current.pind
ngoal.cost = current.cost
break
# expand search grid based on motion model
for i, _ in enumerate(motion):
node = Node(current.x + motion[i][0], current.y + motion[i][1],
current.cost + motion[i][2], c_id)
n_id = calc_index(node, xw, minx, miny)
# Remove the item from the open set
del openset[c_id]
if not verify_node(node, obmap, minx, miny, maxx, maxy):
continue
# Add it to the closed set
closedset[c_id] = current
if n_id in closedset:
continue
# Otherwise if it is already in the open set
if n_id in openset:
if openset[n_id].cost > node.cost:
openset[n_id].cost = node.cost
openset[n_id].pind = c_id
else:
openset[n_id] = node
# expand search grid based on motion model
for i, _ in enumerate(self.motion):
node = self.Node(current.x + self.motion[i][0],
current.y + self.motion[i][1],
current.cost + self.motion[i][2], c_id)
n_id = self.calc_index(node)
rx, ry = calc_final_path(ngoal, closedset, reso)
if n_id in closedset:
continue
return rx, ry
if not self.verify_node(node):
continue
if n_id not in openset:
openset[n_id] = node # Discover a new node
else:
if openset[n_id].cost >= node.cost:
# This path is the best until now. record it!
openset[n_id] = node
def calc_final_path(ngoal, closedset, reso):
# generate final course
rx, ry = [ngoal.x * reso], [ngoal.y * reso]
pind = ngoal.pind
while pind != -1:
n = closedset[pind]
rx.append(n.x * reso)
ry.append(n.y * reso)
pind = n.pind
rx, ry = self.calc_final_path(ngoal, closedset)
return rx, ry
return rx, ry
def calc_final_path(self, ngoal, closedset):
# generate final course
rx, ry = [self.calc_position(ngoal.x, self.minx)], [
self.calc_position(ngoal.y, self.miny)]
pind = ngoal.pind
while pind != -1:
n = closedset[pind]
rx.append(self.calc_position(n.x, self.minx))
ry.append(self.calc_position(n.y, self.miny))
pind = n.pind
def verify_node(node, obmap, minx, miny, maxx, maxy):
return rx, ry
if obmap[node.x][node.y]:
return False
def calc_heuristic(self, n1, n2):
w = 1.0 # weight of heuristic
d = w * math.sqrt((n1.x - n2.x)**2 + (n1.y - n2.y)**2)
return d
if node.x < minx:
return False
elif node.y < miny:
return False
elif node.x > maxx:
return False
elif node.y > maxy:
return False
def calc_position(self, index, minp):
pos = index*self.reso+minp
return pos
return True
def calc_xyindex(self, position, minp):
return round((position - minp)/self.reso)
def calc_index(self, node):
return (node.y - self.miny) * self.xwidth + (node.x - self.minx)
def calc_obstacle_map(ox, oy, reso, vr):
def verify_node(self, node):
px = self.calc_position(node.x, self.minx)
py = self.calc_position(node.y, self.miny)
minx = round(min(ox))
miny = round(min(oy))
maxx = round(max(ox))
maxy = round(max(oy))
# print("minx:", minx)
# print("miny:", miny)
# print("maxx:", maxx)
# print("maxy:", maxy)
if px < self.minx:
return False
elif py < self.miny:
return False
elif px >= self.maxx:
return False
elif py >= self.maxy:
return False
xwidth = round(maxx - minx)
ywidth = round(maxy - miny)
# print("xwidth:", xwidth)
# print("ywidth:", ywidth)
if self.obmap[node.x][node.y]:
return False
# obstacle map generation
obmap = [[False for i in range(ywidth)] for i in range(xwidth)]
for ix in range(xwidth):
x = ix + minx
for iy in range(ywidth):
y = iy + miny
# print(x, y)
for iox, ioy in zip(ox, oy):
d = math.sqrt((iox - x)**2 + (ioy - y)**2)
if d <= vr / reso:
obmap[ix][iy] = True
break
return True
return obmap, minx, miny, maxx, maxy, xwidth, ywidth
def calc_obstacle_map(self, ox, oy):
self.minx = round(min(ox))
self.miny = round(min(oy))
self.maxx = round(max(ox))
self.maxy = round(max(oy))
print("minx:", self.minx)
print("miny:", self.miny)
print("maxx:", self.maxx)
print("maxy:", self.maxy)
def calc_index(node, xwidth, xmin, ymin):
return (node.y - ymin) * xwidth + (node.x - xmin)
self.xwidth = round((self.maxx - self.minx)/self.reso)
self.ywidth = round((self.maxy - self.miny)/self.reso)
print("xwidth:", self.xwidth)
print("ywidth:", self.ywidth)
# obstacle map generation
self.obmap = [[False for i in range(self.ywidth)]
for i in range(self.xwidth)]
for ix in range(self.xwidth):
x = self.calc_position(ix, self.minx)
for iy in range(self.ywidth):
y = self.calc_position(iy, self.miny)
for iox, ioy in zip(ox, oy):
d = math.sqrt((iox - x)**2 + (ioy - y)**2)
if d <= self.rr:
self.obmap[ix][iy] = True
break
def get_motion_model():
# dx, dy, cost
motion = [[1, 0, 1],
[0, 1, 1],
[-1, 0, 1],
[0, -1, 1],
[-1, -1, math.sqrt(2)],
[-1, 1, math.sqrt(2)],
[1, -1, math.sqrt(2)],
[1, 1, math.sqrt(2)]]
def get_motion_model(self):
# dx, dy, cost
motion = [[1, 0, 1],
[0, 1, 1],
[-1, 0, 1],
[0, -1, 1],
[-1, -1, math.sqrt(2)],
[-1, 1, math.sqrt(2)],
[1, -1, math.sqrt(2)],
[1, 1, math.sqrt(2)]]
return motion
return motion
def main():
print(__file__ + " start!!")
# start and goal position
sx = 10.0 # [m]
sy = 10.0 # [m]
sx = -5.0 # [m]
sy = -5.0 # [m]
gx = 50.0 # [m]
gy = 50.0 # [m]
grid_size = 1.0 # [m]
robot_size = 1.0 # [m]
grid_size = 2.0 # [m]
robot_radius = 1.0 # [m]
ox = []
oy = []
for i in range(60):
# set obstacle positions
ox, oy = [], []
for i in range(-10, 60):
ox.append(i)
oy.append(0.0)
for i in range(60):
oy.append(-10.0)
for i in range(-10, 60):
ox.append(60.0)
oy.append(i)
for i in range(61):
for i in range(-10, 61):
ox.append(i)
oy.append(60.0)
for i in range(61):
ox.append(0.0)
for i in range(-10, 61):
ox.append(-10.0)
oy.append(i)
for i in range(40):
for i in range(-10, 40):
ox.append(20.0)
oy.append(i)
for i in range(40):
for i in range(0, 40):
ox.append(40.0)
oy.append(60.0 - i)
if show_animation:
if show_animation: # pragma: no cover
plt.plot(ox, oy, ".k")
plt.plot(sx, sy, "xr")
plt.plot(sx, sy, "og")
plt.plot(gx, gy, "xb")
plt.grid(True)
plt.axis("equal")
rx, ry = dijkstra_planning(sx, sy, gx, gy, ox, oy, grid_size, robot_size)
dijkstra = Dijkstra(ox, oy, grid_size, robot_radius)
rx, ry = dijkstra.planning(sx, sy, gx, gy)
if show_animation:
if show_animation: # pragma: no cover
plt.plot(rx, ry, "-r")
plt.show()

View File

@@ -10,15 +10,26 @@ import math
import numpy as np
import matplotlib.pyplot as plt
import sys
sys.path.append("../../")
show_animation = True
def dwa_control(x, u, config, goal, ob):
"""
Dynamic Window Approach control
"""
dw = calc_dynamic_window(x, config)
u, traj = calc_final_input(x, u, dw, config, goal, ob)
return u, traj
class Config():
# simulation parameters
"""
simulation parameter class
"""
def __init__(self):
# robot parameter
@@ -29,15 +40,19 @@ class Config():
self.max_dyawrate = 40.0 * math.pi / 180.0 # [rad/ss]
self.v_reso = 0.01 # [m/s]
self.yawrate_reso = 0.1 * math.pi / 180.0 # [rad/s]
self.dt = 0.1 # [s]
self.dt = 0.1 # [s] Time tick for motion prediction
self.predict_time = 3.0 # [s]
self.to_goal_cost_gain = 1.0
self.to_goal_cost_gain = 0.15
self.speed_cost_gain = 1.0
self.robot_radius = 1.0 # [m]
self.obstacle_cost_gain = 1.0
self.robot_radius = 1.0 # [m] for collision check
def motion(x, u, dt):
# motion model
"""
motion model
"""
x[2] += u[1] * dt
x[0] += u[0] * math.cos(x[2]) * dt
@@ -49,6 +64,9 @@ def motion(x, u, dt):
def calc_dynamic_window(x, config):
"""
calculation dynamic window based on current state x
"""
# Dynamic window from robot specification
Vs = [config.min_speed, config.max_speed,
@@ -67,9 +85,12 @@ def calc_dynamic_window(x, config):
return dw
def calc_trajectory(xinit, v, y, config):
def predict_trajectory(x_init, v, y, config):
"""
predict trajectory with an input
"""
x = np.array(xinit)
x = np.array(x_init)
traj = np.array(x)
time = 0
while time <= config.predict_time:
@@ -81,42 +102,44 @@ def calc_trajectory(xinit, v, y, config):
def calc_final_input(x, u, dw, config, goal, ob):
"""
calculation final input with dinamic window
"""
xinit = x[:]
min_cost = 10000.0
min_u = u
min_u[0] = 0.0
x_init = x[:]
min_cost = float("inf")
best_u = [0.0, 0.0]
best_traj = np.array([x])
# evalucate all trajectory with sampled input in dynamic window
for v in np.arange(dw[0], dw[1], config.v_reso):
for y in np.arange(dw[2], dw[3], config.yawrate_reso):
traj = calc_trajectory(xinit, v, y, config)
traj = predict_trajectory(x_init, v, y, config)
# calc cost
to_goal_cost = calc_to_goal_cost(traj, goal, config)
to_goal_cost = config.to_goal_cost_gain * calc_to_goal_cost(traj, goal, config)
speed_cost = config.speed_cost_gain * \
(config.max_speed - traj[-1, 3])
ob_cost = calc_obstacle_cost(traj, ob, config)
# print(ob_cost)
ob_cost = config.obstacle_cost_gain*calc_obstacle_cost(traj, ob, config)
final_cost = to_goal_cost + speed_cost + ob_cost
#print (final_cost)
# search minimum trajectory
if min_cost >= final_cost:
min_cost = final_cost
min_u = [v, y]
best_u = [v, y]
best_traj = traj
return min_u, best_traj
return best_u, best_traj
def calc_obstacle_cost(traj, ob, config):
# calc obstacle cost inf: collistion, 0:free
"""
calc obstacle cost inf: collision
"""
skip_n = 2
skip_n = 2 # for speed up
minr = float("inf")
for ii in range(0, len(traj[:, 1]), skip_n):
@@ -137,28 +160,18 @@ def calc_obstacle_cost(traj, ob, config):
def calc_to_goal_cost(traj, goal, config):
# calc to goal cost. It is 2D norm.
"""
calc to goal cost with angle difference
"""
goal_magnitude = math.sqrt(goal[0]**2 + goal[1]**2)
traj_magnitude = math.sqrt(traj[-1, 0]**2 + traj[-1, 1]**2)
dot_product = (goal[0] * traj[-1, 0]) + (goal[1] * traj[-1, 1])
error = dot_product / (goal_magnitude * traj_magnitude)
error_angle = math.acos(error)
cost = config.to_goal_cost_gain * error_angle
dx = goal[0] - traj[-1, 0]
dy = goal[1] - traj[-1, 1]
error_angle = math.atan2(dy, dx)
cost = abs(error_angle - traj[-1, 2])
return cost
def dwa_control(x, u, config, goal, ob):
# Dynamic Window control
dw = calc_dynamic_window(x, config)
u, traj = calc_final_input(x, u, dw, config, goal, ob)
return u, traj
def plot_arrow(x, y, yaw, length=0.5, width=0.1): # pragma: no cover
plt.arrow(x, y, length * math.cos(yaw), length * math.sin(yaw),
head_length=width, head_width=width)
@@ -184,21 +197,20 @@ def main(gx=10, gy=10):
[12.0, 12.0]
])
# input [forward speed, yawrate]
u = np.array([0.0, 0.0])
config = Config()
traj = np.array(x)
for i in range(1000):
u, ltraj = dwa_control(x, u, config, goal, ob)
while True:
u, ptraj = dwa_control(x, u, config, goal, ob)
x = motion(x, u, config.dt)
x = motion(x, u, config.dt) # simulate robot
traj = np.vstack((traj, x)) # store state history
# print(traj)
if show_animation:
plt.cla()
plt.plot(ltraj[:, 0], ltraj[:, 1], "-g")
plt.plot(ptraj[:, 0], ptraj[:, 1], "-g")
plt.plot(x[0], x[1], "xr")
plt.plot(goal[0], goal[1], "xb")
plt.plot(ob[:, 0], ob[:, 1], "ok")
@@ -207,8 +219,9 @@ def main(gx=10, gy=10):
plt.grid(True)
plt.pause(0.0001)
# check goal
if math.sqrt((x[0] - goal[0])**2 + (x[1] - goal[1])**2) <= config.robot_radius:
# check reaching goal
dist_to_goal = math.sqrt((x[0] - goal[0])**2 + (x[1] - goal[1])**2)
if dist_to_goal <= config.robot_radius:
print("Goal!!")
break

View File

@@ -161,12 +161,18 @@ def generate_roadmap(sample_x, sample_y, rr, obkdtree):
def dijkstra_planning(sx, sy, gx, gy, ox, oy, rr, road_map, sample_x, sample_y):
"""
sx: start x position [m]
sy: start y position [m]
gx: goal x position [m]
gx: goal x position [m]
gy: goal y position [m]
ox: x position list of Obstacles [m]
oy: y position list of Obstacles [m]
reso: grid resolution [m]
rr: robot radius[m]
rr: robot radius [m]
road_map: ??? [m]
sample_x: ??? [m]
sample_y: ??? [m]
@return: Two lists of path coordinates ([x1, x2, ...], [y1, y2, ...]), empty list when no path was found
"""
nstart = Node(sx, sy, 0.0, -1)
@@ -175,9 +181,12 @@ def dijkstra_planning(sx, sy, gx, gy, ox, oy, rr, road_map, sample_x, sample_y):
openset, closedset = dict(), dict()
openset[len(road_map) - 2] = nstart
path_found = True
while True:
if not openset:
print("Cannot find path")
path_found = False
break
c_id = min(openset, key=lambda o: openset[o].cost)
@@ -217,6 +226,9 @@ def dijkstra_planning(sx, sy, gx, gy, ox, oy, rr, road_map, sample_x, sample_y):
openset[n_id].pind = c_id
else:
openset[n_id] = node
if path_found is False:
return [], []
# generate final course
rx, ry = [ngoal.x], [ngoal.y]
@@ -249,8 +261,8 @@ def sample_points(sx, sy, gx, gy, rr, ox, oy, obkdtree):
sample_x, sample_y = [], []
while len(sample_x) <= N_SAMPLE:
tx = (random.random() - minx) * (maxx - minx)
ty = (random.random() - miny) * (maxy - miny)
tx = (random.random() * (maxx - minx)) + minx
ty = (random.random() * (maxy - miny)) + miny
index, dist = obkdtree.search(np.array([tx, ty]).reshape(2, 1))

View File

@@ -9,13 +9,15 @@ import numpy as np
import math
import matplotlib.pyplot as plt
k = 0.1 # look forward gain
Lfc = 1.0 # look-ahead distance
Lfc = 2.0 # look-ahead distance
Kp = 1.0 # speed proportional gain
dt = 0.1 # [s]
L = 2.9 # [m] wheel base of vehicle
old_nearest_point_index = None
show_animation = True
@@ -26,6 +28,8 @@ class State:
self.y = y
self.yaw = yaw
self.v = v
self.rear_x = self.x - ((L / 2) * math.cos(self.yaw))
self.rear_y = self.y - ((L / 2) * math.sin(self.yaw))
def update(state, a, delta):
@@ -34,6 +38,8 @@ def update(state, a, delta):
state.y = state.y + state.v * math.sin(state.yaw) * dt
state.yaw = state.yaw + state.v / L * math.tan(delta) * dt
state.v = state.v + a * dt
state.rear_x = state.x - ((L / 2) * math.cos(state.yaw))
state.rear_y = state.y - ((L / 2) * math.sin(state.yaw))
return state
@@ -59,7 +65,7 @@ def pure_pursuit_control(state, cx, cy, pind):
ty = cy[-1]
ind = len(cx) - 1
alpha = math.atan2(ty - state.y, tx - state.x) - state.yaw
alpha = math.atan2(ty - state.rear_y, tx - state.rear_x) - state.yaw
Lf = k * state.v + Lfc
@@ -67,22 +73,43 @@ def pure_pursuit_control(state, cx, cy, pind):
return delta, ind
def calc_distance(state, point_x, point_y):
dx = state.rear_x - point_x
dy = state.rear_y - point_y
return math.sqrt(dx ** 2 + dy ** 2)
def calc_target_index(state, cx, cy):
# search nearest point index
dx = [state.x - icx for icx in cx]
dy = [state.y - icy for icy in cy]
d = [abs(math.sqrt(idx ** 2 + idy ** 2)) for (idx, idy) in zip(dx, dy)]
ind = d.index(min(d))
global old_nearest_point_index
if old_nearest_point_index is None:
# search nearest point index
dx = [state.rear_x - icx for icx in cx]
dy = [state.rear_y - icy for icy in cy]
d = [abs(math.sqrt(idx ** 2 + idy ** 2)) for (idx, idy) in zip(dx, dy)]
ind = d.index(min(d))
old_nearest_point_index = ind
else:
ind = old_nearest_point_index
distance_this_index = calc_distance(state, cx[ind], cy[ind])
while True:
ind = ind + 1 if (ind + 1) < len(cx) else ind
distance_next_index = calc_distance(state, cx[ind], cy[ind])
if distance_this_index < distance_next_index:
break
distance_this_index = distance_next_index
old_nearest_point_index = ind
L = 0.0
Lf = k * state.v + Lfc
# search look ahead target point index
while Lf > L and (ind + 1) < len(cx):
dx = cx[ind] - state.x
dy = cy[ind] - state.y
dx = cx[ind] - state.rear_x
dy = cy[ind] - state.rear_y
L = math.sqrt(dx ** 2 + dy ** 2)
ind += 1

View File

@@ -14,6 +14,7 @@ Python codes for robotics algorithm.
# Table of Contents
* [What is this?](#what-is-this)
* [Requirements](#requirements)
@@ -26,12 +27,13 @@ Python codes for robotics algorithm.
* [Mapping](#mapping)
* [Gaussian grid map](#gaussian-grid-map)
* [Ray casting grid map](#ray-casting-grid-map)
* [Lidar to grid map](#lidar-to-grid-map)
* [k-means object clustering](#k-means-object-clustering)
* [Rectangle fitting](#rectangle-fitting)
* [SLAM](#slam)
* [Iterative Closest Point (ICP) Matching](#iterative-closest-point-icp-matching)
* [FastSLAM 1.0](#fastslam-10)
* [Graph based SLAM](#graph-based-slam)
* [Pose Optimization SLAM](#pose-optimization-slam)
* [Path Planning](#path-planning)
* [Dynamic Window Approach](#dynamic-window-approach)
* [Grid based search](#grid-based-search)
@@ -191,6 +193,12 @@ This is a 2D ray casting grid mapping example.
![2](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Mapping/raycasting_grid_map/animation.gif)
## Lidar to grid map
This example shows how to convert a 2D range measurement to a grid map.
![2](Mapping/lidar_to_grid_map/animation.gif)
## k-means object clustering
This is a 2D object clustering with k-means algorithm.
@@ -242,24 +250,11 @@ Ref:
- [SLAM simulations by Tim Bailey](http://www-personal.acfr.usyd.edu.au/tbailey/software/slam_simulations.htm)
## Graph based SLAM
## Pose Optimization SLAM
This is a graph based SLAM example.
The blue line is ground truth.
The black line is dead reckoning.
The red line is the estimated trajectory with Graph based SLAM.
The black stars are landmarks for graph edge generation.
![3](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/SLAM/GraphBasedSLAM/animation.gif)
Ref:
- [A Tutorial on Graph-Based SLAM](http://www2.informatik.uni-freiburg.de/~stachnis/pdf/grisetti10titsmag.pdf)
This is a graph based pose optimization SLAM example.
![3](https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/SLAM/PoseOptimizationSLAM/animation.gif)
# Path Planning
@@ -557,7 +552,11 @@ MIT
# Use-case
See: [users\_comments](https://github.com/AtsushiSakai/PythonRobotics/blob/master/users_comments.md)
If this project helps your robotics project, please let me know with [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/AtsushiSakai).
Your robot's video, which is using PythonRobotics, is very welcome!!
This is a list of other user's comment and references:[users\_comments](https://github.com/AtsushiSakai/PythonRobotics/blob/master/users_comments.md)
# Contribution
@@ -609,3 +608,4 @@ This is a list: [Users comments](https://github.com/AtsushiSakai/PythonRobotics/

View File

@@ -0,0 +1,9 @@
# How to use
1. Download data
>$ python data_downloader.py
2. run SLAM
>$ python pose_optimization_slam.py

View File

@@ -0,0 +1,28 @@
"""
Data down loader for pose optimization
author: Atsushi Sakai
"""
import subprocess
def main():
print("start!!")
cmd = "wget https://www.dropbox.com/s/vcz8cag7bo0zlaj/input_INTEL_g2o.g2o?dl=0 -O intel.g2o -nc"
subprocess.call(cmd, shell=True)
cmd = "wget https://www.dropbox.com/s/d8fcn1jg1mebx8f/input_MITb_g2o.g2o?dl=0 -O mit_killian.g2o -nc"
subprocess.call(cmd, shell=True)
cmd = "wget https://www.dropbox.com/s/gmdzo74b3tzvbrw/input_M3500_g2o.g2o?dl=0 -O manhattan3500.g2o -nc"
subprocess.call(cmd, shell=True)
print("done!!")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,290 @@
"""
2D (x, y, yaw) pose optimization SLAM
author: Atsushi Sakai
Ref:
- [A Compact and Portable Implementation of Graph\-based SLAM](https://www.researchgate.net/publication/321287640_A_Compact_and_Portable_Implementation_of_Graph-based_SLAM)
- [GitHub \- furo\-org/p2o: Single header 2D/3D graph\-based SLAM library](https://github.com/furo-org/p2o)
"""
import sys
import time
import math
import numpy as np
import matplotlib.pyplot as plt
from scipy import sparse
from scipy.sparse import linalg
class Optimizer2D:
def __init__(self):
self.verbose = False
self.animation = False
self.p_lambda = 0.0
self.init_w = 1e10
self.stop_thre = 1e-3
self.dim = 3 # state dimension
def optimize_path(self, nodes, consts, max_iter, min_iter):
graph_nodes = nodes[:]
prev_cost = sys.float_info.max
for i in range(max_iter):
start = time.time()
cost, graph_nodes = self.optimize_path_one_step(
graph_nodes, consts)
elapsed = time.time() - start
if self.verbose:
print("step ", i, " cost: ", cost, " time:", elapsed, "s")
# check convergence
if (i > min_iter) and (prev_cost - cost < self.stop_thre):
if self.verbose:
print("converged:", prev_cost
- cost, " < ", self.stop_thre)
break
prev_cost = cost
if self.animation:
plt.cla()
plot_nodes(nodes, color="-b")
plot_nodes(graph_nodes)
plt.axis("equal")
plt.pause(1.0)
return graph_nodes
def optimize_path_one_step(self, graph_nodes, constraints):
indlist = [i for i in range(self.dim)]
numnodes = len(graph_nodes)
bf = np.zeros(numnodes * self.dim)
tripletList = TripletList()
for con in constraints:
ida = con.id1
idb = con.id2
assert 0 <= ida and ida < numnodes, "ida is invalid"
assert 0 <= idb and idb < numnodes, "idb is invalid"
r, Ja, Jb = self.calc_error(
graph_nodes[ida], graph_nodes[idb], con.t)
trJaInfo = Ja.transpose() @ con.info_mat
trJaInfoJa = trJaInfo @ Ja
trJbInfo = Jb.transpose() @ con.info_mat
trJbInfoJb = trJbInfo @ Jb
trJaInfoJb = trJaInfo @ Jb
for k in indlist:
for m in indlist:
tripletList.push_back(
ida * self.dim + k, ida * self.dim + m, trJaInfoJa[k, m])
tripletList.push_back(
idb * self.dim + k, idb * self.dim + m, trJbInfoJb[k, m])
tripletList.push_back(
ida * self.dim + k, idb * self.dim + m, trJaInfoJb[k, m])
tripletList.push_back(
idb * self.dim + k, ida * self.dim + m, trJaInfoJb[m, k])
bf[ida * self.dim: ida * self.dim + 3] += trJaInfo @ r
bf[idb * self.dim: idb * self.dim + 3] += trJbInfo @ r
for k in indlist:
tripletList.push_back(k, k, self.init_w)
for i in range(self.dim * numnodes):
tripletList.push_back(i, i, self.p_lambda)
mat = sparse.coo_matrix((tripletList.data, (tripletList.row, tripletList.col)),
shape=(numnodes * self.dim, numnodes * self.dim))
x = linalg.spsolve(mat.tocsr(), -bf)
out_nodes = []
for i in range(len(graph_nodes)):
u_i = i * self.dim
pos = Pose2D(
graph_nodes[i].x + x[u_i],
graph_nodes[i].y + x[u_i + 1],
graph_nodes[i].theta + x[u_i + 2]
)
out_nodes.append(pos)
cost = self.calc_global_cost(out_nodes, constraints)
return cost, out_nodes
def calc_global_cost(self, nodes, constraints):
cost = 0.0
for c in constraints:
diff = self.error_func(nodes[c.id1], nodes[c.id2], c.t)
cost += diff.transpose() @ c.info_mat @ diff
return cost
def error_func(self, pa, pb, t):
ba = self.calc_constraint_pose(pb, pa)
error = np.array([ba.x - t.x,
ba.y - t.y,
self.pi2pi(ba.theta - t.theta)])
return error
def calc_constraint_pose(self, l, r):
diff = np.array([l.x - r.x, l.y - r.y, l.theta - r.theta])
v = self.rot_mat_2d(-r.theta) @ diff
v[2] = self.pi2pi(l.theta - r.theta)
return Pose2D(v[0], v[1], v[2])
def rot_mat_2d(self, theta):
return np.array([[math.cos(theta), -math.sin(theta), 0.0],
[math.sin(theta), math.cos(theta), 0.0],
[0.0, 0.0, 1.0]
])
def calc_error(self, pa, pb, t):
e0 = self.error_func(pa, pb, t)
dx = pb.x - pa.x
dy = pb.y - pa.y
dxdt = -math.sin(pa.theta) * dx + math.cos(pa.theta) * dy
dydt = -math.cos(pa.theta) * dx - math.sin(pa.theta) * dy
Ja = np.array([[-math.cos(pa.theta), -math.sin(pa.theta), dxdt],
[math.sin(pa.theta), -math.cos(pa.theta), dydt],
[0.0, 0.0, -1.0]
])
Jb = np.array([[math.cos(pa.theta), math.sin(pa.theta), 0.0],
[-math.sin(pa.theta), math.cos(pa.theta), 0.0],
[0.0, 0.0, 1.0]
])
return e0, Ja, Jb
def pi2pi(self, rad):
val = math.fmod(rad, 2.0 * math.pi)
if val > math.pi:
val -= 2.0 * math.pi
elif val < -math.pi:
val += 2.0 * math.pi
return val
class TripletList:
def __init__(self):
self.row = []
self.col = []
self.data = []
def push_back(self, irow, icol, idata):
self.row.append(irow)
self.col.append(icol)
self.data.append(idata)
class Pose2D:
def __init__(self, x, y, theta):
self.x = x
self.y = y
self.theta = theta
class Constrant2D:
def __init__(self, id1, id2, t, info_mat):
self.id1 = id1
self.id2 = id2
self.t = t
self.info_mat = info_mat
def plot_nodes(nodes, color ="-r", label = ""):
x, y = [], []
for n in nodes:
x.append(n.x)
y.append(n.y)
plt.plot(x, y, color, label=label)
def load_data(fname):
nodes, consts = [], []
for line in open(fname):
sline = line.split()
tag = sline[0]
if tag == "VERTEX_SE2":
data_id = int(sline[1])
x = float(sline[2])
y = float(sline[3])
theta = float(sline[4])
nodes.append(Pose2D(x, y, theta))
elif tag == "EDGE_SE2":
id1 = int(sline[1])
id2 = int(sline[2])
x = float(sline[3])
y = float(sline[4])
th = float(sline[5])
c1 = float(sline[6])
c2 = float(sline[7])
c3 = float(sline[8])
c4 = float(sline[9])
c5 = float(sline[10])
c6 = float(sline[11])
t = Pose2D(x, y, th)
info_mat = np.array([[c1, c2, c3],
[c2, c4, c5],
[c3, c5, c6]
])
consts.append(Constrant2D(id1, id2, t, info_mat))
print("n_nodes:", len(nodes))
print("n_consts:", len(consts))
return nodes, consts
def main():
print("start!!")
fnames = ["intel.g2o",
"manhattan3500.g2o",
"mit_killian.g2o"
]
max_iter = 20
min_iter = 3
# parameter setting
optimizer = Optimizer2D()
optimizer.p_lambda = 1e-6
optimizer.verbose = True
optimizer.animation = True
for f in fnames:
nodes, consts = load_data(f)
start = time.time()
final_nodes = optimizer.optimize_path(nodes, consts, max_iter, min_iter)
print("elapsed_time", time.time() - start, "sec")
# plotting
plt.cla()
plot_nodes(nodes, color="-b", label="before")
plot_nodes(final_nodes, label="after")
plt.axis("equal")
plt.grid(True)
plt.legend()
plt.pause(3.0)
print("done!!")
if __name__ == '__main__':
main()

View File

@@ -13,6 +13,13 @@ You can check stargazer's locations of this project from:
- [Stargazers location map](https://drive.google.com/open?id=1bBXS9IQmZ3Tfe1wNGvJbObRt9Htt4PGC&usp=sharing)
# Related projects
This is a list of related projects.
- [onlytailei/CppRobotics: cpp implementation of robotics algorithms including localization, mapping, SLAM, path planning and control](https://github.com/onlytailei/CppRobotics)
# Individual users comments
These are comments from user's using [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/AtsushiSakai)
@@ -196,6 +203,42 @@ Dear AtsushiSakai, <br>Thank you for this great resource! I very much appreciate
--Mridul Aanjaneya
---
Thank you for the python robotics sample code and visualizations!! This is my new favorite thing on the internet.
--selamg@mit.edu
---
Mr AtsushiSakai .. <br>Your work and teaching is light for me <br>thank you very much for giving time and effort to make it public for us
--Karim Anass
---
Thank You
--Anonymous
---
I've learned the robotics from the traditional way of solving problem through finding packages and reading papers. This amazing project is unbelievably helpful for new-comers to learn and get familiar with the algorithms. Gods know how many hours you've taken to sort all the materials and written everything into one single coherent project. I'm truly amazed and deepest appreciation.
--Ewing Kang
---
Hey, I'm a student and I just recently got into robotics, and visited your repository multiple times. Today I was super happy to find the link to Patreon! I am impressed by didactic quality of the repo, keep up the good work!
--Carolina Bianchi
---
Dear AtsushiSakai, thanks a lot for the PythonRobotics git repository. I'm a college student who is trying to make a mini autonomous robot for my final project submission, I still haven't started using your code but by the look of it I'm sure it will be of greater use. Will let you know how my project is coming up.
--Pragdheesh
=========
# Citations
@@ -212,3 +255,5 @@ URL: http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=8516589&isnumber=85
- Autonomous Vehicle Readings https://richardkelley.io/readings
- 36 Amazing Python Open Source Projects (v.2019) Mybridge for Professionals https://medium.mybridge.co/36-amazing-python-open-source-projects-v-2019-2fe058d79450