pre和next

std::prev

原型template <class BidirectionalIterator> BidirectionalIterator prev (BidirectionalIterator it, typename iterator_traits<BidirectionalIterator>::difference_type n = 1);

该函数会返回一个距离 it 迭代器 n 个元素的新迭代器。当 n 为正数时,其返回的迭代器将位于 it 左侧;反之,当 n 为负数时,其返回的迭代器位于 it 右侧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>     // std::cout
#include <iterator> // std::next
#include <list> // std::list
using namespace std;
int main() {
//创建并初始化一个 list 容器
std::list<int> mylist{ 1,2,3,4,5 };
std::list<int>::iterator it = mylist.end();
//获取一个距离 it 迭代器 2 个元素的迭代器,由于 2 为正数,newit 位于 it 左侧
auto newit = prev(it, 2);
cout << "prev(it, 2) = " << *newit << endl; // 4

//n为负数,newit 位于 it 右侧
it = mylist.begin();
newit = prev(it, -2);
cout << "prev(it, -2) = " << *newit; // 3
return 0;
}

prev() 函数自身不会检验新迭代器的指向是否合理,需要我们自己来保证其合理性。

next()函数和prev()函数恰好相反


pbstream_main的使用

旧版本为cartographer_pbstream

源码是pbstream_main.cc,又实际调用了pbstream_info.ccpbstream_migrate.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(int argc, char** argv)
{
google::InitGoogleLogging(argv[0]);

FLAGS_logtostderr = true;
const std::string usage_message =
"Swiss Army knife for pbstreams.\n\n"
"Currently supported subcommands are:\n"
"\tinfo - Prints summary of pbstream.\n"
"\tmigrate - Migrates old pbstream (w/o header) to new pbstream format.";
google::ParseCommandLineFlags(&argc, &argv, true);

if (argc < 2) {
google::SetUsageMessage(usage_message);
google::ShowUsageWithFlagsRestrict(argv[0], "pbstream_info_main");
return EXIT_FAILURE;
} else if (std::string(argv[1]) == "info") {
return ::cartographer::io::pbstream_info(argc, argv);
} else if (std::string(argv[1]) == "migrate") {
return ::cartographer::io::pbstream_migrate(argc, argv);
}
// 其他情况会报错
}

可执行文件在carto_ws/install_isolated/bin

pbstream_main info

终端执行pbstream_main info,调用pbstream_info:打印出pbstream文件的概况,路径支持环境变量。

1
./pbstream_main info $MAPS/file.pbstream

结果的中间一大部分是采用的lua参数,如果不想显示这些参数,注释掉源码里的LOG(INFO) << "Trajectory options: " << trajectory_options.DebugString();

结果如下:

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
Reading pbstream file from '/home/xiaoqiang/carto_ws/maps/file.pbstream'...
Header: format_version: 2
Trajectory options: sensor_id {
id: "scan"
}
sensor_id {
type: IMU
id: "imu"
}
sensor_id {
type: ODOMETRY
id: "odom"
}

# ......省略lua参数
Trajectory id: 0 has #nodes 9778 has #submaps 161
SerializedData package contains #node: 9778
SerializedData package contains #odometry_data: 49697

SerializedData package contains #submap: 161
SerializedData package contains #submap_2d: 161
SerializedData package contains #submap_2d_grid: 161

SerializedData package contains #submap_3d: 0
SerializedData package contains #submap_3d_high_resolution_hybrid_grid: 0
SerializedData package contains #trajectory_data: 1

不要在最后加-all_debug_strings=true,否则打印出无穷无尽的内容,包括了栅格值和子图转换表等等。

pbstream_main migrate

终端执行pbstream_main migrate,调用pbstream_migrate:把pbstream文件移植到新的子图格式。把未使用histogram的子图文件移植为包含histogram的子图格式。如果想在输出文件中排除未完成的子图,设置--include_unfinished_submaps为false


ninja

如果没有ninja,在使用时会报错: CMake was unable to find a build program corresponding to "Ninja". CMAKE_MAKE_PROGRAM is not set. You probably need to select a different build tool.

安装

ninja需要依赖于re2c,否则编译是会报错,re2c是一款语法分析器,官网地址是:http://re2c.org/
下载页面的安装说明都在: http://re2c.org/install/install.html, 这里直接从源码安装最新版本

