-
Notifications
You must be signed in to change notification settings - Fork 1
/
laser_icp.cpp
374 lines (241 loc) · 9.15 KB
/
laser_icp.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
#include <chrono>
#include <memory>
#include <vector>
#include <cmath>
#include <iostream>
#include <fstream>
#include <Eigen/Core>
#include <unsupported/Eigen/NonLinearOptimization>
#include <unsupported/Eigen/NumericalDiff>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/joy.hpp"
#include "sensor_msgs/msg/laser_scan.hpp"
#include "nav_msgs/msg/odometry.hpp"
#include "message_filters/subscriber.h"
#include "message_filters/time_synchronizer.h"
#include "knn.h"
using namespace std;
using namespace Eigen;
typedef knncpp::Matrixi Matrixi;
# define PI 3.14159265358979323846
// Setup the shared memory file and obtain key and shared memory id (change the filepath to desired filepath)
// Will heap allocate 1024 bytes
key_t key = ftok("/home/f1tenth3/shmfile", 65);
int shmid = shmget(key, 1024, 0666 | IPC_CREAT);
// Generic functor template that the ICP loss function will inherit from
template<typename _Scalar, int NX = Eigen::Dynamic, int NY = Eigen::Dynamic>
struct Functor
{
typedef _Scalar Scalar;
enum
{
InputsAtCompileTime = NX,
ValuesAtCompileTime = NY
};
// This is needed since the LVM solver requires stack allocated matrices and so the sizes need to be known at compile time
typedef Eigen::Matrix<Scalar, InputsAtCompileTime, 1> InputType;
typedef Eigen::Matrix<Scalar, ValuesAtCompileTime, 1> ValueType;
typedef Eigen::Matrix<Scalar, ValuesAtCompileTime, InputsAtCompileTime> JacobianType;
int m_inputs, m_values;
Functor() : m_inputs(InputsAtCompileTime), m_values(ValuesAtCompileTime) {}
Functor(int inputs, int values) : m_inputs(inputs), m_values(values) {}
int inputs() const
{
return m_inputs;
}
int values() const
{
return m_values;
}
};
// Utilities
// Convert raw LiDAR ranges to point cloud
MatrixXf range2pc(ArrayXf r)
{
ArrayXf angle = ArrayXf::LinSpaced(1081, -3*PI/4, 3*PI/4);
MatrixXf pc = MatrixXf::Zero(2, 1081);
pc << (r * cos(angle)).matrix().transpose(),
(r * sin(angle)).matrix().transpose();
return pc;
}
// Convert from a pose vector to a homogenous transformation matrix
MatrixXf pose2tf(VectorXf x)
{
VectorXf t = x(seq(0, 1), 0);
MatrixXf R = MatrixXf::Zero(2, 2);
R << cos(x(2)), -sin(x(2)),
sin(x(2)), +cos(x(2));
MatrixXf T = MatrixXf::Identity(3, 3);
T.block(0, 0, 2, 2) = R;
T.block(0, 2, 2, 1) = t;
return T;
}
// Apply homogenous transformation to the given point cloud
MatrixXf transformPointCloud(MatrixXf ptcloud, MatrixXf T)
{
int n_pts = ptcloud.cols();
int n_dims = ptcloud.rows();
MatrixXf ptcloud_tf = MatrixXf::Constant(n_dims + 1, n_pts, 1.00);
ptcloud_tf(seq(0, 1), seq(0, n_pts-1)) = ptcloud;
ptcloud_tf = T * ptcloud_tf;
return ptcloud_tf(seq(0, 1), seq(0, n_pts-1));
}
// ICP distance loss function (inherited from Functor class; as needed by the LVM solver interface)
struct ICPLoss : Functor<float>
{
MatrixXf p, q;
ICPLoss(void): Functor<float>(3, 3) {}
// This function computes the gradient of the ICP loss function
int operator()(const Eigen::VectorXf &x, Eigen::VectorXf &fvec) const
{
int n_pts = p.cols();
MatrixXf T = pose2tf(x);
MatrixXf R_dUdt(2, 2);
R_dUdt << -sin(x(2)), -cos(x(2)),
cos(x(2)), -sin(x(2));
MatrixXf p_tf = transformPointCloud(p, T);
MatrixXf d = p_tf - q;
MatrixXf p_rot = R_dUdt * p;
fvec(0) = d(0, seq(0, n_pts-1)).sum();
fvec(1) = d(1, seq(0, n_pts-1)).sum();
fvec(2) = (d(0, seq(0, n_pts-1)).array() * p_rot(0, seq(0, n_pts-1)).array() + d(1, seq(0, n_pts-1)).array() * p_rot(1, seq(0, n_pts-1)).array()).sum();
return 0;
}
};
// Wrapper function for Levenberg Marquardt nonlinear solver
VectorXf solver(MatrixXf p, MatrixXf q)
{
VectorXf x = VectorXf::Zero(3);
ICPLoss loss_;
loss_.p = p;
loss_.q = q;
Eigen::NumericalDiff<ICPLoss> gradient(loss_);
Eigen::LevenbergMarquardt<Eigen::NumericalDiff<ICPLoss>, float> lvm(gradient);
lvm.parameters.maxfev = 2000;
lvm.parameters.xtol = 1e-12;
int ret = lvm.minimize(x);
return x;
}
MatrixXf runICP(MatrixXf p, MatrixXf q, int maxiters)
{
// Setup initial vectors and matrices
VectorXf x = VectorXf::Zero(3);
MatrixXf T = MatrixXf::Identity(3, 3);
MatrixXf Tr = MatrixXf::Identity(3, 3);
MatrixXf p_tf = transformPointCloud(p, T);
// Setup kdtree for nearest neighbours computation with previous point cloud (q)
knncpp::KDTreeMinkowskiX<float, knncpp::EuclideanDistance<float>> kdtree(q);
kdtree.setBucketSize(10);
kdtree.setSorted(true);
kdtree.setTakeRoot(true);
kdtree.setMaxDistance(0);
kdtree.setThreads(0);
kdtree.build();
Matrixi indices;
MatrixXf distances;
// Get interrupt signal from shared memory
int *interrupt = (int*) shmat(shmid, (void*) 0, 0);
for (int k = 0; k < maxiters; k++)
{
// Pass the current number of iterations to the controller node via the shared memory pointer
*(interrupt + 2) = k;
// Obtain 1st nearest point indices and distances from previous point cloud to current point cloud
kdtree.query(p_tf, 1, indices, distances);
// Type cast from Matrixi (indices matrix) to long int matrix using the Map function
Map<Array<long int, Dynamic, Dynamic>> idx_long(indices.data(), indices.cols(), indices.rows());
// Type cast from long int to int matrix
ArrayXi idx = idx_long.cast <int> ();
// Slice previous point cloud to get nearest correspondences
MatrixXf q_knn = q(seq(0, 1), idx);
// Obtain optimal pose estimate from Levenberg-Marquardt nonlinear solver
x = solver(p_tf, q_knn);
// Convert pose to homogenous transform and transform current point cloud
p_tf = transformPointCloud(p_tf, pose2tf(x));
// Update current transformation estimate
Tr = pose2tf(x) * Tr;
// Check to see if the current iteration count of the ICP has exceeded max desired iterations from the controller node
if (*interrupt <= k)
{
*interrupt = 0;
break;
}
}
// Send handshaking signal from the ICP node to the controller node to signal that ICP is completed
*(interrupt + 1) = 1;
// Return homogenous transform matrix
return Tr;
}
class Laser : public rclcpp::Node
{
std::vector<float> axs, range;
std::vector<int> btns;
bool initial;
MatrixXf p, q, Tr, T;
Matrix3f R_3;
Quaternionf q_orientation;
int maxiters;
public:
Laser() : Node("Laser_control")
{
// Get max iterations parameter from external yaml file
declare_parameter("max_iters", 40);
maxiters = get_parameter("max_iters").as_int();
// Set initial to true and create identity rotation matrix
initial = true;
R_3 = Eigen::Matrix3f::Identity();
// Subscribe to LiDAR and create odometry publisher
laser_sub_ = this->create_subscription<sensor_msgs::msg::LaserScan>("/scan", 1, [this](sensor_msgs::msg::LaserScan::SharedPtr msg){ process_laser(msg); });
odom_pub_ = create_publisher<nav_msgs::msg::Odometry>("/icp/odom", 1);
}
private:
void process_laser(const sensor_msgs::msg::LaserScan::SharedPtr laser_in)
{
// Get ranges from LiDAR and convert to Eigen Matrix
range = laser_in.get()->ranges;
Map<ArrayXf> r(&range[0], range.size());
r = (r.array() < 50).select(r, 0.0);
// Set the first point cloud to the previous point cloud (q) : Initialization
if(initial)
{
q = range2pc(r);
T = Eigen::MatrixXf::Identity(3, 3);
Tr = Eigen::MatrixXf::Identity(3, 3);
initial = false;
}
// Convert LiDAR ranges to 2D point cloud
p = range2pc(r);
// Run ICP between successive point clouds and get homogenous transformation matrix
T = runICP(q, p, maxiters);
// Update current transform estimate
Tr = Tr * T;
// Set previous point cloud (q) to current point cloud (p)
q = p;
// Convert Homogenous Transformation to Quaternion
R_3.block(0, 0, 2, 2) = Tr.block(0, 0, 2, 2);
q_orientation = R_3;
// Declare odometry message and set transform frames
nav_msgs::msg::Odometry odom;
odom.header.frame_id = "odom";
odom.child_frame_id = "base_link";
// Position
odom.pose.pose.position.x = Tr(0, 2);
odom.pose.pose.position.y = Tr(1, 2);
// Orientation
odom.pose.pose.orientation.x = 0.0;
odom.pose.pose.orientation.y = 0.0;
odom.pose.pose.orientation.z = q_orientation.z();
odom.pose.pose.orientation.w = q_orientation.w();
odom_pub_->publish(odom);
}
rclcpp::Subscription<sensor_msgs::msg::LaserScan>::SharedPtr laser_sub_;
rclcpp::Publisher<nav_msgs::msg::Odometry>::SharedPtr odom_pub_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<Laser>());
rclcpp::shutdown();
return 0;
}