位姿图优化(CeresG2OGTSAM)

news2025/2/24 12:06:27

0. 简介

作为SLAM中常用的方法,其原因是因为SLAM观测不只考虑到当前帧的情况,而需要加入之前状态量的观测。就比如一个在二维平面上移动的机器人,机器人可以使用一组传感器,例如车轮里程计或激光测距仪。从这些原始测量值中,我们想要估计机器人的轨迹并构建环境地图。为了降低问题的计算复杂度,位姿图方法将原始测量值抽象出来。具体来说,它创建了一个表示机器人姿态的节点图,以及表示两个节点之间的相对变换(增量位置和方向)的边。边缘是源自原始传感器测量值的虚拟测量值,例如通过集成原始车轮里程计或对齐从机器人获取的激光范围扫描。
在这里插入图片描述
对上图的具体描述如下

1. Ceres-Solver的位姿图优化

1.1 Ceres-Solver的用法

之前作者已经已经在《SLAM本质剖析-Ceres》中详细的介绍了SLAM中对Ceres的使用。使用 Ceres Solver求解非线性优化问题,主要包括以下几部分:

  • 构建代价函数(cost function)或残差(residual)
  • 构建优化问题(ceres::Problem):通过 AddResidualBlock 添加代价函数(cost function)、损失函数(loss function核函数) 和 待优化状态量
  • 配置求解器(ceres::Solver::Options)
  • 运行求解器(ceres::Solve(options, &problem, &summary))

与g2o优化中直接在边上添加information(信息矩阵)不同,Ceres无法直接实现此功能。 由于Ceres只接受最小二乘优化就,即 m i n ( r T r ) min(r^{^{T}}r) min(rTr);若要对残差加权,使用马氏距离即 m i n ( r T Σ r ) min (r^{^{T}}\Sigma r) min(rTΣr),则要对信息矩阵(协方差矩阵) Σ \Sigma Σ做Cholesky分解 L L T = Σ LL^{^{T}}=\Sigma LLT=Σ,则 r T Σ r = r T ( L L T ) r = ( L T r ) T ( L T r ) r^{^{T}}\Sigma r=r^{^{T}}(LL^{^{T}})r=(L^{^{T}}r)^{T}(L^{T}r) rTΣr=rT(LLT)r=(LTr)T(LTr),令 r ′ = L T r r'=L^{T}r r=LTr,最终得到纯种的最小二乘优化: m i n ( r ′ T r ′ ) min(r'^{^{T}}r') min(rTr)

1.2 Ceres优化步骤

这一块的内容在前文中已经详细阐述了,这里就不详细分析了,下面直接贴一下每个部分的代码以及结构的组成。

Step1:构建Cost Function

(1)AutoDiffCostFunction(自动求导)

  • 构造 代价函数结构体或代价函数类(例如:struct CostFunctor),在其结构体内对模板括号() 重载用来定义残差;
  • 在重载的 () 函数形参中,**最后一个为残差,前面几个为待优化状态量,注意:**形参类型必须是模板类型。
struct CostFunctor {
    template<typename T>
    bool operator()(const T *const x, T *residual) const {
        residual[0] = 10.0 - x[0]; // r(x) = 10 - x
        return true;
    }
};
  • 利用ceres::CostFunction *cost_function=new ceres::AutoDiffCostFunction<functype,dimension1,dimension2>(new costFunc),获得Cost Function

模板参数分别是:代价结构体仿函数类型名,残差维度,第一个优化变量维度,第二个…;函数参数(new costFunc)是代价结构体(或类)对象,即此对象重载的 () 函数的函数指针

(2)NumericDiffCostFunction(数值求导)

  • 数值求导法 也是构造 代价函数结构体,也需要重载括号()来定义残差,但在重载括号() 时没有用模板;
struct CostFunctorNum {
    bool operator()(const double *const x, double *residual) const {
        residual[0] = 10.0 - x[0]; // r(x) = 10 - x
        return true;
    }
};
  • 并且在实例化代价函数时也稍微有点区别,多了一个模板参数 ceres::CENTRAL
ceres::CostFunction *cost_function;
cost_function =new ceres::NumericDiffCostFunction<CostFunctorNum, ceres::CENTRAL, 1, 1>(new CostFunctorNum);

