Spaces:
Running
on
Zero
Running
on
Zero
import numpy as np | |
import open3d as o3d | |
from scipy.spatial.transform import Rotation | |
from scipy.linalg import orthogonal_procrustes | |
from open3d.pipelines.registration import registration_ransac_based_on_correspondence | |
def rigid_transform_3D(A, B): | |
assert A.shape == B.shape, "Input arrays must have the same shape" | |
assert A.shape[1] == 3, "Input arrays must be Nx3" | |
N = A.shape[0] # Number of points | |
# Compute centroids of A and B | |
centroid_A = np.mean(A, axis=0) | |
centroid_B = np.mean(B, axis=0) | |
# Center the points around the centroids | |
AA = A - centroid_A | |
BB = B - centroid_B | |
# H = AA^T * BB | |
H = np.dot(AA.T, BB) | |
# Singular Value Decomposition | |
U, S, Vt = np.linalg.svd(H) | |
# Compute rotation | |
R = np.dot(Vt.T, U.T) | |
# Ensure a proper rotation (det(R) should be +1) | |
if np.linalg.det(R) < 0: | |
Vt[2, :] *= -1 | |
R = np.dot(Vt.T, U.T) | |
# Compute translation | |
t = centroid_B - np.dot(R, centroid_A) | |
# Construct the transform matrix (4x4) | |
transform_matrix = np.eye(4) | |
transform_matrix[:3, :3] = R | |
transform_matrix[:3, 3] = t | |
return transform_matrix | |
def compute_rigid_transform(points1, points2): | |
""" | |
计算从points1到points2的刚体变换(包括尺度、旋转和平移)。 | |
参数: | |
points1, points2: np.ndarray, 形状为(68, 3)的数组,分别为两组3D对应点。 | |
返回: | |
scale: float, 尺度因子 | |
R: np.ndarray, 3x3的旋转矩阵 | |
t: np.ndarray, 3维的平移向量 | |
""" | |
# 中心化 | |
mean1 = np.mean(points1, axis=0) | |
centered_points1 = points1 - mean1 | |
mean2 = np.mean(points2, axis=0) | |
centered_points2 = points2 - mean2 | |
# 使用orthogonal_procrustes计算旋转和平移 | |
R, _ = orthogonal_procrustes(centered_points1, centered_points2) | |
t = mean2 - R @ mean1 # 计算平移向量 | |
# 计算尺度因子 | |
scale = np.mean(np.linalg.norm(centered_points2, axis=1) / | |
np.linalg.norm(centered_points1, axis=1)) | |
return scale, R, t | |
def compute_rigid_transform_new(points_A, points_B): | |
# 中心化 | |
center_A = np.mean(points_A, axis=0) | |
center_B = np.mean(points_B, axis=0) | |
points_A_centered = points_A - center_A | |
points_B_centered = points_B - center_B | |
# 计算协方差矩阵 | |
cov_matrix = np.dot(points_A_centered.T, points_B_centered) | |
# SVD分解 | |
U, S, Vt = np.linalg.svd(cov_matrix) | |
# 确保旋转矩阵为正交且右手系,这里我们取Vt的转置作为旋转矩阵 | |
rotation_matrix = np.dot(Vt.T, U.T) | |
# 检查行列式是否为-1(表示反射,不满足旋转矩阵要求),如果是,则调整一个列的符号 | |
if np.linalg.det(rotation_matrix) < 0: | |
Vt[2,:] *= -1 | |
rotation_matrix = np.dot(Vt.T, U.T) | |
# 计算尺度因子 | |
scale = np.trace(np.dot(points_A_centered.T, points_B_centered)) / np.trace(np.dot(points_A_centered.T, points_A_centered)) | |
# 计算平移向量 | |
translation_vector = center_B - scale * np.dot(rotation_matrix, center_A) | |
return scale, rotation_matrix, translation_vector | |
# 示范用法 | |
obj_A = '/home/gyalex/Desktop/our_face.obj' | |
obj_B = '/home/gyalex/Desktop/Neutral.obj' | |
mesh_A = o3d.io.read_triangle_mesh(obj_A) | |
mesh_B = o3d.io.read_triangle_mesh(obj_B) | |
vertices_A = np.asarray(mesh_A.vertices) | |
vertices_B = np.asarray(mesh_B.vertices) | |
list_A = list() | |
list_B = list() | |
with open('/home/gyalex/Desktop/our_marker.txt', 'r') as f: | |
lines_A = f.readlines() | |
for line in lines_A: | |
hh = line.strip().split() | |
list_A.append(int(hh[0])) | |
with open('/home/gyalex/Desktop/ARKit_landmarks.txt', 'r') as f: | |
lines_B = f.readlines() | |
for line in lines_B: | |
hh = line.strip().split() | |
list_B.append(int(hh[0])) | |
A = vertices_A[list_A,:] # 第一组3D点 | |
B = vertices_B[list_B,:] # 第二组3D点 | |
# scale, R, t = compute_rigid_transform(A, B) | |
# # 定义尺度变换矩阵 | |
# scale_matrix = np.eye(4) | |
# scale_matrix[0, 0] = scale # x轴方向放大2倍 | |
# scale_matrix[1, 1] = scale # y轴方向放大2倍 | |
# scale_matrix[2, 2] = scale # z轴方向放大2倍 | |
# transform_matrix = np.eye(4) | |
# transform_matrix[:3, :3] = scale | |
# transform_matrix[:3, 3] = R*t | |
# mesh_A.transform(transform_matrix) | |
# # mesh_A.transform(scale_matrix) | |
# o3d.io.write_triangle_mesh('/home/gyalex/Desktop/our_face_new.obj', mesh_A) | |
pcd_source = o3d.utility.Vector3dVector(A) # 示例源点云数据 | |
pcd_target = o3d.utility.Vector3dVector(B) # 示例目标点云数据 + 1偏移,仅作示例 | |
corres_source = list() | |
for idx in range(68): corres_source.append(idx) | |
corres_target = list() | |
for idx in range(68): corres_target.append(idx) | |
# 根据对应点索引获取实际的对应点坐标 | |
corres_source_points = pcd_source | |
corres_target_points = pcd_target | |
corres = o3d.utility.Vector2iVector([[src, tgt] for src, tgt in zip(corres_source, corres_target)]) | |
# 应用RANSAC进行基于对应点的配准 | |
reg_result = registration_ransac_based_on_correspondence( | |
pcd_source, | |
pcd_target, | |
corres, | |
estimation_method=o3d.pipelines.registration.TransformationEstimationPointToPoint(), | |
ransac_n=3, | |
criteria=o3d.pipelines.registration.RANSACConvergenceCriteria(max_iteration=100000, epsilon=1e-6) | |
) | |
# # 使用RANSAC进行配准 | |
# convergence_criteria = o3d.pipelines.registration.RANSACConvergenceCriteria(max_iteration=50000, max_validation=500) | |
# ransac_result = o3d.pipelines.registration.registration_ransac_based_on_correspondence( | |
# pcd_source, | |
# pcd_target, | |
# corres, | |
# o3d.pipelines.registration.TransformationEstimationPointToPoint(), | |
# 3, # RANSAC阈值,根据实际情况调整 | |
# convergence_criteria, | |
# [o3d.pipelines.registration.CorrespondenceCheckerBasedOnEdgeLength(0.9), | |
# o3d.pipelines.registration.CorrespondenceCheckerBasedOnDistance(0.05)], | |
# o3d.pipelines.registration.RANSACLoss()) | |
# 应用变换到源mesh | |
# mesh_source_aligned = mesh_source.transform(reg_result.transformation) | |
a = 0 |