pyoints.registration package

Submodules

pyoints.registration.icp module

Implementation of the Iterative Closest Point Algorithm.

class pyoints.registration.icp.ICP(radii, max_iter=50, max_change_ratio=0.001, assign_class=<class 'pyoints.assign.KnnMatcher'>, update_normals=False, **assign_parameters)[source]

Bases: object

Implementation of a variant of the Iterative Closest Point algorithm [1] with support of multiple point sets and point normals.

Parameters:
  • radii (array_like(Number, shape=(s))) – Maximum distances in each coordinate dimension to assign corresponding points of k dimensions. The length of radii is equal to 2 * k. If point normals shall also be used to find point pairs, the length of radii is k.
  • assign_class (optional, callable class) – Class which assigns pairs of points.
  • max_iter (optional, positive int) – Maximum number of iterations.
  • update_normals (bool) – Indicates whether or not to also transform the normals.
  • **assign_parameters – Parameters passed to assign_class.

Notes

A modified variant of the originally ICP algorithm presented by Besl and McKay (1992) [1]. Inspired by the Normal ICP algorithm of Serafin and Grisetti [2][3] a ICP variant the surface normals has been implemented.

References

[1] P.J. Besl and N.D. McKay (1992): “A Method for Registration of 3-D Shapes”, IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 14 (2): 239-256. [2] J. Serafin and G. Grisetti (2014): “Using augmented measurements to improve the convergence of icp”, International Conference on Simulation, Modeling, and Programming for Autonomous Robots. Springer, Cham: 566-577. [3] J. Serafin and G. Grisetti (2014): “NICP: Dense normal based point cloud registration”, International Conference on Intelligent Robots and Systems (IROS): 742-749.

Examples

Create corresponding point sets.

>>> A = np.array([
...     (0.5, 0.5), (0, 0), (0, -0.1), (1.3, 1), (1, 0), (-1, -2)
... ])
>>> B = np.array([(0.4, 0.5), (0.3, 0), (1, 1), (2, 1), (-1, -2)])

Standard ICP.

>>> coords_dict = {'A': A, 'B': B}
>>> radii = (0.25, 0.25)
>>> weights = {'A': [1, 1, 1]}
>>> icp = ICP(radii, max_iter=10, k=1)
>>> T, pairs, report = icp(coords_dict, weights=weights)
>>> tA = T['A'].to_local(A)
>>> tB = T['B'].to_local(B)
>>> print_rounded(tA, 1)
[[ 0.5  0.5]
 [ 0.   0. ]
 [ 0.  -0.1]
 [ 1.3  1. ]
 [ 1.   0. ]
 [-1.  -2. ]]
>>> print_rounded(tB, 1)
[[ 0.6  0.5]
 [ 0.4  0. ]
 [ 1.2  0.9]
 [ 2.2  0.9]
 [-1.  -1.9]]

Find matches and compare RMSE (Root Mean Squared Error).

>>> matcher = assign.KnnMatcher(tA, radii)
>>> pairs = matcher(tB)
>>> rmse = distance.rmse(A[pairs[:, 0], :], B[pairs[:, 1], :])
>>> print_rounded(rmse, 2)
0.18
>>> rmse = distance.rmse(tA[pairs[:, 0], :], tB[pairs[:, 1], :])
>>> print_rounded(rmse, 2)
0.09

ICP also takes advantage of normals (NICP).

>>> from pyoints.normals import fit_normals
>>> normals_r = 1.5
>>> normals_dict = {
...     'A': fit_normals(A, normals_r),
...     'B': fit_normals(B, normals_r)
... }
>>> radii = (0.25, 0.25, 0.3, 0.3)
>>> nicp = ICP(radii, max_iter=10, k=1)
>>> T, pairs, report = nicp(coords_dict, normals_dict=normals_dict)
>>> tA = T['A'].to_local(A)
>>> print_rounded(tA)
[[ 0.5  0.5]
 [ 0.   0. ]
 [ 0.  -0.1]
 [ 1.3  1. ]
 [ 1.   0. ]
 [-1.  -2. ]]
>>> tB = T['B'].to_local(B)
>>> print_rounded(tB)
[[ 0.4  0.5]
 [ 0.3  0. ]
 [ 1.   1. ]
 [ 2.   1. ]
 [-1.  -2. ]]

pyoints.registration.registration module

Registration or alignment of point sets.

pyoints.registration.registration.find_rototranslation(A, B)[source]

Finds the optimal roto-translation matrix M between two point sets. Each point of point set A is associated with exactly one point in point set B.

Parameters:A,B (array_like(Number, shape=(n, k))) – Arrays representing n corresponding points with k dimensions.
Returns:M – Roto-translation matrix to map B to A with A = B * M.T.
Return type:numpy.matrix(float, shape=(k+1, k+1))

Notes

Implements the registration algorithm of Besl and McKay (1992) [1]. The idea has been taken from Nghia Ho (2013) [2]. Code of [2] has been generalized to k dimensional space.

References

[1] P. J. Besl and N. D. McKay (1992): “A Method for Registration of 3-D Shapes”, IEEE Transactions on Pattern Analysis and Machine Intelligence, Institute of Electrical and Electronics Engineers (IEEE), vol. 14, pp. 239-256.

[2] Nghia Ho (2013): “Finding optimal rotation and translation between corresponding 3D points”, URL http://nghiaho.com/?page_id=671.

[3] Nghia Ho (2013): “Finding optimal rotation and translation between corresponding 3D points”, URL http://nghiaho.com/uploads/code/rigid_transform_3D.py_.

