1
0
forked from zbw/yiliao2026

Please enter the commit message for your changes. Lines starting

with '#' will be ignored, and an empty message aborts the commit.

On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

Changes to be committed:
	modified:   .gitignore
	modified:   README.md
	new file:   bashes/auto-wifi-connect.service
	new file:   bashes/auto-wifi-connect.sh
	deleted:    keyboard_control.py
	new file:   my_model/image.png
	new file:   path_follower_demo.py
	new file:   scripts/PIDtracking.py
	new file:   scripts/__pycache__/publish_sine_path.cpython-310.pyc
	new file:   scripts/publish_sine_path.py
	modified:   src/gc_navigation2_slamtoolbox/params/gc_navigation_slam.yaml
	modified:   src/gc_navigation2_slamtoolbox/params/gc_navigation_slam.yaml.bak
	new file:   src/gc_navigation2_slamtoolbox/params/gc_navigation_slam.yaml.bak2
	modified:   src/origincar_base/config/ekf.yaml
	new file:   src/origincar_base/config/ekf.yaml.bak
	modified:   src/origincar_base/launch/base_serial.launch.py
	new file:   src/origincar_base/launch/base_serial.launch.py.bak
	modified:   src/origincar_base/launch/origincar_bringup.launch.py
	new file:   src/past_control/CMakeLists.txt
	new file:   src/past_control/config/past_control.yaml
	new file:   src/past_control/include/past_control/tools.h
	new file:   src/past_control/launch/past_control.launch.py
	new file:   src/past_control/msg/Obstacle.msg
	new file:   src/past_control/msg/ObstacleArray.msg
	new file:   src/past_control/package.xml
	new file:   src/past_control/src/lane_follower_node.cpp
	new file:   src/past_control/src/obstacle_detector_node.cpp
	new file:   src/past_control/src/racing_orchestrator.cpp
	new file:   src/planner/CMakeLists.txt
	new file:   src/planner/config/planner.yaml
	new file:   src/planner/launch/planner.launch.py
	new file:   src/planner/package.xml
	new file:   src/planner/src/planner_version.cpp
	modified:   src/qr_detection/src/qr_dete_depth.cpp
	new file:   src/racing_control/CMakeLists.txt
	new file:   src/racing_control/include/racing_control/racing_control.hpp
	new file:   src/racing_control/package.xml
	new file:   src/racing_control/src/racing_control.cpp
	modified:   src/vlm_detect/setup.py
	new file:   src/vlm_detect/vlm_detect/__pycache__/__init__.cpython-310.pyc
	new file:   src/vlm_detect/vlm_detect/__pycache__/tts_node.cpython-310.pyc
	new file:   src/vlm_detect/vlm_detect/test_publisher.py
	new file:   src/vlm_detect/vlm_detect/tts_node.py
	modified:   src/vlm_detect/vlm_detect/vlm_node.py
	new file:   tools/measure_turning_radius.py
	new file:   tools/set_volume.py
	new file:   tools/udp_to_cmdvel.py
	new file:   tools/windows_keyboard_control.py
	new file:   vlm_server.py
	new file:   "\350\260\203\350\257\225\350\256\260\345\275\225.Assets/1.png"
	renamed:    "\350\260\203\350\257\225\350\256\260\345\275\225.log" -> "\350\260\203\350\257\225\350\256\260\345\275\225.md"
This commit is contained in:
2026-06-22 17:16:45 +08:00
parent a9bfeef59b
commit 779b32362a
51 changed files with 3974 additions and 398 deletions

View File

@@ -1,7 +1,6 @@
slam_toolbox:
ros__parameters:
use_sim_time: False
bt_navigator:
ros__parameters:
use_sim_time: False
@@ -10,165 +9,111 @@ bt_navigator:
odom_topic: /odom
bt_loop_duration: 50
default_server_timeout: 20
# 阿克曼底盘专用 BT移除 Spin恢复行为 = 清代价地图 → 后退 → 等待
default_nav_to_pose_bt_xml: /home/sunrise/yiliao_ws/install/gc_navigation2_slamtoolbox/share/gc_navigation2_slamtoolbox/config/nav_to_pose_ackermann.xml
plugin_lib_names:
- nav2_compute_path_to_pose_action_bt_node
- nav2_compute_path_through_poses_action_bt_node
- nav2_smooth_path_action_bt_node
- nav2_follow_path_action_bt_node
- nav2_spin_action_bt_node
- nav2_wait_action_bt_node
- nav2_back_up_action_bt_node
- nav2_drive_on_heading_bt_node
- nav2_clear_costmap_service_bt_node
- nav2_is_stuck_condition_bt_node
- nav2_goal_reached_condition_bt_node
- nav2_goal_updated_condition_bt_node
- nav2_globally_updated_goal_condition_bt_node
- nav2_is_path_valid_condition_bt_node
- nav2_initial_pose_received_condition_bt_node
- nav2_reinitialize_global_localization_service_bt_node
- nav2_rate_controller_bt_node
- nav2_distance_controller_bt_node
- nav2_speed_controller_bt_node
- nav2_truncate_path_action_bt_node
- nav2_truncate_path_local_action_bt_node
- nav2_goal_updater_node_bt_node
- nav2_recovery_node_bt_node
- nav2_pipeline_sequence_bt_node
- nav2_round_robin_node_bt_node
- nav2_transform_available_condition_bt_node
- nav2_time_expired_condition_bt_node
- nav2_path_expiring_timer_condition
- nav2_distance_traveled_condition_bt_node
- nav2_single_trigger_bt_node
- nav2_is_battery_low_condition_bt_node
- nav2_navigate_through_poses_action_bt_node
- nav2_navigate_to_pose_action_bt_node
- nav2_remove_passed_goals_action_bt_node
- nav2_planner_selector_bt_node
- nav2_controller_selector_bt_node
- nav2_goal_checker_selector_bt_node
- nav2_controller_cancel_bt_node
- nav2_path_longer_on_approach_bt_node
- nav2_wait_cancel_bt_node
- nav2_spin_cancel_bt_node
- nav2_back_up_cancel_bt_node
- nav2_drive_on_heading_cancel_bt_node
- nav2_compute_path_to_pose_action_bt_node
- nav2_compute_path_through_poses_action_bt_node
- nav2_smooth_path_action_bt_node
- nav2_follow_path_action_bt_node
- nav2_spin_action_bt_node
- nav2_wait_action_bt_node
- nav2_back_up_action_bt_node
- nav2_drive_on_heading_bt_node
- nav2_clear_costmap_service_bt_node
- nav2_is_stuck_condition_bt_node
- nav2_goal_reached_condition_bt_node
- nav2_goal_updated_condition_bt_node
- nav2_globally_updated_goal_condition_bt_node
- nav2_is_path_valid_condition_bt_node
- nav2_initial_pose_received_condition_bt_node
- nav2_reinitialize_global_localization_service_bt_node
- nav2_rate_controller_bt_node
- nav2_distance_controller_bt_node
- nav2_speed_controller_bt_node
- nav2_truncate_path_action_bt_node
- nav2_truncate_path_local_action_bt_node
- nav2_goal_updater_node_bt_node
- nav2_recovery_node_bt_node
- nav2_pipeline_sequence_bt_node
- nav2_round_robin_node_bt_node
- nav2_transform_available_condition_bt_node
- nav2_time_expired_condition_bt_node
- nav2_path_expiring_timer_condition
- nav2_distance_traveled_condition_bt_node
- nav2_single_trigger_bt_node
- nav2_is_battery_low_condition_bt_node
- nav2_navigate_through_poses_action_bt_node
- nav2_navigate_to_pose_action_bt_node
- nav2_remove_passed_goals_action_bt_node
- nav2_planner_selector_bt_node
- nav2_controller_selector_bt_node
- nav2_goal_checker_selector_bt_node
- nav2_controller_cancel_bt_node
- nav2_path_longer_on_approach_bt_node
- nav2_wait_cancel_bt_node
- nav2_spin_cancel_bt_node
- nav2_back_up_cancel_bt_node
- nav2_drive_on_heading_cancel_bt_node
bt_navigator_rclcpp_node:
ros__parameters:
use_sim_time: False
controller_server:
ros__parameters:
use_sim_time: False
controller_frequency: 20.0
FollowPath:
plugin: "nav2_mppi_controller::MPPIController"
time_steps: 20
model_dt: 0.05
batch_size: 200
vx_std: 0.2
vy_std: 0.0
wz_std: 0.4
vx_max: 0.5
vx_min: -0.35
vy_max: 0.0
wz_max: 1.9
iteration_count: 1
temperature: 0.3
gamma: 0.015
motion_model: "Ackermann"
visualize: false
TrajectoryVisualizer:
trajectory_step: 5
time_step: 3
AckermannConstraints:
min_turning_r: 0.4
critics: ["ConstraintCritic", "CostCritic", "GoalCritic", "GoalAngleCritic", "PathAlignCritic", "PathFollowCritic", "PathAngleCritic", "PreferForwardCritic"]
ConstraintCritic:
enabled: true
cost_power: 1
cost_weight: 4.0
GoalCritic:
enabled: true
cost_power: 1
cost_weight: 5.0
threshold_to_consider: 1.4
GoalAngleCritic:
enabled: true
cost_power: 1
cost_weight: 3.0
threshold_to_consider: 0.5
PreferForwardCritic:
enabled: true
cost_power: 1
cost_weight: 2.0
threshold_to_consider: 0.5
CostCritic:
enabled: true
cost_power: 1
cost_weight: 3.81
critical_cost: 300.0
consider_footprint: true
collision_cost: 1000000.0
near_goal_distance: 1.0
trajectory_point_step: 2
PathAlignCritic:
enabled: true
cost_power: 1
cost_weight: 7.0 # PathAlignCritic
max_path_occupancy_ratio: 0.05
trajectory_point_step: 4
threshold_to_consider: 0.5
offset_from_furthest: 20
use_path_orientations: false
PathFollowCritic:
enabled: true
cost_power: 1
cost_weight: 5.0
offset_from_furthest: 5
threshold_to_consider: 1.4
PathAngleCritic:
enabled: true
cost_power: 1
cost_weight: 2.0
offset_from_furthest: 4
threshold_to_consider: 0.5
max_angle_to_furthest: 1.0
forward_preference: true
plugin: nav2_regulated_pure_pursuit_controller::RegulatedPurePursuitController
desired_linear_vel: 0.3
max_angular_vel: 1.9
min_angular_vel: 0.2
lookahead_dist: 0.5
min_lookahead_dist: 0.3
max_lookahead_dist: 0.8
lookahead_time: 1.5
rotate_to_heading_angular_vel: 0.8
transform_tolerance: 0.5
use_velocity_scaled_lookahead_dist: True
min_approach_linear_vel: 0.1
approach_velocity_scaling_dist: 0.5
use_collision_detection: True
max_allowed_time_to_collision: 1.0
use_cost_regulated_linear_velocity_scaling: True
cost_scaling_dist: 0.6
cost_scaling_gain: 1.0
regulated_linear_scaling_min_radius: 0.35
regulated_linear_scaling_min_speed: 0.1
use_rotate_to_heading: True
rotate_to_heading_min_angle: 0.5
max_robot_pose_search_dist: 10.0
use_interpolation: False
allow_reversing: False
controller_server_rclcpp_node:
ros__parameters:
use_sim_time: False
local_costmap:
local_costmap:
ros__parameters:
update_frequency: 1.0
publish_frequency: 2.0
transform_tolerance: 0.5
transform_tolerance: 2.0
global_frame: odom
robot_base_frame: base_link
use_sim_time: False
rolling_window: true
rolling_window: True
width: 3
height: 3
resolution: 0.05
footprint: "[[0.14, 0.085],
[0.14, -0.085],
[-0.14, -0.085],
[-0.14, 0.085]]"
footprint: '[[0.14, 0.085], [0.14, -0.085], [-0.14, -0.085], [-0.14, 0.085]]'
footprint_padding: 0.02
plugins: ["voxel_layer", "inflation_layer"]
plugins:
- voxel_layer
- inflation_layer
inflation_layer:
plugin: "nav2_costmap_2d::InflationLayer"
plugin: nav2_costmap_2d::InflationLayer
cost_scaling_factor: 3.0
inflation_radius: 0.2
inflation_radius: 0.3
voxel_layer:
plugin: "nav2_costmap_2d::VoxelLayer"
plugin: nav2_costmap_2d::VoxelLayer
enabled: True
publish_voxel_map: True
origin_z: 0.0
@@ -182,7 +127,7 @@ local_costmap:
max_obstacle_height: 2.0
clearing: True
marking: True
data_type: "LaserScan"
data_type: LaserScan
raytrace_max_range: 3.0
raytrace_min_range: 0.0
obstacle_max_range: 2.5
@@ -196,27 +141,25 @@ local_costmap:
local_costmap_rclcpp_node:
ros__parameters:
use_sim_time: False
global_costmap:
global_costmap:
ros__parameters:
use_sim_time: False
transform_tolerance: 0.5
transform_tolerance: 2.0
update_frequency: 1.0
publish_frequency: 1.0
global_frame: map
robot_base_frame: base_link
use_sim_time: False
footprint: "[[0.14, 0.085],
[0.14, -0.085],
[-0.14, -0.085],
[-0.14, 0.085]]"
footprint: '[[0.14, 0.085], [0.14, -0.085], [-0.14, -0.085], [-0.14, 0.085]]'
footprint_padding: 0.02
resolution: 0.05
track_unknown_space: true
plugins: ["static_layer", "obstacle_layer", "inflation_layer"]
track_unknown_space: True
plugins:
- static_layer
- obstacle_layer
- inflation_layer
obstacle_layer:
plugin: "nav2_costmap_2d::ObstacleLayer"
plugin: nav2_costmap_2d::ObstacleLayer
enabled: True
observation_sources: scan
scan:
@@ -224,18 +167,18 @@ global_costmap:
max_obstacle_height: 2.0
clearing: True
marking: True
data_type: "LaserScan"
data_type: LaserScan
raytrace_max_range: 3.0
raytrace_min_range: 0.0
obstacle_max_range: 2.5
obstacle_min_range: 0.0
static_layer:
plugin: "nav2_costmap_2d::StaticLayer"
plugin: nav2_costmap_2d::StaticLayer
map_subscribe_transient_local: True
inflation_layer:
plugin: "nav2_costmap_2d::InflationLayer"
plugin: nav2_costmap_2d::InflationLayer
cost_scaling_factor: 3.0
inflation_radius: 0.2
inflation_radius: 0.3
always_send_full_costmap: True
global_costmap_client:
ros__parameters:
@@ -243,7 +186,6 @@ global_costmap:
global_costmap_rclcpp_node:
ros__parameters:
use_sim_time: False
map_saver:
ros__parameters:
use_sim_time: False
@@ -251,93 +193,90 @@ map_saver:
free_thresh_default: 0.25
occupied_thresh_default: 0.65
map_subscribe_transient_local: True
planner_server:
ros__parameters:
planner_plugins: ["GridBased"]
planner_plugins:
- GridBased
use_sim_time: False
GridBased:
plugin: "nav2_smac_planner/SmacPlannerHybrid"
downsample_costmap: false
plugin: nav2_smac_planner/SmacPlannerHybrid
downsample_costmap: False
downsampling_factor: 1
tolerance: 0.25
allow_unknown: true
allow_unknown: True
max_iterations: 100000
max_on_approach_iterations: 1000
max_planning_time: 5.0
motion_model_for_search: "REEDS_SHEPP"
motion_model_for_search: REEDS_SHEPP
angle_quantization_bins: 72
analytic_expansion_ratio: 3.5
analytic_expansion_ratio: 2.0
analytic_expansion_max_length: 3.0
minimum_turning_radius: 0.40
reverse_penalty: 1.3 # 后退惩罚,越小越愿意后退(默认 2.0,设为 1.3 允许灵活倒车)
minimum_turning_radius: 0.35
reverse_penalty: 1.3
change_penalty: 0.0
non_straight_penalty: 0.5
non_straight_penalty: 0.0
cost_penalty: 5.0
retrospective_penalty: 0.015
lookup_table_size: 5.0
cache_obstacle_heuristic: false
viz_expansions: false
cache_obstacle_heuristic: False
viz_expansions: False
smooth_path: True
smoother:
max_iterations: 1000
w_smooth: 0.3
w_smooth: 0.4
w_data: 0.2
tolerance: 1.0e-10
do_refinement: true
refinement_num: 2
do_refinement: True
refinement_num: 4
planner_server_rclcpp_node:
ros__parameters:
use_sim_time: False
smoother_server:
ros__parameters:
use_sim_time: False
smoother_plugins: ["simple_smoother"]
smoother_plugins:
- simple_smoother
simple_smoother:
plugin: "nav2_smoother::SimpleSmoother"
plugin: nav2_smoother::SimpleSmoother
tolerance: 1.0e-10
max_its: 1000
do_refinement: True
behavior_server:
ros__parameters:
costmap_topic: local_costmap/costmap_raw
footprint_topic: local_costmap/published_footprint
cycle_frequency: 10.0
behavior_plugins: ["spin", "backup", "wait"]
behavior_plugins:
- spin
- backup
- wait
spin:
plugin: "nav2_behaviors/Spin" # 需保留以匹配默认 BT XML
plugin: nav2_behaviors/Spin
backup:
plugin: "nav2_behaviors/BackUp"
plugin: nav2_behaviors/BackUp
backup_dist: 0.8
backup_speed: 0.18
wait:
plugin: "nav2_behaviors/Wait"
plugin: nav2_behaviors/Wait
wait_duration: 0.5
global_frame: odom
robot_base_frame: base_link
transform_tolerance: 0.5
transform_tolerance: 2.0
use_sim_time: False
simulate_ahead_time: 2.0
max_rotational_vel: 1.0
min_rotational_vel: 0.4
rotational_acc_lim: 3.2
robot_state_publisher:
ros__parameters:
use_sim_time: False
waypoint_follower:
ros__parameters:
loop_rate: 20
use_sim_time: False
stop_on_failure: false
waypoint_task_executor_plugin: "wait_at_waypoint"
stop_on_failure: False
waypoint_task_executor_plugin: wait_at_waypoint
wait_at_waypoint:
plugin: "nav2_waypoint_follower::WaitAtWaypoint"
plugin: nav2_waypoint_follower::WaitAtWaypoint
enabled: True
waypoint_pause_duration: 200

