Umeyama 算法之源码阅读与测试

Title: Umeyama 算法之源码阅读与测试

文章目录

  • 前言
  • I. Eigen 中 Umeyama 算法源码
    • 1. Eigen/src/Geometry/Umeyama.h 源码
    • 2. 代码测试
  • II. PCL 中 Umeyama 算法源码
  • III. evo 中 Umeyama 算法源码
    • 1. evo/core/geometry.py 源码
    • 2. 代码测试
  • 总结
  • 参考文献


[相关博文介绍]

- 矩阵乘法可交换与可同时对角化的关系 —— Umeyama 算法推导的数学准备 (I)

- 旋转矩阵约束下的朗格朗日乘子 —— Umeyama 算法推导的数学准备 (II)

- 矩阵乘操作、三角化、开方特征值 —— Umeyama 算法推导的数学准备 (III)

- 奇异值分解之常用结论

- 旋转参数的最小方差估计 —— Umeyama 算法详细推导 (I)

- 相似变换参数 (旋转、平移、缩放) 的最小方差估计 —— Umeyama 算法详细推导 (II)

- Umeyama 算法之源码阅读与测试


前言

SLAM 轨迹的对齐和评估时, 多用 Umeyama 算法实现.

该算法从给定的两个欧几里得空间的关联点集中找出最小误差平方意义下的相似变换参数 (Similarity Transformation, 旋转+平移+缩放)[1].

在上一篇博文中, 我们已经详细推导了该算法的数学原理.

这里我们看一下实际应用中 Umeyama 算法的源码实现, 分别是

- Eigen[2] 中 Umeyama 算法源码

- PCL[3] 中 Umeyama 算法源码

- evo[4] 中 Umeyama 算法源码

(看现成的算法源码还是比推导公式稍微轻松一点.)


I. Eigen 中 Umeyama 算法源码

1. Eigen/src/Geometry/Umeyama.h 源码

