参考线平滑 - FemPosDeviation算法

FemPosDeviation参考线平滑方法是离散点平滑方法
参考文章:
(1)参考线平滑-FemPosDeviation-OSQP
(2)Planning基础库——散点曲线平滑
(3)参考线平滑-FemPosDeviation-SQP
(4)Apollo planning之参考线平滑算法
(5)解析百度Apollo之参考线与轨迹
(6)百度Apollo代码阅读:参考线平滑FemPosDeviationSmoother

1 参考线的作用

参考线在planning中的作用相当于一个地基,所有决策与优化都是在参考线的基础上进行

在这里插入图片描述
🍎🍉为什么需要参考线🍉🍎
(1)HD map一般都是人为采集离散点,也就使得原始路径不平滑
(2)全局导航的路径过长,障碍物的投影点也可能不唯一
(3)生成一个局部一定长度且光滑的参考线节省算力

2 ReferenceLine数据结构

参考线是根据车辆位置相对局部的一个数据,它包含了车辆前后一定范围内的路径信息。在车辆行驶过程中,Planning会在每一个计算周期中生成ReferenceLine

// modules/planning/reference_line/reference_line.h

uint32_t priority_ = 0;  // 优先级
struct SpeedLimit { 
  double start_s = 0.0;
  double end_s = 0.0;
  double speed_limit = 0.0;  // unit m/s
};
std::vector<SpeedLimit> speed_limit_; // 限速数据

std::vector<ReferencePoint> reference_points_; // 一系列的点,点包含了位置的信息。因此这些点就是生成车辆行驶轨迹的基础数据
hdmap::Path map_path_;

在这里插入图片描述
说明:
Vec2d描述一个二维的点:

double x_:描述点的x坐标
double y_:描述点的y坐标

MapPathPoint描述了一个地图上的点:

double heading_:描述点的朝向。
std::vector<LaneWaypoint> lane_waypoints_:描述路径上的点。有些车道可能会存在重合的部分,所以地图上的一个点可能同时属于多个车道,因此这里的数据是一个vector结构。

ReferencePoint描述了参考线中的点:

double kappa_:描述曲线的曲率。
double dkappa_:描述曲率的导数。

ReferencePoint中的朝向和曲率直接影响车辆的方向控制。如果ReferenceLine中的ReferencePoint之间存在朝向和曲率大小震动或者数据跳变将可能导致车辆方向盘的相应变化,这种变化对于乘车体验来说是非常糟糕的。

3 参考线处理流程

① 生成参考线,这主要由Routing模块的输出决定
② 参考线平滑,接下来会详细讲解参考线的平滑的算法
在这里插入图片描述
在这里插入图片描述

4 参考线平滑算法

4.1 算法分类

Apollo参考线平滑有三种:
① 离散点平滑:Cos和Fem
② 螺线曲线平滑
③ QPSpline平滑

通过配置参数选择要采用的平滑算法

  if (smoother_config_.has_qp_spline()) {
    smoother_.reset(new QpSplineReferenceLineSmoother(smoother_config_));
  } else if (smoother_config_.has_spiral()) {
    smoother_.reset(new SpiralReferenceLineSmoother(smoother_config_));
  } else if (smoother_config_.has_discrete_points()) {
    smoother_.reset(new DiscretePointsReferenceLineSmoother(smoother_config_));
  } else {
    ACHECK(false) << "unknown smoother config "
                  << smoother_config_.DebugString();
  }
  is_initialized_ = true;

默认采用离散点平滑算法

4.2 参考线平滑算法流程

输入raw_reference_line,设置中间点(GetAnchorPoints),然后smooth,最后输出

bool ReferenceLineProvider::SmoothReferenceLine(
    const ReferenceLine &raw_reference_line, ReferenceLine *reference_line) {
  if (!FLAGS_enable_smooth_reference_line) {
    *reference_line = raw_reference_line;
    return true;
  }
  // 设置中间点
  std::vector<AnchorPoint> anchor_points;
  GetAnchorPoints(raw_reference_line, &anchor_points);
  smoother_->SetAnchorPoints(anchor_points);
  if (!smoother_->Smooth(raw_reference_line, reference_line)) {
    AERROR << "Failed to smooth reference line with anchor points";
    return false;
  }
  if (!IsReferenceLineSmoothValid(raw_reference_line, *reference_line)) {
    AERROR << "The smoothed reference line error is too large";
    return false;
  }
  return true;
}