View File

@@ -1,17 +1,17 @@
slam_toolbox:
ros__parameters:
use_sim_time: True
use_sim_time: False
bt_navigator:
ros__parameters:
use_sim_time: True
use_sim_time: False
global_frame: map
robot_base_frame: base_footprint
odom_topic: /odom
bt_loop_duration: 50
default_server_timeout: 20
# 阿克曼底盘专用 BT移除 Spin恢复行为 = 清代价地图 → 后退 → 等待
default_nav_to_pose_bt_xml: /home/guoch/test_ws/install/gc_navigation2_slamtoolbox/share/gc_navigation2_slamtoolbox/config/nav_to_pose_ackermann.xml
default_nav_to_pose_bt_xml: /home/sunrise/yiliao_ws/install/gc_navigation2_slamtoolbox/share/gc_navigation2_slamtoolbox/config/nav_to_pose_ackermann.xml
plugin_lib_names:
- nav2_compute_path_to_pose_action_bt_node
- nav2_compute_path_through_poses_action_bt_node
@@ -59,17 +59,17 @@ bt_navigator:
bt_navigator_rclcpp_node:
ros__parameters:
use_sim_time: True
use_sim_time: False
controller_server:
ros__parameters:
use_sim_time: True
use_sim_time: False
controller_frequency: 20.0
FollowPath:
plugin: "nav2_mppi_controller::MPPIController"
time_steps: 36
time_steps: 20
model_dt: 0.05
batch_size: 1000
batch_size: 200
vx_std: 0.2
vy_std: 0.0
wz_std: 0.4
@@ -105,7 +105,7 @@ controller_server:
PreferForwardCritic:
enabled: true
cost_power: 1
cost_weight: 5.0
cost_weight: 2.0
threshold_to_consider: 0.5
CostCritic:
enabled: true
@@ -119,7 +119,7 @@ controller_server:
PathAlignCritic:
enabled: true
cost_power: 1
cost_weight: 14.0
cost_weight: 7.0 # PathAlignCritic
max_path_occupancy_ratio: 0.05
trajectory_point_step: 4
threshold_to_consider: 0.5
@@ -142,17 +142,17 @@ controller_server:
controller_server_rclcpp_node:
ros__parameters:
use_sim_time: True
use_sim_time: False
local_costmap:
local_costmap:
ros__parameters:
update_frequency: 5.0
update_frequency: 1.0
publish_frequency: 2.0
transform_tolerance: 0.5
global_frame: odom
robot_base_frame: base_link
use_sim_time: True
use_sim_time: False
rolling_window: true
width: 3
height: 3
@@ -166,7 +166,7 @@ local_costmap:
inflation_layer:
plugin: "nav2_costmap_2d::InflationLayer"
cost_scaling_factor: 3.0
inflation_radius: 0.55
inflation_radius: 0.2
voxel_layer:
plugin: "nav2_costmap_2d::VoxelLayer"
enabled: True
@@ -192,21 +192,21 @@ local_costmap:
always_send_full_costmap: True
local_costmap_client:
ros__parameters:
use_sim_time: True
use_sim_time: False
local_costmap_rclcpp_node:
ros__parameters:
use_sim_time: True
use_sim_time: False
global_costmap:
global_costmap:
ros__parameters:
use_sim_time: True
use_sim_time: False
transform_tolerance: 0.5
update_frequency: 1.0
publish_frequency: 1.0
global_frame: map
robot_base_frame: base_link
use_sim_time: True
use_sim_time: False
footprint: "[[0.14, 0.085],
[0.14, -0.085],
[-0.14, -0.085],
@@ -235,18 +235,18 @@ global_costmap:
inflation_layer:
plugin: "nav2_costmap_2d::InflationLayer"
cost_scaling_factor: 3.0
inflation_radius: 0.55
inflation_radius: 0.2
always_send_full_costmap: True
global_costmap_client:
ros__parameters:
use_sim_time: True
use_sim_time: False
global_costmap_rclcpp_node:
ros__parameters:
use_sim_time: True
use_sim_time: False
map_saver:
ros__parameters:
use_sim_time: True
use_sim_time: False
save_map_timeout: 5.0
free_thresh_default: 0.25
occupied_thresh_default: 0.65
@@ -255,7 +255,7 @@ map_saver:
planner_server:
ros__parameters:
planner_plugins: ["GridBased"]
use_sim_time: True
use_sim_time: False
GridBased:
plugin: "nav2_smac_planner/SmacPlannerHybrid"
@@ -263,7 +263,7 @@ planner_server:
downsampling_factor: 1
tolerance: 0.25
allow_unknown: true
max_iterations: 1000000
max_iterations: 100000
max_on_approach_iterations: 1000
max_planning_time: 5.0
motion_model_for_search: "REEDS_SHEPP"
@@ -273,10 +273,10 @@ planner_server:
minimum_turning_radius: 0.40
reverse_penalty: 1.3 # 后退惩罚,越小越愿意后退(默认 2.0,设为 1.3 允许灵活倒车)
change_penalty: 0.0
non_straight_penalty: 1.2
cost_penalty: 2.0
non_straight_penalty: 0.5
cost_penalty: 5.0
retrospective_penalty: 0.015
lookup_table_size: 20.0
lookup_table_size: 5.0
cache_obstacle_heuristic: false
viz_expansions: false
smooth_path: True
@@ -291,11 +291,11 @@ planner_server:
planner_server_rclcpp_node:
ros__parameters:
use_sim_time: True
use_sim_time: False
smoother_server:
ros__parameters:
use_sim_time: True
use_sim_time: False
smoother_plugins: ["simple_smoother"]
simple_smoother:
plugin: "nav2_smoother::SimpleSmoother"
@@ -321,7 +321,7 @@ behavior_server:
global_frame: odom
robot_base_frame: base_link
transform_tolerance: 0.5
use_sim_time: True
use_sim_time: False
simulate_ahead_time: 2.0
max_rotational_vel: 1.0
min_rotational_vel: 0.4
@@ -329,12 +329,12 @@ behavior_server:
robot_state_publisher:
ros__parameters:
use_sim_time: True
use_sim_time: False
waypoint_follower:
ros__parameters:
loop_rate: 20
use_sim_time: True
use_sim_time: False
stop_on_failure: false
waypoint_task_executor_plugin: "wait_at_waypoint"
wait_at_waypoint:

