KITTI数据集的问题

安装kitt2bag

1
2
3
sudo pip install pandas==0.23.0 -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
sudo pip install pykitti -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
sudo pip install kitti2bag -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

使用kitti-lego-loam,结果报错 Failed to find match for field intensity ,结果发现是kitti转换出的bag中的点云fields是:x, y, z, i。偏偏legoloam需要的是x, y, z, intensity.

参考kitt2bag 解决“Failed to find match for field intensity”问题

PointField('i', 12, PointField.FLOAT32, 1)]改为 PointField('intensity', 12, PointField.FLOAT32, 1)]

同步后的数据中,imu的频率在10Hz左右,如果需要100Hz的IMU数据,可以参考LIO-SAM【项目地址】中作者改过的kitti2bag文件。
该方法同时使用原始数据(extract)和同步数据(synced)制作数据包。

真值

真值pose是左相机坐标系相对于其第1帧的transform

数据集KITTI-00的真值pose.txt,行数就是帧数,每行是12列数据,12列数据很容易想到是3个平移量和一个3x3的旋转矩阵,但是其排列方式是一个3*4的增广矩阵[R|t],说白了就是4x4的变换矩阵T去掉了最后一行的[0 0 0 1],数据存储顺序是从行到列

第一行数据如下

1
1.000000e+00 9.043680e-12 2.326809e-11 5.551115e-17 9.043683e-12 1.000000e+00 2.392370e-10 3.330669e-16 2.326810e-11 2.392370e-10 9.999999e-01 -4.440892e-16

实际就是矩阵


C++的各种报错和报警

还是Qt的环境好,很多时候,不用编译直接就提示有错误。

  • 报错invalid new-expression of abstract class type

有的编译器也会报错 error: allocating an object of abstract class type ‘child’。说明基类中有纯虚函数没有实现,必须在派生类实现所有纯虚函数,不使用派生类时是看不出来的。

  • marked ‘override’, but does not override
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
class Parent
{
public:
virtual std::string getName1(int x)
{
return "Parent";
}

virtual std::string getName2(int x)
{
return "Parent";
}
};

class Child : public Parent
{
public:
virtual std::string getName1(double x) override
{
return "Sub";
}
virtual std::string getName2(int x) const override
{
return "Sub";
}
};

派生类的同名函数和基类的参数类型不同,而且多了const。如果没有override,那么是派生类新加的函数,但是添加override,编译器认为基类有同名函数,但是没发现,所以报错。

如果是重写基类方法,建议使用override标识符,让编译器检查出可能的错误,从而避免无意的错误。


evo的使用

安装evo: pip install evo --upgrade --no-binary evo。最新版本的evo只支持Python3.6+,因此如果你在用Python 2.7,最高能安装的evo版本是1.12.0,可以输入pip install evo==1.12.0安装。安装完之后,终端输入evo看看有没有结果。

evo支持TUM、KITTI、EuRoC MAV、Bag格式的轨迹/姿态文件。可用的命令:

1
2
3
4
5
6
7
8
9
10
Metrics:
evo_ape - absolute pose error
evo_rpe - relative pose error

Tools:
evo_traj - tool for analyzing, plotting or exporting multiple trajectories
evo_res - tool for processing multiple result files from the metrics
evo_ipython - IPython shell with pre-loaded evo modules
evo_fig - (experimental) tool for re-opening serialized plots
evo_config - tool for global settings and config file manipulation

每个命令的第1参数只能是所支持的文件类型,比如kitti,否则报错: evo_rpe: error: argument subcommand: invalid choice: ‘abc’ (choose from ‘kitti’, ‘tum’, ‘euroc’, ‘bag’)

绘制多个轨迹

1
2
evo_traj kitti ground_truth.txt laser_odom.txt -p --plot_mode=xyz
evo_traj kitti --ref=ground_truth.txt laser_odom.txt -p --plot_mode=xy