(3)自定义CostFunction

  • 构建一个 继承自 ceres::SizedCostFunction<1,1> 的类,同样,对于模板参数的数字,第一个为残差的维度,后面几个为待优化状态量的维度
  • 重载虚函数virtual bool Evaluate(double const* const* parameters, double *residuals, double **jacobians) const,根据 待优化变量,实现 残差和雅克比矩阵的计算(雅克比矩阵计算需要提供公式);
    在这里插入图片描述
    virtual bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const;:核心函数,跟自动求导中用仿函数中 operator() 类似,这里重写了基类中的 evaluate 函数,并且需要在其中定义误差和雅克比矩阵的计算方式:
    double const* const* parameters:参数块,主要是传入的各个节点,第一维度为节点的个数(这里是 6),第二维度则是对应节点的维度(这里两个节点的维度都是 6)
    double* residuals:残差,维度取决于误差的维度,这里是 6
    double** jacobians:雅克比矩阵,第一个维度为节点的个数(也就是要求解的雅克比矩阵的数量),第二个维度为对应雅克比矩阵的元素个数(这里两个雅克比矩阵的维度都是 6x6,因此维度是 36)
    详细的解析可以看一下这篇博客Ceres 学习记录(一):2D 位姿图优化

class PoseGraphCFunc : public ceres::SizedCostFunction<6,6,6>
{
public:
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
    PoseGraphCFunc(const Sophus::SE3 measure,const Matrix6d covariance):_measure(measure),_covariance(covariance){}
    ~PoseGraphCFunc(){}
    virtual bool Evaluate(double const* const* parameters,
                          double *residuals,
                          double **jacobians)const
    {
    	//创建带有参数的SE3
        Sophus::SE3 pose_i=Sophus::SE3::exp(Vector6d(parameters[0]));//Eigen::MtatrixXd可以通过double指针来构造
        Sophus::SE3 pose_j=Sophus::SE3::exp(Vector6d(parameters[1]));
        Eigen::Map<Vector6d> residual(residuals);//Eigen::Map<typename MatrixX>(ptr)函数的作用是通过传入指针将c++数据映射为Eigen上的Matrix数据,有点与&相似
        //通过LLT分解得到信息矩阵
        residual=(_measure.inverse()*pose_i.inverse()*pose_j).log();
        Matrix6d sqrt_info=Eigen::LLT<Matrix6d>(_covariance).matrixLLT().transpose();
        //计算雅克比矩阵
        if(jacobians)
        {
            if(jacobians[0])
            {
                Eigen::Map<Eigen::Matrix<double,6,6,Eigen::RowMajor>> jacobian_i(jacobians[0]);
                Matrix6d Jr=JRInv(Sophus::SE3::exp(residual));
                jacobian_i=(sqrt_info)*(-Jr)*pose_j.inverse().Adj();
            }
            if(jacobians[1])
            {
                Eigen::Map<Eigen::Matrix<double,6,6, Eigen::RowMajor>> jacobian_j(jacobians[1]);
                Matrix6d J = JRInv(Sophus::SE3::exp(residual));
                jacobian_j = sqrt_info * J * pose_j.inverse().Adj();
            }
        }
        residual=sqrt_info*residual;
        return true;

    }
private:
    const Sophus::SE3 _measure;
    const Matrix6d _covariance;

};

  • 如果待优化变量处于流形上则需自定义 一个继承于LocalParameterization的类,重载其中的Plus()函数实现迭代递增,重载computeJacobian()定义流形到切平面的雅克比矩阵

class Parameterization:public ceres::LocalParameterization
{
public:
    Parameterization(){}
    virtual ~Parameterization(){}
    virtual bool Plus(const double* x,
                      const double * delta,
                      double *x_plus_delta)const
    {
        Vector6d lie(x);
        Vector6d lie_delta(delta);
        Sophus::SE3 T=Sophus::SE3::exp(lie);
        Sophus::SE3 T_delta=Sophus::SE3::exp(lie_delta);

        Eigen::Map<Vector6d> x_plus(x_plus_delta);
        x_plus=(T_delta*T).log();
        return true;

    }
    //流形到其切平面的雅克比矩阵
    virtual bool ComputeJacobian(const double *x,
                                 double * jacobian) const
    {
        ceres::MatrixRef(jacobian,6,6)=ceres::Matrix::Identity(6,6);//ceres::MatrixRef()函数也类似于Eigen::Map,通过模板参数传入C++指针将c++数据映射为Ceres::Matrix
        return true;
    }
    //定义流形和切平面维度:在本问题中是李代数到李代数故都为6
    virtual int GlobalSize()const{return 6;}
    virtual int LocalSize()const {return 6;}
};
Step2:AddResidualBlock
  • 声明 ceres::Problem problem;
  • 通过 AddResidualBlock代价函数(cost function)、损失函数(loss function) 和 待优化状态量 添加到 problem