安装re2c:

1
2
3
4
5
git clone https://github.com/skvadrik/re2c
cd re2c
./autogen.sh
./configure make
make install

可以从ninja的github仓库切换到release分支或者使用git下载:

1
2
git clone https://github.com/ninja-build/ninja.git
cd ninja

采用python的安装方式
1
./configure.py --bootstrap

等待完成即可

ninja比较精简,只需要一个可执行文件即可,现在可以做软链或者复制到/usr/bin下面就可以直接调用了

1
cp ninja /usr/bin/ 


cartographer_offline_node

cartographer_offline_node对应offline_node_main.cc,它和cartographer_grpc_offline_node对应的offline_node_grpc_main.cc极为相似

在线和离线的建图效果无区别。离线节点直接从bag读取传感器数据,使用了CPU能实现的最大速度处理bag,而且不会丢失消息,不过无法设置速度和看不清建图过程的细节。 如果想控制处理速度,就使用在线节点和rosbag play -r 2,但是如果设置速度太快,可能会丢失消息,这是很常见的。

简单看了源码,离线节点之所以运行快,是因为使用了ROS多线程ros::AsyncSpinner,而在线节点还是普通的ROS程序

运行示例

1
2
wget -P ~/Downloads https://storage.googleapis.com/cartographer-public-data/bags/backpack_2d/b2-2016-04-05-14-44-52.bag
roslaunch cartographer_ros offline_backpack_2d.launch bag_filenames:=${HOME}/Downloads/b2-2016-04-05-14-44-52.bag

此时的CPU占用果然要爆了,所以说只适合运行数据集,不适合需要优化cartographer的场合
CPU占用.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "cartographer/mapping/map_builder.h"
#include "cartographer_ros/offline_node.h"
#include "cartographer_ros/ros_log_sink.h"
#include "gflags/gflags.h"
#include "ros/ros.h"

int main(int argc, char** argv)
{
google::InitGoogleLogging(argv[0]);
google::ParseCommandLineFlags(&argc, &argv, true);

::ros::init(argc, argv, "cartographer_offline_node");
::ros::start();
cartographer_ros::ScopedRosLogSink ros_log_sink;

const cartographer_ros::MapBuilderFactory map_builder_factory =
[](const ::cartographer::mapping::proto::MapBuilderOptions&
map_builder_options) {
return absl::make_unique< ::cartographer::mapping::MapBuilder>(
const_cast<::cartographer::mapping::proto::MapBuilderOptions&>(map_builder_options));
};
cartographer_ros::RunOfflineNode(map_builder_factory);
::ros::shutdown();
}

参数

RunOfflineNode函数转到了offline_node.cc,这个文件主要就是这个函数。这个文件开头是一堆宏定义,根据字符串解释每个可用的参数

  • configuration_directory
  • configuration_basenames
  • load_state_filename

  • bag_filenames: Comma-separated list of bags to process. One bag per trajectory. Any combination of simultaneous and sequential bags is supported.

  • urdf_filenames: Comma-separated list of one or more URDF files that contain. static links for the sensor configurations

  • use_bag_transforms: 默认true,Whether to read, use and republish transforms from bags

  • keep_running: 默认false,最好改为true。Keep running the offline node after all messages from the bag have been processed

  • load_frozen_state: 默认true,Load the saved state as frozen (non-optimized) trajectories

  • skip_seconds: 默认0,Optional amount of seconds to skip from the beginning (i.e. when the earliest bag starts)

一个配置了两个机器人的的launch的关键部分是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<node name="cartographer_offline_node" pkg="cartographer_ros"
required="$(arg no_rviz)"
type="cartographer_offline_node" args="
-configuration_directory $(arg abspath)
-configuration_basenames alpha-cartographer.lua,delta-cartographer.lua
-urdf_filenames $(arg abspath)/pioneer-alpha.urdf,$(arg abspath)/pioneer-delta.urdf
-bag_filenames $(arg abspath)/alpha.bag,$(arg abspath)/delta.bag
-keep_running=true -use_bag_transforms=false"
output="screen">
<remap from="bag_1_scan" to="alpha/scan" />
<remap from="bag_1_odom" to="alpha/pose" />
<remap from="bag_2_scan" to="delta/scan" />
<remap from="bag_2_odom" to="delta/pose" />
</node>

