Fast-Planner(三)详解后端B-Spline曲线优化

news2024/11/13 20:26:46

本文上接Fast-Planner的B-spline曲线生成详解,介绍B-spline曲线优化。如有问题,欢迎各位大佬评论指出,带着我一起进步。

三、B样条优化

初始化获得的B样条曲线只是达到了可达性检测,本章介绍为得到更加光滑安全的轨迹,如何通过优化控制点优化生成的B样条轨迹。

3.1 代价函数

总的代价函数:
f t o t a l = λ 1 f s + λ 2 f c + λ 3 ( f v + f a ) f_{total}=\lambda_1f_s+\lambda_2f_c+\lambda_3(f_v+f_a) ftotal=λ1fs+λ2fc+λ3(fv+fa)
其中 f s f_s fs为平滑度损失, f c f_c fc为障碍损失, f v f_v fv f a f_a fa分别是速度和加速的的软约束。

3.1.1 平滑度损失

弹性损失方程:
在这里插入图片描述这个函数可以这样子理解,对于控制点 Q i Q_{i} Qi,它的前后两个点分别对该点作用一个力即 Q i + 1 − Q i Q_{i+1}-Q_i Qi+1Qi Q i − 1 − Q i Q_{i-1}-Q_i Qi1Qi,合力方向二者相加,合力越小,说明这个点的两个作用力的夹角更近180度且大小差不多相等,这么一来控制点更加平滑。(这属于一种抽象理解,起始可以将加速度控制点由位置控制点结合也能得出这个函数)
代码中似乎没有体现这个函数,如果有大佬懂请在评论区留言

3.1.2 Jerk控制点

论文中提到其他工作采用这一项,本文取而代之用上述平滑度损失,但是在代码中体现了这一块。公式如下:
J e r k i = 1 Δ t ( A i + 1 − A i ) = 1 Δ t 3 ( Q i + 3 − 3 Q i + 2 + 3 Q i + 1 − Q i ) Jerk_i=\frac{1}{\Delta t}(A_{i+1}-A_{i})=\frac{1}{\Delta t^3}(Q_{i+3}-3Q_{i+2}+3Q_{i+1}-Q_{i}) Jerki=Δt1(Ai+1Ai)=Δt31(Qi+33Qi+2+3Qi+1Qi)

/*
  function:
    最小化Jerk输入
  param:
    q:控制点
    cost:损失
    gradient:梯度信息
*/
void BsplineOptimizer::calcSmoothnessCost(const vector<Eigen::Vector3d>& q, double& cost,
                                          vector<Eigen::Vector3d>& gradient) {
  cost = 0.0;
  Eigen::Vector3d zero(0, 0, 0);
  std::fill(gradient.begin(), gradient.end(), zero);
  Eigen::Vector3d jerk, temp_j;
  //遍历控制点求jerk
  for (int i = 0; i < q.size() - order_; i++) {
    /* evaluate jerk */
    jerk = q[i + 3] - 3 * q[i + 2] + 3 * q[i + 1] - q[i];
    cost += jerk.squaredNorm();
    temp_j = 2.0 * jerk;  //cost的一阶导,用于后续梯度更新
    /* jerk gradient */
    gradient[i + 0] += -temp_j;
    gradient[i + 1] += 3.0 * temp_j;
    gradient[i + 2] += -3.0 * temp_j;
    gradient[i + 3] += temp_j;
  }
}

3.1.3 障碍物碰撞惩罚函数

论文中定义为作用于每个控制点的障碍物的排斥力,如下式 d ( Q i ) d(Q_i) d(Qi) Q i Q_i Qi与其最近障碍物之间的距离。
f c = ∑ i = p b N − p b F c ( d ( Q i ) ) f_c=\sum^{N-p_b}_{i=p_b}F_c(d(Q_i)) fc=i=pbNpbFc(d(Qi))
在这里插入图片描述为保证B样条曲线轨迹的安全性,需要保证位置控制点组成的凸包不与障碍物相重合(即下图中的 d h > 0 d_h>0 dh>0),可以推出 d h > d c − ( r 12 + r 23 + r 34 ) d_h>d_c-(r_{12}+r_{23}+r_{34}) dh>dc(r12+r23+r34),则如果满足 r j , j + 1 < d c / 3 ( j ∈ { 1 , 2 , 3 } ) r_{j,j+1}<d_c/3(j\in\{1,2,3\}) rj,j+1<dc/3(j{1,2,3})就能保证凸包不与障碍物碰撞,这样做的好处是能把控制点到障碍物的距离约束导相邻控制点之间的距离。这里论文将 r j , j + 1 < 0.2 r_{j,j+1}<0.2 rj,j+1<0.2做了一个限定(可以调整)。
在这里插入图片描述其中evaluateEDTWithGrad相关函数在地图相关类中