ceres::LossFunction *loss = new ceres::HuberLoss(1.0);
ceres::CostFunction *costfunc = new PoseGraphCFunc(measurement, covariance);//定义costfunction
problem.AddResidualBlock(costfunc, loss, param[vertex_i].se3, param[vertex_j].se3);//为整体代价函数加入快,ceres的优点就是直接在param[vertex_i].se3上优化
problem.SetParameterization(param[vertex_i].se3, local_param);//为待优化的两个位姿变量定义迭代递增方式
problem.SetParameterization(param[vertex_j].se3, local_param);
Step3:配置优化参数并优化Config&solve
  • 定义ceres::Solver::Options options;对options成员进行赋值以配置优化参数;
  • 定义ceres::Solver::Summary summary;用来存储优化输出;
  • 求解:ceres::Solve(options, &problem, &summary);
ceres::Solver::Options options;//配置优化设置
options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY;
options.trust_region_strategy_type=ceres::LEVENBERG_MARQUARDT;
options.max_linear_solver_iterations = 50;
options.minimizer_progress_to_stdout = true;
ceres::Solver::Summary summary;//summary用来存储优化过程
ceres::Solve(options, &problem, &summary);//开始优化

1.3 Ceres代码详解

这里借鉴了这篇文章的信息。并结合自己的理解去进行分析,解决了作者未能解决的问题。

1.3.1.pose_graph_2d_error_term.h

这个文件中主要是实现了ceres计算残差需要的类,也就是之前自己定义的包含模板()运算符的结构体或类。并且在这个类的定义中,官方注释非常详细的解释了残差的计算公式,结合官网的教程很容易明白。

// Computes the error term for two poses that have a relative pose measurement
// between them. Let the hat variables be the measurement.
//
// residual =  information^{1/2} * [  r_a^T * (p_b - p_a) - \hat{p_ab}   ]
//                                 [ Normalize(yaw_b - yaw_a - \hat{yaw_ab}) ]
//
// where r_a is the rotation matrix that rotates a vector represented in frame A
// into the global frame, and Normalize(*) ensures the angles are in the range
// [-pi, pi).
class PoseGraph2dErrorTerm {
 public:
  PoseGraph2dErrorTerm(double x_ab,   // 这些都是实际观测值,也就是位姿图中的边
                       double y_ab,
                       double yaw_ab_radians,
                       const Eigen::Matrix3d& sqrt_information)
      : p_ab_(x_ab, y_ab),
        yaw_ab_radians_(yaw_ab_radians),
        sqrt_information_(sqrt_information) {}

  template <typename T>
   // 括号运算符中传入的是节点的值,然后利用这些值计算估计的观测值,
   // 和实际传入的观测值做差,得到残差
  bool operator()(const T* const x_a,  
                  const T* const y_a,
                  const T* const yaw_a,
                  const T* const x_b,
                  const T* const y_b,
                  const T* const yaw_b,
                  T* residuals_ptr) const {
    const Eigen::Matrix<T, 2, 1> p_a(*x_a, *y_a);
    const Eigen::Matrix<T, 2, 1> p_b(*x_b, *y_b);

    // 为什么要声明为Map呢?经常操作,并且给它赋值的数据是Eigen的数据类型?
    // map就像相当于C++中的数组,主要是为了和原生的C++数组交互
    Eigen::Map<Eigen::Matrix<T, 3, 1>> residuals_map(residuals_ptr);  

    residuals_map.template head<2>() =
        RotationMatrix2D(*yaw_a).transpose() * (p_b - p_a) - p_ab_.cast<T>();
    residuals_map(2) = ceres::examples::NormalizeAngle(
        (*yaw_b - *yaw_a) - static_cast<T>(yaw_ab_radians_));

    // Scale the residuals by the square root information matrix to account for
    // the measurement uncertainty.
    residuals_map = sqrt_information_.template cast<T>() * residuals_map;

    return true;
  }

