calibration_tools_v1.0/svd.py
2025-02-20 10:45:17 +08:00

363 lines
10 KiB
Python

# -- coding: utf-8 --
import numpy as np
from scipy.linalg import svd
from scipy.spatial import KDTree
def best_fit_transform(A, B):
"""
Calculates the least-squares best-fit transform that maps corresponding points A to B in m spatial dimensions.
Input:
A: Nxm numpy array of corresponding points
B: Nxm numpy array of corresponding points
Returns:
R: mxm rotation matrix
t: mx1 translation vector
"""
assert A.shape == B.shape
# get number of dimensions
m = A.shape[1]
# translate points to their centroids
centroid_A = np.mean(A, axis=0)
centroid_B = np.mean(B, axis=0)
AA = A - centroid_A
BB = B - centroid_B
# rotation matrix
H = np.dot(AA.T, BB)
# U, S, Vt = np.linalg.svd(H)
U, S, Vt = svd(H)
R = np.dot(Vt.T, U.T)
# special reflection case
if np.linalg.det(R) < 0:
Vt[m-1,:] *= -1
R = np.dot(Vt.T, U.T)
# translation
t = centroid_B.T - np.dot(R,centroid_A.T)
return R, t
def nearest_neighbor(src, dst):
"""
Find the nearest (Euclidean) neighbor in dst for each point in src
Input:
src: Nxm array of points
dst: Nxm array of points
Output:
distances: Euclidean distances of the nearest neighbor
indices: dst indices of the nearest neighbor
"""
all_dists = np.array([np.linalg.norm(src[i] - dst, axis=1) for i in range(src.shape[0])])
indices = all_dists.argmin(axis=1)
distances = all_dists[np.arange(all_dists.shape[0]), indices]
return distances, indices
def icp(A, B, init_pose=None, max_iterations=1, tolerance=0.0002):
"""
The Iterative Closest Point method: finds best-fit transform that maps points A on to points B
Input:
A: Nxm numpy array of source mD points
B: Nxm numpy array of destination mD point
init_pose: (m+1)x(m+1) homogeneous transformation
max_iterations: exit algorithm after max_iterations
tolerance: convergence criteria
Output:
T: final homogeneous transformation that maps A on to B
distances: Euclidean distances (errors) of the nearest neighbor
i: number of iterations to converge
"""
assert A.shape == B.shape
# get number of dimensions
m = A.shape[1]
# make points homogeneous, copy them so as to maintain the originals
src = np.ones((m+1,A.shape[0]))
dst = np.ones((m+1,B.shape[0]))
src[:m,:] = np.copy(A.T)
dst[:m,:] = np.copy(B.T)
# apply the initial pose estimation
if init_pose is not None:
src = np.dot(init_pose, src)
prev_error = 0
for i in range(max_iterations):
# find the nearest neighbors between the current source and destination points
distances, indices = nearest_neighbor(src[:m,:].T, dst[:m,:].T)
# compute the transformation between the current source and nearest destination points
R, t = best_fit_transform(src[:m,:].T, dst[:m,indices].T)
# update the current source
src[:m,:] = np.dot(R, src[:m,:]) + t[:, np.newaxis]
# check error
mean_error = np.mean(distances)
if abs(prev_error - mean_error) < tolerance:
break
prev_error = mean_error
# calculate final transformation
T = np.eye(m+1)
T[:m, :m] = R
T[:m, m] = t
return R, t, distances, i
# def scipy_svd(A, B):
# """
# Input:
# A: Nx3 points in matrix form in source space
# B: Nx3 points in matrix form in target space
# """
# N = A.shape[0]
# # Mean of source and target points
# centroid_A = np.mean(A, axis=0)
# centroid_B = np.mean(B, axis=0)
# # Center points
# A_centered = A - centroid_A
# B_centered = B - centroid_B
# # Dot product
# H = np.dot(A_centered.T, B_centered)
# # Singular Value Decomposition
# # U, S, Vt = svd(H, full_matrices=False, lapack_driver='gesdd')
# # U, S, Vt = svd(H)
# U, S, Vt = np.linalg.svd(H)
# # V is Vt transposed
# V = Vt.T
# # R, t
# R = np.dot(V, U.T)
# if np.linalg.det(R) < 0:
# # Special case to correct for reflection
# V[:, -1] *= -1
# R = np.dot(V, U.T)
# t = centroid_B.reshape(3, 1) - np.dot(R, centroid_A.reshape(3, 1))
# return R, t.ravel(), None, None
# def scipy_svd(A, B):
# """
# Input:
# A: Nx3 points in matrix form in source space
# B: Nx3 points in matrix form in target space
# """
# N = A.shape[0]
# # Mean of source and target points
# centroid_A = np.mean(A, axis=0)
# centroid_B = np.mean(B, axis=0)
# # Center points
# A_centered = A - centroid_A
# B_centered = B - centroid_B
# # Dot product
# H = np.dot(A_centered.T, B_centered)
# # Singular Value Decomposition
# U, S, Vt = svd(H)
# # V is Vt transposed
# V = Vt.T
# # R, t
# R = np.dot(V, U.T)
# if np.linalg.det(R) < 0:
# # Special case to correct for reflection
# V[:, -1] *= -1
# R = np.dot(V, U.T)
# t = centroid_B.reshape(3, 1) - np.dot(R, centroid_A.reshape(3, 1))
# return R, t.ravel(), None, None
def scipy_svd(A, B):
"""
Input:
A: Nx3 points in matrix form in source space
B: Nx3 points in matrix form in target space
Output:
R: Rotation matrix
t: Translation vector
"""
N = A.shape[0]
# Mean of source and target points
centroid_A = np.mean(A, axis=0)
centroid_B = np.mean(B, axis=0)
# Center points
A_centered = A - centroid_A
B_centered = B - centroid_B
# Dot product
H = np.dot(A_centered.T, B_centered)
# Singular Value Decomposition
U, S, Vt = svd(H)
# Calculate rotation matrix
R = np.dot(Vt.T, U.T)
# Check for reflection and correct if necessary
if np.linalg.det(R) < 0:
Vt[-1, :] *= -1 # Correct the reflection by flipping the sign of the last row of Vt
R = np.dot(Vt.T, U.T)
# Calculate translation vector
t = centroid_B - np.dot(R, centroid_A)
return R, t, None, None
def np_svd(A, B):
"""
Input:
A: Nx3 points in matrix form in source space
B: Nx3 points in matrix form in target space
"""
N = A.shape[0]
# Mean of source and target points
centroid_A = np.mean(A, axis=0)
centroid_B = np.mean(B, axis=0)
# Center points
A_centered = A - centroid_A
B_centered = B - centroid_B
# Dot product
H = np.dot(A_centered.T, B_centered)
# Singular Value Decomposition
U, S, Vt = np.linalg.svd(H)
# V is Vt transposed
V = Vt.T
# R, t
R = np.dot(V, U.T)
if np.linalg.det(R) < 0:
# Special case to correct for reflection
V[:, -1] *= -1
R = np.dot(V, U.T)
t = centroid_B.reshape(3, 1) - np.dot(R, centroid_A.reshape(3, 1))
return R, t.ravel(), None, None
def icp_svd(source_points, target_points, max_iterations=1, tolerance=1e-6):
"""
使用 SVD 方法执行 ICP 算法来对齐两个点云。
参数:
source_points (numpy.ndarray): 源点云,形状为 (N, 3)
target_points (numpy.ndarray): 目标点云,形状为 (M, 3)
max_iterations (int): 最大迭代次数,默认为 100
tolerance (float): 收敛误差阈值,默认为 1e-6
返回:
aligned_source (numpy.ndarray): 变换后的源点云
R (numpy.ndarray): 旋转矩阵
t (numpy.ndarray): 平移向量
"""
# 初始化误差
prev_error = float('inf')
source = source_points.copy()
for iteration in range(max_iterations):
# 寻找对应点
tree = KDTree(target_points)
distances, indices = tree.query(source)
# 计算质心
source_center = np.mean(source, axis=0)
target_center = np.mean(target_points[indices], axis=0)
# 中心化点云
source_centered = source - source_center
target_centered = target_points[indices] - target_center
# 构建协方差矩阵并执行 SVD 分解
H = source_centered.T @ target_centered
# U, _, Vt = np.linalg.svd(H)
U, _, Vt = svd(H)
# 计算旋转矩阵 R
R = Vt.T @ U.T
# 检查是否有反射并修正
if np.linalg.det(R) < 0:
Vt[-1, :] *= -1
R = Vt.T @ U.T
# 计算平移向量 t
t = target_center - R @ source_center
# 应用变换到源点云
source = (R @ source_centered.T).T + t
# 计算误差
current_error = np.mean(distances)
print(f"Iteration {iteration}, Error: {current_error}")
# 检查是否满足终止条件
if abs(prev_error - current_error) < tolerance:
break
prev_error = current_error
return R, t, source, None
if __name__ == "__main__":
source_points = np.array([[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0]], dtype=np.float64)
target_points = np.array([[1.0, 0.0, 0.0],
[2.0, 0.0, 0.0],
[1.0, 1.0, 0.0]], dtype=np.float64)
aligned_source, R, t = icp_svd(source_points, target_points)
print("Aligned Source Points:\n", aligned_source)
print("Rotation Matrix:\n", R)
print("Translation Vector:\n", t)
if __name__ == "__main__":
# Test with random data
np.random.seed(0)
num_points = 7
dim = 3
noise_sigma = .01
outlier_ratio = 0.1
A = np.random.rand(num_points, dim)
T = np.eye(dim+1)
angle = np.pi / 4
T[:dim, :dim] = [[np.cos(angle), -np.sin(angle)],
[np.sin(angle), np.cos(angle)]]
T[:-1, -1] = np.array([1, 2])
B = np.dot(T, np.hstack((A.T, np.ones((1, num_points)))))
B = B[:dim, :].T
# add Gaussian noise
B += np.random.randn(num_points, dim) * noise_sigma
# add some outliers
n_outliers = int(outlier_ratio * float(num_points))
indices = np.arange(num_points)
np.random.shuffle(indices)
B[indices[-n_outliers:], :] += 50 * np.random.randn(n_outliers, dim)
# run ICP
R, t, distances, i = icp(A, B)
print("Final Transformation Matrix:\n", T)
print("Mean Distance:", np.mean(distances))
print("Iterations:", i)