AnchorPoint

struct AnchorPoint {
  common::PathPoint path_point;
  double lateral_bound = 0.0; // 裕度(留有一定余地)
  double longitudinal_bound = 0.0; // 裕度
  // 强制更平滑以严格遵循此参考点
  bool enforced = false;
};

根据原始的参考线来选取中间点

根据referenceline的S值在纵向的投影进行一个均匀的采样,采样的间隔大概是0.25米,采样完毕后就能得到一系列的AnchorPoint,每个AnchorPoint包含了一个path_point和一个横纵向的裕度(采样点周围可以平移的空间)。强约束表示必须必须严格遵守横纵向的裕度,所以在平滑过程中只有首尾两个点是强约束,中间的点都不是强约束。

默认配置:纵向边界(2.0),横向边界(最大值0.5,最小值0.1)

max_constraint_interval : 0.25
longitudinal_boundary_bound : 2.0
max_lateral_boundary_bound : 0.5
min_lateral_boundary_bound : 0.1
curb_shift : 0.2
lateral_buffer : 0.2

Smooth

bool status = false;
 
const auto& smoothing_method = config_.discrete_points().smoothing_method();
std::vector<std::pair<double, double>> smoothed_point2d;
switch (smoothing_method) {
  case DiscretePointsSmootherConfig::COS_THETA_SMOOTHING:
    status = CosThetaSmooth(raw_point2d, anchorpoints_lateralbound,
                            &smoothed_point2d);
    break;
  case DiscretePointsSmootherConfig::FEM_POS_DEVIATION_SMOOTHING:
    status = FemPosSmooth(raw_point2d, anchorpoints_lateralbound,
                          &smoothed_point2d);
    break;
  default:
    AERROR << "Smoother type not defined";
    return false;
}