View File

@@ -0,0 +1,343 @@
slam_toolbox:
ros__parameters:
use_sim_time: False
bt_navigator:
ros__parameters:
use_sim_time: False
global_frame: map
robot_base_frame: base_footprint
odom_topic: /odom
bt_loop_duration: 50
default_server_timeout: 20
# 阿克曼底盘专用 BT移除 Spin恢复行为 = 清代价地图 → 后退 → 等待
default_nav_to_pose_bt_xml: /home/sunrise/yiliao_ws/install/gc_navigation2_slamtoolbox/share/gc_navigation2_slamtoolbox/config/nav_to_pose_ackermann.xml
plugin_lib_names:
- nav2_compute_path_to_pose_action_bt_node
- nav2_compute_path_through_poses_action_bt_node
- nav2_smooth_path_action_bt_node
- nav2_follow_path_action_bt_node
- nav2_spin_action_bt_node
- nav2_wait_action_bt_node
- nav2_back_up_action_bt_node
- nav2_drive_on_heading_bt_node
- nav2_clear_costmap_service_bt_node
- nav2_is_stuck_condition_bt_node
- nav2_goal_reached_condition_bt_node
- nav2_goal_updated_condition_bt_node
- nav2_globally_updated_goal_condition_bt_node
- nav2_is_path_valid_condition_bt_node
- nav2_initial_pose_received_condition_bt_node
- nav2_reinitialize_global_localization_service_bt_node
- nav2_rate_controller_bt_node
- nav2_distance_controller_bt_node
- nav2_speed_controller_bt_node
- nav2_truncate_path_action_bt_node
- nav2_truncate_path_local_action_bt_node
- nav2_goal_updater_node_bt_node
- nav2_recovery_node_bt_node
- nav2_pipeline_sequence_bt_node
- nav2_round_robin_node_bt_node
- nav2_transform_available_condition_bt_node
- nav2_time_expired_condition_bt_node
- nav2_path_expiring_timer_condition
- nav2_distance_traveled_condition_bt_node
- nav2_single_trigger_bt_node
- nav2_is_battery_low_condition_bt_node
- nav2_navigate_through_poses_action_bt_node
- nav2_navigate_to_pose_action_bt_node
- nav2_remove_passed_goals_action_bt_node
- nav2_planner_selector_bt_node
- nav2_controller_selector_bt_node
- nav2_goal_checker_selector_bt_node
- nav2_controller_cancel_bt_node
- nav2_path_longer_on_approach_bt_node
- nav2_wait_cancel_bt_node
- nav2_spin_cancel_bt_node
- nav2_back_up_cancel_bt_node
- nav2_drive_on_heading_cancel_bt_node
bt_navigator_rclcpp_node:
ros__parameters:
use_sim_time: False
controller_server:
ros__parameters:
use_sim_time: False
controller_frequency: 20.0
FollowPath:
plugin: "nav2_mppi_controller::MPPIController"
time_steps: 20
model_dt: 0.05
batch_size: 200
vx_std: 0.2
vy_std: 0.0
wz_std: 0.8
vx_max: 0.5
vx_min: -0.35
vy_max: 0.0
wz_max: 1.9
iteration_count: 1
temperature: 0.3
gamma: 0.015
motion_model: "Ackermann"
visualize: false
TrajectoryVisualizer:
trajectory_step: 5
time_step: 3
AckermannConstraints:
min_turning_r: 0.4
critics: ["ConstraintCritic", "CostCritic", "GoalCritic", "GoalAngleCritic", "PathAlignCritic", "PathFollowCritic", "PathAngleCritic", "PreferForwardCritic"]
ConstraintCritic:
enabled: true
cost_power: 1
cost_weight: 4.0
GoalCritic:
enabled: true
cost_power: 1
cost_weight: 5.0
threshold_to_consider: 2.0
GoalAngleCritic:
enabled: true
cost_power: 1
cost_weight: 3.0
threshold_to_consider: 0.5
PreferForwardCritic:
enabled: true
cost_power: 1
cost_weight: 2.0
threshold_to_consider: 0.5
CostCritic:
enabled: true
cost_power: 1
cost_weight: 8.0
critical_cost: 300.0
consider_footprint: true
collision_cost: 1000000.0
near_goal_distance: 1.0
trajectory_point_step: 2
PathAlignCritic:
enabled: true
cost_power: 1
cost_weight: 5.0 # PathAlignCritic
max_path_occupancy_ratio: 0.05
trajectory_point_step: 4
threshold_to_consider: 0.5
offset_from_furthest: 10
use_path_orientations: false
PathFollowCritic:
enabled: true
cost_power: 1
cost_weight: 3.0
offset_from_furthest: 5
threshold_to_consider: 1.4
PathAngleCritic:
enabled: true
cost_power: 1
cost_weight: 1.0
offset_from_furthest: 4
threshold_to_consider: 0.5
max_angle_to_furthest: 1.0
forward_preference: true
controller_server_rclcpp_node:
ros__parameters:
use_sim_time: False
local_costmap:
local_costmap:
ros__parameters:
update_frequency: 1.0
publish_frequency: 2.0
transform_tolerance: 2.0
global_frame: odom
robot_base_frame: base_link
use_sim_time: False
rolling_window: true
width: 3
height: 3
resolution: 0.05
footprint: "[[0.14, 0.085],
[0.14, -0.085],
[-0.14, -0.085],
[-0.14, 0.085]]"
footprint_padding: 0.02
plugins: ["voxel_layer", "inflation_layer"]
inflation_layer:
plugin: "nav2_costmap_2d::InflationLayer"
cost_scaling_factor: 3.0
inflation_radius: 0.3
voxel_layer:
plugin: "nav2_costmap_2d::VoxelLayer"
enabled: True
publish_voxel_map: True
origin_z: 0.0
z_resolution: 0.05
z_voxels: 16
max_obstacle_height: 2.0
mark_threshold: 0
observation_sources: scan
scan:
topic: /scan
max_obstacle_height: 2.0
clearing: True
marking: True
data_type: "LaserScan"
raytrace_max_range: 3.0
raytrace_min_range: 0.0
obstacle_max_range: 2.5
obstacle_min_range: 0.0
static_layer:
map_subscribe_transient_local: True
always_send_full_costmap: True
local_costmap_client:
ros__parameters:
use_sim_time: False
local_costmap_rclcpp_node:
ros__parameters:
use_sim_time: False
global_costmap:
global_costmap:
ros__parameters:
use_sim_time: False
transform_tolerance: 2.0
update_frequency: 1.0
publish_frequency: 1.0
global_frame: map
robot_base_frame: base_link
use_sim_time: False
footprint: "[[0.14, 0.085],
[0.14, -0.085],
[-0.14, -0.085],
[-0.14, 0.085]]"
footprint_padding: 0.02
resolution: 0.05
track_unknown_space: true
plugins: ["static_layer", "obstacle_layer", "inflation_layer"]
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: 3.0
raytrace_min_range: 0.0
obstacle_max_range: 2.5
obstacle_min_range: 0.0
static_layer:
plugin: "nav2_costmap_2d::StaticLayer"
map_subscribe_transient_local: True
inflation_layer:
plugin: "nav2_costmap_2d::InflationLayer"
cost_scaling_factor: 3.0
inflation_radius: 0.3
always_send_full_costmap: True
global_costmap_client:
ros__parameters:
use_sim_time: False
global_costmap_rclcpp_node:
ros__parameters:
use_sim_time: False
map_saver:
ros__parameters:
use_sim_time: False
save_map_timeout: 5.0
free_thresh_default: 0.25
occupied_thresh_default: 0.65
map_subscribe_transient_local: True
planner_server:
ros__parameters:
planner_plugins: ["GridBased"]
use_sim_time: False
GridBased:
plugin: "nav2_smac_planner/SmacPlannerHybrid"
downsample_costmap: false
downsampling_factor: 1
tolerance: 0.25
allow_unknown: true
max_iterations: 100000
max_on_approach_iterations: 1000
max_planning_time: 5.0
motion_model_for_search: "REEDS_SHEPP"
angle_quantization_bins: 72
analytic_expansion_ratio: 2.0
analytic_expansion_max_length: 3.0
minimum_turning_radius: 0.40
reverse_penalty: 1.3 # 后退惩罚,越小越愿意后退(默认 2.0,设为 1.3 允许灵活倒车)
change_penalty: 0.0
non_straight_penalty: 0.0
cost_penalty: 5.0
retrospective_penalty: 0.015
lookup_table_size: 5.0
cache_obstacle_heuristic: false
viz_expansions: false
smooth_path: True
smoother:
max_iterations: 1000
w_smooth: 0.4
w_data: 0.2
tolerance: 1.0e-10
do_refinement: true
refinement_num: 4
planner_server_rclcpp_node:
ros__parameters:
use_sim_time: False
smoother_server:
ros__parameters:
use_sim_time: False
smoother_plugins: ["simple_smoother"]
simple_smoother:
plugin: "nav2_smoother::SimpleSmoother"
tolerance: 1.0e-10
max_its: 1000
do_refinement: True
behavior_server:
ros__parameters:
costmap_topic: local_costmap/costmap_raw
footprint_topic: local_costmap/published_footprint
cycle_frequency: 10.0
behavior_plugins: ["spin", "backup", "wait"]
spin:
plugin: "nav2_behaviors/Spin" # 需保留以匹配默认 BT XML
backup:
plugin: "nav2_behaviors/BackUp"
backup_dist: 0.8
backup_speed: 0.18
wait:
plugin: "nav2_behaviors/Wait"
wait_duration: 0.5
global_frame: odom
robot_base_frame: base_link
transform_tolerance: 2.0
use_sim_time: False
simulate_ahead_time: 2.0
max_rotational_vel: 1.0
min_rotational_vel: 0.4
rotational_acc_lim: 3.2
robot_state_publisher:
ros__parameters:
use_sim_time: False
waypoint_follower:
ros__parameters:
loop_rate: 20
use_sim_time: False
stop_on_failure: false
waypoint_task_executor_plugin: "wait_at_waypoint"
wait_at_waypoint:
plugin: "nav2_waypoint_follower::WaitAtWaypoint"
enabled: True
waypoint_pause_duration: 200

View File

@@ -20,7 +20,7 @@ ekf_filter_node:
odom0_config: [true, false, false,
false, false, false,
true, true, false,
false, false, true,
false, false, false,
false, false, false]
odom0_queue_size: 10
odom0_nodelay: false

View File

@@ -0,0 +1,48 @@
### ekf config file ###
ekf_filter_node:
ros__parameters:
frequency: 20.0
sensor_timeout: 2.0
two_d_mode: true
transform_time_offset: 0.0
transform_timeout: 0.2
print_diagnostics: false
debug: false
publish_tf: true
publish_acceleration: false
map_frame: map
odom_frame: odom
base_link_frame: base_footprint
world_frame: odom
odom0: odom
odom0_config: [true, false, false,
false, false, false,
true, true, false,
false, false, true,
false, false, false]
odom0_queue_size: 10
odom0_nodelay: false
odom0_differential: true
odom0_relative: false
imu0: /imu/data_raw
imu0_config: [false, false, false,
false, false, true,
false, false, false,
false, false, true,
false, false, false]
imu0_nodelay: false
imu0_differential: false
imu0_relative: true
imu0_queue_size: 10
imu0_remove_gravitational_acceleration: true
use_control: false
stamped_control: false
control_timeout: 0.2
control_config: [true, false, false, false, false, true]
acceleration_limits: [1.3, 0.0, 0.0, 0.0, 0.0, 3.4]
deceleration_limits: [1.3, 0.0, 0.0, 0.0, 0.0, 4.5]
acceleration_gains: [0.8, 0.0, 0.0, 0.0, 0.0, 0.9]

