cartographer 安装及配置
综上,该公式通过最小化所有子图-扫描对的相对位姿约束残差(考虑协方差加权和鲁棒核函数),优化全局位姿,从而消除SLAM过程中的累计误差,实现高精度地图构建。: 尽管扫描匹配很精确,但微小的误差会随着机器人的运动不断累积,导致子图与子图之间出现错位。这是局部 SLAM 的核心。:后端通过子图间的约束关系(而非单帧扫描与全局地图的匹配)实现全局一致性,本质是图优化问题。能力,能够有效地纠正随着时间累积
文章目录
安装
支持apt 安装
# 安装Cartographer核心包:打开终端,输入以下命令(将${ROS_DISTRO}替换为你使用的ROS发行版名称,例如melodic、noetic等):
sudo apt install ros-${ROS_DISTRO}-cartographer
# 安装Cartographer的ROS接口包:接着安装ROS功能包,以便在ROS中使用Cartographer
sudo apt install ros-${ROS_DISTRO}-cartographer-ros
源码安装步骤
sudo apt-get install -y build-essential protobuf-compiler clang cmake g++ git google-mock libboost-all-dev libcairo2-dev libcurl4-openssl-dev libeigen3-dev libgflags-dev libgoogle-glog-dev liblua5.2-dev libsuitesparse-dev lsb-release ninja-build stow python3-sphinx libgmock-dev libmetis-dev libceres-dev
# 1. 清理已安装的 Abseil
sudo rm -rf /usr/local/lib/libabsl*
sudo rm -rf /usr/local/include/absl
sudo rm -rf /usr/local/lib/cmake/absl
mkdir -p carto_ws/src
# 2. 下载最新源码
git clone https://github.com/abseil/abseil-cpp.git
cd abseil-cpp
git checkout 20211102.0 # 使用与你的项目兼容的版本
# 3. 创建构建目录并配置(关键步骤:启用-fPIC)
mkdir build && cd build
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DBUILD_SHARED_LIBS=ON .. # 也可以构建共享库版本
# 4. 编译和安装
make -j$(nproc)
sudo make install
cd carto_ws/src
git clone https://github.com/cartographer-project/cartographer.git
git clone https://github.com/cartographer-project/cartographer_ros.git
cd carto_ws
# 5. 编译cartographer
catkin_make_isolated --pkg cartographer
catkin_make_isolated --pkg cartographer_ros
相关问题
问题:
CMake Error at /usr/local/lib/cmake/Ceres/CeresConfig.cmake:85 (message):
Failed to find Ceres - Missing requested Ceres components: [SuiteSparse]
(components requested: [SuiteSparse]). Detected Ceres version: 2.0.0
installed in: /usr/local with components: [EigenSparse,
SparseLinearAlgebraLibrary, LAPACK, SchurSpecializations, Multithreading].
Call Stack (most recent call first):
/usr/local/lib/cmake/Ceres/CeresConfig.cmake:313 (ceres_report_not_found)
CMakeLists.txt:39 (find_package)
解决:sudo apt-get install libsuitesparse-dev
问题:
/usr/bin/ld: /usr/local/lib/libabsl_base.a(sysinfo.cc.o): relocation R_X86_64_TPOFF32 against
_ZGVZN4absl12lts_2021110213base_internal12GetCachedTIDEvE9thread_id' can not be used when making a shared object; recompile with -fPIC /usr/bin/ld: /usr/local/lib/libabsl_synchronization.a(mutex.cc.o): relocation R_X86_64_PC32 against symbolZN4absl12lts_202111029Condition19CallVoidPtrFunctionEPKS1’ can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: bad value
解决:
mkdir build && cd build
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DBUILD_SHARED_LIBS=ON .. # 也可以构建共享库版本
# 4. 编译和安装
make -j$(nproc)
sudo make install
问题:
[ERROR] [1758800190.715302590]: PluginlibFactory: The plugin for class ‘Submaps’ failed to load. Error: Failed to load library /home/niebaozhen/workspace/docker_ws/ros1_ws/devel_isolated/cartographer_rviz/lib//libcartographer_rviz.so. Make sure that you are calling the PLUGINLIB_EXPORT_CLASS macro in the library code, and that names are consistent between this macro and your XML. Error string: Could not load library (Poco exception = libabsl_graphcycles_internal.so.2111.0.0: cannot open shared object file: No such file or directory)
解决:安装步骤,重新执行3、4和5。
问题:
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to ‘/tmp/runtime-root’
[rospack] Error: package ‘velodyne_description’ not found
[librospack]: error while executing command
解决:cartographer 会自动读取消息类型等,直接订阅velodyne_description 文件。
下载安装
sudo apt-get install ros-<distro>-velodyne-description
因为我是仿真,通过源码下载编译的,所有直接source上velodyne-description对应的空间的setup.sh就可以
问题:The IMU frame must be colocated with the tracking frame.
解决:.lua文件的tracking_frame 必须与/imu topic发布的/frame一致。
Could not compute submap fading: “map” passed to lookupTransform argument target_frame does not exist..lua 文件的map_frame=“map” 换成odom。
source devel_isolated/setup.bash #注意:是devel_isolated
rosrun cartographer_ros cartographer_pbstream_map_converter \
--pbstream_filename=input.pbstream \
--output_file_prefix=output
问题:[FATAL] [1760417080.629158361]: F1014 04:44:40.000000 518873 lua_parameter_dictionary.cc:410] Check failed: 1 == reference_counts_.count(key) (1 vs. 0) Key 'use_imu_data' was used the wrong number of times.
此类问题多为没有对应的参数项,如caartographer 3D 没有use_imu_data的配置项,
解决:通过在carto_ws/devel_isolated/cartographer/share/cartographer/configuration_files下查找对应的lua文件,查看对应的配置项,进行正确配置。
相关链接
添加3D点云支持:
https://zhuanlan.zhihu.com/p/410177996
https://github.com/xiangli0608/cartographer_detailed_comments_ws
官方:
https://google-cartographer.readthedocs.io/en/latest/
https://google-cartographer-ros.readthedocs.io/en/latest/
vlp16 2D支持
https://blog.csdn.net/aidimoli123/article/details/125364445
https://jishuzhan.net/article/1931542166681202689
https://blog.csdn.net/qq_52785580/article/details/127000553
<launch>
<param name="/use_sim_time" value="true" />
<node name="cartographer_node" pkg="cartographer_ros"
type="cartographer_node" args="
-configuration_directory $(find cartographer_ros)/configuration_files
-configuration_basename vlp_3d.lua"
output="screen">
<!--remap from="scan" to="/scan" /-->
<remap from="points2" to="/velodyne_points" />
<remap from="imu" to="/trunk_imu" />
</node>
<node name="cartographer_occupancy_grid_node" pkg="cartographer_ros"
type="cartographer_occupancy_grid_node" args="-resolution 0.05" />
<node name="rviz" pkg="rviz" type="rviz" required="true"
args="-d $(find cartographer_ros)/configuration_files/demo_3d.rviz" />
</launch>
include "map_builder.lua"
include "trajectory_builder.lua"
options = {
map_builder = MAP_BUILDER,
trajectory_builder = TRAJECTORY_BUILDER,
map_frame = "map",
tracking_frame = "imu_link",
published_frame = "velodyne",
odom_frame = "odom",
provide_odom_frame = true,
publish_frame_projected_to_2d = false,
use_pose_extrapolator = true,
use_odometry = false,
use_nav_sat = false,
use_landmarks = false,
num_laser_scans = 0,
num_multi_echo_laser_scans = 0,
num_subdivisions_per_laser_scan = 1,
num_point_clouds = 1,
lookup_transform_timeout_sec = 0.2,
submap_publish_period_sec = 0.3,
pose_publish_period_sec = 5e-3,
trajectory_publish_period_sec = 30e-3,
rangefinder_sampling_ratio = 1.,
odometry_sampling_ratio = 1.,
fixed_frame_pose_sampling_ratio = 1.,
imu_sampling_ratio = 1.,
landmarks_sampling_ratio = 1.,
}
MAP_BUILDER.use_trajectory_builder_3d = true
TRAJECTORY_BUILDER_3D.num_accumulated_range_data = 1
TRAJECTORY_BUILDER_3D.submaps.num_range_data = 160
MAP_BUILDER.num_background_threads = 7
POSE_GRAPH.constraint_builder.sampling_ratio = 0.03
POSE_GRAPH.optimization_problem.ceres_solver_options.max_num_iterations = 10
POSE_GRAPH.constraint_builder.global_localization_min_score = 0.66
POSE_GRAPH.optimization_problem.huber_scale = 1e2
POSE_GRAPH.optimize_every_n_nodes = 35
POSE_GRAPH.constraint_builder.min_score = 0.65
return options
使用
cartographer_ros
launch参数:
<node name="cartographer_node" pkg="cartographer_ros" type="cartographer_node"
args="-configuration_directory $(find cartographer_ros)/configuration_files
-configuration_basename mid360_simple_2d.lua
-minloglevel 2"
output="screen">
<!--remap from="/scan" to="/headLaserScan"/-->
<!--remap from="points2" to="/livox/lidar"/-->
<remap from="imu" to="/livox/imu" />
</node>
-minloglevel 2 :要求输出error及以上级别的log。降低io消耗,从而降低cpu负载const int GLOG_INFO =0,GLOG_WARNING =1,GLOG_ERROR = 2,GLOG_FATAL = 3, NUM SEVERITIES =4:
carto坐标系
在Cartographer中,地图坐标系(map_frame)z轴与重力对齐。初始时,cartographer提供的odom与map对齐,即同时也与重力对齐。
建筑物通常是与重力(铅垂线)对齐的。
而雷达的本体坐标系通常是完全与机身固连及水平的,由于雷达可能不是按水平安装的,因而其机体坐标系与重力方向是没有对齐的,包括起始阶段
当雷达上下轴(多为z轴)不与重力方向一致放置时,能够看到基于雷达机体坐标系的点云是歪的。rviz显示imu方向时,能够看到其与重力方向一致。
参数配置
Cartographer 的 Lua 配置文件是其高效工作的核心,它决定了传感器数据如何被处理、地图如何构建以及机器人如何定位。
一个典型的Lua配置文件开头会包含以下语句,引入更详细的构建器配置:
include "map_builder.lua"
include "trajectory_builder.lua"
map_builder 和 trajectory_builder 字段分别对应这两个引入的配置表。trajectory_builder.lua又包含trajectory_builder_2d.lua和trajectory_builder_3d.lua。
在launch时,会在carto_ws/devel_isolated/cartographer/share/cartographer/configuration_files下查找对应的lua文件。该路径下包含的lua文件有:
map_builder.lua map_builder_server.lua pose_graph.lua trajectory_builder.lua trajectory_builder_2d.lua trajectory_builder_3d.lua
| 参数类别 | 核心参数示例 | 简要说明 |
|---|---|---|
| 框架设置 (Frames) | map_frame, tracking_frame, published_frame |
定义坐标系,是TF树正确构建的基础。 |
| 传感器设置 (Sensors) | use_odometry, num_laser_scans, num_point_clouds |
声明使用哪些传感器以及其数据话题数量。 |
| 采样比率 (Sampling Ratios) | rangefinder_sampling_ratio, imu_sampling_ratio |
控制各类传感器数据的使用频率。 |
| 超时与周期 (Timeouts & Periods) | lookup_transform_timeout_sec, pose_publish_period_sec |
设置TF查找超时和各种信息发布的周期。 |
| 地图与轨迹构建器 | MAP_BUILDER.use_trajectory_builder_2d/3d |
选择2D或3D建图模式,并链接更详细的配置。 |
- 修改后重新编译:对Lua配置文件进行修改后,需要重新编译Cartographer工作空间才能使更改生效。 测试不需要。
核心参数
这部分参数,直接关系到Cartographer能否正确接收到传感器数据并发布正确的坐标变换。
map_frame(字符串,默认"map"): 全局地图的坐标系ID,是所有发布的位姿的父坐标系。tracking_frame(字符串): 关键参数。指定了SLAM算法用于追踪的坐标系。所有传感器数据都需要被转换到这个坐标系下进行计算。如果使用了IMU,通常设置为"imu_link";如果没有IMU,可以设置为机器人的基坐标系(如"base_link")。在Gazebo等仿真环境中,有时由于插件问题,可能需要设置为"base_footprint"。published_frame(字符串): 指定Cartographer发布的位姿变换的子坐标系(最底层)。例如,如果系统的里程计坐标系"odom"由其他部分提供,这里就设为"odom";否则,可以设为"base_link"。odom_frame(字符串,默认"odom"): 仅在provide_odom_frame为true时使用。它定义了在published_frame和map_frame之间用于发布局部SLAM结果(非闭环校正)的坐标系。provide_odom_frame(布尔值): 如果启用(设为true),Cartographer 将提供map_frame到odom_frame的变换,否则直接提供map_frame到published_frame的变换。这决定了TF树的结构是map -> odom -> base_link还是map -> base_link。
注意:
- 坐标系一致性:确保
tracking_frame等参数设置的坐标系在你的机器人TF树中真实存在,并且所有传感器到该坐标系的变换都已正确发布。这是避免Could not transform或frame does not exist错误的关键。 - 参数联动性:例如,
published_frame和provide_odom_frame的设置是联动的,需要根据机器人系统是否有独立的里程计odom来源来共同决定。
传感器参数
这些参数告诉Cartographer使用哪些传感器。
use_odometry(布尔值,默认false): 是否使用里程计数据。如果启用,必须提供nav_msgs/Odometry类型的话题(默认话题名为"odom")。num_laser_scans(整数,默认0): 订阅的单线激光扫描 (sensor_msgs/LaserScan) 话题的数量。如果使用一个普通的单线激光雷达,应设为1。num_multi_echo_laser_scans(整数,默认0): 订阅的多回波激光扫描 (sensor_msgs/MultiEchoLaserScan) 话题的数量。num_point_clouds(整数,默认0): 订阅的点云 (sensor_msgs/PointCloud2) 话题的数量。如果使用VLP-16等3D激光雷达,应设为1。重要提示:
num_laser_scans和num_point_clouds是互斥的,请根据雷达输出类型正确设置,否则会出现类似Queue waiting for data的错误。use_nav_sat(布尔值,默认false): 是否使用GPS数据。use_landmarks(布尔值,默认false): 是否使用路标点。
调优参数
- 采样比率参数: 例如
odometry_sampling_ratio和imu_sampling_ratio。这些参数可以控制传感器数据的使用频率,例如设成0.1即来10帧用1帧。如果某个传感器数据不准,可以降低其采样比率。 - 超时与周期参数:
lookup_transform_timeout_sec(浮点数,默认0.2): 使用tf2查找坐标变换时的超时时间(秒)。submap_publish_period_sec(浮点数,默认0.3): 发布子图的时间间隔。pose_publish_period_sec(浮点数,例如5e-3): 发布位姿的时间间隔,频率为200Hz。
POSE_GRAPH.optimization_problem.use_online_imu_extrinsics_in_3d = true:开启imu在线标定,注意这个参数虽然标识了3d,但是是2D和3D lua文件都可以启用的参数。
lua写法
lua文件的配置项有两种写法,点/拓展和重新定义。
两种写法的作用域和时机不同。
两种写法的本质区别
| 特性 | MAP_BUILDER = { use_trajectory_builder_3d = true } |
MAP_BUILDER.use_trajectory_builder_3d = true |
|---|---|---|
| 行为 | 重新定义了整个 MAP_BUILDER 表。 |
扩展/修改了已存在的 MAP_BUILDER 表。 |
执行一个赋值操作,创建一个全新的、只包含 use_trajectory_builder_3d 这一个字段的表,并把它赋值给 MAP_BUILDER 变量。这会完全覆盖了之前从 map_builder.lua 加载的、包含所有默认配置的那个表。 |
执行一个字段赋值操作。它没有创建新表,而是在已存在的 MAP_BUILDER 表中找到 use_trajectory_builder_3d 这个字段,并将其值修改为 true。 |
|
| 结果 | 重新定义新的MAP_BUILDER,覆盖了之前所有的 MAP_BUILDER 设置。 |
只修改指定字段,保留其他原有设置 |
MAP_BUILDER现在只剩下{ use_trajectory_builder_3d = true }。所有其他必要的默认参数(如 num_background_threads` 等)都丢失了。这很可能导致 Cartographer 内部因配置不完整而无法正确初始化,从而使你的设置"无效"。 |
MAP_BUILDER 表保留了所有从基础文件加载来的默认参数,同时只更新了你关心的这一个参数。这是安全且推荐的做法。 |
mid360
配置原理
| 原理模块 | 对应代码文件 | 主要配置与优化工作 |
|---|---|---|
| 传感器数据接入与转换 | sensor_bridge.cc/hmsg_conversion.cc/htf_bridge.cc/h |
确保数据格式和坐标系正确: • 在Lua配置中正确设置 tracking_frame, published_frame, odom_frame。• 确认IMU数据的坐标系和朝向。 • 检查雷达数据的精度和噪声特性。 |
| ROS管理与数据流 | node.cc/hnode_main.ccnode_options.cc/h |
配置核心参数: • use_odometry: 是否使用里程计。• use_imu_data: 是否使用IMU(2D参数)。• num_laser_scans/num_multi_echo_laser_scans: 雷达类型。• MAP_BUILDER.use_trajectory_builder_2d/3d: 选择2D或3D模式。 |
| 算法桥接与配置 | map_builder_bridge.cc/htrajectory_options.cc/h |
性能调优(主要修改Lua文件): • 前端匹配:调整 TRAJECTORY_BUILDER_xD.ceres_scan_matcher 和 fast_correlative_scan_matcher 参数。• 后端优化:调整 POSE_GRAPH.constraint_builder 相关参数(如搜索范围 max_constraint_distance)。• IMU用法:在3D模式下,配置 TRAJECTORY_BUILDER_3D.use_imu_data。 |
| 地图输出与可视化 | occupancy_grid_node_main.ccsubmap.cc/hassets_writer_main.cc |
结果处理: • 调整 occupancy_grid_node_main.cc 发布的地图分辨率。• 使用 assets_writer_main 导出点云地图用于纯定位。 |
关键配置文件与参数
- IMU使用:在3D模式或2D不平坦地面情况下,IMU用于提供重力方向约束和旋转预测。
- 里程计使用:提供平移运动的先验,减少纯激光匹配的搜索空间。
关键Lua参数:
-- 前端参数
TRAJECTORY_BUILDER_2D = {
ceres_scan_matcher = {
occupied_space_weight = 1.0, -- 占据空间代价权重
translation_weight = 10.0, -- 平移变化惩罚
rotation_weight = 40.0, -- 旋转变化惩罚
},
fast_correlative_scan_matcher = {
linear_search_window = 7.0, -- 线性搜索窗口大小
angular_search_window = math.rad(30.), -- 角度搜索窗口
},
motion_filter = {
max_time_seconds = 0.5, -- 最大时间间隔
max_distance_meters = 0.2, -- 最大移动距离
max_angle_radians = math.rad(1.), -- 最大旋转角度
}
}
-- 后端参数
POSE_GRAPH = {
constraint_builder = {
sampling_ratio = 0.3, -- 子图采样比率
max_constraint_distance = 15.0, -- 最大约束距离
min_score = 0.55, -- 闭环匹配最小分数
},
optimize_every_n_nodes = 90, -- 每N个节点优化一次
}
调试与性能优化要点
-
前端问题(定位漂移):
- 检查
PoseExtrapolator的预测是否准确 - 调整扫描匹配的搜索窗口和代价函数权重
- 检查
-
后端问题(闭环不触发):
- 增加
constraint_builder.max_constraint_distance - 降低
constraint_builder.min_score但要注意误匹配
- 增加
-
性能问题:
- 使用
VoxelFilter降低点云密度 - 调整
motion_filter减少插入的扫描数量
- 使用
使用LiDAR_IMU_Init标定
- 安装依赖库
sudo apt-get install -y liblapack-dev libsuitesparse-dev libcxsparse3 libgflags-dev libgoogle-glog-dev libgtest-dev
下载并编译ceres-1.14.0 (避免版本冲突)
wget http://ceres-solver.org/ceres-solver-1.14.0.tar.gz
tar xvf ceres-solver-1.14.0.tar.gz
mv ceres-solver-1.14.0 ceres-solver && cd ceres-solver
mkdir build && cd build
cmake .. -DBUILD_TESTING=OFF # 关闭测试加速编译
make -j8
sudo make install
需安装livox_ros_driver2。安装参考:https://blog.csdn.net/weixin_41469272/article/details/151049976
- 下载编译Lidar_IMU_Init
cd ~/catkin_ws/src
git clone https://github.com/hku-mars/LiDAR_IMU_Init.git
cd LiDAR_IMU_Init
find . -type f ! -name “*.patch”|xargs sed -i “s/livox_ros_driver/livox_ros_driver2/g”
cd ../.. && catkin_make -j
source devel/setup.bash
- Usage
以mid360为例:
rosrun LiDAR_IMU_Init LiDAR_IMU_Init -r /livox/lidar -i /imu
roslauanch LiDAR_IMU_Init livox_mid360.launch
首先保证雷达最少5秒静止。
然后前后左右上下晃动雷达,对应终端会显示标定进度条。
标定完成后,屏幕会打印标定数据,并保存数据到result/Initialization_result.txt。如下示例:
Initialization result:
Rotation LiDAR to IMU (degree) = 0.735328 0.424355 0.666829
Translation LiDAR to IMU (meter) = 0.006028 -0.000659 0.078033
Time Lag IMU to LiDAR (second) = -0.018201
Bias of Gyroscope (rad/s) = -0.032095 0.002117 0.005433
Bias of Accelerometer (meters/s^2) = -0.009808 -0.010242 0.009945
Gravity in World Frame(meters/s^2) = 0.679424 -0.356164 -9.779961
Homogeneous Transformation Matrix from LiDAR to IMU:
0.999905 -0.011541 0.007554 0.006028
0.011637 0.999851 -0.012746 -0.000659
-0.007406 0.012832 0.999890 0.078033
0.000000 0.000000 0.000000 1.000000
Refinement result:
Rotation LiDAR to IMU (degree) = 0.705306 0.007528 0.690674
Translation LiDAR to IMU (meter) = 0.012561 -0.006829 0.078586
Time Lag IMU to LiDAR (second) = -0.018201
Bias of Gyroscope (rad/s) = -0.033351 0.008406 0.003908
Bias of Accelerometer (meters/s^2) = -0.014032 -0.007099 0.001429
Gravity in World Frame(meters/s^2) = 0.680758 -0.355685 -9.774088
Homogeneous Transformation Matrix from LiDAR to IMU:
0.999927 -0.012051 0.000280 0.012561
0.012053 0.999852 -0.012306 -0.006829
-0.000131 0.012309 0.999924 0.078586
0.000000 0.000000 0.000000 1.000000
- Problems
Lidar_IMU_Init 编译,已经安装了livox_ros_driver,并source livox 的devel setup.sh了,还是报:
– Could not find the required component ‘livox_ros_driver’. The following CMake error indicates that you either need to install the package with the same name or change your environment so that it can be found.
CMake Error at /opt/ros/noetic/share/catkin/cmake/catkinConfig.cmake:83 (find_package):
Could not find a package configuration file provided by “livox_ros_driver”
with any of the following names:
livox_ros_driverConfig.cmake
livox_ros_driver-config.cmake
R:
替换livox_ros_driver驱动:point-lio下提供:
find . -type f ! -name “*.patch”|xargs sed -i “s/livox_ros_driver/livox_ros_driver2/g”
fatal error: numpy/arrayobject.h: No such file or directory
19 | # include <numpy/arrayobject.h>
R:
在CMakelist.txt 对应添加如下内容:
set(Python3_ROOT_DIR "/opt/conda")
find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "import numpy; print(numpy.get_include())"
OUTPUT_VARIABLE NUMPY_INCLUDE_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
include_directories( add:
${NUMPY_INCLUDE_DIR}
urdf
<robot name="mid360_simple">
<material name="orange">
<color rgba="1.0 0.5 0.2 1" />
</material>
<material name="gray">
<color rgba="0.2 0.2 0.2 1" />
</material>
<material name="green">
<color rgba="0.0 1.0 0.0 1" />
</material>
<link name="base_link" />
<link name="livox_frame">
<visual>
<origin xyz="0.0 0.0 0.0" />
<geometry>
<cylinder length="0.07" radius="0.05" />
</geometry>
<material name="gray" />
</visual>
</link>
<link name="imu_link">
<visual>
<origin xyz="0.0 0.0 0.0" />
<geometry>
<box size="0.06 0.04 0.02" />
</geometry>
<material name="orange" />
</visual>
</link>
<!-- imu livox -->
<joint name="imu_link_joint" type="fixed">
<parent link="base_link" />
<child link="imu_link" />
<origin xyz="0 0 0" rpy="0 0 0" />
</joint>
<joint name="livox_frame_joint" type="fixed">
<parent link="base_link" />
<child link="livox_frame" />
<origin xyz="0 0 0" rpy="0 0 0" />
</joint>
</robot>
2D
关键问题
cartographer的map是对齐重力的,即对齐铅垂线。
将3D雷达通过pointcloud_to_laserscan转为2D scan时,当雷达出现俯仰和滚转时,会使得获得到的scan的横截面发生改变,继而影响scan匹配。
因而使用这种方法时,切记雷达的固连,且尽量不要使机体出现。
更新:
<launch>
<param name="/use_sim_time" value="false" /> <!-- true: offline (rosbag); false: online -->
<node pkg="tf2_ros" type="static_transform_publisher" name="lidar2bases"
args="0 0 0 0 -0.298 0 livox_frame base_link_alignG" />
<node pkg="pointcloud_to_laserscan" type="pointcloud_to_laserscan_node" name="pointcloud_to_laserscan"
respawn="false" output="screen">
<remap from="cloud_in" to="/livox/lidar"/>
<!--remap from="cloud_in" to="/rslidar_points"/-->
<!--remap from="/scan" to="/headLaserScan"/-->
<rosparam>
target_frame: base_link_alignG
transform_tolerance: 0.1
min_height: 0.1
max_height: 1.5
angle_min: -3.14159
angle_max: 3.14159
angle_increment: 0.0175
scan_time: 0.1
range_min: 0.5
range_max: 30.0
use_inf: false
# Concurrency level, affects number of pointclouds queued for processing and number of threads used
# 0 : Detect number of cores
# 1 : Single threaded
# 2->inf : Parallelism level
concurrency_level: 1
</rosparam>
</node>
<param name="robot_description" textfile="$(find cartographer_ros)/urdf/mid360_simple.urdf" />
<node name="joint_state_publisher" pkg="joint_state_publisher"
type="joint_state_publisher" />
<node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" />
<node name="cartographer_node" pkg="cartographer_ros" type="cartographer_node"
args="-configuration_directory $(find cartographer_ros)/configuration_files
-configuration_basename mid360_simple_2d.lua"
output="screen">
<!--remap from="/scan" to="/headLaserScan"/-->
<remap from="imu" to="/livox/imu" />
</node>
<!--TODO-->
<node name="cartographer_occupancy_grid_node" pkg="cartographer_ros"
type="cartographer_occupancy_grid_node" args="-resolution 0.075" />
<!--node name="rviz" pkg="rviz" type="rviz" required="true"
args="-d $(find cartographer_ros)/configuration_files/mid360_2d.rviz" /-->
<!--node name="playbag" pkg="rosbag" type="play" args="--><!--clock -s 142 $(arg bagfile)" /-->
</launch>
include "map_builder.lua"
include "trajectory_builder.lua"
options = {
map_builder = MAP_BUILDER,
trajectory_builder = TRAJECTORY_BUILDER,
map_frame = "map",
tracking_frame = "imu_link",
published_frame = "base_link",
odom_frame = "odom",
provide_odom_frame = true,
--provide_tracking_frame_alignG = true,
publish_frame_projected_to_2d = false,
use_pose_extrapolator = false,
use_odometry = false,
use_nav_sat = false,
use_landmarks = false,
num_laser_scans = 1,
num_multi_echo_laser_scans = 0,
num_subdivisions_per_laser_scan = 1,
num_point_clouds = 0,
lookup_transform_timeout_sec = 0.2,
submap_publish_period_sec = 0.3,
pose_publish_period_sec = 5e-3,
trajectory_publish_period_sec = 30e-3,
rangefinder_sampling_ratio = 1.,
odometry_sampling_ratio = 1.,
fixed_frame_pose_sampling_ratio = 1.,
imu_sampling_ratio = 1.,
landmarks_sampling_ratio = 1.,
publish_tracked_pose=true,
}
TRAJECTORY_BUILDER_2D.use_imu_data = true
TRAJECTORY_BUILDER_2D.submaps.num_range_data = 50
TRAJECTORY_BUILDER_2D.min_range = 0.3
TRAJECTORY_BUILDER_2D.max_range = 30.
TRAJECTORY_BUILDER_2D.missing_data_ray_length = 1.
TRAJECTORY_BUILDER_2D.use_online_correlative_scan_matching = true
TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.linear_search_window = 0.2
TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.angular_search_window = 0.3
TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.translation_delta_cost_weight = 20.
TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.rotation_delta_cost_weight = 2
TRAJECTORY_BUILDER_2D.ceres_scan_matcher = {
occupied_space_weight = 2.5e4,
translation_weight = 3e3,
rotation_weight = 3e3,
ceres_solver_options = {
use_nonmonotonic_steps = false,
max_num_iterations = 20,
num_threads = 1,
},
}
TRAJECTORY_BUILDER_2D.motion_filter = {
max_time_seconds = 0.2, -- 最大时间间隔(默认0.5s)
max_distance_meters = 0.1, -- 最大距离间隔(默认0.4m)
max_angle_radians = 0.01, -- 最大角度间隔(默认0.05rad)
}
TRAJECTORY_BUILDER_2D.imu_gravity_time_constant = 3.
TRAJECTORY_BUILDER_2D.pose_extrapolator = {
use_imu_based = false, -- 2D true will error
constant_velocity = {
imu_gravity_time_constant = 1.,
pose_queue_duration = 0.001,
},
imu_based = {
pose_queue_duration = 5.,
gravity_constant = 1,
pose_translation_weight = 1.,
pose_rotation_weight = 1.,
imu_acceleration_weight = 1.,
imu_rotation_weight = 10.,
odometry_translation_weight = 1.,
odometry_rotation_weight = 1.,
solver_options = {
use_nonmonotonic_steps = false;
max_num_iterations = 10;
num_threads = 1;
},
},
}
POSE_GRAPH.optimization_problem.huber_scale = 1e2
POSE_GRAPH.optimize_every_n_nodes = 55
POSE_GRAPH.constraint_builder.min_score = 0.35
POSE_GRAPH.optimization_problem.use_online_imu_extrinsics_in_3d = true
MAP_BUILDER.use_trajectory_builder_2d = true
MAP_BUILDER.num_background_threads = 6
MAP_BUILDER.use_trajectory_builder_2d = true
return options
新
include "map_builder.lua"
include "trajectory_builder.lua"
options = {
map_builder = MAP_BUILDER,
trajectory_builder = TRAJECTORY_BUILDER,
map_frame = "map",
tracking_frame = "imu_link",
published_frame = "base_link",
odom_frame = "odom",
provide_odom_frame = true,
publish_frame_projected_to_2d = false,
use_pose_extrapolator = false,
use_odometry = false,
use_nav_sat = false,
use_landmarks = false,
num_laser_scans = 1,
num_multi_echo_laser_scans = 0,
num_subdivisions_per_laser_scan = 1,
num_point_clouds = 0,
lookup_transform_timeout_sec = 0.2,
submap_publish_period_sec = 0.3,
pose_publish_period_sec = 5e-3,
trajectory_publish_period_sec = 30e-3,
rangefinder_sampling_ratio = 1.,
odometry_sampling_ratio = 1.,
fixed_frame_pose_sampling_ratio = 1.,
imu_sampling_ratio = 1.,
landmarks_sampling_ratio = 1.,
publish_tracked_pose=true,
}
TRAJECTORY_BUILDER_2D.use_imu_data = true
TRAJECTORY_BUILDER_2D.submaps.num_range_data = 35
TRAJECTORY_BUILDER_2D.min_range = 0.3
TRAJECTORY_BUILDER_2D.max_range = 30.
TRAJECTORY_BUILDER_2D.missing_data_ray_length = 1.
TRAJECTORY_BUILDER_2D.use_online_correlative_scan_matching = true
TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.linear_search_window = 0.1
TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.translation_delta_cost_weight = 10.
TRAJECTORY_BUILDER_2D.real_time_correlative_scan_matcher.rotation_delta_cost_weight = 1e-1
--TRAJECTORY_BUILDER_2D = {
-- use_imu_data = true,
-- min_range = 0.01, -- 0.3
-- max_range = 30,
--
-- T_lidar_imu = {
-- translation = {0.012561, -0.006829, 0.078586},
-- rotation = {0.9999, -0.0060, 0.0034, 0.0123},
-- },
-- imu_gravity_time_constant = 3.0,
-- gravity_constant = 9.80665,
-- gravity_constant = 9.774088,
-- initial_imu_bias = {
-- gyroscope = {-0.033351, 0.008406, 0.003908},
-- accelerometer = {-0.014032, -0.007099, 0.001429},
-- },
-- use_online_time_calibration = true,
-- imu_to_lidar_time_offset = -0.018201,
-- num_accumulated_range_data = 60,
-- ceres_scan_matcher = {
-- occupied_space_weight = 1.,
-- translation_weight = 1.,
-- rotation_weight = 1.,
-- }
-- --submaps.num_range_data = 80,
-- --submaps.grid_options_2d.resolution = 0.1
--}
--POSE_GRAPH = {
-- optimization_problem = {
-- huber_scale = 5e2,
-- ceres_solver_options = { max_num_iterations = 10 }
-- },
-- optimize_every_n_nodes = 8,
--
-- constraint_builder = {
-- min_score = 0.55,
-- sampling_ratio = 0.03,
-- global_localization_min_score = 0.66,
-- }
--}
TRAJECTORY_BUILDER_2D.imu_gravity_time_constant = 1.
TRAJECTORY_BUILDER_2D.pose_extrapolator = {
use_imu_based = false, -- 2D must be false
constant_velocity = {
imu_gravity_time_constant = 1.,
pose_queue_duration = 0.001,
},
imu_based = {
pose_queue_duration = 5.,
gravity_constant = 1,
pose_translation_weight = 1.,
pose_rotation_weight = 1.,
imu_acceleration_weight = 1.,
imu_rotation_weight = 10.,
odometry_translation_weight = 1.,
odometry_rotation_weight = 1.,
solver_options = {
use_nonmonotonic_steps = false;
max_num_iterations = 10;
num_threads = 1;
},
},
}
POSE_GRAPH.optimization_problem.huber_scale = 1e2
POSE_GRAPH.optimize_every_n_nodes = 35
POSE_GRAPH.constraint_builder.min_score = 0.65
MAP_BUILDER.use_trajectory_builder_2d = true
MAP_BUILDER.num_background_threads = 8
MAP_BUILDER.use_trajectory_builder_2d = true
return options
mid360_simple_2d.lua
include "map_builder.lua"
include "trajectory_builder.lua"
options = {
map_builder = MAP_BUILDER, -- map_builder.lua的配置信息
trajectory_builder = TRAJECTORY_BUILDER, -- trajectory_builder.lua的配置信息
map_frame = "map", -- 地图坐标系的名字
tracking_frame = "imu_link", -- 将所有传感器数据转换到这个坐标系下 有imu_link就用imu_link
published_frame = "base_link", -- tf: map -> footprint
odom_frame = "odom", -- 里程计的坐标系名字
provide_odom_frame = true, -- 是否提供odom的tf, 如果为true, 则tf树为map->odom->footprint
-- 如果为false tf树为map->footprint
publish_frame_projected_to_2d = false, -- 是否将坐标系投影到平面上
use_pose_extrapolator = false, -- 发布tf时是使用pose_extrapolator的位姿还是前端计算出来的位姿
use_odometry = false, -- 是否使用里程计,如果使用要求一定要有odom的tf
use_nav_sat = false, -- 是否使用gps
use_landmarks = false, -- 是否使用landmark
num_laser_scans = 1, -- 是否使用单线激光数据
num_multi_echo_laser_scans = 0, -- 是否使用multi_echo_laser_scans数据
num_subdivisions_per_laser_scan = 1, -- 1帧数据被分成几次处理,一般为1
num_point_clouds = 0, -- 是否使用点云数据
lookup_transform_timeout_sec = 0.2, -- 查找tf时的超时时间
submap_publish_period_sec = 0.3, -- 发布数据的时间间隔
pose_publish_period_sec = 5e-3,
trajectory_publish_period_sec = 30e-3,
rangefinder_sampling_ratio = 1., -- 传感器数据的采样频率
odometry_sampling_ratio = 1.,
fixed_frame_pose_sampling_ratio = 1.,
imu_sampling_ratio = 1.,
landmarks_sampling_ratio = 1.,
publish_tracked_pose=true,
}
MAP_BUILDER.num_background_threads = 8
MAP_BUILDER.use_trajectory_builder_2d = true
TRAJECTORY_BUILDER_2D.use_imu_data = true
TRAJECTORY_BUILDER_2D.min_range = 0.01 -- 0.3
TRAJECTORY_BUILDER_2D.max_range = 30.
TRAJECTORY_BUILDER_2D.min_z = 0.05
TRAJECTORY_BUILDER_2D.max_z = 1.1
--TRAJECTORY_BUILDER_2D.voxel_filter_size = 0.02
--TRAJECTORY_BUILDER_2D.adaptive_voxel_filter.max_length = 0.5
--TRAJECTORY_BUILDER_2D.adaptive_voxel_filter.min_num_points = 200.
--TRAJECTORY_BUILDER_2D.adaptive_voxel_filter.max_range = 50.
--TRAJECTORY_BUILDER_2D.loop_closure_adaptive_voxel_filter.max_length = 0.9
--TRAJECTORY_BUILDER_2D.loop_closure_adaptive_voxel_filter.min_num_points = 100
--TRAJECTORY_BUILDER_2D.loop_closure_adaptive_voxel_filter.max_range = 50.
TRAJECTORY_BUILDER_2D.use_online_correlative_scan_matching = true
TRAJECTORY_BUILDER_2D.ceres_scan_matcher.occupied_space_weight = 1.
TRAJECTORY_BUILDER_2D.ceres_scan_matcher.translation_weight = 1.
TRAJECTORY_BUILDER_2D.ceres_scan_matcher.rotation_weight = 1.
--TRAJECTORY_BUILDER_2D.ceres_scan_matcher.ceres_solver_options.max_num_iterations = 12
--TRAJECTORY_BUILDER_2D.motion_filter.max_distance_meters = 0.1
--TRAJECTORY_BUILDER_2D.motion_filter.max_angle_radians = 0.004
--TRAJECTORY_BUILDER_2D.imu_gravity_time_constant = 1.
TRAJECTORY_BUILDER_2D.submaps.num_range_data = 80.
TRAJECTORY_BUILDER_2D.submaps.grid_options_2d.resolution = 0.1
POSE_GRAPH.optimize_every_n_nodes = 160.
POSE_GRAPH.constraint_builder.sampling_ratio = 0.3
POSE_GRAPH.constraint_builder.max_constraint_distance = 15.
POSE_GRAPH.constraint_builder.min_score = 0.48
POSE_GRAPH.constraint_builder.global_localization_min_score = 0.60
return options
mid360_simple_2d.launch
<launch>
<param name="/use_sim_time" value="false" /> <!-- true: offline (rosbag); false: online -->
<node pkg="pointcloud_to_laserscan" type="pointcloud_to_laserscan_node" name="pointcloud_to_laserscan"
respawn="false" output="screen">
<remap from="cloud_in" to="/livox/lidar"/>
<!--remap from="cloud_in" to="/rslidar_points"/-->
<!--remap from="/scan" to="/headLaserScan"/-->
<rosparam>
target_frame: livox_frame
transform_tolerance: 0.1
min_height: 0.0
max_height: 1.0
angle_min: -3.14159
angle_max: 3.14159
angle_increment: 0.0175
scan_time: 0.1
range_min: 0.5
range_max: 30.0
use_inf: false
# Concurrency level, affects number of pointclouds queued for processing and number of threads used
# 0 : Detect number of cores
# 1 : Single threaded
# 2->inf : Parallelism level
concurrency_level: 1
</rosparam>
</node>
<param name="robot_description" textfile="$(find cartographer_ros)/urdf/mid360_simple.urdf" />
<node name="joint_state_publisher" pkg="joint_state_publisher"
type="joint_state_publisher" />
<node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" />
<node name="cartographer_node" pkg="cartographer_ros" type="cartographer_node"
args="-configuration_directory $(find cartographer_ros)/configuration_files
-configuration_basename mid360_simple_2d.lua"
output="screen">
<!--remap from="/scan" to="/headLaserScan"/-->
<remap from="imu" to="/livox/imu" />
</node>
<!--TODO-->
<!--node pkg="tf2_ros" type="static_transform_publisher" name="lidar2bases"
args="0 0 0 0 0 0 lidar_link rslidar" /-->
<!--node pkg="tf2_ros" type="static_transform_publisher" name="lidar2basei"
args="0 0 0 0 0 0 imu_link livox_frame" /-->
<node name="cartographer_occupancy_grid_node" pkg="cartographer_ros"
type="cartographer_occupancy_grid_node" args="-resolution 0.075" />
<node name="rviz" pkg="rviz" type="rviz" required="true"
args="-d $(find cartographer_ros)/configuration_files/demo_2d.rviz" />
<!--node name="playbag" pkg="rosbag" type="play" args="--><!--clock -s 142 $(arg bagfile)" /-->
</launch>
3D
mid360_simple_3d.lua
新的:
include "map_builder.lua"
include "trajectory_builder.lua"
options = {
map_builder = MAP_BUILDER,
trajectory_builder = TRAJECTORY_BUILDER,
map_frame = "map",
tracking_frame = "livox_frame",
published_frame = "base_link",
odom_frame = "odom",
provide_odom_frame = true,
use_pose_extrapolator = false, --位姿外推,用加计积分
use_odometry = false,
use_nav_sat = false,
use_landmarks = false,
num_laser_scans = 0,
num_point_clouds = 1,
num_multi_echo_laser_scans = 0,
num_subdivisions_per_laser_scan = 1,
publish_frame_projected_to_2d = false,
lookup_transform_timeout_sec = 0.2,
submap_publish_period_sec = 0.3,
pose_publish_period_sec = 5e-3,
trajectory_publish_period_sec = 30e-3,
rangefinder_sampling_ratio = 1.,
odometry_sampling_ratio = 1.,
fixed_frame_pose_sampling_ratio = 1.,
imu_sampling_ratio = 1.,
landmarks_sampling_ratio = 1.,
publish_tracked_pose=true,
}
MAP_BUILDER.use_trajectory_builder_3d = true
MAP_BUILDER.num_background_threads = 8
TRAJECTORY_BUILDER_3D.min_range = 0.1
TRAJECTORY_BUILDER_3D.max_range = 30.0
TRAJECTORY_BUILDER_3D.imu_gravity_time_constant = 1.
TRAJECTORY_BUILDER_3D.pose_extrapolator.use_imu_based = true
TRAJECTORY_BUILDER_3D.pose_extrapolator.imu_based = {
pose_queue_duration = 5.,
gravity_constant = 1, -- livox imu acc norm = 1
pose_translation_weight = 1.,
pose_rotation_weight = 10.,
imu_acceleration_weight = 1.,
imu_rotation_weight = 10.,
odometry_translation_weight = 1.,
odometry_rotation_weight = 1.,
solver_options = {
use_nonmonotonic_steps = false;
max_num_iterations = 10;
num_threads = 8;
},
}
TRAJECTORY_BUILDER_3D.motion_filter = {
max_time_seconds = 10.0,
max_distance_meters = 0.1,
max_angle_radians = math.rad(0.5),
}
POSE_GRAPH.optimization_problem = {
huber_scale = 1e1,
acceleration_weight = 1.1e2,
huber_scale = 1e1,
acceleration_weight = 1.1e2,
rotation_weight = 1.6e4,
local_slam_pose_translation_weight = 1e5,
local_slam_pose_rotation_weight = 1e5,
odometry_translation_weight = 1e5,
odometry_rotation_weight = 1e5,
fixed_frame_pose_translation_weight = 1e1,
fixed_frame_pose_rotation_weight = 1e2,
fixed_frame_pose_use_tolerant_loss = false,
fixed_frame_pose_tolerant_loss_param_a = 1,
fixed_frame_pose_tolerant_loss_param_b = 1,
log_solver_summary = false,
use_online_imu_extrinsics_in_3d = true, --自动标定imu
fix_z_in_3d = false,
ceres_solver_options = {
use_nonmonotonic_steps = false,
max_num_iterations = 50,
num_threads = 8,
},
}
POSE_GRAPH.optimize_every_n_nodes = 100
POSE_GRAPH.constraint_builder.min_score = 0.60
POSE_GRAPH.constraint_builder.sampling_ratio = 0.3
POSE_GRAPH.constraint_builder.global_localization_min_score = 0.66
POSE_GRAPH.constraint_builder.max_constraint_distance = 10.0
return options
include "map_builder.lua"
include "trajectory_builder.lua"
options = {
map_builder = MAP_BUILDER,
trajectory_builder = TRAJECTORY_BUILDER,
map_frame = "map",
tracking_frame = "imu_link",
published_frame = "base_link",
odom_frame = "odom",
provide_odom_frame = true,
publish_frame_projected_to_2d = false,
use_pose_extrapolator = true,
use_odometry = false,
use_nav_sat = false,
use_landmarks = false,
num_laser_scans = 0,
num_multi_echo_laser_scans = 0,
num_subdivisions_per_laser_scan = 1,
num_point_clouds = 1,
lookup_transform_timeout_sec = 0.2,
submap_publish_period_sec = 0.3,
pose_publish_period_sec = 5e-3,
trajectory_publish_period_sec = 30e-3,
rangefinder_sampling_ratio = 1.,
odometry_sampling_ratio = 1.,
fixed_frame_pose_sampling_ratio = 1.,
imu_sampling_ratio = 1.,
landmarks_sampling_ratio = 1.,
}
TRAJECTORY_BUILDER_3D.num_accumulated_range_data = 160
MAP_BUILDER.use_trajectory_builder_3d = true
MAP_BUILDER.num_background_threads = 7
POSE_GRAPH.optimization_problem.huber_scale = 5e2
POSE_GRAPH.optimize_every_n_nodes = 320
POSE_GRAPH.constraint_builder.sampling_ratio = 0.03
POSE_GRAPH.optimization_problem.ceres_solver_options.max_num_iterations = 10
POSE_GRAPH.constraint_builder.min_score = 0.62
POSE_GRAPH.constraint_builder.global_localization_min_score = 0.66
return options
<launch>
<param name="/use_sim_time" value="false" /> <!-- true: offline (rosbag); false: online -->
<param name="robot_description"
textfile="$(find cartographer_ros)/urdf/mid360_simple.urdf" />
<node name="joint_state_publisher" pkg="joint_state_publisher"
type="joint_state_publisher" />
<node name="robot_state_publisher" pkg="robot_state_publisher"
type="robot_state_publisher" />
<node name="cartographer_node" pkg="cartographer_ros"
type="cartographer_node" args="
-configuration_directory $(find cartographer_ros)/configuration_files
-configuration_basename mid360_simple_3d.lua"
output="screen">
<remap from="points2" to="/livox/lidar" />
<remap from="imu" to="/livox/imu" />
</node>
<node name="cartographer_occupancy_grid_node" pkg="cartographer_ros"
type="cartographer_occupancy_grid_node" args="-resolution 0.05" />
<node name="rviz" pkg="rviz" type="rviz" required="true"
args="-d $(find cartographer_ros)/configuration_files/demo_3d.rviz" />
<!--node name="playbag" pkg="rosbag" type="play" args="--><!--clock -s 142 $(arg bagfile)" /-->
</launch>
说明:cartographer 2D和3D的lua对应的配置项不一样。
详细的差别可以通过对比查看trajectory_builder_2d.lua和trajectory_builder_3d.lua。
Cartographer 的 3D 配置中确实没有 use_imu_data 和 initial_imu_bias 这两个参数。在 3D 模式下,IMU 数据是必须的,系统会默认使用,因此不需要单独的开关选项。
相关链接
LiDAR_IMU_Init标定: https://blog.csdn.net/qq_46238675/article/details/148529340
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)