Publish LIO anchor frame as odom, calibrate LiDAR mount pitch, tune point filtering

- Rename the published frame_id from camera_init to odom everywhere in laserMapping.cpp (child stays aft_mapped, matching the URDF root) so AMCL's map->odom broadcast no longer conflicts with a separate static camera_init->odom bridge in fori_ws's Nav2 launch
- Set extrinsic_R for the LiDAR's actual 20-degree nose-down mounting pitch (was identity)
- Increase point_filter_num and filter_size_surf/map for lighter point cloud load
This commit is contained in:
2026-08-27 11:45:54 +09:00
parent ba83f4f437
commit d0a113ee18
2 changed files with 48 additions and 16 deletions
+10 -6
View File
@@ -1,10 +1,10 @@
/**: /**:
ros__parameters: ros__parameters:
feature_extract_enable: false feature_extract_enable: false
point_filter_num: 4 point_filter_num: 7
max_iteration: 3 max_iteration: 3
filter_size_surf: 0.5 filter_size_surf: 0.8
filter_size_map: 0.5 filter_size_map: 0.8
cube_side_length: 200.0 cube_side_length: 200.0
runtime_pos_log_enable: false runtime_pos_log_enable: false
map_file_path: "./PCD/scans.pcd" map_file_path: "./PCD/scans.pcd"
@@ -31,9 +31,13 @@
det_range: 50.0 det_range: 50.0
extrinsic_est_en: false extrinsic_est_en: false
extrinsic_T: [ -0.011, -0.02329, 0.04412 ] extrinsic_T: [ -0.011, -0.02329, 0.04412 ]
extrinsic_R: [ 1.0, 0.0, 0.0, # 라이다가 전방 지면을 보도록 20도 nose-down(pitch)으로 장착됨.
0.0, 1.0, 0.0, # extrinsic_R은 "라이다 자세를 IMU/바디 프레임 기준으로" 표현(FAST-LIO
0.0, 0.0, 1.0 ] # README: extrinsic = LiDAR pose in IMU frame). Ry(+20deg): forward(+X)가
# -Z(아래) 쪽으로 기움 -> nose-down과 일치.
extrinsic_R: [ 0.9396926, 0.0, 0.3420201,
0.0, 1.0, 0.0,
-0.3420201, 0.0, 0.9396926 ]
publish: publish:
path_en: true # true: publish Path path_en: true # true: publish Path
+38 -10
View File
@@ -485,7 +485,7 @@ void publish_frame_world(rclcpp::Publisher<sensor_msgs::msg::PointCloud2>::Share
pcl::toROSMsg(*laserCloudWorld, laserCloudmsg); pcl::toROSMsg(*laserCloudWorld, laserCloudmsg);
// laserCloudmsg.header.stamp = ros::Time().fromSec(lidar_end_time); // laserCloudmsg.header.stamp = ros::Time().fromSec(lidar_end_time);
laserCloudmsg.header.stamp = get_ros_time(lidar_end_time); laserCloudmsg.header.stamp = get_ros_time(lidar_end_time);
laserCloudmsg.header.frame_id = "camera_init"; laserCloudmsg.header.frame_id = "odom";
pubLaserCloudFull->publish(laserCloudmsg); pubLaserCloudFull->publish(laserCloudmsg);
publish_count -= PUBFRAME_PERIOD; publish_count -= PUBFRAME_PERIOD;
} }
@@ -554,7 +554,7 @@ void publish_effect_world(rclcpp::Publisher<sensor_msgs::msg::PointCloud2>::Shar
sensor_msgs::msg::PointCloud2 laserCloudFullRes3; sensor_msgs::msg::PointCloud2 laserCloudFullRes3;
pcl::toROSMsg(*laserCloudWorld, laserCloudFullRes3); pcl::toROSMsg(*laserCloudWorld, laserCloudFullRes3);
laserCloudFullRes3.header.stamp = get_ros_time(lidar_end_time); laserCloudFullRes3.header.stamp = get_ros_time(lidar_end_time);
laserCloudFullRes3.header.frame_id = "camera_init"; laserCloudFullRes3.header.frame_id = "odom";
pubLaserCloudEffect->publish(laserCloudFullRes3); pubLaserCloudEffect->publish(laserCloudFullRes3);
} }
@@ -576,13 +576,13 @@ void publish_map(rclcpp::Publisher<sensor_msgs::msg::PointCloud2>::SharedPtr pub
pcl::toROSMsg(*pcl_wait_pub, laserCloudmsg); pcl::toROSMsg(*pcl_wait_pub, laserCloudmsg);
// laserCloudmsg.header.stamp = ros::Time().fromSec(lidar_end_time); // laserCloudmsg.header.stamp = ros::Time().fromSec(lidar_end_time);
laserCloudmsg.header.stamp = get_ros_time(lidar_end_time); laserCloudmsg.header.stamp = get_ros_time(lidar_end_time);
laserCloudmsg.header.frame_id = "camera_init"; laserCloudmsg.header.frame_id = "odom";
pubLaserCloudMap->publish(laserCloudmsg); pubLaserCloudMap->publish(laserCloudmsg);
// sensor_msgs::msg::PointCloud2 laserCloudMap; // sensor_msgs::msg::PointCloud2 laserCloudMap;
// pcl::toROSMsg(*featsFromMap, laserCloudMap); // pcl::toROSMsg(*featsFromMap, laserCloudMap);
// laserCloudMap.header.stamp = get_ros_time(lidar_end_time); // laserCloudMap.header.stamp = get_ros_time(lidar_end_time);
// laserCloudMap.header.frame_id = "camera_init"; // laserCloudMap.header.frame_id = "odom";
// pubLaserCloudMap->publish(laserCloudMap); // pubLaserCloudMap->publish(laserCloudMap);
} }
@@ -607,10 +607,13 @@ void set_posestamp(T & out)
void publish_odometry(const rclcpp::Publisher<nav_msgs::msg::Odometry>::SharedPtr pubOdomAftMapped, std::unique_ptr<tf2_ros::TransformBroadcaster> & tf_br) void publish_odometry(const rclcpp::Publisher<nav_msgs::msg::Odometry>::SharedPtr pubOdomAftMapped, std::unique_ptr<tf2_ros::TransformBroadcaster> & tf_br)
{ {
odomAftMapped.header.frame_id = "camera_init"; // 이 프레임(과거 이름 "camera_init")은 FAST-LIO의 고정 매핑 시작 원점으로,
// URDF(fori_robot.urdf) 루트 링크 이름이 "aft_mapped"라서(FAST-LIVO2 관례를 따름), // ROS 관례상 표준 오도메트리 프레임 "odom"과 역할이 같다. AMCL이 표준대로
// map->odom을 발행할 수 있게 이름을 "odom"으로 맞춘다(doc/07 Phase 1).
odomAftMapped.header.frame_id = "odom";
// URDF(fori_robot.xacro) 루트 링크 이름이 "aft_mapped"라서(FAST-LIVO2 관례를 따름),
// TF 체인이 base_link까지 이어지려면 이 child_frame_id가 URDF 루트 이름과 일치해야 // TF 체인이 base_link까지 이어지려면 이 child_frame_id가 URDF 루트 이름과 일치해야
// 한다. 기본 FAST_LIO 상류 코드의 "body"를 그대로 쓰면 camera_init->body 트리와 // 한다. 기본 FAST_LIO 상류 코드의 "body"를 그대로 쓰면 odom->body 트리와
// aft_mapped->base_link 트리가 서로 안 이어져 Nav2가 TF를 못 찾는다. // aft_mapped->base_link 트리가 서로 안 이어져 Nav2가 TF를 못 찾는다.
odomAftMapped.child_frame_id = "aft_mapped"; odomAftMapped.child_frame_id = "aft_mapped";
odomAftMapped.header.stamp = get_ros_time(lidar_end_time); odomAftMapped.header.stamp = get_ros_time(lidar_end_time);
@@ -629,7 +632,7 @@ void publish_odometry(const rclcpp::Publisher<nav_msgs::msg::Odometry>::SharedPt
} }
geometry_msgs::msg::TransformStamped trans; geometry_msgs::msg::TransformStamped trans;
trans.header.frame_id = "camera_init"; trans.header.frame_id = "odom";
trans.header.stamp = odomAftMapped.header.stamp; trans.header.stamp = odomAftMapped.header.stamp;
trans.child_frame_id = "aft_mapped"; trans.child_frame_id = "aft_mapped";
trans.transform.translation.x = odomAftMapped.pose.pose.position.x; trans.transform.translation.x = odomAftMapped.pose.pose.position.x;
@@ -646,7 +649,7 @@ void publish_path(rclcpp::Publisher<nav_msgs::msg::Path>::SharedPtr pubPath)
{ {
set_posestamp(msg_body_pose); set_posestamp(msg_body_pose);
msg_body_pose.header.stamp = get_ros_time(lidar_end_time); // ros::Time().fromSec(lidar_end_time); msg_body_pose.header.stamp = get_ros_time(lidar_end_time); // ros::Time().fromSec(lidar_end_time);
msg_body_pose.header.frame_id = "camera_init"; msg_body_pose.header.frame_id = "odom";
/*** if path is too large, the rvis will crash ***/ /*** if path is too large, the rvis will crash ***/
static int jjj = 0; static int jjj = 0;
@@ -857,7 +860,7 @@ public:
RCLCPP_INFO(this->get_logger(), "p_pre->lidar_type %d", p_pre->lidar_type); RCLCPP_INFO(this->get_logger(), "p_pre->lidar_type %d", p_pre->lidar_type);
path.header.stamp = this->get_clock()->now(); path.header.stamp = this->get_clock()->now();
path.header.frame_id ="camera_init"; path.header.frame_id ="odom";
// /*** variables definition ***/ // /*** variables definition ***/
// int effect_feat_num = 0, frame_num = 0; // int effect_feat_num = 0, frame_num = 0;
@@ -1124,6 +1127,31 @@ private:
RCLCPP_WARN(this->get_logger(), "Position jump clamped: %.2fm -> 1.5m", pos_jump); RCLCPP_WARN(this->get_logger(), "Position jump clamped: %.2fm -> 1.5m", pos_jump);
} }
position_last = state_point.pos; position_last = state_point.pos;
// Ground-vehicle planar constraint: this robot is wheeled and cannot
// physically pitch/roll beyond normal terrain slope. If ICP degeneracy
// (e.g. "No Effective Points!") lets the orientation drift off-plane,
// the tilted attitude corrupts every downstream 2D consumer (AMCL,
// costmaps, collision_monitor) via pointcloud_to_laserscan's
// base_link-relative height slicing. Clamp tilt-from-vertical to keep
// yaw free but pull roll/pitch back toward level.
Eigen::Matrix3d R_cur = state_point.rot.toRotationMatrix();
double r22_clamped = R_cur(2, 2);
if (r22_clamped > 1.0) r22_clamped = 1.0;
if (r22_clamped < -1.0) r22_clamped = -1.0;
double tilt = std::acos(r22_clamped);
constexpr double kMaxTiltRad = 0.436332; // 25 degrees
if (tilt > kMaxTiltRad)
{
double yaw = std::atan2(R_cur(1, 0), R_cur(0, 0));
Eigen::Matrix3d R_yaw_only;
R_yaw_only << std::cos(yaw), -std::sin(yaw), 0,
std::sin(yaw), std::cos(yaw), 0,
0, 0, 1;
state_point.rot = SO3(R_yaw_only);
kf.change_x(state_point);
RCLCPP_WARN(this->get_logger(), "Orientation tilt clamped: %.1f deg -> 0 deg (yaw preserved)", tilt * 57.29578);
}
} }
euler_cur = SO3ToEuler(state_point.rot); euler_cur = SO3ToEuler(state_point.rot);
pos_lid = state_point.pos + state_point.rot * state_point.offset_T_L_I; pos_lid = state_point.pos + state_point.rot * state_point.offset_T_L_I;