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.rhkb.cn/news/192862.html

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

相关文章

Avatar虚拟形象解决方案,趣味化的视频拍摄与直播新体验

企业们正在寻找新的方式来吸引和保持观众的注意力,一种新兴的解决方案就是使用Avatar虚拟形象技术&#xff0c;这种技术可以让用户在视频拍摄或直播场景中&#xff0c;以自定义的数字人形象出现&#xff0c;同时保持所有的表情和脸部驱动。美摄科技正是这个领域的领军者&#x…

Elastic stack8.10.4搭建、启用安全认证,启用https,TLS,SSL 安全配置详解

ELK大家应该很了解了&#xff0c;废话不多说开始部署 kafka在其中作为消息队列解耦和让logstash高可用 kafka和zk 的安装可以参考这篇文章 深入理解Kafka3.6.0的核心概念&#xff0c;搭建与使用-CSDN博客 第一步、官网下载安装包 需要 elasticsearch-8.10.4 logstash-8.…

【Pytorch和深度学习】栏目导读

一、栏目说明 本栏目《pytorch实践》是为初学者入门深度学习准备的。本文是该栏目的导读部分&#xff0c;因为计划本栏目在明年完成&#xff0c;因此&#xff0c;导读部分&#xff0c;即本文也在持续更新中。 本栏目设计目标是将深度学习全面用pytorch实践一遍&#xff0c;由浅…

【考研数据结构代码题6】构建二叉树及四大遍历(先中后层)

题目&#xff1a;请你编写完整的程序构建一棵二叉树并对其进行先序遍历、中序遍历、后序遍历与层次遍历&#xff0c;分别打印并输出遍历结果 难度&#xff1a;★★★ 二叉树的存储结构 typedef struct Node{char data;//数据域struct Node* left;//左子树struct Node* right;//…

物联网AI MicroPython学习之语法 GPIO输入输出模块

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; GPIO 介绍 模块功能: GPIO通用输入输出。 接口说明 GPIO - 构建GPIO对象 函数原型&#xff1a;Pin(port, dir , pull)参数说明&#xff1a; 参数类型必选参数&#xff1f;说明portintY对应开发板的引脚号…

[LeetCode周赛复盘] 第 371 场周赛20231112

[LeetCode周赛复盘] 第 371 场周赛20231112 一、本周周赛总结100120. 找出强数对的最大异或值 I1. 题目描述2. 思路分析3. 代码实现 100128. 高访问员工1. 题目描述2. 思路分析3. 代码实现 100117. 最大化数组末位元素的最少操作次数1. 题目描述2. 思路分析3. 代码实现 100124…

初始MySQL(四)(查询加强练习,多表查询)

目录 查询加强 where加强 order by加强 group by 分页查询 总结 多表查询(重点) 笛卡尔集及其过滤 自连接 子查询 子查询当作临时表 all/any 多列子查询 #先创建三张表 #第一张表 CREATE TABLE dept(deptno MEDIUMINT NOT NULL DEFAULT 0,dname VARCHAR(20) NOT …

Python数据结构:字典(dict)详解

1.字典概念 字典在其他语言中可能会被称为“关联存储”或“关联数组”。   在Python中&#xff0c;字典&#xff08;Dictionary&#xff09;是一种可变、无序且键值对&#xff08;key-value pairs&#xff09;唯一的数据结构。   字典也是一种标准映射类型&#xff0c;mapp…

【嵌入式设计】Main Memory:SPM 便签存储器 | 缓存锁定 | 读取 DRAM 内存 | DREM 猝发(Brust)

目录 0x00 便签存储器&#xff08;Scratchpad memory&#xff09; 0x01 缓存锁定&#xff08;Cache lockdown&#xff09; 0x02 读取 DRAM 内存 0x03 DREM Banking 0x04 DRAM 猝发&#xff08;DRAM Burst&#xff09; 0x00 便签存储器&#xff08;Scratchpad memory&#…

OpenCV颜色识别及应用

OpenCV是一个开源计算机视觉库&#xff0c;提供了丰富的图像处理和计算机视觉算法&#xff0c;其中包括颜色识别。本文首先介绍了OpenCV库&#xff0c;然后着重描述了颜色识别的基本原理和方法&#xff0c;包括颜色空间的转换、阈值处理、颜色检测等技术。接下来详细探讨了Open…

【验证码逆向专栏】百某网数字九宫格验证码逆向分析

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;不提供完整代码&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 本文章未…

修改ubuntu终端目录背景颜色

Ubuntu终端上有部分目录是黄绿色底色&#xff0c;看着很不舒服。如下图所示&#xff1a; 这是由于修改用户权限导致的问题。 通过下面指令可以看到 echo $LS_COLORS | grep "ow" ​ 可以看到ow的默认参数是34:42ow:OTHER_WRITABLE&#xff0c;即其他用户可写权限 …

分类预测 | Matlab实现PSO-GRU粒子群算法优化门控循环单元的数据多输入分类预测

分类预测 | Matlab实现PSO-GRU粒子群算法优化门控循环单元的数据多输入分类预测 目录 分类预测 | Matlab实现PSO-GRU粒子群算法优化门控循环单元的数据多输入分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现PSO-GRU粒子群算法优化门控循环单元的数据…

『亚马逊云科技产品测评』活动征文|阿里云服务器亚马逊服务器综合评测

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 文章目录 引言一、亚马逊&阿里云发展历史介绍1.1 亚马逊发展历史1.2…

腾讯云CVM服务器5年可选2核4G和4核8G配置

腾讯云服务器网整理五年云服务器优惠活动 txyfwq.com/go/txy 配置可选2核4G和4核8G&#xff0c;公网带宽可选1M、3M或5M&#xff0c;系统盘为50G高性能云硬盘&#xff0c;标准型S5实例CPU采用主频2.5GHz的Intel Xeon Cascade Lake或者Intel Xeon Cooper Lake处理器&#xff0c;…

基于MS16F3211芯片的触摸控制灯的状态变化和亮度控制(11.15)

1.任务所需实现基本功能 关机状态时白灯亮蓝灯灭&#xff0c;此时长按按键无反应&#xff0c;白灯亮度降低的状态&#xff0c;蓝灯保持灭的状态。点按按键一次&#xff0c;白灯熄灭&#xff0c;蓝灯亮此时W引脚控制的灯亮。继续点按按键。蓝灯亮&#xff0c;此时W引脚控制的灯…

iOS 设置图标和upload包时显示错误

右键-show in finder-AppIcon.appiconset-然后替换图片 然后遇到个问题 就是图片不能有alpha [Xcode]应用图标&#xff1a;ERROR ITMS-90717: “Invalid App Store Icon. The App Store Icon in the asset catalog in x… 具体操作&#xff1a;只需确保【AppIcon】图片集中不…

【评论送书】十本架构师成长和软件架构技术相关的好书(可以任选)

正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 参与规则 本次送书1~5本参与方式&#xff1a;关注博主、点赞、收藏、评论&#xff08;从评论区…

基础课4——客服中心管理者面临的挑战

客服管理者在当今的数字化时代也面临着许多挑战。以下是一些主要的挑战&#xff1a; 同行业竞争加剧&#xff1a;客服行业面临着来自同行业的竞争压力。为了获得竞争优势&#xff0c;企业需要不断提高自身的产品和服务质量&#xff0c;同时还需要不断降低成本、提高效率。然而…