/*
  function:
    计算障碍物距离代价惩罚函数
*/
void BsplineOptimizer::calcDistanceCost(const vector<Eigen::Vector3d>& q, double& cost,
                                        vector<Eigen::Vector3d>& gradient) {
  cost = 0.0;
  Eigen::Vector3d zero(0, 0, 0);
  std::fill(gradient.begin(), gradient.end(), zero);

  double          dist;
  Eigen::Vector3d dist_grad, g_zero(0, 0, 0);
  //检查是否包含ENDPOINT标志,函数根据是否需要考虑路径的终点来调整计算
  //如果包含 ENDPOINT,则处理整个路径点向量;如果不包含,就排除掉最后order_个点,避免在计算中出现索引越界的问题。
  int end_idx = (cost_function_ & ENDPOINT) ? q.size() : q.size() - order_;

  for (int i = order_; i < end_idx; i++) {
    //计算距离梯度dist与距离梯度dist_grad
    edt_environment_->evaluateEDTWithGrad(q[i], -1.0, dist, dist_grad);
    //如果梯度的范数大于阈值,则对梯度进行归一化
    if (dist_grad.norm() > 1e-4) dist_grad.normalize();
    //当路径点距离环境较近时,代价增加,通过梯度的计算,可在后续优化中调整路径点,以增加与环境的距离,从而减少代价。
    if (dist < dist0_) {
      cost += pow(dist - dist0_, 2);
      gradient[i] += 2.0 * (dist - dist0_) * dist_grad;
    }
  }
}

3.1.4 动力学可行性惩罚

为了让每一个超过速度、加速度限制的控制范围进行惩罚,作如下限制,这里的速度并不是合速度作限制,而是三个方向的速度分量分别作限制:

在这里插入图片描述在这里插入图片描述

/*
  function:
    计算控制点速度、加速度可行性函数
*/
void BsplineOptimizer::calcFeasibilityCost(const vector<Eigen::Vector3d>& q, double& cost,
                                           vector<Eigen::Vector3d>& gradient) {
  cost = 0.0;
  Eigen::Vector3d zero(0, 0, 0);
  std::fill(gradient.begin(), gradient.end(), zero);
  
  //最大速度、加速度平方,ts时间倒数平方与四次方
  /* abbreviation */
  double ts, vm2, am2, ts_inv2, ts_inv4;
  vm2 = max_vel_ * max_vel_;
  am2 = max_acc_ * max_acc_;

  ts      = bspline_interval_;
  ts_inv2 = 1 / ts / ts;
  ts_inv4 = ts_inv2 * ts_inv2;

  /* velocity feasibility */
  for (int i = 0; i < q.size() - 1; i++) {
    //位置控制点转速度控制点
    Eigen::Vector3d vi = q[i + 1] - q[i];

    for (int j = 0; j < 3; j++) {
      //速度控制点公式带入
      double vd = vi(j) * vi(j) * ts_inv2 - vm2;
      if (vd > 0.0) {
        cost += pow(vd, 2);
        //求导
        double temp_v = 4.0 * vd * ts_inv2;
        gradient[i + 0](j) += -temp_v * vi(j);
        gradient[i + 1](j) += temp_v * vi(j);
      }
    }
  }

  /* acceleration feasibility */
  for (int i = 0; i < q.size() - 2; i++) {
    //位置控制点转速度控制点
    Eigen::Vector3d ai = q[i + 2] - 2 * q[i + 1] + q[i];

    for (int j = 0; j < 3; j++) {
      double ad = ai(j) * ai(j) * ts_inv4 - am2;
      if (ad > 0.0) {
        cost += pow(ad, 2);
        //求导
        double temp_a = 4.0 * ad * ts_inv4;
        gradient[i + 0](j) += temp_a * ai(j);
        gradient[i + 1](j) += -2 * temp_a * ai(j);
        gradient[i + 2](j) += temp_a * ai(j);
      }
    }
  }
}

3.1.5 计算总代价函数和

