ROS坐标系规范:REP (定义了坐标系命名规范及各自的含义)
base_link (机器人底盘坐标系)
base_laser
odom (用于短期局部参考)
map (用于长期全局参考)
earth (用于多个机器人的场景)
坐标系间的关系
Axis Orientation (坐标轴方向)
比较项 | ODDS(赔率) | PROBABILITY(概率) |
---|---|---|
Meaning | Odds refers to the chances in favor of the event to the chances against it. | Probability refers to the likelihood of occurrence of an event. |
Expressed in | Ratio | Percent or decimal |
Lies between | 0 to ∞ | 0 to 1 |
Formula | Occurrence/Non-occurrence | Occurrence/Whole |
// probability_values.h
// Clamps 'value' to be in the range ['min', 'max'].
template
T Clamp(const T value, const T min, const T max) {
if (value > max) {
return max;
}
if (value < min) {
return min;
}
return value;
}
inline uint16 BoundedFloatToValue(const float float_value,
const float lower_bound,
const float upper_bound) {
const int value =
common::RoundToInt(
(common::Clamp(float_value, lower_bound, upper_bound) - lower_bound) *
(32766.f / (upper_bound - lower_bound))) +
1;
// DCHECK for performance.
DCHECK_GE(value, 1);
DCHECK_LE(value, 32767);
return value;
}
} // namespace
inline float Odds(float probability) {
return probability / (1.f - probability);
}
inline float ProbabilityFromOdds(const float odds) {
return odds / (odds + 1.f);
}
// Converts the given probability to log odds.
inline float Logit(float probability) {
return std::log(probability / (1.f - probability));
}
const float kMaxLogOdds = Logit(kMaxProbability);
const float kMinLogOdds = Logit(kMinProbability);
// Converts a probability to a log odds integer. 0 means unknown, [kMinLogOdds,
// kMaxLogOdds] is mapped to [1, 255].
inline uint8 ProbabilityToLogOddsInteger(const float probability) {
const int value = common::RoundToInt((Logit(probability) - kMinLogOdds) *
254.f / (kMaxLogOdds - kMinLogOdds)) +
1;
CHECK_LE(1, value);
CHECK_GE(255, value);
return value;
}
inline float ProbabilityToCorrespondenceCost(const float probability) {
return 1.f - probability;
}
inline float CorrespondenceCostToProbability(const float correspondence_cost) {
return 1.f - correspondence_cost;
}
constexpr float kMinProbability = 0.1f;
constexpr float kMaxProbability = 1.f - kMinProbability;
constexpr float kMinCorrespondenceCost = 1.f - kMaxProbability;
constexpr float kMaxCorrespondenceCost = 1.f - kMinProbability;
// Clamps probability to be in the range [kMinProbability, kMaxProbability].
inline float ClampProbability(const float probability) {
return common::Clamp(probability, kMinProbability, kMaxProbability);
}
// Clamps correspondece cost to be in the range [kMinCorrespondenceCost,
// kMaxCorrespondenceCost].
inline float ClampCorrespondenceCost(const float correspondence_cost) {
return common::Clamp(correspondence_cost, kMinCorrespondenceCost,
kMaxCorrespondenceCost);
安装 ROS Kinetic
http://wiki.ros.org/kinetic/Installation/Ubuntu
Build and install proto3.
https://google-cartographer.readthedocs.io/en/latest/ (安装完之后重启系统)
或
https://github.com/google/protobuf/blob/master/src/README.md
Cartographer ROS for TurtleBots (cartographer, cartographer_ros, cartographer_turtlebot)
https://google-cartographer-ros-for-turtlebots.readthedocs.io/en/latest/
下载TurtleBot3
cd ~/burger_ws/src/
git clone https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git
git clone https://github.com/ROBOTIS-GIT/turtlebot3.git
cd ~/catkin_ws && catkin_make
Public Data
http://google-cartographer-ros.readthedocs.io/en/latest/data.html#public-data
Resource
Real-time indoor SLAM with glass detection
https://github.com/uts-magic-lab/slam_glass
Cartographer Public Data
https://google-cartographer-ros.readthedocs.io/en/latest/data.html
cartographer_hector_tracker
It is an example of 3D SLAM with a tilting 2D lidar on a mobile robot
https://github.com/tu-darmstadt-ros-pkg/cartographer_hector_tracker
Bag validation tool
$ rosrun cartographer_ros cartographer_rosbag_validate -bag_filename ~/Downloads/2017-10-17-08-59-28.bag
VeloView performs real-time visualization of live captured 3D LiDAR data from Velodyne’s HDL sensors (HDL-64E, HDL-32E, and VLP-16)
http://www.paraview.org/VeloView/
https://github.com/Kitware/VeloView
ROS Tutorials
std::unique_ptr LocalTrajectoryBuilder2D::ScanMatch(
const common::Time time, const transform::Rigid2d& pose_prediction,
const sensor::RangeData& gravity_aligned_range_data) {
std::shared_ptr matching_submap =
active_submaps_.submaps().front();
// The online correlative scan matcher will refine the initial estimate for
// the Ceres scan matcher.
transform::Rigid2d initial_ceres_pose = pose_prediction;
sensor::AdaptiveVoxelFilter adaptive_voxel_filter(
options_.adaptive_voxel_filter_options());
// voxel filter
const sensor::PointCloud filtered_gravity_aligned_point_cloud =
adaptive_voxel_filter.Filter(gravity_aligned_range_data.returns);
if (filtered_gravity_aligned_point_cloud.empty()) {
return nullptr;
}
// RTCSM scan matching refine the pose_prediction that is from
// IMU/Odom/Kinetics for Ceres scan matcher
if (options_.use_online_correlative_scan_matching()) {
CHECK_EQ(options_.submaps_options().grid_options_2d().grid_type(),
proto::GridOptions2D_GridType_PROBABILITY_GRID);
double score = real_time_correlative_scan_matcher_.Match(
pose_prediction, filtered_gravity_aligned_point_cloud,
*static_cast(matching_submap->grid()),
&initial_ceres_pose);
kFastCorrelativeScanMatcherScoreMetric->Observe(score);
}
auto pose_observation = common::make_unique();
ceres::Solver::Summary summary;
// Ceres scan matcher (Non-linear Least Squares)
ceres_scan_matcher_.Match(pose_prediction.translation(), initial_ceres_pose,
filtered_gravity_aligned_point_cloud,
*matching_submap->grid(), pose_observation.get(),
&summary);
if (pose_observation) {
kCeresScanMatcherCostMetric->Observe(summary.final_cost);
double residual_distance =
(pose_observation->translation() - pose_prediction.translation())
.norm();
kScanMatcherResidualDistanceMetric->Observe(residual_distance);
double residual_angle = std::abs(pose_observation->rotation().angle() -
pose_prediction.rotation().angle());
kScanMatcherResidualAngleMetric->Observe(residual_angle);
}
return pose_observation;
}
Node::HandleImuMessage->
SensorBridge::HandleImuMessage->
CollatedTrajectoryBuilder::AddSensorData->
CollatedTrajectoryBuilder::AddData->
TrajectoryCollator::AddSensorData->
//把传感器数据放在队列中
//TrajectoryCollator(std::unordered_map trajectory_to_queue_)
//trajectory_to_queue_.at(trajectory_id).Add(std::move(queue_key), std::move(data))
OrderedMultiQueue::Add(const QueueKey& queue_key, std::unique_ptr data)->
OrderedMultiQueue::Dispatch()->
//callback为CollatedTrajectoryBuilder::HandleCollatedSensorData
//见CollatedTrajectoryBuilder::CollatedTrajectoryBuilder
callback(..) ->
Dispatchable::AddToTrajectoryBuilder(TrajectoryBuilderInterface*)->
GlobalTracjectoryBuilder::addSensorData(sensor_id, Sensordata)
GlobalTrajectoryBuilder::AddSensorData(const std::string& sensor_id, const sensor::TimedPointCloudData& timed_point_cloud_data)
std::unique_ptr LocalTrajectoryBuilder2D::AddRangeData(const std::string& sensor_id,const sensor::TimedPointCloudData& range_data)
TimedPointCloudOriginData RangeDataCollator::AddRangeData(string& sensor_id,TimedPointCloudData& timed_point_cloud_data)
//把一个点云数据加入到vector中,以搜集一帧数据
RangeData LocalTrajectoryBuilder2D::TransformToGravityAlignedFrameAndFilter
sensor::VoxelFilter(options_.voxel_filter_size()).Filter(cropped.returns)
MatchingResult LocalTrajectoryBuilder2D::AddAccumulatedRangeData(Time,RangeData&, Rigid3d& gravity_alignment) (***)
-Rigid3d PoseExtrapolator::ExtrapolatePose(const common::Time time) (得用IMU进行旋转、Odom进行平移预测,从而获得新的Pose)
-Rigid2d LocalTrajectoryBuilder2D::ScanMatch(Time time, Rigid2d& pose_prediction,RangeData& gravity_aligned_range_data) (****)
PointCloud AdaptiveVoxelFilter::Filter(const PointCloud& point_cloud) (体素滤波)
// Aligns 'point_cloud' within the 'grid' given an initial_pose_estimate'.
// Scan-to-Submap匹配 (RealTimeCorrelativeScanMatcher2D)
double RealTimeCorrelativeScanMatcher2D::Match(Rigid2d& initial_pose_estimate, //输入 @函数返回分数
PointCloud& point_cloud, //点云数据
ProbabilityGrid& probability_grid, //submap grid
Rigid2d* pose_estimate) //输出新的Pose
// Generates a collection of rotated scans.
vector GenerateRotatedScans(PointCloud& point_cloud,SearchParameters& search_parameters)
// Translates and discretizes the rotated scans into a vector of integer indices.
vector DiscretizeScans(MapLimits& map_limits,vector& scans,Translation2f& initial_translation)
// 在搜索范围内均匀生成候选者
vector RealTimeCorrelativeScanMatcher2D::GenerateExhaustiveSearchCandidates(SearchParameters& search_parameters)
//为每个候选者打分(Computes the score for each Candidate2D in a collection. The cost is computed as the sum of probabilities,)
void RealTimeCorrelativeScanMatcher2D::ScoreCandidates(ProbabilityGrid& probability_grid,
vector& discrete_scans,
SearchParameters& search_parameters,
vector* const candidates)
选一个分数最高的候选者Pose返回
// 创建三个ResidualBlock<空间占用、平移、旋转>然后利用ceres求解最优pose_estimate
// 根据Odom预测值和RTCSM估计值的残差进行优化求解
void CeresScanMatcher2D::Match(Eigen::Vector2d& target_translation, //通过IMU, Odom运动预测的值
transform::Rigid2d& initial_pose_estimate, //RTCSM所估计的最佳值 (scan-to-map)
sensor::PointCloud& point_cloud,
Grid2D& grid,
transform::Rigid2d* const pose_estimate, //优化的输出Pose结果
ceres::Solver::Summary* const summary)
//基于一个pose,计算从'point_cloud'匹配到'grid'的cost, 残差个数与点云个数一致
CostFunction* OccupiedSpaceCostFunction2D::CreateAutoDiffCostFunction
//计算 the cost of translating 'pose' to 'target_translation', 两个残差(x,y)
CostFunction* TranslationDeltaCostFunctor2D::CreateAutoDiffCostFunction
//计算 the cost of rotating 'pose' to 'target_angle',一个残差(偏航角之差)
CostFunction* RotationDeltaCostFunctor2D::CreateAutoDiffCostFunction
-void PoseExtrapolator::AddPose(const common::Time time,const transform::Rigid3d& pose)
-std::unique_ptrLocalTrajectoryBuilder2D::InsertIntoSubmap( //***把Laser Scan插入submap
const common::Time time, const sensor::RangeData& range_data_in_local,
const sensor::RangeData& gravity_aligned_range_data,
const transform::Rigid3d& pose_estimate,
const Eigen::Quaterniond& gravity_alignment)
void ActiveSubmaps2D::InsertRangeData(const sensor::RangeData& range_data)
void Submap2D::InsertRangeData(RangeData& range_data,RangeDataInserterInterface* range_data_inserter)
void ProbabilityGridRangeDataInserter2D::Insert(RangeData& range_data, GridInterface* const grid) // grid is ProbabilityGrid
void CastRays(RangeData& range_data,vector& hit_table,vector& miss_table,
insert_free_space,ProbabilityGrid* const probability_grid)
GlobalTrajectoryBuilder::AddSensorData( string& sensor_id,sensor::TimedPointCloudData& timed_point_cloud_data)->
*LocalTrajectoryBuilder3D::AddRangeData(string& sensor_id,sensor::TimedPointCloudData& unsynchronized_data)->
TimedPointCloudOriginData RangeDataCollator::AddRangeData(string& sensor_id,sensor::TimedPointCloudData& timed_point_cloud_data)
LocalTrajectoryBuilder3D::AddAccumulatedRangeData(Time time, sensor::RangeData& filtered_range_data_in_tracking)-> (***)
Rigid3d PoseExtrapolator::ExtrapolatePose(const common::Time time)
float RealTimeCorrelativeScanMatcher3D::Match(const transform::Rigid3d& initial_pose_estimate,const sensor::PointCloud& point_cloud,
const HybridGrid& hybrid_grid,transform::Rigid3d* pose_estimate) // 暴力匹配
void CeresScanMatcher3D::Match(const Eigen::Vector3d& target_translation, const transform::Rigid3d& initial_pose_estimate,
const std::vector& point_clouds_and_hybrid_grids,
transform::Rigid3d* const pose_estimate, ceres::Solver::Summary* const summary)
// 每个点云数据有两个残差:高低精度(点云->地图),根据概率进行计算
// 点云残差=scaling_factor * (1-probability)
// 平移3个残差,旋转3个残差
void PoseExtrapolator::AddPose(const common::Time time,const transform::Rigid3d& pose) // 把新的Pose加入双向队列
std::unique_ptr LocalTrajectoryBuilder3D::InsertIntoSubmap(
const common::Time time,
const sensor::RangeData& filtered_range_data_in_local,
const sensor::RangeData& filtered_range_data_in_tracking,
const sensor::PointCloud& high_resolution_point_cloud_in_tracking,
const sensor::PointCloud& low_resolution_point_cloud_in_tracking,
const transform::Rigid3d& pose_estimate,
const Eigen::Quaterniond& gravity_alignment)->
void ActiveSubmaps3D::InsertRangeData(sensor::RangeData& range_data,Eigen::Quaterniond& gravity_alignment)->
void Submap3D::InsertRangeData(const sensor::RangeData& range_data,RangeDataInserter3D& range_data_inserter,
const int high_resolution_max_range)->
void RangeDataInserter3D::Insert(const sensor::RangeData& range_data,HybridGrid* hybrid_grid)->
// 调用两次,分别把RangeData插入高/低精度HybridGrid
bool ApplyLookupTable(const Eigen::Array3i& index, const std::vector& table)
// 通过查表方法更新Cell的概率值[1,32767],与概率对应的值(即归一化值)
*NodeId PoseGraph3D::AddNode(std::shared_ptr constant_data, const int trajectory_id,
std::vector>& insertion_submaps)->
PoseGraphData.Append //1) 为此Trajectory增加TrajectoryNode
PoseGraphData.Append //2) 如果对应的Submap3D没有被增加到轨迹中,则增加到轨迹中
*PoseGraph3D::ComputeConstraintsForNode(NodeId& node_id,vector> insertion_submaps,
bool newly_finished_submap) //3) 为新增加的节点计算约束
vector PoseGraph3D::InitializeGlobalSubmapPoses(...)-> // 获取新插入的两个Submap的SubMapId
OptimizationProblem3D::AddSubmap(int trajectory_id, transform::Rigid3d& global_submap_pose) //把Submap的全局位姿增加到优化器中
根据新节点的局部位姿计算其全局位姿
OptimizationProblem3D::AddTrajectoryNode(int trajectory_id,NodeSpec3D& node_data) // 把新Node的局部和全局位姿增加到优化器中
PoseGraphData.constraints.push_back(..) //计算新节点与每个插入子图(2个)间的约束变换,然后增加到约束列表中
PoseGraph3D::ComputeConstraint(NodeId& node_id,SubmapId& submap_id) //计算新节点与以前每个Submap的约束变换
ConstraintBuilder3D::MaybeAddConstraint(...)
ConstraintBuilder3D::ComputeConstraint(...) //计算(submap i <- node j) 的约束变换
unique_ptrFastCorrelativeScanMatcher3D::Match(...)
或
unique_ptr FastCorrelativeScanMatcher3D::Match(...)->
unique_ptrFastCorrelativeScanMatcher3D::MatchWithSearchParameters(...)->
Candidate3D FastCorrelativeScanMatcher3D::BranchAndBound(...)
CeresScanMatcher3D::Match(...)
或
ConstraintBuilder3D::MaybeAddGlobalConstraint(...)
PoseGraph3D::ComputeConstraintsForOldNodes(const SubmapId& submap_id) //计算以前加入的Nodes与新加入的Submap间的约束
PoseGraph3D::DispatchOptimization() // 闭环之后,当节点增加到90 (optimize_every_n_nodes in pose_graph.lua),
struct Result {
float score;
transform::Rigid3d pose_estimate;
float rotational_score;
float low_resolution_score;
};
struct SearchParameters {
const int linear_xy_window_size; // voxels
const int linear_z_window_size; // voxels
const double angular_search_window; // radians
const MatchingFunction* const low_resolution_matcher;
};
const int linear_window_size =
(width_in_voxels_ + 1) / 2 +
common::RoundToInt(max_point_distance / resolution_ + 0.5f);
const auto low_resolution_matcher = scan_matching::CreateLowResolutionMatcher(
low_resolution_hybrid_grid_, &constant_data.low_resolution_point_cloud);
const SearchParameters search_parameters{
linear_window_size, linear_window_size, M_PI, &low_resolution_matcher};
struct DiscreteScan3D {
transform::Rigid3f pose; // 此点云对应Node的位姿
// Contains a vector of discretized scans for each 'depth'.
std::vector> cell_indices_per_depth;
float rotational_score; //此角度旋转匹配得分,通过匹配统计直方图而得(向量点乘)
};
struct Candidate3D {
Candidate3D(const int scan_index, const Eigen::Array3i& offset)
: scan_index(scan_index), offset(offset) {}
static Candidate3D Unsuccessful() {
return Candidate3D(0, Eigen::Array3i::Zero());
}
// Index into the discrete scans vectors.
// 对应搜索角度
int scan_index;
// Linear offset from the initial pose in cell indices. For lower resolution
// candidates this is the lowest offset of the 2^depth x 2^depth x 2^depth
// block of possibilities.
Eigen::Array3i offset; // 对应空间搜索位置索引
// Score, higher is better.
float score = -std::numeric_limits::infinity();
// Score of the low resolution matcher.
float low_resolution_score = 0.f;
bool operator<(const Candidate3D& other) const { return score < other.score; }
bool operator>(const Candidate3D& other) const { return score > other.score; }
};
// 计算每个搜索角度的匹配得分
std::vector RotationalScanMatcher::Match(Eigen::VectorXf& histogram,float initial_angle,std::vector& angles) ->
// 按角度旋转直方图
Eigen::VectorXf RotateHistogram(const Eigen::VectorXf& histogram,const float angle)
// 通过点乘计算两个直方图的相似性,越相似,分数越高
float MatchHistograms(Eigen::VectorXf& submap_histogram, Eigen::VectorXf& scan_histogram)
DiscreteScan3D{pose, cell_indices_per_depth, rotational_score};
Eigen::VectorXf RotationalScanMatcher::ComputeHistogram(
const sensor::PointCloud& point_cloud, const int histogram_size)
void AddPointCloudSliceToHistogram(const sensor::PointCloud& slice,
Eigen::VectorXf* const histogram) {
if (slice.empty()) {
return;
}
// We compute the angle of the ray from a point to the centroid of the whole
// point cloud. If it is orthogonal to the angle we compute between points, we
// will add the angle between points to the histogram with the maximum weight.
// This is to reject, e.g., the angles observed on the ceiling and floor.
const Eigen::Vector3f centroid = ComputeCentroid(slice);
Eigen::Vector3f last_point = slice.front();
for (const Eigen::Vector3f& point : slice) {
const Eigen::Vector2f delta = (point - last_point).head<2>();
const Eigen::Vector2f direction = (point - centroid).head<2>();
const float distance = delta.norm();
if (distance < kMinDistance || direction.norm() < kMinDistance) {
continue;
}
if (distance > kMaxDistance) {
last_point = point;
continue;
}
const float angle = common::atan2(delta);
const float value = std::max(
0.f, 1.f - std::abs(delta.normalized().dot(direction.normalized())));
AddValueToHistogram(angle, value, histogram);
}
}
syntax = "proto3";
package testx; #对应namespace
message Person { #对应class name
string name = 1;
int32 id = 2;
string email = 3;
}
cmake_minimum_required(VERSION 2.8)
project(test)
find_package(protobuf CONFIG REQUIRED)
set(CMAKE_INCLUDE_CURRENT_DIR TRUE)
#find_package(Ceres REQUIRED)
include_directories(${PROTOBUF_INCLUDE_DIRS})
# test
add_executable(test test.cc test.pb.cc)
target_link_libraries(test protobuf::libprotobuf)
// cartographer_ros/cartographer_ros/time_conversion.h
::cartographer::common::Time FromRosNow();
// To improve Odom, IMU time inconsistent
// cartographer_ros/cartographer_ros/time_conversion.cc
::cartographer::common::Time FromRosNow(){
const std::chrono::nanoseconds now =
std::chrono::duration_cast(
std::chrono::system_clock::now().time_since_epoch());
::ros::Time rosTime;
rosTime.sec = now.count()/1000000000;
rosTime.nsec = now.count()%1000000000;
return FromRos(rosTime);
}
// cartographer_ros/cartographer_ros/msg_conversion.cc
// Func: LaserScanToPointCloudWithIntensities(const LaserMessageType& msg)
// ::cartographer::common::Time timestamp = FromRos(msg.header.stamp);
::cartographer::common::Time timestamp = FromRosNow();
// cartographer_ros/cartographer_ros/sensor_bridge.cc
// Func: SensorBridge::SensorBridge
// const carto::common::Time time = FromRos(msg->header.stamp);
// const auto sensor_to_tracking = tf_bridge_.LookupToTracking(
// time, CheckNoLeadingSlash(msg->child_frame_id));
const carto::common::Time time = FromRosNow();
const auto sensor_to_tracking = tf_bridge_.LookupToTracking(
time, CheckNoLeadingSlash(msg->header.frame_id));
// cartographer_ros/cartographer_ros/sensor_bridge.cc
// Func: SensorBridge::ToImuData
//const carto::common::Time time = FromRos(msg->header.stamp);
const carto::common::Time time = FromRosNow();
// cartographer_ros/cartographer_ros/tf_bridge.cc
// return ::cartographer::common::make_unique<
// ::cartographer::transform::Rigid3d>(ToRigid3d(buffer_->lookupTransform(
// tracking_frame_, frame_id, requested_time, timeout)));
return ::cartographer::common::make_unique<
::cartographer::transform::Rigid3d>(ToRigid3d(buffer_->lookupTransform(
tracking_frame_, frame_id, ::ros::Time(0.), timeout)));
// Check laser scan data (msg_conversion.cc)
LOG(ERROR) << "range_min=" << msg.range_min << ", range_max=" << msg.range_max \
<< ", angle_min=" << msg.angle_min << ", angle_max=" << msg.angle_max \
<< ", angle_increment=" << msg.angle_increment << ", msg.ranges.size=" << msg.ranges.size() \
<< ", frame_id=" << msg.header.frame_id << ", scan_time=" << msg.scan_time \
<< ", time_increment=" << msg.time_increment;
std::unique_ptr
LocalTrajectoryBuilder3D::AddRangeData(
const std::string& sensor_id,
const sensor::TimedPointCloudData& unsynchronized_data)
template
std::tuple
LaserScanToPointCloudWithIntensities(const LaserMessageType& msg)
max_length = 2., // 最大边长
min_num_points = 150, // 点云需要保留的最小点数
max_range = 15., // 点云的最大距离,距离(l2范数)大于此值的点云数据直接丢弃