View File

@@ -1,51 +1,24 @@
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch.conditions import IfCondition, UnlessCondition
import launch_ros.actions
def generate_launch_description():
akmcar = LaunchConfiguration('akmcar', default='false')
robot_parameters = [
{'usart_port_name': '/dev/ttyACM0',
'serial_baud_rate': 115200,
'robot_frame_id': 'base_link',
'odom_frame_id': 'odom',
'cmd_vel': 'cmd_vel',
'akm_cmd_vel': 'none',
'product_number': 0,
# Odom covariance (higher = more uncertainty, EKF trusts odom less)
'odom_pose_cov_x': 0.01,
'odom_pose_cov_y': 0.01,
'odom_pose_cov_yaw': 0.0225}
]
return LaunchDescription([
DeclareLaunchArgument(
'akmcar',
default_value='false',
description='Use simulation (Gazebo) clock if true'
),
launch_ros.actions.Node(
condition=IfCondition(akmcar),
package='origincar_base',
executable='origincar_base_node',
parameters=robot_parameters + [{'akm_cmd_vel': 'ackermann_cmd'}],
remappings=[('/cmd_vel', 'cmd_vel')],
),
launch_ros.actions.Node(
condition=IfCondition(akmcar),
package='origincar_base',
executable='cmd_vel_to_ackermann_drive.py',
name='cmd_vel_to_ackermann_drive',
),
launch_ros.actions.Node(
condition=UnlessCondition(akmcar),
package='origincar_base',
executable='origincar_base_node',
parameters=robot_parameters + [{'akm_cmd_vel': 'none'}],
parameters=robot_parameters,
)
])

View File

@@ -0,0 +1,51 @@
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch.conditions import IfCondition, UnlessCondition
import launch_ros.actions
def generate_launch_description():
akmcar = LaunchConfiguration('akmcar', default='false')
robot_parameters = [
{'usart_port_name': '/dev/ttyACM0',
'serial_baud_rate': 115200,
'robot_frame_id': 'base_link',
'odom_frame_id': 'odom',
'cmd_vel': 'cmd_vel',
'product_number': 0,
# Odom covariance (higher = more uncertainty, EKF trusts odom less)
'odom_pose_cov_x': 0.01,
'odom_pose_cov_y': 0.01,
'odom_pose_cov_yaw': 0.0225}
]
return LaunchDescription([
DeclareLaunchArgument(
'akmcar',
default_value='false',
description='Use simulation (Gazebo) clock if true'
),
launch_ros.actions.Node(
condition=IfCondition(akmcar),
package='origincar_base',
executable='origincar_base_node',
parameters=robot_parameters + [{'akm_cmd_vel': 'ackermann_cmd'}],
remappings=[('/cmd_vel', 'cmd_vel')],
),
launch_ros.actions.Node(
condition=IfCondition(akmcar),
package='origincar_base',
executable='cmd_vel_to_ackermann_drive.py',
name='cmd_vel_to_ackermann_drive',
),
launch_ros.actions.Node(
condition=UnlessCondition(akmcar),
package='origincar_base',
executable='origincar_base_node',
parameters=robot_parameters + [{'akm_cmd_vel': 'none'}],
)
])

View File

@@ -23,7 +23,7 @@ def generate_launch_description():
carto_slam = LaunchConfiguration('carto_slam', default='false')
carto_slam_dec = DeclareLaunchArgument('carto_slam',default_value='false')
akmcar = LaunchConfiguration('akmcar', default='true')
akmcar = LaunchConfiguration('akmcar', default='false')
akmcar_dec = DeclareLaunchArgument('akmcar', default_value='true',
description='阿克曼底盘模式 (true=阿克曼, false=差速)')

View File