--ref似乎就是把对应轨迹设置为虚线,其他没什么特别。--plot_mode如果设置为xyz,那么绘制的轨迹就是3D的。如果加入--save_results results.zip,那么把数据存入了zip文件。

evo_traj相当于定性评价了精度,定量评价用到evo_ape命令和evo_rpe,前者评价的是每段距离内的误差,后者评价的是绝对误差随路程的累计。

评价每段距离内的误差可以使用指令: evo_rpe kitti ground_truth.txt laser_odom.txt -r trans_part --delta 100 --plot --plot_mode xyz。 其中--delta 100表示的是每隔100米统计一次误差,这样统计的其实就是误差的百分比,和kitti的odometry榜单中的距离误差指标就可以直接对应了。

评价总累计误差可以用指令: evo_ape kitti ground_truth.txt laser_odom.txt -r full --plot --plot_mode xyz

还有个evo_res,以后用到再说。


PCL中的NDT
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <ros/ros.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/PCLPointCloud2.h>
#include <pcl/common/transforms.h>
#include <pcl/console/time.h>
#include <pcl_ros/point_cloud.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/registration/ndt.h> //NDT(正态分布)配准类头文件
#include <pcl/filters/approximate_voxel_grid.h> //滤波类头文件 (使用体素网格过滤器处理的效果比较好)
#include <pcl/visualization/pcl_visualizer.h>
#include <iostream>
#include <boost/thread/thread.hpp>

typedef pcl::PointXYZ PointType;
typedef pcl::PointCloud<PointType> PointCloud;
typedef pcl::PointCloud<PointType>::Ptr PointCloudPtr;
typedef pcl::PointCloud<PointType>::ConstPtr PointCloudConstPtr;

int main (int argc, char** argv)
{
ros::init(argc, argv, "test_ndt");
ros::NodeHandle nh;

PointCloudPtr target_cloud(new PointCloud);
if(pcl::io::loadPCDFile<PointType>("/home/user/test_nodes/src/test_pcl/room_scan1.pcd", *target_cloud) == -1)
{
ROS_ERROR("load pcd file error !");
return -1;
}
cout << "load target pcl " << target_cloud->size() << " points" << endl;

PointCloudPtr input_cloud(new PointCloud);
if(pcl::io::loadPCDFile<PointType>("/home/user/test_nodes/src/test_pcl/room_scan2.pcd", *input_cloud) == -1)
{
ROS_ERROR("load pcd file error !");
return -1;
}
cout << "load target pcl " << input_cloud->size() << " points" << endl;

PointCloudPtr filtered_cloud(new PointCloud);
pcl::VoxelGrid<PointType> filter;
filter.setLeafSize(0.1, 0.1, 0.1);
filter.setInputCloud(input_cloud);
filter.filter(*filtered_cloud);
cout << "after filtered, input cloud has "<< filtered_cloud->size() << " points" << endl;

pcl::NormalDistributionsTransform<PointType, PointType>::Ptr ndt_ptr(new pcl::NormalDistributionsTransform<PointType, PointType>() );
ndt_ptr->setMaximumIterations(35);
ndt_ptr->setTransformationEpsilon(0.01);
ndt_ptr->setStepSize(0.1);
ndt_ptr->setResolution(0.1);
ndt_ptr->setInputSource(filtered_cloud);
ndt_ptr->setInputTarget(target_cloud);

Eigen::AngleAxisf init_rotation (0.6931, Eigen::Vector3f::UnitZ ());
Eigen::Translation3f init_translation (1.79387, 0.720047, 0);
Eigen::Matrix4f init_guess = (init_translation * init_rotation).matrix ();

PointCloudPtr output_cloud(new PointCloud);
ndt_ptr->align(*output_cloud, init_guess);
cout << "output cloud points size: "<< output_cloud->size() << endl;
cout << "ndt transform: "<< endl << ndt_ptr->getFinalTransformation() << endl;
cout << "score: "<< ndt_ptr->getFitnessScore() << endl;

pcl::transformPointCloud(*input_cloud, *output_cloud, ndt_ptr->getFinalTransformation() );
pcl::io::savePCDFileASCII("/home/user/test_nodes/src/test_pcl/final.pcd", *output_cloud );

// 初始化点云可视化对象
boost::shared_ptr<pcl::visualization::PCLVisualizer>
viewer_final (new pcl::visualization::PCLVisualizer ("3D Viewer"));
viewer_final->setBackgroundColor (0, 0, 0); //设置背景颜色为黑色

// 对目标点云着色可视化 (red).
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>
target_color (target_cloud, 255, 0, 0);
viewer_final->addPointCloud<pcl::PointXYZ> (target_cloud, target_color, "target cloud");
viewer_final->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE,
1, "target cloud");

