DWA规划路径时,机器人到了位置误差范围内会开始转动,直到满足角度误差。但是如果两个误差设置太小,很可能在转动过程中机器人又不能满足位置误差了,这样就又进入局部规划了,这种现象被称为dance
,也就是当机器人接近目标点时,会在附近左右摆来摆去的。这显然不是我们想要的。
latch_xy_goal_tolerance
机制是确保机器人一旦到达目标的position就停止,默认是关闭的。有些人反复设置总是不起作用,原因其实是LatchedStopRotateController
的bug,它的构造函数中的latch_xy_goal_tolerance
参数设置为false,即使在yaml中设置为了true,参数服务器里也是true,用日志在源码里打印会发现变量latch_xy_goal_tolerance_
还是false,只能去构造函数里修改。
但是latched机制不能一直打开,否则又会给全局路径造成问题。因为接收到新目标时,不能再latch机器人了,move_base将reset latching. 但是全局路径在机器人到达目标前是不会关闭的,结果会出现:第一次latch机制是正常的,然后再发导航目标,机器人会依然不动。
解决的方法是在DWAPlannerROS::setPlan
里,在reset latch之前检查全局路径的目标是否改变,若没有改变就不要reset latch。 更省事的方法是到move_base
里把全局规划频率planner_frequency
设置为0,这样全局规划只有在接收到新目标或局部规划失败时才进行规划,看各自需求了。
1 | bool DWAPlannerROS::setPlan(const std::vector<geometry_msgs::PoseStamped>& orig_global_plan) |
DWA算法的优点是计算的复杂度较低,由于考虑到速度和加速度的限制,只有安全的轨迹会被考虑,且每次采样的时间较短,因此轨迹空间较小。采样的速度即形成了一个动态窗口
DWA的弊端时很明显的,他对heading的实现是一个软约束,并不能够很好的在终点达到期望的heading.
该论文相对于之前的创新点在于:
建立的假设:
a. 运动学模型的速度设为随时间变化的分段函数,因此机器人轨迹可看做许多的圆弧积分组成
b. 加速度也不是一直变化,假设有n个时间片,在每个时间片内的线加速度和角加速度不变
c. 省略 的二次项
DWA的整体轨迹评价函数主要是三个方面:
也就是在局部规划出一条路径,希望与目标越来越近,且速度较快,与障碍物尽可能远。评价函数权衡以上三个部分得到一条最优路径。 这样一看确实和CMU张楫团队提出的路径算法挺像
根据机器人的速度加速度假设和运动学模型,可以得出
其中的 都可以用 表示,这个式子就是圆在平面的公式,根据此公式可以求出圆的轨迹,即通过一系列的分段圆弧和直线来拟合轨迹。
圆弧轨迹:动态窗口法仅仅考虑圆弧轨迹,该轨迹由采样速度 (v,w) 决定,这些速度构成一个速度搜索空间
允许速度:如果机器人能够在碰到最近的障碍物之前停止,则该采样速度参与打分,机器人不与障碍物碰撞的速度集合记做
动态窗口:由于机器人加速度的限制,只保留以当前加速度可到达的速度,速度集合记做
最终的搜索空间
有点cartographer分支定界算法的感觉
说是使用最优化方法,但是我看目标函数方程十分简单,不用使用什么优化算法,求最大值即可
实现细节:
当机器人陷入局部最优时(即不存在路径可以通过),使其原地旋转,直到找到可行路径。但这样看着会很奇怪,这就是一个缺点
安全裕度:在路径规划时,设定一安全裕度,即在路径和障碍物之间保留一定间隙,且该间隙随着速度增大线性增长。
占比重太大,机器人运动自由度大,窄的区域不容易通过, 占比重太小,机器人轨迹则不够平滑。因此 越大,越适合在窄区域, 越小,越适合在宽区域。
在不涉及TEB的情况下,ROS中经常使用的局部规划器,有base_local_planner
和 dwa_local_planner
,简单来看,两者属并列关系,其实就是选择使用 TrajectoryPlannerROS
还是 DWAPlannerROS
,我们用ROS就只看DWAPlannerROS
。两种配置如下:1
2
3<arg name="base_local_planner" default="base_local_planner/TrajectoryPlannerROS"/>
<arg name="base_local_planner" default="dwa_local_planner/DWAPlannerROS"/>
其实base_local_planner也是包含dwa选项的,不过通常还是使用独立的DWA模块。 base_local_planner::TrajectoryPlanner
是对DWA的实现和Trajectory Rollout。 可以向dwa_local_planner
添加自己的代价函数或者trajectory generators
Matlab对DWA算法的仿真
DWA泊车算法的实现
Dynamic Window Approach_机器人局部避障的动态窗口法
参考:DWA算法的论文
接上一篇,分析如果还没到x y误差范围内,进行DWA规划的情况1
2
3
4
5
6
7
8
9
10
11
12
13
14else
{
// 是否规划成功
bool isOk = dwaComputeVelocityCommands(current_pose_, cmd_vel);
if (isOk)
publishGlobalPlan(transformed_plan);
else
ROS_WARN_NAMED("dwa_local_planner", "DWA planner failed to produce path.");
std::vector<geometry_msgs::PoseStamped> empty_plan;
publishGlobalPlan(empty_plan);
}
return isOk;
关键就是dwaComputeVelocityCommands
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53bool DWAPlannerROS::dwaComputeVelocityCommands(
tf::Stamped<tf::Pose> &global_pose,
geometry_msgs::Twist& cmd_vel )
{
// dynamic window sampling approach to get useful velocity commands
if(! isInitialized() )
{
ROS_ERROR("This planner has not been initialized, please call initialize() before using this planner");
return false;
}
tf::Stamped<tf::Pose> robot_vel;
odom_helper_.getRobotVel(robot_vel);
/* For timing uncomment
struct timeval start, end;
double start_t, end_t, t_diff;
gettimeofday(&start, NULL);
*/
//compute what trajectory to drive along
tf::Stamped<tf::Pose> drive_cmds;
drive_cmds.frame_id_ = costmap_ros_->getBaseFrameID();
// call with updated footprint
base_local_planner::Trajectory path = dp_->findBestPath(global_pose, robot_vel, drive_cmds, costmap_ros_->getRobotFootprint());
//ROS_ERROR("Best: %.2f, %.2f, %.2f, %.2f", path.xv_, path.yv_, path.thetav_, path.cost_);
/* For timing uncomment
gettimeofday(&end, NULL);
start_t = start.tv_sec + double(start.tv_usec) / 1e6;
end_t = end.tv_sec + double(end.tv_usec) / 1e6;
t_diff = end_t - start_t;
ROS_INFO("Cycle time: %.9f", t_diff);
*/
//pass along drive commands
cmd_vel.linear.x = drive_cmds.getOrigin().getX();
cmd_vel.linear.y = drive_cmds.getOrigin().getY();
cmd_vel.angular.z = tf::getYaw(drive_cmds.getRotation());
//if we cannot move... tell someone
std::vector<geometry_msgs::PoseStamped> local_plan;
if(path.cost_ < 0)
{
ROS_DEBUG_NAMED("dwa_local_planner",
"The dwa local planner failed to find a valid plan, cost functions discarded all candidates. This can mean there is an obstacle too close to the robot.");
local_plan.clear();
publishLocalPlan(local_plan);
return false;
}
ROS_DEBUG_NAMED("dwa_local_planner", "A valid velocity command of (%.2f, %.2f, %.2f) was found for this cycle.",
cmd_vel.linear.x, cmd_vel.linear.y, cmd_vel.angular.z);
1 | // Fill out the local plan |
接着上一篇,继续DWAPlannerROS::computeVelocityCommands
如果x y坐标已经在目标的距离误差范围内1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19if (latchedStopRotateController_.isPositionReached(
&planner_util_, current_pose_) )
{
// 发布空的路径,这段可以删除
std::vector<geometry_msgs::PoseStamped> local_plan;
std::vector<geometry_msgs::PoseStamped> transformed_plan;
publishGlobalPlan(transformed_plan);
publishLocalPlan(local_plan);
// 这个类保存了所有 DWA参数,只有一个getAccLimits函数
base_local_planner::LocalPlannerLimits limits = planner_util_.getCurrentLimits();
return latchedStopRotateController_.computeVelocityCommandsStopRotate(
cmd_vel,
limits.getAccLimits(),
dp_->getSimPeriod(),
&planner_util_,
odom_helper_,
current_pose_,
boost::bind(&DWAPlanner::checkTrajectory, dp_, _1, _2, _3) );
}latchedStopRotateController_
是 base_local_planner::LatchedStopRotateController
,它用到一个参数latch_xy_goal_tolerance
1 | /** |
这里有两种情况可以出现 if(true)
, getGoalPositionDistance
不用看也知道是求 global_pose 和 goal的空间距离,再跟参数xy_goal_tolerance
比较。
latch_xy_goal_tolerance_
本意是在LatchedStopRotateController
构造函数里获取设置的参数,但是源码在这里出现了bug,应当是命名空间导致的。在DWA参数里设置其为true没有效果,从参数服务器看到的确实是true,但是在源码里的latch_xy_goal_tolerance_
还是默认的false. 可以直接修改构造函数的设置值。 剩下的关键就是变量xy_tolerance_latch_
了,有一个关键函数void resetLatching() { xy_tolerance_latch_ = false; }
。 如何实现这一情况,在下面函数。
注意isPositionReached
只是判断,没有发布零速度,没有停止
机器人到达位置时,停止平移,只做旋转1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39bool LatchedStopRotateController::computeVelocityCommandsStopRotate(
geometry_msgs::Twist& cmd_vel,
Eigen::Vector3f acc_lim,
double sim_period,
LocalPlannerUtil* planner_util,
OdometryHelperRos& odom_helper_,
tf::Stamped<tf::Pose> global_pose,
// obstacle_check 在调用里是 DWAPlanner::checkTrajectory
boost::function<bool (Eigen::Vector3f pos,
Eigen::Vector3f vel,
Eigen::Vector3f vel_samples)> obstacle_check)
{
// global goal is the last point in the global plan
tf::Stamped<tf::Pose> goal_pose;
if ( ! planner_util->getGoal(goal_pose) )
{
ROS_ERROR("Could not get goal pose");
return false;
}
base_local_planner::LocalPlannerLimits limits = planner_util->getCurrentLimits();
// latch goal tolerance, if we ever reach the goal location,
// we'll just rotate in place
// 这里就是latched机制了,在第5篇再详细解释。这里先不考虑latch,看下面的情况。
if (latch_xy_goal_tolerance_ && ! xy_tolerance_latch_ )
{
ROS_INFO("Goal position reached, stopping and turning in place");
xy_tolerance_latch_ = true;
}
// 检查是否达到了 goal orientation
double goal_th = tf::getYaw(goal_pose.getRotation());
double angle = base_local_planner::getGoalOrientationAngleDifference(global_pose, goal_th);
if (fabs(angle) <= limits.yaw_goal_tolerance)
{
// 到达, 停止转动
cmd_vel.linear.x = 0.0;
cmd_vel.linear.y = 0.0;
cmd_vel.angular.z = 0.0;
rotating_to_goal_ = false;
}
如果发布了零速度,会直接运行到最后的return true
最后看角度没有到导航误差范围的情况,比较复杂1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52else
{
tf::Stamped<tf::Pose> robot_vel;
odom_helper_.getRobotVel(robot_vel);
nav_msgs::Odometry base_odom;
odom_helper_.getOdom(base_odom);
// if we're not stopped yet... we want to stop...
// taking into account the acceleration limits of the robot
/* 这里是机器人在继续运动的情况
rotating_to_goal_ 在构造函数里是 false
stopped函数: 如果里程计的角速度小于 rot_stopped_vel,且线速度小于
trans_stopped_vel, 则返回 true,认为已经停止 */
if ( ! rotating_to_goal_ && !base_local_planner::stopped(
base_odom, limits.rot_stopped_vel, limits.trans_stopped_vel) )
{
if ( ! stopWithAccLimits(
global_pose,
robot_vel,
cmd_vel,
acc_lim,
sim_period,
obstacle_check) )
{
ROS_INFO("Error when stopping.");
return false;
}
ROS_DEBUG("Stopping...");
}
//if we're stopped... then we want to rotate to goal
else
{
//set this so that we know its OK to be moving
rotating_to_goal_ = true;
if ( ! rotateToGoal(
global_pose,
robot_vel,
goal_th,
cmd_vel,
acc_lim,
sim_period,
limits,
obstacle_check) )
{
ROS_INFO("Error when rotating.");
return false;
}
ROS_DEBUG("Rotating...");
}
}
return true;
}
我们需要看的是DWAPlannerROS
类的源码,继承自nav_core::BaseLocalPlanner
。 但是它还用到了base_local_planner
的LatchedStopRotateController
模块,用于latch机制和到达位置后转向。
原型是void DWAPlannerROS::initialize(std::string name, tf2_ros::Buffer* tf, costmap_2d::Costmap2DROS* costmap_ros)
其实主要是if( !isInitialized())
的部分,直接看里面的内容
1 | ros::NodeHandle private_nh("~/" + name); |
但是搜索发现,DWA的参数基本都在TrajectoryPlannerROS::initialize
里加载。
根据机器人离目标是否足够接近,路径规划由dwa sampling
或者stop and rotate
负责。
先是全局路径的处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 开始规划时再次获取位姿
if ( !costmap_ros_->getRobotPose(current_pose_))
{
ROS_ERROR("Could not get robot pose");
return false;
}
// TEB的部分全局路径也叫 transformed_plan,应该是因为下面的getLocalPlan参数也是这个名字
std::vector<geometry_msgs::PoseStamped> transformed_plan;
// 获取全局路径
if ( !planner_util_.getLocalPlan(current_pose_, transformed_plan) )
{
ROS_ERROR("Could not get local plan");
return false;
}
if(transformed_plan.empty())
{
ROS_WARN_NAMED("dwa_local_planner", "Received an empty transformed plan.");
return false;
}
ROS_DEBUG_NAMED("dwa_local_planner", "Received a transformed plan with %zu points.", transformed_plan.size());
上面的planner_util_.getLocalPlan
的内容实际是:1
2
3
4
5
6
7
8
9
10
11
12base_local_planner::transformGlobalPlan(
*tf_,
global_plan_,
global_pose,
*costmap_,
global_frame_,
transformed_plan)
if(limits_.prune_plan)
{
base_local_planner::prunePlan(global_pose, transformed_plan, global_plan_);
}
涉及到了DWA的参数prune_plan
,如果需要剪辑全局路径,就进入base_local_planner::prunePlan
,此函数在goal_funtions.cpp
,一看就是在namespace里。这里不如TEB的剪切全局路径复杂,其实就是把机器人当前位姿身后的部分剪掉,这部分是从路径起点到离位姿的欧氏距离的平方不到1米的点,这个1米已经写死,不能用参数更改,不过并不重要。
1 | // update plan in dwa_planner even if we just stop and rotate, to allow checkTrajectory |
1 | void DWAPlanner::updatePlanAndLocalCosts( |
有旋转向量、四元数和旋转矩阵三种表示方法,在Eigen里用前两种进行定义,这样参数少,可以转化为第三种。
牢记下面这张图,根据《视觉SLAM十四讲》总结:
使用的是模板类:class Eigen::AngleAxis< Scalar >
,这被称为角轴表示法,顾名思义给定旋转角度和旋转轴即可。其创建方式符合Eigen的原则,传入数据类型作为模板参数。旋转角度以弧度表示,旋转轴为Vector类型的向量,注意向量必须要被归一化(vec.normalized()
即可)。Eigen中也预先定义好了AngleAxisd
和AngleAxisf
两种方便使用。
参考:基于eigen实现欧拉角(RPY), 旋转矩阵, 旋转向量, 四元数之间的变换
1 | Vector3f v1 = Vector3f::UnitX(); |
Quaternion是四元数表示法,Quaternion的构造是标准Eigen格式:数据类型作为模板参数+构造参数,而且重载了多个构造函数,因此可以方便地从角轴、旋转矩阵等数据类型进行构造。
Eigen提供了Quaternionf和Quaterniond方便使用。Quaterniond的初始化:1
2
3
4
5Eigen::Quaterniond q1(x, y, z, w);
Eigen::Quaterniond q2(Vector4d(x, y, z, w));
Eigen::Quaterniond q2(Matrix3d(R));
在Quaternion内部的保存中,虚部在前,实部在后。
如果以第一种方式构造四元数,则实部是x,虚部是[y,z,w]
。 对于第二种方式,则实部是w,虚部是[x,y,z]; 对于第三种方式,则是用3x3的旋转矩阵初始化四元数。
所以最常用是 Eigen::Quaterniond q1(w, x, y, z); ,四个数的传入顺序是w、x、y、z,对应w+xi+yj+zk
。
Eigen::Quaterniond
不能使用cout
Identity()
: 返回一个表示单位旋转的四元数
w(): 返回四元数中的w分量。 同理 x(), y(), z()是返回相应的分量。
coeffs(): 返回四元数的四个数,可以对其进行索引[]获取值。 需要注意的是返回顺序是x、y、z、w,和构造函数的不同
toRotationMatrix() 将当前Quaternion对象转换为3×3的旋转矩阵。另外,没有fromRotationMatrix()函数,只能用构造函数传入旋转矩阵
slerp():对两个四元数进行球面线性插值(Spherical linear interpolation,通常简称Slerp),是四元数的一种线性插值运算,主要用于在两个表示旋转的四元数之间平滑差值。
可以让三个AngleAxis
相乘:1
2
3
4
5
6// 参数顺序和 static_transform_publisher 的一致
Eigen::Vector3f angle(roll, pitch, yaw);
Eigen::Matrix3d rotation;
rotation = Eigen::AngleAxisd(angle[0], Eigen::Vector3d::UnitZ()) *
Eigen::AngleAxisd(angle[1], Eigen::Vector3d::UnitY()) *
Eigen::AngleAxisd(angle[2], Eigen::Vector3d::UnitX()) ;
这样计算的结果和每个AngleAxisf转换成旋转矩阵后再相乘得到的结果是相同的
其实这样更简单,我们可以直接从tf2_ros::Buffer
和geometry_msgs::TransformStamped
直接获得四元数1
2
3
4
5
6Eigen::Quaterniond quaternion(transform.transform.rotation.w,
transform.transform.rotation.x,
transform.transform.rotation.y,
transform.transform.rotation.z
);
Eigen::Matrix3d rotation = quaternion.matrix();
采用Eigen标准构造原则,构造示例如下:1
2
3
4Translation<float,2>(tx, ty)
Translation<float,3>(tx, ty, tz)
Translation<float,N>(s)
Translation<float,N>(vecN)
多个平移合并用乘号而不是加号,需要注意。和旋转相比,它的成员函数也相对少一些:
x():获取平移的x分量(可修改)
y():获取平移的y分量(可修改)
z():获取平移的z分量(可修改),注意,千万不要对一个二维的Translation获取z分量,否则直接会运行报错
vector()&.translation():返回当前平移的向量表示(可修改),可以索引[]获取各分量
1 | Translation2d t1(1,4); |
构造一个EigenSolver,然后分别调用其成员函数.eigenvalues()、.eigenvectors()即可获得特征值与特征向量。
1 |
|
输出结果并不是我们平时熟悉的形式,而是1
2
3
4
5
6
7(4,0)
(1,0)
(-2,0)
(-0.666667,0) (-0.666667,0) (-0.333333,0)
(0.666667,0) (-0.333333,0) (-0.666667,0)
(-0.333333,0) (0.666667,0) (-0.666667,0)
实际特征值是4,1,-2. 4对应的特征向量分别为(-0.666667, -0.666667,-0.333333)
如果sudo rosdep init
和 rosdep update
没有执行成功,有些ROS功能会无法执行,比如rqt_tf_tree
会报错:1
2the rosdep view is empty: call 'sudo rosdep init' and 'rosdep update'
[WARN] wait_for_service(/rqt_gui_py_node_26204/tf2_frames): failed to contact, will keep trying
这两个老大难其实都是网络的问题,sudo rosdep init
的目的是为了在/etc目录下新建/ros/rosdep/sources.list.d/20-default.list
文件。
小鱼基于rosdep源码制作了rosdepc,专门服务国内ROS用户,使用pip3 和 rosdepc 可以彻底消灭这个问题1
2
3
4
5sudo apt-get install python3-pip
sudo pip3 install rosdepc
sudo rosdepc init
rosdepc update
rosdep update
根据20-default.list
文件中的网址链接去下载相应的文件,最终在/etc/ros/rosdep
下放5个文件,分别是base.yaml,gentoo.yaml,osx-homebrew.yaml,python.yaml,ruby.yaml. 但又做了一些配置,所以不是下载文件能解决的。
以下步骤先从网上下载rosdistro
文件夹,放到/etc/ros
sudo rosdep init
后报错1 | ERROR: cannot download default sources list from: |
从网上直接下载这个文件,然后放到/etc/ros/rosdep/sources.list.d
,再修改如下:1
2
3
4
5
6
7
8
9
10
11# os-specific listings first
yaml file:///etc/ros/rosdistro/rosdep/osx-homebrew.yaml osx
# generic
yaml file:///etc/ros/rosdistro/rosdep/base.yaml
yaml file:///etc/ros/rosdistro/rosdep/python.yaml
yaml file:///etc/ros/rosdistro/rosdep/ruby.yaml
gbpdistro file:///etc/ros/rosdistro/releases/fuerte.yaml fuerte
# newer distributions (Groovy, Hydro, ...) must not be listed anymore, they are being fetched from the rosdistro index.yaml instead
kinetic 要修改python2.7的路径, melodic和noetic要修改python3 ,当然另一种方法是从其他电脑拷贝已经生成的文件。
kinetic 的修改1
2reading in sources list data from /etc/ros/rosdep/sources.list.d
ERROR: unable to process source
对下面三个文件做修改1
2
3
4
5
6
7
8
9
10/usr/lib/python2.7/dist-packages/rosdep2/gbpdistro_support.py
修改: FUERTE_GBPDISTRO_URL = 'file:///etc/ros/rosdistro/releases/fuerte.yaml'
/usr/lib/python2.7/dist-packages/rosdep2/rep3.py
修改: REP3_TARGETS_URL = 'file:///etc/ros/rosdistro/releases/targets.yaml'
/usr/lib/python2.7/dist-packages/rosdistro/__init__.py
修改: DEFAULT_INDEX_URL = 'file:///etc/ros/rosdistro/index-v4.yaml'
启动新终端,再次执行rosdep update
melodic和noetic 的修改:clone代码仓https://github.com/ros/rosdistro
到本地,并更改其文件rosdep/sources.list.d/20-default.list
,将其url改成本地文件路径,内容类似如下:1
2
3
4
5
6
7
8# os-specific listings first
yaml file:///home/baby/rosdistro/rosdep/osx-homebrew.yaml osx
# generic
yaml file:///home/baby/rosdistro/rosdep/base.yaml
yaml file:///home/baby/rosdistro/rosdep/python.yaml
yaml file:///home/baby/rosdistro/rosdep/ruby.yaml
gbpdistro file:///home/baby/rosdistro/releases/fuerte.yaml fuerte
由于rosdep使用python3,可直接该动源码。我们需要改动三个文件:
/usr/lib/python3/dist-packages/rosdep2/sources_list.py
,修改 DEFAULT_SOURCES_LIST_URL = 'file:///home/baby/rosdistro/rosdep/sources.list.d/20-default.list'
/usr/lib/python3/dist-packages/rosdep2/rep3.py
,修改 REP3_TARGETS_URL = 'file:///home/baby/rosdistro/releases/targets.yaml'
/usr/lib/python3/dist-packages/rosdep2/gbpdistro_support.py
,修改 FUERTE_GBPDISTRO_URL = 'file:///home/baby/rosdistro/releases/fuerte.yaml'
/usr/lib/python3/dist-packages/rosdistro/__init__.py
,修改 DEFAULT_INDEX_URL = 'file:///home/baby/rosdistro/index-v4.yaml'
完成后先把/etc/ros/rosdep/sources.list.d/20-default.list
删除,再执行sudo rosdep init
与rosdep update
就可以成功了。
下载gtsam源码: git clone https://bitbucket.org/gtborg/gtsam.git
依赖:1
2Boost >= 1.43 (Ubuntu: sudo apt-get install libboost-all-dev)
CMake >= 3.0 (Ubuntu: sudo apt-get install cmake)
安装可选的依赖:
sudo apt-get install libtbb-dev
https://software.intel.com/content/www/us/en/develop/articles/installing-intel-free-libs-and-python-apt-repo.html
1 | mkdir build |
ubuntu-drivers devices
查看显卡硬件型号,也就是其中的model
。 如果同意安装推荐版本,那我们只需要终端输入:sudo ubuntu-drivers autoinstall 就可以自动安装了。
当然我们也可以使用 apt 命令安装自己想要安装的版本,比如我想安装 340 这个版本号的版本,终端输入:sudo apt install nvidia-340 就自动安装了。
lshw -numeric -C display | grep product
也可以查看电脑显卡的十六进制ID
电脑装好好,启动一直失败,报错failed to start User Manager for UID 121
,连文本界面都进不去,后来才知道是ubuntu下缺少显卡驱动
先完全卸载之前安装的显卡驱动
ppa源文件卸载:sudo apt-get remove --purge nvidia*
runfile源文件卸载:sudo ./NVIDIA-Linux-x86_64-384.59.run --uninstall
先查询电脑最适合的显卡驱动版本,命令 ubuntu-drivers devices
,这个命令不仅适用于显卡
最佳显卡驱动版本为 nvidia-driver-435, 用命令行进行安装1
2
3
4sudo add-apt-repository ppa:graphics-drivers/ppa
sudo apt-get update
sudo apt-get install nvidia-driver-435 #此处数字要对应上面查询到的版本号
sudo apt-get install mesa-common-dev
如果前面没有禁用secure boot,则在安装过程中会提示设置一个密码,在重启时需要输入密码验证以禁用secure boot,重启后会出现蓝屏,这时候不能直接选择continue, 而应该按下按键,选择Enroll MOK, 确认后在下一个选项中选择continue,接着输入安装驱动时设置的密码,开机。
安装完成后重启,nvidia-smi
命令显示驱动信息,若出现驱动版本,就是成功
到 NVIDIA 的官网下载相应型号的驱动,官网地址
操作系统那里,一定选择Linux 64-bit
需要先安装一些 NVIDIA 显卡依赖的软件,在终端依次执行如下命令:1
2
3sudo dpkg --add-architecture i386
sudo apt update
sudo apt install build-essential libc6:i386
Ubuntu 系统默认安装好是使用的一个开源的驱动:nouveau
,我们需要先禁用这个开源驱动,方法如下,依次执行:1
2sudo bash -c "echo blacklist nouveau > /etc/modprobe.d/blacklist-nvidia-nouveau.conf"
sudo bash -c "echo options nouveau modeset=0 >> /etc/modprobe.d/blacklist-nvidia-nouveau.conf"
重启一下系统,运行之前下载的驱动。可能会出现下面的信息:1
2
3The distribution-provided pre-install script failed!
Are you sure you want to continue? -> CONTINUE INSTALLATION
Would you like to run the nvidia-xconfig utility? -> YES
安装完成后重启系统就可以点击软件列表中的 NVIDIA 的配置软件配置显卡驱动了,如果你遇到1
WARNING: Unable to find suitable destination to install 32-bit compatibility libraries
解决办法:1
2
3sudo dpkg --add-architecture i386
sudo apt update
sudo apt install libc6:i386