/*
  优化函数combineCost:
    输入参数:
      1.x为Nlopt优化的变量,应该与三个维度的控制点对应。
      2.grad,总的优化项关于每个优化变量的梯度信息。
      3.f_combine,结合后的cost。
*/
void BsplineOptimizer::combineCost(const std::vector<double>& x, std::vector<double>& grad,
                                   double& f_combine) {
  /* convert the NLopt format vector to control points. */
  // NLopt格式转换到控制点表示,以支持1D至3D的B样条优化
  // This solver can support 1D-3D B-spline optimization, but we use Vector3d to store each control point
  // For 1D case, the second and third elements are zero, and similar for the 2D case.
  for (int i = 0; i < order_; i++) {
    for (int j = 0; j < dim_; ++j) {
      g_q_[i][j] = control_points_(i, j);
    }
    //如果 dim_ 小于3,则剩余的维度设置为0。这是为了处理1D和2D情况。
    for (int j = dim_; j < 3; ++j) {
      g_q_[i][j] = 0.0;
    }
  }
  // 处理由NLopt传入的变量x,将其转换为控制点并存储在g_q_中
  for (int i = 0; i < variable_num_ / dim_; i++) {
    for (int j = 0; j < dim_; ++j) {
      g_q_[i + order_][j] = x[dim_ * i + j];
    }
    for (int j = dim_; j < 3; ++j) {
      g_q_[i + order_][j] = 0.0;
    }
  }
    //考虑终点,如果 cost_function_ 不包含 ENDPOINT 标志,则将 control_points_ 中的最后 order_ 个控制点复制到 g_q_ 的相应位置
  if (!(cost_function_ & ENDPOINT)) {
    for (int i = 0; i < order_; i++) {

      for (int j = 0; j < dim_; ++j) {
        g_q_[order_ + variable_num_ / dim_ + i][j] =
            control_points_(control_points_.rows() - order_ + i, j);
      }
      for (int j = dim_; j < 3; ++j) {
        g_q_[order_ + variable_num_ / dim_ + i][j] = 0.0;
      }
    }
  }

  f_combine = 0.0;  //总代价函数
  grad.resize(variable_num_);
  fill(grad.begin(), grad.end(), 0.0);

  /*  evaluate costs and their gradient  */
  //每个代价变量
  double f_smoothness, f_distance, f_feasibility, f_endpoint, f_guide, f_waypoints;
  f_smoothness = f_distance = f_feasibility = f_endpoint = f_guide = f_waypoints = 0.0;

  if (cost_function_ & SMOOTHNESS) {
    calcSmoothnessCost(g_q_, f_smoothness, g_smoothness_);
    f_combine += lambda1_ * f_smoothness;
    for (int i = 0; i < variable_num_ / dim_; i++)
      for (int j = 0; j < dim_; j++) grad[dim_ * i + j] += lambda1_ * g_smoothness_[i + order_](j);
  }
  if (cost_function_ & DISTANCE) {
    calcDistanceCost(g_q_, f_distance, g_distance_);
    f_combine += lambda2_ * f_distance;
    for (int i = 0; i < variable_num_ / dim_; i++)
      for (int j = 0; j < dim_; j++) grad[dim_ * i + j] += lambda2_ * g_distance_[i + order_](j);
  }
  if (cost_function_ & FEASIBILITY) {
    calcFeasibilityCost(g_q_, f_feasibility, g_feasibility_);
    f_combine += lambda3_ * f_feasibility;
    for (int i = 0; i < variable_num_ / dim_; i++)
      for (int j = 0; j < dim_; j++) grad[dim_ * i + j] += lambda3_ * g_feasibility_[i + order_](j);
  }
  if (cost_function_ & ENDPOINT) {
    calcEndpointCost(g_q_, f_endpoint, g_endpoint_);
    f_combine += lambda4_ * f_endpoint;
    for (int i = 0; i < variable_num_ / dim_; i++)
      for (int j = 0; j < dim_; j++) grad[dim_ * i + j] += lambda4_ * g_endpoint_[i + order_](j);
  }
  if (cost_function_ & GUIDE) {
    calcGuideCost(g_q_, f_guide, g_guide_);
    f_combine += lambda5_ * f_guide;
    for (int i = 0; i < variable_num_ / dim_; i++)
      for (int j = 0; j < dim_; j++) grad[dim_ * i + j] += lambda5_ * g_guide_[i + order_](j);
  }
  if (cost_function_ & WAYPOINTS) {
    calcWaypointsCost(g_q_, f_waypoints, g_waypoints_);
    f_combine += lambda7_ * f_waypoints;
    for (int i = 0; i < variable_num_ / dim_; i++)
      for (int j = 0; j < dim_; j++) grad[dim_ * i + j] += lambda7_ * g_waypoints_[i + order_](j);
  }
  /*  print cost  */
  // if ((cost_function_ & WAYPOINTS) && iter_num_ % 10 == 0) {
  //   cout << iter_num_ << ", total: " << f_combine << ", acc: " << lambda8_ * f_view
  //        << ", waypt: " << lambda7_ * f_waypoints << endl;
  // }

  // if (optimization_phase_ == SECOND_PHASE) {
  //  << ", smooth: " << lambda1_ * f_smoothness
  //  << " , dist:" << lambda2_ * f_distance
  //  << ", fea: " << lambda3_ * f_feasibility << endl;
  // << ", end: " << lambda4_ * f_endpoint
  // << ", guide: " << lambda5_ * f_guide
  // }
}