@@ -0,0 +1,85 @@
cmake_minimum_required(VERSION 3.5)
project(past_control)
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(std_msgs REQUIRED)
find_package(nav_msgs REQUIRED)
find_package(nav2_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(tf2 REQUIRED)
find_package(tf2_ros REQUIRED)
find_package(visualization_msgs REQUIRED)
find_package(origincar_msg REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(builtin_interfaces REQUIRED)
# generate custom messages
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Obstacle.msg"
"msg/ObstacleArray.msg"
DEPENDENCIES std_msgs
ADD_LINTER_TESTS
)
include_directories(include)
# lane_follower_node
add_executable(lane_follower_node
src/lane_follower_node.cpp
)
ament_target_dependencies(lane_follower_node
rclcpp std_msgs geometry_msgs
)
# obstacle_detector_node
add_executable(obstacle_detector_node
src/obstacle_detector_node.cpp
)
ament_target_dependencies(obstacle_detector_node
rclcpp std_msgs geometry_msgs nav_msgs tf2 tf2_ros visualization_msgs
)
rosidl_get_typesupport_target(cpp_typesupport_target ${PROJECT_NAME} rosidl_typesupport_cpp)
target_link_libraries(obstacle_detector_node ${cpp_typesupport_target})
# racing_orchestrator — subscribes to /plan (Nav2 planner_server) + PID following
add_executable(racing_orchestrator
src/racing_orchestrator.cpp
)
ament_target_dependencies(racing_orchestrator
rclcpp rclcpp_action std_msgs nav_msgs nav2_msgs geometry_msgs tf2 tf2_ros origincar_msg visualization_msgs
)
install(TARGETS
lane_follower_node
obstacle_detector_node
racing_orchestrator
DESTINATION lib/${PROJECT_NAME}
)
install(
DIRECTORY launch config
DESTINATION share/${PROJECT_NAME}
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
set(ament_cmake_copyright_FOUND TRUE)
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()

View File

@@ -0,0 +1,51 @@
# past_control — Racing control stack configuration
# No a_star_planner_node: global planning handled by Nav2 planner_server via /plan
# ============================================================
# lane_follower_node — Visual lane centering
# ============================================================
lane_follower_node:
ros__parameters:
follow_linear_speed: 0.3
follow_angular_ratio: 1.0
image_width: 640.0
# ============================================================
# obstacle_detector_node — DNN-based obstacle perception
# ============================================================
obstacle_detector_node:
ros__parameters:
confidence_threshold: 0.5
processing_latency: 0.1
camera_hfov: 1.0472 # 60 degrees
camera_height: 0.3
camera_pitch: 0.0
max_detection_range: 3.0
publish_rate: 10.0
# ============================================================
# racing_orchestrator — Task FSM + PID path following (Nav2 /plan) + arbitration
# ============================================================
racing_orchestrator:
ros__parameters:
# PID path following (path source: Nav2 planner_server /plan)
follow_linear_speed: 0.4
guide_step: 5
arrive_square: 0.25 # distance^2 threshold (0.5m)
angular_kp: 10.0
angular_ki: 0.0
angular_kd: 0.1
angular_integral_max: 1.0
angular_output_max: 2.0
angular_max_err: 3.14
# Obstacle avoidance fallback
avoid_linear_speed: 0.2
avoid_angular_z: 0.8
# QR scan mode
qr_scan_speed: 0.15
qr_scan_duration: 3.0
# Control loop rate
control_rate: 20.0

View File

@@ -0,0 +1,87 @@
#ifndef PAST_CONTROL__TOOLS_H
#define PAST_CONTROL__TOOLS_H
#include <algorithm>
#include <chrono>
namespace past_control
{
inline double limit(double val, double min_val, double max_val)
{
return std::min(std::max(val, min_val), max_val);
}
class PID
{
public:
PID()
: kp_(0.0), ki_(0.0), kd_(0.0),
integral_max_(0.0), output_max_(0.0),
integral_(0.0), prev_error_(0.0), first_run_(true),
max_err_(0.0)
{
}
void init(double kp, double ki, double kd,
double integral_max, double output_max, double max_err = 0.0)
{
kp_ = kp;
ki_ = ki;
kd_ = kd;
integral_max_ = integral_max;
output_max_ = output_max;
max_err_ = max_err;
reset();
}
void reset()
{
integral_ = 0.0;
prev_error_ = 0.0;
first_run_ = true;
}
double update(double error, double dt = 0.02)
{
// Clamp error if max_err_ > 0
if (max_err_ > 0.0) {
error = limit(error, -max_err_, max_err_);
}
// Proportional
double p_out = kp_ * error;
// Integral (with clamping)
integral_ += error * dt;
integral_ = limit(integral_, -integral_max_, integral_max_);
double i_out = ki_ * integral_;
// Derivative (skip on first call)
double d_out = 0.0;
if (!first_run_) {
double derivative = (error - prev_error_) / dt;
d_out = kd_ * derivative;
}
first_run_ = false;
prev_error_ = error;
// Output clamping
double output = p_out + i_out + d_out;
output = limit(output, -output_max_, output_max_);
return output;
}
private:
double kp_, ki_, kd_;
double integral_max_, output_max_;
double integral_;
double prev_error_;
bool first_run_;
double max_err_; // initialized to 0 in constructor
};
} // namespace past_control
#endif // PAST_CONTROL__TOOLS_H

View File

@@ -0,0 +1,52 @@
"""
Launch file for past_control — racing control stack.
Launches 3 nodes (a_star_planner removed — Nav2 planner_server handles global planning):
1. lane_follower_node — visual lane centering
2. obstacle_detector_node — DNN obstacle perception
3. racing_orchestrator — task FSM + PID following (/plan from Nav2) + cmd_vel arbitration
Usage:
ros2 launch past_control past_control.launch.py
"""
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
pkg_dir = get_package_share_directory("past_control")
config_path = os.path.join(pkg_dir, "config", "past_control.yaml")
lane_follower = Node(
package="past_control",
executable="lane_follower_node",
name="lane_follower_node",
output="screen",
parameters=[config_path],
)
obstacle_detector = Node(
package="past_control",
executable="obstacle_detector_node",
name="obstacle_detector_node",
output="screen",
parameters=[config_path],
)
racing_orchestrator = Node(
package="past_control",
executable="racing_orchestrator",
name="racing_orchestrator",
output="screen",
parameters=[config_path],
)
return LaunchDescription([
lane_follower,
obstacle_detector,
racing_orchestrator,
])

View File

@@ -0,0 +1,5 @@
std_msgs/Header header
float64 x
float64 y
float64 radius
string type

View File

@@ -0,0 +1 @@
Obstacle[] obstacles

View File

@@ -0,0 +1,36 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>past_control</name>
<version>0.0.0</version>
<description>Racing control stack: lane follower, obstacle detector, and racing orchestrator (Nav2 /plan subscriber + PID following)</description>
<maintainer email="2314753575@qq.com">sunrise</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
<depend>rclcpp</depend>
<depend>rclcpp_action</depend>
<depend>std_msgs</depend>
<depend>nav_msgs</depend>
<depend>nav2_msgs</depend>
<depend>geometry_msgs</depend>
<depend>sensor_msgs</depend>
<depend>tf2</depend>
<depend>tf2_ros</depend>
<depend>visualization_msgs</depend>
<depend>origincar_msg</depend>
<depend>builtin_interfaces</depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>

View File

@@ -0,0 +1,81 @@
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include "std_msgs/msg/float32_multi_array.hpp"
#include "std_msgs/msg/float32_multi_array.hpp"
class LaneFollowerNode : public rclcpp::Node
{
public:
LaneFollowerNode()
: Node("lane_follower_node")
{
this->declare_parameter("follow_linear_speed", 0.3);
this->declare_parameter("follow_angular_ratio", 1.0);
this->declare_parameter("image_width", 640.0);
follow_linear_speed_ = this->get_parameter("follow_linear_speed").as_double();
follow_angular_ratio_ = this->get_parameter("follow_angular_ratio").as_double();
image_width_ = this->get_parameter("image_width").as_double();
// Subscriber: racing track center detection
// Expects Float32MultiArray with [center_x] in image coordinates,
// or a custom format. We assume center_x relative to image center.
track_sub_ = this->create_subscription<std_msgs::msg::Float32MultiArray>(
"racing_track_center_detection", 10,
std::bind(&LaneFollowerNode::trackCallback, this, std::placeholders::_1));
// Publisher: lane cmd_vel (NOT /cmd_vel directly)
cmd_vel_pub_ = this->create_publisher<geometry_msgs::msg::Twist>(
"/lane_cmd_vel", 10);
RCLCPP_INFO(this->get_logger(),
"LaneFollowerNode started. linear=%.2f, angular_ratio=%.2f",
follow_linear_speed_, follow_angular_ratio_);
}
private:
void trackCallback(const std_msgs::msg::Float32MultiArray::SharedPtr msg)
{
if (msg->data.empty()) {
// No track detected — stop
geometry_msgs::msg::Twist cmd;
cmd.linear.x = 0.0;
cmd.angular.z = 0.0;
cmd_vel_pub_->publish(cmd);
return;
}
// center_x from detection: pixel offset from image center
// range: [-image_width/2, image_width/2]
double center_offset = msg->data[0]; // in pixels
// Normalize to [-1, 1]
double normalized_error = center_offset / (image_width_ / 2.0);
normalized_error = std::max(-1.0, std::min(1.0, normalized_error));
// Angular velocity: steer toward center line
// Positive error = line is to the right → positive angular to turn right
double angular_z = follow_angular_ratio_ * normalized_error;
geometry_msgs::msg::Twist cmd;
cmd.linear.x = follow_linear_speed_;
cmd.angular.z = angular_z;
cmd_vel_pub_->publish(cmd);
}
rclcpp::Subscription<std_msgs::msg::Float32MultiArray>::SharedPtr track_sub_;
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr cmd_vel_pub_;
double follow_linear_speed_;
double follow_angular_ratio_;
double image_width_;
};
int main(int argc, char* argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<LaneFollowerNode>());
rclcpp::shutdown();
return 0;
}

View File

@@ -0,0 +1,222 @@
#include <memory>
#include <mutex>
#include <cmath>
#include <vector>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/float32_multi_array.hpp"
#include "nav_msgs/msg/odometry.hpp"
#include "visualization_msgs/msg/marker_array.hpp"
#include "geometry_msgs/msg/point.hpp"
#include "std_msgs/msg/header.hpp"
#include "tf2/LinearMath/Quaternion.h"
#include "tf2/LinearMath/Matrix3x3.h"
#include "past_control/msg/obstacle_array.hpp"
#include "past_control/msg/obstacle.hpp"
// DNN detection format (per the existing hobot_dnn convention)
// Typically a custom array of detections: [class_id, x1, y1, x2, y2, confidence, ...]
// We'll use Float32MultiArray for input
class ObstacleDetectorNode : public rclcpp::Node
{
public:
ObstacleDetectorNode()
: Node("obstacle_detector_node"), has_odom_(false)
{
this->declare_parameter("confidence_threshold", 0.5);
this->declare_parameter("processing_latency", 0.1); // seconds
this->declare_parameter("camera_hfov", 1.0472); // 60 degrees
this->declare_parameter("camera_height", 0.3); // meters above ground
this->declare_parameter("camera_pitch", 0.0); // radians
this->declare_parameter("max_detection_range", 3.0); // meters
this->declare_parameter("publish_rate", 10.0);
confidence_threshold_ = this->get_parameter("confidence_threshold").as_double();
processing_latency_ = this->get_parameter("processing_latency").as_double();
camera_hfov_ = this->get_parameter("camera_hfov").as_double();
camera_height_ = this->get_parameter("camera_height").as_double();
camera_pitch_ = this->get_parameter("camera_pitch").as_double();
max_detection_range_ = this->get_parameter("max_detection_range").as_double();
// Subscribers
dnn_sub_ = this->create_subscription<std_msgs::msg::Float32MultiArray>(
"hobot_dnn_detection", 10,
std::bind(&ObstacleDetectorNode::dnnCallback, this, std::placeholders::_1));
odom_sub_ = this->create_subscription<nav_msgs::msg::Odometry>(
"/odom", 10,
std::bind(&ObstacleDetectorNode::odomCallback, this, std::placeholders::_1));
// Publishers
obstacles_pub_ = this->create_publisher<past_control::msg::ObstacleArray>(
"/obstacles", 10);
marker_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
"/obstacle_markers", 10);
double rate = this->get_parameter("publish_rate").as_double();
int period_ms = static_cast<int>(1000.0 / rate);
timer_ = this->create_wall_timer(
std::chrono::milliseconds(period_ms),
std::bind(&ObstacleDetectorNode::publishLoop, this));
RCLCPP_INFO(this->get_logger(), "ObstacleDetectorNode started");
}
private:
void dnnCallback(const std_msgs::msg::Float32MultiArray::SharedPtr msg)
{
std::lock_guard<std::mutex> lock(mutex_);
// Parse DNN detections
// Expected format: interleaved [class_id, x_center, y_center, width, height, conf]
// Normalized coordinates (0-1) within image frame
const int fields_per_detection = 6;
int num_detections = msg->data.size() / fields_per_detection;
latest_detections_.clear();
for (int i = 0; i < num_detections; ++i) {
int base = i * fields_per_detection;
Detection det;
det.class_id = static_cast<int>(msg->data[base + 0]);
det.x_center = msg->data[base + 1]; // normalized [0,1]
det.y_center = msg->data[base + 2]; // normalized [0,1]
det.width = msg->data[base + 3]; // normalized
det.height = msg->data[base + 4]; // normalized
det.confidence = msg->data[base + 5];
if (det.confidence >= confidence_threshold_) {
latest_detections_.push_back(det);
}
}
has_detections_ = true;
}
void odomCallback(const nav_msgs::msg::Odometry::SharedPtr msg)
{
std::lock_guard<std::mutex> lock(mutex_);
cur_x_ = msg->pose.pose.position.x;
cur_y_ = msg->pose.pose.position.y;
// Extract yaw from quaternion
double qx = msg->pose.pose.orientation.x;
double qy = msg->pose.pose.orientation.y;
double qz = msg->pose.pose.orientation.z;
double qw = msg->pose.pose.orientation.w;
cur_yaw_ = std::atan2(2.0*(qw*qz + qx*qy), 1.0 - 2.0*(qy*qy + qz*qz));
has_odom_ = true;
}
void publishLoop()
{
std::lock_guard<std::mutex> lock(mutex_);
past_control::msg::ObstacleArray obs_array;
visualization_msgs::msg::MarkerArray marker_array;
if (!has_odom_ || !has_detections_) {
// Publish empty
obstacles_pub_->publish(obs_array);
marker_pub_->publish(marker_array);
return;
}
int marker_id = 0;
for (const auto& det : latest_detections_) {
// Convert pixel coords to world coords using simple pinhole model
// x_center normalized [0,1] → angle offset from camera optical axis
double pixel_offset = det.x_center - 0.5; // [-0.5, 0.5]
double angle_offset = pixel_offset * camera_hfov_; // radians
double obstacle_angle = cur_yaw_ + angle_offset;
// Estimate distance from bounding box height
// Larger bbox = closer object (simple inverse relationship)
double est_distance = (1.0 - det.height) * max_detection_range_ + 0.5;
// World coordinates of obstacle
double obs_x = cur_x_ + est_distance * std::cos(obstacle_angle);
double obs_y = cur_y_ + est_distance * std::sin(obstacle_angle);
double obs_radius = 0.15; // default obstacle radius
// Add to obstacle array
past_control::msg::Obstacle obs;
obs.header.stamp = this->now();
obs.header.frame_id = "odom";
obs.x = obs_x;
obs.y = obs_y;
obs.radius = obs_radius;
obs.type = "circle";
obs_array.obstacles.push_back(obs);
// Add marker
visualization_msgs::msg::Marker marker;
marker.header.stamp = this->now();
marker.header.frame_id = "odom";
marker.ns = "obstacles";
marker.id = marker_id++;
marker.type = visualization_msgs::msg::Marker::CYLINDER;
marker.action = visualization_msgs::msg::Marker::ADD;
marker.pose.position.x = obs_x;
marker.pose.position.y = obs_y;
marker.pose.position.z = 0.0;
marker.pose.orientation.w = 1.0;
marker.scale.x = obs_radius * 2;
marker.scale.y = obs_radius * 2;
marker.scale.z = 0.3;
marker.color.r = 1.0f;
marker.color.g = 0.0f;
marker.color.b = 0.0f;
marker.color.a = 0.8f;
marker.lifetime = rclcpp::Duration::from_seconds(0.5);
marker_array.markers.push_back(marker);
}
obstacles_pub_->publish(obs_array);
marker_pub_->publish(marker_array);
}
struct Detection
{
int class_id;
double x_center, y_center;
double width, height;
double confidence;
};
// Subscribers
rclcpp::Subscription<std_msgs::msg::Float32MultiArray>::SharedPtr dnn_sub_;
rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odom_sub_;
// Publishers
rclcpp::Publisher<past_control::msg::ObstacleArray>::SharedPtr obstacles_pub_;
rclcpp::Publisher<visualization_msgs::msg::MarkerArray>::SharedPtr marker_pub_;
// Timer
rclcpp::TimerBase::SharedPtr timer_;
// State
std::vector<Detection> latest_detections_;
double cur_x_, cur_y_, cur_yaw_;
bool has_odom_, has_detections_;
std::mutex mutex_;
// Parameters
double confidence_threshold_;
double processing_latency_;
double camera_hfov_;
double camera_height_;
double camera_pitch_;
double max_detection_range_;
};
int main(int argc, char* argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<ObstacleDetectorNode>());
rclcpp::shutdown();
return 0;
}

View File