// 对转换后的源点云着色 (green)可视化.
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ>
output_color(output_cloud, 0, 0, 255);
viewer_final->addPointCloud<pcl::PointXYZ> (output_cloud, output_color, "output cloud");
viewer_final->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE,
1, "output cloud");
// 启动可视化
viewer_final->addCoordinateSystem (1.0); //显示XYZ指示轴
viewer_final->initCameraParameters (); //初始化摄像头参数

// 等待直到可视化窗口关闭
while (!viewer_final->wasStopped ())
{
viewer_final->spinOnce (101);
boost::this_thread::sleep (boost::posix_time::microseconds (100000));
}
}

三个迭代终止条件设置:

  1. setMaximumIterations(30): 设置最大的迭代次数。 一般来说在这个限制值之前,优化程序会在epsilon(最大容差)变换阀值下终止,添加最大迭代次数限制能够增加程序的鲁棒性阻止了它在错误的方向上运行时间过长
  2. setTransformationEpsilon: 终止条件设置最小转换差异,设置前后两次迭代的转换矩阵的最大容差epsilion,在变换中Epsilon参数分别从长度和弧度,定义了变换矢量[x, y, z, roll, pitch, yaw]的最小许可的递增量,一旦两次迭代小于这个最大容差,则认为已经收敛到最优解,默认值为:0
  3. setEuclideanFitnessEpsilon: 设置前后两次迭代的点对的欧式距离均值的最大容差,迭代终止条件之三,默认值为:-std::numeric_limits::max

setTransformationEpsilon的参数从0.01改为0.001,FitnessScore 只有细微变化,而且相对之前有时变大有时变小。但是改为0.1,FitnessScore与0.01时相比,也是有大有小。迭代次数不管怎么调整,FitnessScore毫无变化,可见确实是以epsilon为优先终止条件。

  • setStepSize(0.1): More-Thuente线搜索设置最大步长,即设置牛顿法优化的最大步长。当你靠近最优解时该算法会缩短迭代步长,在最大步长将会在较少的迭代次数下遍历较大的距离,但是却有过度迭代和在不符合要求的局部最小值处结束的风险。 调试的经验 :step的话可以先放大后变小。

  • setResolution(1.0);: 设置网格化时立方体的边长,网格大小设置在NDT中非常重要,太大会导致精度不高,太小导致内存过高,并且只有两幅点云相差不大的情况才能匹配。它需要足够大,每个体素包含与点有关的统计数据,平均值,协方差等。每个体素至少包含6个点,但小到足以唯一地描述环境。 调试的经验 :resolution在追求精度的情况下最好与点密度相等或者低一个数量级,这里是激光点云,所以设了1米

  • getFitnessScore():欧几里得适合度得分 FitnessScore ,该分数计算为从输出云到目标云中最近点的距离的平方。分数越大,准确率越低,配准效果也就越差。

align函数中,output_cloud保存变换后的input_cloud,它和target_cloud其实很相近了。但是它不能作为最终的源点云变换,因为上面对input_cloud进行了滤波处理,所以又经过pcl::transformPointCloud(*input_cloud, *output_cloud, ndt_ptr->getFinalTransformation() );获得未经滤波的output_cloud

注意的地方:

  • 开始做体素滤波,栅格太大,过滤太多,最后的output_cloudtarget_cloud看着不太契合
  • 只对源点云进行滤波,减少其数据量,而目标点云不需要滤波处理