  // 再次看到static,只是为了提高程序紧凑型,因为下面这个函数只给当前这个类用,类似一种全局函数
  static ceres::CostFunction* Create(double x_ab,
                                     double y_ab,
                                     double yaw_ab_radians,
                                     const Eigen::Matrix3d& sqrt_information) {
    return (new ceres::
                AutoDiffCostFunction<PoseGraph2dErrorTerm, 3, 1, 1, 1, 1, 1, 1>(
                    new PoseGraph2dErrorTerm(
                        x_ab, y_ab, yaw_ab_radians, sqrt_information)));
  }

  EIGEN_MAKE_ALIGNED_OPERATOR_NEW

 private:
  // The position of B relative to A in the A frame.
  const Eigen::Vector2d p_ab_;
  // The orientation of frame B relative to frame A.
  const double yaw_ab_radians_;
  // The inverse square root of the measurement covariance matrix.
  const Eigen::Matrix3d sqrt_information_;
};

注意上面程序中的几个细节:

  1. 还是原来的套路,类的形参传入的是实际观测的数据点,也就是g2o中的边,传入的这些数据值会赋值给类的成员变量,在()运算中计算残差的时候会用到这些成员变量, 也就是观测的数据值。对于()运算符,传入的是顶点的值,就是g2o中的顶点,或者说是要被优化的值。然后在()运算中,先利用这些顶点值得到计算的观测值,然后和实际的观测值相减,就得到了残差。

  2. 这个类中再次出现了static函数,这个函数就是返回new出来的一个自动求导对象指针。这样写在类里的好处是让程序更加紧凑,因为自动求导对象里的构造参数是和我们定义的这个类数据紧密相关的。

  3. ()运算中定义残差的时候,使用到了eigen中的map类。实际上map类是为了兼容eigen的矩阵操作与C++的数组操作。
    (1)看程序中有一句EIGEN_MAKE_ALIGNED_OPERATOR_NEW声明内存对齐,这在自定义的类中使用Eigen的数据类型时是必要的,还有的时候能看到在std::vector中插入Eigen的变量后面要加一个Eigen::Aligend...,也是为了声明Eigen的内存对齐,这样才能使用STL容器。所以这里要清楚,Eigen为了实现矩阵运算,其内存和原生的C++内存分配出现了差别,很多时候都需要声明一下。
    (2)再回到map类,这个类就是为了实现和C++中数组一样的内存分配方式,比如使用指针可以快速访问;但是同时他又可以支持Eigen的矩阵运算,所以在需要对矩阵元素频繁操作或者访问的时候,声明为map类是一个很好的选择。再来看这里为什么要声明为map类?首先频繁操作,要把算出来的残差存到这个矩阵中;其次计算的结果(=右边)都是eigen的数据类型,所以也要和Eigen的数据类型兼容。所以满足使用map的两个情况,因此使用map类很合适。
    (3)详细资料参见:Eigen库中的Map类到底是做什么的
    (4) map.template中template是由于block是模板化成员,如果矩阵类型也是模板参数,则必须使用关键字模板

residuals_map.template head<2>() =
        RotationMatrix2D(*yaw_a).transpose() * (p_b - p_a) - p_ab_.cast<T>();
    residuals_map(2) = ceres::examples::NormalizeAngle(
        (*yaw_b - *yaw_a) - static_cast<T>(yaw_ab_radians_));

1.3.2.pose_graph_2d.h

这个文件就是主文件,主要就是实现添加残差块。对于每一个观测数据,都需要添加残差块。然后其中还有官网说的那个Normalize限制旋转角度的范围的问题。

其次还有一个比较重要的问题,就是程序中最后把最开始的那个位姿设置成了常量,不优化它,也就是那段长注释解释的,具体我没有看的很懂。

// The pose graph optimization problem has three DOFs that are not fully  
// constrained. This is typically referred to as gauge freedom. You can apply  
// a rigid body transformation to all the nodes and the optimization problem  
// will still have the exact same cost. The Levenberg-Marquardt algorithm has  
// internal damping which mitigate this issue, but it is better to properly  
// constrain the gauge freedom. This can be done by setting one of the poses  
// as constant so the optimizer cannot change it.  

位姿图优化问题具有三个未完全约束的自由度。这通常称为规范自由度。您可以对所有节点应用刚体变换,优化问题仍然具有完全相同的成本。 Levenberg-Marquardt 算法具有内部阻尼来缓解这个问题,但最好适当地约束规范自由度。这可以通过将其中一个姿势设置为常量来完成,这样优化器就无法更改它。

