
简介这份资源是一套基于ROS 2与Navigation 2框架搭建的智能巡检机器人仿真系统面向机器人方向的学生、开发者及工业巡检与仓库管理场景的研发人员用于在无实体硬件条件下验证运动控制、环境感知与自主导航等软件逻辑。压缩包共63个文件约96KB以Python节点脚本、xacro与urdf机器人模型、yaml与xml参数配置、srv自定义服务接口及world仿真场景为主另含pgm地图、rviz配置与说明文档目录按描述、导航、应用与接口分层组织。系统集成运动控制、激光雷达与摄像头环境感知、路径规划与避障、语音播报、图像采集以及目标点循环巡检等模块可支撑周期性设备监控类任务。目前已有110人学习下载读者可据此快速搭建仿真环境、理解Navigation 2导航栈配置流程并在此基础上迭代自己的巡检应用。1. 从零搭一套能跑循环巡检的 ROS 2 仿真这套系统到底解决了什么问题工业巡检和仓库管理场景里最让人头疼的不是算法本身而是「算法验证环境」的搭建成本。真机调试要协调场地、排期、充电、安全围栏一次完整的多点巡检跑下来光准备时间就够写三版代码了。这套基于 ROS 2 和 Navigation 2 的智能巡检机器人仿真系统核心价值就在于把运动控制、环境感知、自主导航、语音播报、图像采集、目标点循环巡检这六件事全部塞进一个可复现的仿真环境里让你在本地就能把整条业务链路跑通。它适合三类人一是刚接触 ROS 2 想找一个完整项目练手的开发者二是需要快速验证巡检路径规划策略的算法工程师三是做仓储自动化方案、想先出 Demo 再谈硬件的产品同学。整套系统的骨架是 Navigation 2 的nav2_bringup感知层用 Gazebo 的传感器插件模拟业务层靠一个自定义的巡检调度节点串起来。下面我按「环境怎么搭 → 导航怎么配 → 业务怎么串 → 坑在哪」的顺序把每个环节的参数和代码都摊开讲。2. 仿真环境搭建与机器人模型从 URDF 到 Gazebo 能跑起来2.1 为什么选 Gazebo 而不是其他仿真器做巡检仿真仿真器的选择直接决定了你后面调试的舒适度。Gazebo 和 ROS 2 的集成度最高gazebo_ros包提供了现成的传感器插件激光雷达、深度相机、IMU 都能通过 URDF 里的gazebo标签直接挂载不需要额外写桥接代码。另一个常见选择是 Isaac Sim渲染效果好、物理精度高但对显卡要求高配置流程也长对于「先把巡检逻辑跑通」这个目标来说属于过度投入。我一般建议先用 Gazebo 把业务逻辑跑顺等路径规划和调度策略稳定了再考虑迁移到更高保真的仿真器做视觉算法验证。这样前期迭代速度快不会卡在环境配置上。2.2 URDF 模型的关键配置机器人模型是整个仿真的基础。一个典型的巡检机器人是差速驱动底盘需要定义base_link、左右驱动轮、万向轮以及激光雷达和相机的安装位置。下面是一个精简版的差速底盘 URDF 片段!-- 差速驱动底盘核心配置 -- robot nameinspection_robot !-- 底盘主体 -- link namebase_link visual geometrybox size0.5 0.4 0.2//geometry /visual collision geometrybox size0.5 0.4 0.2//geometry /collision inertial mass value15.0/ inertia ixx0.3 ixy0 ixz0 iyy0.4 iyz0 izz0.5/ /inertial /link !-- 左驱动轮 -- link nameleft_wheel visual geometrycylinder radius0.08 length0.04//geometry /visual collision geometrycylinder radius0.08 length0.04//geometry /collision inertial mass value1.0/ inertia ixx0.002 ixy0 ixz0 iyy0.002 iyz0 izz0.003/ /inertial /link joint nameleft_wheel_joint typecontinuous parent linkbase_link/ child linkleft_wheel/ origin xyz0 0.22 -0.05 rpy-1.5708 0 0/ axis xyz0 0 1/ /joint !-- 差速驱动插件 -- gazebo plugin namediff_drive filenamelibgazebo_ros_diff_drive.so ros namespace//namespace /ros left_jointleft_wheel_joint/left_joint right_jointright_wheel_joint/right_joint wheel_separation0.44/wheel_separation wheel_diameter0.16/wheel_diameter max_wheel_torque20.0/max_wheel_torque max_wheel_acceleration1.0/max_wheel_acceleration command_topiccmd_vel/command_topic odometry_topicodom/odometry_topic odometry_frameodom/odometry_frame robot_base_framebase_link/robot_base_frame publish_odomtrue/publish_odom publish_odom_tftrue/publish_odom_tf publish_wheel_tftrue/publish_wheel_tf /plugin /gazebo /robot这里有几个参数必须和实际需求对齐。wheel_separation是左右轮中心距wheel_diameter是轮径这两个值如果和 URDF 里的几何尺寸不一致里程计会严重漂移表现为机器人实际走了 1 米但odom只报了 0.7 米。max_wheel_torque决定了爬坡和越障能力仓库地面一般 20 就够如果场景里有减速带或者小坡建议加到 30 以上。max_wheel_acceleration限制加速度设太大机器人会猛冲猛停Navigation 2 的控制器会来不及响应。2.3 传感器插件挂载激光雷达是 Navigation 2 建图和定位的核心输入。在 URDF 里加一个ray类型的传感器插件!-- 2D 激光雷达 -- gazebo referencelaser_link sensor typeray namelaser_sensor pose0 0 0 0 0 0/pose visualizetrue/visualize update_rate10/update_rate ray scan horizontal samples360/samples resolution1/resolution min_angle-3.14159/min_angle max_angle3.14159/max_angle /horizontal /scan range min0.12/min max12.0/max resolution0.015/resolution /range /ray plugin namelaser_controller filenamelibgazebo_ros_ray_sensor.so ros remapping~/out:scan/remapping /ros output_typesensor_msgs/LaserScan/output_type frame_namelaser_link/frame_name /plugin /sensor /gazeboupdate_rate设 10Hz 是常规做法Navigation 2 的代价地图默认也是 10Hz 更新两者对齐可以减少时间戳不匹配的问题。samples设 360 意味着角分辨率 1 度对于仓库通道宽度 1.5 米以上的场景足够用。如果通道更窄建议加到 720。range的max设 12 米实际仓库货架高度有限激光打过去反射回来可能衰减如果发现远处点云稀疏可以适当降到 8 米减少无效计算。2.4 启动文件组织把 Gazebo 启动、机器人状态发布、传感器数据桥接组织成一个 launch 文件# inspection_sim.launch.py from launch import LaunchDescription from launch.actions import IncludeLaunchDescription, ExecuteProcess from launch.launch_description_sources import PythonLaunchDescriptionSource from launch_ros.actions import Node from ament_index_python.packages import get_package_share_directory import os def generate_launch_description(): pkg_dir get_package_share_directory(inspection_robot) # 启动 Gazebo 空世界 gazebo IncludeLaunchDescription( PythonLaunchDescriptionSource( os.path.join(get_package_share_directory(gazebo_ros), launch, gazebo.launch.py) ), launch_arguments{world: os.path.join(pkg_dir, worlds, warehouse.world)}.items() ) # 发布机器人状态 robot_state_publisher Node( packagerobot_state_publisher, executablerobot_state_publisher, parameters[{robot_description: open( os.path.join(pkg_dir, urdf, inspection_robot.urdf)).read()}] ) # 在 Gazebo 中生成机器人 spawn_entity Node( packagegazebo_ros, executablespawn_entity.py, arguments[-topic, robot_description, -entity, inspection_robot, -x, 0.0, -y, 0.0, -z, 0.1] ) return LaunchDescription([gazebo, robot_state_publisher, spawn_entity])spawn_entity的-z参数设 0.1 是为了让机器人从略高于地面的位置落下避免初始穿模导致物理引擎把机器人弹飞。这个细节很多人会忽略结果一启动机器人就翻车。3. Navigation 2 导航栈配置让机器人真正会走路3.1 代价地图参数怎么调Navigation 2 的核心是代价地图分为全局和局部两层。全局代价地图负责长期路径规划局部代价地图负责实时避障。配置文件里最容易出问题的是inflation_radius和cost_scaling_factor这两个参数。# nav2_params.yaml 关键片段 global_costmap: global_costmap: ros__parameters: update_frequency: 1.0 publish_frequency: 1.0 global_frame: map robot_base_frame: base_link robot_radius: 0.3 resolution: 0.05 track_unknown_space: true plugins: [static_layer, obstacle_layer, inflation_layer] inflation_layer: plugin: nav2_costmap_2d::InflationLayer cost_scaling_factor: 3.0 inflation_radius: 0.55 obstacle_layer: plugin: nav2_costmap_2d::ObstacleLayer enabled: true observation_sources: scan scan: topic: /scan max_obstacle_height: 2.0 clearing: true marking: true data_type: LaserScan raytrace_max_range: 12.0 raytrace_min_range: 0.0 obstacle_max_range: 10.0 obstacle_min_range: 0.0 local_costmap: local_costmap: ros__parameters: update_frequency: 5.0 publish_frequency: 2.0 global_frame: odom robot_base_frame: base_link robot_radius: 0.3 resolution: 0.05 rolling_window: true width: 3 height: 3 plugins: [obstacle_layer, inflation_layer] inflation_layer: plugin: nav2_costmap_2d::InflationLayer cost_scaling_factor: 3.0 inflation_radius: 0.55robot_radius设 0.3 米这是机器人外接圆半径。如果你的机器人是长方形用外接圆半径会更保守但可能过不了窄通道。这时候可以改用footprint参数定义多边形轮廓精度更高但计算量略大。inflation_radius设 0.55 米意味着障碍物周围 0.55 米内都有代价值机器人会倾向于远离障碍物。这个值太小机器人会贴着货架走容易刮蹭太大则窄通道直接不可通行。仓库通道一般 1.5 到 2 米宽0.55 是个比较稳妥的中间值。cost_scaling_factor控制代价随距离衰减的速度设 3.0 表示衰减较慢机器人会更明显地避开障碍物。如果发现机器人规划出的路径过于保守、绕大圈可以降到 2.0 试试。3.2 路径规划器与控制器选型Navigation 2 默认的全局规划器是 NavFn基于 Dijkstra 算法稳定但路径不够平滑。对于巡检场景我一般推荐换成 Smac Planner它支持 Hybrid-A*能考虑机器人的运动学约束生成的路径更贴合差速底盘的转弯半径。planner_server: ros__parameters: expected_planner_frequency: 1.0 plugins: [GridBased] GridBased: plugin: nav2_smac_planner/SmacPlannerHybrid tolerance: 0.5 downsample_costmap: false allow_unknown: true max_iterations: 1000000 max_on_approach_iterations: 1000 minimum_turning_radius: 0.2 angle_quantization_bins: 72 analytic_expansion_ratio: 3.5 analytic_expansion_max_length: 3.0 smooth_path: true motion_model_for_search: DUBINminimum_turning_radius设 0.2 米对应差速底盘原地转向能力。angle_quantization_bins设 72 表示每 5 度一个方向精度够用且计算量可控。smooth_path开启后会对路径做平滑处理减少机器人频繁启停。控制器方面默认的 DWB 控制器在低速巡检场景表现不错但如果机器人载重变化大或者地面摩擦系数不稳定建议换成 RPPRegulated Pure Pursuit它对速度的调节更平滑controller_server: ros__parameters: controller_frequency: 20.0 min_x_velocity_threshold: 0.001 min_y_velocity_threshold: 0.5 min_theta_velocity_threshold: 0.001 progress_checker_plugin: progress_checker goal_checker_plugins: [general_goal_checker] controller_plugins: [FollowPath] FollowPath: plugin: nav2_regulated_pure_pursuit_controller::RegulatedPurePursuitController desired_linear_vel: 0.5 lookahead_dist: 0.6 min_lookahead_dist: 0.3 max_lookahead_dist: 0.9 lookahead_time: 1.5 rotate_to_heading_angular_vel: 1.0 transform_tolerance: 0.1 use_velocity_scaled_lookahead_dist: true min_approach_linear_velocity: 0.05 approach_velocity_scaling_dist: 0.6 use_collision_detection: true max_allowed_time_to_collision_up_to_carrot: 1.0 use_regulated_linear_velocity_scaling: true use_cost_regulated_linear_velocity_scaling: true regulated_linear_scaling_min_radius: 0.9 regulated_linear_scaling_min_speed: 0.25 use_rotate_to_heading: true allow_reversing: falsedesired_linear_vel设 0.5 m/s巡检场景不需要太快稳定优先。lookahead_dist是前视距离设 0.6 米太小机器人会画龙太大则切弯不及时。use_cost_regulated_linear_velocity_scaling开启后机器人靠近障碍物时会自动减速这个在仓库窄通道里特别有用。3.3 定位与建图AMCL 还是 SLAM如果仿真环境的地图是预先建好的用 AMCL 做定位就够了。如果想让机器人边巡检边建图那就得用 SLAM Toolbox。巡检场景我一般建议先用 AMCL 跑通因为地图固定意味着路径可复现调试起来变量少。amcl: ros__parameters: alpha1: 0.2 alpha2: 0.2 alpha3: 0.2 alpha4: 0.2 alpha5: 0.2 base_frame_id: base_link beam_skip_distance: 0.5 beam_skip_error_threshold: 0.9 beam_skip_threshold: 0.3 do_beamskip: false global_frame_id: map lambda_short: 0.1 laser_likelihood_max_dist: 2.0 laser_max_range: 12.0 laser_min_range: 0.12 laser_model_type: likelihood_field max_beams: 60 max_particles: 2000 min_particles: 500 odom_frame_id: odom pf_err: 0.05 pf_z: 0.99 recovery_alpha_fast: 0.0 recovery_alpha_slow: 0.0 resample_interval: 1 robot_model_type: nav2_amcl::DifferentialMotionModel save_pose_rate: 0.5 sigma_hit: 0.2 tf_broadcast: true transform_tolerance: 1.0 update_min_a: 0.2 update_min_d: 0.25 z_hit: 0.5 z_max: 0.05 z_rand: 0.5 z_short: 0.05 scan_topic: scanmax_particles和min_particles控制粒子滤波的粒子数2000 和 500 是平衡精度和计算量的常用值。update_min_d设 0.25 米表示机器人移动超过 0.25 米才做一次定位更新太小会导致计算浪费太大则定位滞后。laser_model_type用likelihood_field比beam模型更鲁棒对动态障碍物和地图噪声容忍度更高。4. 巡检业务逻辑目标点循环、语音播报与图像采集怎么串4.1 巡检调度节点的设计思路Navigation 2 提供了NavigateToPoseaction 接口巡检调度节点的核心就是按顺序发送目标点等到达后再发下一个循环往复。这里的关键是处理「到达失败」和「需要重试」的情况不能一个点卡住整个巡检流程。# inspection_scheduler.py import rclpy from rclpy.node import Node from rclpy.action import ActionClient from nav2_msgs.action import NavigateToPose from geometry_msgs.msg import PoseStamped from std_msgs.msg import String import math class InspectionScheduler(Node): def __init__(self): super().__init__(inspection_scheduler) self._action_client ActionClient(self, NavigateToPose, navigate_to_pose) self._voice_pub self.create_publisher(String, /voice_text, 10) self._image_pub self.create_publisher(String, /capture_command, 10) # 巡检点列表x, y, yaw, 停留时间(秒) self.waypoints [ (2.0, 0.0, 0.0, 3.0), (2.0, 3.0, 1.57, 3.0), (0.0, 3.0, 3.14, 3.0), (0.0, 0.0, -1.57, 3.0), ] self.current_index 0 self.retry_count 0 self.max_retries 3 def send_next_goal(self): if self.current_index len(self.waypoints): self.current_index 0 # 循环巡检 self.get_logger().info(完成一轮巡检重新开始) x, y, yaw, dwell self.waypoints[self.current_index] goal_msg NavigateToPose.Goal() goal_msg.pose.header.frame_id map goal_msg.pose.header.stamp self.get_clock().now().to_msg() goal_msg.pose.pose.position.x x goal_msg.pose.pose.position.y y goal_msg.pose.pose.orientation.z math.sin(yaw / 2.0) goal_msg.pose.pose.orientation.w math.cos(yaw / 2.0) self._action_client.wait_for_server() self._send_goal_future self._action_client.send_goal_async( goal_msg, feedback_callbackself.feedback_callback) self._send_goal_future.add_done_callback(self.goal_response_callback) def goal_response_callback(self, future): goal_handle future.result() if not goal_handle.accepted: self.get_logger().warn(目标点被拒绝尝试下一个) self.current_index 1 self.send_next_goal() return self._get_result_future goal_handle.get_result_async() self._get_result_future.add_done_callback(self.get_result_callback) def get_result_callback(self, future): status future.result().status if status 4: # SUCCEEDED self.on_waypoint_reached() else: self.retry_count 1 if self.retry_count self.max_retries: self.get_logger().warn(f导航失败第 {self.retry_count} 次重试) self.send_next_goal() else: self.get_logger().error(重试超限跳过该点) self.retry_count 0 self.current_index 1 self.send_next_goal() def on_waypoint_reached(self): self.retry_count 0 x, y, yaw, dwell self.waypoints[self.current_index] # 语音播报 voice_msg String() voice_msg.data f已到达巡检点 {self.current_index 1}开始采集 self._voice_pub.publish(voice_msg) # 触发图像采集 img_msg String() img_msg.data fcapture_{self.current_index} self._image_pub.publish(img_msg) # 停留后前往下一个点 self.create_timer(dwell, self.after_dwell) def after_dwell(self): self.current_index 1 self.send_next_goal() def feedback_callback(self, feedback_msg): pass # 可在此处理剩余距离等反馈这个调度节点的几个设计点值得说明。max_retries设 3 次超过就跳过避免一个点卡死整个巡检。on_waypoint_reached里先发语音播报再触发图像采集顺序不能反因为图像采集需要机器人完全停稳而语音播报是即时动作。create_timer做停留延时注意这个 timer 是一次性的触发后要销毁否则会重复执行。4.2 语音播报的实现方式仿真环境里没有真实扬声器语音播报一般用两种方式模拟一是发布文本到话题由外部节点订阅后调用 TTS 引擎二是直接在仿真里播放预录的音频文件。我一般用第一种因为文本话题更灵活方便调试时直接看日志。# voice_broadcast.py import rclpy from rclpy.node import Node from std_msgs.msg import String class VoiceBroadcast(Node): def __init__(self): super().__init__(voice_broadcast) self.subscription self.create_subscription( String, /voice_text, self.voice_callback, 10) self.get_logger().info(语音播报节点已启动) def voice_callback(self, msg): # 实际项目中这里调用 TTS 引擎仿真中打印日志 self.get_logger().info(f[语音播报] {msg.data}) # 可选发布到音频话题供仿真播放 # self.audio_pub.publish(...)如果要做更真实的仿真可以用sound_play包它支持播放 wav 文件。把常用播报内容预录成 wav按编号索引收到文本后映射到对应文件播放。这样在 Gazebo 里虽然听不到声音但可以通过ros2 topic echo验证播报逻辑是否正确。4.3 图像采集与保存图像采集节点订阅相机话题收到采集命令后保存当前帧。仿真里相机数据来自 Gazebo 的libgazebo_ros_camera.so插件发布sensor_msgs/Image类型消息。# image_capture.py import rclpy from rclpy.node import Node from sensor_msgs.msg import Image from std_msgs.msg import String from cv_bridge import CvBridge import cv2 import os class ImageCapture(Node): def __init__(self): super().__init__(image_capture) self.bridge CvBridge() self.latest_image None self.save_dir os.path.expanduser(~/inspection_images) os.makedirs(self.save_dir, exist_okTrue) self.image_sub self.create_subscription( Image, /camera/image_raw, self.image_callback, 10) self.cmd_sub self.create_subscription( String, /capture_command, self.capture_callback, 10) def image_callback(self, msg): self.latest_image msg def capture_callback(self, msg): if self.latest_image is None: self.get_logger().warn(尚未收到图像数据) return cv_image self.bridge.imgmsg_to_cv2(self.latest_image, bgr8) filename os.path.join(self.save_dir, f{msg.data}.jpg) cv2.imwrite(filename, cv_image) self.get_logger().info(f图像已保存: {filename})cv_bridge的编码参数用bgr8这是 OpenCV 的默认格式。如果相机插件发布的是rgb8转换后颜色会偏需要显式指定。保存路径建议放在用户目录下避免权限问题。文件名用采集命令里的标识方便和巡检点对应。5. 避坑与排查仿真巡检系统最常见的五个翻车点5.1 机器人原地打转或画龙现象机器人启动后不按规划路径走原地转圈或者走 S 形。原因最常见的是wheel_separation和 URDF 里的实际轮距不一致导致里程计计算出的转向角度偏差。其次是lookahead_dist设得太小控制器频繁修正方向。解决先用ros2 topic echo /odom看里程计数据手动推机器人走直线检查pose.pose.position是否和实际位移一致。如果不一致调整wheel_separation和wheel_diameter。然后把lookahead_dist从 0.6 逐步加到 0.9 测试找到不画龙的最大值。5.2 代价地图里障碍物消失或残留现象激光明明扫到了货架但代价地图上不显示或者机器人已经走过去了障碍物还留在原地。原因obstacle_layer的raytrace_max_range和obstacle_max_range设置不匹配。raytrace负责清除障碍物obstacle负责标记障碍物。如果raytrace_max_range小于obstacle_max_range就会出现标记了但清除不掉的情况。解决确保raytrace_max_range大于等于obstacle_max_range。一般设raytrace_max_range: 12.0obstacle_max_range: 10.0留 2 米余量。另外检查scan话题的frame_id是否和 URDF 里的激光 link 名称一致不一致会导致 TF 变换失败障碍物无法正确投影。5.3 导航到目标点后不停止现象机器人到达目标点附近后继续往前走或者反复前后微调。原因goal_checker的容差设置太小机器人无法精确到达。或者xy_goal_tolerance和yaw_goal_tolerance不匹配实际控制精度。解决在controller_server里调整general_goal_checker参数general_goal_checker: stateful: True plugin: nav2_controller::SimpleGoalChecker xy_goal_tolerance: 0.25 yaw_goal_tolerance: 0.25xy_goal_tolerance设 0.25 米yaw_goal_tolerance设 0.25 弧度约 14 度这是仿真环境里比较宽松的值。如果实际场景需要更高精度可以降到 0.1但要确保控制器能稳定达到。5.4 语音播报和图像采集不同步现象语音说「开始采集」了但图像还没保存或者图像保存了但语音没播。原因两个节点独立运行没有同步机制。调度节点发布命令后立即进入下一个流程没有等待采集完成。解决在调度节点里加一个简单的确认机制。图像采集节点保存完图像后发布一个capture_done话题调度节点收到后再继续。或者更简单的方式在on_waypoint_reached里用time.sleep硬等 1 秒确保采集完成。仿真环境里硬等可以接受真机上建议用话题确认。5.5 循环巡检时内存持续增长现象跑了几轮巡检后系统变慢最后卡死。原因图像采集节点每轮都保存图像但不释放或者latest_image一直持有大尺寸图像消息。ROS 2 的默认 QoS 是KEEP_LAST深度 10如果订阅者处理速度跟不上消息会堆积。解决图像采集节点里保存完图像后把latest_image置为None释放内存。订阅图像时用SensorDataQoS它适合高频传感器数据会自动丢弃旧消息from rclpy.qos import qos_profile_sensor_data self.image_sub self.create_subscription( Image, /camera/image_raw, self.image_callback, qos_profile_sensor_data)另外保存图像时用cv2.imwrite的压缩参数JPEG 质量设 85 就够别用默认的 95文件大小差一倍。6. 进阶技巧用行为树定制巡检策略与仿真加速6.1 用行为树替代硬编码调度前面写的调度节点是硬编码的 Python 逻辑简单场景够用但一旦要加「电量低自动返回充电」「发现异常停留更久」这类条件分支代码会迅速膨胀。Navigation 2 内置了行为树引擎可以用 XML 描述巡检逻辑改策略不用改代码。一个简化的循环巡检行为树片段root main_tree_to_executeMainTree BehaviorTree IDMainTree Repeat num_cycles-1 Sequence NavigateToPose goal{waypoint_1}/ Wait wait_duration3.0/ PublishVoice text到达巡检点1/ CaptureImage id1/ NavigateToPose goal{waypoint_2}/ Wait wait_duration3.0/ PublishVoice text到达巡检点2/ CaptureImage id2/ /Sequence /Repeat /BehaviorTree /rootRepeat节点的num_cycles设 -1 表示无限循环。每个巡检点用Sequence串起来到达后等待、播报、采集。如果要加条件判断比如「电量低于 20% 就去充电」可以在Sequence里插入Condition节点。行为树的好处是逻辑可视化改流程不用重新编译代码改 XML 就行。6.2 仿真加速与批量测试Gazebo 默认按实时速度运行跑一轮完整巡检可能要几分钟。做参数调优时可以开启仿真加速# 启动 Gazebo 时设置物理更新率 gzserver --verbose -s libgazebo_ros_init.so -s libgazebo_ros_factory.so \ --physics real_time_update_rate 0real_time_update_rate设 0 表示不限速Gazebo 会以最快速度跑物理仿真。注意这会导致传感器数据时间戳和真实时间脱节Navigation 2 的 TF 变换可能报超时。更稳妥的方式是设一个较高的值比如 500让仿真跑得比实时快但又不至于失控。批量测试时可以用ros2 launch的参数化启动把巡检点列表和导航参数作为参数传入跑完一轮自动记录成功率和耗时ros2 launch inspection_robot inspection_sim.launch.py \ waypoints_file:/path/to/waypoints.yaml \ nav_params_file:/path/to/nav2_params.yaml \ record_metrics:true6.3 从仿真到真机的迁移检查清单仿真跑通后往真机迁移最容易出问题的几个地方一是传感器安装位置和 URDF 不一致导致 TF 树错位二是真机轮径磨损后和 URDF 标称值有偏差里程计漂移三是真机计算资源有限Navigation 2 的规划频率要适当降低。我一般会在真机上先跑一遍ros2 run tf2_tools view_frames确认 TF 树和仿真一致。然后用卷尺量实际轮距和轮径更新 URDF 参数。最后把controller_frequency从 20Hz 降到 10Hzplanner_server的expected_planner_frequency从 1Hz 降到 0.5Hz给计算资源留余量。这套仿真系统最大的价值不是代码本身而是让你在不动硬件的情况下把巡检逻辑、导航参数、异常处理全部验证一遍。我自己的习惯是每次改完导航参数先在仿真里跑 10 轮循环巡检记录成功率和平均耗时确认稳定后再上真机。这个习惯帮我省下了大量现场调试的时间也避免了不少「真机撞货架」的血泪教训。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。