参考: 参考程序


GeographicLib

CMakeLists.txt:

1
2
3
find_package (GeographicLib REQUIRED)
include_directories(${GeographicLib_INCLUDE_DIRS})
target_link_libraries(<可执行程序> ${GeographicLib_LIBRARIES})

直接看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <GeographicLib/LocalCartesian.hpp>

GeographicLib::LocalCartesian geo_converter;
double longitude = 0.0;
double latitude = 0.0;
double altitude = 0.0;
double local_E, local_N, local_U;

GeographicLib::LocalCartesian geo_converter;
// 这步获得了当前经纬高的值,初始化原点
geo_converter.Reset(latitude, longitude, altitude);
// 这里的意思是相对于原点,移动的经纬高的数值
latitude = 1.0;
longitude = 1.0;
altitude = 1.0;
// 将经纬高转换成东北天坐标[E,N,U],前三个传入,后三个传出
geo_converter.Forward(latitude, longitude, altitude, local_E, local_N, local_U);
std::cout << "local_E: "<< local_E<< " local_N: "<<local_N << " local_U: "<< local_U << std::endl<<std::endl;

运行结果是local_E: 111297 local_N: 110569 local_U: -1935.19,也就是说移动经纬1°,对应地面上的距离为11万米多。


LegoLoam IMU加速度去除重力影响

IMU数据去除重力

这就涉及IMU的测量模型,包含两种:加速度计和陀螺仪。加速度计用来测量加速度的大小,陀螺仪用来测量角速度的大小。

IMU的测量值是基于自身坐标系(body坐标系)的三轴加速度和三轴角速度数据,也就是 ,由于受重力加速度的影响,IMU 的测量值并不直接反映其本身的加速度,并且其测量的加速度的坐标是在 Body 系下表示的,与世界坐标系之间存在一个旋转关系,所以需要转换为世界坐标系下的数据,而角速度不需要转换。

IMU的姿态解算选用的旋转顺序为ZYX,即IMU坐标系初始时刻与世界坐标系重合,然后依次绕自己的Z、Y、X轴进行旋转,这样坐标系就会改变,也可以称新的坐标系为body坐标系

IMU加速度计测量的是其感受到的加速度,在静止的时候,其本身是没有加速运动的,但因为重力加速度的作用,其感受的加速度与重力加速度正好相反,所以读到的线加速度z是9.8,而不是-9.8

当加速度计水平放置,即Z轴竖直向上,而且在静止时,读到的加速度可以记作 (0, 0, 9.8) (世界坐标系)

当加速度计旋转一定的姿态时,重力加速度会在加速度的3个轴上产生相应的分量,其本质是世界坐标系下的(0, 0, 9.8)在新的加速度计坐标系(body坐标系)下的坐标,此时读到的是三轴加速度的新坐标。数学关系如下

这个结果是在body坐标系

在世界坐标系下,
转换到body坐标系, ,即 ,上面图中的计算结果就是 是IMU直接输出的。

那么按说真实加速度应该是这样的

1
2
3
float accX = imuIn->linear_acceleration.x + sin(pitch) * 9.81;
float accY = imuIn->linear_acceleration.y - sin(roll) * cos(pitch) * 9.81;
float accZ = imuIn->linear_acceleration.z - cos(roll) * cos(pitch) * 9.81;

坐标系变换

IMU不能随意安装时,其坐标系和载体坐标系是对齐的,都是右手坐标系,所以rosrun tf echo imu base_link的结果中,角度应该都是0,这样IMU得到的角度就是IMU当前姿态向载体坐标系对齐的过程。LOAM框架作者为了与其他视觉SLAM的工作进行匹配统一,将点云坐标系进行了坐标变换。

所有常用坐标系
在这张图里,只看IMU和camera坐标系的关系,IMU的x变成Camera的z,y—->x, z—->y。比如车上装相机识别二维码时,镜头总是朝前的,也就是图中的朝向。