话题和service

Publications:

  • cartographer_offline_node/bagfile_progress [cartographer_ros_msgs/BagfileProgress]
  • clock [rosgraph_msgs/Clock]
  • constraint_list [visualization_msgs/MarkerArray]
  • landmark_poses_list [visualization_msgs/MarkerArray]
  • rosout [rosgraph_msgs/Log]
  • scan_matched_points2 [sensor_msgs/PointCloud2]
  • submap_list [cartographer_ros_msgs/SubmapList]
  • tf [tf2_msgs/TFMessage]
  • tf_static [tf2_msgs/TFMessage]
  • tracked_pose [geometry_msgs/PoseStamped]
  • trajectory_node_list [visualization_msgs/MarkerArray]

Subscriptions:

  • clock [rosgraph_msgs/Clock]

Services:

  • finish_trajectory
  • get_trajectory_states
  • read_metrics
  • start_trajectory
  • submap_query
  • trajectory_query
  • write_state

离线节点是直接在程序里读取消息,所以它没有像rosbag play那样把bag里的话题发布出来,我运行bag时,因为还需要运行一个节点对bag里的话题进行类型转换,而话题又没发布出来,所以离线节点实际无法使用了。

源码的重要部分

1
2
3
4
5
6
7
8
9
constexpr char kClockTopic[] = "clock";
constexpr char kTfStaticTopic[] = "/tf_static";
constexpr char kTfTopic[] = "tf";
constexpr double kClockPublishFrequencySec = 1. / 30.;
constexpr int kSingleThreaded = 1;
// We publish tf messages one second earlier than other messages. Under
// the assumption of higher frequency tf this should ensure that tf can
// always interpolate.
const ::ros::Duration kDelay = ::ros::Duration(1.0);

sort函数

sort使用快速排序的递归形式,时间复杂度是O(nlog(n) )

Sort函数有三个参数:(第三个参数可不写)

  1. 第一个是要排序的数组的起始地址。
  2. 第二个是结束的地址(最后一位要排序的地址)
  3. 第三个参数是排序的方法,可以是从大到小也可是从小到大,还可以不写第三个参数,此时默认的排序方法是从小到大排序。
1
2
3
int b = 3, c = 12;
qDebug() << std::greater<int>()(b, c);
qDebug() << std::less<int>()(b, c);
1
2
3
4
5
6
7
8
int a[]={3,4,1,12,0,8,4};
// 默认从小到大排列
std::sort(a, a+7);
// 从大到小
std::sort(a, a+7, std::greater<int>());
for(int i=0; i < 7; i++)
cout << a[i] <<" ";
// 0 1 3 4 4 8 12

针对自定义的数据类型,需要自定义排序的方式

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
struct Frontier
{
Frontier(double c):
cost(c){}

double cost;
};

bool compareFrontier(Frontier f1, Frontier f2)
{
return (f1.cost < f2.cost);
}
void showFrontiers(const vector<Frontier>& v)
{
for(auto f: v)
cout << f.cost << " ";
cout << endl;
}

vector<Frontier> frontiers;
frontiers.push_back(Frontier(1.2) );
frontiers.push_back(Frontier(4.9) );
frontiers.push_back(Frontier(12.7) );
frontiers.push_back(Frontier(0.3) );
frontiers.push_back(Frontier(3.6) );
showFrontiers(frontiers);

std::sort(frontiers.begin(), frontiers.end(),
[](const Frontier& f1, const Frontier& f2) { return f1.cost < f2.cost; });
// std::sort(frontiers.begin(), frontiers.end(), compareFrontier);

showFrontiers(frontiers);

frontiers一般用vector或数组,不能是list, dequeue, queue

运行结果:

1
2
1.2  4.9  12.7  0.3  3.6  
0.3 1.2 3.6 4.9 12.7

reverse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <vector>
#include <iostream>
#include <iterator>
#include <algorithm>

int main()
{
std::vector<int> v({1,2,3});
std::reverse(std::begin(v), std::end(v));
std::cout << v[0] << v[1] << v[2] << '\n';

int a[] = {4, 5, 6, 7};
std::reverse(std::begin(a), std::end(a));
std::cout << a[0] << a[1] << a[2] << a[3] << '\n';
}