/**
* \geometry_module \ingroup Geometry_Module
*
* \brief Returns the transformation between two point sets.
*
* The algorithm is based on:
* "Least-squares estimation of transformation parameters between two point patterns",
* Shinji Umeyama, PAMI 1991, DOI: 10.1109/34.88573
*
* It estimates parameters \f$ c, \mathbf{R}, \f$ and \f$ \mathbf{t} \f$ such that
* \f{align*}
*   \frac{1}{n} \sum_{i=1}^n \vert\vert y_i - (c\mathbf{R}x_i + \mathbf{t}) \vert\vert_2^2
* \f}
* is minimized.
*
* The algorithm is based on the analysis of the covariance matrix
* \f$ \Sigma_{\mathbf{x}\mathbf{y}} \in \mathbb{R}^{d \times d} \f$
* of the input point sets \f$ \mathbf{x} \f$ and \f$ \mathbf{y} \f$ where 
* \f$d\f$ is corresponding to the dimension (which is typically small).
* The analysis is involving the SVD having a complexity of \f$O(d^3)\f$
* though the actual computational effort lies in the covariance
* matrix computation which has an asymptotic lower bound of \f$O(dm)\f$ when 
* the input point sets have dimension \f$d \times m\f$.
*
* Currently the method is working only for floating point matrices.
*
* \todo Should the return type of umeyama() become a Transform?
*
* \param src Source points \f$ \mathbf{x} = \left( x_1, \hdots, x_n \right) \f$.
* \param dst Destination points \f$ \mathbf{y} = \left( y_1, \hdots, y_n \right) \f$.
* \param with_scaling Sets \f$ c=1 \f$ when <code>false</code> is passed.
* \return The homogeneous transformation 
* \f{align*}
*   T = \begin{bmatrix} c\mathbf{R} & \mathbf{t} \\ \mathbf{0} & 1 \end{bmatrix}
* \f}
* minimizing the residual above. This transformation is always returned as an 
* Eigen::Matrix.
*/
template <typename Derived, typename OtherDerived>
typename internal::umeyama_transform_matrix_type<Derived, OtherDerived>::type
umeyama(const MatrixBase<Derived>& src, const MatrixBase<OtherDerived>& dst, bool with_scaling = true)
{typedef typename internal::umeyama_transform_matrix_type<Derived, OtherDerived>::type TransformationMatrixType;typedef typename internal::traits<TransformationMatrixType>::Scalar Scalar;typedef typename NumTraits<Scalar>::Real RealScalar;EIGEN_STATIC_ASSERT(!NumTraits<Scalar>::IsComplex, NUMERIC_TYPE_MUST_BE_REAL)EIGEN_STATIC_ASSERT((internal::is_same<Scalar, typename internal::traits<OtherDerived>::Scalar>::value),YOU_MIXED_DIFFERENT_NUMERIC_TYPES__YOU_NEED_TO_USE_THE_CAST_METHOD_OF_MATRIXBASE_TO_CAST_NUMERIC_TYPES_EXPLICITLY)enum { Dimension = EIGEN_SIZE_MIN_PREFER_DYNAMIC(Derived::RowsAtCompileTime, OtherDerived::RowsAtCompileTime) };typedef Matrix<Scalar, Dimension, 1> VectorType;typedef Matrix<Scalar, Dimension, Dimension> MatrixType;typedef typename internal::plain_matrix_type_row_major<Derived>::type RowMajorMatrixType;const Index m = src.rows(); // dimensionconst Index n = src.cols(); // number of measurements// required for demeaning ...const RealScalar one_over_n = RealScalar(1) / static_cast<RealScalar>(n);// computation of meanconst VectorType src_mean = src.rowwise().sum() * one_over_n;const VectorType dst_mean = dst.rowwise().sum() * one_over_n;// demeaning of src and dst pointsconst RowMajorMatrixType src_demean = src.colwise() - src_mean;const RowMajorMatrixType dst_demean = dst.colwise() - dst_mean;// Eq. (36)-(37)const Scalar src_var = src_demean.rowwise().squaredNorm().sum() * one_over_n;// Eq. (38)const MatrixType sigma = one_over_n * dst_demean * src_demean.transpose();JacobiSVD<MatrixType> svd(sigma, ComputeFullU | ComputeFullV);// Initialize the resulting transformation with an identity matrix...TransformationMatrixType Rt = TransformationMatrixType::Identity(m+1,m+1);// Eq. (39)VectorType S = VectorType::Ones(m);if  ( svd.matrixU().determinant() * svd.matrixV().determinant() < 0 )S(m-1) = -1;// Eq. (40) and (43)Rt.block(0,0,m,m).noalias() = svd.matrixU() * S.asDiagonal() * svd.matrixV().transpose();if (with_scaling){// Eq. (42)const Scalar c = Scalar(1)/src_var * svd.singularValues().dot(S);// Eq. (41)Rt.col(m).head(m) = dst_mean;Rt.col(m).head(m).noalias() -= c*Rt.topLeftCorner(m,m)*src_mean;Rt.block(0,0,m,m) *= c;}else{Rt.col(m).head(m) = dst_mean;Rt.col(m).head(m).noalias() -= Rt.topLeftCorner(m,m)*src_mean;}return Rt;
}

代码实现是完全按照 Umeyama 论文中的 [定理] 展开的, 理解了这些公式的来龙去脉, 就能很好的理解上面的代码. 我们没有多余的话要说的了.


2. 代码测试

我们根据 Eigen 中提供的单元测试代码 test/umeyama.cpp, 简单改写一下进行了测试.

// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2009 Hauke Heibel <hauke.heibel@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.#include <Eigen/Core>
#include <Eigen/Geometry>#include <Eigen/LU> // required for MatrixBase::determinant
#include <Eigen/SVD> // required for SVD
#include <iostream>
#include <unistd.h>using namespace Eigen;//  Constructs a random matrix from the unitary group U(size).
template <typename T>
Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> randMatrixUnitary(int size)
{typedef T Scalar;typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> MatrixType;MatrixType Q;int max_tries = 40;bool is_unitary = false;while (!is_unitary && max_tries > 0){// initialize random matrixQ = MatrixType::Random(size, size);// orthogonalize columns using the Gram-Schmidt algorithmfor (int col = 0; col < size; ++col){typename MatrixType::ColXpr colVec = Q.col(col);for (int prevCol = 0; prevCol < col; ++prevCol){typename MatrixType::ColXpr prevColVec = Q.col(prevCol);colVec -= colVec.dot(prevColVec)*prevColVec;}Q.col(col) = colVec.normalized();}// this additional orthogonalization is not necessary in theory but should enhance// the numerical orthogonality of the matrixfor (int row = 0; row < size; ++row){typename MatrixType::RowXpr rowVec = Q.row(row);for (int prevRow = 0; prevRow < row; ++prevRow){typename MatrixType::RowXpr prevRowVec = Q.row(prevRow);rowVec -= rowVec.dot(prevRowVec)*prevRowVec;}Q.row(row) = rowVec.normalized();}// final checkis_unitary = Q.isUnitary();--max_tries;}if (max_tries == 0)eigen_assert(false && "randMatrixUnitary: Could not construct unitary matrix!");return Q;
}//  Constructs a random matrix from the special unitary group SU(size).
template <typename T>
Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> randMatrixSpecialUnitary(int size)
{typedef T Scalar;typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> MatrixType;// initialize unitary matrixMatrixType Q = randMatrixUnitary<Scalar>(size);// tweak the first column to make the determinant be 1Q.col(0) *= numext::conj(Q.determinant());return Q;
}template <typename MatrixType>
void run_test(int dim, int num_elements)
{using std::abs;typedef typename internal::traits<MatrixType>::Scalar Scalar;typedef Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> MatrixX;typedef Matrix<Scalar, Eigen::Dynamic, 1> VectorX;// MUST be positive because in any other case det(cR_t) may become negative for// odd dimensions!srand((unsigned)time(NULL));const Scalar c = abs(internal::random<Scalar>());MatrixX R = randMatrixSpecialUnitary<Scalar>(dim);VectorX t = Scalar(50)*VectorX::Random(dim,1);MatrixX cR_t = MatrixX::Identity(dim+1,dim+1);cR_t.block(0,0,dim,dim) = c*R;cR_t.block(0,dim,dim,1) = t;std::cout << "[c]:\n" << c << "\n" << std::endl;std::cout << "[R]:\n"<< R << "\n" << std::endl;std::cout << "[t]:\n"<< t << "\n" << std::endl;std::cout << "[Original cR_t]:\n"<< cR_t << "\n" << std::endl;MatrixX src = MatrixX::Random(dim+1, num_elements);src.row(dim) = Matrix<Scalar, 1, Dynamic>::Constant(num_elements, Scalar(1));MatrixX dst = cR_t*src;MatrixX cR_t_umeyama = umeyama(src.block(0,0,dim,num_elements), dst.block(0,0,dim,num_elements));const Scalar error = ( cR_t_umeyama*src - dst ).norm() / dst.norm();// VERIFY(error < Scalar(40)*std::numeric_limits<Scalar>::epsilon());std::cout << "[cR_t_umeyama]:\n"<< cR_t_umeyama << "\n" << std::endl;std::cout << "[error]:\n"<< error << "\n" << std::endl;}int main(int argc, char** argv)
{std::cout << "\n----- First Experiment -----" << std::endl; run_test<MatrixXf>(3, 20);sleep(1);std::cout << "\n----- Second Experiment -----" << std::endl; run_test<MatrixXf>(4, 10);return 0;
}

这里我们进行了两组测试, 一组是 3 维空间中的 20 个随机数据点, 另一组是 4 维空间中的 10 个随机数据点.

将随机数据点保存为原始点云, 通过随机生成的相似变换 (旋转、平移、缩放) 从原始点云变换到目标点云.

利用 Umeyama 算法, 由原始点云和目标点云数据计算出该相似变换矩阵.

测试结果如下, 比较 [Original cR_t] 和 [cR_t_umeyama] 两个矩阵, 可以看出算法能够成功计算出相似变换参数.

----- First Experiment -----
[c]:
0.737838[R]:0.27562 -0.872498  0.403461
-0.780523 -0.448117 -0.4358620.561086 -0.194778 -0.804514[t]:4.90685
-11.29446.41211[Original cR_t]:0.203363 -0.643762  0.297689   4.90685
-0.575899 -0.330638 -0.321596  -11.29440.413991 -0.143715 -0.593601   6.412110         0         0         1[cR_t_umeyama]:0.203363 -0.643763  0.297689   4.90685
-0.575899 -0.330638 -0.321596  -11.29440.413991 -0.143715 -0.593602   6.412110         0         0         1[error]:
7.80264e-08----- Second Experiment -----
[c]:
0.0245536[R]:
-0.449366 -0.347432 -0.438747  0.696321
-0.449418  0.583524 -0.572782 -0.359784
0.0110775 -0.718994  -0.37278 -0.5864820.771991  0.147782 -0.583487  0.204285[t]:10.0548.1491
-14.796228.6888[Original cR_t]:-0.0110336  -0.0085307  -0.0107728   0.0170972       10.05-0.0110348   0.0143276  -0.0140639 -0.00883398     48.1491
0.000271992  -0.0176539 -0.00915309  -0.0144002    -14.79620.0189552  0.00362858  -0.0143267  0.00501594     28.68880           0           0           0           1[cR_t_umeyama]:-0.0110338 -0.00853069  -0.0107728   0.0170973       10.05-0.0110348   0.0143278   -0.014064 -0.00883401     48.1491
0.000271604   -0.017654 -0.00915301  -0.0144004    -14.79620.0189553  0.00362824   -0.014327  0.00501585     28.68880           0           0           0           1[error]:
7.49672e-08

II. PCL 中 Umeyama 算法源码

registration/include/pcl/registration/impl/transformation_estimation_svd.hpp 源码如下:

template <typename PointSource, typename PointTarget, typename Scalar>
inline void
TransformationEstimationSVD<PointSource, PointTarget, Scalar>::estimateRigidTransformation(ConstCloudIterator<PointSource>& source_it,ConstCloudIterator<PointTarget>& target_it,Matrix4& transformation_matrix) const
{// Convert to Eigen formatconst int npts = static_cast<int>(source_it.size());if (use_umeyama_) {Eigen::Matrix<Scalar, 3, Eigen::Dynamic> cloud_src(3, npts);Eigen::Matrix<Scalar, 3, Eigen::Dynamic> cloud_tgt(3, npts);for (int i = 0; i < npts; ++i) {cloud_src(0, i) = source_it->x;cloud_src(1, i) = source_it->y;cloud_src(2, i) = source_it->z;++source_it;cloud_tgt(0, i) = target_it->x;cloud_tgt(1, i) = target_it->y;cloud_tgt(2, i) = target_it->z;++target_it;}// Call Umeyama directly from Eigen (PCL patched version until Eigen is released)transformation_matrix = pcl::umeyama(cloud_src, cloud_tgt, false);}else {source_it.reset();target_it.reset();// <cloud_src,cloud_src> is the source datasettransformation_matrix.setIdentity();Eigen::Matrix<Scalar, 4, 1> centroid_src, centroid_tgt;// Estimate the centroids of source, targetcompute3DCentroid(source_it, centroid_src);compute3DCentroid(target_it, centroid_tgt);source_it.reset();target_it.reset();// Subtract the centroids from source, targetEigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> cloud_src_demean,cloud_tgt_demean;demeanPointCloud(source_it, centroid_src, cloud_src_demean);demeanPointCloud(target_it, centroid_tgt, cloud_tgt_demean);getTransformationFromCorrelation(cloud_src_demean,centroid_src,cloud_tgt_demean,centroid_tgt,transformation_matrix);}
}template <typename PointSource, typename PointTarget, typename Scalar>
void
TransformationEstimationSVD<PointSource, PointTarget, Scalar>::getTransformationFromCorrelation(const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& cloud_src_demean,const Eigen::Matrix<Scalar, 4, 1>& centroid_src,const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>& cloud_tgt_demean,const Eigen::Matrix<Scalar, 4, 1>& centroid_tgt,Matrix4& transformation_matrix) const
{transformation_matrix.setIdentity();// Assemble the correlation matrix H = source * target'Eigen::Matrix<Scalar, 3, 3> H =(cloud_src_demean * cloud_tgt_demean.transpose()).topLeftCorner(3, 3);// Compute the Singular Value DecompositionEigen::JacobiSVD<Eigen::Matrix<Scalar, 3, 3>> svd(H, Eigen::ComputeFullU | Eigen::ComputeFullV);Eigen::Matrix<Scalar, 3, 3> u = svd.matrixU();Eigen::Matrix<Scalar, 3, 3> v = svd.matrixV();// Compute R = V * U'if (u.determinant() * v.determinant() < 0) {for (int x = 0; x < 3; ++x)v(x, 2) *= -1;}Eigen::Matrix<Scalar, 3, 3> R = v * u.transpose();// Return the correct transformationtransformation_matrix.topLeftCorner(3, 3) = R;const Eigen::Matrix<Scalar, 3, 1> Rc(R * centroid_src.head(3));transformation_matrix.block(0, 3, 3, 1) = centroid_tgt.head(3) - Rc;
}} // namespace registration
} // namespace pcl

