【C++】sophus : rotation_matrix.hpp 处理旋转矩阵的辅助函数 (二)

这段代码属于Sophus 命名空间,提供了一些处理旋转矩阵的辅助函数,具体功能如下:

  1. isOrthogonal函数

  • 用途:检查一个方阵是否为正交矩阵。

  • 实现方法:通过计算矩阵与其转置矩阵相乘后与单位矩阵的差值的范数是否小于一个很小的常数来判断。

isScaledOrthogonalAndPositive函数

  • 用途:检查一个方阵是否为“缩放正交”矩阵且行列式为正。

  • 实现方法:首先计算矩阵的行列式,如果行列式小于等于零则直接返回 false。否则,计算比例平方并检查矩阵与其转置矩阵相乘后与单位矩阵乘比例的差值的无穷范数是否小于一个很小的常数。

  • 6ab52fbfefd2818cab241415de48f6ce.png

makeRotationMatrix函数

  • 用途:接收一个方阵(2x2或更大),返回最接近的正交矩阵且行列式为正。

  • 实现方法:使用 JacobiSVD 分解计算 U 和 V 矩阵,并调整单位矩阵的最后一个元素以确保返回的矩阵的行列式为 1。

这三个函数主要用于验证和生成旋转矩阵,确保它们符合特定的数学性质,如正交性和行列式为正。