@@ -0,0 +1,448 @@
#include <chrono>
#include <memory>
#include <mutex>
#include <cmath>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "nav_msgs/msg/path.hpp"
#include "nav_msgs/msg/odometry.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include "geometry_msgs/msg/pose_stamped.hpp"
#include "nav2_msgs/action/compute_path_to_pose.hpp"
#include "origincar_msg/msg/sign.hpp"
#include "tf2/LinearMath/Quaternion.h"
#include "tf2/LinearMath/Matrix3x3.h"
#include "past_control/tools.h"
using namespace std::chrono_literals;
// Task states
enum class TaskState
{
IDLE = 0,
GOING = 1,
RETURN = 2,
QR_SCAN = 3,
RESET = 4
};
class RacingOrchestrator : public rclcpp::Node
{
public:
using ComputePathToPose = nav2_msgs::action::ComputePathToPose;
using GoalHandleComputePathToPose = rclcpp_action::ClientGoalHandle<ComputePathToPose>;
RacingOrchestrator()
: Node("racing_orchestrator"),
state_(TaskState::IDLE),
has_odom_(false), has_goal_(false),
has_path_(false), got_lane_cmd_(false),
is_back_(false), activate_avoid_(false),
waypoint_index_(0), guide_step_(5),
follow_linear_speed_(0.4),
sub_target_(true),
cmd_vel_linear_x_(0.0), cmd_vel_angular_z_(0.0),
cur_x_(0.0), cur_y_(0.0), cur_yaw_(0.0)
{
// Parameters
// PID path following
this->declare_parameter("follow_linear_speed", 0.4);
this->declare_parameter("guide_step", 5);
this->declare_parameter("arrive_square", 0.25);
this->declare_parameter("angular_kp", 1.5);
this->declare_parameter("angular_ki", 0.0);
this->declare_parameter("angular_kd", 0.1);
this->declare_parameter("angular_integral_max", 1.0);
this->declare_parameter("angular_output_max", 2.0);
this->declare_parameter("angular_max_err", 3.14);
// Obstacle avoidance
this->declare_parameter("avoid_linear_speed", 0.2);
this->declare_parameter("avoid_angular_z", 0.8);
// QR scan params
this->declare_parameter("qr_scan_speed", 0.15);
this->declare_parameter("qr_scan_duration", 3.0);
// Control rate
this->declare_parameter("control_rate", 20.0);
// Load params
follow_linear_speed_ = this->get_parameter("follow_linear_speed").as_double();
guide_step_ = this->get_parameter("guide_step").as_int();
arrive_square_ = this->get_parameter("arrive_square").as_double();
avoid_linear_speed_ = this->get_parameter("avoid_linear_speed").as_double();
avoid_angular_z_ = this->get_parameter("avoid_angular_z").as_double();
qr_scan_speed_ = this->get_parameter("qr_scan_speed").as_double();
qr_scan_duration_ = this->get_parameter("qr_scan_duration").as_double();
// Init PID
double kp = this->get_parameter("angular_kp").as_double();
double ki = this->get_parameter("angular_ki").as_double();
double kd = this->get_parameter("angular_kd").as_double();
double i_max = this->get_parameter("angular_integral_max").as_double();
double o_max = this->get_parameter("angular_output_max").as_double();
double max_err = this->get_parameter("angular_max_err").as_double();
angular_pid_.init(kp, ki, kd, i_max, o_max, max_err);
// Subscribers
sign_sub_ = this->create_subscription<origincar_msg::msg::Sign>(
"sign4return", 10,
std::bind(&RacingOrchestrator::signCallback, this, std::placeholders::_1));
sign_foxglove_sub_ = this->create_subscription<origincar_msg::msg::Sign>(
"sign_foxglove", 10,
std::bind(&RacingOrchestrator::signFoxgloveCallback, this, std::placeholders::_1));
path_sub_ = this->create_subscription<nav_msgs::msg::Path>(
"/plan", 10,
std::bind(&RacingOrchestrator::pathCallback, this, std::placeholders::_1));
goal_sub_ = this->create_subscription<geometry_msgs::msg::PoseStamped>(
"/goal_pose", 10,
std::bind(&RacingOrchestrator::goalCallback, this, std::placeholders::_1));
odom_sub_ = this->create_subscription<nav_msgs::msg::Odometry>(
"/odom", 10,
std::bind(&RacingOrchestrator::odomCallback, this, std::placeholders::_1));
lane_cmd_sub_ = this->create_subscription<geometry_msgs::msg::Twist>(
"/lane_cmd_vel", 10,
std::bind(&RacingOrchestrator::laneCmdCallback, this, std::placeholders::_1));
// Publishers
cmd_vel_pub_ = this->create_publisher<geometry_msgs::msg::Twist>("/cmd_vel", 10);
// ComputePathToPose action client (triggers Nav2 planner_server)
planner_client_ = rclcpp_action::create_client<ComputePathToPose>(
this, "compute_path_to_pose");
// Control loop
double rate = this->get_parameter("control_rate").as_double();
int period_ms = static_cast<int>(1000.0 / rate);
timer_ = this->create_wall_timer(
std::chrono::milliseconds(period_ms),
std::bind(&RacingOrchestrator::controlLoop, this));
RCLCPP_INFO(this->get_logger(), "RacingOrchestrator started. State: IDLE");
}
private:
// ─────────────────────────────────────────────
// A. Task State Machine — sign callbacks
// ─────────────────────────────────────────────
void signCallback(const origincar_msg::msg::Sign::SharedPtr msg)
{
processSign(msg->sign_data);
}
void signFoxgloveCallback(const origincar_msg::msg::Sign::SharedPtr msg)
{
processSign(msg->sign_data);
}
void processSign(int sign_data)
{
RCLCPP_INFO(this->get_logger(), "Received sign: %d", sign_data);
if (sign_data == -1) {
state_ = TaskState::RESET;
has_path_ = false;
global_path_ = nav_msgs::msg::Path();
waypoint_index_ = 0;
angular_pid_.reset();
RCLCPP_INFO(this->get_logger(), "State -> RESET");
}
else if (sign_data == 3 || sign_data == 4) {
state_ = TaskState::QR_SCAN;
qr_scan_start_ = this->now();
RCLCPP_INFO(this->get_logger(), "State -> QR_SCAN");
}
else if (sign_data == 5) {
state_ = TaskState::GOING;
is_back_ = false;
sub_target_ = true;
has_path_ = false;
waypoint_index_ = 0;
angular_pid_.reset();
// Trigger Nav2 replanning if we have a goal
if (has_goal_) {
requestPlan();
}
RCLCPP_INFO(this->get_logger(), "State -> GOING (forward)");
}
else if (sign_data == -2) {
state_ = TaskState::RETURN;
is_back_ = true;
has_path_ = false;
waypoint_index_ = 0;
angular_pid_.reset();
if (has_goal_) {
requestPlan();
}
RCLCPP_INFO(this->get_logger(), "State -> RETURN");
}
}
// ─────────────────────────────────────────────
// B. Callbacks for path, goal, odom, lane cmd_vel
// ─────────────────────────────────────────────
void pathCallback(const nav_msgs::msg::Path::SharedPtr msg)
{
std::lock_guard<std::mutex> lock(mutex_);
if (msg->poses.empty()) {
has_path_ = false;
return;
}
global_path_ = *msg;
has_path_ = true;
waypoint_index_ = 0;
angular_pid_.reset();
// Auto-transition IDLE -> GOING when receiving an external path
// (e.g. publish_sine_path.py publishes /plan without /goal_pose)
if (state_ == TaskState::IDLE || state_ == TaskState::RESET) {
state_ = TaskState::GOING;
is_back_ = false;
sub_target_ = true;
RCLCPP_INFO(this->get_logger(),
"Auto State -> GOING (external path, %zu waypoints)",
msg->poses.size());
}
}
void goalCallback(const geometry_msgs::msg::PoseStamped::SharedPtr msg)
{
{
std::lock_guard<std::mutex> lock(mutex_);
goal_pose_ = *msg;
has_goal_ = true;
RCLCPP_INFO(this->get_logger(), "New goal: (%.2f, %.2f)",
msg->pose.position.x, msg->pose.position.y);
// Auto-transition IDLE → GOING if not already active
if (state_ == TaskState::IDLE || state_ == TaskState::RESET) {
state_ = TaskState::GOING;
is_back_ = false;
sub_target_ = true;
has_path_ = false;
waypoint_index_ = 0;
angular_pid_.reset();
RCLCPP_INFO(this->get_logger(), "Auto State -> GOING (goal received)");
}
}
// Request plan outside mutex to avoid blocking other callbacks
requestPlan();
}
void odomCallback(const nav_msgs::msg::Odometry::SharedPtr msg)
{
std::lock_guard<std::mutex> lock(mutex_);
cur_x_ = msg->pose.pose.position.x;
cur_y_ = msg->pose.pose.position.y;
double qx = msg->pose.pose.orientation.x;
double qy = msg->pose.pose.orientation.y;
double qz = msg->pose.pose.orientation.z;
double qw = msg->pose.pose.orientation.w;
cur_yaw_ = std::atan2(2.0 * (qw * qz + qx * qy),
1.0 - 2.0 * (qy * qy + qz * qz));
has_odom_ = true;
}
void laneCmdCallback(const geometry_msgs::msg::Twist::SharedPtr msg)
{
std::lock_guard<std::mutex> lock(mutex_);
lane_cmd_vel_ = *msg;
got_lane_cmd_ = true;
}
// ─────────────────────────────────────────────
// C. ComputePathToPose action client
// ─────────────────────────────────────────────
void requestPlan()
{
if (!planner_client_->wait_for_action_server(std::chrono::seconds(2))) {
RCLCPP_WARN(this->get_logger(),
"Planner action server (compute_path_to_pose) not available");
return;
}
auto goal_msg = ComputePathToPose::Goal();
goal_msg.goal = goal_pose_;
goal_msg.planner_id = "GridBased";
auto send_goal_options = rclcpp_action::Client<ComputePathToPose>::SendGoalOptions();
send_goal_options.result_callback =
[this](const GoalHandleComputePathToPose::WrappedResult& result) {
if (result.code == rclcpp_action::ResultCode::SUCCEEDED) {
RCLCPP_INFO(this->get_logger(), "Plan received: %zu waypoints",
result.result->path.poses.size());
} else {
RCLCPP_WARN(this->get_logger(), "Plan request failed");
}
};
RCLCPP_INFO(this->get_logger(), "Requesting plan from Nav2 planner_server...");
planner_client_->async_send_goal(goal_msg, send_goal_options);
}
// ─────────────────────────────────────────────
// D. Main Control Loop — cmd_vel arbitration
// ─────────────────────────────────────────────
void controlLoop()
{
std::lock_guard<std::mutex> lock(mutex_);
// Priority 1: Task state control
switch (state_)
{
case TaskState::IDLE:
publishStop();
return;
case TaskState::RESET:
publishStop();
state_ = TaskState::IDLE;
return;
case TaskState::QR_SCAN:
{
auto elapsed = this->now() - qr_scan_start_;
if (elapsed.seconds() < 1.0) {
geometry_msgs::msg::Twist cmd;
cmd.linear.x = qr_scan_speed_;
cmd.angular.z = 0.0;
cmd_vel_pub_->publish(cmd);
}
else if (elapsed.seconds() < qr_scan_duration_) {
publishStop();
}
else {
state_ = TaskState::GOING;
RCLCPP_INFO(this->get_logger(), "QR scan complete, resuming");
}
return;
}
case TaskState::GOING:
case TaskState::RETURN:
break;
}
// Priority 2: Obstacle avoidance
if (activate_avoid_) {
geometry_msgs::msg::Twist cmd;
cmd.linear.x = avoid_linear_speed_;
cmd.angular.z = avoid_angular_z_;
cmd_vel_pub_->publish(cmd);
return;
}
// Priority 3: Lane following (sub_target_ == false)
if (!sub_target_ && got_lane_cmd_) {
cmd_vel_pub_->publish(lane_cmd_vel_);
return;
}
// Priority 4: PID path following (path from Nav2 planner_server via /plan)
if (sub_target_ && has_path_ && has_odom_) {
if (waypoint_index_ >= static_cast<int>(global_path_.poses.size())) {
RCLCPP_INFO(this->get_logger(), "Path complete - stopping");
publishStop();
has_path_ = false;
return;
}
int target_idx = std::min(waypoint_index_ + guide_step_,
static_cast<int>(global_path_.poses.size()) - 1);
const auto& target = global_path_.poses[target_idx];
double dx = target.pose.position.x - cur_x_;
double dy = target.pose.position.y - cur_y_;
double distance_sq = dx * dx + dy * dy;
if (distance_sq < arrive_square_) {
waypoint_index_++;
angular_pid_.reset();
}
double target_yaw = std::atan2(dy, dx);
double heading_error = target_yaw - cur_yaw_;
while (heading_error > M_PI) heading_error -= 2.0 * M_PI;
while (heading_error < -M_PI) heading_error += 2.0 * M_PI;
double dt = 1.0 / this->get_parameter("control_rate").as_double();
double angular_z = angular_pid_.update(heading_error, dt);
geometry_msgs::msg::Twist cmd;
cmd.linear.x = follow_linear_speed_;
cmd.angular.z = angular_z;
cmd_vel_pub_->publish(cmd);
return;
}
// Priority 5: No path - stop and wait
publishStop();
}
void publishStop()
{
geometry_msgs::msg::Twist cmd;
cmd.linear.x = 0.0;
cmd.angular.z = 0.0;
cmd_vel_pub_->publish(cmd);
}
// ─────────────────────────────────────────────
// Members (order must match initializer list)
// ─────────────────────────────────────────────
TaskState state_;
bool has_odom_, has_goal_;
bool has_path_, got_lane_cmd_;
bool is_back_, activate_avoid_;
int waypoint_index_;
int guide_step_;
double follow_linear_speed_;
bool sub_target_;
double cmd_vel_linear_x_, cmd_vel_angular_z_;
double cur_x_, cur_y_, cur_yaw_;
double arrive_square_;
past_control::PID angular_pid_;
double avoid_linear_speed_;
double avoid_angular_z_;
double qr_scan_speed_;
double qr_scan_duration_;
rclcpp::Time qr_scan_start_;
nav_msgs::msg::Path global_path_;
geometry_msgs::msg::Twist lane_cmd_vel_;
geometry_msgs::msg::PoseStamped goal_pose_;
rclcpp::Subscription<origincar_msg::msg::Sign>::SharedPtr sign_sub_;
rclcpp::Subscription<origincar_msg::msg::Sign>::SharedPtr sign_foxglove_sub_;
rclcpp::Subscription<nav_msgs::msg::Path>::SharedPtr path_sub_;
rclcpp::Subscription<geometry_msgs::msg::PoseStamped>::SharedPtr goal_sub_;
rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odom_sub_;
rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr lane_cmd_sub_;
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr cmd_vel_pub_;
rclcpp_action::Client<ComputePathToPose>::SharedPtr planner_client_;
rclcpp::TimerBase::SharedPtr timer_;
std::mutex mutex_;
};
int main(int argc, char* argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<RacingOrchestrator>());
rclcpp::shutdown();
return 0;
}