期刊会议的情况

机器人顶级期刊

IEEE Transactions on Robotics (TRO)

International Journal of Robotics Research (IJRR)

Journal of Field Robotics (JFR)

IEEE Robotics & Automation Magazine (RAM)

机器人次顶级期刊

Robotics and Autonomous Systems (RAS)

Autonomous Robots (AURO)

Robotics and Computer-Integrated Manufacturing (RCIM)

期刊影响因子应该至少为2

机器人顶级会议

Robotics: Science and Systems (RSS)

IEEE The International Conference on Robotics and Automation (ICRA)

IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS)

机器人次顶级会议

IEEE International Conference on Robotics and Biomimetics (ROBIO)

IEEE International Conference on Real-time Computing and Robotics (RCAR)

International Conference on Advanced Robotics (ICAR)

IEEE International Conference on CYBER Technology in Automation, Control, and Intelligent Systems (IEEE-CYBER)

IEEE International Conference on Multisensor Fusion and Integration for Intelligent Systems (MFI)

机器人一般会议

IEEE International Conference on Advanced Intelligent Mechatronics (AIM)

IEEE International Conference on Humanoid Robots (Humanoids)

International Conference on Climbing and Walking Robots (CLAWAR)

IEEE International Conference on Information and Automation (ICIA)

论文的相关工作一项也是有意义的。比如论文A,作者说他的方法很好,我看了之后找不出问题,后来又看到了论文B,B在介绍论文A时会提出A存在的问题,而它又如何避免了这种问题。 写的差的论文往往相关工作只是简单介绍,它和以往的论文没多大关系,或者介绍的论文价值不高。


轨迹的可视化

Node::PublishSubmapList: 这里的子图只有节点id和子图的节点数,没有地图数据

scan_matched_points2 话题

trajectory_node_list话题


结束轨迹

node_main.cc中的Run函数最后:

1
2
3
4
::ros::spin();
// 终止所有轨迹
node.FinishAllTrajectories();
node.RunFinalOptimization();

主要看FinishAllTrajectories

1
2
3
4
5
6
7
8
9
10
11
12
void Node::FinishAllTrajectories()
{
carto::common::MutexLocker lock(&mutex_);
for (auto& entry : is_active_trajectory_) {
const int trajectory_id = entry.first;
// 已经lock了,所以叫做 FinishTrajectoryUnderLock
if (entry.second) {
CHECK_EQ(FinishTrajectoryUnderLock(trajectory_id).code,
cartographer_ros_msgs::StatusCode::OK);
}
}
}

offline_node.cc 中还调用了一个Node::FinishTrajectory(是这个函数唯一被调用的地方),还有就是finish_trajectory服务的回调函数,以上3个函数实际都调用FinishTrajectoryUnderLock


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
cartographer_ros_msgs::StatusResponse Node::FinishTrajectoryUnderLock(
const int trajectory_id)
{
cartographer_ros_msgs::StatusResponse status_response;

// First, check if we can actually finish the trajectory
// trajectory_id 是否属于正在结束的轨迹集合,如果是 FROZEN 则返回
if (map_builder_bridge_.GetFrozenTrajectoryIds().count(trajectory_id))
{
const std::string error =
"Trajectory " + std::to_string(trajectory_id) + " is frozen.";
LOG(ERROR) << error;
status_response.code = cartographer_ros_msgs::StatusCode::INVALID_ARGUMENT;
status_response.message = error;
return status_response;
}
// trajectory_id 是否是 ACTIVE状态
if (is_active_trajectory_.count(trajectory_id) == 0)
{
const std::string error =
"Trajectory " + std::to_string(trajectory_id) + " is not created yet.";
LOG(ERROR) << error;
status_response.code = cartographer_ros_msgs::StatusCode::NOT_FOUND;
status_response.message = error;
return status_response;
}
// trajectory_id 是否已经 finish
if (!is_active_trajectory_[trajectory_id])
{
const std::string error = "Trajectory " + std::to_string(trajectory_id) +
" has already been finished.";
LOG(ERROR) << error;
status_response.code =
cartographer_ros_msgs::StatusCode::RESOURCE_EXHAUSTED;
status_response.message = error;
return status_response;
}
// Shutdown the subscribers of this trajectory.
for (auto& entry : subscribers_[trajectory_id])
{
entry.subscriber.shutdown();
subscribed_topics_.erase(entry.topic);
LOG(INFO) << "Shutdown the subscriber of [" << entry.topic << "]";
}
CHECK_EQ(subscribers_.erase(trajectory_id), 1);
CHECK(is_active_trajectory_.at(trajectory_id));
// 主要是这里
map_builder_bridge_.FinishTrajectory(trajectory_id);

is_active_trajectory_[trajectory_id] = false;
const std::string message =
"Finished trajectory " + std::to_string(trajectory_id) + ".";
status_response.code = cartographer_ros_msgs::StatusCode::OK;
status_response.message = message;
return status_response;
}

