Skip to content

Commit 40e5c2c

Browse files
committed
first release pose_optimization_slam
1 parent a5db2fe commit 40e5c2c

File tree

4 files changed

+328
-0
lines changed

4 files changed

+328
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
*.csv
22
*.gif
3+
*.g2o
34

45
# Byte-compiled / optimized / DLL files
56
__pycache__/
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# How to use
2+
3+
1. Download data
4+
5+
python data_downloader.py
6+
7+
2. run SLAM
8+
9+
python pose_optimization_slam.py
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""
2+
3+
Data down loader for pose optimization
4+
5+
author: Atsushi Sakai
6+
7+
"""
8+
9+
10+
import subprocess
11+
def main():
12+
print("start!!")
13+
14+
cmd = "wget https://www.dropbox.com/s/vcz8cag7bo0zlaj/input_INTEL_g2o.g2o?dl=0 -O intel.g2o -nc"
15+
subprocess.call(cmd, shell=True)
16+
17+
cmd = "wget https://www.dropbox.com/s/d8fcn1jg1mebx8f/input_MITb_g2o.g2o?dl=0 -O mit_killian.g2o -nc"
18+
19+
subprocess.call(cmd, shell=True)
20+
cmd = "wget https://www.dropbox.com/s/gmdzo74b3tzvbrw/input_M3500_g2o.g2o?dl=0 -O manhattan3500.g2o -nc"
21+
subprocess.call(cmd, shell=True)
22+
23+
print("done!!")
24+
25+
26+
if __name__ == '__main__':
27+
main()
28+
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
"""
2+
3+
2D (x, y, yaw) pose optimization SLAM
4+
5+
author: Atsushi Sakai
6+
7+
Ref:
8+
- [A Compact and Portable Implementation of Graph\-based SLAM](https://www.researchgate.net/publication/321287640_A_Compact_and_Portable_Implementation_of_Graph-based_SLAM)
9+
10+
- [GitHub \- furo\-org/p2o: Single header 2D/3D graph\-based SLAM library](https://github.com/furo-org/p2o)
11+
12+
"""
13+
14+
import sys
15+
import time
16+
import math
17+
import numpy as np
18+
import matplotlib.pyplot as plt
19+
from scipy import sparse
20+
from scipy.sparse import linalg
21+
22+
23+
class Optimizer2D:
24+
25+
def __init__(self):
26+
self.verbose = False
27+
self.animation = False
28+
self.p_lambda = 0.0
29+
self.init_w = 1e10
30+
self.stop_thre = 1e-3
31+
self.dim = 3 # state dimension
32+
33+
def optimize_path(self, nodes, consts, max_iter, min_iter):
34+
35+
graph_nodes = nodes[:]
36+
prev_cost = sys.float_info.max
37+
38+
for i in range(max_iter):
39+
start = time.time()
40+
cost, graph_nodes = self.optimize_path_one_step(
41+
graph_nodes, consts)
42+
elapsed = time.time() - start
43+
if self.verbose:
44+
print("step ", i, " cost: ", cost, " time:", elapsed, "s")
45+
46+
# check convergence
47+
if (i > min_iter) and (prev_cost - cost < self.stop_thre):
48+
if self.verbose:
49+
print("converged:", prev_cost
50+
- cost, " < ", self.stop_thre)
51+
break
52+
prev_cost = cost
53+
54+
if self.animation:
55+
plt.cla()
56+
plot_nodes(nodes, color="-b")
57+
plot_nodes(graph_nodes)
58+
plt.axis("equal")
59+
plt.pause(1.0)
60+
61+
return graph_nodes
62+
63+
def optimize_path_one_step(self, graph_nodes, constraints):
64+
65+
indlist = [i for i in range(self.dim)]
66+
numnodes = len(graph_nodes)
67+
bf = np.zeros(numnodes * self.dim)
68+
tripletList = TripletList()
69+
70+
for con in constraints:
71+
ida = con.id1
72+
idb = con.id2
73+
assert 0 <= ida and ida < numnodes, "ida is invalid"
74+
assert 0 <= idb and idb < numnodes, "idb is invalid"
75+
r, Ja, Jb = self.calc_error(
76+
graph_nodes[ida], graph_nodes[idb], con.t)
77+
78+
trJaInfo = Ja.transpose() @ con.info_mat
79+
trJaInfoJa = trJaInfo @ Ja
80+
trJbInfo = Jb.transpose() @ con.info_mat
81+
trJbInfoJb = trJbInfo @ Jb
82+
trJaInfoJb = trJaInfo @ Jb
83+
84+
for k in indlist:
85+
for m in indlist:
86+
tripletList.push_back(
87+
ida * self.dim + k, ida * self.dim + m, trJaInfoJa[k, m])
88+
tripletList.push_back(
89+
idb * self.dim + k, idb * self.dim + m, trJbInfoJb[k, m])
90+
tripletList.push_back(
91+
ida * self.dim + k, idb * self.dim + m, trJaInfoJb[k, m])
92+
tripletList.push_back(
93+
idb * self.dim + k, ida * self.dim + m, trJaInfoJb[m, k])
94+
95+
bf[ida * self.dim: ida * self.dim + 3] += trJaInfo @ r
96+
bf[idb * self.dim: idb * self.dim + 3] += trJbInfo @ r
97+
98+
for k in indlist:
99+
tripletList.push_back(k, k, self.init_w)
100+
101+
for i in range(self.dim * numnodes):
102+
tripletList.push_back(i, i, self.p_lambda)
103+
104+
mat = sparse.coo_matrix((tripletList.data, (tripletList.row, tripletList.col)),
105+
shape=(numnodes * self.dim, numnodes * self.dim))
106+
107+
x = linalg.spsolve(mat.tocsr(), -bf)
108+
109+
out_nodes = []
110+
for i in range(len(graph_nodes)):
111+
u_i = i * self.dim
112+
pos = Pose2D(
113+
graph_nodes[i].x + x[u_i],
114+
graph_nodes[i].y + x[u_i + 1],
115+
graph_nodes[i].theta + x[u_i + 2]
116+
)
117+
out_nodes.append(pos)
118+
119+
cost = self.calc_global_cost(out_nodes, constraints)
120+
121+
return cost, out_nodes
122+
123+
def calc_global_cost(self, nodes, constraints):
124+
cost = 0.0
125+
for c in constraints:
126+
diff = self.error_func(nodes[c.id1], nodes[c.id2], c.t)
127+
cost += diff.transpose() @ c.info_mat @ diff
128+
129+
return cost
130+
131+
def error_func(self, pa, pb, t):
132+
ba = self.calc_constraint_pose(pb, pa)
133+
error = np.array([ba.x - t.x,
134+
ba.y - t.y,
135+
self.pi2pi(ba.theta - t.theta)])
136+
return error
137+
138+
def calc_constraint_pose(self, l, r):
139+
diff = np.array([l.x - r.x, l.y - r.y, l.theta - r.theta])
140+
v = self.rot_mat_2d(-r.theta) @ diff
141+
v[2] = self.pi2pi(l.theta - r.theta)
142+
return Pose2D(v[0], v[1], v[2])
143+
144+
def rot_mat_2d(self, theta):
145+
return np.array([[math.cos(theta), -math.sin(theta), 0.0],
146+
[math.sin(theta), math.cos(theta), 0.0],
147+
[0.0, 0.0, 1.0]
148+
])
149+
150+
def calc_error(self, pa, pb, t):
151+
e0 = self.error_func(pa, pb, t)
152+
dx = pb.x - pa.x
153+
dy = pb.y - pa.y
154+
dxdt = -math.sin(pa.theta) * dx + math.cos(pa.theta) * dy
155+
dydt = -math.cos(pa.theta) * dx - math.sin(pa.theta) * dy
156+
Ja = np.array([[-math.cos(pa.theta), -math.sin(pa.theta), dxdt],
157+
[math.sin(pa.theta), -math.cos(pa.theta), dydt],
158+
[0.0, 0.0, -1.0]
159+
])
160+
Jb = np.array([[math.cos(pa.theta), math.sin(pa.theta), 0.0],
161+
[-math.sin(pa.theta), math.cos(pa.theta), 0.0],
162+
[0.0, 0.0, 1.0]
163+
])
164+
return e0, Ja, Jb
165+
166+
def pi2pi(self, rad):
167+
168+
val = math.fmod(rad, 2.0 * math.pi)
169+
if val > math.pi:
170+
val -= 2.0 * math.pi
171+
elif val < -math.pi:
172+
val += 2.0 * math.pi
173+
174+
return val
175+
176+
177+
class TripletList:
178+
179+
def __init__(self):
180+
self.row = []
181+
self.col = []
182+
self.data = []
183+
184+
def push_back(self, irow, icol, idata):
185+
self.row.append(irow)
186+
self.col.append(icol)
187+
self.data.append(idata)
188+
189+
190+
class Pose2D:
191+
192+
def __init__(self, x, y, theta):
193+
self.x = x
194+
self.y = y
195+
self.theta = theta
196+
197+
198+
class Constrant2D:
199+
200+
def __init__(self, id1, id2, t, info_mat):
201+
self.id1 = id1
202+
self.id2 = id2
203+
self.t = t
204+
self.info_mat = info_mat
205+
206+
207+
def plot_nodes(nodes, color ="-r", label = ""):
208+
x, y = [], []
209+
for n in nodes:
210+
x.append(n.x)
211+
y.append(n.y)
212+
plt.plot(x, y, color, label=label)
213+
214+
215+
def load_data(fname):
216+
nodes, consts = [], []
217+
218+
for line in open(fname):
219+
sline = line.split()
220+
tag = sline[0]
221+
222+
if tag == "VERTEX_SE2":
223+
data_id = int(sline[1])
224+
x = float(sline[2])
225+
y = float(sline[3])
226+
theta = float(sline[4])
227+
nodes.append(Pose2D(x, y, theta))
228+
elif tag == "EDGE_SE2":
229+
id1 = int(sline[1])
230+
id2 = int(sline[2])
231+
x = float(sline[3])
232+
y = float(sline[4])
233+
th = float(sline[5])
234+
c1 = float(sline[6])
235+
c2 = float(sline[7])
236+
c3 = float(sline[8])
237+
c4 = float(sline[9])
238+
c5 = float(sline[10])
239+
c6 = float(sline[11])
240+
t = Pose2D(x, y, th)
241+
info_mat = np.array([[c1, c2, c3],
242+
[c2, c4, c5],
243+
[c3, c5, c6]
244+
])
245+
consts.append(Constrant2D(id1, id2, t, info_mat))
246+
247+
print("n_nodes:", len(nodes))
248+
print("n_consts:", len(consts))
249+
250+
return nodes, consts
251+
252+
253+
def main():
254+
print("start!!")
255+
256+
fnames = ["intel.g2o",
257+
"manhattan3500.g2o",
258+
"mit_killian.g2o"
259+
]
260+
261+
max_iter = 20
262+
min_iter = 3
263+
264+
# parameter setting
265+
optimizer = Optimizer2D()
266+
optimizer.p_lambda = 1e-6
267+
optimizer.verbose = True
268+
optimizer.animation = True
269+
270+
for f in fnames:
271+
nodes, consts = load_data(f)
272+
273+
start = time.time()
274+
final_nodes = optimizer.optimize_path(nodes, consts, max_iter, min_iter)
275+
print("elapsed_time", time.time() - start, "sec")
276+
277+
# plotting
278+
plt.cla()
279+
plot_nodes(nodes, color="-b", label="before")
280+
plot_nodes(final_nodes, label="after")
281+
plt.axis("equal")
282+
plt.grid(True)
283+
plt.legend()
284+
plt.pause(3.0)
285+
286+
print("done!!")
287+
288+
289+
if __name__ == '__main__':
290+
main()

0 commit comments

Comments
 (0)