// Constructs the nonlinear least squares optimization problem from the pose
// graph constraints.
// Constraint2d 2D的约束,是一个自定义的结构体类,描述了顶点之间的位姿约束,也就是g2o中的边
void BuildOptimizationProblem(const std::vector<Constraint2d>& constraints,
                              std::map<int, Pose2d>* poses,
                              ceres::Problem* problem) {
  CHECK(poses != NULL);
  CHECK(problem != NULL);
  if (constraints.empty()) {
    LOG(INFO) << "No constraints, no problem to optimize.";
    return;
  }
  // LossFunction就是和函数,这里不使用核函数
  ceres::LossFunction* loss_function = NULL;
  // 局部参数化:
  // Lastly, we use a local parameterization to normalize the orientation in the range which is normalized between【-pi,pi)
  ceres::LocalParameterization* angle_local_parameterization =
      AngleLocalParameterization::Create();

  // 使用常量迭代器访问容器,得到容器中的所有元素
  for (std::vector<Constraint2d>::const_iterator constraints_iter =
           constraints.begin();
       constraints_iter != constraints.end();
       ++constraints_iter) {
    const Constraint2d& constraint = *constraints_iter;  // 得到当前访问的约束元素

    std::map<int, Pose2d>::iterator pose_begin_iter =
        poses->find(constraint.id_begin);  // 根据约束的顶点Id号得到指向顶点1的迭代器
    CHECK(pose_begin_iter != poses->end())  // 找到了
        << "Pose with ID: " << constraint.id_begin << " not found.";
    std::map<int, Pose2d>::iterator pose_end_iter = // 根据约束的顶点Id号得到指向顶点2的迭代器
        poses->find(constraint.id_end);
    CHECK(pose_end_iter != poses->end())
        << "Pose with ID: " << constraint.id_end << " not found.";

    const Eigen::Matrix3d sqrt_information =
            // llt = LL^T,cholesky分解,然后matrixL是cholesky因子组成的矩阵
        constraint.information.llt().matrixL();  // 信息矩阵,即协方差矩阵的逆
    // Ceres will take ownership of the pointer.
    // PoseGraph2dErrorTerm是专门定义的一个用于2D位姿图优化的类,相当于这个类中已经定义好了残差块的()运算符
    // 和之前自己写计算残差的结构体或者类是一样的。costfunction是添加参数块的时候必须使用的
    ceres::CostFunction* cost_function = PoseGraph2dErrorTerm::Create(
        constraint.x, constraint.y, constraint.yaw_radians, sqrt_information);
    // 对每一个观测值,都添加残差块
    problem->AddResidualBlock(cost_function,
                              loss_function,
                              &pose_begin_iter->second.x,  // 因为这是一个map的迭代器,map中first就是key值,second就是value值
                              &pose_begin_iter->second.y,  // 因为key存的是pose的id号,这里用的是顶点的pose,所以是value的值
                              &pose_begin_iter->second.yaw_radians,
                              &pose_end_iter->second.x,
                              &pose_end_iter->second.y,
                              &pose_end_iter->second.yaw_radians);

    // 设置参数化,这里设置边的两个顶点的pose的旋转角度为 local_parameterization
    problem->SetParameterization(&pose_begin_iter->second.yaw_radians,
                                 angle_local_parameterization);
    problem->SetParameterization(&pose_end_iter->second.yaw_radians,
                                 angle_local_parameterization);
  }

  // The pose graph optimization problem has three DOFs that are not fully
  // constrained. This is typically referred to as gauge freedom. You can apply
  // a rigid body transformation to all the nodes and the optimization problem
  // will still have the exact same cost. The Levenberg-Marquardt algorithm has
  // internal damping which mitigate this issue, but it is better to properly
  // constrain the gauge freedom. This can be done by setting one of the poses
  // as constant so the optimizer cannot change it.
  std::map<int, Pose2d>::iterator pose_start_iter = poses->begin();
  CHECK(pose_start_iter != poses->end()) << "There are no poses.";
  problem->SetParameterBlockConstant(&pose_start_iter->second.x);
  problem->SetParameterBlockConstant(&pose_start_iter->second.y);
  problem->SetParameterBlockConstant(&pose_start_iter->second.yaw_radians);
}

2. G2O的位姿图优化