PCL 中做的是 3 维欧式空间中刚体变换, 不涉及高维空间, 且只计算了旋转参数和平移参数, 而不涉及缩放.

实现方法分为一种直接调用 Eigen 中的 Umeyama 算法实现, 另一种在 PCL 内自己实现. PCL 自己实现的部分和 Umeyama 算法类似, 确切地说是 Umeyama 算法的上一代算法. 可参考文献 [5], 我们此处不展开.


III. evo 中 Umeyama 算法源码

1. evo/core/geometry.py 源码

def umeyama_alignment(x: np.ndarray, y: np.ndarray,with_scale: bool = False) -> UmeyamaResult:"""Computes the least squares solution parameters of an Sim(m) matrixthat minimizes the distance between a set of registered points.Umeyama, Shinji: Least-squares estimation of transformation parametersbetween two point patterns. IEEE PAMI, 1991:param x: mxn matrix of points, m = dimension, n = nr. of data points:param y: mxn matrix of points, m = dimension, n = nr. of data points:param with_scale: set to True to align also the scale (default: 1.0 scale):return: r, t, c - rotation matrix, translation vector and scale factor"""if x.shape != y.shape:raise GeometryException("data matrices must have the same shape")# m = dimension, n = nr. of data pointsm, n = x.shape# means, eq. 34 and 35mean_x = x.mean(axis=1)mean_y = y.mean(axis=1)# variance, eq. 36# "transpose" for column subtractionsigma_x = 1.0 / n * (np.linalg.norm(x - mean_x[:, np.newaxis])**2)# covariance matrix, eq. 38outer_sum = np.zeros((m, m))for i in range(n):outer_sum += np.outer((y[:, i] - mean_y), (x[:, i] - mean_x))cov_xy = np.multiply(1.0 / n, outer_sum)# SVD (text betw. eq. 38 and 39)u, d, v = np.linalg.svd(cov_xy)if np.count_nonzero(d > np.finfo(d.dtype).eps) < m - 1:raise GeometryException("Degenerate covariance rank, ""Umeyama alignment is not possible")# S matrix, eq. 43s = np.eye(m)if np.linalg.det(u) * np.linalg.det(v) < 0.0:# Ensure a RHS coordinate system (Kabsch algorithm).s[m - 1, m - 1] = -1# rotation, eq. 40r = u.dot(s).dot(v)# scale & translation, eq. 42 and 41c = 1 / sigma_x * np.trace(np.diag(d).dot(s)) if with_scale else 1.0t = mean_y - np.multiply(c, r.dot(mean_x))return r, t, c