if (!status) {
  AERROR << "discrete_points reference line smoother fails";
  return false;
bool DiscretePointsReferenceLineSmoother::FemPosSmooth(
    const std::vector<std::pair<double, double>>& raw_point2d,
    const std::vector<double>& bounds,
    std::vector<std::pair<double, double>>* ptr_smoothed_point2d) {
  const auto& fem_pos_config =
      config_.discrete_points().fem_pos_deviation_smoothing();
 
  FemPosDeviationSmoother smoother(fem_pos_config);
 
  // box contraints on pos are used in fem pos smoother, thus shrink the
  // bounds by 1.0 / sqrt(2.0)
  // 裕度收缩
  std::vector<double> box_bounds = bounds;
  const double box_ratio = 1.0 / std::sqrt(2.0);
  for (auto& bound : box_bounds) {
    bound *= box_ratio;
  }
 
  std::vector<double> opt_x;
  std::vector<double> opt_y;
  // 问题求解
  // 从其输入的参数来看,主要是AnchorPoint中的x,y和横纵向的裕度,输出是平滑后的point
  bool status = smoother.Solve(raw_point2d, box_bounds, &opt_x, &opt_y);
 
  if (!status) {
    AERROR << "Fem Pos reference line smoothing failed";
    return false;
  }
 
  if (opt_x.size() < 2 || opt_y.size() < 2) {
    AERROR << "Return by fem pos smoother is wrong. Size smaller than 2 ";
    return false;
  }

Solve

如果考虑了曲率的约束,该优化问题就是非线性的,就可以用Ipopt非线性求解器进行求解,也可以将曲率约束进行线性化之后用Osqp进行求解。
如果不考虑曲率约束的话,就可以直接用Osqp进行求解该二次优化的问题。

bool FemPosDeviationSmoother::Solve(
    const std::vector<std::pair<double, double>>& raw_point2d,
    const std::vector<double>& bounds, std::vector<double>* opt_x,
    std::vector<double>* opt_y) {
   // 考虑曲率约束
  if (config_.apply_curvature_constraint()) {
    if (config_.use_sqp()) {
      // 线性求解
      return SqpWithOsqp(raw_point2d, bounds, opt_x, opt_y);
    } else {
     // 非线性求解
      return NlpWithIpopt(raw_point2d, bounds, opt_x, opt_y);
    }
  }
  // 不考虑曲率约束 
  else 
  {
    // 线性求解(默认)
    return QpWithOsqp(raw_point2d, bounds, opt_x, opt_y);
  }
  return true;
}
4.3 具体算法原理

对于平滑度的衡量有两种方式:
① FemPosSmooth相对不精准,但是只需用二次规划能快速求解
② CosThetaSmooth相对精准,但是需要非线性规划,计算量大
优化目标
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

满足的约束:
在这里插入图片描述
二次规划求解首先要将问题转化为二次型式:
min ⁡ f ( x ) = 1 2 X T P X + q T X  subject to  : l ≤ A X ≤ u \begin{array}{l} \min f(x)=\frac{1}{2} X^{T} P X+q^{T} X \\ \text { subject to }: l \leq A X \leq u \end{array} minf(x)=21XTPX+qTX subject to :lAXu
在这里插入图片描述

FemPosDeviation算法使用OSQP二次规划求解器进行求解,其代码实现在modules/planning/math/discretized_points_smoothing/FemPosDeviationOsqpInterface.cc

fem_pos_deviation_osqp_interface中通过 CalculateKernel实现P矩阵,通过CalculateOffset实现q矩阵,通过CalculateAffineConstraint计算系数A
osqp中表阵用了压缩列的系数矩阵csc的格式保存,通过osqp的csc_matrix函数构造出来,其中m为矩阵的行数,n为矩阵的列数,nzmax是非0元素的数目,x为保存的数据对应链接中的ENTRY,p为对应的COL,指出i中第一列对应的位置;i为非零元素所在的行号,对应的ROW。

csc* csc_matrix(c_int    m,
                c_int    n,
                c_int    nzmax,
                c_float *x,
                c_int   *i,
                c_int   *p);

对优化目标函数的实现:

void FemPosDeviationOsqpInterface::CalculateKernel(
    std::vector<c_float>* P_data, std::vector<c_int>* P_indices,
    std::vector<c_int>* P_indptr) {
  CHECK_GT(num_of_variables_, 4);

  // Three quadratic penalties are involved:
  // 1. Penalty x on distance between middle point and point by finite element
  // estimate;
  // 2. Penalty y on path length;
  // 3. Penalty z on difference between points and reference points

  // General formulation of P matrix is as below(with 6 points as an example):
  // I is a two by two identity matrix, X, Y, Z represents x * I, y * I, z * I
  // 0 is a two by two zero matrix
  // |X+Y+Z, -2X-Y,   X,       0,       0,       0    |
  // |0,     5X+2Y+Z, -4X-Y,   X,       0,       0    |
  // |0,     0,       6X+2Y+Z, -4X-Y,   X,       0    |
  // |0,     0,       0,       6X+2Y+Z, -4X-Y,   X    |
  // |0,     0,       0,       0,       5X+2Y+Z, -2X-Y|
  // |0,     0,       0,       0,       0,       X+Y+Z|

  // Only upper triangle needs to be filled
  std::vector<std::vector<std::pair<c_int, c_float>>> columns;
  columns.resize(num_of_variables_);
  int col_num = 0;

  for (int col = 0; col < 2; ++col) {
    columns[col].emplace_back(col, weight_fem_pos_deviation_ +
                                       weight_path_length_ +
                                       weight_ref_deviation_);
    ++col_num;
  }

  for (int col = 2; col < 4; ++col) {
    columns[col].emplace_back(
        col - 2, -2.0 * weight_fem_pos_deviation_ - weight_path_length_);
    columns[col].emplace_back(col, 5.0 * weight_fem_pos_deviation_ +
                                       2.0 * weight_path_length_ +
                                       weight_ref_deviation_);
    ++col_num;
  }

  int second_point_from_last_index = num_of_points_ - 2;
  for (int point_index = 2; point_index < second_point_from_last_index;
       ++point_index) {
    int col_index = point_index * 2;
    for (int col = 0; col < 2; ++col) {
      col_index += col;
      columns[col_index].emplace_back(col_index - 4, weight_fem_pos_deviation_);
      columns[col_index].emplace_back(
          col_index - 2,
          -4.0 * weight_fem_pos_deviation_ - weight_path_length_);
      columns[col_index].emplace_back(
          col_index, 6.0 * weight_fem_pos_deviation_ +
                         2.0 * weight_path_length_ + weight_ref_deviation_);
      ++col_num;
    }
  }

  int second_point_col_from_last_col = num_of_variables_ - 4;
  int last_point_col_from_last_col = num_of_variables_ - 2;
  for (int col = second_point_col_from_last_col;
       col < last_point_col_from_last_col; ++col) {
    columns[col].emplace_back(col - 4, weight_fem_pos_deviation_);
    columns[col].emplace_back(
        col - 2, -4.0 * weight_fem_pos_deviation_ - weight_path_length_);
    columns[col].emplace_back(col, 5.0 * weight_fem_pos_deviation_ +
                                       2.0 * weight_path_length_ +
                                       weight_ref_deviation_);
    ++col_num;
  }

  for (int col = last_point_col_from_last_col; col < num_of_variables_; ++col) {
    columns[col].emplace_back(col - 4, weight_fem_pos_deviation_);
    columns[col].emplace_back(
        col - 2, -2.0 * weight_fem_pos_deviation_ - weight_path_length_);
    columns[col].emplace_back(col, weight_fem_pos_deviation_ +
                                       weight_path_length_ +
                                       weight_ref_deviation_);
    ++col_num;
  }

  CHECK_EQ(col_num, num_of_variables_);

  int ind_p = 0;
  for (int i = 0; i < col_num; ++i) {
    P_indptr->push_back(ind_p);
    for (const auto& row_data_pair : columns[i]) {
      // Rescale by 2.0 as the quadratic term in osqp default qp problem setup
      // is set as (1/2) * x' * P * x
      P_data->push_back(row_data_pair.second * 2.0);
      P_indices->push_back(row_data_pair.first);
      ++ind_p;
    }
  }
  P_indptr->push_back(ind_p);
}
void FemPosDeviationOsqpInterface::CalculateOffset(std::vector<c_float>* q) {
  for (int i = 0; i < num_of_points_; ++i) {
    const auto& ref_point_xy = ref_points_[i];
    q->push_back(-2.0 * weight_ref_deviation_ * ref_point_xy.first);
    q->push_back(-2.0 * weight_ref_deviation_ * ref_point_xy.second);
  }
}

对约束的代码实现:

void FemPosDeviationOsqpInterface::CalculateAffineConstraint(
    std::vector<c_float>* A_data, std::vector<c_int>* A_indices,
    std::vector<c_int>* A_indptr, std::vector<c_float>* lower_bounds,
    std::vector<c_float>* upper_bounds) {
  int ind_A = 0;
  for (int i = 0; i < num_of_variables_; ++i) {
    A_data->push_back(1.0);
    A_indices->push_back(i);
    A_indptr->push_back(ind_A);
    ++ind_A;
  }
  A_indptr->push_back(ind_A);

  for (int i = 0; i < num_of_points_; ++i) {
    const auto& ref_point_xy = ref_points_[i];
    upper_bounds->push_back(ref_point_xy.first + bounds_around_refs_[i]);
    upper_bounds->push_back(ref_point_xy.second + bounds_around_refs_[i]);
    lower_bounds->push_back(ref_point_xy.first - bounds_around_refs_[i]);
    lower_bounds->push_back(ref_point_xy.second - bounds_around_refs_[i]);
  }
}

OSQP还需要设定迭代初值,设定迭代初值为原始参考点坐标

void FemPosDeviationOsqpInterface::SetPrimalWarmStart(
    std::vector<c_float>* primal_warm_start) {
  CHECK_EQ(ref_points_.size(), static_cast<size_t>(num_of_points_));
  for (const auto& ref_point_xy : ref_points_) {
    primal_warm_start->push_back(ref_point_xy.first);
    primal_warm_start->push_back(ref_point_xy.second);
  }

将相关参数输入osqp求解器

data->n = kernel_dim;
data->m = num_affine_constraint;
data->P = csc_matrix(data->n, data->n, P_data->size(), P_data->data(),
                     P_indices->data(), P_indptr->data());
data->q = q->data();
data->A = csc_matrix(data->m, data->n, A_data->size(), A_data->data(),
                     A_indices->data(), A_indptr->data());
data->l = lower_bounds->data();
data->u = upper_bounds->data();

*work = osqp_setup(data, settings);

osqp_warm_start_x(*work, primal_warm_start->data());

// Solve Problem
osqp_solve(*work);

从work中抽取计算结果

x_.resize(num_of_points_);
y_.resize(num_of_points_);
for (int i = 0; i < num_of_points_; ++i) {
	int index = i * 2;
	 x_.at(i) = work->solution->x[index];
	 y_.at(i) = work->solution->x[index + 1];
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序开发系列(二十六)·小程序运行机制(启动、前后台状态、挂起、销毁)和小程序更新机制

目录 1. 小程序运行机制 1.1 启动 1.2 前台和后台状态 1.3 挂起 1.4 销毁 2. 小程序更新机制 1. 小程序运行机制 1.1 启动 小程序启动可以分为两种情况&#xff0c;一种是冷启动&#xff0c;一种是热启动。 冷启动&#xff1a;如果用户首次打开&#xff0c;或小…

基于Gui Guider进行LVGL的页面绘制和移植

在之前的文章里讲过一种页面切换的方式&#xff0c;那就是&#xff1a;定义和创建页面对象绘制页面内容切换页面。参考这篇文章&#xff1a; LVGL如何创建页面并实现页面的切换-CSDN博客 这篇文章讲了如何绘制并切换页面。 但是现在遇到一个问题&#xff0c;那就是页面绘制&…

力扣爆刷第93天之hot100五连刷51-55

力扣爆刷第93天之hot100五连刷51-55 文章目录 力扣爆刷第93天之hot100五连刷51-55一、200. 岛屿数量二、994. 腐烂的橘子三、207. 课程表四、208. 实现 Trie (前缀树)五、46. 全排列 一、200. 岛屿数量 题目链接&#xff1a;https://leetcode.cn/problems/number-of-islands/d…

php7.3.4连接sqlserver(windows平台)

前言 有个项目需要手上laravel连接客户的sqlserver数据库读取数据&#xff0c;故在本地开发的lnmp环境中&#xff0c;php需要增加扩展 过程 从微软官网下载sqlsrv扩展,注意注意php版本&#xff0c;下载地址 解压的文件会有nts和ts两个版本&#xff0c;本地打开phpinfo查看 将…

Claude3 正式发布,支持多模态(附注册使用教程)

免费使用教程请看到最后&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; AnthropicAI 官推发布消息&#xff0c;正式推出Claude 3&#xff0c;沉寂了很久的Anthropic 终于亮剑放了大招。Claude 3 系列模型&#xff0c;包括Claude 3 Opus、Claude 3 Sonnet 和 C…

耐腐蚀PFA气体洗涤瓶可多级串联透明特氟龙塑料氢气吸收装置

洗气瓶是一种常用于净化和干燥各种气体的实验室器皿&#xff0c;以去除其中的水分、油脂、颗粒物等杂质&#xff0c;从而使需要用到的气体满足实验要求。 PFA洗气瓶的工作原理&#xff1a; 主要是通过液体吸收、溶解或发生化学反应来去除气体中的杂质。在洗气过程中&#xff…

优思学院|为什么企业要做质量管理体系认证?

在二战后的美国&#xff0c;公司对自己的产品质量颇为自满。市场需求旺盛&#xff0c;产品销售状况良好&#xff0c;即便产品存在质量缺陷&#xff0c;消费者似乎也能接受。这种态度导致了一种现象&#xff1a;即使在生产结束时发现了一定比例的缺陷&#xff0c;公司也能通过加…

Day39-2-Rsync企业级备份工具讲解

Day39-2-Rsync企业级备份工具讲解 1. 什么是rsync?2. 什么是全量和增量&#xff1f;3. 为什么要用rsync&#xff1f;4. rsync功能特性5. 增量复制原理6. rsync三种工作模式介绍6.1 本地&#xff08;local&#xff09;6.2 远程Shell模式6.2.1 远程Shell模式企业场景和实践&…

vue.js 页面中设置多个swiper

效果&#xff1a; 设置主要设置了 动态的 包含类、 左右按钮的类 <template><div class"swiper-container_other"><!-- 右侧按钮 --><div :class"[(id)?swiper-button-nextid:swiper-button-next, swiper-button-next]"></div…

每日一题 第一期 洛谷 铺地毯

[NOIP2011 提高组] 铺地毯 https://www.luogu.com.cn/problem/P1003 题目描述 为了准备一个独特的颁奖典礼&#xff0c;组织者在会场的一片矩形区域&#xff08;可看做是平面直角坐标系的第一象限&#xff09;铺上一些矩形地毯。一共有 n n n 张地毯&#xff0c;编号从 1 …

探秘C语言扫雷游戏实现技巧

本篇博客会讲解&#xff0c;如何使用C语言实现扫雷小游戏。 0.思路及准备工作 使用2个二维数组mine和show&#xff0c;分别来存储雷的位置信息和排查出来的雷的信息&#xff0c;前者隐藏&#xff0c;后者展示给玩家。假设盘面大小是99&#xff0c;这2个二维数组都要开大一圈…

北京公司注册地址想要迁到新疆该如何操作

尊敬的客户&#xff0c;您好&#xff01;我是经典世纪胡云帅&#xff08;游览器搜经典世纪胡云帅&#xff09;&#xff0c;您选择了北京经典世纪集团有限公司-资 质代办&#xff0c;我们将竭诚为您服务&#xff01;如果您的公司注册地址想要迁到新疆&#xff0c;这里有一些重要…

SSM整合和实战练习笔记1

SSM整合和实战练习1 SSM整合和实战练习springmvc配置业务层 service aop tx的配置mybatis整合配置&#xff08;方式2容器初始化配置类访问测试mapper层service层controller层前端程序搭建 SSM整合和实战练习 springmvc配置 业务层 service aop tx的配置 mybatis整合配置&#…

亲测抖音小程序备案流程,抖音小程序如何备案,抖音小程序备案所需准备资料

抖音小程序为什么要备案&#xff0c;抖音官方给出如下说明&#xff1a; 1、2024年3月15日后提交备案的小程序将不保证2024年3月31日前平台可初审通过&#xff1b; 2、2024年3月31日后未完成备案小程序将被下架处理。 一&#xff0c;备案前需准备资料 &#xff08;一&#xff0…

bpmn-js系列之Palette

前边写了四篇文章介绍了bpmn.js的基本使用&#xff0c;最近陆续有小伙伴加我催更&#xff0c;感谢对我这个半吊子前端的信任&#xff0c;接着更新bpmn.js的一些高级用法&#xff0c;本篇介绍对左侧工具栏Palette的隐藏和自定义修改 隐藏shape 左侧工具栏Palette有些图标我用不…

【数据挖掘】实验2:R入门2

实验2&#xff1a;R入门2 一&#xff1a;实验目的与要求 1&#xff1a;熟悉和掌握R数据类型。 2&#xff1a;熟悉和掌握R语言的数据读写。 二&#xff1a;实验内容 1&#xff1a;R数据类型 【基本赋值】 Eg.1代码&#xff1a; x <- 8 x Eg.2代码&#xff1a; a city …

碳实践 | 基于“界、源、算、质、查”五步法,实现企业组织碳核算

碳排放核算是夯实碳排放统计的基础&#xff0c;提高碳排放数据质量的关键&#xff0c;同时&#xff0c;将推动能耗“双控”向碳排放“双控”转变。总体来看&#xff0c;碳核算分为区域层面、组织层面和产品层面的碳核算&#xff0c;这三个层面的意义和计算方法完全不同。本文将…

【高通camera hal bug分析】高通自带相机镜像问题

首先打了两个log&#xff0c;一个是开启镜像的log&#xff0c;还有一个是没有开启镜像的log&#xff0c;如果我们开启镜像以后&#xff0c;观察开启镜像log发现 , 这段代码走的没有任何问题&#xff0c;因为Flip的值等于1了。 关闭镜像log如下&#xff1a; 如果我们不开启镜像…

<机器学习初识>——《机器学习》

目录 一、人工智能概述 1 人工智能应用场景 2 人工智能发展必备三要素 3 人工智能、机器学习和深度学习 二、人工智能发展历程 1 人工智能的起源 1.1 图灵测试 1.2 达特茅斯会议 2 发展历程 三、 人工智能主要分支 1 主要分支介绍 1.1 分支一&#xff1a;计算机视觉…

STM32点亮LED灯与蜂鸣器发声

STM32之GPIO GPIO在输出模式时可以控制端口输出高低电平&#xff0c;用以驱动Led蜂鸣器等外设&#xff0c;以及模拟通信协议输出时序等。 输入模式时可以读取端口的高低电平或电压&#xff0c;用于读取按键输入&#xff0c;外接模块电平信号输入&#xff0c;ADC电压采集灯 GP…