G2O这部分也在我们的系列文章《SLAM本质剖析-G2O》中详细的阐述了。这里不详细的去写了。G2O本身就是经常被用于位姿图优化的,所以这里我们直接给出例子。算法的整个流程:1、构造g2o优化问题并执行优化;2、每次优化完成后,对地图点是否为外点进行卡方检验,检验到为内点的加入下次优化,否则下次不优化;3、得到优化后的当前帧的位姿(此函数只优化当前帧位姿)

…详情请参照古月居

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python用selenium实现自动登录和下单的脚本

前言 学python对selenium应该不陌生吧 Selenium 是最广泛使用的开源 Web UI&#xff08;用户界面&#xff09;自动化测试套件之一。Selenium 支持的语言包括C#&#xff0c;Java&#xff0c;Perl&#xff0c;PHP&#xff0c;Python 和 Ruby。目前&#xff0c;Selenium Web 驱动…

打游戏哪种蓝牙耳机比较好?适合玩游戏的无线蓝牙耳机

2023年耳机市场一如既往地卷&#xff0c;不只是卷音质&#xff0c;还在外观和功能上做了许多的改进&#xff0c;以至于现在哪怕不懂耳机的人从各电商平台都能闭眼入一个款平价品牌耳机且极少会踩雷&#xff0c;玩游戏是当前年轻人的娱乐方式&#xff0c;下面整理了几款适合玩游…

Git push报错DeployKey does not support push code

错误描述用Git从本地仓库上传服务器仓库报错&#xff1a;DeployKey does not support push code错误代码&#xff1a;(通过$ git push origin master命令从本地仓库上传到服务器仓库)错误原因&#xff1a;没有注册ssh公钥解决办法&#xff1a;添加ssh公钥&#xff1a;先生成对应…

滤波算法 | 无迹卡尔曼滤波(UKF)算法及其MATLAB实现

目录简介UKF滤波滤波流程和公式MATLAB程序结论简介 本文接着分享位姿跟踪和滤波算法中用到的一些常用程序&#xff0c;希望为后来者减少一些基础性内容的工作时间。以往分享总结见文章&#xff1a;位姿跟踪 | 相关内容目录和链接总结&#xff08;不断更新中~~~&#xff09; 本…

代码随想录算法训练营第六天 |哈希表理论基础、242.有效的字母异位词、349. 两个数组的交集 、202. 快乐数、 1. 两数之和

打卡第六天&#xff0c;补昨天的卡 今日任务 哈希表理论基础242.有效的字母异位词349.两个数组的交集202.快乐数1.两数之和 哈希表理论基础 哈希表是根据关键码的值而直接进行访问的数据结构。 哈希表能解决什么问题呢? 一般哈希表都是用来快速判断一个元素是否出现集合里。 …

JavaDoc生成API文档(powernode document)(内含源代码和导出的文档)

JavaDoc生成API文档&#xff08;powernode document&#xff09;&#xff08;内含源代码和导出的文档&#xff09; 源代码和导出的文档下载链接地址&#xff1a;https://download.csdn.net/download/weixin_46411355/87473296 目录JavaDoc生成API文档&#xff08;powernode do…

恺望数据:解决智驾数据生产痛点,提供自动化生产线和规模化人力

最近Chat GPT引起了一个热点话题&#xff0c;就是人工智能是否真的可以替代人类工作&#xff0c;特别是在需要进行数据标注等需要人力的领域。 自动驾驶数据服务公司恺望数据在最近的一个会议上透露了一些消息&#xff0c;他们已经推出了一个基于自动化的数据生产系统&#xff…

linux下安装minio

获取 MinIO 下载 URL:访问&#xff1a;https://docs.min.io/ 一&#xff0c;进入/opt 目录&#xff0c;创建minio文件夹 cd /optmkdir minio二&#xff0c;wget下载安装包 wget https://dl.minio.io/server/minio/release/linux-amd64/minio三&#xff0c;进入minio文件夹创建…

蓝海创意云vLive虚拟直播亮相2023昆山元宇宙产品展览会

2月15日-19日&#xff0c;由中国计算机行业协会“元宇宙创见未来”2023元宇宙产品展览会在江苏昆山隆重召开&#xff0c;共吸引了省内外32家企业参展&#xff0c;展出近百款元宇宙产品或技术&#xff0c;涵盖芯片、显示、VR、AR等硬件设备&#xff0c;以及工业、文旅、娱乐、教…

