Skip to content

Commit e43af93

Browse files
authored
Feature/arun scaled: sim3 for PC aligment using the Arun method (#44)
* sim3 for PC aligment using the Arun method. * Fix minor issue, now scaled Arun tested
1 parent 9e4fc71 commit e43af93

File tree

6 files changed

+202
-1
lines changed

6 files changed

+202
-1
lines changed

python_examples/PC_scaled_align.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#
2+
# add path to local library mrob on bashr_rc: "export PYTHONPATH=${PYTHONPATH}:${HOME}/mrob/mrob/lib"
3+
import pybind as mrob
4+
import numpy as np
5+
import open3d
6+
7+
# generate random data
8+
N = 10
9+
X = np.random.rand(N,3)
10+
theta = np.random.randn(3)
11+
t = np.array([0,0,0])
12+
T = mrob.geometry.SE3(mrob.geometry.SO3(theta),t)
13+
Y = T.transform_array(X)
14+
scale = 1000
15+
t = np.random.randn(3)
16+
Y = scale*Y + t + np.random.rand(N,3)*0.01
17+
18+
S = T.T()
19+
S[:3,:3] *= scale
20+
S[:3,3] = t
21+
22+
print('X = \n', X,'\n S = \n', S,'\n Y =\n', Y)
23+
24+
25+
26+
27+
def pcd_1(X, color, T = np.identity(4)):
28+
pcd = open3d.geometry.PointCloud()
29+
pcd.points = open3d.utility.Vector3dVector(X)
30+
pcd.transform(T)
31+
pcd.paint_uniform_color(color)
32+
return pcd
33+
34+
def vis_her(X, Y, T = np.identity(4)):
35+
blue = np.array([0,0,1], dtype='float64')
36+
red = np.array([1,0,0], dtype='float64')
37+
open3d.visualization.draw_geometries([pcd_1(X,red,T), pcd_1(Y,blue)])
38+
39+
def vis_arr(X):
40+
pcd = open3d.PointCloud()
41+
pcd.points =open3d.utility.Vector3dVector(X)
42+
pcd.paint_uniform_color(np.random.rand(3,).astype(np.float64))
43+
open3d.visualization.draw_geometries([pcd])
44+
45+
46+
47+
# solve the problem
48+
vis_her(X,Y)
49+
S_sol = mrob.registration.scaled_arun(X,Y)
50+
print('Arun solution =\n', S_sol)
51+
print('Initial transformation =\n',S)
52+
vis_her(X,Y,S_sol)
53+

src/PCRegistration/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ SET(sources
66
arun.cpp
77
gicp.cpp
88
weight_point.cpp
9+
scaled_arun.cpp
910
factors/factor1PosePoint2Point.cpp
1011
factors/factor1PosePoint2Plane.cpp
1112
)

src/PCRegistration/arun.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
using namespace mrob;
3232
using namespace Eigen;
3333

34-
int PCRegistration::arun(const Ref<const MatX> X, const Ref<const MatX> Y, SE3 &T)
34+
int PCRegistration::arun(MatRefConst X, MatRefConst Y, SE3 &T)
3535
{
3636
assert(X.cols() == 3 && "PCRegistration::Arun: Incorrect sizing, we expect Nx3");
3737
assert(X.rows() >= 3 && "PCRegistration::Arun: Incorrect sizing, we expect at least 3 correspondences (not aligned)");

src/PCRegistration/mrob/pc_registration.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,19 @@ int weighted_point(MatRefConst X, MatRefConst Y,
9696
VectRefConst w, SE3 &T, double tol = 1e-4);
9797

9898

99+
/**
100+
* This solution is based on the paper by Arun et al. "Least-squares fitting of two 3-D point sets", 1987
101+
* Given N points x = x_1,x_2,x_3 and their correspondences y_i, calculate the
102+
* transformation S = [sR t], such as: min sum (y_i - (sR * x_i + t))^2
103+
*
104+
* An improvement over it was proposed by Sinji Umeyama 1991, which uniqueness determine R
105+
*
106+
* 3 points in x and y are the minimum requirements, and provide the right solution IF
107+
* there is no noise.
108+
*
109+
* Returns 0 if failed and 1 if a correct solution was found
110+
*/
111+
int scaled_arun(MatRefConst X, MatRefConst Y, Mat4 &S);
112+
99113
}}//namespace
100114
#endif /* PC_REGISTRATION_HPP_ */