StatusResponse不正常的codeINVALID_ARGUMENT, NOT_FOUND, RESOURCE_EXHAUSTED, 只有OK是正常的

其中的关键函数就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void MapBuilderBridge::FinishTrajectory(const int trajectory_id)
{
LOG(INFO) << "Finishing trajectory with ID '" << trajectory_id << "'...";
// Make sure there is a trajectory with 'trajectory_id'.
CHECK_EQ(sensor_bridges_.count(trajectory_id), 1);
map_builder_->FinishTrajectory(trajectory_id);
sensor_bridges_.erase(trajectory_id);
}

// 再看这个函数
void MapBuilder::FinishTrajectory(const int trajectory_id) {
sensor_collator_->FinishTrajectory(trajectory_id);
pose_graph_->FinishTrajectory(trajectory_id);
}

sensor_collator_就是Collator::FinishTrajectory 或者 TrajectoryCollator::FinishTrajectory,不用关注。

重要的是后端的同名函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void PoseGraph2D::FinishTrajectory(const int trajectory_id)
{
AddWorkItem([this, trajectory_id]() LOCKS_EXCLUDED(mutex_)
{
absl::MutexLock locker(&mutex_);
CHECK(!IsTrajectoryFinished(trajectory_id)); // 不能是已结束
data_.trajectories_state[trajectory_id].state = TrajectoryState::FINISHED;

for (const auto& submap : data_.submap_data.trajectory(trajectory_id)) {
data_.submap_data.at(submap.id).state = SubmapState::kFinished;
}
return WorkItem::Result::kRunOptimization;
});
}

这里又是全局优化的标志kRunOptimization,然后又去PoseGraph2D::HandleWorkQueue,也是一次优化过程了


map_builder_server.cc

map_builder_server.cc中注册了一大堆回调函数,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server_builder.RegisterHandler<handlers::GetLocalToGlobalTransformHandler>();

// get_local_to_global_transform_handler.cc 文件
void GetLocalToGlobalTransformHandler::OnRequest(
const proto::GetLocalToGlobalTransformRequest& request)
{
auto response = absl::make_unique<proto::GetLocalToGlobalTransformResponse>();
auto local_to_global =
GetContext<MapBuilderContextInterface>()
->map_builder()
.pose_graph()
->GetLocalToGlobalTransform(request.trajectory_id());
*response->mutable_local_to_global() = transform::ToProto(local_to_global);
Send(std::move(response));
}

GetLocalToGlobalTransform可用于获取local坐标系的原点

1
2
3
4
5
6
7
transform::Rigid3d PoseGraph2D::GetLocalToGlobalTransform(
const int trajectory_id) const
{
absl::MutexLock locker(&mutex_);
return ComputeLocalToGlobalTransform(data_.global_submap_poses_2d,
trajectory_id);
}

同样的还有 PoseGraph2D::GetAllSubmapPoses, PoseGraph2D::GetTrajectoryStates, PoseGraph2D::GetTrajectoryNodePoses, PoseGraph2D::GetLandmarkPoses(), PoseGraph2D::constraints()等等

cartographer\cartographer\cloud\internal\handlers目录有一堆文件,每个文件只定义了一个对应的回调函数,这个回调函数只在map_builder_server.cc中调用,最终是运行cartographer_grpc_server


metrics机制

若要启用metrics机制,必须在launch文件里添加参数-collect_metrics true,这个本质也是gflag