3.2 通过Nlopt进行优化

Nlopt是非线性优化库,注意fast-planner不要用ros版的nlopt,那样版本不对,要通过第三方下载,Nlopt库下载链接
下载完正常安装到local就行。Nlopt基础使用参考博客

//利用Nlopt对控制点进行非线性优化
Eigen::MatrixXd BsplineOptimizer::BsplineOptimizeTraj(const Eigen::MatrixXd& points, const double& ts,
                                                      const int& cost_function, int max_num_id,
                                                      int max_time_id) {
  //初始化控制点与维度
  setControlPoints(points);
  //初始化节点向量
  setBsplineInterval(ts);
  //初始化代价函数
  setCostFunction(cost_function);
  //初始化最大控制点与时间id
  setTerminateCond(max_num_id, max_time_id);
  //执行优化
  optimize();
  //返回优化后的控制点
  return this->control_points_;
}

//优化函数
void BsplineOptimizer::optimize() {
  /* initialize solver */
  //初始化迭代次数和最小代价、设置控制点的数量
  iter_num_        = 0;
  min_cost_        = std::numeric_limits<double>::max();
  const int pt_num = control_points_.rows();
  //初始化存储代价向量
  g_q_.resize(pt_num);
  g_smoothness_.resize(pt_num);
  g_distance_.resize(pt_num);
  g_feasibility_.resize(pt_num);
  g_endpoint_.resize(pt_num);
  g_waypoints_.resize(pt_num);
  g_guide_.resize(pt_num);

  //判断是否有终点硬约束
  if (cost_function_ & ENDPOINT) {
    variable_num_ = dim_ * (pt_num - order_);
    // end position used for hard constraint
    end_pt_ = (1 / 6.0) *
        (control_points_.row(pt_num - 3) + 4 * control_points_.row(pt_num - 2) +
         control_points_.row(pt_num - 1));
  } else {
    variable_num_ = max(0, dim_ * (pt_num - 2 * order_)) ;
  }

  /* do optimization using NLopt slover */
  //创建NLopt优化器,选择合适的算法
  nlopt::opt opt(nlopt::algorithm(isQuadratic() ? algorithm1_ : algorithm2_), variable_num_);
  opt.set_min_objective(BsplineOptimizer::costFunction, this);
  //设置最大迭代次数、最长运行时间和相对容差
  opt.set_maxeval(max_iteration_num_[max_num_id_]);
  opt.set_maxtime(max_iteration_time_[max_time_id_]);
  opt.set_xtol_rel(1e-5);

  vector<double> q(variable_num_);
  for (int i = order_; i < pt_num; ++i) {
    if (!(cost_function_ & ENDPOINT) && i >= pt_num - order_) continue;
    for (int j = 0; j < dim_; j++) {
      q[dim_ * (i - order_) + j] = control_points_(i, j);
    }
  }

  if (dim_ != 1) {
    vector<double> lb(variable_num_), ub(variable_num_);
    const double   bound = 10.0;
    for (int i = 0; i < variable_num_; ++i) {
      lb[i] = q[i] - bound;
      ub[i] = q[i] + bound;
    }
    opt.set_lower_bounds(lb);
    opt.set_upper_bounds(ub);
  }

  try {
    // cout << fixed << setprecision(7);
    // vec_time_.clear();
    // vec_cost_.clear();
    // time_start_ = ros::Time::now();
    //执行优化
    double        final_cost;
    nlopt::result result = opt.optimize(q, final_cost);

    /* retrieve the optimization result */
    // cout << "Min cost:" << min_cost_ << endl;
  } catch (std::exception& e) {
    ROS_WARN("[Optimization]: nlopt exception");
    cout << e.what() << endl;
  }
  //遍历向量
  for (int i = order_; i < control_points_.rows(); ++i) {
    if (!(cost_function_ & ENDPOINT) && i >= pt_num - order_) continue;
    for (int j = 0; j < dim_; j++) {
      control_points_(i, j) = best_variable_[dim_ * (i - order_) + j];
    }
  }

  if (!(cost_function_ & GUIDE)) ROS_INFO_STREAM("iter num: " << iter_num_);
}