很直接地由 Umeyama 论文中的 [定理] 翻译成了 Python 代码进行实现, 不做过多解释了.


2. 代码测试

根据 doc/alignment_demo.py 示例程序, 简单修改并测试.

import copy
import logging
import sysimport evo.core.lie_algebra as lie
from evo.core import trajectory
from evo.tools import plot, file_interface, logimport numpy as np
import matplotlib.pyplot as pltlogger = logging.getLogger("evo")
log.configure_logging(verbose=True)traj_ref = file_interface.read_kitti_poses_file("../test/data/KITTI_00_gt.txt")
traj_est = file_interface.read_kitti_poses_file("../test/data/KITTI_00_ORB.txt")# add artificial Sim(3) transformation
traj_est.transform(lie.se3(lie.so3_exp([-0.7,2.3,1.2]), np.array([-215.7, -114.1, -198.3])))
# traj_est.transform(lie.se3(np.eye(3), np.array([0, 0, 0])))
traj_est.scale(0.7)logger.info("\nUmeyama alignment without scaling")
traj_est_aligned = copy.deepcopy(traj_est)
traj_est_aligned.align(traj_ref)logger.info("\nUmeyama alignment with scaling")
traj_est_aligned_scaled = copy.deepcopy(traj_est)
traj_est_aligned_scaled.align(traj_ref, correct_scale=True)logger.info("\nUmeyama alignment with scaling only")
traj_est_aligned_only_scaled = copy.deepcopy(traj_est)
traj_est_aligned_only_scaled.align(traj_ref, correct_only_scale=True)fig = plt.figure(figsize=(8, 8))
plot_mode = plot.PlotMode.xyzax = plot.prepare_axis(fig, plot_mode, subplot_arg=221)
plot.traj(ax, plot_mode, traj_ref, '--', 'red')
plot.traj(ax, plot_mode, traj_est, '-', 'blue')
fig.axes.append(ax)
plt.title('not aligned')ax = plot.prepare_axis(fig, plot_mode, subplot_arg=222)
plot.traj(ax, plot_mode, traj_ref, '--', 'red')
plot.traj(ax, plot_mode, traj_est_aligned, '-', 'blue')
fig.axes.append(ax)
plt.title('$\mathrm{SE}(3)$ alignment without scaling')ax = plot.prepare_axis(fig, plot_mode, subplot_arg=223)
plot.traj(ax, plot_mode, traj_ref, '--', 'red')
plot.traj(ax, plot_mode, traj_est_aligned_scaled, '-', 'blue')
fig.axes.append(ax)
plt.title('$\mathrm{Sim}(3)$ alignment')ax = plot.prepare_axis(fig, plot_mode, subplot_arg=224)
plot.traj(ax, plot_mode, traj_ref, '--', 'red')
plot.traj(ax, plot_mode, traj_est_aligned_only_scaled, '-', 'blue')
fig.axes.append(ax)
plt.title('only scaled')fig.tight_layout()
plt.show()