src/PCRegistration/scaled_arun.cpp

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/* Copyright (c) 2022, Gonzalo Ferrer
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*
15+
*
16+
* arun.cpp
17+
*
18+
* Created on: July 12, 2023
19+
* Author: Gonzalo Ferrer
20+
* g.ferrer@skoltech.ru
21+
* Mobile Robotics Lab, Skoltech
22+
*/
23+
24+
#include <Eigen/LU>
25+
#include <Eigen/SVD>
26+
27+
#include <memory>
28+
#include <iostream>
29+
#include "mrob/pc_registration.hpp"
30+
#include <cmath>
31+
32+
using namespace mrob;
33+
using namespace Eigen;
34+
35+
int PCRegistration::scaled_arun(MatRefConst X, MatRefConst Y, Mat4 &S)
36+
{
37+
assert(X.cols() == 3 && "PCRegistration::Arun: Incorrect sizing, we expect Nx3");
38+
assert(X.rows() >= 3 && "PCRegistration::Arun: Incorrect sizing, we expect at least 3 correspondences (not aligned)");
39+
assert(Y.rows() == X.rows() && "PCRegistration::Arun: Same number of correspondences");
40+
uint_t N = X.rows();
41+
/** Algorithm:
42+
* 1) calculate centroids cx = sum x_i. cy = sum y_i
43+
* 2) calculate dispersion from centroids qx = x_i - cx
44+
* 3) calculate matrix H = sum qx_i * qy_i^T
45+
* 4) svd decomposition: H = U*D*V'
46+
* 4.5) look for co-linear solutions, that is 2 of the 3 singular values are equal
47+
* 5) Calculate the rotation solution R = V*U'
48+
* 5.5) check for correct solution (det = +1) or reflection (det = -1)
49+
* step 5.5 is actually unnecessary IF applying Umeyama technique
50+
* 6) NEW calcualte scale as
51+
* s =
52+
* 7) NEW calculate translation as: t = cy - s R * cx
53+
*/
54+
// We have already asserted in base_T that they are 3xN matrices. (and the same length).
55+
56+
//std::cout << "X: \n" << X << "\nY:\n" << Y << std::endl;
57+
// 1) calculate centroids cx = E{x_i}. cy = E{y_i}
58+
//More efficient than creating a matrix of ones when on Release mode (not is Debug mode)
59+
Mat13 cxm = X.colwise().sum();
60+
cxm /= (double)N;
61+
Mat13 cym = Y.colwise().sum();
62+
cym /= (double)N;
63+
64+
// 2) calculate dispersion from centroids qx = x_i - cx
65+
MatX qx = X.rowwise() - cxm;
66+
MatX qy = Y.rowwise() - cym;
67+
68+
69+
// 3) calculate matrix H = sum qx_i * qy_i^T (noting that we are obtaingin row vectors)
70+
Mat3 H = qx.transpose() * qy;
71+
72+
// 4) svd decomposition: H = U*D*V'
73+
JacobiSVD<Matrix3d> SVD(H, ComputeFullU | ComputeFullV);//Full matrices indicate Square matrices
74+
75+
//test: prints results so far
76+
/*std::cout << "Checking matrix SVD: \n" << SVD.singularValues() <<
77+
",\n U = " << SVD.matrixU() <<
78+
",\n V = " << SVD.matrixV() << std::endl;*/
79+
80+
81+
// 4.5) look for co-linear solutions, that is 2 of the 3 singular values are equal
82+
double l_prev = SVD.singularValues()(0), l;
83+
for(int i =1; i < 3; ++i)
84+
{
85+
l = SVD.singularValues()(i);
86+
if (fabs(l - l_prev) < 1e-6)
87+
88+
return 0; //they are co-linear, there exist infinite transformations
89+
else
90+
l_prev = l;//this works because we assume that they singular values are ordered.
91+
}
92+
93+
// 5) Calculate the rotation solution R = V*U'
94+
Mat3 R = SVD.matrixV() * SVD.matrixU().transpose();
95+
96+
// 5.5) check for correct solution (det = +1) or reflection (det = -1)
97+
// that is, solve the problem for co-planar set of points and centroid, when is l1 > l2 > l3 = 0
98+
// Since H = D1*u1*v1' + D2*u2*v2' + D3*u3*v3', and D3 = 0, we can swap signs in V
99+
// such as Vp = [v1,v2,-v3] and the solution is still minimal, but we want a valid rotation R \in SO(3)
100+
if (R.determinant() < 0.0 )
101+
{
102+
Mat3 Vn;
103+
Vn << SVD.matrixV().topLeftCorner<3,2>(), -SVD.matrixV().topRightCorner<3,1>();
104+
R << Vn * SVD.matrixU().transpose();
105+
//std::cout << "R value = " << R << std::endl;
106+
}
107+
108+
109+
// 6) calculate scale
110+
matData_t scale;
111+
matData_t qx2_sum = qx.col(0).squaredNorm() + qx.col(1).squaredNorm() + qx.col(2).squaredNorm();
112+
matData_t qy2_sum = qy.col(0).squaredNorm() + qy.col(1).squaredNorm() + qy.col(2).squaredNorm();
113+
scale = std::sqrt(qy2_sum / qx2_sum);
114+
115+
// 7) calculate translation as: t = cy - scale *R * cx
116+
Mat31 t = cym.transpose() - scale*R*cxm.transpose();
117+
//std::cout << "t = " << t << std::endl;
118+
119+
// 7) return result
120+
S << scale * R, t,
121+
0,0,0,1;
122+
123+
return 1;
124+
}

src/pybind/PCRegistrationPy.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,20 @@ SE3 weighted_solve(const py::EigenDRef<const MatX> X, const py::EigenDRef<const
6464
return res;
6565
}
6666

67+
68+
Mat4 scaled_arun_solve(const py::EigenDRef<const MatX> X, const py::EigenDRef<const MatX> Y)
69+
{
70+
Mat4 res;
71+
PCRegistration::scaled_arun(X,Y,res);
72+
return res;
73+
}
74+
6775
void init_PCRegistration(py::module &m)
6876
{
6977
m.def("arun", &arun_solve);
7078
m.def("gicp", &gicp_solve);
7179
m.def("weighted", &weighted_solve);
80+
m.def("scaled_arun", &scaled_arun_solve);
7281
}
7382

7483

0 commit comments

Comments
 (0)