本章实现的是B样条控制点的优化,最后输出的是一组优化后的控制点。
本章完,fast-planner系列未完待遇。。。

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

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

相关文章

训练自己的分类数据集

文章目录 1.数据集准备2. 数据集划分3.设计模型进行训练1. 随便乱写的网络2. 借鉴优秀的网络模型&#xff08;MobileNetV3&#xff09; 分析&#xff1a;练习&#xff1a; 1.数据集准备 准备你要进行分类的数据集&#xff0c;可以自己拍摄&#xff0c;可以网上进行爬取。 将不…

pdf压缩文件怎么压缩最小?一键压缩PDF

pdf文件压缩是为了减小文件大小&#xff0c;以便更轻松地共享、传输和存储文件&#xff0c;通过压缩pdf文件&#xff0c;可以减少文件占用的存储空间&#xff0c;加快文件的上传和下载速度&#xff0c;并节省带宽和存储成本;在本教程中&#xff0c;我们将介绍一些有效的方法来最…

人工智能三剑客NumPy、pandas、matplotlib和Jupyter四者之间的关系

NumPy 主要用途&#xff1a;NumPy&#xff08;Numerical Python的缩写&#xff09;主要用于处理大型多维数组和矩阵的科学计算。它提供了一个高性能的多维数组对象&#xff0c;以及用于数组操作的工具。与其他三者的联系&#xff1a;NumPy是pandas和matplotlib的基础库之一。许…

10.IO流(一)

文章目录 IO流一、File1.创建对象1.1 创建File类的对象1.2 注意1.3 绝对路径、相对路径 2.常用方法1:判断文件类型、获取文件信息3.常用方法2:创建文件、删除文件3.1 File类创建文件的功能3.2 File类删除文件的功能 4.常用方法3:遍历文件夹4.1 File类提供的遍历文件夹的功能4.2…

【JAVA】数据类型与变量(主要学习与c语言不同之处)

✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;橘橙黄又青-CSDN博客 目标&#xff1a; 1. 字面常量 2. 数据类型 3. 变量 1.字面常量 在上节课 Hello…

paramiko,一个强大的 Python 库!

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个强大的 Python 库 - paramiko。 Github地址&#xff1a;https://github.com/paramiko/paramiko 在网络编程中&#xff0c;远程操作是一项非常常见的需求&#xff0c;特别是…

