讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下:
(02)Cartographer源码无死角解析- (00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/127350885
文 末 正 下 方 中 心 提 供 了 本 人 联 系 方 式 , 点 击 本 人 照 片 即 可 显 示 W X → 官 方 认 证 {\color{blue}{文末正下方中心}提供了本人 \color{red} 联系方式,\color{blue}点击本人照片即可显示WX→官方认证} 文末正下方中心提供了本人联系方式,点击本人照片即可显示WX→官方认证
在前面的博客中,还有很多细节的东西没有讲解,如下:
CollatedTrajectoryBuilder::AddSensorData() 函数中的sensor::MakeDispatchable() 函数
CollatedTrajectoryBuilder::AddData(std::unique_ptr<sensor::Data> data) 函数的参数sensor::Data类
CollatedTrajectoryBuilder::HandleCollatedSensorData() 中 rate_timers_ 变量的使用
那么接下来就会对他们进行一个纤细的分析。
首先来看 src/cartographer/cartographer/sensor/data.h 文件中的 class Data,这里就不啰嗦了,其就是一个接口类,该接口定义了两个纯虚函数,其只有一个成员 const std::string sensor_id_,该为 topic name。
该类的一个派生类位于 src/cartographer/cartographer/sensor/internal/dispatchable.h 中,值得注意的是该类为一个模板类,模板参数为 DataType。
template <typename DataType> //模板类,模板参数
class Dispatchable : public Data {//继承于类 data
public:
//构造函数,同时调用会调用父类的构造函数。对参数data赋值给成员变量data_
Dispatchable(const std::string &sensor_id, const DataType &data)
: Data(sensor_id), data_(data) {}
//重写父类函数,直接返回 data_.time 即可(表示DataType类型的数据,必须还有成员变量.time)
common::Time GetTime() const override { return data_.time; }
// 重写父类函数,调用传入的trajectory_builder的AddSensorData()
void AddToTrajectoryBuilder(
mapping::TrajectoryBuilderInterface *const trajectory_builder) override {
trajectory_builder->AddSensorData(sensor_id_, data_);
}
//返回成员变量data_的一个引用
const DataType &data() const { return data_; }
private:
const DataType data_;//构造函数初始化列表进行赋值,之后便不可再进行更改
};
另外,在 dispatchable.h 文件的下面,还可以看到如下代码:
// c++11: template
// 函数模板的调用使用 实参推演 来进行
// 类模板 模板形参的类型必须在类名后的尖括号中明确指定, 不能使用实参推演
// 在类外声明一个 函数模板, 使用 实参推演 的方式来使得 类模板可以自动适应不同的数据类型
// 根据传入的data的数据类型,自动推断DataType, 实现一个函数处理不同类型的传感器数据
template <typename DataType>
std::unique_ptr<Dispatchable<DataType>> MakeDispatchable(
const std::string &sensor_id, const DataType &data) {
return absl::make_unique<Dispatchable<DataType>>(sensor_id, data);
}
简单的说,其就是创建一个 Dispatchable类型的智能指针,该类型的模板参数为DataType。在创建时需要传入两个参数,分别为const std::string &sensor_id, 与 const DataType &data。
那么为什么要在 Dispatchable 的外面写这样一个函数呢?而不把他写在 class Dispatchable 之中呢?主要时应为类成员模板函数不具备自动推导模板参数的能力,而非成员函数,也就是普通函数时具备这个能力的。如CollatedTrajectoryBuilder.h 中如下一段代码:
// 处理雷达点云数据
void AddSensorData(
const std::string& sensor_id,
const sensor::TimedPointCloudData& timed_point_cloud_data) override {
AddData(sensor::MakeDispatchable(sensor_id, timed_point_cloud_data));
}
其调用了 sensor::MakeDispatchable(sensor_id, timed_point_cloud_data) 函数,其会根据 timed_point_cloud_data 的类型,自动推导 sensor::MakeDispatchable 的模板参数 DataType= sensor::TimedPointCloudData。但是类别成员函数时不具备这个能力的。
sensor::TimedPointCloudData 的定义如下:
// 时间同步前的点云
struct TimedPointCloudData {
common::Time time; // 点云最后一个点的时间
Eigen::Vector3f origin; // 以tracking_frame_到雷达坐标系的坐标变换为原点
TimedPointCloud ranges; // 数据点的集合, 每个数据点包含xyz与time, time是负的
// 'intensities' has to be same size as 'ranges', or empty.
std::vector<float> intensities; // 空的
};
与前面的推断一致,该结构体时存在成员变量 time 的。struct ImuData 同样包含成员变量time。
在 CollatedTrajectoryBuilder 中存在成员变量 rate_timers_,定义如下:
std::map<std::string, common::RateTimer<>> rate_timers_;
其是在回调函数 CollatedTrajectoryBuilder::HandleCollatedSensorData() 中被使用,该函数实现代码如下:
```cpp
/**
* @brief 处理 按照时间顺序分发出来的传感器数据
*
* @param[in] sensor_id 传感器的topic的名字
* @param[in] data 需要处理的数据(Data是个类模板,可处理多种不同数据类型的数据)
*/
void CollatedTrajectoryBuilder::HandleCollatedSensorData(
const std::string& sensor_id, std::unique_ptr<sensor::Data> data) {
auto it = rate_timers_.find(sensor_id);
// 找不到就新建一个
if (it == rate_timers_.end()) {
// map::emplace()返回一个pair
// emplace().first表示新插入元素或者原始位置的迭代器
// emplace().second表示插入成功,只有在key在map中不存在时才插入成功
it = rate_timers_
.emplace(
std::piecewise_construct,
std::forward_as_tuple(sensor_id),
std::forward_as_tuple(
common::FromSeconds(kSensorDataRatesLoggingPeriodSeconds)))
.first;
}
// 对数据队列进行更新
it->second.Pulse(data->GetTime());
if (std::chrono::steady_clock::now() - last_logging_time_ >
common::FromSeconds(kSensorDataRatesLoggingPeriodSeconds)) {
for (const auto& pair : rate_timers_) {
LOG(INFO) << pair.first << " rate: " << pair.second.DebugString();
}
last_logging_time_ = std::chrono::steady_clock::now();
}
// 也就是跑carto时候的消息:
// [ INFO]: collated_trajectory_builder.cc:72] imu rate: 10.00 Hz 1.00e-01 s +/- 4.35e-05 s (pulsed at 100.44% real time)
// [ INFO]: collated_trajectory_builder.cc:72] scan rate: 19.83 Hz 5.04e-02 s +/- 4.27e-05 s (pulsed at 99.82% real time)
// 将排序好的数据送入 GlobalTrajectoryBuilder中的AddSensorData()函数中进行使用
data->AddToTrajectoryBuilder(wrapped_trajectory_builder_.get());
}
其上的代码主要分为如下个部分(RateTimer位于src/cartographer/cartographer/common/internal/rate_timer.h):
( 1 ) : \color{blue}(1): (1): 其首先呢,会根据 sensor_id 在 rate_timers_ 这个字典中查找一下,是否已经创建了与 sensor_id 对应的 RateTimer 对象,如果没有则会实例化一个 RateTimer 对象,其传给构造函数的参数为kSensorDataRatesLoggingPeriodSeconds=15秒转换成标准时间的数据,然后赋值给成员变量RateTimer::window_duration_。
( 2 ) : \color{blue}(2): (2): 与当前 sensor_id 对应的 RateTimer 实例对象调用其成员函数 Pulse()。该函数如下所示:
// Records an event that will contribute to the computed rate.
// 对数据队列进行更新
void Pulse(common::Time time) {
// 将传入的时间放入队列中
events_.push_back(Event{time, ClockType::now()});
// 删除队列头部数据,直到队列中最后与最前间的时间间隔小于window_duration_
while (events_.size() > 2 &&
(events_.back().wall_time - events_.front().wall_time) >
window_duration_) {
events_.pop_front();
}
}
从上面可以看到Event 实例存在两个成员变量,第一个是传入的形参 time,其为订阅话题消息数据中的时间戳。第二个参数为调用该数据的时间。该函数的目录,就是计算在 window_duration_ 时间内,其共调用了多少(events_.size())数据。为了方便理解,下面举两个例子,因为HandleCollatedSensorData是回调函数,所以可能同时有多个线程执行到 Pulse(common::Time time):
①→假设为100个线程执行同时执行到Pulse,那么此时 events_ 中的这100个时间点,可得 events_.back().wall_time-events_.front().wall_time ②→假设为20个线程执行到Pulse,且 events_.back().wall_time - events_.front().wall_time > window_duration_= 15秒。也就是说,接受数据比较快,但是调用数据时比较慢,此时就会把循环 events_ 第一个元素抛掉, 直到events_.back().wall_time - events_.front().wall_time < window_duration_。总的来说,events_包含的还是15秒内的.wall 时间点。其 events_.size()等于15秒的数据个数。 注 意 : \color{red}注意: 注意: 最终 events_.back().wall_time - events_.front().wall_time 表示15秒内调用数据的首尾差。 ( 3 ) : \color{blue}(3): (3): 执行信息的打印,主要涉及到 RateTimer中的 DebugString() 函数: 其打印的格式类似如下: DebugString() 函数通过带哦用 ComputeRate(),DeltasDebugString(),ComputeWallTimeRateRatio() 分别计算频率,数据时间间隔的均值与标准差,以及数据生成与数据调用的比例。总体代码注释如下。 结合上篇博客的结论→初始注册的回调函数,整理数据之后,最终都会调用到 sensor::Collator::AddTrajectory(),把数据传送给了 GlobalTrajectoryBuilder。那么该篇博客的重要代码: 表示的,就是传感器数据的利用率,如下打印的 100.44%: 所以这里在打印的信息中,该信息是及为重要的,如果超过100%,那么说明系统能够处理更多的数据。 // Returns a debug string representation.
std::string DebugString() const {
if (events_.size() < 2) {
return "unknown";
}
// c++11: std::fixed 与 std::setprecision(2) 一起使用, 表示输出2位小数点的数据
std::ostringstream out;
out << std::fixed << std::setprecision(2) << ComputeRate() << " Hz "
<< DeltasDebugString() << " (pulsed at "
<< ComputeWallTimeRateRatio() * 100. << "% real time)";
return out.str();
}
// 也就是跑carto时候的消息:
[ INFO]: collated_trajectory_builder.cc:72] imu rate: 10.00 Hz 1.00e-01 s +/- 4.35e-05 s (pulsed at 100.44% real time)
[ INFO]: collated_trajectory_builder.cc:72] scan rate: 19.83 Hz 5.04e-02 s +/- 4.27e-05 s (pulsed at 99.82% real time)
四、RateTimer代码注释
class RateTimer {
public:
// Computes the rate at which pulses come in over 'window_duration' in wall
// time.
//explicit禁止隐式类型转换
explicit RateTimer(const common::Duration window_duration)
: window_duration_(window_duration) {}
~RateTimer() {}
RateTimer(const RateTimer&) = delete;//禁用默认拷贝构造函数
RateTimer& operator=(const RateTimer&) = delete;//禁用默认赋值=号赋值函数
// Returns the pulse rate in Hz.
// 计算平均频率,数据的个数 - 1 除以调用该批数据开始与结束的时间间隔
double ComputeRate() const {
if (events_.empty()) {
return 0.;
}
return static_cast<double>(events_.size() - 1) /
common::ToSeconds((events_.back().time - events_.front().time));
}
// Returns the ratio of the pulse rate (with supplied times) to the wall time
// rate. For example, if a sensor produces pulses at 10 Hz, but we call Pulse
// at 20 Hz wall time, this will return 2.
//这里的events_表示window_duration_(默认15秒)内所有数据的
//生成时间.time(订阅话题msg的时间戳)与调用时间.wall_time
double ComputeWallTimeRateRatio() const {
if (events_.empty()) {
return 0.;
}
//计算生产该批数据消耗的时间
return common::ToSeconds((events_.back().time - events_.front().time)) /
//除以该批数据被调用消耗的时间
common::ToSeconds(events_.back().wall_time -
events_.front().wall_time);
}
// Records an event that will contribute to the computed rate.
// 对数据队列进行更新
void Pulse(common::Time time) {
// 将传入的时间放入队列中
events_.push_back(Event{time, ClockType::now()});
// 删除队列头部数据,直到队列中最后与最前间的时间间隔小于window_duration_
while (events_.size() > 2 &&
(events_.back().wall_time - events_.front().wall_time) >
window_duration_) {
events_.pop_front();
}
}
// Returns a debug string representation.
std::string DebugString() const {
if (events_.size() < 2) {
return "unknown";
}
// c++11: std::fixed 与 std::setprecision(2) 一起使用, 表示输出2位小数点的数据
std::ostringstream out;
out << std::fixed << std::setprecision(2) << ComputeRate() << " Hz "
<< DeltasDebugString() << " (pulsed at "
<< ComputeWallTimeRateRatio() * 100. << "% real time)";
return out.str();
}
private:
struct Event {
common::Time time;
typename ClockType::time_point wall_time;
};
// Computes all differences in seconds between consecutive pulses.
// 返回每2个数据间的时间间隔
std::vector<double> ComputeDeltasInSeconds() const {
CHECK_GT(events_.size(), 1);
const size_t count = events_.size() - 1;
std::vector<double> result;
result.reserve(count);
for (size_t i = 0; i != count; ++i) {
result.push_back(
common::ToSeconds(events_[i + 1].time - events_[i].time));
}
return result;
}
// Returns the average and standard deviation of the deltas.
// 计算数据时间间隔的均值与标准差
std::string DeltasDebugString() const {
const auto deltas = ComputeDeltasInSeconds();
const double sum = std::accumulate(deltas.begin(), deltas.end(), 0.);
// 计算均值
const double mean = sum / deltas.size();
double squared_sum = 0.;
for (const double x : deltas) {
squared_sum += common::Pow2(x - mean);
}
// 计算标准差
const double sigma = std::sqrt(squared_sum / (deltas.size() - 1));
std::ostringstream out;
out << std::scientific << std::setprecision(2) << mean << " s +/- " << sigma
<< " s";
return out.str();
}
std::deque<Event> events_;
const common::Duration window_duration_;
};
五、结语
double ComputeWallTimeRateRatio() const{......}
[ INFO]: collated_trajectory_builder.cc:72] imu rate: 10.00 Hz 1.00e-01 s +/- 4.35e-05 s (pulsed at 100.44% real time)