源码的顺序如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// node.cc中
if (collect_metrics)
{
metrics_registry_ = absl::make_unique<metrics::FamilyFactory>();
carto::metrics::RegisterAllMetrics(metrics_registry_.get());
}


// register.cc 中定义
void RegisterAllMetrics(FamilyFactory* registry) {
mapping::constraints::ConstraintBuilder2D::RegisterMetrics(registry);
mapping::GlobalTrajectoryBuilderRegisterMetrics(registry);
mapping::LocalTrajectoryBuilder2D::RegisterMetrics(registry);
mapping::PoseGraph2D::RegisterMetrics(registry);
sensor::TrajectoryCollator::RegisterMetrics(registry);
// 不需要可以注释掉
mapping::constraints::ConstraintBuilder3D::RegisterMetrics(registry);
mapping::LocalTrajectoryBuilder3D::RegisterMetrics(registry);
mapping::PoseGraph3D::RegisterMetrics(registry);
}

以上所有RegisterMetricslocal_trajectory_builder_2d.cc, pose_graph_2d.cc, constraint_builder_2d.cc(排除3D情况)定义了所有metrics参数


kLocalSlamCpuRealTimeRatio为例进行说明, 在local_trajectory_builder_2d.cc开始部分的声明:

1
static auto* kLocalSlamCpuRealTimeRatio = metrics::Gauge::Null();`

然后在LocalTrajectoryBuilder2D::RegisterMetrics

1
2
3
4
5
auto* cpu_real_time_ratio = family_factory->NewGaugeFamily(
"mapping_2d_local_trajectory_builder_cpu_real_time_ratio",
"sensor duration / cpu duration.");

kLocalSlamCpuRealTimeRatio = cpu_real_time_ratio->Add({});

最后是赋值部分

1
2
3
4
5
6
7
8
if (last_thread_cpu_time_seconds_.has_value()) {
const double thread_cpu_duration_seconds =
thread_cpu_time_seconds - last_thread_cpu_time_seconds_.value();
if (sensor_duration.has_value()) {
kLocalSlamCpuRealTimeRatio->Set(
common::ToSeconds(sensor_duration.value()) / thread_cpu_duration_seconds );
}
}

如果想查看metrics,只能rosservice call /read_metrics "{}",一次调用的结果部分:

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
name: "mapping_constraints_constraint_builder_2d_constraints"
description: "Constraints computed"
metrics:
type: 0
labels:
-
key: "matcher"
value: "searched"
-
key: "search_region"
value: "global"
value: 4206.0
counts_by_bucket: []
-
type: 0
labels:
-
key: "matcher"
value: "found"
-
key: "search_region"
value: "global"
value: 0.0
counts_by_bucket: []

-
name: "mapping_2d_pose_graph_constraints"
description: "Current number of constraints in the pose graph"
metrics:
-
type: 1
labels:
-
key: "tag"
value: "inter_submap"
-
key: "trajectory"
value: "different"
value: 0.0
counts_by_bucket: []

name: "mapping_constraints_constraint_builder_2d_num_submap_scan_matchers"
description: "Current number of constructed submap scan matchers"
metrics:
-
type: 1
labels: []
value: 18.0
counts_by_bucket: []
-
name: "mapping_2d_pose_graph_constraints"
description: "Current number of constraints in the pose graph"
metrics:
-
type: 1
labels:
-
key: "tag"
value: "inter_submap"
-
key: "trajectory"
value: "different"
value: 27.0
counts_by_bucket: []
-
type: 1
labels:
-
key: "tag"
value: "inter_submap"
-
key: "trajectory"
value: "same"
value: 10.0
counts_by_bucket: []
-
name: "mapping_constraints_constraint_builder_2d_scores"
description: "Constraint scores built"
metrics:
-
type: 2
labels:
-
key: "search_region"
value: "local"

获知不同轨迹之间的查找次数和找到的约束数目,源码在ConstraintBuilder2D::ComputeConstraint中的
1
2
kGlobalConstraintsFoundMetric->Increment();
kGlobalConstraintScoresMetric->Observe(score);

模仿源码,可以根据自己的需要添加 metrics

如果不需要3D的metrics,可以到ConstraintBuilder3D::RegisterMetrics, LocalTrajectoryBuilder3D::RegisterMetrics, PoseGraph3D::RegisterMetrics中return