这样一来,实际三轴加速度就成了源码里的

1
2
3
float accX = imuIn->linear_acceleration.y - sin(roll) * cos(pitch) * 9.81;
float accY = imuIn->linear_acceleration.z - cos(roll) * cos(pitch) * 9.81;
float accZ = imuIn->linear_acceleration.x + sin(pitch) * 9.81;

在`imuHandler()`函数中,只对加速度做了这个转换,方位角、角速度还在原来的右手坐标系下。

这导致了后面的处理中看似旋转顺序的表示产生了变化,而本质上旋转顺序按物理意义的偏航-俯仰-滚转保持了一致。

参考:IMU去除重力影响


深入分析 constraint_list 话题

这个话题是显示所有的约束,包括同一轨迹和不同轨迹的Inter约束,Intra约束。

Inter constraints, different trajectoriesInter residuals, different trajectories为例进行分析,内容如下:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# 每个点的 z为0.0,省略
header:
seq: 0
stamp:
secs: 1673860622
nsecs: 104007813
frame_id: "map"
ns: "Inter constraints, different trajectories"
# 12个点,有6个点相同或极为接近
points:
-
x: 2.93968786067
y: -1.41820975755
-
x: 3.44855066169
y: -1.24014780956
-
x: -0.000569545301176
y: 0.00522600210993
-
x: 3.4816963258
y: -1.19978045681
-
x: 3.45484823588
y: -1.19817846586
-
x: 3.78063457495
y: -1.54584384415
-
x: 3.45484823588
y: -1.19817846586
-
x: 4.61884003257
y: -1.49287256215
-
x: 3.45484823588
y: -1.19817846586
-
x: 4.6545497049
y: -1.84995755765
-
x: 3.45484823588
y: -1.19817846586
-
x: 4.91335613134
y: -2.16836009999


-
header:
seq: 0
stamp:
secs: 1673860622
nsecs: 104007813
frame_id: "map"
ns: "Inter residuals, different trajectories"
# 12个点,有些点很接近,结果构成的线很短
points:
-
x: 3.44855066169
y: -1.24014780956
-
x: 3.47573816704
y: -1.1999840042
-
x: 3.4816963258
y: -1.19978045681
-
x: 3.47896481246
y: -1.20118000282
-
x: 3.78063457495
y: -1.54584384415
-
x: 3.80065304682
y: -1.55796489639
-
x: 4.61884003257
y: -1.49287256215
-
x: 4.65790271882
y: -1.44107978827
-
x: 4.6545497049
y: -1.84995755765
-
x: 3.00991092288
y: -2.53605574275
-
x: 4.91335613134
y: -2.16836009999
-
x: 3.11523021674
y: -2.61951559764

源码中可以看到Rviz显示的是LINE_LIST类型的visualization_msgs::Marker,这里要提一下Rviz显示的线类型,如下

LINE_LIST是两两连接而成的线段,在carto这里,点数一定为偶数。把上面消息内容,没两个点的坐标连线,就可以得到Rviz里的显示

黄线有6条,水绿线有2条比较长,仔细还能看到3条很短的,最后一条在坐标系附近,肉眼已经看不到。Inter约束和residual个数相同,有一个共同的端点原因可以看源码。