结果如下图所示, 左上角是原始的两组数据 (未对齐), 右上角仅计算了平移和旋转 (没有缩放) 后的 “对齐” 结果, 右下角仅计算了缩放 (没有旋转和平移) 后的 “对齐” 结果, 左下角是计算了相似变换 (旋转、平移、缩放) 后的对齐结果. 可以看出相似变换后的对齐效果比较好, 体现 Umeyama 算法的较好的特性.

evo_result

总结

本篇博文中

- 查阅了 Eigen 中 Umeyama 算法源码并简单测试

- 查阅了 PCL 中 Umeyama 算法源码 (基于 Eigen)

- 查阅了 evo 中 Umeyama 算法源码并简单测试

到此, 我们完成了对 Umeyama 算法的数学推导和源码阅读, 可以暂时画个休止符了.

(如有问题, 请指出)


参考文献

[1] S. Umeyama, “Least-squares estimation of transformation parameters between two point patterns,” in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 13, no. 4, pp. 376-380, April 1991

[2] Eigen, https://eigen.tuxfamily.org/index.php?title=Main_Page

[3] The Point Cloud Library (PCL), https://pointclouds.org/

[4] Grupp Michael, “evo: Python package for the evaluation of odometry and SLAM”, https://github.com/MichaelGrupp/evo

[5] Kaxlamangla S. Arun and Thomas S. Huang and Steven D. Blostein, “Least-Squares Fitting of Two 3-D Point Sets”, IEEE Transactions on Pattern Analysis and Machine Intelligence, 1987, PAMI-9, pages 698-700

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/175610.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

后端接口性能优化分析-程序结构优化

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码&#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44d;三连支持&…

1、NPC 三电平SVPWM simulink仿真

