无人机避障——(运动规划部分)深蓝学院动力学kinodynamic A* 3D算法理论解读(附C++代码)
开源代码链接:GitHub - Perishell/motion-planning
效果展示:
ROS 节点展示全局规划和轨迹生成部分:
Kinodynamic A*代码主体:
int KinoAstar::search(Eigen::Vector3d start_pt, Eigen::Vector3d start_vel,Eigen::Vector3d end_pt, Eigen::Vector3d end_vel,std::vector<Eigen::Vector3d>& path)
{// 记录算法启动时间,计算加速度分辨率倒数,初始化最优时间为无穷大ros::Time start_time = ros::Time::now();double inv_acc_res_ = 1.0 / acc_res_;double optimal_time = inf;// 从节点池中分配起点节点,设置位置、速度、索引、实际代价g_cost(起点到当前节点的累积代价)和综合代价f_cost(g_cost + 启发式代价)KinoAstarNodePtr start_node = path_node_pool_[use_node_num_];start_node->position = start_pt;start_node->velocity = start_vel;start_node->index = posToIndex(start_pt);start_node->g_cost = 0.0;start_node->f_cost = lambda_heu_ * getHeuristicCost(start_pt, start_vel, end_pt, end_vel, optimal_time);use_node_num_++;// 将起点加入优先队列open_list_(按f_cost排序),并标记为“待扩展”open_list_.push(start_node);expanded_list_.insert(start_node->index, start_node);start_node->node_state = IN_OPEN_LIST_;std::vector<Eigen::Vector3d> path_nodes_list;while (!open_list_.empty()){// 循环处理开放列表,取出当前最优节点,移入关闭列表close_list_,标记为“已扩展”KinoAstarNodePtr current_node = open_list_.top();open_list_.pop();close_list_.insert(current_node->index, current_node);current_node->node_state = IN_CLOSE_LIST_;current_node->duration = sample_tau_;// debug// std::cout << "current_node: " << current_node->position.transpose() << std::endl;// check if near goal// 若当前节点接近终点,尝试直接生成无碰撞的最优轨迹(如多项式插值或动力学可行路径)if ((current_node->position - end_pt).norm() < goal_tolerance_){double tmp_cost = lambda_heu_ * getHeuristicCost(current_node->position, current_node->velocity, end_pt, end_vel, optimal_time);bool shot_path_found = computeShotTraj(current_node->position, current_node->velocity, end_pt, end_vel, optimal_time);// std::cout << "optimal_time: " << optimal_time << std::endl;if (shot_path_found){ros::Time end_time = ros::Time::now();std::cout << "kinodynamic path found, time cost: " << (end_time - start_time).toSec() << std::endl;std::cout << "use_node_num: " << use_node_num_ << std::endl;std::cout << "total_cost_J: " << current_node->g_cost + tmp_cost << std::endl;current_node->duration = optimal_time;std::vector<KinoAstarNodePtr> path_pool = retrievePath(current_node, path_nodes_list);path_nodes_list.push_back(end_pt);visPathNodes(path_nodes_list);switch (collision_check_type_){case 1: // grid map check{samplePath(path_pool, path);break;}case 2: // local cloud check{sampleEllipsoid(path_pool, path, rot_list);visEllipsoid(path, rot_list);break;} }return REACH_END;}else if (current_node->parent != NULL){// std::cout << "near end!" << std::endl;}else{std::cout << "no shot path found, end searching" << std::endl;return NO_PATH_FOUND; }// else continue; }// expand current node// (2r+1)^3 samplefor (double ax = -max_accel_; ax <= max_accel_ + 1e-3; ax += inv_acc_res_ * max_accel_)for (double ay = -max_accel_; ay <= max_accel_ + 1e-3; ay += inv_acc_res_ * max_accel_)for (double az = -max_accel_; az <= max_accel_ + 1e-3; az += inv_acc_res_ * max_accel_){Eigen::Vector3d ut;ut << ax, ay, az;Eigen::Matrix<double, 6, 1> x0;x0.head(3) = current_node->position;x0.tail(3) = current_node->velocity;Eigen::Matrix<double, 6, 1> xt;int segment_num = std::floor(sample_tau_ / step_size_);bool flag = false;bool collision_flag = false;for (int i = 0; i <= segment_num; i++){double t = i * step_size_;StateTransit(x0, xt, ut, t);Eigen::Vector3d tmp_pos = xt.head(3);// check collision and if out of mapif (grid_map_->isInMap(tmp_pos) == false){flag = true;break;}// check collisionswitch (collision_check_type_){case 1: // grid map checkif (grid_map_->getInflateOccupancy(tmp_pos) == 1){collision_flag = true;break;}case 2: // local cloud checkif (isCollisionFree(tmp_pos, ut) == false){collision_flag = true;break;}}if (collision_flag){flag = true;break;}// check velocity limitif (xt.tail(3)(0) < -max_vel_ || xt.tail(3)(0) > max_vel_ || xt.tail(3)(1) < -max_vel_ || xt.tail(3)(1) > max_vel_ || xt.tail(3)(2) < -max_vel_ || xt.tail(3)(2) > max_vel_){flag = true;break;}}if (flag) continue;StateTransit(x0, xt, ut, sample_tau_);// std::cout << "xt: " << xt.head(3).transpose() << std::endl;// std::cout << "index: " << posToIndex(xt.head(3)).transpose() << std::endl;// check if in close_list_if (close_list_.find(posToIndex(xt.head(3))) != NULL){continue;}// check if in expanded_list_else if (expanded_list_.find(posToIndex(xt.head(3))) == NULL){KinoAstarNodePtr pro_node = path_node_pool_[use_node_num_];pro_node->position = xt.head(3);pro_node->velocity = xt.tail(3);pro_node->index = posToIndex(xt.head(3));pro_node->g_cost = current_node->g_cost + (ut.dot(ut) + rou_) * sample_tau_;pro_node->f_cost = pro_node->g_cost + lambda_heu_ * getHeuristicCost(pro_node->position, pro_node->velocity, end_pt, end_vel, optimal_time);pro_node->parent = current_node;pro_node->input = ut;pro_node->duration = sample_tau_;pro_node->node_state = IN_OPEN_LIST_;use_node_num_++;open_list_.push(pro_node);expanded_list_.insert(pro_node->index, pro_node);// check if use_node_num reach the max_node_numif (use_node_num_ >= allocated_node_num_){std::cout << "reach max node num, end searching" << std::endl;return NO_PATH_FOUND;}}else{// pruning, if in same grid, check if g_cost is smaller and updatedouble tmp_g_cost = current_node->g_cost + (ut.dot(ut) + rou_) * sample_tau_;KinoAstarNodePtr old_node = expanded_list_.find(posToIndex(xt.head(3)));if (tmp_g_cost < old_node->g_cost){old_node->position = xt.head(3);old_node->velocity = xt.tail(3);// old_node->index = posToIndex(xt.head(3));old_node->g_cost = tmp_g_cost;old_node->f_cost = old_node->g_cost + lambda_heu_ * getHeuristicCost(old_node->position, old_node->velocity, end_pt, end_vel, optimal_time);old_node->parent = current_node;old_node->input = ut;}}}}std::cout << "open_list is empty, end searching, no path found" << std::endl;return NO_PATH_FOUND;
}
初始化阶段:
ros::Time start_time = ros::Time::now();
double inv_acc_res_ = 1.0 / acc_res_;
double optimal_time = inf;
记录算法启动时间,计算加速度分辨率倒数,初始化最优时间为无穷大。
起点节点初始化:
KinoAstarNodePtr start_node = path_node_pool_[use_node_num_];
start_node->position = start_pt;
start_node->velocity = start_vel;
start_node->index = posToIndex(start_pt);
start_node->g_cost = 0.0;
start_node->f_cost = lambda_heu_ * getHeuristicCost(...);
use_node_num_++;
从节点池中分配起点节点,设置位置、速度、索引、实际代价g_cost
(起点到当前节点的累积代价)和综合代价f_cost
(g_cost + 启发式代价
)。
开放列表与扩展列表管理:
open_list_.push(start_node);
expanded_list_.insert(start_node->index, start_node);
start_node->node_state = IN_OPEN_LIST_;
将起点加入优先队列open_list_
(按f_cost
排序),并标记为“待扩展”。
主搜索循环:
while (!open_list_.empty()) {
KinoAstarNodePtr current_node = open_list_.top();
open_list_.pop();
close_list_.insert(current_node->index, current_node);
current_node->node_state = IN_CLOSE_LIST_;
循环处理开放列表,取出当前最优节点,移入关闭列表close_list_
,标记为“已扩展”。
终点检查与轨迹优化:
if ((current_node->position - end_pt).norm() < goal_tolerance_) {bool shot_path_found = computeShotTraj(...);if (shot_path_found) {samplePath(path_pool, path);return REACH_END;}
}
若当前节点接近终点,尝试直接生成无碰撞的最优轨迹(如多项式插值或动力学可行路径)。
samplePath:
主体部分:
void KinoAstar::samplePath(std::vector<KinoAstarNodePtr> path_pool, std::vector<Eigen::Vector3d>& path)
{// 当路径池中有多个节点时(非单节点情况)if (path_pool.size() != 1){// 遍历路径池中相邻的节点对for (int i = 0; i < path_pool.size() - 1; i++){KinoAstarNodePtr curr_node = path_pool[i]; // 当前路径节点KinoAstarNodePtr next_node = path_pool[i + 1]; // 下一路径节点double curr_t = 0.0; // 当前时间戳Eigen::Matrix<double, 6, 1> x0, xt; // 状态向量(6维:位置+速度)x0 << curr_node->position, curr_node->velocity; // 初始化当前状态// 根据节点持续时间分割时间段(基于固定步长step_size_)int segment_num = std::floor(curr_node->duration / step_size_);// 逐时间段生成路径点for (int j = 0; j < segment_num; j++){curr_t = j * step_size_; // 计算当前时间StateTransit(x0, xt, next_node->input, curr_t); // 状态转移计算(可能涉及运动学方程)[1,5](@ref)path.push_back(xt.head(3)); // 将位置信息(x,y,z)加入路径集合}// 处理最后一个时间段(可能不完整的步长)StateTransit(x0, xt, next_node->input, curr_node->duration);// 验证最终状态与目标节点的位置一致性if ((xt.head(3) - next_node->position).norm() > 1e-2){std::cerr << "error in sample!" << std::endl; // 超阈值报错}}// 处理路径池中最后一个节点的轨迹KinoAstarNodePtr last_node = path_pool.back();double td = last_node->duration; // 获取总持续时间Eigen::Matrix<double, 4, 1> t_vector; // 时间多项式向量(t^0, t^1, t^2, t^3)// 分割时间段生成路径点int segment_num = std::floor(td / step_size_);double curr_t = 0.0;for (int j = 0; j <= segment_num; j++){curr_t = j * step_size_; // 当前时间戳for (int i = 0; i < 4; i++) {t_vector(i) = pow(curr_t, i); // 构建时间多项式项(如t³, t², t, 1)}Eigen::Vector3d shot_pos = shot_coef_ * t_vector; // 通过多项式系数生成路径点(轨迹插值)[5](@ref)path.push_back(shot_pos); // 将插值点加入路径}}else // 当路径池只有单个节点时的处理逻辑{KinoAstarNodePtr last_node = path_pool.back();double td = last_node->duration; // 总持续时间Eigen::Matrix<double, 4, 1> t_vector; // 同上时间多项式向量int segment_num = std::floor(td / step_size_);double curr_t = 0.0;for (int j = 0; j <= segment_num; j++) {curr_t = j * step_size_;for (int i = 0; i < 4; i++) {t_vector(i) = pow(curr_t, i);}Eigen::Vector3d shot_pos = shot_coef_ * t_vector; // 多项式轨迹生成path.push_back(shot_pos);}}
}
该函数主要用于:
- 路径采样:根据路径池(
path_pool
)中的节点,通过时间分片(step_size_
)生成连续路径点。 - 状态转移:在多节点情况下,通过
StateTransit
函数计算相邻节点间的运动状态(可能包含速度/加速度模型) - 轨迹插值:在单节点或最终阶段,利用四次多项式系数(
shot_coef_
)生成平滑轨迹,适用于机器人/自动驾驶的运动规划 - 错误校验:检查采样结果与目标节点的位置偏差,确保路径精度。
getHeuristicCost:
主体部分:
double KinoAstar::getHeuristicCost(Eigen::Vector3d x1, Eigen::Vector3d v1,Eigen::Vector3d x2, Eigen::Vector3d v2,double &optimal_time)
{Eigen::Vector3d dp = x2 - x1;double optimal_cost = inf;double a = -36 * dp.dot(dp);double b = 24 * dp.dot(v1 + v2);double c = -4 * (v1.dot(v1) + v1.dot(v2) + v2.dot(v2));double d = 0;double e = rou_;std::vector<double> dts = quartic(e, d, c, b, a);double T_bar = ((x1 - x2).lpNorm<Eigen::Infinity>() / max_vel_);for (int i = 0; i < dts.size(); i++){double t = dts[i];double tmp_cost = a / (-3 * t * t * t) + b / (-2 * t * t) + c / (-1 * t) + e * t;if (tmp_cost < optimal_cost && t > T_bar && tmp_cost > 0){optimal_cost = tmp_cost;optimal_time = t;}}return tie_breaker_ * optimal_cost;}
KinoAstar::getHeuristicCost
是运动规划中结合动力学约束的启发式函数,其核心目标是通过最优控制理论计算从当前状态到目标状态的最小控制能量代价,用于引导A*算法的搜索方向并加速收敛。以下是逐层解析:
函数参数与目标:
- 输入参数:
x1
/v1
:起点位置和速度x2
/v2
:终点位置和速度optimal_time
(输出):最优时间估计
- 输出:启发式代价(预估最小控制能量)
数学原理:最优控制问题建模:
该函数基于庞特里亚金极小值原理(Pontryagin's Minimum Principle),求解两点边界最优控制问题(OBVP)。目标是最小化控制能量(如加加速度的积分),同时满足位置和速度的终点约束。
其中:
- j(t) 是加加速度(控制输入)
- ρ 是时间权重(对应代码中的
rou_
)
通过状态方程和协态方程推导,最终得到关于时间 T 的四次多项式方程,其根对应可能的极值点。
代码实现步骤:
-
构造四次方程系数:
- 计算位置差
dp = x2 - x1
- 根据几何关系和控制能量积分,生成系数
a, b, c, d, e
(对应四次方程 aT^4+bT^3+cT^2+d^T+e=0)
- 计算位置差
-
求解方程根:
- 调用
quartic(e, d, c, b, a)
解四次方程,得到候选时间解dts
。
- 调用
-
筛选最优时间与代价:
- 计算时间下限
T_bar
(确保速度不超过最大值max_vel_
) - 遍历所有候选时间
t
,计算对应的能量代价tmp_cost
,筛选满足 t>T_bar 的最小代价optimal_cost
及对应时间optimal_time
。
- 计算时间下限
-
返回加权代价:
- 最终代价乘以
tie_breaker_
(通常略大于1的系数,避免路径重复扩展)
- 最终代价乘以
computeShotTraj:
主体部分:
bool KinoAstar::computeShotTraj(Eigen::Vector3d x1, Eigen::Vector3d v1,Eigen::Vector3d x2, Eigen::Vector3d v2,double optimal_time)
{double td = optimal_time;Eigen::Vector3d dp = x2 - x1;Eigen::Vector3d dv = v2 - v1;shot_coef_.col(0) = x1;shot_coef_.col(1) = v1;shot_coef_.col(2) = 0.5 * (6 / (td * td) * (dp - v1 * td) - 2 * dv / td);shot_coef_.col(3) = 1.0 / 6.0 * (-12 / (td * td *td) * (dp - v1 * td) + 6 * dv / (td * td));Eigen::Matrix<double, 4, 4> Transit_v;Transit_v << 0, 0, 0, 0, 1, 0, 0, 0,0, 2, 0, 0, 0, 0, 3, 0;Eigen::Matrix<double, 4, 4> Transit_a;Transit_a << 0, 0, 0, 0, 1, 0, 0, 0,0, 2, 0, 0, 0, 0, 0, 0;vel_coef_ = shot_coef_ * Transit_v;acc_coef_ = vel_coef_ * Transit_a;Eigen::Matrix<double, 4, 1> t_vector;int segment_num = std::floor(td / step_size_);double curr_t = 0.0;for (int j = 0; j <= segment_num; j++){curr_t = j * step_size_;for (int i = 0; i < 4; i++){t_vector(i) = pow(curr_t, i);}Eigen::Vector3d shot_pos = shot_coef_ * t_vector;Eigen::Vector3d shot_vel = vel_coef_ * t_vector;Eigen::Vector3d shot_acc = acc_coef_ * t_vector;// check collisionif (grid_map_->getInflateOccupancy(shot_pos)){return false;}// only check collision, vel and acc limit by limit T_// // check velocity limit// if (shot_vel(0) > max_vel_ || shot_vel(0) < -max_vel_ || // shot_vel(1) > max_vel_ || shot_vel(1) < -max_vel_ || // shot_vel(2) > max_vel_ || shot_vel(2) < -max_vel_)// {// return false;// }// // check acceleration limit// if (shot_acc(0) > max_accel_ || shot_acc(0) < -max_accel_ || // shot_acc(1) > max_accel_ || shot_acc(1) < -max_accel_ || // shot_acc(2) > max_accel_ || shot_acc(2) < -max_accel_)// {// return false;// }}return true;
}
定义了一个函数computeShotTraj
,属于类KinoAstar:
bool KinoAstar::computeShotTraj(Eigen::Vector3d x1, Eigen::Vector3d v1,Eigen::Vector3d x2, Eigen::Vector3d v2,double optimal_time)
该函数用于计算一步到位的航迹,参数包括初始位置x1
、初始速度v1
、目标位置x2
、目标速度v2
,以及最优时间optimal_time
,返回值为布尔类型。
double td = optimal_time;
将optimal_time
赋值给变量td
,表示轨迹的时间长度。
Eigen::Vector3d dp = x2 - x1;
Eigen::Vector3d dv = v2 - v1;
计算位置差和速度差。
shot_coef_.col(0) = x1;
shot_coef_.col(1) = v1;
shot_coef_.col(2) = 0.5 * (6 / (td * td) * (dp - v1 * td) - 2 * dv / td);
shot_coef_.col(3) = 1.0 / 6.0 * (-12 / (td * td *td) * (dp - v1 * td) + 6 * dv / (td * td));
将初始位置x1
和初始速度v1
赋值给shot_coef_
的第一列和第二列,shot_coef_
是一个用于存储三次多项式系数的矩阵。计算三次多项式的第二项系数,涉及位置差dp
、初始速度v1
、速度差dv
,以及时间td
的计算。
Eigen::Matrix<double, 4, 4> Transit_v;Transit_v << 0, 0, 0, 0, 1, 0, 0, 0,0, 2, 0, 0, 0, 0, 3, 0;Eigen::Matrix<double, 4, 4> Transit_a;Transit_a << 0, 0, 0, 0, 1, 0, 0, 0,0, 2, 0, 0, 0, 0, 0, 0;
定义一个4x4
的矩阵Transit_v
,用于将位置系数转换为速度系数。矩阵的结构是根据速度的导数特性定义的。定义一个4x4
的矩阵Transit_a
,用于将速度系数转换为加速度系数。矩阵的结构是根据加速度的导数特性定义的。
vel_coef_ = shot_coef_ * Transit_v;
acc_coef_ = vel_coef_ * Transit_a;
通过shot_coef_
和Transit_v
的矩阵乘法,计算出速度系数矩阵vel_coef_
。 通过vel_coef_
和Transit_a
的矩阵乘法,计算出加速度系数矩阵acc_coef_
。
Eigen::Matrix<double, 4, 1> t_vector;
int segment_num = std::floor(td / step_size_);
double curr_t = 0.0;
for (int j = 0; j <= segment_num; j++){curr_t = j * step_size_;for (int i = 0; i < 4; i++){t_vector(i) = pow(curr_t, i);}Eigen::Vector3d shot_pos = shot_coef_ * t_vector;Eigen::Vector3d shot_vel = vel_coef_ * t_vector;Eigen::Vector3d shot_acc = acc_coef_ * t_vector;// check collisionif (grid_map_->getInflateOccupancy(shot_pos)){return false;}
定义一个4x1
的矩阵t_vector
,用于存储时间的幂次计算。计算时间分割数segment_num
,即时间td
除以步长step_size_
并向下取整。循环遍历时间分割点,从0到segment_num
。计算当前时间curr_t
,即j
乘以步长step_size_
。通过shot_coef_
和t_vector
的矩阵乘法,计算出当前位置shot_pos
。通过vel_coef_
和t_vector
的矩阵乘法,计算出当前速度shot_vel
。通过acc_coef_
和t_vector
的矩阵乘法,计算出当前加速度shot_acc
。检查当前位置shot_pos
是否与网格地图中的膨胀障碍物发生碰撞。如果发生碰撞,返回false
,表示轨迹不可行。
retrievePath:
主体部分:
std::vector<KinoAstarNodePtr> KinoAstar::retrievePath(KinoAstarNodePtr end_node, std::vector<Eigen::Vector3d>& path_nodes_list)
{KinoAstarNodePtr current_node = end_node;std::vector<KinoAstarNodePtr> path_nodes;while (current_node->parent != NULL){path_nodes.push_back(current_node);path_nodes_list.push_back(current_node->position);current_node = current_node->parent;}path_nodes.push_back(current_node);path_nodes_list.push_back(current_node->position);std::reverse(path_nodes.begin(), path_nodes.end());std::reverse(path_nodes_list.begin(), path_nodes_list.end());return path_nodes;
}
std::vector<KinoAstarNodePtr> path_pool = retrievePath(current_node, path_nodes_list);
这段代码定义了一个函数 retrievePath
,属于类 KinoAstar
,用于从终点节点回溯并提取出路径上的所有节点以及它们的位置,形成完整的路径。
- 路径回溯:从终点节点开始,通过每个节点的
parent
指针逐级向上访问,直到找到起点(即parent
为NULL
的节点)。 - 路径构建:在回溯过程中,将每个节点及其位置分别添加到
path_nodes
和path_nodes_list
中。 - 顺序调整:由于回溯得到的路径是从终点到起点的顺序,使用
std::reverse
将两个向量反转,最终得到从起点到终点的正确路径顺序。
节点扩展与状态转移:
for (double ax = -max_accel_; ...) {Eigen::Vector3d ut(ax, ay, az);StateTransit(x0, xt, ut, sample_tau_);// 碰撞检测与速度限制检查
}
功能:遍历所有可能的加速度控制输入(三维离散化),生成子节点状态
- 关键点:
- 状态转移方程:
StateTransit
根据动力学模型(如匀加速运动学)计算下一时刻状态 - 碰撞检测:分段检查轨迹段是否在自由空间内(
grid_map_
或点云检测)
- 状态转移方程:
StateTransit:
主体部分:
void KinoAstar::StateTransit(Eigen::Matrix<double, 6, 1> &x0, Eigen::Matrix<double, 6, 1> &xt,Eigen::Vector3d ut, double t)
{// 初始化6x6状态转移矩阵e_At为单位矩阵(初始状态下时间t=0时的状态转移)Eigen::Matrix<double, 6, 6> e_At = Eigen::Matrix<double, 6, 6>::Identity();// 前三行的位置-速度关系处理:位置 += 速度 * 时间t(例如x = x0 + vx*t)for (int i = 0; i < 3; i++) {e_At(i, 3 + i) = t; // 例如e_At矩阵第0行第3列设为t,对应x方向的位置增量}// 初始化6x3的积分矩阵(用于控制输入ut对状态的影响)Eigen::Matrix<double, 6, 3> Integral = Eigen::Matrix<double, 6, 3>::Zero();// 填充积分矩阵:// 前三行对角线元素为0.5*t²(加速度对位置的影响,如Δx = 0.5*a*t²)// 后三行对角线元素为t(加速度对速度的影响,如Δv = a*t)for (int i = 0; i < 6; i++) {if (i < 3) Integral(i, i) = 0.5 * t * t; // 位置积分项else Integral(i, i - 3) = t; // 速度积分项}// 状态转移方程:xt = e_At * x0 + Integral * ut// 其中:// - e_At * x0 表示初始状态的线性传播(位置+速度)// - Integral * ut 表示控制输入(如加速度)对状态的影响xt = e_At * x0 + Integral * ut;
}
功能总结:
-
状态转移模型
基于线性运动学模型,通过矩阵运算实现状态预测。假设系统为二阶模型(包含位置和速度),控制输入ut
可能对应加速度 -
矩阵作用
- e_At:6x6矩阵,描述位置和速度随时间变化的线性关系(如匀速运动模型)。
- Integral:6x3矩阵,计算控制输入对状态的积分贡献(如匀加速运动中的位移和速度变化)
-
物理意义
假设系统状态为[x, y, z, vx, vy, vz]
,控制输入为加速度[ax, ay, az]
,则:- 位置更新:
x_new = x0 + vx*t + 0.5*ax*t²
- 速度更新:
vx_new = vx + ax*t
- 位置更新:
相关文章:
无人机避障——(运动规划部分)深蓝学院动力学kinodynamic A* 3D算法理论解读(附C++代码)
开源代码链接:GitHub - Perishell/motion-planning 效果展示: ROS 节点展示全局规划和轨迹生成部分: Kinodynamic A*代码主体: int KinoAstar::search(Eigen::Vector3d start_pt, Eigen::Vector3d start_vel,Eigen::Vector3d en…...
电脑声音小怎么调大 查看声音调整方法
电脑是我们工作学习经常需要用到的工具,同时电脑也可以播放音乐、视频、游戏等,享受声音的效果。但是,有些电脑的声音很小,即使把音量调到最大,也听不清楚,这让我们很苦恼。那么,电脑声音小怎么…...
无人机信号监测系统技术解析
一、模块技术要点 1. 天线阵列与信号接收模块 多频段自适应切换:采用天线阵列模块,根据复杂地形和不同频段自动切换合适的天线,提升信号接收灵敏度。 双天线测向技术:通过双天线的RSSI(信号接收强度)差值…...
Excel的详细使用指南
### **一、Excel基础操作** #### **1. 界面与基本概念** - **工作簿(Workbook)**:一个Excel文件(扩展名.xlsx)。 - **工作表(Worksheet)**:工作簿中的单个表格(默认名…...
基于SSM实现的健身房系统功能实现十六
一、前言介绍: 1.1 项目摘要 随着社会的快速发展和人们健康意识的不断提升,健身行业也在迅速扩展。越来越多的人加入到健身行列,健身房的数量也在不断增加。这种趋势使得健身房的管理变得越来越复杂,传统的手工或部分自动化的管…...
序列化和反序列化(hadoop)
1.先将上一个博客的Student复制粘贴后面加上H 在StudentH中敲下面代码 package com.example.sei; import org.apache.hadoop.io.Writable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; //学生类,姓名,年龄 //支…...
大模型MCP_MCP从流式SSE到流式HTTP_1.8.0支持流式HTTP交互_介绍_从应用到最优--人工智能工作笔记0245
从最开始的大模型时代,到现在MCP,大模型技术,人工智能技术迭代真的非常快 之前的大模型更像一个大脑,能帮大家出点子,然后告诉你思路,你去解决问题,但是 一直不能自己解决问题,后来出来了通用的manus智能体,声称可以解决很多问题.直接操作 一个自带的电脑,但是也有局限性,还…...
docker大镜像优化实战
在 Docker 镜像优化方面,有许多实战技巧可以显著减小镜像体积、提高构建效率和运行时性能。以下是一些实用的优化策略和具体操作方法: 1. 选择合适的基础镜像 策略 使用 Alpine 版本:Alpine 镜像通常只有 5-10MB,比 Ubuntu/Deb…...
【25软考网工】第六章(5)应用层安全协议
博客主页:christine-rr-CSDN博客 专栏主页:软考中级网络工程师笔记 大家好,我是christine-rr !目前《软考中级网络工程师》专栏已经更新三十篇文章了,每篇笔记都包含详细的知识点,希望能帮助到你ÿ…...
RevIN(Reversible Instance Normalization)及其在时间序列中的应用
详细介绍 RevIN(Reversible Instance Normalization)及其在时间序列中的应用 1. RevIN 的定义与背景 RevIN(可逆实例归一化)是一种专门为时间序列预测设计的归一化方法,旨在处理非平稳数据(non-stationar…...
JSON 和 cJSON 库入门教程
第一部分:了解 JSON (JavaScript Object Notation) 什么是 JSON? JSON 是一种轻量级的数据交换格式。它易于人阅读和编写,同时也易于机器解析和生成。 JSON 基于 JavaScript 编程语言的一个子集,但它是一种独立于语言的文本格式…...
Unity 2D 行走动画示例工程手动构建教程-AI变成配额前端UI-完美游戏开发流程
🎮 Unity 2D 行走动画示例工程手动构建教程 ✅ 1. 新建 Unity 项目 打开 Unity Hub: 创建一个新项目,模板选择:2D Core项目名:WalkAnimationDemo ✅ 2. 创建文件夹结构 在 Assets/ 目录下新建以下文件夹:…...
[Java][Leetcode middle] 45. 跳跃游戏 II
这题没做出来,看的答案解析 可以理解为希望采用最少得跳槽次数跳到最高级别的公司。 下标i为公司本身的职级,每个公司可以提供本身等级nums[i]的职级提升。 每次从这些选择中选择自己能够达到最大职级的公司跳槽。 public int jump(int[] nums) {if(nu…...
leetcode 3335. 字符串转换后的长度 I
给你一个字符串 s 和一个整数 t,表示要执行的 转换 次数。每次 转换 需要根据以下规则替换字符串 s 中的每个字符: 如果字符是 z,则将其替换为字符串 "ab"。否则,将其替换为字母表中的下一个字符。例如,a 替…...
Leetcode 3542. Minimum Operations to Convert All Elements to Zero
Leetcode 3542. Minimum Operations to Convert All Elements to Zero 1. 解题思路2. 代码实现 题目链接:3542. Minimum Operations to Convert All Elements to Zero 1. 解题思路 这一题的处理方法其实还是挺好想明白的,其实就是从小到大依次处理各个…...
如何使用C51的Timer0实现定时功能
在C51单片机中,使用定时器0(Timer0)实现定时功能需要以下步骤: 1. 定时器基础知识 时钟源:C51的定时器时钟来源于晶振(如12MHz)。机器周期:1个机器周期 12个时钟周期(1…...
Day1 时间复杂度
一 概念 在 C 中,时间复杂度是衡量算法运行时间随输入规模增长的趋势的关键指标,用于评估算法的效率。它通过 大 O 表示法(Big O Notation) 描述,关注的是输入规模 n 趋近于无穷大时,算法时间增长的主导因…...
PostgreSQL 配置设置函数
PostgreSQL 配置设置函数 PostgreSQL 提供了一组配置设置函数(Configuration Settings Functions),用于查询和修改数据库服务器的运行时配置参数。这些函数为数据库管理员提供了动态管理数据库配置的能力,无需重启数据库服务。 …...
美学心得(第二百七十六集) 罗国正
美学心得(第二百七十六集) 罗国正 (2025年4月) 3275、人类将迎来真、善、美快速发展的时期,人‐机合一的天人合一(可简称为“天人机合一”)的境界已渐露头角,在优秀的人群中迅猛地…...
描述性统计工具 - AxureMost 落葵网
描述性统计工具是用于汇总和分析数据,以更好地了解数据特征的工具1。以下是一些常见的描述性统计工具简介: 描述性统计工具 Excel 基本统计函数:提供了丰富的函数用于计算描述性统计量。例如,AVERAGE 函数用于计算平均值…...
mybatis中${}和#{}的区别
先测试,再说结论 userService.selectStudentByClssIds(10000, "wzh or 11");List<StudentEntity> selectStudentByClssIds(Param("stuId") int stuId, Param("field") String field);<select id"selectStudentByClssI…...
【OpenCV】网络模型推理的简单流程分析(readNetFromONNX、setInput和forward等)
目录 1.模型读取(readNetFromONNX())1.1 初始化解析函数(parseOperatorSet())1.2 提取张量(getGraphTensors())1.3 节点处理(handleNode()) 2.数据准备(blobFromImage() …...
代码随想录算法训练营第三十九天
LeetCode题目: 115. 不同的子序列583. 两个字符串的删除操作72. 编辑距离 其他: 今日总结 往期打卡 115. 不同的子序列 跳转: 115. 不同的子序列 学习: 代码随想录公开讲解 问题: 给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。 测试用例保…...
InternVL3: 利用AI处理文本、图像、视频、OCR和数据分析
InternVL3推动了视觉-语言理解、推理和感知的边界。 在其前身InternVL 2.5的基础上,这个新版本引入了工具使用、GUI代理操作、3D视觉和工业图像分析方面的突破性能力。 让我们来分析一下是什么让InternVL3成为游戏规则的改变者 — 以及今天你如何开始尝试使用它。 InternVL…...
力扣刷题Day 48:盛最多水的容器(283)
1.题目描述 2.思路 学习了Krahets佬的双指针思路,初始化两个边界作为容器边界,然后逐个向数组内遍历,直到左右两指针相遇。 3.代码(Python3) class Solution:def maxArea(self, height: List[int]) -> int:left,…...
基于单应性矩阵变换的图像拼接融合
单应性矩阵变换 单应性矩阵是一个 3x3 的可逆矩阵,它描述了两个平面之间的投影变换关系。在图像领域,单应性矩阵可以将一幅图像中的点映射到另一幅图像中的对应点,前提是这两幅图像是从不同视角拍摄的同一平面场景。 常见的应用场景&#x…...
《驱动开发硬核特训 · 专题篇》:深入理解 I2C 子系统
关键词:i2c_adapter、i2c_client、i2c_driver、i2c-core、platform_driver、设备树匹配、驱动模型 本文目标:通过实际代码一步步讲清楚 I2C 子系统的结构与运行机制,让你不再混淆 platform_driver 与 i2c_driver 的职责。 🧩 一、…...
Spark缓存-cache
一、RDD持久化 1.什么时候该使用持久化(缓存) 2. RDD cache & persist 缓存 3. RDD CheckPoint 检查点 4. cache & persist & checkpoint 的特点和区别 特点 区别 二、cache & persist 的持久化级别及策略选择 Spark的几种持久化…...
tails os系统详解
一、起源与发展背景 1. 项目初衷与历史 创立时间:Tails 项目始于 2004 年,最初名为 “Anonymous Live CD”,2009 年正式更名为 “Tails”(The Amnesic Incognito Live System,“健忘的匿名实时系统”)。核…...
[洛谷刷题9]
P2376 [USACO09OCT] Allowance G(贪心) https://www.luogu.com.cn/problem/P2376 题目描述 作为创造产奶纪录的回报,Farmer John 决定开始每个星期给Bessie 一点零花钱。 FJ 有一些硬币,一共有 N ( 1 ≤ N ≤ 20 ) N (1 \le …...
数据挖掘入门-二手车交易价格预测
一、二手车交易价格预测 1-1 项目背景 随着二手车市场的快速发展,二手车交易价格的预测成为了一个热门研究领域。精准的价格预测不仅能帮助买卖双方做出更明智的决策,还能促进市场的透明度和公平性。对于买家来说,了解合理的市场价格可以避免…...
【PostgreSQL数据分析实战:从数据清洗到可视化全流程】金融风控分析案例-10.4 模型部署与定期评估
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 10.4 模型部署与定期评估10.4.1 模型部署架构设计1.1 模型存储方案1.2 实时预测接口 10.4.2 定期评估体系构建2.1 评估指标体系2.2 自动化评估流程2.3 模型衰退预警 10.4.3 …...
构建可信数据空间需要突破技术、规则和生态三大关键
构建可信数据空间需要突破技术、规则和生态三大关键:技术上要解决"可用不可见"的隐私计算难题,规则上要建立动态确权和跨境流动的治理框架,生态上要形成多方协同的标准体系。他强调,只有实现技术可控、规则可信、生态协…...
阳光学院【2020下】计算机网络原理-A卷-试卷-期末考试试卷
一、单选题(共25分,每空1分) 1.ICMP协议工作在TCP/IP参考模型的 ( ) A.主机-网络 B.网络互联层 C.传输层 D.应用层 2.下列关于交换技术的说法中,错误的是 ( ) A.电路交换适用于突发式通信 B.报文交换不能满足实时通信 C.报文…...
python: union()函数用法
在 Python 中,union() 是集合(set)类型的内置方法,用于返回两个或多个集合的并集(即所有元素的合集,自动去重)。以下是它的用法详解: 1. 基本语法 python 复制 下载 set.union(*…...
docker部署WeDataSphere开源大数据平台
GitHub:https://github.com/WeBankFinTech/WeDataSphere **WDS容器化版本是由Docker构建的一个能够让用户在半小时内完成所有组件安装部署并使用的镜像包。**无需再去部署Hadoop等基础组件,也不需要部署WDS的各功能组件,即可让您快速体验 WD…...
【计算机视觉】OpenCV项目实战:基于face_recognition库的实时人脸识别系统深度解析
基于face_recognition库的实时人脸识别系统深度解析 1. 项目概述2. 技术原理与算法设计2.1 人脸检测模块2.2 特征编码2.3 相似度计算 3. 实战部署指南3.1 环境配置3.2 数据准备3.3 实时识别流程 4. 常见问题与解决方案4.1 dlib安装失败4.2 人脸检测性能差4.3 误识别率高 5. 关键…...
uni-app学习笔记五-vue3响应式基础
一.使用ref定义响应式变量 在组合式 API 中,推荐使用 ref() 函数来声明响应式状态,ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回 示例代码: <template> <view>{{ num1 }}</view><vi…...
阿克曼-幻宇机器人系列教程2- 机器人交互实践(Topic)
在上一篇文章中,我们介绍了两种登录机器人的方式,接下来我们介绍登录机器人之后,我们如何通过topic操作命令实现与机器人的交互。 1. 启动 & 获取topic 在一个终端登录树莓派后,执行下列命令运行机器人 roslaunch huanyu_r…...
Windows系统事件查看器管理单元不可用
报错:Windows系统事件查看器管理单元不可用 现象原因:为误触关闭管理单元或者该模块卡死 解决办法:重启Windows server服务,若不行,则重启服务器即可...
milvus+flask山寨《从零构建向量数据库》第7章case2
继续流水账完这本书,这个案例是打造文字形式的个人知识库雏形。 create_context_db: # Milvus Setup Arguments COLLECTION_NAME text_content_search DIMENSION 2048 MILVUS_HOST "localhost" MILVUS_PORT "19530"# Inference Arguments…...
前端如何应对精确数字运算?用BigNumber.js解决JavaScript原生Number类型在处理大数或高精度计算时的局限性
目录 前端如何应对精确数字运算?用BigNumber.js解决JavaScript原生Number类型在处理大数或高精度计算时的局限性 一、BigNumber.js介绍 1、什么是 BigNumber.js? 2、作用领域 3、核心特性 二、安装配置与基础用法 1、引入 BigNumber.js 2、配置 …...
多目应用:三目相机在汽车智能驾驶领域的应用与技术创新
随着汽车智能驾驶技术不断完善,智能汽车也不断加速向全民普惠迈进,其中智驾“眼睛”三目视觉方案凭借低成本、高精度、强适配性成为众多汽车品牌关注的焦点。三目相机在汽车智能驾驶领域的创新应用,主要依托其多视角覆盖、高动态范围…...
webpack重构优化
好的,以下是一个关于如何通过重构 Webpack 构建策略来优化性能的示例。这个过程包括分析现有构建策略的问题、优化策略的制定以及具体的代码实现。 --- ### 项目背景 在参与公司的性能专项优化过程中,我发现现有的 Webpack 构建策略存在一些问题&#…...
MySQL 8.0 OCP(1Z0-908)英文题库(31-40)
目录 第31题题目分析正确答案 第32题题目分析正确答案 第33题题目分析正确答案: 第34题题目解析正确答案 第35题题目分析正确答案 第36题题目分析正确答案 第37题题目分析正确答案 第38题题目分析正确答案 第39题题目分析正确答案 第40题题目分析正确答案 第31题 Y…...
aardio - 虚表 —— vlistEx.listbar2 多层菜单演示
在 近我者赤 修改版的基础上,做了些许优化。 请升级到最新版本。 import win.ui; import godking.vlistEx.listbar2; import fonts.fontAwesome; /*DSG{{*/ mainForm win.form(text"多层折叠菜单";right1233;bottom713) mainForm.add({ custom{cls"…...
22.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--增加公共代码
在拆分服务之前,我们需要先提取一些公共代码。本篇将重点新增日志记录、异常处理以及Redis的通用代码。这些组件将被整合到一个共享类库中,便于在微服务架构中高效复用。 Tip:在后续的教程中我们会穿插多篇提取公共代码的文章,帮助…...
EasyOps®5月热力焕新:三大核心模块重构效能边界
在应用系统管理中,我们将管理对象从「服务实例」优化为「部署实例」,这一改变旨在提升管理效率与数据展示清晰度。 此前,系统以 “IP Port” 组合定义服务实例。当同一 IP 下启用多个进程或端口时,会产生多个服务实例。比如一台…...
基于深度学习的工业OCR数字识别系统架构解析
一、项目场景 春晖数字识别视觉检测系统专注于工业自动化生产监控、设备运行数据记录等关键领域。系统通过高精度OCR算法,能够实时识别设备上显示的关键数据(如温度、压力、计数等),并定时存储至Excel文件中。这些数据对于生产过…...
R语言绘图 | 渐变火山图
客户要求绘制类似文章中的这种颜色渐变火山图,感觉挺好看的。网上找了一圈,发现有别人已经实现的类似代码,拿来修改后即可使用,这里做下记录,以便后期查找。 简单实现 library(tidyverse)library(ggrepel)library(ggf…...