MapBuilderBridge::GetConstraintList源码部分有些跳过了

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
const auto trajectory_node_poses =
map_builder_->pose_graph()->GetTrajectoryNodePoses();
const auto submap_poses = map_builder_->pose_graph()->GetAllSubmapPoses();
const auto constraints = map_builder_->pose_graph()->constraints();
for (const auto& constraint : constraints)
{
const auto submap_it = submap_poses.find(constraint.submap_id);
if (submap_it == submap_poses.end()) {
continue;
}
const auto& submap_pose = submap_it->data.pose;
const auto node_it = trajectory_node_poses.find(constraint.node_id);
if (node_it == trajectory_node_poses.end()) {
continue;
}
const auto& trajectory_node_pose = node_it->data.global_pose;
const Rigid3d constraint_pose = submap_pose * constraint.pose.zbar_ij;

// Inter constraints, different trajectories 线段的两个端点
constraint_marker->points.push_back(
ToGeometryMsgPoint(submap_pose.translation()));
constraint_marker->points.push_back(
ToGeometryMsgPoint(constraint_pose.translation()));
// Inter residuals, different trajectories 线段的两个端点
residual_marker->points.push_back(
ToGeometryMsgPoint(constraint_pose.translation()));
residual_marker->points.push_back(
ToGeometryMsgPoint(trajectory_node_pose.translation()));

从最后可以看出,共同的端点是constraint_posemap坐标系下。


ceres 5 核函数

核函数:某个数据点的残差过大的时候,降低其权重。以曲线拟合为例,

我们拟合点的时候,显然希望红线而不是黄线,outlier可以抛弃。

残差大于0.1则降低其权重
ceres::LossFunction *loss_function = new ceres::HuberLoss(0.1);


CMake 各种报错报警
  • integer_sequence’ is not a member of ‘std’

编译工程时报错 integer_sequence’ is not a member of ‘std’
CMake中添加 set(CMAKE_CXX_FLAGS "-std=c++14")

  • std::make_unique的报错

在CMake3.16版本编译std::make_unique部分的代码,没有报错。在3.10版本上编译却报错了,在CMakeLists.txt加入set(CMAKE_CXX_STANDARD 14)后才成功。

  • CMake Deprecation Warning at CMakeLists.txt:1 (cmake_minimum_required): Compatibility with CMake < 2.8.12 will be removed from a future version of CMake

我升级CMake后出现的报警,虽然不影响,但是一旦工程多了以后,这个报警会很烦人。只需要修改cmake_minimum_required (VERSION 3.19.0)

但是有时CMakeLists太多了,不可能一一去修改,使用sed命令集体修改:

1
sed -i "s/2.8.3/3.19/g" `grep 2.8.3 -rl . --include="*.txt" `

这个命令不要滥用,否则可能更改过多

  • 涉及PCL的一个警告
1
2
3
4
5
CMake Warning (dev) at /usr/local/share/cmake-3.17/Modules/FindPackageHandleStandardArgs.cmake:272 (message):
The package name passed to `find_package_handle_standard_args` (PCL_KDTREE)
does not match the name of the calling package (PCL). This can lead to
problems in calling code that expects `find_package` result variables
(e.g., `_FOUND`) to follow a certain pattern.

find_package(PCL REQUIRED)之前加上

1
2
3
if(NOT DEFINED CMAKE_SUPPRESS_DEVELOPER_WARNINGS)
set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE INTERNAL "No dev warnings")
endif()

  • No rule to make target

有时明明写好了,但编译会出现报错,看上去是CMakeLists中没有编译规则

1
2
3
4
5
6
make[2]: *** No rule to make target 'package/CMakeFiles/test_bin.dir/build'。 停止。
CMakeFiles/Makefile2:3192: recipe for target 'package/CMakeFiles/test_bin.dir/all' failed
make[1]: *** [package/CMakeFiles/test_bin.dir/all] Error 2
Makefile:138: recipe for target 'all' failed
make: *** [all] Error 2
Invoking "make -j4 -l4" failed

此时再重新编译仍然报错,只要把CMakeLists改一下,再编译就通过了

  • CMakeCache 报错

执行编译时报错:

1
CMake Error: The current CMakeCache.txt directory /home/user/common/build/CMakeCache.txt is different than the directory /home/user/space/build where CMakeCache.txt was created. This may result in binaries being created in the wrong place. If you are not sure, reedit the CMakeCache.txt

将当前工程的CMakeCache全删除再编译
1
rm $(find -name *.txt | grep CMakeCache)

  • unrecognized command line option -msse

这是因为在arm平台上不存在SSE指令集的,在x86平台才会有,因此需要在CMakLists文件中把—msse字样的都注释掉


纯定位的优化改进
abstract Welcome to my blog, enter password to read.
Read more