1、SVPWM时间计算函数&#xff0c;是从matlab的SVPWM3L_TimingCalculation.p文件中反汇编出来的函数&#xff1a; function [TgABC_On ,TgABC_Off ,Sn ]SVPWM3L_TimingCalculation_frompfile (Vref ,DeltaVdc ,Fsw ) %#codegen %coder .allowpcode (plain ); TgABC_On [0 ,0 ,…

玩转ChatGPT:ARIMA模型定制GPT-1.0

一、写在前面 好久不更新咯&#xff01; OpenAI又推出了GPT的一系列重大更新&#xff0c;其中GPTs深得我心啊。 GPTs允许用户创建自定义的ChatGPT版本&#xff0c;以满足自己各种特定需求。其核心理念在于&#xff0c;用户可以为不同的场景和任务创建定制化的ChatGPT。这意味…

WorldView 1 2 3 4卫星影像

WorldView WorldView卫星是Digitalglobe公司的商业成像卫星系统。它由两颗(WorldView-I和WorldView-II)卫星组成。 WorldView-1 WorldView-1卫星为美国DigitalGlobe公司的高分辨率商用卫星&#xff0c;于2007年9月18日成功发射&#xff0c;可提供0.5m分辨率卫星影像。灵活的…

No207.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

Java 反射设置List属性

使用 Java 反射可以动态地设置对象的属性值&#xff0c;包括 List 类型的属性。以下是一个示例代码&#xff0c;演示如何通过反射设置 List 类型的属性&#xff1a; 假设有一个类 Person&#xff0c;包含一个 List 类型的属性 names&#xff1a; java public class Person { …

【python】爬取酷狗音乐Top500排行榜【附源码】

一、导入必要的模块&#xff1a; 这篇博客将介绍如何使用Python编写一个爬虫程序&#xff0c;从斗鱼直播网站上获取图片信息并保存到本地。我们将使用requests模块发送HTTP请求和接收响应&#xff0c;以及os模块处理文件和目录操作。 如果出现模块报错 进入控制台输入&#xff…

绘图软件 OmniGraffle mac中文版特点说明

OmniGraffle mac是一款图形绘制和图表设计软件&#xff0c;主要面向 macOS 和 iOS 平台。它适用于用户创建流程图、组织结构图、原型设计、网站线框图、地图等各种类型的图形。该软件的界面直观&#xff0c;用户友好&#xff0c;让用户能够轻松地创建和编辑复杂的图形。 OmniGr…

【概率论】Python:实现求联合分布函数 | 求边缘分布函数 | Joint distribution | Marginal distribution

猛戳订阅! 👉 《一起玩蛇》🐍 💭 写在前面:本章我们将通过 Python 手动实现联合分布函数和边缘分布函数,部署的测试代码放到文后了,运行所需环境 python version >= 3.6,numpy >= 1.15,nltk >= 3.4,tqdm >= 4.24.0,scikit-learn >= 0.22。 0x00 …

uniapp蓝牙搜索设备并列表展示

1.需求&#xff1a;3.0的桩可以值扫码通过蓝牙名字直接绑定&#xff0c;2.0的桩二维码无蓝牙名称则需通过蓝牙列表来绑定 2.碰到问题 1.0 蓝牙列表需要去重&#xff08;蓝牙列表通过deviceId去重再放进展示列表&#xff09; 2.0页面会卡顿&#xff08;调用my.stopBluetoothDevi…

时间序列预测实战(十六)PyTorch实现GRU-FCN模型长期预测并可视化结果

往期回顾&#xff1a;时间序列预测专栏——包含上百种时间序列模型带你从入门到精通时间序列预测 一、本文介绍 本文讲解的实战内容是GRU-FCN(门控循环单元-全卷积网络)&#xff0c;这是一种结合了GRU&#xff08;用于处理时间序列数据&#xff09;和FCN&#xff08;全卷积网络…

Elasticsearch:ES|QL 动手实践

在我之前的文章 “Elasticsearch&#xff1a;ES|QL 查询语言简介”&#xff0c;我对 Elasticsearch 的最新查询语言 ES|QL 做了一个简单的介绍。在今天的文章中&#xff0c;我们详细来使用一些例子来展示 ES|QL 强大的搜索与分析功能。 安装 如果你还没有安装好自己的 Elastic…