icp_2d.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2015 Fetch Robotics Inc.
3  * Author: Michael Ferguson
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <icp_2d.h>
20 
21 #include <eigen3/Eigen/Dense>
22 #include <eigen3/Eigen/SVD>
23 #include <eigen3/Eigen/Eigenvalues>
24 
25 namespace icp_2d
26 {
27 
28 double thetaFromQuaternion(const geometry_msgs::Quaternion& q)
29 {
30  // If all zeros, is invalid, assume no rotation
31  if (q.x == 0.0 && q.y == 0.0 && q.z == 0.0 && q.w == 0.0)
32  {
33  return 0.0;
34  }
35 
36  // Compute theta (this only works if q is in 2d)
37  return 2.0*atan2(q.z, q.w);
38 }
39 
40 std::vector<geometry_msgs::Point>
41 transform(const std::vector<geometry_msgs::Point>& points,
42  double x,
43  double y,
44  double theta)
45 {
46  std::vector<geometry_msgs::Point> points_t;
47 
48  double cos_th = cos(theta);
49  double sin_th = sin(theta);
50 
51  for (size_t i = 0; i < points.size(); i++)
52  {
53  geometry_msgs::Point pt = points[i];
54  geometry_msgs::Point pt_t;
55 
56  // Rotate then Translate
57  pt_t.x = cos_th * pt.x - sin_th * pt.y + x;
58  pt_t.y = sin_th * pt.x + cos_th * pt.y + y;
59 
60  points_t.push_back(pt_t);
61  }
62 
63  return points_t;
64 }
65 
66 geometry_msgs::Point getCentroid(const std::vector<geometry_msgs::Point> points)
67 {
68  geometry_msgs::Point pt;
69  for (size_t i = 0; i < points.size(); i++)
70  {
71  pt.x += points[i].x;
72  pt.y += points[i].y;
73  }
74  pt.x /= points.size();
75  pt.y /= points.size();
76  return pt;
77 }
78 
79 // Computes correspondences for alignment
80 // correspondences will be equal in size to source, and contain the
81 // closest point in target that corresponds to that in source.
82 bool computeCorrespondences(const std::vector<geometry_msgs::Point>& source,
83  const std::vector<geometry_msgs::Point>& target,
84  std::vector<geometry_msgs::Point>& correspondences)
85 {
86  correspondences.clear();
87  std::vector<size_t> c_num;
88 
89  for (size_t i = 0; i < source.size(); i++)
90  {
91  double d = 1.0;
92  size_t best = 0;
93 
94  for (size_t j = 0; j < target.size(); j++)
95  {
96  double dx = source[i].x - target[j].x;
97  double dy = source[i].y - target[j].y;
98  double dist = dx * dx + dy * dy;
99  if (dist < d)
100  {
101  best = j;
102  d = dist;
103  }
104  }
105 
106  if (d >= 1.0)
107  {
108  // No correspondence found!
109  return false;
110  }
111  correspondences.push_back(target[best]);
112  c_num.push_back(best);
113  }
114 
115  // All points have correspondence
116  return true;
117 }
118 
119 bool alignPCA(const std::vector<geometry_msgs::Point> source,
120  const std::vector<geometry_msgs::Point> target,
121  geometry_msgs::Transform& t)
122 {
123  // Get initial rotation angle
124  double theta = thetaFromQuaternion(t.rotation);
125 
126  // Transform source based on initial transfrom
127  std::vector<geometry_msgs::Point> source_t = transform(source,
128  t.translation.x,
129  t.translation.y,
130  theta);
131 
132  // Get centroid of source and target
133  geometry_msgs::Point cs = getCentroid(source_t);
134  geometry_msgs::Point ct = getCentroid(target);
135 
136  // Update translation
137  t.translation.x += ct.x - cs.x;
138  t.translation.y += ct.y - cs.y;
139 
140  // Compute P for source
141  Eigen::MatrixXf Ps(2, source_t.size());
142  for (size_t i = 0; i < source_t.size(); i++)
143  {
144  Ps(0, i) = source_t[i].x - cs.x;
145  Ps(1, i) = source_t[i].y - cs.y;
146  }
147 
148  // Compute M for source (covariance matrix)
149  Eigen::MatrixXf Ms = Ps * Ps.transpose();
150 
151  // Get EigenVectors of Covariance Matrix
152  Eigen::EigenSolver<Eigen::MatrixXf> solver_s(Ms);
153  Eigen::MatrixXf A = solver_s.eigenvectors().real();
154  Eigen::MatrixXf eig_of_Ms = solver_s.eigenvalues().real();
155  // Sort the eigen vectors for the ideal dock.
156  // There are only two eigenvalues. If they come out not sorted,
157  // swap the vectors, and make sure the axes are still right handed.
158  if (eig_of_Ms(0, 0) < eig_of_Ms(1, 0))
159  {
160  A.col(0).swap(A.col(1));
161  A.col(1) = -A.col(1);
162  }
163  // Compute P for target
164  Eigen::MatrixXf Pt(2, target.size());
165  for (size_t i = 0; i < target.size(); i++)
166  {
167  Pt(0, i) = target[i].x - ct.x;
168  Pt(1, i) = target[i].y - ct.y;
169  }
170 
171  // Compute M for target (covariance matrix)
172  Eigen::MatrixXf Mt = Pt * Pt.transpose();
173 
174  // Get EigenVectors of Covariance Matrix
175  Eigen::EigenSolver<Eigen::MatrixXf> solver_t(Mt);
176  Eigen::MatrixXf B = solver_t.eigenvectors().real();
177  Eigen::MatrixXf eig_of_Mt = solver_t.eigenvalues().real();
178  // Sort the eigen vectors for the candidate dock.
179  // There are only two eigenvalues. If they come out not sorted,
180  // swap the vectors.
181  if (eig_of_Mt(0, 0) < eig_of_Mt(1, 0))
182  {
183  B.col(0).swap(B.col(1));
184  B.col(1) = -B.col(1);
185  }
186 
187  // Get rotation
188  Eigen::MatrixXf R = B * A.transpose();
189 
190  // Update rotation
191  theta += atan2(R(1, 0), R(0, 0));
192  t.rotation.z = sin(theta / 2.0);
193  t.rotation.w = cos(theta / 2.0);
194  return true;
195 }
196 
197 bool alignSVD(const std::vector<geometry_msgs::Point> source,
198  const std::vector<geometry_msgs::Point> target,
199  geometry_msgs::Transform& t)
200 {
201  double theta = thetaFromQuaternion(t.rotation);
202 
203  // Transform source based on initial transform
204  std::vector<geometry_msgs::Point> source_t = transform(source,
205  t.translation.x,
206  t.translation.y,
207  theta);
208 
209  // Get Correspondences
210  // Technically this should be source_t -> target, however
211  // to keep the frame transforms simpler, we actually call this
212  // function with the ideal cloud as source, and the sensor
213  // data as target. The correspondences then need to be setup
214  // so that we map each point in the target onto a point in the
215  // source, since the laser may not see the whole dock.
216  std::vector<geometry_msgs::Point> corr;
217  if (!computeCorrespondences(target, source_t, corr))
218  {
219  return false;
220  }
221 
222  // Get centroid of source_t and corr
223  geometry_msgs::Point cs = getCentroid(corr);
224  geometry_msgs::Point ct = getCentroid(target);
225 
226  // Compute P
227  Eigen::MatrixXf P(2, corr.size());
228  for (size_t i = 0; i < corr.size(); i++)
229  {
230  P(0, i) = corr[i].x - cs.x;
231  P(1, i) = corr[i].y - cs.y;
232  }
233 
234  // Compute Q
235  Eigen::MatrixXf Q(2, target.size());
236  for (size_t i = 0; i < target.size(); i++)
237  {
238  Q(0, i) = target[i].x - ct.x;
239  Q(1, i) = target[i].y - ct.y;
240  }
241 
242  // Compute M
243  Eigen::MatrixXf M = P * Q.transpose();
244 
245  // Find SVD of M
246  Eigen::JacobiSVD<Eigen::MatrixXf> svd(M, Eigen::ComputeThinU | Eigen::ComputeThinV);
247 
248  // Get R (rotation matrix from source to target)
249  Eigen::MatrixXf R = svd.matrixV() * svd.matrixU().transpose();
250 
251  // Compute final transformation
252  t.translation.x += (ct.x - cs.x);
253  t.translation.y += (ct.y - cs.y);
254 
255  // Update rotation of transform
256  theta += atan2(R(1, 0), R(0, 0));
257  t.rotation.z = sin(theta / 2.0);
258  t.rotation.w = cos(theta / 2.0);
259  return true;
260 }
261 
262 double getRMSD(const std::vector<geometry_msgs::Point> source,
263  const std::vector<geometry_msgs::Point> target)
264 {
265  // Compute correspondences
266  std::vector<geometry_msgs::Point> corr;
267  if (!computeCorrespondences(source, target, corr))
268  {
269  // No correspondences, return massive number
270  // (we considered using std::numeric_limits<double>::max() here, but
271  // that could lead to under/overflow later on when comparing RMSD)
272  return 10e6;
273  }
274 
275  // Get Root Mean Squared Distance (RMSD)
276  double rmsd = 0.0;
277  for (size_t i = 0; i < source.size(); i++)
278  {
279  double dx = source[i].x - corr[i].x;
280  double dy = source[i].y - corr[i].y;
281  rmsd += dx * dx + dy * dy;
282  }
283  rmsd /= corr.size();
284  rmsd = sqrt(rmsd);
285 
286  return rmsd;
287 }
288 
289 double alignICP(const std::vector<geometry_msgs::Point> source,
290  const std::vector<geometry_msgs::Point> target,
291  geometry_msgs::Transform & t,
292  size_t max_iterations,
293  double min_delta_rmsd)
294 {
295  // Initial alignment with PCA
296  alignPCA(source, target, t);
297 
298  // Previous RMSD
299  double prev_rmsd = -1.0;
300 
301  // Iteratively refine with SVD
302  for (size_t iteration = 0; iteration < max_iterations; iteration++)
303  {
304  // Perform SVD
305  if (!alignSVD(source, target, t))
306  {
307  // SVD failed
308  return -1.0;
309  }
310 
311  // Transform source to target
312  std::vector<geometry_msgs::Point>
313  source_t = transform(source,
314  t.translation.x,
315  t.translation.y,
316  thetaFromQuaternion(t.rotation));
317 
318  // As above, this should technically be the other way around,
319  // However, at this phase we want to check the
320  double rmsd = getRMSD(target, source_t);
321 
322  // Check termination condition
323  if (prev_rmsd > 0.0)
324  {
325  if (fabs(prev_rmsd - rmsd) < min_delta_rmsd)
326  {
327  // Terminate on RMSD, return based on ideal -> observed
328  return getRMSD(source_t, target);
329  }
330  }
331  prev_rmsd = rmsd;
332  }
333 
334  // Transform source to target one last time
335  std::vector<geometry_msgs::Point>
336  source_t = transform(source,
337  t.translation.x,
338  t.translation.y,
339  thetaFromQuaternion(t.rotation));
340 
341  // Return RMSD based on ideal -> observed
342  return getRMSD(source_t, target);
343 }
344 
345 } // namespace icp_2d
d
std::vector< geometry_msgs::Point > transform(const std::vector< geometry_msgs::Point > &points, double x, double y, double theta)
Transform a vector of points in 2d.
Definition: icp_2d.cpp:41
double alignICP(const std::vector< geometry_msgs::Point > source, const std::vector< geometry_msgs::Point > target, geometry_msgs::Transform &transform, size_t max_iterations=10, double min_delta_rmsd=0.000001)
Perform Iterative Closest Point (ICP) algorithm to align two point clouds in a two dimensional plane...
Definition: icp_2d.cpp:289
double getRMSD(const std::vector< geometry_msgs::Point > source, const std::vector< geometry_msgs::Point > target)
Definition: icp_2d.cpp:262
double thetaFromQuaternion(const geometry_msgs::Quaternion &q)
Get the 2d rotation from a quaternion.
Definition: icp_2d.cpp:28
Definition: icp_2d.h:26
geometry_msgs::Point getCentroid(const std::vector< geometry_msgs::Point > points)
Get the centroid of a set of points.
Definition: icp_2d.cpp:66
INLINE Rall1d< T, V, S > sqrt(const Rall1d< T, V, S > &arg)
bool alignSVD(const std::vector< geometry_msgs::Point > source, const std::vector< geometry_msgs::Point > target, geometry_msgs::Transform &transform)
Perform SVD optimization to align two point clouds in a two dimensional plane.
Definition: icp_2d.cpp:197
bool computeCorrespondences(const std::vector< geometry_msgs::Point > &source, const std::vector< geometry_msgs::Point > &target, std::vector< geometry_msgs::Point > &correspondences)
Definition: icp_2d.cpp:82
bool alignPCA(const std::vector< geometry_msgs::Point > source, const std::vector< geometry_msgs::Point > target, geometry_msgs::Transform &transform)
Perform PCA algorithm to align two point clouds in a two dimensional plane.
Definition: icp_2d.cpp:119
INLINE Rall1d< T, V, S > atan2(const Rall1d< T, V, S > &y, const Rall1d< T, V, S > &x)
INLINE Rall1d< T, V, S > cos(const Rall1d< T, V, S > &arg)
INLINE Rall1d< T, V, S > sin(const Rall1d< T, V, S > &arg)


caster_app
Author(s): Ye Tian
autogenerated on Wed Dec 18 2019 03:34:44