QT 界面2.1

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {this->setWindowIcon(QIcon(":/Logo/1bc87d9b4c1ea878d5e0845257a06f7f (1).jpg")); // 图标this->setWindowTitle(&…

环境变量与进程详解

文章目录 1.进程的优先级1.1 查看优先级1.2 调制优先级 2.环境变量2.1 命令行参数2.2 环境变量 3.程序的地址空间3.1 为什么要有地址空间&#xff1f;3.2 linux的大O(1)调度算法 4.进程创建5.进程中止5.1 进程的退出码&#xff08;错误码&#xff09;5.2 进程终止的最优写法&am…

苍穹外卖Day07——总结7

前期文章 文章标题地址苍穹外卖Day01——总结1https://lushimeng.blog.csdn.net/article/details/135466359苍穹外卖Day02——总结2https://lushimeng.blog.csdn.net/article/details/135484126苍穹外卖Day03——总结3https://blog.csdn.net/qq_43751200/article/details/1363…

Data.olllo解密:秒数转换为日期格式的神奇技能!

引言&#xff1a; 时间是数据世界中不可或缺的一环&#xff0c;而将秒数转换为易读的日期格式往往是数据处理中的一大挑战。但是&#xff0c;有了Data.olllo&#xff0c;这一任务将变得异常简单&#xff01; 功能介绍&#xff1a; Data.olllo的秒数转换功能可以让您轻松地将秒…

花钱买不到系列-linux信号[2]卷

上一篇信号&#xff0c;我们知道了&#xff0c;进程需要保存信号&#xff0c;信号究竟是什么&#xff1f;什么又叫做发送信号呢&#xff1f;什么又叫做进程保存信号呢&#xff1f;那么&#xff0c;实际上呢&#xff1f;给大家一个基本的思考切入点&#xff0c;就是我们常见的信…

摩根大通:金和铜短期都涨多了 “一年十倍股”超微:预计在AI服务器占10-15%份额,给24倍PE很合理

今年以来大宗商品持续回暖&#xff0c;黄金、铜等金属表现尤为亮眼&#xff0c;乐观情绪蔓延之际摩根大通站出来提醒&#xff0c;小心回调风险&#xff01; 摩根大通在最新商品金属周报指出&#xff0c;黄金和铜价的急剧上涨反映了市场对中长期趋势的乐观预期&#xff0c;然而…

出差补助怎么发放更高效省心?这套攻略快看看

交补、餐补、话补等各类补助场景分散&#xff0c;无法实现一站式统筹管理。不仅如此&#xff0c;补贴核算也总是需要员工提供各类凭证&#xff0c;经过财务反复核实才能发放……出差发放补助原本是为了传递企业关怀&#xff0c;鼓励员工积极出差&#xff0c;由于发放和管理不当…

刷题日记——济南大学机试

折戟厦大&#xff0c;考虑调剂济南大学&#xff0c;但是更想去的是杭师大&#xff0c;还是刷题&#xff0c;济南大学比厦门大学题目简单很多&#xff0c;因此一篇文章不多分析&#xff0c;直接给出代码&#xff0c;全部采用纯C语言编写并且AC&#xff0c;不用C的stl库。 争取今…

Python学习笔记(二)

一&#xff1a;异常&#xff1a; 1.1&#xff1a;异常处理&#xff1a; 1.2&#xff1a;异常捕获&#xff1a; 1.3&#xff1a;异常传递&#xff1a; 二&#xff1a;模块&#xff1a; 2.1&#xff1a;模块的定义&#xff1a; 2.2&#xff1a;模块的导入&#xff1a; 2.3&…

vue3+ts+elementplus写一个登录页面教程

文章目录 前言1. 安装 Vue CLI 和 TypeScript 支持2. 创建登录组件 文章重点内容 前言 前期准备步骤&#xff1a; 创建一个使用 Vue 3 和 TypeScript 的登录页面涉及到多个步骤。以下是一个基本的教程&#xff0c;帮助你从头开始构建这样一个页面&#xff1a; 1. 安装 Vue CL…

电路笔记 :灯光画 元器件焊接+连锡处理

https://oshwhub.com/qazwsx1987/dengguanghua_0#P3 基础工具 常用的电路焊接工具&#xff1a; 工具描述电烙铁我买了一个便携电烙铁&#xff0c;但是烙铁头温度太低&#xff0c;焊锡总是粘在烙铁头上&#xff08;因为电量不足&#xff09;, 打火机秒变电烙铁焊台用于支撑工…

突破创作瓶颈:Kompas.ai的创意激发法

在创作的世界里&#xff0c;灵感如同一股清泉&#xff0c;滋养着每一个创作者的心灵。然而&#xff0c;即便是最富创造力的头脑&#xff0c;也难免会遇到创作瓶颈——那些令人窒息的、灵感枯竭的时刻。这些瓶颈不仅阻碍了创作的进程&#xff0c;还可能严重影响内容的质量和创作…

程序员35岁会失业吗?【来自主流AI的回答】

程序员35岁会失业吗&#xff1f; 35岁被认为是程序员职业生涯的分水岭&#xff0c;许多程序员开始担忧自己的职业发展是否会受到年龄的限制。有人担心随着年龄的增长&#xff0c;技术更新换代的速度会使得资深程序员难以跟上&#xff1b;而另一些人则认为&#xff0c;丰富的经…

卷起来——高级数据分析师

要成为一名高级数据分析师&#xff0c;需要掌握一系列的技能&#xff0c;包括数据处理、统计分析、机器学习、数据可视化以及业务理解等&#xff0c;喜欢或者想往这方面发展的童鞋们&#xff0c;卷起来&#xff0c;点击以下链接中的链接&#xff0c;备注"分析"进群交…