View File

@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.5)
project(planner)
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra)
endif()
find_package(ament_cmake REQUIRED)
# Dummy executable to satisfy ament_cmake + install of launch/config
add_executable(planner_version src/planner_version.cpp)
install(TARGETS planner_version
DESTINATION lib/${PROJECT_NAME}
)
install(
DIRECTORY launch config
DESTINATION share/${PROJECT_NAME}
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
set(ament_cmake_copyright_FOUND TRUE)
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()

View File

@@ -0,0 +1,232 @@
# ============================================================================
# planner.yaml — Nav2 minimal global planning stack
# 节点: slam_toolbox + planner_server + global_costmap + local_costmap
# 来源: gc_navigation2_slamtoolbox (slam + nav params) 剪裁
# ============================================================================
# ---------------------------------------------------------------------------
# slam_toolbox — online_async 实时建图 → 发布 /map + map→odom transform
# 来源: slam_toolbox_mapping.yaml
# ---------------------------------------------------------------------------
slam_toolbox:
ros__parameters:
use_sim_time: False
# Solver
solver_plugin: solver_plugins::CeresSolver
ceres_linear_solver: SPARSE_NORMAL_CHOLESKY
ceres_preconditioner: SCHUR_JACOBI
ceres_trust_strategy: LEVENBERG_MARQUARDT
ceres_dogleg_type: TRADITIONAL_DOGLEG
ceres_loss_function: None
# ROS base
odom_frame: odom
map_frame: map
base_frame: base_link
scan_topic: /scan
mode: mapping
use_map_saver: true
# Debug & performance
debug_logging: false
throttle_scans: 1
transform_publish_period: 0.02
map_update_interval: 3.0
resolution: 0.05
min_laser_range: 0.15
max_laser_range: 20.0
minimum_time_interval: 0.5
transform_timeout: 0.2
tf_buffer_duration: 30.0
stack_size_to_use: 40000000
enable_interactive_mode: true
# Mapping
use_scan_matching: true
use_scan_barycenter: true
minimum_travel_distance: 0.5
minimum_travel_heading: 0.1
scan_buffer_size: 10
scan_buffer_maximum_scan_distance: 10.0
link_match_minimum_response_fine: 0.1
link_scan_maximum_distance: 1.5
# Loop closure
do_loop_closing: true
loop_match_minimum_chain_size: 10
loop_match_maximum_variance_coarse: 3.0
loop_match_minimum_response_coarse: 0.35
loop_match_minimum_response_fine: 0.45
loop_search_maximum_distance: 3.0
# Scan matching
correlation_search_space_dimension: 0.5
correlation_search_space_resolution: 0.01
correlation_search_space_smear_deviation: 0.1
loop_search_space_dimension: 8.0
loop_search_space_resolution: 0.05
loop_search_space_smear_deviation: 0.03
# Matcher params
distance_variance_penalty: 0.5
angle_variance_penalty: 1.0
fine_search_angle_offset: 0.00349
coarse_search_angle_offset: 0.349
coarse_angle_resolution: 0.0349
minimum_angle_penalty: 0.9
minimum_distance_penalty: 0.5
use_response_expansion: true
min_pass_through: 2
occupancy_threshold: 0.1
scan_queue_size: 20
# ---------------------------------------------------------------------------
# planner_server — SmacPlannerHybrid 全局路径规划 → /plan
# 来源: gc_navigation_slam.yaml planner_server section
# ---------------------------------------------------------------------------
planner_server:
ros__parameters:
planner_plugins:
- GridBased
use_sim_time: False
GridBased:
plugin: nav2_smac_planner/SmacPlannerHybrid
downsample_costmap: False
downsampling_factor: 1
tolerance: 0.25
allow_unknown: True
max_iterations: 100000
max_on_approach_iterations: 1000
max_planning_time: 5.0
motion_model_for_search: REEDS_SHEPP
angle_quantization_bins: 72
analytic_expansion_ratio: 2.0
analytic_expansion_max_length: 3.0
minimum_turning_radius: 0.35
reverse_penalty: 1.3
change_penalty: 0.0
non_straight_penalty: 0.0
cost_penalty: 5.0
retrospective_penalty: 0.015
lookup_table_size: 5.0
cache_obstacle_heuristic: False
viz_expansions: False
smooth_path: True
smoother:
max_iterations: 1000
w_smooth: 0.4
w_data: 0.2
tolerance: 1.0e-10
do_refinement: True
refinement_num: 4
planner_server_rclcpp_node:
ros__parameters:
use_sim_time: False
# ---------------------------------------------------------------------------
# global_costmap — 全局代价地图(订阅 /scan + /map
# 来源: gc_navigation_slam.yaml global_costmap section
# ---------------------------------------------------------------------------
global_costmap:
global_costmap:
ros__parameters:
use_sim_time: False
transform_tolerance: 2.0
update_frequency: 1.0
publish_frequency: 1.0
global_frame: map
robot_base_frame: base_link
footprint: '[[0.14, 0.085], [0.14, -0.085], [-0.14, -0.085], [-0.14, 0.085]]'
footprint_padding: 0.02
resolution: 0.05
track_unknown_space: True
plugins:
- static_layer
- obstacle_layer
- inflation_layer
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: 3.0
raytrace_min_range: 0.0
obstacle_max_range: 2.5
obstacle_min_range: 0.0
static_layer:
plugin: nav2_costmap_2d::StaticLayer
map_subscribe_transient_local: True
inflation_layer:
plugin: nav2_costmap_2d::InflationLayer
cost_scaling_factor: 3.0
inflation_radius: 0.2
always_send_full_costmap: True
global_costmap_client:
ros__parameters:
use_sim_time: False
global_costmap_rclcpp_node:
ros__parameters:
use_sim_time: False
# ---------------------------------------------------------------------------
# local_costmap — 局部代价地图obstacle_detector 可注入)
# 来源: gc_navigation_slam.yaml local_costmap section
# ---------------------------------------------------------------------------
local_costmap:
local_costmap:
ros__parameters:
update_frequency: 1.0
publish_frequency: 2.0
transform_tolerance: 2.0
global_frame: odom
robot_base_frame: base_link
use_sim_time: False
rolling_window: True
width: 3
height: 3
resolution: 0.05
footprint: '[[0.14, 0.085], [0.14, -0.085], [-0.14, -0.085], [-0.14, 0.085]]'
footprint_padding: 0.02
plugins:
- voxel_layer
- inflation_layer
inflation_layer:
plugin: nav2_costmap_2d::InflationLayer
cost_scaling_factor: 3.0
inflation_radius: 0.2
voxel_layer:
plugin: nav2_costmap_2d::VoxelLayer
enabled: True
publish_voxel_map: True
origin_z: 0.0
z_resolution: 0.05
z_voxels: 16
max_obstacle_height: 2.0
mark_threshold: 0
observation_sources: scan
scan:
topic: /scan
max_obstacle_height: 2.0
clearing: True
marking: True
data_type: LaserScan
raytrace_max_range: 3.0
raytrace_min_range: 0.0
obstacle_max_range: 2.5
obstacle_min_range: 0.0
static_layer:
map_subscribe_transient_local: True
always_send_full_costmap: True
local_costmap_client:
ros__parameters:
use_sim_time: False
local_costmap_rclcpp_node:
ros__parameters:
use_sim_time: False

View File

@@ -0,0 +1,77 @@
# ============================================================================
# planner.launch.py
# Nav2 最小全局规划模块
# 启动: slam_toolbox + planner_server + lifecycle_manager
# costmap 由 planner_server 内部实例化,不需要单独启动
# 不启动: controller / behavior / bt_navigator / velocity_smoother / smoother
#
# 参考: gc_nav2_with_slam_online_real.launch.py
# ============================================================================
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
"""启动 Nav2 最小规划栈slam + planner"""
ld = LaunchDescription()
# === 包路径 ===
pkg_dir = get_package_share_directory('planner')
# === 参数 ===
use_sim_time = LaunchConfiguration('use_sim_time', default='false')
params_file = LaunchConfiguration(
'params_file',
default=os.path.join(pkg_dir, 'config', 'planner.yaml'))
declare_params = DeclareLaunchArgument(
'params_file',
default_value=os.path.join(pkg_dir, 'config', 'planner.yaml'),
description='Path to the planner parameters file')
ld.add_action(declare_params)
# =====================================================================
# 1. slam_toolbox — 异步建图 (online_async),发布 /map + map→odom
# =====================================================================
slam_toolbox_node = Node(
package='slam_toolbox',
executable='async_slam_toolbox_node',
name='slam_toolbox',
output='screen',
parameters=[params_file,
{'use_sim_time': use_sim_time}],
)
ld.add_action(slam_toolbox_node)
# =====================================================================
# 2. planner_server — SmacPlannerHybridComputePathToPose action
# planner_server 内部实例化 global_costmap从 planner.yaml 读取参数)
# =====================================================================
planner_server_node = Node(
package='nav2_planner',
executable='planner_server',
name='planner_server',
output='screen',
parameters=[params_file],
)
ld.add_action(planner_server_node)
# =====================================================================
# 3. lifecycle_manager — 自动激活 planner_server (lifecycle 节点)
# =====================================================================
lifecycle_manager_node = Node(
package='nav2_lifecycle_manager',
executable='lifecycle_manager',
name='lifecycle_manager_planner',
output='screen',
parameters=[{'autostart': True},
{'node_names': ['planner_server']}],
)
ld.add_action(lifecycle_manager_node)
return ld

26
src/planner/package.xml Normal file
View File

@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>planner</name>
<version>0.0.0</version>
<description>Minimal Nav2 global planning stack: slam_toolbox + planner_server + costmaps + lifecycle_manager</description>
<maintainer email="2314753575@qq.com">sunrise</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>slam_toolbox</depend>
<depend>nav2_planner</depend>
<depend>nav2_costmap_2d</depend>
<depend>nav2_lifecycle_manager</depend>
<depend>nav2_common</depend>
<depend>nav2_util</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>

View File

@@ -0,0 +1,11 @@
#include <iostream>
int main(int argc, char* argv[])
{
std::cout << "planner package: Nav2 minimal global planning stack" << std::endl;
std::cout << " - slam_toolbox (online_async)" << std::endl;
std::cout << " - planner_server (SmacPlannerHybrid)" << std::endl;
std::cout << " - global_costmap + local_costmap" << std::endl;
std::cout << " - lifecycle_manager" << std::endl;
return 0;
}

View File

@@ -22,11 +22,13 @@ public:
std::bind(&MinimalHbmemSubscriber::image_callback, this, std::placeholders::_1));
// 创建订阅器,订阅 'sign4return' 话题
subscription_sign_ =
this->create_subscription<std_msgs::msg::Int32>(
"sign4return",
10,
std::bind(&MinimalHbmemSubscriber::sign_callback, this, std::placeholders::_1));
// subscription_sign_ =
// this->create_subscription<std_msgs::msg::Int32>(
// "sign4return",
// 10,
// std::bind(&MinimalHbmemSubscriber::sign_callback, this, std::placeholders::_1));
// 创建发布器,发布 'sign4return' 话题
sign_publisher_ = this->create_publisher<std_msgs::msg::Int32>("sign4return", 10);
// 创建 publishertopic 为 "qr_results"
publisher_ =
@@ -87,6 +89,23 @@ private:
symbol->get_type_name().c_str(), symbol->get_data().c_str());
}
qr_results_buff_ = qr_results; // 更新缓存
// 检测字符合法性
// if (ResultLegal(qr_results)){
// // 发布sign4return话题
// auto message = std_msgs::msg::Int32();
// message.data = 5; // 5表示检测到二维码
// sign_publisher_->publish(message);
// RCLCPP_INFO(this->get_logger(), "QR Results Legal");
// }
// else{
// RCLCPP_INFO(this->get_logger(), "\033[31m QR Results Illegal \033[00m");
// }
// 发布sign4return话题
auto message = std_msgs::msg::Int32();
message.data = 5; // 5表示检测到二维码
sign_publisher_->publish(message);
} else {
qr_results = "QR Code not detected";
RCLCPP_INFO(this->get_logger(), "QR Code not detected");
@@ -104,24 +123,36 @@ private:
}
// 消息回调函数,处理 sign4return 话题
void sign_callback(const std_msgs::msg::Int32::SharedPtr msg)
{
if (msg->data == 0)
{
detect_qr_code_ = true; // 启动二维码检测
RCLCPP_INFO(this->get_logger(), "QR detection started");
}
else if (msg->data == 5)
{
detect_qr_code_ = false; // 停止二维码检测
RCLCPP_INFO(this->get_logger(), "QR detection stopped");
}
// void sign_callback(const std_msgs::msg::Int32::SharedPtr msg)
// {
// if (msg->data == 0)
// {
// detect_qr_code_ = true; // 启动二维码检测
// RCLCPP_INFO(this->get_logger(), "QR detection started");
// }
// else if (msg->data == 5)
// {
// detect_qr_code_ = false; // 停止二维码检测
// RCLCPP_INFO(this->get_logger(), "QR detection stopped");
// }
// }
// 识别结果合法性检测
bool ResultLegal(std::string& input){
// 合法字符串集合
static const std::unordered_set<std::string> kValidStrings = {
"1", "2", "顺时针", "逆时针", "", ""
};
return kValidStrings.find(input) != kValidStrings.end();
}
// /aurora/rgb/image_raw 订阅器
rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr subscription_image_;
// sign4return 订阅器
rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr subscription_sign_;
// rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr subscription_sign_;
// sign4return 发布器
rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr sign_publisher_;
// QR code results 发布器
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;

View File

@@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.8)
project(racing_control)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()