/// 旋转矩阵辅助函数#pragma once
#include <Eigen/Core>#include <Eigen/SVD>
#include "types.hpp"
namespace Sophus {
/// 接收任意方阵,如果它是正交的则返回 true。template <class D>SOPHUS_FUNC bool isOrthogonal(Eigen::MatrixBase<D> const& R) {  // 使用 D 类型的标量  using Scalar = typename D::Scalar;  // 定义标量类型  static int const N = D::RowsAtCompileTime;  // 在编译时确定矩阵的行数  static int const M = D::ColsAtCompileTime;  // 在编译时确定矩阵的列数
  static_assert(N == M, "必须是方阵");  // 确保矩阵是方阵  static_assert(N >= 2, "编译时维数必须 >= 2");  // 确保矩阵的维数 >= 2
  return (R * R.transpose() - Matrix<Scalar, N, N>::Identity()).norm() <  // 判断矩阵是否正交         Constants<Scalar>::epsilon();  // 判断标准是与单位矩阵的差值的范数小于一个很小的常数}
/// 接收任意方阵,如果它是"缩放正交"且行列式为正则返回 true。template <class D>SOPHUS_FUNC bool isScaledOrthogonalAndPositive(Eigen::MatrixBase<D> const& sR) {  // 使用 D 类型的标量  using Scalar = typename D::Scalar;  // 定义标量类型  static int const N = D::RowsAtCompileTime;  // 在编译时确定矩阵的行数  static int const M = D::ColsAtCompileTime;  // 在编译时确定矩阵的列数  using std::pow;  // 使用 std 库的 pow 函数  using std::sqrt;  // 使用 std 库的 sqrt 函数
  Scalar det = sR.determinant();  // 计算矩阵的行列式
  if (det <= Scalar(0)) {  // 如果行列式 <= 0 则返回 false    return false;  }
  Scalar scale_sqr = pow(det, Scalar(2. / N));  // 计算比例平方
  static_assert(N == M, "必须是方阵");  // 确保矩阵是方阵  static_assert(N >= 2, "编译时维数必须 >= 2");  // 确保矩阵的维数 >= 2
  return (sR * sR.transpose() - scale_sqr * Matrix<Scalar, N, N>::Identity())  // 判断矩阵是否"缩放正交"             .template lpNorm<Eigen::Infinity>() <  // 判断标准是与单位矩阵乘比例的差值的无穷范数小于一个很小的常数         sqrt(Constants<Scalar>::epsilon());}
/// 接收任意方阵(2x2或更大),返回最接近的正交矩阵且行列式为正。template <class D>SOPHUS_FUNC std::enable_if_t<    std::is_floating_point<typename D::Scalar>::value,    Matrix<typename D::Scalar, D::RowsAtCompileTime, D::RowsAtCompileTime>>makeRotationMatrix(Eigen::MatrixBase<D> const& R) {  // 使用 D 类型的标量  using Scalar = typename D::Scalar;  // 定义标量类型  static int const N = D::RowsAtCompileTime;  // 在编译时确定矩阵的行数  static int const M = D::ColsAtCompileTime;  // 在编译时确定矩阵的列数
  static_assert(N == M, "必须是方阵");  // 确保矩阵是方阵  static_assert(N >= 2, "编译时维数必须 >= 2");  // 确保矩阵的维数 >= 2
  Eigen::JacobiSVD<Matrix<Scalar, N, N>> svd(  // 使用JacobiSVD分解计算      R, Eigen::ComputeFullU | Eigen::ComputeFullV);  // 计算完整的 U 和 V 矩阵
  // 确定正交矩阵 U*V' 的行列式  Scalar d = (svd.matrixU() * svd.matrixV().transpose()).determinant();  // 从单位矩阵 D 开始,将最后一个元素设置为 d (+1 或 -1),以使 det(U*D*V') = 1。  Matrix<Scalar, N, N> Diag = Matrix<Scalar, N, N>::Identity();  Diag(N - 1, N - 1) = d;  return svd.matrixU() * Diag * svd.matrixV().transpose();  // 返回最接近的正交矩阵}
}  // namespace Sophus
import numpy as np import open3d as o3d from scipy.sparse import lil_matrix from scipy.sparse.linalg import lsqr class SlidingWindowICPOptimizer: def __init__(self, window_size=5): """ 初始化滑动窗口ICP优化器 参数: window_size: 滑动窗口大小(关键帧数量) """ self.window_size = window_size self.poses = [] # 存储窗口内的位姿(4x4变换矩阵) self.source_point_clouds = [] # 存储窗口内的源点云数据 self.target_point_clouds = [] # 存储窗口内的目标点云数据 self.constraints = [] # 存储帧间约束(i, j, T_ij) def add_point_cloud(self, source_cloud,target_cloud, initial_pose=np.eye(4)): """ 添加新点云到滑动窗口并执行ICP配准 参数: pcd: 当前帧的点云数据 initial_pose: 当前帧的初始位姿(4x4变换矩阵) """ if len(self.poses) >= self.window_size: # 移除最旧点云和位姿(滑动窗口机制) self.poses.pop(0) self.source_point_clouds.pop(0) self.target_point_clouds.pop(0) # 更新约束列表以移除与最旧位姿相关的约束 self.constraints = [(i - 1, j - 1, T_ij) for (i, j, T_ij) in self.constraints if i != 0 and j != 0] # 执行ICP配准 if len(self.poses) > 0: last_source_pcd = self.source_point_clouds[-1] last_target_pcd = self.target_point_clouds[-1] last_pose = self.poses[-1] transformation = self.icp_registration(last_source_pcd, last_target_pcd, last_pose) pose = transformation else: pose = initial_pose # 添加新点云和位姿 self.source_point_clouds.append(source_cloud) self.target_point_clouds.append(target_cloud) self.poses.append(pose) def icp_registration(self, source, target, initial_transformation): """ 执行ICP配准 参数: source: 源点云 target: 目标点云 initial_transformation: 初始变换矩阵 返回: transformation: 最终变换矩阵 """ reg_result = o3d.pipelines.registration.registration_icp( source, target, 0.02, initial_transformation, o3d.pipelines.registration.TransformationEstimationPointToPoint() ) return reg_result.transformation def optimize(self): """ 执行滑动窗口内的位姿优化 返回: optimized_poses: 优化后的位姿列表 """ if len(self.poses) < 2: return self.poses # 构建优化问题:最小化残差函数 H, b = self._build_hessian() delta = lsqr(H, b)[0] # 稀疏矩阵求解 # 应用位姿更新 optimized_poses = [] for i in range(len(self.poses)): update_vec = delta[i * 6:(i + 1) * 6] update_mat = self._vec_to_transform(update_vec) optimized_pose = update_mat @ self.poses[i] optimized_poses.append(optimized_pose) return optimized_poses def _build_hessian(self): """ 构建Hessian矩阵和右侧向量b 返回: H: Hessian矩阵 b: 右侧向量 """ num_poses = len(self.poses) num_constraints = len(self.constraints) # 雅可比矩阵维度:6*约束数 x 6*位姿数 H = lil_matrix((6 * num_constraints, 6 * num_poses)) b = np.zeros(6 * num_constraints) for idx, (i, j, T_obs) in enumerate(self.constraints): # 计算残差:e = log(T_obs^{-1} * T_i^{-1} * T_j) T_i = self.poses[i] T_j = self.poses[j] T_pred = np.linalg.inv(T_i) @ T_j e = self._log_map(np.linalg.inv(T_obs) @ T_pred) # 计算雅可比矩阵(李代数求导) J_i = -self._adjoint(T_pred) J_j = self._adjoint(T_pred) # 填充Hessian矩阵 H[idx * 6:(idx + 1) * 6, i * 6:(i + 1) * 6] = J_i H[idx * 6:(idx + 1) * 6, j * 6:(j + 1) * 6] = J_j b[idx * 6:(idx + 1) * 6] = -e return H, b def _log_map(self, T): """ 将SE(3)变换转换为李代数向量 参数: T: 4x4变换矩阵 返回: vec: 6维李代数向量 """ R = T[:3, :3] t = T[:3, 3] # 计算旋转部分的李代数 theta = np.arccos((np.trace(R) - 1) / 2) if theta < 1e-6: omega = np.zeros(3) else: omega = (theta / (2 * np.sin(theta))) * np.array([R[2, 1] - R[1, 2], R[0, 2] - R[2, 0], R[1, 0] - R[0, 1]]) # 构造6维向量 vec = np.hstack([t, omega]) return vec def _adjoint(self, T): """ 计算伴随矩阵 参数: T: 4x4变换矩阵 返回: adj: 6x6伴随矩阵 """ R = T[:3, :3] t = T[:3, 3] adj = np.zeros((6, 6)) adj[:3, :3] = R adj[3:, 3:] = R adj[:3, 3:] = np.cross(t, R) return adj def _vec_to_transform(self, vec): """ 6维向量转换为4x4变换矩阵 参数: vec: 6维李代数向量 返回: T: 4x4变换矩阵 """ t = vec[:3] omega = vec[3:] # 计算旋转矩阵 theta = np.linalg.norm(omega) if theta < 1e-6: R = np.eye(3) else: k = omega / theta K = np.array([[0, -k[2], k[1]], [k[2], 0, -k[0]], [-k[1], k[0], 0]]) R = np.eye(3) + np.sin(theta) * K + (1 - np.cos(theta)) * K @ K # 构造变换矩阵 T = np.eye(4) T[:3, :3] = R T[:3, 3] = t return T # 初始化优化器 optimizer = SlidingWindowICPOptimizer(window_size=2) # 加载点云数据 pcd1s = o3d.io.read_point_cloud("A//1s.pcd") pcd1t = o3d.io.read_point_cloud("A//1t.pcd") pcd2s = o3d.io.read_point_cloud("A//2s.pcd") pcd2t = o3d.io.read_point_cloud("A//2t.pcd") pcd3s = o3d.io.read_point_cloud("A//3s.pcd") pcd3t = o3d.io.read_point_cloud("A//3s.pcd") pcd4s = o3d.io.read_point_cloud("A//4s.pcd") pcd4t = o3d.io.read_point_cloud("A//4s.pcd") # 添加点云并执行ICP配准 optimizer.add_point_cloud(pcd1s,pcd1t) optimizer.add_point_cloud(pcd2s,pcd2t) optimizer.add_point_cloud(pcd3s,pcd3t) optimizer.add_point_cloud(pcd4s,pcd4t) # 执行优化 optimized_poses = optimizer.optimize() # 输出优化结果 for i, pose in enumerate(optimized_poses): print(f"优化后位姿 {i}:\n{pose}") 换成C++版本Open3D实现
06-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值