golang及goland的安装

1.电脑环境 2.软件下载 链接&#xff1a;https://pan.baidu.com/s/1YHM_jazftwkqRAuxJqMHZg 提取码&#xff1a;cdbm go1.17.5.windows-amd64.msi是go语言的开发及运行环境类似于Java的JDK。 goland-2020.2.2.exe 是go语言的开发工具(IDE),类似于Java的 Intelli J IDEA。 3…

【计算机网络】应用题方法总结

0.前言本篇博客主要记录自己在学习到的部分解决计算机网络应用题方法&#xff0c;主要参考视频如下&#xff1a;计算机网络期末复习 应用题_哔哩哔哩_bilibili【计算机网络】子网划分题型总结_哔哩哔哩_bilibili循环冗余码step 1&#xff1a;确定冗余码长度。多项式最高位即为冗…

阶段二11_面向对象高级_学生管理系统案例1

说明&#xff1a;学生管理系统案例需求和步骤1请查看上一张《阶段二10_面向对象高级_分类分包思想》 一.学生管理系统案例 步骤2&#xff1a;搭建主菜单和学生管理系统菜单 (0).主菜单和学习菜单界面和思路 界面&#xff1a; --------欢迎来到信息管理系统-------- 请输入您的…

CMake编译opencv4.6

openCV系列文章目录 文章目录openCV系列文章目录前言一、准备工作二、使用步骤1.使用CMake编译openCV总结前言 最近在项目中遇到图片处理&#xff0c;一拍脑袋就想到大名鼎鼎的opencv 一、准备工作 1.openCV官网下载 2.CMake官方下载 3.vs2019官方下载 二、使用步骤 1.使用…

[ vulhub漏洞复现篇 ] Drupal XSS漏洞 (CVE-2019-6341)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

Qt基础之二十九:图形视图框架(Graphics View Framework)及其应用

无意中从网络获取一份俄罗斯方块源码,基于图形视图框架(Graphics View Framework)实现的。当然源码的核心从来都不是界面,而是方块的移动、变形和消除等算法。源码非常完整,注释详细,经改动后已能在Qt5中运行,下面是运行效果,背景音乐和音效也是有的。 一.效果 二.原理 …

Spring MVC 源码 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器

HandlerMapping 组件HandlerMapping 组件&#xff0c;请求的处理器匹配器&#xff0c;负责为请求找到合适的 HandlerExecutionChain 处理器执行链&#xff0c;包含处理器&#xff08;handler&#xff09;和拦截器们&#xff08;interceptors&#xff09;handler 处理器是 Objec…

复旦大学邱锡鹏团队发布类 ChatGPT 模型MOSS

不知道这个人工智能&#xff0c;有没有获得完整的一生。 ChatGPT 是最先进的 AI&#xff0c;也是最热门的应用 —— 自去年 11 月底发布以来&#xff0c;它的月活跃用户两个月超过一亿&#xff0c;轻松拿到了全球互联网史上用户增长速度的第一。 它也是一种门槛很高的技术。由…

Metasploit 使用篇(二)

文章目录前言一、侦察1.banner信息2.http头检测小结二、枚举DNS枚举枚举文件三、漏洞扫描四、漏洞评估前言 内容均来自《Web渗透测试实战 基于Metasploit5.0》&#xff0c;包括渗透测试生命周期中的4个部分&#xff1a; 侦察Web应用枚举漏洞扫描漏洞评估 以对metasplitable …

入门力扣自学笔记236 C++ (题目编号:1326)(贪心算法)

1326. 灌溉花园的最少水龙头数目 题目&#xff1a; 在 x 轴上有一个一维的花园。花园长度为 n&#xff0c;从点 0 开始&#xff0c;到点 n 结束。 花园里总共有 n 1 个水龙头&#xff0c;分别位于 [0, 1, ..., n] 。 给你一个整数 n 和一个长度为 n 1 的整数数组 ranges …

江南爱窗帘十大品牌 | 推荐3种简单的窗帘上色方法

窗帘可以在色彩搭配好的空间里营造温度和情调&#xff1f;窗帘不仅是用来挡光的&#xff0c;更能营造温暖和氛围家。除了成品家具&#xff0c;家中软装的另一大部分就是窗帘了。窗帘的颜色搭配好&#xff0c;我们家瞬间有温度。今天我们将讨论一些简单的窗帘上色方法。1、窗帘我…