Examples

Creates similar, but shifted and rotated point sets.

>>> A = np.array([[0, 0], [0, 1], [1, 1], [1, 0]])
>>> B = transformation.transform(A, transformation.matrix(t=[3, 5], r=0.3))

Finds roto-translation.

>>> M = find_rototranslation(A, B)
>>> C = transformation.transform(B, M, inverse=False)
>>> print_rounded(C, 2)
[[ 0.  0.]
 [ 0.  1.]
 [ 1.  1.]
 [ 1.  0.]]
pyoints.registration.registration.find_transformation(A, B)[source]

Finds the optimal (non-rigid) transformation matrix M between two point sets. Each point of point set A is associated with exactly one point in point set B.

Parameters:
  • A (array_like(Number, shape=(n, k))) – Array representing n reference points with k dimensions.
  • B (array_like(Number, shape=(n, k))) – Array representing n points with k dimensions.
Returns:

M – Tranformation matrix which maps B to A with A = B * M.T.

Return type:

np.matrix(Number, shape=(k+1, k+1))

pyoints.registration.rototranslations module

Finds roto-translation matrices of multiple point sets.

pyoints.registration.rototranslations.find_rototranslations(coords_dict, pairs_dict, weights=None)[source]

Finds the optimal roto-translation matrices between multiple point sets using pairs of points. The algorithm assumes infinitesimal rotations between the point sets.

Parameters:
  • coords_dict (dict of array_like(int, shape=(n, k))) – Dictionary of point sets with k dimensions.
  • pairs_dict (dict of array_like(int, shape=(m, 2))) – Dictionary of point pairs.
  • weights (optional, dict or list or int.) – Tries to keep the original location and orientation by weighting. Each point set can be weighted by a list of values. The first k values represent the weighting factors for location. The last values represent the weighting factors for orientation (angles). The weights can be provided for each point set individially in form of a dictionary. If not provided, weights are set to zero.
Returns:

T_dict – Dictionary of desired roto-translation matrices.

Return type:

dict of np.ndarray(Number, shape=(k+1, k+1))

Notes

Algorithm idea taken from [1].

References

[1] University of Genoa (2017): URL http://geomatica.como.polimi.it/corsi/def_monitoring/roto-translationsb.pdf.

Examples

2D coordinates.

>>> coordsA = [(-1, -2), (-1, 2), (1, 2), (1, -2), (5, 10)]
>>> T = transformation.matrix(
...     t=[10, 4],
...     r=0.03,
... )
>>> coordsB = transformation.transform(coordsA, T)
>>> coords_dict = {'A': coordsA, 'B': coordsB}
>>> pairs_dict = { 'A': { 'B': [(0, 0), (1, 1), (2, 2)] } }
>>> weights = {'A': [1, 1, 1], 'B': [0, 0, 0]}
>>> res = find_rototranslations(coords_dict, pairs_dict, weights=weights)
>>> print(sorted(res.keys()))
['A', 'B']
>>> tA = res['A'].to_local(coords_dict['A'])
>>> print_rounded(tA)
[[ -1.  -2.]
 [ -1.   2.]
 [  1.   2.]
 [  1.  -2.]
 [  5.  10.]]
>>> tB = res['B'].to_local(coords_dict['B'])
>>> print_rounded(tB)
[[ -1.  -2.]
 [ -1.   2.]
 [  1.   2.]
 [  1.  -2.]
 [  5.  10.]]

3D coordinates.

>>> coordsA = [(-10, -20, 3), (-1, 2, 4), (1, 10, 5), (1, -2, 60)]
>>> TB = transformation.matrix(
...     t=[2, 5, 10], r=[-0.01, 0.02, 0.03], order='trs')
>>> coordsB = transformation.transform(coordsA, TB)
>>> TC= transformation.matrix(
...     t=[-2, 3, -6], r=[-0.03, 0.01, -0.02], order='trs')
>>> coordsC = transformation.transform(coordsA, TC)
>>> coords_dict = {'A': coordsA, 'B': coordsB, 'C': coordsC}
>>> pairs_dict = {
...     'A': { 'B': [(0, 0), (1, 1), (3, 3)], 'C': [(0, 0), (3, 3)]  },
...     'B': { 'A': [(0, 0), (1, 1), (3, 3)], 'C': [(1, 1), (2, 2)] },
...     'C': { 'A': [(0, 0), (1, 1), (3, 3)], 'B': [(1, 1), (2, 2)] },
... }
>>> weights = {'A': [1, 1, 1, 1, 1, 1], 'B': [0, 0, 0, 0, 0, 0]}
>>> res = find_rototranslations(coords_dict, pairs_dict, weights=weights)
>>> print(sorted(res.keys()))
['A', 'B', 'C']
>>> tA = res['A'].to_local(coords_dict['A'])
>>> print_rounded(tA, 1)
[[-10. -20.   3.]
 [ -1.   2.   4.]
 [  1.  10.   5.]
 [  1.  -2.  60.]]
>>> tB = res['B'].to_local(coords_dict['B'])
>>> print_rounded(tB, 1)
[[-10. -20.   3.]
 [ -1.   2.   4.]
 [  1.  10.   5.]
 [  1.  -2.  60.]]
>>> tC = res['C'].to_local(coords_dict['C'])
>>> print_rounded(tC, 1)
[[-10. -20.   3.]
 [ -1.   2.   4.]
 [  1.  10.   5.]
 [  1.  -2.  60.]]

Module contents

Alignment of point clouds.