View File

@@ -0,0 +1,35 @@
/*
功能描述:
1. 状态指令:
1 - 比赛开始,进行任务一寻找二维码
2 - 找到二维码,执行任务一到任务二过渡阶段导航
3 - 到达任务二起始阶段,执行顺/逆时针绕圈(可集成图生文)
4 - 减速拍照图像传入图生文节点随后回到3备用
5 - 完整走完一圈,执行任务三
2. 全部流程
启动小车->slamtoolbox开始建图并开始发布导航命令这时候小车还不能动->打开电机开关,小车开始行动->走到一半扫到二维码,发布在/qr_results上->停掉二维码节点sign=5和导航1同时开始导航2到任务二入口并顺逆时针转圈->到达指定位置触发一次vlm请求->开始语音播报同时完成任务二、三
*/
#include "rclcpp/rclcpp.hpp"
static int QR_SEARCHING = 1;
static int ENTRY = 2;
static int CIRCLE = 3;
static int VLM = 4;
static int TASK3 = 5;
class RacingControl : public rclcpp::Node
{
public:
RacingControl() : Node("racing_control")
{
// 初始化状态指令
state_command_ = QR_SEARCHING;
}
private:
// 状态指令变量
int state_command_;
};

View File

@@ -0,0 +1,18 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>racing_control</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="2314753575@qq.com">sunrise</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>

View File

@@ -21,6 +21,8 @@ setup(
entry_points={
'console_scripts': [
'vlm_node = vlm_detect.vlm_node:main',
'test_publisher = vlm_detect.test_publisher:main',
'tts_node = vlm_detect.tts_node:main',
],
},
)

View File

@@ -0,0 +1,88 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试发布者:发送图片和触发信号给 VLM 节点
用法: ros2 run vlm_detect test_publisher --ros-args -p image_path:="/path/to/image.jpg"
"""
import rclpy
from rclpy.node import Node
from std_msgs.msg import Int32, String
from sensor_msgs.msg import CompressedImage
import cv2
import time
class TestPublisher(Node):
def __init__(self):
super().__init__("test_publisher")
# 声明参数
self.declare_parameter(
"image_path", "/home/sunrise/yiliao_ws/my_model/image.png"
)
self.declare_parameter("interval", 5.0) # 每 N 秒触发一次
image_path = self.get_parameter("image_path").value
interval = self.get_parameter("interval").value
# 发布者
self.image_pub = self.create_publisher(CompressedImage, "/image_mjpeg", 10)
self.sign_pub = self.create_publisher(Int32, "/sign4return", 10)
# 订阅结果
self.result_sub = self.create_subscription(
String, "/vlm_result", self.result_callback, 10
)
# 加载图片
self.image_data = cv2.imread(image_path)
if self.image_data is None:
self.get_logger().error(f"无法读取图片: {image_path}")
raise FileNotFoundError(f"Image not found: {image_path}")
encode_params = [cv2.IMWRITE_JPEG_QUALITY, 90]
_, jpeg_data = cv2.imencode(".jpg", self.image_data, encode_params)
self.jpeg_bytes = jpeg_data.tobytes()
self.get_logger().info(f"已加载图片: {image_path}")
# 定时器
self.timer = self.create_timer(interval, self.timer_callback)
self.get_logger().info(f"{interval}s 发送一次图片和触发信号")
def timer_callback(self):
# 1. 发送压缩图像
img_msg = CompressedImage()
img_msg.header.stamp = self.get_clock().now().to_msg()
img_msg.format = "jpeg"
img_msg.data = self.jpeg_bytes
self.image_pub.publish(img_msg)
self.get_logger().info("已发送图片")
# 2. 等待一小段时间让订阅者收到图片
time.sleep(1.0)
# 3. 发送触发信号 (sign=9)
sign_msg = Int32()
sign_msg.data = 9
self.sign_pub.publish(sign_msg)
self.get_logger().info("已发送触发信号 (sign=9),等待 VLM 结果...")
def result_callback(self, msg):
self.get_logger().info(f"🔍 VLM 识别结果: {msg.data}")
def main(args=None):
rclpy.init(args=args)
node = TestPublisher()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env python3
import rclpy, subprocess, requests, os
from rclpy.node import Node
from std_msgs.msg import String
VLM_HOST = "http://192.168.10.173:8000"
# USB Audio Device (Card 1)
AUDIO_SINK = "alsa_output.usb-C-Media_Electronics_Inc._USB_Audio_Device-00.analog-stereo"
AUDIO_ENV = {**os.environ, "PULSE_SINK": AUDIO_SINK}
class TTSNode(Node):
def __init__(self):
super().__init__("tts_node")
self.sub = self.create_subscription(String, "/vlm_result", self.callback, 10)
self.get_logger().info("TTS 播报节点已启动 (USB Audio Device, edge-tts 自然语音)")
def callback(self, msg):
text = msg.data
self.get_logger().info(f"播报: {text}")
try:
resp = requests.post(f"{VLM_HOST}/v1/tts",
json={"text": text, "voice": "zh-CN-XiaoxiaoNeural"}, timeout=60)
mp3 = "/tmp/tts_out.mp3"
with open(mp3, "wb") as f:
f.write(resp.content)
subprocess.Popen(["ffplay", "-nodisp", "-autoexit", mp3],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
env=AUDIO_ENV)
except Exception as e:
self.get_logger().error(f"TTS 失败,降级 espeak: {e}")
subprocess.Popen(["espeak-ng", "-v", "zh", "-s", "150", text],
env=AUDIO_ENV)
def main(args=None):
rclpy.init(args=args)
node = TTSNode()
try: rclpy.spin(node)
except KeyboardInterrupt: pass
finally: node.destroy_node(); rclpy.shutdown()
if __name__ == "__main__":
main()

View File

@@ -15,14 +15,14 @@ import numpy as np
class VLMProcessor(Node):
def __init__(self):
super().__init__('vlm_dtetct')
super().__init__('vlm_detect')
# 初始化 OpenAI 客户端
self.client = OpenAI(
base_url="http://192.168.1.103:8000/v1",
api_key="EMPTY"
base_url="http://192.168.10.173:8000/v1", # 本地 API 地址
api_key="EMPTY", # 不需要真实 API key
)
# ROS2 组件
self.bridge = CvBridge()
self.latest_image = None
@@ -31,7 +31,7 @@ class VLMProcessor(Node):
# 订阅图像话题
self.image_sub = self.create_subscription(
CompressedImage,
'/image',
'/image_mjpeg',
self.image_callback,
10
)
@@ -57,18 +57,20 @@ class VLMProcessor(Node):
"""保存最新的图像"""
with self.image_lock:
try:
# bridge = CvBridge()
np_arr = np.frombuffer(msg.data, np.uint8)
# 使用 OpenCV 解码图像
cv_image = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
# cv_image =bridge.imgmsg_to_cv2(msg,desired_encoding='bgr8')
self.latest_image = cv_image
self.get_logger().debug("recive picture")
except Exception as e:
self.get_logger().error("err picture")
self.get_logger().error(f"err picture: {e}")
def sign_callback(self, msg):
"""处理触发信号"""
if msg.data == 9:
self.get_logger().info("收到触发信号 (6),开始处理图像...")
self.get_logger().info(f"收到触发信号 ({msg.data}),开始处理图像...")
# 检查是否有可用图像
with self.image_lock:
@@ -79,12 +81,12 @@ class VLMProcessor(Node):
# 保存临时图像文件
temp_path = "/tmp/vlm_temp_image.jpg"
cv2.imwrite(temp_path, self.latest_image)
self.get_logger().info("已保存临时图像}")
self.get_logger().info(f"已保存临时图像: {temp_path}")
# 处理图像
try:
description = self.process_image(temp_path)
self.get_logger().info(f"图像描述结果:")
self.get_logger().info(f"图像描述结果: {description}")
# 发布结果
result_msg = String()
@@ -94,7 +96,7 @@ class VLMProcessor(Node):
# 清理临时文件
os.remove(temp_path)
except Exception as e:
self.get_logger().error(f"处理图像时出错:{e}")
self.get_logger().error(f"处理图像时出错: {e}")
def process_image(self, image_path):
"""使用 VLM 模型处理图像"""
@@ -106,26 +108,26 @@ class VLMProcessor(Node):
start_time = time.time()
response = self.client.chat.completions.create(
model="/home/yyh/vllm_test/model/InternVL3-1B",
model="./OpenGVLab/InternVL3-1B/",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "描述图片中的动漫病人"},
{"type": "text", "text": "描述图片中有一个病人的特征字数控制在20字以内。"},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
}
}
},
},
]
}
],
max_tokens=100
max_tokens=100,
)
processing_time = time.time() - start_time
self.get_logger().info("VLM 处理耗时")
self.get_logger().info(f"VLM 处理耗时 {processing_time:.1f}s")
return response.choices[0].message.content