From e84d4f51cb8958c4b2d41cea1cdb41586e28f573 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Thu, 18 Dec 2025 19:19:44 +0800 Subject: [PATCH 01/26] =?UTF-8?q?=E6=8F=90=E4=BA=A4main.py=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/main.py | 368 +++++++++++++++++++++++++++++ 1 file changed, 368 insertions(+) create mode 100644 src/autonomous_driving_car/main.py diff --git a/src/autonomous_driving_car/main.py b/src/autonomous_driving_car/main.py new file mode 100644 index 0000000000..10a4076619 --- /dev/null +++ b/src/autonomous_driving_car/main.py @@ -0,0 +1,368 @@ +#!/usr/bin/env python +""" +CARLA Basic Vehicle Spawn and Spectator Setup (sd_1/__main__.py) +完全适配 CARLA 0.9.15 版本(无任何天气预设依赖) + +This script connects to a CARLA simulator instance, removes any +pre-existing vehicles with the role 'my_car', spawns a new +Tesla Model 3 at a default spawn point, and positions the +spectator camera behind the newly spawned vehicle. +新增功能:定时循环切换CARLA模拟器的天气(晴天、多云、雨天、雾天、日落等) + +The script keeps the simulation running until interrupted (Ctrl+C), +but the vehicle does not move. +""" + +# 导入CARLA模拟器的Python API +import carla +# 导入时间模块,用于延时和计时 +import time + + +def remove_previous_vehicle(world: carla.World) -> None: + """ + 查找并销毁所有角色名为'my_car'的车辆Actor,避免重复生成导致冲突 + + Args: + world (carla.World): CARLA模拟器的世界对象,用于获取当前所有Actor + """ + print("Searching for previous 'my_car' vehicles...") + # 过滤出所有车辆类型的Actor(vehicle.* 匹配所有车辆蓝图) + actors = world.get_actors().filter('vehicle.*') + # 记录成功销毁的车辆数量 + removed_count = 0 + + for actor in actors: + # 检查Actor的角色名是否为'my_car' + if actor.attributes.get('role_name') == 'my_car': + print(f" - Removing previous vehicle: {actor.type_id} (ID {actor.id})") + # 销毁Actor,返回布尔值表示是否成功 + if actor.destroy(): + removed_count += 1 + else: + print(f" - Failed to remove vehicle {actor.id}") + + print(f"Removed {removed_count} previous vehicles.") + + +def set_spectator_behind_vehicle(world: carla.World, vehicle: carla.Vehicle) -> None: + """ + 将旁观者相机(spectator)定位到指定车辆的后上方,实现跟随视角 + + Args: + world (carla.World): CARLA模拟器的世界对象 + vehicle (carla.Vehicle): 目标车辆Actor,用于获取车辆的位置和姿态 + """ + # 获取旁观者相机Actor(CARLA中全局唯一的 spectator) + spectator = world.get_spectator() + # 获取车辆的当前位姿(位置+旋转) + vehicle_transform = vehicle.get_transform() + # 获取车辆的前向向量,用于计算相机的相对偏移(保证相机始终在车辆后方) + forward_vector = vehicle_transform.get_forward_vector() + + # 计算相机偏移:向后15米,向上6米(基于车辆的前向向量,保证方向正确) + camera_offset = carla.Location( + x=-15 * forward_vector.x, + y=-15 * forward_vector.y, + z=6 + ) + # 构建旁观者相机的位姿: + # 位置 = 车辆位置 + 偏移量 + # 旋转 = 俯仰角-20°(向下看),偏航角与车辆一致,滚转角为0 + spectator_transform = carla.Transform( + vehicle_transform.location + camera_offset, + carla.Rotation( + pitch=-20, # 俯仰角,负数表示向下看 + yaw=vehicle_transform.rotation.yaw, # 偏航角与车辆一致 + roll=0 # 滚转角,保持水平 + ) + ) + + # 尝试设置相机位姿,增加异常处理提高鲁棒性 + try: + spectator.set_transform(spectator_transform) + print("Spectator camera positioned behind the vehicle.") + except Exception as e: + print(f"Error setting spectator transform: {e}") + + +def get_weather_presets() -> list: + """ + 纯手动定义天气参数列表(完全不依赖CARLA预设,适配0.9.15) + 每个天气通过手动设置WeatherParameters的所有关键参数实现,确保兼容性 + + Returns: + list: 元组列表,每个元组包含(天气名称,carla.WeatherParameters对象) + """ + # 1. 晴天中午(太阳高悬、无云、无雨、无雾) + clear_noon = carla.WeatherParameters( + sun_altitude_angle=75.0, # 太阳高度角(75°=中午,天顶为90°) + sun_azimuth_angle=90.0, # 太阳方位角 + cloudiness=0.0, # 云量(0=无云) + precipitation=0.0, # 降水量(0=无雨) + precipitation_deposits=0.0, # 降水沉积(路面雨水) + wind_intensity=5.0, # 风力 + fog_density=0.0, # 雾密度(0=无雾) + fog_distance=0.0, # 雾的可见距离 + fog_falloff=1.0, # 雾的衰减率 + wetness=0.0, # 路面湿度 + scattering_intensity=0.0, # 光的散射强度 + mie_scattering_scale=0.0, # 米氏散射比例 + rayleigh_scattering_scale=0.0 # 瑞利散射比例 + ) + + # 2. 多云中午(高云量,阳光散射) + cloudy_noon = carla.WeatherParameters( + sun_altitude_angle=75.0, + sun_azimuth_angle=90.0, + cloudiness=80.0, # 云量80% + precipitation=0.0, + precipitation_deposits=0.0, + wind_intensity=10.0, + fog_density=0.0, + fog_distance=0.0, + fog_falloff=1.0, + wetness=0.0, + scattering_intensity=0.1, + mie_scattering_scale=0.1, + rayleigh_scattering_scale=0.1 + ) + + # 3. 小雨中午(少量降雨、路面微湿) + light_rain_noon = carla.WeatherParameters( + sun_altitude_angle=75.0, + sun_azimuth_angle=90.0, + cloudiness=90.0, # 云量90% + precipitation=20.0, # 降水量20%(小雨) + precipitation_deposits=5.0, # 路面雨水沉积5% + wind_intensity=15.0, + fog_density=5.0, # 轻微雾霭 + fog_distance=50.0, + fog_falloff=0.8, + wetness=0.2, # 路面湿度20% + scattering_intensity=0.2, + mie_scattering_scale=0.2, + rayleigh_scattering_scale=0.2 + ) + + # 4. 中雨中午(中等降雨、路面湿滑) + mid_rain_noon = carla.WeatherParameters( + sun_altitude_angle=75.0, + sun_azimuth_angle=90.0, + cloudiness=100.0, # 满云 + precipitation=50.0, # 降水量50%(中雨) + precipitation_deposits=20.0, # 路面雨水沉积20% + wind_intensity=20.0, + fog_density=15.0, # 雾密度15% + fog_distance=30.0, + fog_falloff=0.6, + wetness=0.5, # 路面湿度50% + scattering_intensity=0.3, + mie_scattering_scale=0.3, + rayleigh_scattering_scale=0.3 + ) + + # 5. 雾天中午(大雾、能见度低) + mist_noon = carla.WeatherParameters( + sun_altitude_angle=75.0, + sun_azimuth_angle=90.0, + cloudiness=50.0, + precipitation=0.0, + precipitation_deposits=0.0, + wind_intensity=5.0, + fog_density=30.0, # 雾密度30%(大雾) + fog_distance=10.0, # 雾的可见距离10米 + fog_falloff=0.5, + wetness=0.1, + scattering_intensity=0.4, + mie_scattering_scale=0.4, + rayleigh_scattering_scale=0.4 + ) + + # 6. 晴天日落(太阳低垂、暖色调、无云) + clear_sunset = carla.WeatherParameters( + sun_altitude_angle=15.0, # 太阳高度角15°(日落,地平线为0°) + sun_azimuth_angle=180.0, # 太阳方位角180°(西方) + cloudiness=0.0, + precipitation=0.0, + precipitation_deposits=0.0, + wind_intensity=5.0, + fog_density=0.0, + fog_distance=0.0, + fog_falloff=1.0, + wetness=0.0, + scattering_intensity=0.1, + mie_scattering_scale=0.1, + rayleigh_scattering_scale=0.1 + ) + + # 7. 潮湿路面(无雨但路面湿滑、轻微雾) + wet_road = carla.WeatherParameters( + sun_altitude_angle=75.0, + sun_azimuth_angle=90.0, + cloudiness=30.0, + precipitation=0.0, + precipitation_deposits=0.0, + wind_intensity=10.0, + fog_density=5.0, + fog_distance=40.0, + fog_falloff=0.9, + wetness=0.8, # 路面湿度80%(湿滑) + scattering_intensity=0.1, + mie_scattering_scale=0.1, + rayleigh_scattering_scale=0.1 + ) + + # 组合天气预设列表 + weather_presets = [ + ("Clear Noon", clear_noon), + ("Cloudy Noon", cloudy_noon), + ("Light Rain Noon", light_rain_noon), + ("Mid Rain Noon", mid_rain_noon), + ("Mist Noon", mist_noon), + ("Clear Sunset", clear_sunset), + ("Wet Road Noon", wet_road) + ] + + return weather_presets + + +def switch_weather(world: carla.World, weather: carla.WeatherParameters, weather_name: str) -> None: + """ + 设置CARLA世界的天气,并打印切换信息 + + Args: + world (carla.World): CARLA模拟器的世界对象 + weather (carla.WeatherParameters): 目标天气参数对象 + weather_name (str): 天气名称,用于打印日志 + """ + try: + # 设置世界天气 + world.set_weather(weather) + print(f"\n=== Switched to weather: {weather_name} ===") + except Exception as e: + print(f"Error switching weather to {weather_name}: {e}") + + +def main() -> None: + """ + 主执行函数: + 1. 连接CARLA服务器 + 2. 清理旧车辆 + 3. 生成特斯拉Model3车辆 + 4. 设置旁观者相机 + 5. 定时循环切换天气 + 6. 保持仿真运行直到用户中断 + """ + # 初始化变量,避免finally块中引用未定义的变量 + client: carla.Client = None + world: carla.World = None + vehicle: carla.Vehicle = None + + # 天气相关变量初始化 + weather_presets = get_weather_presets() # 获取天气预设列表 + current_weather_index = 0 # 当前天气的索引 + weather_switch_interval = 10 # 天气切换间隔(秒) + last_weather_switch_time = time.time() # 上一次天气切换的时间戳 + + try: + # 连接到本地CARLA服务器(地址:localhost,端口:2000) + client = carla.Client('localhost', 2000) + # 设置连接超时时间(10秒),避免无限等待 + client.set_timeout(10.0) + print("Connecting to CARLA server...") + + # 获取当前CARLA世界对象(包含地图、Actor、天气等信息) + world = client.get_world() + # 打印当前加载的地图名称 + print(f"Connected to world: {world.get_map().name}") + + # 清理之前运行残留的'my_car'车辆 + remove_previous_vehicle(world) + + # 获取地图的所有预设生成点(用于车辆/行人的生成) + spawn_points = world.get_map().get_spawn_points() + if not spawn_points: + print("Error: No spawn points found on the map!") + return + # 选择第一个生成点作为车辆生成位置 + spawn_point = spawn_points[0] + + # 获取蓝图库(包含所有可生成的Actor蓝图:车辆、行人、传感器等) + vehicle_bp_library = world.get_blueprint_library() + # 过滤出特斯拉Model3的蓝图(vehicle.tesla.model3 是CARLA中该车辆的唯一标识) + vehicle_bp = vehicle_bp_library.filter('vehicle.tesla.model3')[0] + # 设置车辆的角色名,方便后续清理 + vehicle_bp.set_attribute('role_name', 'my_car') + + # 尝试生成车辆(try_spawn_actor会检查生成点是否被占用,返回None表示失败) + print("Attempting to spawn vehicle...") + vehicle = world.try_spawn_actor(vehicle_bp, spawn_point) + + if vehicle is None: + # 生成失败(可能生成点被占用) + print(f"Error: Failed to spawn vehicle at {spawn_point.location}.") + return + + # 打印生成成功的车辆信息 + print(f"Vehicle {vehicle.type_id} (ID {vehicle.id}) spawned successfully.") + + # 等待车辆稳定(等待一次仿真tick,再加0.5秒延时) + world.wait_for_tick() + time.sleep(0.5) + + # 设置旁观者相机到车辆后上方 + set_spectator_behind_vehicle(world, vehicle) + + # 初始化天气为第一个预设 + switch_weather(world, weather_presets[0][1], weather_presets[0][0]) + + # 打印运行提示 + print(f"\nSimulation running. Vehicle is stationary.") + print(f"Weather will switch every {weather_switch_interval} seconds.") + print("Press Ctrl+C to stop.\n") + + # 主循环:保持仿真运行并定时切换天气 + while True: + # 等待仿真tick(推进仿真时间) + world.wait_for_tick() + + # 检查是否到达天气切换时间 + current_time = time.time() + if current_time - last_weather_switch_time >= weather_switch_interval: + # 切换到下一个天气(循环遍历预设列表) + current_weather_index = (current_weather_index + 1) % len(weather_presets) + weather_name, weather = weather_presets[current_weather_index] + switch_weather(world, weather, weather_name) + # 更新上一次切换时间 + last_weather_switch_time = current_time + + # 小延时,避免循环过于频繁(减少CPU占用) + time.sleep(0.1) + + except KeyboardInterrupt: + # 捕获用户Ctrl+C中断,友好退出 + print("\nScript stopped by user (Ctrl+C).") + except Exception as e: + # 捕获其他未预期的异常,打印错误信息和堆栈跟踪 + print(f"\nAn unexpected error occurred: {e}") + import traceback + traceback.print_exc() + finally: + # 资源清理:确保车辆被销毁,避免残留 + print("\nStarting resource cleanup...") + if vehicle is not None and vehicle.is_alive: + print(f"Destroying vehicle: {vehicle.type_id} (ID {vehicle.id})") + if vehicle.destroy(): + print("Vehicle destroyed successfully.") + else: + print("Vehicle destroy() returned False.") + else: + print("Vehicle was None or not alive, no destruction needed.") + + print("Simulation finished.") + + +# 程序入口 +if __name__ == '__main__': + main() \ No newline at end of file From 938de18944447a50c53835bff100eaafbea2f9e6 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Thu, 18 Dec 2025 22:09:40 +0800 Subject: [PATCH 02/26] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=AC=AC=E4=B8=80?= =?UTF-8?q?=E4=BA=BA=E7=A7=B0=E8=A7=86=E8=A7=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/v1.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/autonomous_driving_car/v1.py diff --git a/src/autonomous_driving_car/v1.py b/src/autonomous_driving_car/v1.py new file mode 100644 index 0000000000..e69de29bb2 From 5f2b01ea510d1b92e99d9847acbd5a102bed69a3 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Thu, 18 Dec 2025 22:27:28 +0800 Subject: [PATCH 03/26] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=AC=AC=E4=B8=80?= =?UTF-8?q?=E8=A7=86=E8=A7=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/v1.py | 208 +++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/src/autonomous_driving_car/v1.py b/src/autonomous_driving_car/v1.py index e69de29bb2..8e65a90916 100644 --- a/src/autonomous_driving_car/v1.py +++ b/src/autonomous_driving_car/v1.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python +""" +CARLA Vehicle Spawn with Pygame Display & Plotting (sd_3/__main__.py) + +This script connects to CARLA, spawns a vehicle, applies constant +forward throttle, and visualizes the simulation using: +1. PygameDisplay: Shows a rigidly attached camera view in a Pygame window. +2. Plotter: Plots the vehicle's X-coordinate and speed vs. time using Matplotlib. + +It also sets the initial spectator camera position to a fixed viewpoint. + +Dependencies: +- tools.pygame_display (Specifically the PygameDisplay class) +- tools.plotter_x (Specifically the Plotter class for X-coord/Speed plots) +""" + +import carla +import numpy as np +import time +import pygame # Pygame is implicitly needed by PygameDisplay +import math + +# Import helper classes from 'tools' directory using the specified format +from tools.pygame_display import PygameDisplay +from tools.plotter_x import Plotter + +# Global variable to store the simulation start time for plotting +simulation_start_time = 0.0 + +# --- Vehicle Data Functions --- +def get_location(vehicle): + """Returns the current location of the vehicle.""" + return vehicle.get_location() + +def get_speed_kmh(vehicle): + """Returns the current speed of the vehicle in km/h.""" + velocity_vector = vehicle.get_velocity() + # Calculate the magnitude of the velocity vector (speed in m/s) + speed_meters_per_second = np.linalg.norm([velocity_vector.x, velocity_vector.y, velocity_vector.z]) + return 3.6 * speed_meters_per_second + +# --- Simulation Management Functions --- +def remove_previous_vehicle(world): + """Finds and removes all vehicles with the role 'my_car'.""" + print("Searching for previous 'my_car' vehicles...") + actors = world.get_actors().filter('vehicle.*') + count = 0 + for actor in actors: + if actor.attributes.get('role_name') == 'my_car': + print(f" - Removing previous vehicle: {actor.type_id} (ID {actor.id})") + if actor.destroy(): + count += 1 + else: + print(f" - Failed to remove vehicle {actor.id}") + print(f"Removed {count} previous vehicles.") + + +# --- Spectator Setup --- +def set_initial_spectator_view(world): + """Sets the spectator camera to a predefined fixed position and rotation.""" + spectator = world.get_spectator() + # Define spectator transform using the desired fixed values (adjust as needed) + spectator_location = carla.Location(x=63.12, y=29.88, z=5.61) + spectator_rotation = carla.Rotation(pitch=-4.27, yaw=-170.21, roll=0.00) + spectator_transform = carla.Transform(spectator_location, spectator_rotation) + try: + spectator.set_transform(spectator_transform) + print(f"Initial spectator position set to: Loc=[{spectator_location.x:.2f}, {spectator_location.y:.2f}, {spectator_location.z:.2f}], Rot=[P:{spectator_rotation.pitch:.2f}, Y:{spectator_rotation.yaw:.2f}, R:{spectator_rotation.roll:.2f}]") + except Exception as e: + print(f"Error setting spectator transform: {e}") + + +def main(): + """Main execution function.""" + global simulation_start_time # Allow modification of the global variable + + client = None + world = None + vehicle = None + pygame_display = None + plotter = None + + try: + # Connect to CARLA + client = carla.Client('localhost', 2000) + client.set_timeout(10.0) + print("Connecting to CARLA server...") + world = client.get_world() + print(f"Connected to world: {world.get_map().name}") + + # Cleanup previous actors + remove_previous_vehicle(world) + + # Get spawn point + spawn_points = world.get_map().get_spawn_points() + if not spawn_points: + print("Error: No spawn points found on the map!") + return + spawn_point = spawn_points[0] # Use the first spawn point + print(f"Selected spawn point: {spawn_point.location}") + + # Get vehicle blueprint + vehicle_bp_library = world.get_blueprint_library() + vehicle_bp = vehicle_bp_library.filter('vehicle.tesla.model3')[0] + vehicle_bp.set_attribute('role_name', 'my_car') + + # Spawn vehicle + print("Attempting to spawn vehicle...") + vehicle = world.try_spawn_actor(vehicle_bp, spawn_point) + if vehicle is None: + print(f"Error: Failed to spawn vehicle at {spawn_point.location}") + return + print(f"Vehicle {vehicle.type_id} (ID {vehicle.id}) spawned at {vehicle.get_location()}.") + + # Set spectator view + set_initial_spectator_view(world) + + # --- Initialize Plotter & Pygame --- + print("Initializing Plotter...") + plotter = Plotter() # Uses tools.plotter_x + plotter.init_plot() + print("Plotter initialized.") + + print("Initializing Pygame display...") + pygame_display = PygameDisplay(world, vehicle) # Uses tools.pygame_display + print("Pygame display initialized.") + + # Record the start time for the plot's time axis + simulation_start_time = time.time() + + # --- Main Simulation Loop --- + print("Simulation running. Applying constant throttle. Press ESC or close Pygame window to stop.") + while True: + current_loop_time = time.time() # Get time at the start of the loop iteration + + # Handle Pygame events (QUIT, ESC) + if pygame_display.parse_events(): + print("Quit requested via Pygame window.") + break # Exit the loop if quit is requested + + # Wait for the next simulation tick + world.wait_for_tick() + + # --- Data Collection for Plotter --- + current_location = get_location(vehicle) + current_speed_kmh = get_speed_kmh(vehicle) + # Calculate time elapsed since simulation start + current_sim_time_sec = current_loop_time - simulation_start_time + + # --- Render the Pygame display --- + pygame_display.render() + + # --- Update Plot --- + if plotter is not None and plotter.is_initialized: + try: + # plotter_x expects time, x, current_speed, desired_speed + # We don't have a desired speed here, so pass 0 or current speed + plotter.update_plot(current_sim_time_sec, current_location.x, current_speed_kmh, 0.0) + except Exception as plot_update_e: + # Handle potential errors if the plot window was closed + print(f"Error updating plot (likely closed): {plot_update_e}") + plotter.cleanup_plot() # Attempt cleanup + plotter = None # Stop trying to update + + # --- Vehicle Control --- + # Apply constant forward throttle + control = carla.VehicleControl(throttle=0.8, steer=0.0, brake=0.0) + vehicle.apply_control(control) + + except KeyboardInterrupt: + print("\nScript interrupted by user (Ctrl+C).") + except Exception as e: + print(f"\nAn unexpected error occurred: {e}") + import traceback + traceback.print_exc() + + finally: + print("Starting resource cleanup...") + + # Destroy Pygame display first (handles its own camera cleanup) + if pygame_display is not None: + print("Destroying Pygame display...") + pygame_display.destroy() + print("Pygame display destroyed.") + + # Cleanup plotter + if plotter is not None: + # Check if it needs cleanup (might already be None if closed/error) + if plotter.is_initialized: + print("Cleaning up plotter...") + plotter.cleanup_plot() + print("Plotter cleaned up.") + + # Destroy the main vehicle + if vehicle is not None and vehicle.is_alive: + print(f"Destroying vehicle: {vehicle.type_id} (ID {vehicle.id})") + # vehicle.set_simulate_physics(False) # Optional: might help ensure clean removal + if vehicle.destroy(): + print("Vehicle destroyed successfully.") + else: + print("Vehicle destroy() returned False.") + else: + print("Vehicle was None or not alive, no destruction needed.") + + print("Simulation finished.") + +if __name__ == '__main__': + main() From 6032ef7ce4754a2c660acde8ec5c51a43faeb8aa Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Fri, 19 Dec 2025 10:37:05 +0800 Subject: [PATCH 04/26] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=BD=A6=E8=BE=86?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E4=BF=A1=E6=81=AF=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/v2.py | 257 +++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 src/autonomous_driving_car/v2.py diff --git a/src/autonomous_driving_car/v2.py b/src/autonomous_driving_car/v2.py new file mode 100644 index 0000000000..4e0a889bc9 --- /dev/null +++ b/src/autonomous_driving_car/v2.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +CARLA Vehicle Simulation with Visualization (sd_3/__main__.py) + +核心功能: +1. 连接CARLA仿真服务器,清理历史车辆 Actor +2. 生成特斯拉Model3车辆,施加恒定油门控制 +3. 基于PygameDisplay显示车辆挂载摄像头的实时画面 +4. 基于Plotter绘制车辆X坐标和速度随时间的变化曲线 +5. 设置固定的旁观者相机视角,方便观察仿真过程 + +依赖: +- tools.pygame_display.PygameDisplay:Pygame窗口显示摄像头画面 +- tools.plotter_x.Plotter:Matplotlib绘制X坐标/速度曲线 +""" + +# 加入这几行代码,解决模块导入问题 +import sys +import os + +# 获取项目根目录(carla-python-examples) +PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +# 将项目根目录加入Python搜索路径 +sys.path.append(PROJECT_ROOT) +import carla +import numpy as np +import time +import pygame +import math +import traceback +from typing import Optional, Tuple + +# 导入自定义工具类 +from tools.pygame_display import PygameDisplay +from tools.plotter_x import Plotter + +# ===================== 配置集中化(便于修改和维护)===================== +class SimConfig: + """仿真配置类:集中管理所有硬编码参数""" + # CARLA服务器连接信息 + CARLA_HOST = "localhost" + CARLA_PORT = 2000 + CARLA_TIMEOUT = 10.0 # 连接超时时间(秒) + + # 车辆配置 + VEHICLE_MODEL = "vehicle.tesla.model3" # 车辆模型 + VEHICLE_ROLE_NAME = "my_car" # 车辆角色名(用于清理历史车辆) + CONSTANT_THROTTLE = 0.8 # 恒定油门值(0-1) + STEER_ANGLE = 0.0 # 恒定转向角(0=直线行驶) + BRAKE_VALUE = 0.0 # 刹车值(0=不刹车) + + # 旁观者相机固定视角(可根据地图调整) + SPECTATOR_LOCATION = carla.Location(x=63.12, y=29.88, z=5.61) + SPECTATOR_ROTATION = carla.Rotation(pitch=-4.27, yaw=-170.21, roll=0.00) + + # 绘图配置 + DESIRED_SPEED = 0.0 # 期望速度(若无速度控制,设为0) + +# ===================== 车辆状态计算工具函数 ===================== +def calculate_vehicle_speed_kmh(vehicle: carla.Vehicle) -> float: + """ + 计算车辆当前速度(km/h) + :param vehicle: CARLA车辆Actor对象 + :return: 车辆速度(km/h) + """ + velocity = vehicle.get_velocity() + # 计算速度矢量的模(m/s),转换为km/h(×3.6) + speed_mps = np.linalg.norm([velocity.x, velocity.y, velocity.z]) + return speed_mps * 3.6 + +def get_vehicle_location(vehicle: carla.Vehicle) -> carla.Location: + """ + 获取车辆当前位置 + :param vehicle: CARLA车辆Actor对象 + :return: 车辆的Location对象 + """ + return vehicle.get_location() + +# ===================== 仿真资源管理工具函数 ===================== +def clean_up_prev_vehicles(world: carla.World, role_name: str) -> int: + """ + 清理地图中指定角色名的历史车辆 + :param world: CARLA的World对象 + :param role_name: 车辆角色名 + :return: 成功销毁的车辆数量 + """ + print(f"\n[资源清理] 搜索角色为'{role_name}'的历史车辆...") + vehicles = world.get_actors().filter("vehicle.*") + destroyed_count = 0 + + for vehicle in vehicles: + if vehicle.attributes.get("role_name") == role_name: + print(f" - 销毁历史车辆:{vehicle.type_id} (ID: {vehicle.id})") + if vehicle.destroy(): + destroyed_count += 1 + else: + print(f" - 销毁车辆{vehicle.id}失败") + + print(f"[资源清理] 共销毁{destroyed_count}辆历史车辆") + return destroyed_count + +def set_spectator_fixed_view(world: carla.World, location: carla.Location, rotation: carla.Rotation) -> bool: + """ + 设置旁观者相机的固定视角 + :param world: CARLA的World对象 + :param location: 旁观者相机位置 + :param rotation: 旁观者相机旋转角度 + :return: 是否设置成功 + """ + try: + spectator = world.get_spectator() + spectator_transform = carla.Transform(location, rotation) + spectator.set_transform(spectator_transform) + print(f"\n[视角设置] 旁观者相机位置:({location.x:.2f}, {location.y:.2f}, {location.z:.2f})") + print(f"[视角设置] 旁观者相机旋转:(俯仰:{rotation.pitch:.2f}, 偏航:{rotation.yaw:.2f}, 翻滚:{rotation.roll:.2f})") + return True + except Exception as e: + print(f"[视角设置] 失败:{e}") + return False + +# ===================== 主仿真函数 ===================== +def main(): + """主仿真入口函数:完成所有仿真流程的初始化、运行和清理""" + # 初始化核心变量(所有资源对象初始化为None,便于后续清理) + client: Optional[carla.Client] = None + world: Optional[carla.World] = None + vehicle: Optional[carla.Vehicle] = None + pygame_display: Optional[PygameDisplay] = None + plotter: Optional[Plotter] = None + simulation_start_time: Optional[float] = None + + try: + # 1. 连接CARLA服务器 + print(f"[CARLA连接] 尝试连接 {SimConfig.CARLA_HOST}:{SimConfig.CARLA_PORT}...") + client = carla.Client(SimConfig.CARLA_HOST, SimConfig.CARLA_PORT) + client.set_timeout(SimConfig.CARLA_TIMEOUT) + world = client.get_world() + print(f"[CARLA连接] 成功连接到地图:{world.get_map().name}") + + # 2. 清理历史车辆 + clean_up_prev_vehicles(world, SimConfig.VEHICLE_ROLE_NAME) + + # 3. 获取车辆生成点 + spawn_points = world.get_map().get_spawn_points() + if not spawn_points: + raise RuntimeError("[生成车辆] 地图中未找到可用的生成点!") + spawn_point = spawn_points[0] + print(f"[生成车辆] 选择生成点:({spawn_point.location.x:.2f}, {spawn_point.location.y:.2f}, {spawn_point.location.z:.2f})") + + # 4. 加载车辆蓝图并设置属性 + blueprint_library = world.get_blueprint_library() + vehicle_bp = blueprint_library.filter(SimConfig.VEHICLE_MODEL)[0] + vehicle_bp.set_attribute("role_name", SimConfig.VEHICLE_ROLE_NAME) + print(f"[生成车辆] 加载车辆蓝图:{vehicle_bp.id}") + + # 5. 生成车辆 + vehicle = world.try_spawn_actor(vehicle_bp, spawn_point) + if vehicle is None: + raise RuntimeError(f"[生成车辆] 在生成点{spawn_point.location}生成车辆失败!") + print(f"[生成车辆] 成功生成:{vehicle.type_id} (ID: {vehicle.id})") + + # 6. 设置旁观者相机视角 + set_spectator_fixed_view(world, SimConfig.SPECTATOR_LOCATION, SimConfig.SPECTATOR_ROTATION) + + # 7. 初始化可视化组件 + print("\n[可视化] 初始化绘图器(Plotter)...") + plotter = Plotter() + plotter.init_plot() + print("[可视化] 绘图器初始化完成") + + print("[可视化] 初始化Pygame显示窗口...") + pygame_display = PygameDisplay(world, vehicle) + print("[可视化] Pygame显示窗口初始化完成") + + # 8. 记录仿真开始时间 + simulation_start_time = time.time() + print(f"\n[仿真启动] 开始运行仿真(恒定油门:{SimConfig.CONSTANT_THROTTLE})") + print("[仿真启动] 按ESC或关闭Pygame窗口停止仿真...") + + # 9. 主仿真循环 + while True: + # 9.1 处理Pygame事件(关闭窗口、ESC键) + if pygame_display.parse_events(): + print("[仿真控制] 检测到Pygame退出请求,停止仿真...") + break + + # 9.2 等待仿真tick(同步仿真时间) + world.wait_for_tick() + + # 9.3 收集车辆状态数据 + current_time = time.time() + vehicle_location = get_vehicle_location(vehicle) + vehicle_speed = calculate_vehicle_speed_kmh(vehicle) + sim_elapsed_time = current_time - simulation_start_time # 仿真已运行时间(秒) + + # 9.4 渲染Pygame窗口(摄像头画面) + pygame_display.render() + + # 9.5 更新绘图器(X坐标、速度曲线) + if plotter and plotter.is_initialized: + try: + plotter.update_plot( + sim_elapsed_time, + vehicle_location.x, + vehicle_speed, + SimConfig.DESIRED_SPEED + ) + except Exception as e: + print(f"[绘图器] 更新失败(可能已关闭窗口):{e}") + plotter.cleanup_plot() + plotter = None + + # 9.6 车辆控制:施加恒定油门 + vehicle_control = carla.VehicleControl( + throttle=SimConfig.CONSTANT_THROTTLE, + steer=SimConfig.STEER_ANGLE, + brake=SimConfig.BRAKE_VALUE + ) + vehicle.apply_control(vehicle_control) + + # 异常处理 + except KeyboardInterrupt: + print("\n[仿真中断] 用户按下Ctrl+C,停止仿真...") + except RuntimeError as e: + print(f"\n[仿真错误] 运行时错误:{e}") + traceback.print_exc() + except Exception as e: + print(f"\n[仿真错误] 未知异常:{e}") + traceback.print_exc() + + # 资源清理(无论是否异常,都执行) + finally: + print("\n[资源清理] 开始清理仿真资源...") + + # 清理Pygame显示窗口 + if pygame_display: + print("[资源清理] 销毁Pygame显示窗口...") + pygame_display.destroy() + + # 清理绘图器 + if plotter and plotter.is_initialized: + print("[资源清理] 清理绘图器...") + plotter.cleanup_plot() + + # 销毁车辆 + if vehicle and vehicle.is_alive: + print(f"[资源清理] 销毁车辆:{vehicle.type_id} (ID: {vehicle.id})") + if vehicle.destroy(): + print("[资源清理] 车辆销毁成功") + else: + print("[资源清理] 车辆销毁失败") + + print("[资源清理] 仿真资源清理完成") + +if __name__ == "__main__": + main() \ No newline at end of file From 2218a0f225e93e4423d9514032857b43e5a1a218 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Fri, 19 Dec 2025 14:39:50 +0800 Subject: [PATCH 05/26] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=87=AA=E4=B8=BB?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E4=B8=8E=E8=B7=AF=E5=BE=84=E8=B7=9F=E9=9A=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/v3.py | 344 +++++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 src/autonomous_driving_car/v3.py diff --git a/src/autonomous_driving_car/v3.py b/src/autonomous_driving_car/v3.py new file mode 100644 index 0000000000..174d1de37d --- /dev/null +++ b/src/autonomous_driving_car/v3.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +CARLA Waypoint Following with Simple Speed Control & Pygame Display (sd_4/__main__.py) + +核心功能: +1. 连接CARLA仿真服务器,清理历史车辆Actor +2. 生成特斯拉Model3车辆,基于预定义航点实现路径跟随 +3. 采用简易的bang-bang速度控制器,根据当前速度与目标速度调整油门/刹车 +4. 基于PygameDisplay显示车辆挂载摄像头的实时画面 +5. 基于Plotter绘制车辆X坐标、实际速度与期望速度的时间曲线 +6. 设置固定的旁观者相机视角,方便观察仿真过程 + +依赖: +- tools.plotter_x.Plotter:Matplotlib绘制X坐标/速度曲线 +- tools.pygame_display.PygameDisplay:Pygame窗口显示摄像头画面 +""" + +# ===================== 路径处理(解决tools模块导入问题)===================== +import sys +import os +PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(PROJECT_ROOT) +# ============================================================================== + +import carla +import numpy as np +import time +import traceback +from typing import Optional, List, Tuple + +# 导入自定义工具类 +from tools.plotter_x import Plotter +from tools.pygame_display import PygameDisplay + +# ===================== 配置集中化(便于修改和维护)===================== +class SimConfig: + """仿真配置类:集中管理所有硬编码参数""" + # CARLA服务器连接信息 + CARLA_HOST = "localhost" + CARLA_PORT = 2000 + CARLA_TIMEOUT = 10.0 # 连接超时时间(秒) + + # 车辆配置 + VEHICLE_MODEL = "vehicle.tesla.model3" + VEHICLE_ROLE_NAME = "my_car" + + # 航点配置:[x坐标, y坐标, 期望速度(km/h)] + WAYPOINTS: List[List[float]] = [ + [-64.0, 24.5, 40.0], # 起始点(第一段路径的参考点) + [40.0, 24.5, 40.0], # 制动点前的行驶点(保持40km/h) + [70.0, 24.5, 0.0] # 停止点(目标速度0km/h) + ] + WAYPOINT_THRESHOLD = 5.0 # 切换航点的距离阈值(米) + INITIAL_TARGET_WAYPOINT_ID = 1 # 初始目标航点索引(从1开始,0为起始点) + + # 旁观者相机固定视角 + SPECTATOR_LOCATION = carla.Location(x=63.12, y=29.88, z=5.61) + SPECTATOR_ROTATION = carla.Rotation(pitch=-4.27, yaw=-170.21, roll=0.00) + +# ===================== 车辆状态计算工具函数 ===================== +def get_vehicle_location(vehicle: carla.Vehicle) -> carla.Location: + """ + 获取车辆当前位置 + :param vehicle: CARLA车辆Actor对象 + :return: 车辆的Location对象 + """ + return vehicle.get_location() + +def calculate_vehicle_speed_kmh(vehicle: carla.Vehicle) -> float: + """ + 计算车辆当前速度(km/h) + :param vehicle: CARLA车辆Actor对象 + :return: 车辆速度(km/h) + """ + velocity = vehicle.get_velocity() + speed_mps = np.linalg.norm([velocity.x, velocity.y, velocity.z]) + return speed_mps * 3.6 + +# ===================== 仿真资源管理工具函数 ===================== +def clean_up_prev_vehicles(world: carla.World, role_name: str) -> int: + """ + 清理地图中指定角色名的历史车辆 + :param world: CARLA的World对象 + :param role_name: 车辆角色名 + :return: 成功销毁的车辆数量 + """ + print(f"\n[资源清理] 搜索角色为'{role_name}'的历史车辆...") + vehicles = world.get_actors().filter("vehicle.*") + destroyed_count = 0 + + for vehicle in vehicles: + if vehicle.attributes.get("role_name") == role_name: + print(f" - 销毁历史车辆:{vehicle.type_id} (ID: {vehicle.id})") + if vehicle.destroy(): + destroyed_count += 1 + else: + print(f" - 销毁车辆{vehicle.id}失败") + + print(f"[资源清理] 共销毁{destroyed_count}辆历史车辆") + return destroyed_count + +def set_spectator_fixed_view(world: carla.World) -> bool: + """ + 设置旁观者相机的固定视角 + :param world: CARLA的World对象 + :return: 是否设置成功 + """ + try: + spectator = world.get_spectator() + spectator_transform = carla.Transform( + SimConfig.SPECTATOR_LOCATION, + SimConfig.SPECTATOR_ROTATION + ) + spectator.set_transform(spectator_transform) + print(f"\n[视角设置] 旁观者相机位置:({SimConfig.SPECTATOR_LOCATION.x:.2f}, {SimConfig.SPECTATOR_LOCATION.y:.2f}, {SimConfig.SPECTATOR_LOCATION.z:.2f})") + print(f"[视角设置] 旁观者相机旋转:(俯仰:{SimConfig.SPECTATOR_ROTATION.pitch:.2f}, 偏航:{SimConfig.SPECTATOR_ROTATION.yaw:.2f}, 翻滚:{SimConfig.SPECTATOR_ROTATION.roll:.2f})") + return True + except Exception as e: + print(f"[视角设置] 失败:{e}") + return False + +# ===================== 航点管理函数 ===================== +def update_target_waypoint( + vehicle_location: carla.Location, + current_target_id: int, + waypoints: List[List[float]], + threshold: float +) -> int: + """ + 检查是否到达当前目标航点,若到达则切换到下一个航点 + :param vehicle_location: 车辆当前位置 + :param current_target_id: 当前目标航点索引 + :param waypoints: 航点列表 + :param threshold: 切换航点的距离阈值 + :return: 更新后的目标航点索引 + """ + # 若已是最后一个航点,不再切换 + if current_target_id >= len(waypoints) - 1: + return current_target_id + + # 获取当前目标航点的位置 + target_wp_data = waypoints[current_target_id] + target_loc = carla.Location(x=target_wp_data[0], y=target_wp_data[1]) + + # 关键修改:手动计算2D距离(替代CARLA低版本不存在的distance_2d方法) + dx = vehicle_location.x - target_loc.x + dy = vehicle_location.y - target_loc.y + distance = np.sqrt(dx ** 2 + dy ** 2) + + # 若距离小于阈值,切换到下一个航点 + if distance < threshold: + print(f"[航点更新] 到达航点{current_target_id}(距离:{distance:.1f}m),新目标航点:{current_target_id + 1}") + return current_target_id + 1 + + return current_target_id + +# ===================== 速度控制器 ===================== +def simple_speed_controller(v_desired: float, v_current: float) -> carla.VehicleControl: + """ + 简易的3状态速度控制器(bang-bang控制) + 根据当前速度与期望速度的差值,全量施加油门或刹车 + :param v_desired: 期望速度(km/h) + :param v_current: 当前速度(km/h) + :return: CARLA车辆控制指令 + """ + control = carla.VehicleControl() + control.steer = 0.0 # 暂不控制转向 + control.throttle = 0.0 + control.brake = 0.0 + + # 目标速度为0时,全力刹车 + if v_desired == 0: + control.brake = 1.0 + # 当前速度小于期望速度,全力加速 + elif v_current < v_desired: + control.throttle = 1.0 + # 当前速度大于期望速度,全力刹车 + elif v_current > v_desired: + control.brake = 1.0 + + return control + +# ===================== 主仿真函数 ===================== +def main(): + """主仿真入口函数:完成所有仿真流程的初始化、运行和清理""" + # 初始化核心变量 + client: Optional[carla.Client] = None + world: Optional[carla.World] = None + vehicle: Optional[carla.Vehicle] = None + plotter: Optional[Plotter] = None + pygame_display: Optional[PygameDisplay] = None + simulation_start_time: Optional[float] = None + target_waypoint_id: int = SimConfig.INITIAL_TARGET_WAYPOINT_ID + + try: + # 1. 连接CARLA服务器 + print(f"[CARLA连接] 尝试连接 {SimConfig.CARLA_HOST}:{SimConfig.CARLA_PORT}...") + client = carla.Client(SimConfig.CARLA_HOST, SimConfig.CARLA_PORT) + client.set_timeout(SimConfig.CARLA_TIMEOUT) + world = client.get_world() + print(f"[CARLA连接] 成功连接到地图:{world.get_map().name}") + + # 2. 清理历史车辆 + clean_up_prev_vehicles(world, SimConfig.VEHICLE_ROLE_NAME) + + # 3. 生成车辆(基于第一个航点作为起始位置) + map_spawn_points = world.get_map().get_spawn_points() + if not map_spawn_points: + raise RuntimeError("[生成车辆] 地图中未找到可用的生成点!") + + # 获取起始航点的位置 + start_waypoint = SimConfig.WAYPOINTS[0] + # 关键修改:将z轴高度从0.5提高到1.5,避免与地面碰撞 + start_location = carla.Location(x=start_waypoint[0], y=start_waypoint[1], z=1.5) + + # 找到距离起始位置最近的地图生成点(用于获取道路朝向) + spawn_point = min(map_spawn_points, key=lambda sp: sp.location.distance(start_location)) + spawn_point.location = start_location # 覆盖为自定义起始位置 + + print(f"[生成车辆] 目标生成位置:{start_location},使用地图朝向:{spawn_point.rotation}") + + # 加载车辆蓝图 + blueprint_library = world.get_blueprint_library() + vehicle_bp = blueprint_library.filter(SimConfig.VEHICLE_MODEL)[0] + vehicle_bp.set_attribute("role_name", SimConfig.VEHICLE_ROLE_NAME) + + # 生成车辆 + print("[生成车辆] 尝试生成车辆...") + vehicle = world.try_spawn_actor(vehicle_bp, spawn_point) + + # 关键修改:添加重试逻辑,第一个点失败则尝试第二个地图默认点 + if vehicle is None: + print("[生成车辆] 第一个点生成失败,尝试第二个地图默认点...") + spawn_point = map_spawn_points[1] + vehicle = world.try_spawn_actor(vehicle_bp, spawn_point) + if vehicle is None: + raise RuntimeError(f"[生成车辆] 生成失败,请检查坐标是否合法!") + + print(f"[生成车辆] 成功生成:{vehicle.type_id} (ID: {vehicle.id})") + + # 4. 设置旁观者相机视角 + set_spectator_fixed_view(world) + + # 5. 初始化可视化组件 + print("\n[可视化] 初始化绘图器(Plotter)...") + plotter = Plotter() + plotter.init_plot() + print("[可视化] 绘图器初始化完成") + + print("[可视化] 初始化Pygame显示窗口...") + pygame_display = PygameDisplay(world, vehicle) + print("[可视化] Pygame显示窗口初始化完成") + + # 6. 记录仿真开始时间 + simulation_start_time = time.time() + target_waypoint_id = SimConfig.INITIAL_TARGET_WAYPOINT_ID + + # 7. 主仿真循环 + print("\n[仿真启动] 开始航点跟随与速度控制。按ESC或关闭Pygame窗口停止仿真...") + while True: + current_loop_time = time.time() + + # 处理Pygame事件(关闭窗口、ESC键) + if pygame_display.parse_events(): + print("[仿真控制] 检测到Pygame退出请求,停止仿真...") + break + + # 等待仿真tick(同步仿真时间) + world.wait_for_tick() + + # 8. 收集车辆状态数据 + vehicle_location = get_vehicle_location(vehicle) + vehicle_speed = calculate_vehicle_speed_kmh(vehicle) + sim_elapsed_time = current_loop_time - simulation_start_time + + # 9. 更新可视化 + # 更新绘图器 + if plotter and plotter.is_initialized: + current_desired_speed = SimConfig.WAYPOINTS[target_waypoint_id][2] + try: + plotter.update_plot(sim_elapsed_time, vehicle_location.x, vehicle_speed, current_desired_speed) + except Exception as e: + print(f"[绘图器] 更新失败(可能已关闭窗口):{e}") + plotter.cleanup_plot() + plotter = None + + # 渲染Pygame窗口 + pygame_display.render() + + # 10. 航点跟随逻辑 + # 更新目标航点 + target_waypoint_id = update_target_waypoint( + vehicle_location, + target_waypoint_id, + SimConfig.WAYPOINTS, + SimConfig.WAYPOINT_THRESHOLD + ) + + # 获取当前目标航点的期望速度 + current_desired_speed = SimConfig.WAYPOINTS[target_waypoint_id][2] if 0 <= target_waypoint_id < len(SimConfig.WAYPOINTS) else 0.0 + + # 计算车辆控制指令 + control = simple_speed_controller(current_desired_speed, vehicle_speed) + + # 应用控制指令 + vehicle.apply_control(control) + + # 异常处理 + except KeyboardInterrupt: + print("\n[仿真中断] 用户按下Ctrl+C,停止仿真...") + except RuntimeError as e: + print(f"\n[仿真错误] 运行时错误:{e}") + traceback.print_exc() + except Exception as e: + print(f"\n[仿真错误] 未知异常:{e}") + traceback.print_exc() + + # 资源清理 + finally: + print("\n[资源清理] 开始清理仿真资源...") + + # 清理Pygame显示窗口 + if pygame_display: + print("[资源清理] 销毁Pygame显示窗口...") + pygame_display.destroy() + + # 清理绘图器 + if plotter and plotter.is_initialized: + print("[资源清理] 清理绘图器...") + plotter.cleanup_plot() + + # 销毁车辆 + if vehicle and vehicle.is_alive: + print(f"[资源清理] 销毁车辆:{vehicle.type_id} (ID: {vehicle.id})") + if vehicle.destroy(): + print("[资源清理] 车辆销毁成功") + else: + print("[资源清理] 车辆销毁失败") + + print("[资源清理] 仿真资源清理完成") + +if __name__ == "__main__": + main() \ No newline at end of file From b1a8426883cbeb93406fe2603418e48541f97ee9 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Fri, 19 Dec 2025 16:05:15 +0800 Subject: [PATCH 06/26] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E4=BA=86=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E5=85=A5=E5=8F=A3=E8=84=9A=E6=9C=AC=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/{ => FPV}/v1.py | 0 src/autonomous_driving_car/{ => MCP}/v2.py | 0 src/autonomous_driving_car/v3.py | 344 --------------------- 3 files changed, 344 deletions(-) rename src/autonomous_driving_car/{ => FPV}/v1.py (100%) rename src/autonomous_driving_car/{ => MCP}/v2.py (100%) delete mode 100644 src/autonomous_driving_car/v3.py diff --git a/src/autonomous_driving_car/v1.py b/src/autonomous_driving_car/FPV/v1.py similarity index 100% rename from src/autonomous_driving_car/v1.py rename to src/autonomous_driving_car/FPV/v1.py diff --git a/src/autonomous_driving_car/v2.py b/src/autonomous_driving_car/MCP/v2.py similarity index 100% rename from src/autonomous_driving_car/v2.py rename to src/autonomous_driving_car/MCP/v2.py diff --git a/src/autonomous_driving_car/v3.py b/src/autonomous_driving_car/v3.py deleted file mode 100644 index 174d1de37d..0000000000 --- a/src/autonomous_driving_car/v3.py +++ /dev/null @@ -1,344 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -CARLA Waypoint Following with Simple Speed Control & Pygame Display (sd_4/__main__.py) - -核心功能: -1. 连接CARLA仿真服务器,清理历史车辆Actor -2. 生成特斯拉Model3车辆,基于预定义航点实现路径跟随 -3. 采用简易的bang-bang速度控制器,根据当前速度与目标速度调整油门/刹车 -4. 基于PygameDisplay显示车辆挂载摄像头的实时画面 -5. 基于Plotter绘制车辆X坐标、实际速度与期望速度的时间曲线 -6. 设置固定的旁观者相机视角,方便观察仿真过程 - -依赖: -- tools.plotter_x.Plotter:Matplotlib绘制X坐标/速度曲线 -- tools.pygame_display.PygameDisplay:Pygame窗口显示摄像头画面 -""" - -# ===================== 路径处理(解决tools模块导入问题)===================== -import sys -import os -PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -sys.path.append(PROJECT_ROOT) -# ============================================================================== - -import carla -import numpy as np -import time -import traceback -from typing import Optional, List, Tuple - -# 导入自定义工具类 -from tools.plotter_x import Plotter -from tools.pygame_display import PygameDisplay - -# ===================== 配置集中化(便于修改和维护)===================== -class SimConfig: - """仿真配置类:集中管理所有硬编码参数""" - # CARLA服务器连接信息 - CARLA_HOST = "localhost" - CARLA_PORT = 2000 - CARLA_TIMEOUT = 10.0 # 连接超时时间(秒) - - # 车辆配置 - VEHICLE_MODEL = "vehicle.tesla.model3" - VEHICLE_ROLE_NAME = "my_car" - - # 航点配置:[x坐标, y坐标, 期望速度(km/h)] - WAYPOINTS: List[List[float]] = [ - [-64.0, 24.5, 40.0], # 起始点(第一段路径的参考点) - [40.0, 24.5, 40.0], # 制动点前的行驶点(保持40km/h) - [70.0, 24.5, 0.0] # 停止点(目标速度0km/h) - ] - WAYPOINT_THRESHOLD = 5.0 # 切换航点的距离阈值(米) - INITIAL_TARGET_WAYPOINT_ID = 1 # 初始目标航点索引(从1开始,0为起始点) - - # 旁观者相机固定视角 - SPECTATOR_LOCATION = carla.Location(x=63.12, y=29.88, z=5.61) - SPECTATOR_ROTATION = carla.Rotation(pitch=-4.27, yaw=-170.21, roll=0.00) - -# ===================== 车辆状态计算工具函数 ===================== -def get_vehicle_location(vehicle: carla.Vehicle) -> carla.Location: - """ - 获取车辆当前位置 - :param vehicle: CARLA车辆Actor对象 - :return: 车辆的Location对象 - """ - return vehicle.get_location() - -def calculate_vehicle_speed_kmh(vehicle: carla.Vehicle) -> float: - """ - 计算车辆当前速度(km/h) - :param vehicle: CARLA车辆Actor对象 - :return: 车辆速度(km/h) - """ - velocity = vehicle.get_velocity() - speed_mps = np.linalg.norm([velocity.x, velocity.y, velocity.z]) - return speed_mps * 3.6 - -# ===================== 仿真资源管理工具函数 ===================== -def clean_up_prev_vehicles(world: carla.World, role_name: str) -> int: - """ - 清理地图中指定角色名的历史车辆 - :param world: CARLA的World对象 - :param role_name: 车辆角色名 - :return: 成功销毁的车辆数量 - """ - print(f"\n[资源清理] 搜索角色为'{role_name}'的历史车辆...") - vehicles = world.get_actors().filter("vehicle.*") - destroyed_count = 0 - - for vehicle in vehicles: - if vehicle.attributes.get("role_name") == role_name: - print(f" - 销毁历史车辆:{vehicle.type_id} (ID: {vehicle.id})") - if vehicle.destroy(): - destroyed_count += 1 - else: - print(f" - 销毁车辆{vehicle.id}失败") - - print(f"[资源清理] 共销毁{destroyed_count}辆历史车辆") - return destroyed_count - -def set_spectator_fixed_view(world: carla.World) -> bool: - """ - 设置旁观者相机的固定视角 - :param world: CARLA的World对象 - :return: 是否设置成功 - """ - try: - spectator = world.get_spectator() - spectator_transform = carla.Transform( - SimConfig.SPECTATOR_LOCATION, - SimConfig.SPECTATOR_ROTATION - ) - spectator.set_transform(spectator_transform) - print(f"\n[视角设置] 旁观者相机位置:({SimConfig.SPECTATOR_LOCATION.x:.2f}, {SimConfig.SPECTATOR_LOCATION.y:.2f}, {SimConfig.SPECTATOR_LOCATION.z:.2f})") - print(f"[视角设置] 旁观者相机旋转:(俯仰:{SimConfig.SPECTATOR_ROTATION.pitch:.2f}, 偏航:{SimConfig.SPECTATOR_ROTATION.yaw:.2f}, 翻滚:{SimConfig.SPECTATOR_ROTATION.roll:.2f})") - return True - except Exception as e: - print(f"[视角设置] 失败:{e}") - return False - -# ===================== 航点管理函数 ===================== -def update_target_waypoint( - vehicle_location: carla.Location, - current_target_id: int, - waypoints: List[List[float]], - threshold: float -) -> int: - """ - 检查是否到达当前目标航点,若到达则切换到下一个航点 - :param vehicle_location: 车辆当前位置 - :param current_target_id: 当前目标航点索引 - :param waypoints: 航点列表 - :param threshold: 切换航点的距离阈值 - :return: 更新后的目标航点索引 - """ - # 若已是最后一个航点,不再切换 - if current_target_id >= len(waypoints) - 1: - return current_target_id - - # 获取当前目标航点的位置 - target_wp_data = waypoints[current_target_id] - target_loc = carla.Location(x=target_wp_data[0], y=target_wp_data[1]) - - # 关键修改:手动计算2D距离(替代CARLA低版本不存在的distance_2d方法) - dx = vehicle_location.x - target_loc.x - dy = vehicle_location.y - target_loc.y - distance = np.sqrt(dx ** 2 + dy ** 2) - - # 若距离小于阈值,切换到下一个航点 - if distance < threshold: - print(f"[航点更新] 到达航点{current_target_id}(距离:{distance:.1f}m),新目标航点:{current_target_id + 1}") - return current_target_id + 1 - - return current_target_id - -# ===================== 速度控制器 ===================== -def simple_speed_controller(v_desired: float, v_current: float) -> carla.VehicleControl: - """ - 简易的3状态速度控制器(bang-bang控制) - 根据当前速度与期望速度的差值,全量施加油门或刹车 - :param v_desired: 期望速度(km/h) - :param v_current: 当前速度(km/h) - :return: CARLA车辆控制指令 - """ - control = carla.VehicleControl() - control.steer = 0.0 # 暂不控制转向 - control.throttle = 0.0 - control.brake = 0.0 - - # 目标速度为0时,全力刹车 - if v_desired == 0: - control.brake = 1.0 - # 当前速度小于期望速度,全力加速 - elif v_current < v_desired: - control.throttle = 1.0 - # 当前速度大于期望速度,全力刹车 - elif v_current > v_desired: - control.brake = 1.0 - - return control - -# ===================== 主仿真函数 ===================== -def main(): - """主仿真入口函数:完成所有仿真流程的初始化、运行和清理""" - # 初始化核心变量 - client: Optional[carla.Client] = None - world: Optional[carla.World] = None - vehicle: Optional[carla.Vehicle] = None - plotter: Optional[Plotter] = None - pygame_display: Optional[PygameDisplay] = None - simulation_start_time: Optional[float] = None - target_waypoint_id: int = SimConfig.INITIAL_TARGET_WAYPOINT_ID - - try: - # 1. 连接CARLA服务器 - print(f"[CARLA连接] 尝试连接 {SimConfig.CARLA_HOST}:{SimConfig.CARLA_PORT}...") - client = carla.Client(SimConfig.CARLA_HOST, SimConfig.CARLA_PORT) - client.set_timeout(SimConfig.CARLA_TIMEOUT) - world = client.get_world() - print(f"[CARLA连接] 成功连接到地图:{world.get_map().name}") - - # 2. 清理历史车辆 - clean_up_prev_vehicles(world, SimConfig.VEHICLE_ROLE_NAME) - - # 3. 生成车辆(基于第一个航点作为起始位置) - map_spawn_points = world.get_map().get_spawn_points() - if not map_spawn_points: - raise RuntimeError("[生成车辆] 地图中未找到可用的生成点!") - - # 获取起始航点的位置 - start_waypoint = SimConfig.WAYPOINTS[0] - # 关键修改:将z轴高度从0.5提高到1.5,避免与地面碰撞 - start_location = carla.Location(x=start_waypoint[0], y=start_waypoint[1], z=1.5) - - # 找到距离起始位置最近的地图生成点(用于获取道路朝向) - spawn_point = min(map_spawn_points, key=lambda sp: sp.location.distance(start_location)) - spawn_point.location = start_location # 覆盖为自定义起始位置 - - print(f"[生成车辆] 目标生成位置:{start_location},使用地图朝向:{spawn_point.rotation}") - - # 加载车辆蓝图 - blueprint_library = world.get_blueprint_library() - vehicle_bp = blueprint_library.filter(SimConfig.VEHICLE_MODEL)[0] - vehicle_bp.set_attribute("role_name", SimConfig.VEHICLE_ROLE_NAME) - - # 生成车辆 - print("[生成车辆] 尝试生成车辆...") - vehicle = world.try_spawn_actor(vehicle_bp, spawn_point) - - # 关键修改:添加重试逻辑,第一个点失败则尝试第二个地图默认点 - if vehicle is None: - print("[生成车辆] 第一个点生成失败,尝试第二个地图默认点...") - spawn_point = map_spawn_points[1] - vehicle = world.try_spawn_actor(vehicle_bp, spawn_point) - if vehicle is None: - raise RuntimeError(f"[生成车辆] 生成失败,请检查坐标是否合法!") - - print(f"[生成车辆] 成功生成:{vehicle.type_id} (ID: {vehicle.id})") - - # 4. 设置旁观者相机视角 - set_spectator_fixed_view(world) - - # 5. 初始化可视化组件 - print("\n[可视化] 初始化绘图器(Plotter)...") - plotter = Plotter() - plotter.init_plot() - print("[可视化] 绘图器初始化完成") - - print("[可视化] 初始化Pygame显示窗口...") - pygame_display = PygameDisplay(world, vehicle) - print("[可视化] Pygame显示窗口初始化完成") - - # 6. 记录仿真开始时间 - simulation_start_time = time.time() - target_waypoint_id = SimConfig.INITIAL_TARGET_WAYPOINT_ID - - # 7. 主仿真循环 - print("\n[仿真启动] 开始航点跟随与速度控制。按ESC或关闭Pygame窗口停止仿真...") - while True: - current_loop_time = time.time() - - # 处理Pygame事件(关闭窗口、ESC键) - if pygame_display.parse_events(): - print("[仿真控制] 检测到Pygame退出请求,停止仿真...") - break - - # 等待仿真tick(同步仿真时间) - world.wait_for_tick() - - # 8. 收集车辆状态数据 - vehicle_location = get_vehicle_location(vehicle) - vehicle_speed = calculate_vehicle_speed_kmh(vehicle) - sim_elapsed_time = current_loop_time - simulation_start_time - - # 9. 更新可视化 - # 更新绘图器 - if plotter and plotter.is_initialized: - current_desired_speed = SimConfig.WAYPOINTS[target_waypoint_id][2] - try: - plotter.update_plot(sim_elapsed_time, vehicle_location.x, vehicle_speed, current_desired_speed) - except Exception as e: - print(f"[绘图器] 更新失败(可能已关闭窗口):{e}") - plotter.cleanup_plot() - plotter = None - - # 渲染Pygame窗口 - pygame_display.render() - - # 10. 航点跟随逻辑 - # 更新目标航点 - target_waypoint_id = update_target_waypoint( - vehicle_location, - target_waypoint_id, - SimConfig.WAYPOINTS, - SimConfig.WAYPOINT_THRESHOLD - ) - - # 获取当前目标航点的期望速度 - current_desired_speed = SimConfig.WAYPOINTS[target_waypoint_id][2] if 0 <= target_waypoint_id < len(SimConfig.WAYPOINTS) else 0.0 - - # 计算车辆控制指令 - control = simple_speed_controller(current_desired_speed, vehicle_speed) - - # 应用控制指令 - vehicle.apply_control(control) - - # 异常处理 - except KeyboardInterrupt: - print("\n[仿真中断] 用户按下Ctrl+C,停止仿真...") - except RuntimeError as e: - print(f"\n[仿真错误] 运行时错误:{e}") - traceback.print_exc() - except Exception as e: - print(f"\n[仿真错误] 未知异常:{e}") - traceback.print_exc() - - # 资源清理 - finally: - print("\n[资源清理] 开始清理仿真资源...") - - # 清理Pygame显示窗口 - if pygame_display: - print("[资源清理] 销毁Pygame显示窗口...") - pygame_display.destroy() - - # 清理绘图器 - if plotter and plotter.is_initialized: - print("[资源清理] 清理绘图器...") - plotter.cleanup_plot() - - # 销毁车辆 - if vehicle and vehicle.is_alive: - print(f"[资源清理] 销毁车辆:{vehicle.type_id} (ID: {vehicle.id})") - if vehicle.destroy(): - print("[资源清理] 车辆销毁成功") - else: - print("[资源清理] 车辆销毁失败") - - print("[资源清理] 仿真资源清理完成") - -if __name__ == "__main__": - main() \ No newline at end of file From 9768bf171088cb8dc1faf8605835424a4c93e577 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Fri, 19 Dec 2025 17:14:36 +0800 Subject: [PATCH 07/26] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=87=AA=E4=B8=BB?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E4=B8=8E=E8=B7=AF=E5=BE=84=E8=B7=9F=E9=9A=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/MCP/v2.py | 247 ++++++++++++++++++--------- 1 file changed, 167 insertions(+), 80 deletions(-) diff --git a/src/autonomous_driving_car/MCP/v2.py b/src/autonomous_driving_car/MCP/v2.py index 4e0a889bc9..174d1de37d 100644 --- a/src/autonomous_driving_car/MCP/v2.py +++ b/src/autonomous_driving_car/MCP/v2.py @@ -1,39 +1,37 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -CARLA Vehicle Simulation with Visualization (sd_3/__main__.py) +CARLA Waypoint Following with Simple Speed Control & Pygame Display (sd_4/__main__.py) 核心功能: -1. 连接CARLA仿真服务器,清理历史车辆 Actor -2. 生成特斯拉Model3车辆,施加恒定油门控制 -3. 基于PygameDisplay显示车辆挂载摄像头的实时画面 -4. 基于Plotter绘制车辆X坐标和速度随时间的变化曲线 -5. 设置固定的旁观者相机视角,方便观察仿真过程 +1. 连接CARLA仿真服务器,清理历史车辆Actor +2. 生成特斯拉Model3车辆,基于预定义航点实现路径跟随 +3. 采用简易的bang-bang速度控制器,根据当前速度与目标速度调整油门/刹车 +4. 基于PygameDisplay显示车辆挂载摄像头的实时画面 +5. 基于Plotter绘制车辆X坐标、实际速度与期望速度的时间曲线 +6. 设置固定的旁观者相机视角,方便观察仿真过程 依赖: -- tools.pygame_display.PygameDisplay:Pygame窗口显示摄像头画面 - tools.plotter_x.Plotter:Matplotlib绘制X坐标/速度曲线 +- tools.pygame_display.PygameDisplay:Pygame窗口显示摄像头画面 """ -# 加入这几行代码,解决模块导入问题 +# ===================== 路径处理(解决tools模块导入问题)===================== import sys import os - -# 获取项目根目录(carla-python-examples) PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -# 将项目根目录加入Python搜索路径 sys.path.append(PROJECT_ROOT) +# ============================================================================== + import carla import numpy as np import time -import pygame -import math import traceback -from typing import Optional, Tuple +from typing import Optional, List, Tuple # 导入自定义工具类 -from tools.pygame_display import PygameDisplay from tools.plotter_x import Plotter +from tools.pygame_display import PygameDisplay # ===================== 配置集中化(便于修改和维护)===================== class SimConfig: @@ -44,20 +42,31 @@ class SimConfig: CARLA_TIMEOUT = 10.0 # 连接超时时间(秒) # 车辆配置 - VEHICLE_MODEL = "vehicle.tesla.model3" # 车辆模型 - VEHICLE_ROLE_NAME = "my_car" # 车辆角色名(用于清理历史车辆) - CONSTANT_THROTTLE = 0.8 # 恒定油门值(0-1) - STEER_ANGLE = 0.0 # 恒定转向角(0=直线行驶) - BRAKE_VALUE = 0.0 # 刹车值(0=不刹车) - - # 旁观者相机固定视角(可根据地图调整) + VEHICLE_MODEL = "vehicle.tesla.model3" + VEHICLE_ROLE_NAME = "my_car" + + # 航点配置:[x坐标, y坐标, 期望速度(km/h)] + WAYPOINTS: List[List[float]] = [ + [-64.0, 24.5, 40.0], # 起始点(第一段路径的参考点) + [40.0, 24.5, 40.0], # 制动点前的行驶点(保持40km/h) + [70.0, 24.5, 0.0] # 停止点(目标速度0km/h) + ] + WAYPOINT_THRESHOLD = 5.0 # 切换航点的距离阈值(米) + INITIAL_TARGET_WAYPOINT_ID = 1 # 初始目标航点索引(从1开始,0为起始点) + + # 旁观者相机固定视角 SPECTATOR_LOCATION = carla.Location(x=63.12, y=29.88, z=5.61) SPECTATOR_ROTATION = carla.Rotation(pitch=-4.27, yaw=-170.21, roll=0.00) - # 绘图配置 - DESIRED_SPEED = 0.0 # 期望速度(若无速度控制,设为0) - # ===================== 车辆状态计算工具函数 ===================== +def get_vehicle_location(vehicle: carla.Vehicle) -> carla.Location: + """ + 获取车辆当前位置 + :param vehicle: CARLA车辆Actor对象 + :return: 车辆的Location对象 + """ + return vehicle.get_location() + def calculate_vehicle_speed_kmh(vehicle: carla.Vehicle) -> float: """ 计算车辆当前速度(km/h) @@ -65,18 +74,9 @@ def calculate_vehicle_speed_kmh(vehicle: carla.Vehicle) -> float: :return: 车辆速度(km/h) """ velocity = vehicle.get_velocity() - # 计算速度矢量的模(m/s),转换为km/h(×3.6) speed_mps = np.linalg.norm([velocity.x, velocity.y, velocity.z]) return speed_mps * 3.6 -def get_vehicle_location(vehicle: carla.Vehicle) -> carla.Location: - """ - 获取车辆当前位置 - :param vehicle: CARLA车辆Actor对象 - :return: 车辆的Location对象 - """ - return vehicle.get_location() - # ===================== 仿真资源管理工具函数 ===================== def clean_up_prev_vehicles(world: carla.World, role_name: str) -> int: """ @@ -100,35 +100,98 @@ def clean_up_prev_vehicles(world: carla.World, role_name: str) -> int: print(f"[资源清理] 共销毁{destroyed_count}辆历史车辆") return destroyed_count -def set_spectator_fixed_view(world: carla.World, location: carla.Location, rotation: carla.Rotation) -> bool: +def set_spectator_fixed_view(world: carla.World) -> bool: """ 设置旁观者相机的固定视角 :param world: CARLA的World对象 - :param location: 旁观者相机位置 - :param rotation: 旁观者相机旋转角度 :return: 是否设置成功 """ try: spectator = world.get_spectator() - spectator_transform = carla.Transform(location, rotation) + spectator_transform = carla.Transform( + SimConfig.SPECTATOR_LOCATION, + SimConfig.SPECTATOR_ROTATION + ) spectator.set_transform(spectator_transform) - print(f"\n[视角设置] 旁观者相机位置:({location.x:.2f}, {location.y:.2f}, {location.z:.2f})") - print(f"[视角设置] 旁观者相机旋转:(俯仰:{rotation.pitch:.2f}, 偏航:{rotation.yaw:.2f}, 翻滚:{rotation.roll:.2f})") + print(f"\n[视角设置] 旁观者相机位置:({SimConfig.SPECTATOR_LOCATION.x:.2f}, {SimConfig.SPECTATOR_LOCATION.y:.2f}, {SimConfig.SPECTATOR_LOCATION.z:.2f})") + print(f"[视角设置] 旁观者相机旋转:(俯仰:{SimConfig.SPECTATOR_ROTATION.pitch:.2f}, 偏航:{SimConfig.SPECTATOR_ROTATION.yaw:.2f}, 翻滚:{SimConfig.SPECTATOR_ROTATION.roll:.2f})") return True except Exception as e: print(f"[视角设置] 失败:{e}") return False +# ===================== 航点管理函数 ===================== +def update_target_waypoint( + vehicle_location: carla.Location, + current_target_id: int, + waypoints: List[List[float]], + threshold: float +) -> int: + """ + 检查是否到达当前目标航点,若到达则切换到下一个航点 + :param vehicle_location: 车辆当前位置 + :param current_target_id: 当前目标航点索引 + :param waypoints: 航点列表 + :param threshold: 切换航点的距离阈值 + :return: 更新后的目标航点索引 + """ + # 若已是最后一个航点,不再切换 + if current_target_id >= len(waypoints) - 1: + return current_target_id + + # 获取当前目标航点的位置 + target_wp_data = waypoints[current_target_id] + target_loc = carla.Location(x=target_wp_data[0], y=target_wp_data[1]) + + # 关键修改:手动计算2D距离(替代CARLA低版本不存在的distance_2d方法) + dx = vehicle_location.x - target_loc.x + dy = vehicle_location.y - target_loc.y + distance = np.sqrt(dx ** 2 + dy ** 2) + + # 若距离小于阈值,切换到下一个航点 + if distance < threshold: + print(f"[航点更新] 到达航点{current_target_id}(距离:{distance:.1f}m),新目标航点:{current_target_id + 1}") + return current_target_id + 1 + + return current_target_id + +# ===================== 速度控制器 ===================== +def simple_speed_controller(v_desired: float, v_current: float) -> carla.VehicleControl: + """ + 简易的3状态速度控制器(bang-bang控制) + 根据当前速度与期望速度的差值,全量施加油门或刹车 + :param v_desired: 期望速度(km/h) + :param v_current: 当前速度(km/h) + :return: CARLA车辆控制指令 + """ + control = carla.VehicleControl() + control.steer = 0.0 # 暂不控制转向 + control.throttle = 0.0 + control.brake = 0.0 + + # 目标速度为0时,全力刹车 + if v_desired == 0: + control.brake = 1.0 + # 当前速度小于期望速度,全力加速 + elif v_current < v_desired: + control.throttle = 1.0 + # 当前速度大于期望速度,全力刹车 + elif v_current > v_desired: + control.brake = 1.0 + + return control + # ===================== 主仿真函数 ===================== def main(): """主仿真入口函数:完成所有仿真流程的初始化、运行和清理""" - # 初始化核心变量(所有资源对象初始化为None,便于后续清理) + # 初始化核心变量 client: Optional[carla.Client] = None world: Optional[carla.World] = None vehicle: Optional[carla.Vehicle] = None - pygame_display: Optional[PygameDisplay] = None plotter: Optional[Plotter] = None + pygame_display: Optional[PygameDisplay] = None simulation_start_time: Optional[float] = None + target_waypoint_id: int = SimConfig.INITIAL_TARGET_WAYPOINT_ID try: # 1. 连接CARLA服务器 @@ -141,29 +204,45 @@ def main(): # 2. 清理历史车辆 clean_up_prev_vehicles(world, SimConfig.VEHICLE_ROLE_NAME) - # 3. 获取车辆生成点 - spawn_points = world.get_map().get_spawn_points() - if not spawn_points: + # 3. 生成车辆(基于第一个航点作为起始位置) + map_spawn_points = world.get_map().get_spawn_points() + if not map_spawn_points: raise RuntimeError("[生成车辆] 地图中未找到可用的生成点!") - spawn_point = spawn_points[0] - print(f"[生成车辆] 选择生成点:({spawn_point.location.x:.2f}, {spawn_point.location.y:.2f}, {spawn_point.location.z:.2f})") - # 4. 加载车辆蓝图并设置属性 + # 获取起始航点的位置 + start_waypoint = SimConfig.WAYPOINTS[0] + # 关键修改:将z轴高度从0.5提高到1.5,避免与地面碰撞 + start_location = carla.Location(x=start_waypoint[0], y=start_waypoint[1], z=1.5) + + # 找到距离起始位置最近的地图生成点(用于获取道路朝向) + spawn_point = min(map_spawn_points, key=lambda sp: sp.location.distance(start_location)) + spawn_point.location = start_location # 覆盖为自定义起始位置 + + print(f"[生成车辆] 目标生成位置:{start_location},使用地图朝向:{spawn_point.rotation}") + + # 加载车辆蓝图 blueprint_library = world.get_blueprint_library() vehicle_bp = blueprint_library.filter(SimConfig.VEHICLE_MODEL)[0] vehicle_bp.set_attribute("role_name", SimConfig.VEHICLE_ROLE_NAME) - print(f"[生成车辆] 加载车辆蓝图:{vehicle_bp.id}") - # 5. 生成车辆 + # 生成车辆 + print("[生成车辆] 尝试生成车辆...") vehicle = world.try_spawn_actor(vehicle_bp, spawn_point) + + # 关键修改:添加重试逻辑,第一个点失败则尝试第二个地图默认点 if vehicle is None: - raise RuntimeError(f"[生成车辆] 在生成点{spawn_point.location}生成车辆失败!") + print("[生成车辆] 第一个点生成失败,尝试第二个地图默认点...") + spawn_point = map_spawn_points[1] + vehicle = world.try_spawn_actor(vehicle_bp, spawn_point) + if vehicle is None: + raise RuntimeError(f"[生成车辆] 生成失败,请检查坐标是否合法!") + print(f"[生成车辆] 成功生成:{vehicle.type_id} (ID: {vehicle.id})") - # 6. 设置旁观者相机视角 - set_spectator_fixed_view(world, SimConfig.SPECTATOR_LOCATION, SimConfig.SPECTATOR_ROTATION) + # 4. 设置旁观者相机视角 + set_spectator_fixed_view(world) - # 7. 初始化可视化组件 + # 5. 初始化可视化组件 print("\n[可视化] 初始化绘图器(Plotter)...") plotter = Plotter() plotter.init_plot() @@ -173,51 +252,59 @@ def main(): pygame_display = PygameDisplay(world, vehicle) print("[可视化] Pygame显示窗口初始化完成") - # 8. 记录仿真开始时间 + # 6. 记录仿真开始时间 simulation_start_time = time.time() - print(f"\n[仿真启动] 开始运行仿真(恒定油门:{SimConfig.CONSTANT_THROTTLE})") - print("[仿真启动] 按ESC或关闭Pygame窗口停止仿真...") + target_waypoint_id = SimConfig.INITIAL_TARGET_WAYPOINT_ID - # 9. 主仿真循环 + # 7. 主仿真循环 + print("\n[仿真启动] 开始航点跟随与速度控制。按ESC或关闭Pygame窗口停止仿真...") while True: - # 9.1 处理Pygame事件(关闭窗口、ESC键) + current_loop_time = time.time() + + # 处理Pygame事件(关闭窗口、ESC键) if pygame_display.parse_events(): print("[仿真控制] 检测到Pygame退出请求,停止仿真...") break - # 9.2 等待仿真tick(同步仿真时间) + # 等待仿真tick(同步仿真时间) world.wait_for_tick() - # 9.3 收集车辆状态数据 - current_time = time.time() + # 8. 收集车辆状态数据 vehicle_location = get_vehicle_location(vehicle) vehicle_speed = calculate_vehicle_speed_kmh(vehicle) - sim_elapsed_time = current_time - simulation_start_time # 仿真已运行时间(秒) - - # 9.4 渲染Pygame窗口(摄像头画面) - pygame_display.render() + sim_elapsed_time = current_loop_time - simulation_start_time - # 9.5 更新绘图器(X坐标、速度曲线) + # 9. 更新可视化 + # 更新绘图器 if plotter and plotter.is_initialized: + current_desired_speed = SimConfig.WAYPOINTS[target_waypoint_id][2] try: - plotter.update_plot( - sim_elapsed_time, - vehicle_location.x, - vehicle_speed, - SimConfig.DESIRED_SPEED - ) + plotter.update_plot(sim_elapsed_time, vehicle_location.x, vehicle_speed, current_desired_speed) except Exception as e: print(f"[绘图器] 更新失败(可能已关闭窗口):{e}") plotter.cleanup_plot() plotter = None - # 9.6 车辆控制:施加恒定油门 - vehicle_control = carla.VehicleControl( - throttle=SimConfig.CONSTANT_THROTTLE, - steer=SimConfig.STEER_ANGLE, - brake=SimConfig.BRAKE_VALUE + # 渲染Pygame窗口 + pygame_display.render() + + # 10. 航点跟随逻辑 + # 更新目标航点 + target_waypoint_id = update_target_waypoint( + vehicle_location, + target_waypoint_id, + SimConfig.WAYPOINTS, + SimConfig.WAYPOINT_THRESHOLD ) - vehicle.apply_control(vehicle_control) + + # 获取当前目标航点的期望速度 + current_desired_speed = SimConfig.WAYPOINTS[target_waypoint_id][2] if 0 <= target_waypoint_id < len(SimConfig.WAYPOINTS) else 0.0 + + # 计算车辆控制指令 + control = simple_speed_controller(current_desired_speed, vehicle_speed) + + # 应用控制指令 + vehicle.apply_control(control) # 异常处理 except KeyboardInterrupt: @@ -229,7 +316,7 @@ def main(): print(f"\n[仿真错误] 未知异常:{e}") traceback.print_exc() - # 资源清理(无论是否异常,都执行) + # 资源清理 finally: print("\n[资源清理] 开始清理仿真资源...") From e4c440b21e54ce30f5e68dfdb0ce44c20cd1dea0 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Fri, 19 Dec 2025 20:50:08 +0800 Subject: [PATCH 08/26] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=BC=AF=E9=81=93?= =?UTF-8?q?=E8=87=AA=E9=80=82=E5=BA=94=E8=BD=AC=E5=90=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/CAS/v4.py | 338 +++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 src/autonomous_driving_car/CAS/v4.py diff --git a/src/autonomous_driving_car/CAS/v4.py b/src/autonomous_driving_car/CAS/v4.py new file mode 100644 index 0000000000..3fbfcf65c4 --- /dev/null +++ b/src/autonomous_driving_car/CAS/v4.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +CARLA 弯道自适应转向:根据弯道大小动态调整转弯幅度 +""" + +import sys +import os +import carla +import numpy as np +import math +import pygame +import traceback + +# ===================== 全局配置(动态参数)===================== +# CARLA连接 +CARLA_HOST = "localhost" +CARLA_PORT = 2000 +CARLA_TIMEOUT = 10.0 + +# 车辆配置 +VEHICLE_MODEL = "vehicle.tesla.model3" +VEHICLE_WHEELBASE = 2.9 # 特斯拉Model3轴距(米) +VEHICLE_REAR_AXLE_OFFSET = 1.45 # 后轴偏移(正数) + +# 转向控制(动态参数基准) +LOOKAHEAD_DIST_STRAIGHT = 7.0 # 直道预瞄距离 +LOOKAHEAD_DIST_CURVE = 4.0 # 急弯预瞄距离 +STEER_GAIN_STRAIGHT = 0.7 # 直道转向增益 +STEER_GAIN_CURVE = 1.0 # 急弯转向增益 +STEER_DEADZONE = 0.05 # 转向死区 +STEER_LOWPASS_ALPHA = 0.6 # 低通滤波系数 +STEER_DIR_COEFF = 1.0 # 转向方向校准 +MAX_STEER = 1.0 # 最大转向角 + +# 弯道等级划分(方向变化量阈值) +DIR_CHANGE_GENTLE = 0.03 # 缓弯阈值 +DIR_CHANGE_SHARP = 0.08 # 急弯阈值 + +# 速度控制 +BASE_SPEED = 28.0 # 直道基础速度 +CURVE_SPEED_FACTOR = 0.6 # 弯道速度系数(更明显的速度衰减) +PID_KP = 0.15 +PID_KI = 0.01 +PID_KD = 0.01 + +# 相机 +CAMERA_POS = carla.Transform(carla.Location(x=-5.0, z=2.0)) +CAMERA_WIDTH = 800 +CAMERA_HEIGHT = 600 +CAMERA_FOV = 90 + +# ===================== 纯追踪控制器(动态适配)===================== +class AdaptivePurePursuit: + def __init__(self, wheelbase): + self.wheelbase = wheelbase + self.last_steer = 0.0 # 上一帧转向角 + self.last_lookahead = LOOKAHEAD_DIST_STRAIGHT # 上一帧预瞄距离 + + def calculate_steer(self, vehicle_transform, target_point, dir_change): + """ + 自适应纯追踪计算(根据弯道大小调整参数) + :param vehicle_transform: 车辆变换 + :param target_point: 目标点(carla.Location) + :param dir_change: 方向变化量(弯道大小) + :return: 自适应后的转向角 + """ + # 1. 获取车辆后轴位置 + forward_vec = vehicle_transform.get_forward_vector() + rear_axle_loc = carla.Location( + x=vehicle_transform.location.x - forward_vec.x * VEHICLE_REAR_AXLE_OFFSET, + y=vehicle_transform.location.y - forward_vec.y * VEHICLE_REAR_AXLE_OFFSET, + z=vehicle_transform.location.z + ) + + # 2. 转换到车辆坐标系 + dx = target_point.x - rear_axle_loc.x + dy = target_point.y - rear_axle_loc.y + yaw = math.radians(vehicle_transform.rotation.yaw) + + dx_vehicle = dx * math.cos(yaw) + dy * math.sin(yaw) + dy_vehicle = -dx * math.sin(yaw) + dy * math.cos(yaw) + + # 3. 动态计算转向增益(根据弯道大小线性插值) + # 方向变化量越大,增益越高 + steer_gain = np.interp( + dir_change, + [0, DIR_CHANGE_SHARP], + [STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE] + ) + steer_gain = np.clip(steer_gain, STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE) + + # 4. 纯追踪核心计算 + if dx_vehicle < 0.1: + steer = self.last_steer + else: + # 纯追踪公式 + 弯道大小系数(dir_change*2 放大差异) + steer_rad = math.atan2(2 * self.wheelbase * dy_vehicle * (1 + dir_change * 2), dx_vehicle ** 2 + dy_vehicle ** 2) + steer = steer_rad / math.pi + + # 应用增益和方向校准 + steer *= steer_gain * STEER_DIR_COEFF + + # 5. 应用死区 + if abs(steer) < STEER_DEADZONE: + steer = 0.0 + + # 6. 低通滤波(平滑) + steer = STEER_LOWPASS_ALPHA * steer + (1 - STEER_LOWPASS_ALPHA) * self.last_steer + + # 7. 限制范围 + steer = np.clip(steer, -MAX_STEER, MAX_STEER) + + # 8. 更新状态 + self.last_steer = steer + + return steer + + def get_adaptive_lookahead(self, dir_change): + """ + 动态获取预瞄距离(根据弯道大小线性插值) + :param dir_change: 方向变化量 + :return: 自适应预瞄距离 + """ + lookahead_dist = np.interp( + dir_change, + [0, DIR_CHANGE_SHARP], + [LOOKAHEAD_DIST_STRAIGHT, LOOKAHEAD_DIST_CURVE] + ) + lookahead_dist = np.clip(lookahead_dist, LOOKAHEAD_DIST_CURVE, LOOKAHEAD_DIST_STRAIGHT) + self.last_lookahead = lookahead_dist + return lookahead_dist + +# ===================== 速度控制器(动态调整)===================== +class SpeedController: + def __init__(self, kp, ki, kd): + self.kp = kp + self.ki = ki + self.kd = kd + self.last_error = 0.0 + self.integral = 0.0 + + def calculate(self, target_speed, current_speed): + """PID速度控制""" + error = target_speed - current_speed + + p = self.kp * error + self.integral += self.ki * error + self.integral = np.clip(self.integral, -1.0, 1.0) + i = self.integral + d = self.kd * (error - self.last_error) + self.last_error = error + + output = p + i + d + return np.clip(output, 0.0, 1.0) + +# ===================== 辅助函数:计算方向变化(替代曲率)===================== +def calculate_dir_change(current_wp): + """ + 计算相邻Waypoint的方向变化量(更精细的计算,增加采样点) + :param current_wp: 当前Waypoint + :return: 方向变化的绝对值之和,弯道等级(0=直道,1=缓弯,2=急弯) + """ + waypoints = [current_wp] + # 增加采样点到5个,更准确的判断弯道大小 + for i in range(5): + next_wps = waypoints[-1].next(1.0) + if next_wps: + waypoints.append(next_wps[0]) + else: + break + + if len(waypoints) < 4: + return 0.0, 0 + + # 计算每个相邻点的方向 + dirs = [] + for i in range(1, len(waypoints)): + wp_prev = waypoints[i-1] + wp_curr = waypoints[i] + dir_rad = math.atan2( + wp_curr.transform.location.y - wp_prev.transform.location.y, + wp_curr.transform.location.x - wp_prev.transform.location.x + ) + dirs.append(dir_rad) + + # 计算方向变化的绝对值之和(放大差异) + dir_change = 0.0 + for i in range(1, len(dirs)): + dir_change += abs(dirs[i] - dirs[i-1]) * 2 # 放大差异 + + # 划分弯道等级 + if dir_change < DIR_CHANGE_GENTLE: + curve_level = 0 # 直道 + elif dir_change < DIR_CHANGE_SHARP: + curve_level = 1 # 缓弯 + else: + curve_level = 2 # 急弯 + + return dir_change, curve_level + +# ===================== 相机管理器 ===================== +class CameraManager: + def __init__(self, world, vehicle, display): + self.world = world + self.vehicle = vehicle + self.display = display + self.camera = None + self._create_camera() + + def _create_camera(self): + bp = self.world.get_blueprint_library().find("sensor.camera.rgb") + bp.set_attribute("image_size_x", str(CAMERA_WIDTH)) + bp.set_attribute("image_size_y", str(CAMERA_HEIGHT)) + bp.set_attribute("fov", str(CAMERA_FOV)) + self.camera = self.world.spawn_actor(bp, CAMERA_POS, attach_to=self.vehicle) + self.camera.listen(self._on_image) + + def _on_image(self, image): + array = np.frombuffer(image.raw_data, dtype=np.uint8) + array = array.reshape((CAMERA_HEIGHT, CAMERA_WIDTH, 4))[:, :, :3] + array = array[:, :, ::-1].swapaxes(0, 1) + self.display.blit(pygame.surfarray.make_surface(array), (0, 0)) + pygame.display.flip() + + def destroy(self): + if self.camera: + self.camera.stop() + self.camera.destroy() + +# ===================== 主函数(核心逻辑)===================== +def main(): + pygame.init() + display = pygame.display.set_mode((CAMERA_WIDTH, CAMERA_HEIGHT)) + pygame.display.set_caption("CARLA 弯道自适应转向(最终版)") + + client = None + world = None + vehicle = None + camera_manager = None + pp_controller = None + speed_controller = None + + try: + # 1. 连接CARLA并初始化 + client = carla.Client(CARLA_HOST, CARLA_PORT) + client.set_timeout(CARLA_TIMEOUT) + world = client.get_world() + map = world.get_map() + + # 清理现有车辆 + for actor in world.get_actors().filter("vehicle.*"): + actor.destroy() + + # 生成车辆 + vehicle_bp = world.get_blueprint_library().find(VEHICLE_MODEL) + spawn_points = map.get_spawn_points() + vehicle = world.spawn_actor(vehicle_bp, spawn_points[0]) + print(f"车辆生成成功:{vehicle.type_id}") + + # 初始化控制器 + pp_controller = AdaptivePurePursuit(VEHICLE_WHEELBASE) + speed_controller = SpeedController(PID_KP, PID_KI, PID_KD) + camera_manager = CameraManager(world, vehicle, display) + + # 2. 主循环 + print("仿真启动,按ESC退出...") + clock = pygame.time.Clock() + running = True + + while running: + # 事件处理 + for event in pygame.event.get(): + if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): + running = False + + # 3. 获取关键数据 + vehicle_transform = vehicle.get_transform() + vehicle_vel = vehicle.get_velocity() + current_speed = math.hypot(vehicle_vel.x, vehicle_vel.y) * 3.6 # m/s → km/h + + # 获取当前Waypoint + current_wp = map.get_waypoint(vehicle_transform.location, project_to_road=True) + + # 计算方向变化量和弯道等级 + dir_change, curve_level = calculate_dir_change(current_wp) + + # 动态获取预瞄距离 + lookahead_dist = pp_controller.get_adaptive_lookahead(dir_change) + + # 获取目标Waypoint(动态预瞄距离) + target_wps = current_wp.next(lookahead_dist) + target_point = target_wps[0].transform.location if target_wps else vehicle_transform.location + + # 4. 计算目标速度(根据弯道等级调整) + curve_speed_factors = [1.0, 0.7, 0.4] # 直道、缓弯、急弯的速度系数 + speed_factor = curve_speed_factors[min(curve_level, 2)] + target_speed = BASE_SPEED * speed_factor + target_speed = max(10.0, target_speed) + + # 5. 计算转向角(传入方向变化量,实现自适应) + steer = pp_controller.calculate_steer(vehicle_transform, target_point, dir_change) + + # 6. 计算油门/刹车 + throttle = speed_controller.calculate(target_speed, current_speed) + brake = 1.0 - throttle if current_speed > target_speed + 5 else 0.0 + + # 7. 应用控制 + control = carla.VehicleControl() + control.steer = steer + control.throttle = throttle + control.brake = brake + control.hand_brake = False + vehicle.apply_control(control) + + # 8. 打印状态(调试用,显示弯道等级和动态参数) + curve_names = ["直道", "缓弯", "急弯"] + print(f"速度:{current_speed:.1f}km/h | 目标速度:{target_speed:.1f}km/h | 弯道:{curve_names[curve_level]} | 预瞄:{lookahead_dist:.1f}m | 转向:{steer:.3f}") + + # 控制帧率 + clock.tick(30) + + except Exception as e: + print(f"错误:{e}") + traceback.print_exc() + + finally: + # 清理资源 + print("清理资源...") + if camera_manager: + camera_manager.destroy() + if vehicle: + vehicle.destroy() + pygame.quit() + print("仿真结束") + +if __name__ == "__main__": + main() \ No newline at end of file From 85e2c10d83d883b0c7e55c1537e46b65fd46d020 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Fri, 19 Dec 2025 21:14:28 +0800 Subject: [PATCH 09/26] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=BC=AF=E9=81=93?= =?UTF-8?q?=E8=87=AA=E9=80=82=E5=BA=94=E8=BD=AC=E5=90=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/main.py | 668 ++++++++++++++--------------- 1 file changed, 319 insertions(+), 349 deletions(-) diff --git a/src/autonomous_driving_car/main.py b/src/autonomous_driving_car/main.py index 10a4076619..3fbfcf65c4 100644 --- a/src/autonomous_driving_car/main.py +++ b/src/autonomous_driving_car/main.py @@ -1,368 +1,338 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- """ -CARLA Basic Vehicle Spawn and Spectator Setup (sd_1/__main__.py) -完全适配 CARLA 0.9.15 版本(无任何天气预设依赖) - -This script connects to a CARLA simulator instance, removes any -pre-existing vehicles with the role 'my_car', spawns a new -Tesla Model 3 at a default spawn point, and positions the -spectator camera behind the newly spawned vehicle. -新增功能:定时循环切换CARLA模拟器的天气(晴天、多云、雨天、雾天、日落等) - -The script keeps the simulation running until interrupted (Ctrl+C), -but the vehicle does not move. +CARLA 弯道自适应转向:根据弯道大小动态调整转弯幅度 """ -# 导入CARLA模拟器的Python API +import sys +import os import carla -# 导入时间模块,用于延时和计时 -import time - - -def remove_previous_vehicle(world: carla.World) -> None: - """ - 查找并销毁所有角色名为'my_car'的车辆Actor,避免重复生成导致冲突 - - Args: - world (carla.World): CARLA模拟器的世界对象,用于获取当前所有Actor - """ - print("Searching for previous 'my_car' vehicles...") - # 过滤出所有车辆类型的Actor(vehicle.* 匹配所有车辆蓝图) - actors = world.get_actors().filter('vehicle.*') - # 记录成功销毁的车辆数量 - removed_count = 0 - - for actor in actors: - # 检查Actor的角色名是否为'my_car' - if actor.attributes.get('role_name') == 'my_car': - print(f" - Removing previous vehicle: {actor.type_id} (ID {actor.id})") - # 销毁Actor,返回布尔值表示是否成功 - if actor.destroy(): - removed_count += 1 - else: - print(f" - Failed to remove vehicle {actor.id}") - - print(f"Removed {removed_count} previous vehicles.") - - -def set_spectator_behind_vehicle(world: carla.World, vehicle: carla.Vehicle) -> None: - """ - 将旁观者相机(spectator)定位到指定车辆的后上方,实现跟随视角 - - Args: - world (carla.World): CARLA模拟器的世界对象 - vehicle (carla.Vehicle): 目标车辆Actor,用于获取车辆的位置和姿态 - """ - # 获取旁观者相机Actor(CARLA中全局唯一的 spectator) - spectator = world.get_spectator() - # 获取车辆的当前位姿(位置+旋转) - vehicle_transform = vehicle.get_transform() - # 获取车辆的前向向量,用于计算相机的相对偏移(保证相机始终在车辆后方) - forward_vector = vehicle_transform.get_forward_vector() - - # 计算相机偏移:向后15米,向上6米(基于车辆的前向向量,保证方向正确) - camera_offset = carla.Location( - x=-15 * forward_vector.x, - y=-15 * forward_vector.y, - z=6 - ) - # 构建旁观者相机的位姿: - # 位置 = 车辆位置 + 偏移量 - # 旋转 = 俯仰角-20°(向下看),偏航角与车辆一致,滚转角为0 - spectator_transform = carla.Transform( - vehicle_transform.location + camera_offset, - carla.Rotation( - pitch=-20, # 俯仰角,负数表示向下看 - yaw=vehicle_transform.rotation.yaw, # 偏航角与车辆一致 - roll=0 # 滚转角,保持水平 +import numpy as np +import math +import pygame +import traceback + +# ===================== 全局配置(动态参数)===================== +# CARLA连接 +CARLA_HOST = "localhost" +CARLA_PORT = 2000 +CARLA_TIMEOUT = 10.0 + +# 车辆配置 +VEHICLE_MODEL = "vehicle.tesla.model3" +VEHICLE_WHEELBASE = 2.9 # 特斯拉Model3轴距(米) +VEHICLE_REAR_AXLE_OFFSET = 1.45 # 后轴偏移(正数) + +# 转向控制(动态参数基准) +LOOKAHEAD_DIST_STRAIGHT = 7.0 # 直道预瞄距离 +LOOKAHEAD_DIST_CURVE = 4.0 # 急弯预瞄距离 +STEER_GAIN_STRAIGHT = 0.7 # 直道转向增益 +STEER_GAIN_CURVE = 1.0 # 急弯转向增益 +STEER_DEADZONE = 0.05 # 转向死区 +STEER_LOWPASS_ALPHA = 0.6 # 低通滤波系数 +STEER_DIR_COEFF = 1.0 # 转向方向校准 +MAX_STEER = 1.0 # 最大转向角 + +# 弯道等级划分(方向变化量阈值) +DIR_CHANGE_GENTLE = 0.03 # 缓弯阈值 +DIR_CHANGE_SHARP = 0.08 # 急弯阈值 + +# 速度控制 +BASE_SPEED = 28.0 # 直道基础速度 +CURVE_SPEED_FACTOR = 0.6 # 弯道速度系数(更明显的速度衰减) +PID_KP = 0.15 +PID_KI = 0.01 +PID_KD = 0.01 + +# 相机 +CAMERA_POS = carla.Transform(carla.Location(x=-5.0, z=2.0)) +CAMERA_WIDTH = 800 +CAMERA_HEIGHT = 600 +CAMERA_FOV = 90 + +# ===================== 纯追踪控制器(动态适配)===================== +class AdaptivePurePursuit: + def __init__(self, wheelbase): + self.wheelbase = wheelbase + self.last_steer = 0.0 # 上一帧转向角 + self.last_lookahead = LOOKAHEAD_DIST_STRAIGHT # 上一帧预瞄距离 + + def calculate_steer(self, vehicle_transform, target_point, dir_change): + """ + 自适应纯追踪计算(根据弯道大小调整参数) + :param vehicle_transform: 车辆变换 + :param target_point: 目标点(carla.Location) + :param dir_change: 方向变化量(弯道大小) + :return: 自适应后的转向角 + """ + # 1. 获取车辆后轴位置 + forward_vec = vehicle_transform.get_forward_vector() + rear_axle_loc = carla.Location( + x=vehicle_transform.location.x - forward_vec.x * VEHICLE_REAR_AXLE_OFFSET, + y=vehicle_transform.location.y - forward_vec.y * VEHICLE_REAR_AXLE_OFFSET, + z=vehicle_transform.location.z ) - ) - - # 尝试设置相机位姿,增加异常处理提高鲁棒性 - try: - spectator.set_transform(spectator_transform) - print("Spectator camera positioned behind the vehicle.") - except Exception as e: - print(f"Error setting spectator transform: {e}") + # 2. 转换到车辆坐标系 + dx = target_point.x - rear_axle_loc.x + dy = target_point.y - rear_axle_loc.y + yaw = math.radians(vehicle_transform.rotation.yaw) -def get_weather_presets() -> list: - """ - 纯手动定义天气参数列表(完全不依赖CARLA预设,适配0.9.15) - 每个天气通过手动设置WeatherParameters的所有关键参数实现,确保兼容性 - - Returns: - list: 元组列表,每个元组包含(天气名称,carla.WeatherParameters对象) - """ - # 1. 晴天中午(太阳高悬、无云、无雨、无雾) - clear_noon = carla.WeatherParameters( - sun_altitude_angle=75.0, # 太阳高度角(75°=中午,天顶为90°) - sun_azimuth_angle=90.0, # 太阳方位角 - cloudiness=0.0, # 云量(0=无云) - precipitation=0.0, # 降水量(0=无雨) - precipitation_deposits=0.0, # 降水沉积(路面雨水) - wind_intensity=5.0, # 风力 - fog_density=0.0, # 雾密度(0=无雾) - fog_distance=0.0, # 雾的可见距离 - fog_falloff=1.0, # 雾的衰减率 - wetness=0.0, # 路面湿度 - scattering_intensity=0.0, # 光的散射强度 - mie_scattering_scale=0.0, # 米氏散射比例 - rayleigh_scattering_scale=0.0 # 瑞利散射比例 - ) - - # 2. 多云中午(高云量,阳光散射) - cloudy_noon = carla.WeatherParameters( - sun_altitude_angle=75.0, - sun_azimuth_angle=90.0, - cloudiness=80.0, # 云量80% - precipitation=0.0, - precipitation_deposits=0.0, - wind_intensity=10.0, - fog_density=0.0, - fog_distance=0.0, - fog_falloff=1.0, - wetness=0.0, - scattering_intensity=0.1, - mie_scattering_scale=0.1, - rayleigh_scattering_scale=0.1 - ) - - # 3. 小雨中午(少量降雨、路面微湿) - light_rain_noon = carla.WeatherParameters( - sun_altitude_angle=75.0, - sun_azimuth_angle=90.0, - cloudiness=90.0, # 云量90% - precipitation=20.0, # 降水量20%(小雨) - precipitation_deposits=5.0, # 路面雨水沉积5% - wind_intensity=15.0, - fog_density=5.0, # 轻微雾霭 - fog_distance=50.0, - fog_falloff=0.8, - wetness=0.2, # 路面湿度20% - scattering_intensity=0.2, - mie_scattering_scale=0.2, - rayleigh_scattering_scale=0.2 - ) - - # 4. 中雨中午(中等降雨、路面湿滑) - mid_rain_noon = carla.WeatherParameters( - sun_altitude_angle=75.0, - sun_azimuth_angle=90.0, - cloudiness=100.0, # 满云 - precipitation=50.0, # 降水量50%(中雨) - precipitation_deposits=20.0, # 路面雨水沉积20% - wind_intensity=20.0, - fog_density=15.0, # 雾密度15% - fog_distance=30.0, - fog_falloff=0.6, - wetness=0.5, # 路面湿度50% - scattering_intensity=0.3, - mie_scattering_scale=0.3, - rayleigh_scattering_scale=0.3 - ) - - # 5. 雾天中午(大雾、能见度低) - mist_noon = carla.WeatherParameters( - sun_altitude_angle=75.0, - sun_azimuth_angle=90.0, - cloudiness=50.0, - precipitation=0.0, - precipitation_deposits=0.0, - wind_intensity=5.0, - fog_density=30.0, # 雾密度30%(大雾) - fog_distance=10.0, # 雾的可见距离10米 - fog_falloff=0.5, - wetness=0.1, - scattering_intensity=0.4, - mie_scattering_scale=0.4, - rayleigh_scattering_scale=0.4 - ) - - # 6. 晴天日落(太阳低垂、暖色调、无云) - clear_sunset = carla.WeatherParameters( - sun_altitude_angle=15.0, # 太阳高度角15°(日落,地平线为0°) - sun_azimuth_angle=180.0, # 太阳方位角180°(西方) - cloudiness=0.0, - precipitation=0.0, - precipitation_deposits=0.0, - wind_intensity=5.0, - fog_density=0.0, - fog_distance=0.0, - fog_falloff=1.0, - wetness=0.0, - scattering_intensity=0.1, - mie_scattering_scale=0.1, - rayleigh_scattering_scale=0.1 - ) - - # 7. 潮湿路面(无雨但路面湿滑、轻微雾) - wet_road = carla.WeatherParameters( - sun_altitude_angle=75.0, - sun_azimuth_angle=90.0, - cloudiness=30.0, - precipitation=0.0, - precipitation_deposits=0.0, - wind_intensity=10.0, - fog_density=5.0, - fog_distance=40.0, - fog_falloff=0.9, - wetness=0.8, # 路面湿度80%(湿滑) - scattering_intensity=0.1, - mie_scattering_scale=0.1, - rayleigh_scattering_scale=0.1 - ) - - # 组合天气预设列表 - weather_presets = [ - ("Clear Noon", clear_noon), - ("Cloudy Noon", cloudy_noon), - ("Light Rain Noon", light_rain_noon), - ("Mid Rain Noon", mid_rain_noon), - ("Mist Noon", mist_noon), - ("Clear Sunset", clear_sunset), - ("Wet Road Noon", wet_road) - ] - - return weather_presets - - -def switch_weather(world: carla.World, weather: carla.WeatherParameters, weather_name: str) -> None: - """ - 设置CARLA世界的天气,并打印切换信息 - - Args: - world (carla.World): CARLA模拟器的世界对象 - weather (carla.WeatherParameters): 目标天气参数对象 - weather_name (str): 天气名称,用于打印日志 - """ - try: - # 设置世界天气 - world.set_weather(weather) - print(f"\n=== Switched to weather: {weather_name} ===") - except Exception as e: - print(f"Error switching weather to {weather_name}: {e}") + dx_vehicle = dx * math.cos(yaw) + dy * math.sin(yaw) + dy_vehicle = -dx * math.sin(yaw) + dy * math.cos(yaw) + # 3. 动态计算转向增益(根据弯道大小线性插值) + # 方向变化量越大,增益越高 + steer_gain = np.interp( + dir_change, + [0, DIR_CHANGE_SHARP], + [STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE] + ) + steer_gain = np.clip(steer_gain, STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE) -def main() -> None: + # 4. 纯追踪核心计算 + if dx_vehicle < 0.1: + steer = self.last_steer + else: + # 纯追踪公式 + 弯道大小系数(dir_change*2 放大差异) + steer_rad = math.atan2(2 * self.wheelbase * dy_vehicle * (1 + dir_change * 2), dx_vehicle ** 2 + dy_vehicle ** 2) + steer = steer_rad / math.pi + + # 应用增益和方向校准 + steer *= steer_gain * STEER_DIR_COEFF + + # 5. 应用死区 + if abs(steer) < STEER_DEADZONE: + steer = 0.0 + + # 6. 低通滤波(平滑) + steer = STEER_LOWPASS_ALPHA * steer + (1 - STEER_LOWPASS_ALPHA) * self.last_steer + + # 7. 限制范围 + steer = np.clip(steer, -MAX_STEER, MAX_STEER) + + # 8. 更新状态 + self.last_steer = steer + + return steer + + def get_adaptive_lookahead(self, dir_change): + """ + 动态获取预瞄距离(根据弯道大小线性插值) + :param dir_change: 方向变化量 + :return: 自适应预瞄距离 + """ + lookahead_dist = np.interp( + dir_change, + [0, DIR_CHANGE_SHARP], + [LOOKAHEAD_DIST_STRAIGHT, LOOKAHEAD_DIST_CURVE] + ) + lookahead_dist = np.clip(lookahead_dist, LOOKAHEAD_DIST_CURVE, LOOKAHEAD_DIST_STRAIGHT) + self.last_lookahead = lookahead_dist + return lookahead_dist + +# ===================== 速度控制器(动态调整)===================== +class SpeedController: + def __init__(self, kp, ki, kd): + self.kp = kp + self.ki = ki + self.kd = kd + self.last_error = 0.0 + self.integral = 0.0 + + def calculate(self, target_speed, current_speed): + """PID速度控制""" + error = target_speed - current_speed + + p = self.kp * error + self.integral += self.ki * error + self.integral = np.clip(self.integral, -1.0, 1.0) + i = self.integral + d = self.kd * (error - self.last_error) + self.last_error = error + + output = p + i + d + return np.clip(output, 0.0, 1.0) + +# ===================== 辅助函数:计算方向变化(替代曲率)===================== +def calculate_dir_change(current_wp): """ - 主执行函数: - 1. 连接CARLA服务器 - 2. 清理旧车辆 - 3. 生成特斯拉Model3车辆 - 4. 设置旁观者相机 - 5. 定时循环切换天气 - 6. 保持仿真运行直到用户中断 + 计算相邻Waypoint的方向变化量(更精细的计算,增加采样点) + :param current_wp: 当前Waypoint + :return: 方向变化的绝对值之和,弯道等级(0=直道,1=缓弯,2=急弯) """ - # 初始化变量,避免finally块中引用未定义的变量 - client: carla.Client = None - world: carla.World = None - vehicle: carla.Vehicle = None - - # 天气相关变量初始化 - weather_presets = get_weather_presets() # 获取天气预设列表 - current_weather_index = 0 # 当前天气的索引 - weather_switch_interval = 10 # 天气切换间隔(秒) - last_weather_switch_time = time.time() # 上一次天气切换的时间戳 + waypoints = [current_wp] + # 增加采样点到5个,更准确的判断弯道大小 + for i in range(5): + next_wps = waypoints[-1].next(1.0) + if next_wps: + waypoints.append(next_wps[0]) + else: + break + + if len(waypoints) < 4: + return 0.0, 0 + + # 计算每个相邻点的方向 + dirs = [] + for i in range(1, len(waypoints)): + wp_prev = waypoints[i-1] + wp_curr = waypoints[i] + dir_rad = math.atan2( + wp_curr.transform.location.y - wp_prev.transform.location.y, + wp_curr.transform.location.x - wp_prev.transform.location.x + ) + dirs.append(dir_rad) + + # 计算方向变化的绝对值之和(放大差异) + dir_change = 0.0 + for i in range(1, len(dirs)): + dir_change += abs(dirs[i] - dirs[i-1]) * 2 # 放大差异 + + # 划分弯道等级 + if dir_change < DIR_CHANGE_GENTLE: + curve_level = 0 # 直道 + elif dir_change < DIR_CHANGE_SHARP: + curve_level = 1 # 缓弯 + else: + curve_level = 2 # 急弯 + + return dir_change, curve_level + +# ===================== 相机管理器 ===================== +class CameraManager: + def __init__(self, world, vehicle, display): + self.world = world + self.vehicle = vehicle + self.display = display + self.camera = None + self._create_camera() + + def _create_camera(self): + bp = self.world.get_blueprint_library().find("sensor.camera.rgb") + bp.set_attribute("image_size_x", str(CAMERA_WIDTH)) + bp.set_attribute("image_size_y", str(CAMERA_HEIGHT)) + bp.set_attribute("fov", str(CAMERA_FOV)) + self.camera = self.world.spawn_actor(bp, CAMERA_POS, attach_to=self.vehicle) + self.camera.listen(self._on_image) + + def _on_image(self, image): + array = np.frombuffer(image.raw_data, dtype=np.uint8) + array = array.reshape((CAMERA_HEIGHT, CAMERA_WIDTH, 4))[:, :, :3] + array = array[:, :, ::-1].swapaxes(0, 1) + self.display.blit(pygame.surfarray.make_surface(array), (0, 0)) + pygame.display.flip() + + def destroy(self): + if self.camera: + self.camera.stop() + self.camera.destroy() + +# ===================== 主函数(核心逻辑)===================== +def main(): + pygame.init() + display = pygame.display.set_mode((CAMERA_WIDTH, CAMERA_HEIGHT)) + pygame.display.set_caption("CARLA 弯道自适应转向(最终版)") + + client = None + world = None + vehicle = None + camera_manager = None + pp_controller = None + speed_controller = None try: - # 连接到本地CARLA服务器(地址:localhost,端口:2000) - client = carla.Client('localhost', 2000) - # 设置连接超时时间(10秒),避免无限等待 - client.set_timeout(10.0) - print("Connecting to CARLA server...") - - # 获取当前CARLA世界对象(包含地图、Actor、天气等信息) + # 1. 连接CARLA并初始化 + client = carla.Client(CARLA_HOST, CARLA_PORT) + client.set_timeout(CARLA_TIMEOUT) world = client.get_world() - # 打印当前加载的地图名称 - print(f"Connected to world: {world.get_map().name}") - - # 清理之前运行残留的'my_car'车辆 - remove_previous_vehicle(world) - - # 获取地图的所有预设生成点(用于车辆/行人的生成) - spawn_points = world.get_map().get_spawn_points() - if not spawn_points: - print("Error: No spawn points found on the map!") - return - # 选择第一个生成点作为车辆生成位置 - spawn_point = spawn_points[0] - - # 获取蓝图库(包含所有可生成的Actor蓝图:车辆、行人、传感器等) - vehicle_bp_library = world.get_blueprint_library() - # 过滤出特斯拉Model3的蓝图(vehicle.tesla.model3 是CARLA中该车辆的唯一标识) - vehicle_bp = vehicle_bp_library.filter('vehicle.tesla.model3')[0] - # 设置车辆的角色名,方便后续清理 - vehicle_bp.set_attribute('role_name', 'my_car') - - # 尝试生成车辆(try_spawn_actor会检查生成点是否被占用,返回None表示失败) - print("Attempting to spawn vehicle...") - vehicle = world.try_spawn_actor(vehicle_bp, spawn_point) - - if vehicle is None: - # 生成失败(可能生成点被占用) - print(f"Error: Failed to spawn vehicle at {spawn_point.location}.") - return - - # 打印生成成功的车辆信息 - print(f"Vehicle {vehicle.type_id} (ID {vehicle.id}) spawned successfully.") - - # 等待车辆稳定(等待一次仿真tick,再加0.5秒延时) - world.wait_for_tick() - time.sleep(0.5) - - # 设置旁观者相机到车辆后上方 - set_spectator_behind_vehicle(world, vehicle) - - # 初始化天气为第一个预设 - switch_weather(world, weather_presets[0][1], weather_presets[0][0]) - - # 打印运行提示 - print(f"\nSimulation running. Vehicle is stationary.") - print(f"Weather will switch every {weather_switch_interval} seconds.") - print("Press Ctrl+C to stop.\n") - - # 主循环:保持仿真运行并定时切换天气 - while True: - # 等待仿真tick(推进仿真时间) - world.wait_for_tick() - - # 检查是否到达天气切换时间 - current_time = time.time() - if current_time - last_weather_switch_time >= weather_switch_interval: - # 切换到下一个天气(循环遍历预设列表) - current_weather_index = (current_weather_index + 1) % len(weather_presets) - weather_name, weather = weather_presets[current_weather_index] - switch_weather(world, weather, weather_name) - # 更新上一次切换时间 - last_weather_switch_time = current_time - - # 小延时,避免循环过于频繁(减少CPU占用) - time.sleep(0.1) - - except KeyboardInterrupt: - # 捕获用户Ctrl+C中断,友好退出 - print("\nScript stopped by user (Ctrl+C).") + map = world.get_map() + + # 清理现有车辆 + for actor in world.get_actors().filter("vehicle.*"): + actor.destroy() + + # 生成车辆 + vehicle_bp = world.get_blueprint_library().find(VEHICLE_MODEL) + spawn_points = map.get_spawn_points() + vehicle = world.spawn_actor(vehicle_bp, spawn_points[0]) + print(f"车辆生成成功:{vehicle.type_id}") + + # 初始化控制器 + pp_controller = AdaptivePurePursuit(VEHICLE_WHEELBASE) + speed_controller = SpeedController(PID_KP, PID_KI, PID_KD) + camera_manager = CameraManager(world, vehicle, display) + + # 2. 主循环 + print("仿真启动,按ESC退出...") + clock = pygame.time.Clock() + running = True + + while running: + # 事件处理 + for event in pygame.event.get(): + if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): + running = False + + # 3. 获取关键数据 + vehicle_transform = vehicle.get_transform() + vehicle_vel = vehicle.get_velocity() + current_speed = math.hypot(vehicle_vel.x, vehicle_vel.y) * 3.6 # m/s → km/h + + # 获取当前Waypoint + current_wp = map.get_waypoint(vehicle_transform.location, project_to_road=True) + + # 计算方向变化量和弯道等级 + dir_change, curve_level = calculate_dir_change(current_wp) + + # 动态获取预瞄距离 + lookahead_dist = pp_controller.get_adaptive_lookahead(dir_change) + + # 获取目标Waypoint(动态预瞄距离) + target_wps = current_wp.next(lookahead_dist) + target_point = target_wps[0].transform.location if target_wps else vehicle_transform.location + + # 4. 计算目标速度(根据弯道等级调整) + curve_speed_factors = [1.0, 0.7, 0.4] # 直道、缓弯、急弯的速度系数 + speed_factor = curve_speed_factors[min(curve_level, 2)] + target_speed = BASE_SPEED * speed_factor + target_speed = max(10.0, target_speed) + + # 5. 计算转向角(传入方向变化量,实现自适应) + steer = pp_controller.calculate_steer(vehicle_transform, target_point, dir_change) + + # 6. 计算油门/刹车 + throttle = speed_controller.calculate(target_speed, current_speed) + brake = 1.0 - throttle if current_speed > target_speed + 5 else 0.0 + + # 7. 应用控制 + control = carla.VehicleControl() + control.steer = steer + control.throttle = throttle + control.brake = brake + control.hand_brake = False + vehicle.apply_control(control) + + # 8. 打印状态(调试用,显示弯道等级和动态参数) + curve_names = ["直道", "缓弯", "急弯"] + print(f"速度:{current_speed:.1f}km/h | 目标速度:{target_speed:.1f}km/h | 弯道:{curve_names[curve_level]} | 预瞄:{lookahead_dist:.1f}m | 转向:{steer:.3f}") + + # 控制帧率 + clock.tick(30) + except Exception as e: - # 捕获其他未预期的异常,打印错误信息和堆栈跟踪 - print(f"\nAn unexpected error occurred: {e}") - import traceback + print(f"错误:{e}") traceback.print_exc() - finally: - # 资源清理:确保车辆被销毁,避免残留 - print("\nStarting resource cleanup...") - if vehicle is not None and vehicle.is_alive: - print(f"Destroying vehicle: {vehicle.type_id} (ID {vehicle.id})") - if vehicle.destroy(): - print("Vehicle destroyed successfully.") - else: - print("Vehicle destroy() returned False.") - else: - print("Vehicle was None or not alive, no destruction needed.") - - print("Simulation finished.") - -# 程序入口 -if __name__ == '__main__': + finally: + # 清理资源 + print("清理资源...") + if camera_manager: + camera_manager.destroy() + if vehicle: + vehicle.destroy() + pygame.quit() + print("仿真结束") + +if __name__ == "__main__": main() \ No newline at end of file From 51bb9b3033b91787ac063d8885b1568d25cd0096 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Fri, 19 Dec 2025 22:02:15 +0800 Subject: [PATCH 10/26] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=BD=A6=E8=BE=86?= =?UTF-8?q?=E6=B2=BF=E9=81=93=E8=B7=AF=E7=9A=84=E8=87=AA=E4=B8=BB=E8=88=AA?= =?UTF-8?q?=E7=82=B9=E8=B7=9F=E8=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/main.py | 855 +++++++++++++++++++---------- 1 file changed, 552 insertions(+), 303 deletions(-) diff --git a/src/autonomous_driving_car/main.py b/src/autonomous_driving_car/main.py index 3fbfcf65c4..64f88ed002 100644 --- a/src/autonomous_driving_car/main.py +++ b/src/autonomous_driving_car/main.py @@ -1,338 +1,587 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """ -CARLA 弯道自适应转向:根据弯道大小动态调整转弯幅度 +CARLA Waypoint Following - Dynamic Road Waypoints (sd_6/__main__.py) + +核心改进: +1. 动态生成沿道路的航点,替代固定坐标,避开障碍物 +2. 选择地图开阔区域生成车辆(Town03主干道) +3. 增加障碍物检测,确保行驶路段安全 +4. 优化航点跟踪逻辑,适配动态道路 """ +# ===================== 系统导入 & 路径配置 ===================== import sys import os import carla import numpy as np +import time import math +import logging +from typing import List, Tuple, Optional, Union import pygame -import traceback - -# ===================== 全局配置(动态参数)===================== -# CARLA连接 -CARLA_HOST = "localhost" -CARLA_PORT = 2000 -CARLA_TIMEOUT = 10.0 - -# 车辆配置 -VEHICLE_MODEL = "vehicle.tesla.model3" -VEHICLE_WHEELBASE = 2.9 # 特斯拉Model3轴距(米) -VEHICLE_REAR_AXLE_OFFSET = 1.45 # 后轴偏移(正数) - -# 转向控制(动态参数基准) -LOOKAHEAD_DIST_STRAIGHT = 7.0 # 直道预瞄距离 -LOOKAHEAD_DIST_CURVE = 4.0 # 急弯预瞄距离 -STEER_GAIN_STRAIGHT = 0.7 # 直道转向增益 -STEER_GAIN_CURVE = 1.0 # 急弯转向增益 -STEER_DEADZONE = 0.05 # 转向死区 -STEER_LOWPASS_ALPHA = 0.6 # 低通滤波系数 -STEER_DIR_COEFF = 1.0 # 转向方向校准 -MAX_STEER = 1.0 # 最大转向角 - -# 弯道等级划分(方向变化量阈值) -DIR_CHANGE_GENTLE = 0.03 # 缓弯阈值 -DIR_CHANGE_SHARP = 0.08 # 急弯阈值 - -# 速度控制 -BASE_SPEED = 28.0 # 直道基础速度 -CURVE_SPEED_FACTOR = 0.6 # 弯道速度系数(更明显的速度衰减) -PID_KP = 0.15 -PID_KI = 0.01 -PID_KD = 0.01 - -# 相机 -CAMERA_POS = carla.Transform(carla.Location(x=-5.0, z=2.0)) -CAMERA_WIDTH = 800 -CAMERA_HEIGHT = 600 -CAMERA_FOV = 90 - -# ===================== 纯追踪控制器(动态适配)===================== -class AdaptivePurePursuit: - def __init__(self, wheelbase): - self.wheelbase = wheelbase - self.last_steer = 0.0 # 上一帧转向角 - self.last_lookahead = LOOKAHEAD_DIST_STRAIGHT # 上一帧预瞄距离 - - def calculate_steer(self, vehicle_transform, target_point, dir_change): - """ - 自适应纯追踪计算(根据弯道大小调整参数) - :param vehicle_transform: 车辆变换 - :param target_point: 目标点(carla.Location) - :param dir_change: 方向变化量(弯道大小) - :return: 自适应后的转向角 - """ - # 1. 获取车辆后轴位置 - forward_vec = vehicle_transform.get_forward_vector() - rear_axle_loc = carla.Location( - x=vehicle_transform.location.x - forward_vec.x * VEHICLE_REAR_AXLE_OFFSET, - y=vehicle_transform.location.y - forward_vec.y * VEHICLE_REAR_AXLE_OFFSET, - z=vehicle_transform.location.z - ) - - # 2. 转换到车辆坐标系 - dx = target_point.x - rear_axle_loc.x - dy = target_point.y - rear_axle_loc.y - yaw = math.radians(vehicle_transform.rotation.yaw) - - dx_vehicle = dx * math.cos(yaw) + dy * math.sin(yaw) - dy_vehicle = -dx * math.sin(yaw) + dy * math.cos(yaw) - - # 3. 动态计算转向增益(根据弯道大小线性插值) - # 方向变化量越大,增益越高 - steer_gain = np.interp( - dir_change, - [0, DIR_CHANGE_SHARP], - [STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE] - ) - steer_gain = np.clip(steer_gain, STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE) - - # 4. 纯追踪核心计算 - if dx_vehicle < 0.1: - steer = self.last_steer - else: - # 纯追踪公式 + 弯道大小系数(dir_change*2 放大差异) - steer_rad = math.atan2(2 * self.wheelbase * dy_vehicle * (1 + dir_change * 2), dx_vehicle ** 2 + dy_vehicle ** 2) - steer = steer_rad / math.pi - - # 应用增益和方向校准 - steer *= steer_gain * STEER_DIR_COEFF - - # 5. 应用死区 - if abs(steer) < STEER_DEADZONE: - steer = 0.0 - - # 6. 低通滤波(平滑) - steer = STEER_LOWPASS_ALPHA * steer + (1 - STEER_LOWPASS_ALPHA) * self.last_steer - - # 7. 限制范围 - steer = np.clip(steer, -MAX_STEER, MAX_STEER) +import matplotlib.pyplot as plt + +# 获取当前脚本目录并加入搜索路径 +current_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(current_dir) + +# ===================== PID控制器(优化版)===================== +class PIDController: + """PID速度控制器""" + def __init__(self): + self.kp = 0.3 + self.ki = 0.01 + self.kd = 0.1 + self.prev_error = 0.0 + self.integral = 0.0 + self.integral_limit = 0.5 - # 8. 更新状态 - self.last_steer = steer + def calculate_control(self, target_speed, current_speed): + error = target_speed - current_speed + self.integral = np.clip(self.integral + error * self.ki, -self.integral_limit, self.integral_limit) + derivative = (error - self.prev_error) * self.kd + output = self.kp * error + self.integral + derivative - return steer + throttle = max(0.0, min(1.0, output)) if output > 0 else 0.0 + brake = max(0.0, min(1.0, -output)) if output < 0 else 0.0 - def get_adaptive_lookahead(self, dir_change): - """ - 动态获取预瞄距离(根据弯道大小线性插值) - :param dir_change: 方向变化量 - :return: 自适应预瞄距离 - """ - lookahead_dist = np.interp( - dir_change, - [0, DIR_CHANGE_SHARP], - [LOOKAHEAD_DIST_STRAIGHT, LOOKAHEAD_DIST_CURVE] - ) - lookahead_dist = np.clip(lookahead_dist, LOOKAHEAD_DIST_CURVE, LOOKAHEAD_DIST_STRAIGHT) - self.last_lookahead = lookahead_dist - return lookahead_dist - -# ===================== 速度控制器(动态调整)===================== -class SpeedController: - def __init__(self, kp, ki, kd): - self.kp = kp - self.ki = ki - self.kd = kd - self.last_error = 0.0 - self.integral = 0.0 + self.prev_error = error - def calculate(self, target_speed, current_speed): - """PID速度控制""" - error = target_speed - current_speed + from collections import namedtuple + Control = namedtuple('Control', ['throttle', 'brake']) + return Control(throttle=throttle, brake=brake) - p = self.kp * error - self.integral += self.ki * error - self.integral = np.clip(self.integral, -1.0, 1.0) - i = self.integral - d = self.kd * (error - self.last_error) - self.last_error = error - - output = p + i + d - return np.clip(output, 0.0, 1.0) - -# ===================== 辅助函数:计算方向变化(替代曲率)===================== -def calculate_dir_change(current_wp): - """ - 计算相邻Waypoint的方向变化量(更精细的计算,增加采样点) - :param current_wp: 当前Waypoint - :return: 方向变化的绝对值之和,弯道等级(0=直道,1=缓弯,2=急弯) - """ - waypoints = [current_wp] - # 增加采样点到5个,更准确的判断弯道大小 - for i in range(5): - next_wps = waypoints[-1].next(1.0) - if next_wps: - waypoints.append(next_wps[0]) - else: - break - - if len(waypoints) < 4: - return 0.0, 0 - - # 计算每个相邻点的方向 - dirs = [] - for i in range(1, len(waypoints)): - wp_prev = waypoints[i-1] - wp_curr = waypoints[i] - dir_rad = math.atan2( - wp_curr.transform.location.y - wp_prev.transform.location.y, - wp_curr.transform.location.x - wp_prev.transform.location.x - ) - dirs.append(dir_rad) - - # 计算方向变化的绝对值之和(放大差异) - dir_change = 0.0 - for i in range(1, len(dirs)): - dir_change += abs(dirs[i] - dirs[i-1]) * 2 # 放大差异 - - # 划分弯道等级 - if dir_change < DIR_CHANGE_GENTLE: - curve_level = 0 # 直道 - elif dir_change < DIR_CHANGE_SHARP: - curve_level = 1 # 缓弯 - else: - curve_level = 2 # 急弯 - - return dir_change, curve_level - -# ===================== 相机管理器 ===================== -class CameraManager: - def __init__(self, world, vehicle, display): +# ===================== Pygame显示(优化版)===================== +class PygameDisplay: + """Pygame摄像头显示类""" + def __init__(self, world, vehicle): self.world = world self.vehicle = vehicle - self.display = display self.camera = None - self._create_camera() - - def _create_camera(self): - bp = self.world.get_blueprint_library().find("sensor.camera.rgb") - bp.set_attribute("image_size_x", str(CAMERA_WIDTH)) - bp.set_attribute("image_size_y", str(CAMERA_HEIGHT)) - bp.set_attribute("fov", str(CAMERA_FOV)) - self.camera = self.world.spawn_actor(bp, CAMERA_POS, attach_to=self.vehicle) - self.camera.listen(self._on_image) - - def _on_image(self, image): - array = np.frombuffer(image.raw_data, dtype=np.uint8) - array = array.reshape((CAMERA_HEIGHT, CAMERA_WIDTH, 4))[:, :, :3] - array = array[:, :, ::-1].swapaxes(0, 1) - self.display.blit(pygame.surfarray.make_surface(array), (0, 0)) + self.screen = None + self.surface = None + + pygame.init() + self.screen = pygame.display.set_mode((800, 600)) + pygame.display.set_caption("CARLA Camera View (ESC to quit)") + + self._setup_camera() + + def _setup_camera(self): + try: + bp_lib = self.world.get_blueprint_library() + camera_bp = bp_lib.find('sensor.camera.rgb') + camera_bp.set_attribute('image_size_x', '800') + camera_bp.set_attribute('image_size_y', '600') + camera_bp.set_attribute('fov', '90') + camera_transform = carla.Transform(carla.Location(x=2.5, z=1.8)) + self.camera = self.world.spawn_actor(camera_bp, camera_transform, attach_to=self.vehicle) + self.camera.listen(lambda image: self._process_image(image)) + except Exception as e: + logging.warning(f"摄像头初始化失败:{e}") + self.camera = None + + def _process_image(self, image): + try: + array = np.frombuffer(image.raw_data, dtype=np.uint8) + array = array.reshape((image.height, image.width, 4))[:, :, :3] + array = array[:, :, ::-1].swapaxes(0, 1) + self.surface = pygame.surfarray.make_surface(array) + except Exception as e: + logging.warning(f"图像处理失败:{e}") + + def parse_events(self): + for event in pygame.event.get(): + if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): + return True + return False + + def render(self): + self.screen.fill((0, 0, 0)) + if self.surface is not None: + self.screen.blit(self.surface, (0, 0)) pygame.display.flip() def destroy(self): if self.camera: self.camera.stop() self.camera.destroy() + pygame.quit() -# ===================== 主函数(核心逻辑)===================== -def main(): - pygame.init() - display = pygame.display.set_mode((CAMERA_WIDTH, CAMERA_HEIGHT)) - pygame.display.set_caption("CARLA 弯道自适应转向(最终版)") - - client = None - world = None - vehicle = None - camera_manager = None - pp_controller = None - speed_controller = None - - try: - # 1. 连接CARLA并初始化 - client = carla.Client(CARLA_HOST, CARLA_PORT) - client.set_timeout(CARLA_TIMEOUT) - world = client.get_world() +# ===================== 绘图类(优化版)===================== +class Plotter: + """轨迹+速度绘图类""" + def __init__(self, waypoints): + self.waypoints = waypoints + self.is_initialized = False + self.fig, (self.ax1, self.ax2) = plt.subplots(1, 2, figsize=(14, 6)) + self.x_data = [] + self.y_data = [] + self.time_data = [] + self.speed_data = [] + self.target_speed_data = [] + + def init_plot(self): + try: + waypoints_np = np.array(self.waypoints) + self.ax1.plot(waypoints_np[:, 0], waypoints_np[:, 1], 'ro-', label='Waypoints', markersize=4) + self.ax1.set_xlabel('X (m)') + self.ax1.set_ylabel('Y (m)') + self.ax1.set_title('Vehicle Trajectory') + self.ax1.legend() + self.ax1.grid(True) + + self.ax2.set_xlabel('Time (s)') + self.ax2.set_ylabel('Speed (km/h)') + self.ax2.set_title('Speed vs Time') + self.ax2.grid(True) + + plt.ion() + plt.show(block=False) + self.is_initialized = True + except Exception as e: + logging.error(f"绘图初始化失败:{e}") + self.is_initialized = False + + def update_plot(self, time, x, y, speed, target_speed): + if not self.is_initialized: + return + + try: + self.x_data.append(x) + self.y_data.append(y) + self.time_data.append(time) + self.speed_data.append(speed) + self.target_speed_data.append(target_speed) + + self.ax1.plot(self.x_data, self.y_data, 'b-', label='Trajectory' if len(self.x_data) == 1 else "", linewidth=1) + if len(self.x_data) == 1: + self.ax1.legend() + + self.ax2.plot(self.time_data, self.speed_data, 'b-', label='Current Speed' if len(self.time_data) == 1 else "", linewidth=1) + self.ax2.plot(self.time_data, self.target_speed_data, 'r--', label='Target Speed' if len(self.time_data) == 1 else "", linewidth=1) + if len(self.time_data) == 1: + self.ax2.legend() + + self.fig.canvas.draw() + self.fig.canvas.flush_events() + except Exception as e: + logging.warning(f"绘图更新失败:{e}") + + def cleanup_plot(self): + if self.is_initialized: + plt.ioff() + plt.close(self.fig) + self.is_initialized = False + +# ===================== 配置类(动态航点版)===================== +class Config: + """仿真配置类""" + # 动态航点配置 + NUM_WAYPOINTS = 50 # 生成的航点数量 + WAYPOINT_SPACING = 2.0 # 航点间距(米) + TARGET_SPEED = 30.0 # 统一目标速度(km/h) + + # 控制器参数 + WAYPOINT_THRESHOLD = 1.5 + MIN_SPEED_STANLEY = 1e-4 + STANLEY_K = 0.4 + MAX_STEER_DEG = 40.0 + MAX_STEER_RAD = math.radians(MAX_STEER_DEG) + + # CARLA配置 + CARLA_HOST = "localhost" + CARLA_PORT = 2000 + CARLA_TIMEOUT = 10.0 + + # 车辆配置(Town03主干道生成位置) + VEHICLE_MODEL = "vehicle.tesla.model3" + SPAWN_LOCATION = carla.Location(x=100.0, y=10.0, z=0.5) # Town03开阔主干道 + SPAWN_YAW = 0.0 # 初始朝向 + + # 观察者视角(适配新生成位置) + SPECTATOR_LOCATION = carla.Location(x=110.0, y=10.0, z=8.0) + SPECTATOR_ROTATION = carla.Rotation(pitch=-30.0, yaw=0.0, roll=0.0) + +# ===================== 工具类(优化版)===================== +class Utils: + """工具函数类""" + + @staticmethod + def normalize_angle(angle: float) -> float: + while angle > np.pi: + angle -= 2 * np.pi + while angle < -np.pi: + angle += 2 * np.pi + return angle + + @staticmethod + def get_vehicle_speed(vehicle: carla.Vehicle, unit: str = "kmh") -> float: + vel = vehicle.get_velocity() + speed_mps = np.linalg.norm([vel.x, vel.y, vel.z]) + return speed_mps * 3.6 if unit == "kmh" else speed_mps + + @staticmethod + def calculate_cte(vehicle_loc: carla.Location, prev_wp: List[float], target_wp: List[float]) -> float: + x1, y1 = prev_wp[0], prev_wp[1] + x2, y2 = target_wp[0], target_wp[1] + x, y = vehicle_loc.x, vehicle_loc.y + + dx = x2 - x1 + dy = y2 - y1 + if abs(dx) < 1e-6 and abs(dy) < 1e-6: + return math.hypot(x - x1, y - y1) + + if abs(dx) < 1e-6: + cte = x - x1 + else: + slope = dy / dx + a = -slope + b = 1.0 + c = slope * x1 - y1 + cte = (a * x + b * y + c) / np.sqrt(a ** 2 + b ** 2) + + yaw_path = np.arctan2(dy, dx) + yaw_ct = np.arctan2(y - y1, x - x1) + yaw_diff = Utils.normalize_angle(yaw_path - yaw_ct) + + return abs(cte) if yaw_diff > 0 else -abs(cte) + + @staticmethod + def calculate_distance_2d(loc1: carla.Location, loc2: carla.Location) -> float: + dx = loc1.x - loc2.x + dy = loc1.y - loc2.y + return math.sqrt(dx ** 2 + dy ** 2) + + @staticmethod + def generate_road_waypoints(world, start_loc: carla.Location, num_waypoints: int, spacing: float) -> List[List[float]]: + """ + 沿道路动态生成航点(核心:避免障碍物) + :param world: CARLA世界对象 + :param start_loc: 起始位置 + :param num_waypoints: 航点数量 + :param spacing: 航点间距 + :return: 航点列表 [[x, y, speed], ...] + """ map = world.get_map() + waypoints = [] + current_waypoint = map.get_waypoint(start_loc) + + for i in range(num_waypoints): + # 添加当前航点(x, y, 目标速度) + waypoints.append([current_waypoint.transform.location.x, current_waypoint.transform.location.y, Config.TARGET_SPEED]) + # 获取下一个道路航点(沿道路前进) + next_waypoints = current_waypoint.next(spacing) + if not next_waypoints: + break + current_waypoint = next_waypoints[0] + + logging.info(f"动态生成了 {len(waypoints)} 个道路航点") + return waypoints + +# ===================== 航点管理器(优化版)===================== +class WaypointManager: + """航点管理类""" + def __init__(self, waypoints: List[List[float]], threshold: float): + self.waypoints = waypoints + self.threshold = threshold + self.current_target_id = 1 + + def update_target(self, vehicle_loc: carla.Location) -> None: + if self.current_target_id >= len(self.waypoints) - 1: + return + + target_wp = self.waypoints[self.current_target_id] + target_loc = carla.Location(x=target_wp[0], y=target_wp[1]) + distance = Utils.calculate_distance_2d(vehicle_loc, target_loc) + + if distance < self.threshold: + self._log_waypoint_switch(distance) + self.current_target_id += 1 + + def _log_waypoint_switch(self, distance: float) -> None: + reached_id = self.current_target_id + reached_coords = (self.waypoints[reached_id][0], self.waypoints[reached_id][1]) + log_msg = f"到达航点 {reached_id} (X={reached_coords[0]:.1f}, Y={reached_coords[1]:.1f}, 距离={distance:.1f}m)" + + if self.current_target_id + 1 < len(self.waypoints): + next_id = self.current_target_id + 1 + next_coords = (self.waypoints[next_id][0], self.waypoints[next_id][1]) + log_msg += f",新目标:航点 {next_id} (X={next_coords[0]:.1f}, Y={next_coords[1]:.1f})" + + logging.info(log_msg) + + def get_current_target(self) -> List[float]: + return self.waypoints[self.current_target_id] + + def get_target_speed(self) -> float: + return self.get_current_target()[2] if self.current_target_id < len(self.waypoints) else 0.0 + +# ===================== Stanley控制器(优化版)===================== +class StanleyController: + """Stanley横向控制器""" + def __init__(self, k: float, max_steer_rad: float, min_speed: float): + self.k = k + self.max_steer_rad = max_steer_rad + self.min_speed = min_speed + self.prev_steer = 0.0 + self.smoothing_factor = 0.7 + + def calculate_steer(self, vehicle: carla.Vehicle, waypoints: List[List[float]], target_id: int) -> Tuple[float, float]: + if target_id < 1: + return 0.0, 0.0 + + vehicle_transform = vehicle.get_transform() + vehicle_loc = vehicle_transform.location + vehicle_yaw = math.radians(vehicle_transform.rotation.yaw) + vehicle_speed = Utils.get_vehicle_speed(vehicle, unit="mps") + + prev_wp = waypoints[target_id - 1] + target_wp = waypoints[target_id] + + yaw_path = np.arctan2(target_wp[1] - prev_wp[1], target_wp[0] - prev_wp[0]) + yaw_error = Utils.normalize_angle(yaw_path - vehicle_yaw) + cte = Utils.calculate_cte(vehicle_loc, prev_wp, target_wp) + + # 动态K值 + if vehicle_speed < 5: + k = self.k * 2 + elif vehicle_speed > 15: + k = self.k * 0.5 + else: + k = self.k + + safe_speed = max(vehicle_speed, self.min_speed) + cte_steer = np.arctan(k * cte / safe_speed) + + total_steer = Utils.normalize_angle(yaw_error + cte_steer) + total_steer = np.clip(total_steer, -self.max_steer_rad, self.max_steer_rad) + steer_carla = total_steer / self.max_steer_rad + + # 平滑转向 + steer_carla = self.smoothing_factor * self.prev_steer + (1 - self.smoothing_factor) * steer_carla + self.prev_steer = steer_carla + + return steer_carla, cte + +# ===================== 主仿真类(动态航点版)===================== +class CarlaSimulation: + """CARLA仿真主类""" + def __init__(self): + logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") + self.logger = logging.getLogger(__name__) + + self.config = Config() + self.client: Optional[carla.Client] = None + self.world: Optional[carla.World] = None + self.vehicle: Optional[carla.Vehicle] = None + + # 动态航点(后续生成) + self.waypoints = [] + self.waypoint_manager = None + self.stanley_controller = StanleyController( + k=self.config.STANLEY_K, + max_steer_rad=self.config.MAX_STEER_RAD, + min_speed=self.config.MIN_SPEED_STANLEY + ) + self.pid_controller = PIDController() + + self.plotter: Optional[Plotter] = None + self.pygame_display: Optional[PygameDisplay] = None + self.sim_start_time: float = 0.0 + + def connect_carla(self) -> bool: + try: + self.client = carla.Client(self.config.CARLA_HOST, self.config.CARLA_PORT) + self.client.set_timeout(self.config.CARLA_TIMEOUT) + self.world = self.client.get_world() + self.logger.info(f"成功连接到CARLA世界:{self.world.get_map().name}") + return True + except Exception as e: + self.logger.error(f"连接CARLA失败:{e}") + return False + + def cleanup_vehicles(self) -> None: + self.logger.info("清理历史车辆...") + actors = self.world.get_actors().filter("vehicle.*") + removed_count = 0 + for actor in actors: + if actor.attributes.get("role_name") == "my_car": + if actor.destroy(): + removed_count += 1 + self.logger.info(f"销毁车辆:{actor.type_id} (ID: {actor.id})") + self.logger.info(f"共销毁 {removed_count} 辆历史车辆") + + def spawn_vehicle(self) -> bool: + """在开阔道路生成车辆(避开障碍物)""" + # 获取生成位置的道路Waypoint + map = self.world.get_map() + spawn_waypoint = map.get_waypoint(self.config.SPAWN_LOCATION) + if not spawn_waypoint: + self.logger.error("生成位置不在道路上") + return False + + # 生成Transform + spawn_transform = spawn_waypoint.transform + spawn_transform.location.z += 0.5 + spawn_transform.rotation.yaw = self.config.SPAWN_YAW + + # 加载车辆蓝图 + bp_lib = self.world.get_blueprint_library() + vehicle_bp = bp_lib.filter(self.config.VEHICLE_MODEL)[0] + vehicle_bp.set_attribute("role_name", "my_car") + + # 生成车辆(重试) + self.vehicle = self.world.try_spawn_actor(vehicle_bp, spawn_transform) + if self.vehicle is None: + spawn_transform.location.z += 0.5 + self.vehicle = self.world.try_spawn_actor(vehicle_bp, spawn_transform) + if self.vehicle is None: + self.logger.error(f"无法生成车辆:{spawn_transform.location}") + return False + + # 动态生成道路航点(核心:避开障碍物) + self.waypoints = Utils.generate_road_waypoints( + world=self.world, + start_loc=self.config.SPAWN_LOCATION, + num_waypoints=self.config.NUM_WAYPOINTS, + spacing=self.config.WAYPOINT_SPACING + ) + if len(self.waypoints) < 2: + self.logger.error("生成的航点数量不足") + return False + + # 初始化航点管理器 + self.waypoint_manager = WaypointManager( + waypoints=self.waypoints, + threshold=self.config.WAYPOINT_THRESHOLD + ) - # 清理现有车辆 - for actor in world.get_actors().filter("vehicle.*"): - actor.destroy() - - # 生成车辆 - vehicle_bp = world.get_blueprint_library().find(VEHICLE_MODEL) - spawn_points = map.get_spawn_points() - vehicle = world.spawn_actor(vehicle_bp, spawn_points[0]) - print(f"车辆生成成功:{vehicle.type_id}") - - # 初始化控制器 - pp_controller = AdaptivePurePursuit(VEHICLE_WHEELBASE) - speed_controller = SpeedController(PID_KP, PID_KI, PID_KD) - camera_manager = CameraManager(world, vehicle, display) - - # 2. 主循环 - print("仿真启动,按ESC退出...") - clock = pygame.time.Clock() - running = True - - while running: - # 事件处理 - for event in pygame.event.get(): - if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): - running = False - - # 3. 获取关键数据 - vehicle_transform = vehicle.get_transform() - vehicle_vel = vehicle.get_velocity() - current_speed = math.hypot(vehicle_vel.x, vehicle_vel.y) * 3.6 # m/s → km/h - - # 获取当前Waypoint - current_wp = map.get_waypoint(vehicle_transform.location, project_to_road=True) - - # 计算方向变化量和弯道等级 - dir_change, curve_level = calculate_dir_change(current_wp) - - # 动态获取预瞄距离 - lookahead_dist = pp_controller.get_adaptive_lookahead(dir_change) - - # 获取目标Waypoint(动态预瞄距离) - target_wps = current_wp.next(lookahead_dist) - target_point = target_wps[0].transform.location if target_wps else vehicle_transform.location - - # 4. 计算目标速度(根据弯道等级调整) - curve_speed_factors = [1.0, 0.7, 0.4] # 直道、缓弯、急弯的速度系数 - speed_factor = curve_speed_factors[min(curve_level, 2)] - target_speed = BASE_SPEED * speed_factor - target_speed = max(10.0, target_speed) - - # 5. 计算转向角(传入方向变化量,实现自适应) - steer = pp_controller.calculate_steer(vehicle_transform, target_point, dir_change) - - # 6. 计算油门/刹车 - throttle = speed_controller.calculate(target_speed, current_speed) - brake = 1.0 - throttle if current_speed > target_speed + 5 else 0.0 + self.logger.info(f"成功生成车辆:{self.vehicle.type_id} (ID: {self.vehicle.id})") + self.logger.info(f"生成位置:{spawn_transform.location}, 初始朝向:{spawn_transform.rotation.yaw:.1f}度") + return True - # 7. 应用控制 + def setup_spectator(self) -> None: + """设置观察者视角(清晰查看车辆前方)""" + spectator = self.world.get_spectator() + spectator_transform = carla.Transform( + self.config.SPECTATOR_LOCATION, + self.config.SPECTATOR_ROTATION + ) + spectator.set_transform(spectator_transform) + self.logger.info("已设置观察者视角") + + def init_visualization(self) -> None: + self.plotter = Plotter(waypoints=self.waypoints) + self.plotter.init_plot() + self.logger.info("Plotter初始化完成") + + self.pygame_display = PygameDisplay(self.world, self.vehicle) + self.logger.info("Pygame显示初始化完成") + + def run_simulation(self) -> None: + self.sim_start_time = time.time() + self.logger.info("开始仿真循环...") + + while True: + if self.pygame_display.parse_events(): + self.logger.info("用户请求退出仿真") + break + + self.world.wait_for_tick() + + # 获取车辆状态 + vehicle_transform = self.vehicle.get_transform() + vehicle_loc = vehicle_transform.location + current_speed = Utils.get_vehicle_speed(vehicle=self.vehicle, unit="kmh") + sim_time = time.time() - self.sim_start_time + + # 更新航点 + self.waypoint_manager.update_target(vehicle_loc) + target_speed = self.waypoint_manager.get_target_speed() + + # 计算控制 + throttle, brake = self.pid_controller.calculate_control(target_speed, current_speed) + steer, cte = self.stanley_controller.calculate_steer( + vehicle=self.vehicle, + waypoints=self.waypoints, + target_id=self.waypoint_manager.current_target_id + ) + + # 应用控制 control = carla.VehicleControl() - control.steer = steer control.throttle = throttle control.brake = brake + control.steer = steer control.hand_brake = False - vehicle.apply_control(control) - - # 8. 打印状态(调试用,显示弯道等级和动态参数) - curve_names = ["直道", "缓弯", "急弯"] - print(f"速度:{current_speed:.1f}km/h | 目标速度:{target_speed:.1f}km/h | 弯道:{curve_names[curve_level]} | 预瞄:{lookahead_dist:.1f}m | 转向:{steer:.3f}") - - # 控制帧率 - clock.tick(30) - - except Exception as e: - print(f"错误:{e}") - traceback.print_exc() - - finally: - # 清理资源 - print("清理资源...") - if camera_manager: - camera_manager.destroy() - if vehicle: - vehicle.destroy() - pygame.quit() - print("仿真结束") + control.manual_gear_shift = False + self.vehicle.apply_control(control) + + # 调试日志 + if int(sim_time) % 1 == 0 and not hasattr(self, f"_logged_{int(sim_time)}"): + setattr(self, f"_logged_{int(sim_time)}", True) + self.logger.info( + f"时间:{sim_time:.1f}s | " + f"速度:{current_speed:.1f}km/h | " + f"目标速度:{target_speed:.1f}km/h | " + f"转向角:{steer:.2f} | " + f"横向偏差:{cte:.2f}m" + ) + + # 更新可视化 + if self.plotter and self.plotter.is_initialized: + try: + self.plotter.update_plot(sim_time, vehicle_loc.x, vehicle_loc.y, current_speed, target_speed) + except Exception as e: + self.logger.warning(f"绘图更新失败:{e}") + self.plotter.cleanup_plot() + self.plotter = None + + if self.pygame_display: + self.pygame_display.render() + + def cleanup(self) -> None: + self.logger.info("开始清理资源...") + + if self.pygame_display: + self.pygame_display.destroy() + self.logger.info("Pygame显示已销毁") + + if self.plotter and self.plotter.is_initialized: + self.plotter.cleanup_plot() + self.logger.info("绘图器已清理") + + if self.vehicle and self.vehicle.is_alive: + self.vehicle.destroy() + self.logger.info("车辆已销毁") + + self.logger.info("资源清理完成") + + def start(self) -> None: + try: + if not self.connect_carla(): + return + + self.cleanup_vehicles() + + if not self.spawn_vehicle(): + return + + self.setup_spectator() + self.init_visualization() + self.run_simulation() + + except KeyboardInterrupt: + self.logger.info("用户中断仿真") + except Exception as e: + self.logger.error(f"仿真异常:{e}", exc_info=True) + finally: + self.cleanup() + +# ===================== 主函数 ===================== +def main(): + simulation = CarlaSimulation() + simulation.start() if __name__ == "__main__": main() \ No newline at end of file From 3edfc11fe39c511973bf6aa1eea00e13981c7456 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Sat, 20 Dec 2025 12:29:37 +0800 Subject: [PATCH 11/26] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=BD=A6=E8=BE=86?= =?UTF-8?q?=E8=87=AA=E4=B8=BB=E9=81=93=E8=B7=AF=E8=88=AA=E7=82=B9=E8=B7=9F?= =?UTF-8?q?=E9=9A=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/main.py | 826 ++++++++++------------------- 1 file changed, 290 insertions(+), 536 deletions(-) diff --git a/src/autonomous_driving_car/main.py b/src/autonomous_driving_car/main.py index 64f88ed002..2e6e2fd2f2 100644 --- a/src/autonomous_driving_car/main.py +++ b/src/autonomous_driving_car/main.py @@ -1,214 +1,48 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- """ -CARLA Waypoint Following - Dynamic Road Waypoints (sd_6/__main__.py) - -核心改进: -1. 动态生成沿道路的航点,替代固定坐标,避开障碍物 -2. 选择地图开阔区域生成车辆(Town03主干道) -3. 增加障碍物检测,确保行驶路段安全 -4. 优化航点跟踪逻辑,适配动态道路 +CARLA Waypoint Following(原生道路航点版) +核心:直接使用CARLA地图的道路航点,车辆100%沿道路行驶 """ -# ===================== 系统导入 & 路径配置 ===================== import sys import os import carla import numpy as np -import time import math -import logging -from typing import List, Tuple, Optional, Union -import pygame import matplotlib.pyplot as plt +import cv2 +import time -# 获取当前脚本目录并加入搜索路径 -current_dir = os.path.dirname(os.path.abspath(__file__)) -sys.path.append(current_dir) - -# ===================== PID控制器(优化版)===================== -class PIDController: - """PID速度控制器""" - def __init__(self): - self.kp = 0.3 - self.ki = 0.01 - self.kd = 0.1 - self.prev_error = 0.0 - self.integral = 0.0 - self.integral_limit = 0.5 - - def calculate_control(self, target_speed, current_speed): - error = target_speed - current_speed - self.integral = np.clip(self.integral + error * self.ki, -self.integral_limit, self.integral_limit) - derivative = (error - self.prev_error) * self.kd - output = self.kp * error + self.integral + derivative - - throttle = max(0.0, min(1.0, output)) if output > 0 else 0.0 - brake = max(0.0, min(1.0, -output)) if output < 0 else 0.0 - - self.prev_error = error - - from collections import namedtuple - Control = namedtuple('Control', ['throttle', 'brake']) - return Control(throttle=throttle, brake=brake) - -# ===================== Pygame显示(优化版)===================== -class PygameDisplay: - """Pygame摄像头显示类""" - def __init__(self, world, vehicle): - self.world = world - self.vehicle = vehicle - self.camera = None - self.screen = None - self.surface = None - - pygame.init() - self.screen = pygame.display.set_mode((800, 600)) - pygame.display.set_caption("CARLA Camera View (ESC to quit)") - - self._setup_camera() - - def _setup_camera(self): - try: - bp_lib = self.world.get_blueprint_library() - camera_bp = bp_lib.find('sensor.camera.rgb') - camera_bp.set_attribute('image_size_x', '800') - camera_bp.set_attribute('image_size_y', '600') - camera_bp.set_attribute('fov', '90') - camera_transform = carla.Transform(carla.Location(x=2.5, z=1.8)) - self.camera = self.world.spawn_actor(camera_bp, camera_transform, attach_to=self.vehicle) - self.camera.listen(lambda image: self._process_image(image)) - except Exception as e: - logging.warning(f"摄像头初始化失败:{e}") - self.camera = None - - def _process_image(self, image): - try: - array = np.frombuffer(image.raw_data, dtype=np.uint8) - array = array.reshape((image.height, image.width, 4))[:, :, :3] - array = array[:, :, ::-1].swapaxes(0, 1) - self.surface = pygame.surfarray.make_surface(array) - except Exception as e: - logging.warning(f"图像处理失败:{e}") - - def parse_events(self): - for event in pygame.event.get(): - if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): - return True - return False - - def render(self): - self.screen.fill((0, 0, 0)) - if self.surface is not None: - self.screen.blit(self.surface, (0, 0)) - pygame.display.flip() - - def destroy(self): - if self.camera: - self.camera.stop() - self.camera.destroy() - pygame.quit() - -# ===================== 绘图类(优化版)===================== -class Plotter: - """轨迹+速度绘图类""" - def __init__(self, waypoints): - self.waypoints = waypoints - self.is_initialized = False - self.fig, (self.ax1, self.ax2) = plt.subplots(1, 2, figsize=(14, 6)) - self.x_data = [] - self.y_data = [] - self.time_data = [] - self.speed_data = [] - self.target_speed_data = [] - - def init_plot(self): - try: - waypoints_np = np.array(self.waypoints) - self.ax1.plot(waypoints_np[:, 0], waypoints_np[:, 1], 'ro-', label='Waypoints', markersize=4) - self.ax1.set_xlabel('X (m)') - self.ax1.set_ylabel('Y (m)') - self.ax1.set_title('Vehicle Trajectory') - self.ax1.legend() - self.ax1.grid(True) - - self.ax2.set_xlabel('Time (s)') - self.ax2.set_ylabel('Speed (km/h)') - self.ax2.set_title('Speed vs Time') - self.ax2.grid(True) - - plt.ion() - plt.show(block=False) - self.is_initialized = True - except Exception as e: - logging.error(f"绘图初始化失败:{e}") - self.is_initialized = False - - def update_plot(self, time, x, y, speed, target_speed): - if not self.is_initialized: - return - - try: - self.x_data.append(x) - self.y_data.append(y) - self.time_data.append(time) - self.speed_data.append(speed) - self.target_speed_data.append(target_speed) - - self.ax1.plot(self.x_data, self.y_data, 'b-', label='Trajectory' if len(self.x_data) == 1 else "", linewidth=1) - if len(self.x_data) == 1: - self.ax1.legend() - - self.ax2.plot(self.time_data, self.speed_data, 'b-', label='Current Speed' if len(self.time_data) == 1 else "", linewidth=1) - self.ax2.plot(self.time_data, self.target_speed_data, 'r--', label='Target Speed' if len(self.time_data) == 1 else "", linewidth=1) - if len(self.time_data) == 1: - self.ax2.legend() - - self.fig.canvas.draw() - self.fig.canvas.flush_events() - except Exception as e: - logging.warning(f"绘图更新失败:{e}") - - def cleanup_plot(self): - if self.is_initialized: - plt.ioff() - plt.close(self.fig) - self.is_initialized = False - -# ===================== 配置类(动态航点版)===================== +# ===================== 核心配置 ===================== class Config: - """仿真配置类""" - # 动态航点配置 - NUM_WAYPOINTS = 50 # 生成的航点数量 - WAYPOINT_SPACING = 2.0 # 航点间距(米) - TARGET_SPEED = 30.0 # 统一目标速度(km/h) - - # 控制器参数 - WAYPOINT_THRESHOLD = 1.5 - MIN_SPEED_STANLEY = 1e-4 - STANLEY_K = 0.4 - MAX_STEER_DEG = 40.0 - MAX_STEER_RAD = math.radians(MAX_STEER_DEG) - - # CARLA配置 - CARLA_HOST = "localhost" - CARLA_PORT = 2000 - CARLA_TIMEOUT = 10.0 - - # 车辆配置(Town03主干道生成位置) + # 速度控制 + TARGET_SPEED = 20.0 # km/h + PID_KP = 0.3 + PID_KI = 0.02 + PID_KD = 0.01 + + # 纯追踪算法参数 + LOOKAHEAD_DISTANCE = 8.0 # 前瞻距离(米) + MAX_STEER_ANGLE = 30.0 # 最大转向角(度) + + # 摄像头配置 + CAMERA_WIDTH = 800 + CAMERA_HEIGHT = 600 + CAMERA_FOV = 90 + + # 车辆配置 VEHICLE_MODEL = "vehicle.tesla.model3" - SPAWN_LOCATION = carla.Location(x=100.0, y=10.0, z=0.5) # Town03开阔主干道 - SPAWN_YAW = 0.0 # 初始朝向 - - # 观察者视角(适配新生成位置) - SPECTATOR_LOCATION = carla.Location(x=110.0, y=10.0, z=8.0) - SPECTATOR_ROTATION = carla.Rotation(pitch=-30.0, yaw=0.0, roll=0.0) -# ===================== 工具类(优化版)===================== -class Utils: - """工具函数类""" + # 可视化配置 + PLOT_SIZE = (12, 10) + WAYPOINT_COUNT = 50 # 预先生成的道路航点数量 +# ===================== 工具类 ===================== +class Tools: @staticmethod - def normalize_angle(angle: float) -> float: + def normalize_angle(angle): + """将角度归一化到[-pi, pi]""" while angle > np.pi: angle -= 2 * np.pi while angle < -np.pi: @@ -216,372 +50,292 @@ def normalize_angle(angle: float) -> float: return angle @staticmethod - def get_vehicle_speed(vehicle: carla.Vehicle, unit: str = "kmh") -> float: + def get_vehicle_pose(vehicle): + """获取车辆的位置、朝向和速度""" + transform = vehicle.get_transform() + loc = transform.location + yaw = math.radians(transform.rotation.yaw) vel = vehicle.get_velocity() - speed_mps = np.linalg.norm([vel.x, vel.y, vel.z]) - return speed_mps * 3.6 if unit == "kmh" else speed_mps + speed = 3.6 * np.linalg.norm([vel.x, vel.y, vel.z]) + return loc, yaw, speed @staticmethod - def calculate_cte(vehicle_loc: carla.Location, prev_wp: List[float], target_wp: List[float]) -> float: - x1, y1 = prev_wp[0], prev_wp[1] - x2, y2 = target_wp[0], target_wp[1] - x, y = vehicle_loc.x, vehicle_loc.y - - dx = x2 - x1 - dy = y2 - y1 - if abs(dx) < 1e-6 and abs(dy) < 1e-6: - return math.hypot(x - x1, y - y1) - - if abs(dx) < 1e-6: - cte = x - x1 - else: - slope = dy / dx - a = -slope - b = 1.0 - c = slope * x1 - y1 - cte = (a * x + b * y + c) / np.sqrt(a ** 2 + b ** 2) - - yaw_path = np.arctan2(dy, dx) - yaw_ct = np.arctan2(y - y1, x - x1) - yaw_diff = Utils.normalize_angle(yaw_path - yaw_ct) - - return abs(cte) if yaw_diff > 0 else -abs(cte) + def clear_all_actors(world): + """清理所有车辆和传感器""" + for actor in world.get_actors(): + try: + if actor.type_id.startswith('vehicle') or actor.type_id.startswith('sensor'): + actor.destroy() + except: + pass + time.sleep(0.5) + print("已清理所有残留Actor") @staticmethod - def calculate_distance_2d(loc1: carla.Location, loc2: carla.Location) -> float: - dx = loc1.x - loc2.x - dy = loc1.y - loc2.y - return math.sqrt(dx ** 2 + dy ** 2) + def focus_vehicle(world, vehicle): + """将CARLA客户端视角聚焦到车辆""" + spectator = world.get_spectator() + t = vehicle.get_transform() + spectator.set_transform(carla.Transform(t.location + carla.Location(x=0, y=-8, z=5), t.rotation)) + print("CARLA客户端已聚焦到车辆") @staticmethod - def generate_road_waypoints(world, start_loc: carla.Location, num_waypoints: int, spacing: float) -> List[List[float]]: + def generate_road_waypoints(world, start_loc, count=50, step=2.0): """ - 沿道路动态生成航点(核心:避免障碍物) + 从起点沿道路生成连续的原生航点 :param world: CARLA世界对象 - :param start_loc: 起始位置 - :param num_waypoints: 航点数量 - :param spacing: 航点间距 - :return: 航点列表 [[x, y, speed], ...] + :param start_loc: 起点位置 + :param count: 航点数量 + :param step: 每个航点的步长(米) + :return: 航点列表[(x, y, z), ...] """ - map = world.get_map() waypoints = [] - current_waypoint = map.get_waypoint(start_loc) - - for i in range(num_waypoints): - # 添加当前航点(x, y, 目标速度) - waypoints.append([current_waypoint.transform.location.x, current_waypoint.transform.location.y, Config.TARGET_SPEED]) - # 获取下一个道路航点(沿道路前进) - next_waypoints = current_waypoint.next(spacing) - if not next_waypoints: - break - current_waypoint = next_waypoints[0] - - logging.info(f"动态生成了 {len(waypoints)} 个道路航点") + map = world.get_map() + wp = map.get_waypoint(start_loc) + for i in range(count): + waypoints.append((wp.transform.location.x, wp.transform.location.y, wp.transform.location.z)) + # 沿道路下一个航点(直走,不考虑分叉) + wp = wp.next(step)[0] + print(f"生成了{len(waypoints)}个原生道路航点") return waypoints -# ===================== 航点管理器(优化版)===================== -class WaypointManager: - """航点管理类""" - def __init__(self, waypoints: List[List[float]], threshold: float): - self.waypoints = waypoints - self.threshold = threshold - self.current_target_id = 1 - - def update_target(self, vehicle_loc: carla.Location) -> None: - if self.current_target_id >= len(self.waypoints) - 1: - return - - target_wp = self.waypoints[self.current_target_id] - target_loc = carla.Location(x=target_wp[0], y=target_wp[1]) - distance = Utils.calculate_distance_2d(vehicle_loc, target_loc) - - if distance < self.threshold: - self._log_waypoint_switch(distance) - self.current_target_id += 1 - - def _log_waypoint_switch(self, distance: float) -> None: - reached_id = self.current_target_id - reached_coords = (self.waypoints[reached_id][0], self.waypoints[reached_id][1]) - log_msg = f"到达航点 {reached_id} (X={reached_coords[0]:.1f}, Y={reached_coords[1]:.1f}, 距离={distance:.1f}m)" - - if self.current_target_id + 1 < len(self.waypoints): - next_id = self.current_target_id + 1 - next_coords = (self.waypoints[next_id][0], self.waypoints[next_id][1]) - log_msg += f",新目标:航点 {next_id} (X={next_coords[0]:.1f}, Y={next_coords[1]:.1f})" - - logging.info(log_msg) - - def get_current_target(self) -> List[float]: - return self.waypoints[self.current_target_id] - - def get_target_speed(self) -> float: - return self.get_current_target()[2] if self.current_target_id < len(self.waypoints) else 0.0 - -# ===================== Stanley控制器(优化版)===================== -class StanleyController: - """Stanley横向控制器""" - def __init__(self, k: float, max_steer_rad: float, min_speed: float): - self.k = k - self.max_steer_rad = max_steer_rad - self.min_speed = min_speed - self.prev_steer = 0.0 - self.smoothing_factor = 0.7 - - def calculate_steer(self, vehicle: carla.Vehicle, waypoints: List[List[float]], target_id: int) -> Tuple[float, float]: - if target_id < 1: - return 0.0, 0.0 - - vehicle_transform = vehicle.get_transform() - vehicle_loc = vehicle_transform.location - vehicle_yaw = math.radians(vehicle_transform.rotation.yaw) - vehicle_speed = Utils.get_vehicle_speed(vehicle, unit="mps") - - prev_wp = waypoints[target_id - 1] - target_wp = waypoints[target_id] - - yaw_path = np.arctan2(target_wp[1] - prev_wp[1], target_wp[0] - prev_wp[0]) - yaw_error = Utils.normalize_angle(yaw_path - vehicle_yaw) - cte = Utils.calculate_cte(vehicle_loc, prev_wp, target_wp) - - # 动态K值 - if vehicle_speed < 5: - k = self.k * 2 - elif vehicle_speed > 15: - k = self.k * 0.5 - else: - k = self.k - - safe_speed = max(vehicle_speed, self.min_speed) - cte_steer = np.arctan(k * cte / safe_speed) - - total_steer = Utils.normalize_angle(yaw_error + cte_steer) - total_steer = np.clip(total_steer, -self.max_steer_rad, self.max_steer_rad) - steer_carla = total_steer / self.max_steer_rad - - # 平滑转向 - steer_carla = self.smoothing_factor * self.prev_steer + (1 - self.smoothing_factor) * steer_carla - self.prev_steer = steer_carla - - return steer_carla, cte - -# ===================== 主仿真类(动态航点版)===================== -class CarlaSimulation: - """CARLA仿真主类""" - def __init__(self): - logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") - self.logger = logging.getLogger(__name__) - - self.config = Config() - self.client: Optional[carla.Client] = None - self.world: Optional[carla.World] = None - self.vehicle: Optional[carla.Vehicle] = None - - # 动态航点(后续生成) - self.waypoints = [] - self.waypoint_manager = None - self.stanley_controller = StanleyController( - k=self.config.STANLEY_K, - max_steer_rad=self.config.MAX_STEER_RAD, - min_speed=self.config.MIN_SPEED_STANLEY - ) - self.pid_controller = PIDController() - - self.plotter: Optional[Plotter] = None - self.pygame_display: Optional[PygameDisplay] = None - self.sim_start_time: float = 0.0 - - def connect_carla(self) -> bool: - try: - self.client = carla.Client(self.config.CARLA_HOST, self.config.CARLA_PORT) - self.client.set_timeout(self.config.CARLA_TIMEOUT) - self.world = self.client.get_world() - self.logger.info(f"成功连接到CARLA世界:{self.world.get_map().name}") - return True - except Exception as e: - self.logger.error(f"连接CARLA失败:{e}") - return False - - def cleanup_vehicles(self) -> None: - self.logger.info("清理历史车辆...") - actors = self.world.get_actors().filter("vehicle.*") - removed_count = 0 - for actor in actors: - if actor.attributes.get("role_name") == "my_car": - if actor.destroy(): - removed_count += 1 - self.logger.info(f"销毁车辆:{actor.type_id} (ID: {actor.id})") - self.logger.info(f"共销毁 {removed_count} 辆历史车辆") - - def spawn_vehicle(self) -> bool: - """在开阔道路生成车辆(避开障碍物)""" - # 获取生成位置的道路Waypoint - map = self.world.get_map() - spawn_waypoint = map.get_waypoint(self.config.SPAWN_LOCATION) - if not spawn_waypoint: - self.logger.error("生成位置不在道路上") - return False - - # 生成Transform - spawn_transform = spawn_waypoint.transform - spawn_transform.location.z += 0.5 - spawn_transform.rotation.yaw = self.config.SPAWN_YAW - - # 加载车辆蓝图 - bp_lib = self.world.get_blueprint_library() - vehicle_bp = bp_lib.filter(self.config.VEHICLE_MODEL)[0] - vehicle_bp.set_attribute("role_name", "my_car") - - # 生成车辆(重试) - self.vehicle = self.world.try_spawn_actor(vehicle_bp, spawn_transform) - if self.vehicle is None: - spawn_transform.location.z += 0.5 - self.vehicle = self.world.try_spawn_actor(vehicle_bp, spawn_transform) - if self.vehicle is None: - self.logger.error(f"无法生成车辆:{spawn_transform.location}") - return False - - # 动态生成道路航点(核心:避开障碍物) - self.waypoints = Utils.generate_road_waypoints( - world=self.world, - start_loc=self.config.SPAWN_LOCATION, - num_waypoints=self.config.NUM_WAYPOINTS, - spacing=self.config.WAYPOINT_SPACING - ) - if len(self.waypoints) < 2: - self.logger.error("生成的航点数量不足") - return False - - # 初始化航点管理器 - self.waypoint_manager = WaypointManager( - waypoints=self.waypoints, - threshold=self.config.WAYPOINT_THRESHOLD - ) - - self.logger.info(f"成功生成车辆:{self.vehicle.type_id} (ID: {self.vehicle.id})") - self.logger.info(f"生成位置:{spawn_transform.location}, 初始朝向:{spawn_transform.rotation.yaw:.1f}度") - return True - - def setup_spectator(self) -> None: - """设置观察者视角(清晰查看车辆前方)""" - spectator = self.world.get_spectator() - spectator_transform = carla.Transform( - self.config.SPECTATOR_LOCATION, - self.config.SPECTATOR_ROTATION - ) - spectator.set_transform(spectator_transform) - self.logger.info("已设置观察者视角") - - def init_visualization(self) -> None: - self.plotter = Plotter(waypoints=self.waypoints) - self.plotter.init_plot() - self.logger.info("Plotter初始化完成") - - self.pygame_display = PygameDisplay(self.world, self.vehicle) - self.logger.info("Pygame显示初始化完成") - - def run_simulation(self) -> None: - self.sim_start_time = time.time() - self.logger.info("开始仿真循环...") - - while True: - if self.pygame_display.parse_events(): - self.logger.info("用户请求退出仿真") - break - - self.world.wait_for_tick() - - # 获取车辆状态 - vehicle_transform = self.vehicle.get_transform() - vehicle_loc = vehicle_transform.location - current_speed = Utils.get_vehicle_speed(vehicle=self.vehicle, unit="kmh") - sim_time = time.time() - self.sim_start_time - - # 更新航点 - self.waypoint_manager.update_target(vehicle_loc) - target_speed = self.waypoint_manager.get_target_speed() - - # 计算控制 - throttle, brake = self.pid_controller.calculate_control(target_speed, current_speed) - steer, cte = self.stanley_controller.calculate_steer( - vehicle=self.vehicle, - waypoints=self.waypoints, - target_id=self.waypoint_manager.current_target_id - ) - - # 应用控制 - control = carla.VehicleControl() - control.throttle = throttle - control.brake = brake - control.steer = steer - control.hand_brake = False - control.manual_gear_shift = False - self.vehicle.apply_control(control) - - # 调试日志 - if int(sim_time) % 1 == 0 and not hasattr(self, f"_logged_{int(sim_time)}"): - setattr(self, f"_logged_{int(sim_time)}", True) - self.logger.info( - f"时间:{sim_time:.1f}s | " - f"速度:{current_speed:.1f}km/h | " - f"目标速度:{target_speed:.1f}km/h | " - f"转向角:{steer:.2f} | " - f"横向偏差:{cte:.2f}m" - ) +# ===================== 摄像头回调 ===================== +def camera_callback(image, data_dict): + array = np.frombuffer(image.raw_data, dtype=np.uint8).reshape((image.height, image.width, 4))[:, :, :3] + data_dict['image'] = array + +# ===================== 控制器 ===================== +class PIDSpeedController: + def __init__(self, config): + self.kp = config.PID_KP + self.ki = config.PID_KI + self.kd = config.PID_KD + self.prev_err = 0.0 + self.integral = 0.0 + self.target_speed = config.TARGET_SPEED + + def calculate(self, current_speed): + err = self.target_speed - current_speed + self.integral = np.clip(self.integral + err * 0.05, -1.0, 1.0) + deriv = (err - self.prev_err) / 0.05 + output = self.kp * err + self.ki * self.integral + self.kd * deriv + self.prev_err = err + return np.clip(output, 0.1, 1.0) + +class PurePursuitController: + def __init__(self, config): + self.lookahead_dist = config.LOOKAHEAD_DISTANCE + self.max_steer_rad = math.radians(config.MAX_STEER_ANGLE) + + def calculate_steer(self, vehicle_loc, vehicle_yaw, waypoints): + """ + 纯追踪算法计算转向角 + :param vehicle_loc: 车辆位置 + :param vehicle_yaw: 车辆朝向(弧度) + :param waypoints: 道路航点列表 + :return: 转向角(-1~1) + """ + # 1. 将航点转换为车辆坐标系 + wp_coords = np.array(waypoints) + vehicle_x = vehicle_loc.x + vehicle_y = vehicle_loc.y + + # 旋转和平移(车辆坐标系:x向前,y向左) + cos_yaw = math.cos(vehicle_yaw) + sin_yaw = math.sin(vehicle_yaw) + translated_x = wp_coords[:, 0] - vehicle_x + translated_y = wp_coords[:, 1] - vehicle_y + rotated_x = translated_x * cos_yaw + translated_y * sin_yaw + rotated_y = -translated_x * sin_yaw + translated_y * cos_yaw + + # 2. 找到距离车辆>=前瞻距离的第一个航点 + distances = np.hypot(rotated_x, rotated_y) + valid_wp_indices = np.where(distances >= self.lookahead_dist)[0] + if len(valid_wp_indices) == 0: + return 0.0 + + target_idx = valid_wp_indices[0] + target_x = rotated_x[target_idx] + target_y = rotated_y[target_idx] + + # 3. 计算转向角(纯追踪公式:steer = arctan(2*L*y/(x²+y²)),L为车辆轴距,这里简化为1.0) + L = 1.0 # 车辆轴距(米) + steer_rad = math.atan2(2 * L * target_y, self.lookahead_dist ** 2) + + # 4. 限制转向角 + steer_rad = np.clip(steer_rad, -self.max_steer_rad, self.max_steer_rad) + steer = steer_rad / self.max_steer_rad + + return steer + +# ===================== 可视化 ===================== +class Visualizer: + def __init__(self, config, spawn_loc, initial_waypoints): + self.waypoints = np.array(initial_waypoints) + self.trajectory = [] + self.spawn_loc = (spawn_loc.x, spawn_loc.y) + + plt.rcParams['backend'] = 'TkAgg' + plt.ioff() + self.fig, self.ax = plt.subplots(figsize=config.PLOT_SIZE) + + # 绘制道路航点(CARLA原生) + self.ax.scatter(self.waypoints[:, 0], self.waypoints[:, 1], c='blue', s=50, label='Road Waypoints (CARLA)', zorder=3) + # 绘制生成点 + self.ax.scatter(self.spawn_loc[0], self.spawn_loc[1], c='orange', marker='s', s=150, label='Spawn Point', zorder=5) + # 轨迹和车辆 + self.traj_line, = self.ax.plot([], [], c='red', linewidth=4, label='Vehicle Trajectory', zorder=2) + self.vehicle_dot, = self.ax.plot([], [], c='green', marker='o', markersize=20, label='Vehicle', zorder=6) + + self.ax.set_xlabel('X (m)', fontsize=14) + self.ax.set_ylabel('Y (m)', fontsize=14) + self.ax.set_title('CARLA Road Following (Native Waypoints)', fontsize=16) + self.ax.legend(fontsize=12) + self.ax.grid(True, alpha=0.3) + self.ax.axis('equal') + + plt.show(block=False) + self.fig.canvas.draw() + self.fig.canvas.flush_events() + + def update(self, vehicle_x, vehicle_y, new_waypoints=None): + """更新轨迹和航点""" + self.trajectory.append([vehicle_x, vehicle_y]) + if len(self.trajectory) > 1000: + self.trajectory = self.trajectory[-1000:] + + # 更新轨迹 + traj = np.array(self.trajectory) + self.traj_line.set_data(traj[:, 0], traj[:, 1]) + self.vehicle_dot.set_data(vehicle_x, vehicle_y) + + # 更新航点(如果有新航点) + if new_waypoints is not None: + self.waypoints = np.array(new_waypoints) + self.ax.scatter(self.waypoints[:, 0], self.waypoints[:, 1], c='blue', s=50, zorder=3) + + self.ax.relim() + self.ax.autoscale_view(True, True, True) + self.fig.canvas.draw() + self.fig.canvas.flush_events() - # 更新可视化 - if self.plotter and self.plotter.is_initialized: - try: - self.plotter.update_plot(sim_time, vehicle_loc.x, vehicle_loc.y, current_speed, target_speed) - except Exception as e: - self.logger.warning(f"绘图更新失败:{e}") - self.plotter.cleanup_plot() - self.plotter = None +# ===================== 主函数 ===================== +def main(): + config = Config() + tools = Tools() + + # 初始化OpenCV窗口 + cv2.namedWindow('CARLA Vehicle View', cv2.WINDOW_NORMAL) + cv2.resizeWindow('CARLA Vehicle View', config.CAMERA_WIDTH, config.CAMERA_HEIGHT) + + # 连接CARLA + try: + client = carla.Client('localhost', 2000) + client.set_timeout(30.0) + world = client.load_world('Town03') + map = world.get_map() - if self.pygame_display: - self.pygame_display.render() + # 设置同步模式 + settings = world.get_settings() + settings.synchronous_mode = True + settings.fixed_delta_seconds = 0.05 + world.apply_settings(settings) + + # 清理Actor + tools.clear_all_actors(world) + + # 获取道路生成点 + spawn_points = map.get_spawn_points() + spawn_transform = spawn_points[0] + print(f"使用道路生成点:{spawn_transform.location}") + + # 生成车辆 + bp_lib = world.get_blueprint_library() + vehicle_bp = bp_lib.find(config.VEHICLE_MODEL) + vehicle = world.spawn_actor(vehicle_bp, spawn_transform) + if not vehicle: + print("车辆生成失败!") + return + print(f"车辆{config.VEHICLE_MODEL}生成成功") - def cleanup(self) -> None: - self.logger.info("开始清理资源...") + # 聚焦车辆 + tools.focus_vehicle(world, vehicle) - if self.pygame_display: - self.pygame_display.destroy() - self.logger.info("Pygame显示已销毁") + # 挂载摄像头 + camera_bp = bp_lib.find('sensor.camera.rgb') + camera_bp.set_attribute('image_size_x', str(config.CAMERA_WIDTH)) + camera_bp.set_attribute('image_size_y', str(config.CAMERA_HEIGHT)) + camera_bp.set_attribute('fov', str(config.CAMERA_FOV)) + camera = world.spawn_actor(camera_bp, carla.Transform(carla.Location(x=2.0, z=1.8)), attach_to=vehicle) - if self.plotter and self.plotter.is_initialized: - self.plotter.cleanup_plot() - self.logger.info("绘图器已清理") + # 摄像头数据 + camera_data = {'image': np.zeros((config.CAMERA_HEIGHT, config.CAMERA_WIDTH, 3), dtype=np.uint8)} + camera.listen(lambda img: camera_callback(img, camera_data)) - if self.vehicle and self.vehicle.is_alive: - self.vehicle.destroy() - self.logger.info("车辆已销毁") + # 生成初始道路航点 + initial_waypoints = tools.generate_road_waypoints(world, spawn_transform.location, config.WAYPOINT_COUNT) - self.logger.info("资源清理完成") + # 初始化控制器 + speed_controller = PIDSpeedController(config) + path_controller = PurePursuitController(config) - def start(self) -> None: - try: - if not self.connect_carla(): - return + # 初始化可视化 + visualizer = Visualizer(config, spawn_transform.location, initial_waypoints) - self.cleanup_vehicles() + # 主循环 + while True: + world.tick() - if not self.spawn_vehicle(): - return + # 获取车辆状态 + vehicle_loc, vehicle_yaw, current_speed = tools.get_vehicle_pose(vehicle) - self.setup_spectator() - self.init_visualization() - self.run_simulation() + # 实时更新道路航点(每10帧更新一次,减少计算量) + if world.get_snapshot().frame % 10 == 0: + new_waypoints = tools.generate_road_waypoints(world, vehicle_loc, config.WAYPOINT_COUNT) + else: + new_waypoints = None - except KeyboardInterrupt: - self.logger.info("用户中断仿真") - except Exception as e: - self.logger.error(f"仿真异常:{e}", exc_info=True) - finally: - self.cleanup() + # 更新可视化 + visualizer.update(vehicle_loc.x, vehicle_loc.y, new_waypoints) -# ===================== 主函数 ===================== -def main(): - simulation = CarlaSimulation() - simulation.start() + # 显示摄像头画面 + cv2.imshow('CARLA Vehicle View', camera_data['image']) + if cv2.waitKey(1) & 0xFF == ord('q'): + break -if __name__ == "__main__": + # 计算控制量 + steer = path_controller.calculate_steer(vehicle_loc, vehicle_yaw, initial_waypoints) + throttle = speed_controller.calculate(current_speed) + brake = 1.0 if current_speed > config.TARGET_SPEED * 2 else 0.0 + + # 控制车辆 + vehicle.apply_control(carla.VehicleControl( + throttle=throttle, + brake=brake, + steer=steer, + hand_brake=False, + reverse=False + )) + + # 打印状态 + print(f"速度:{current_speed:.1f}km/h | 位置:({vehicle_loc.x:.1f}, {vehicle_loc.y:.1f}) | 转向:{steer:.2f}", end='\r') + + except Exception as e: + print(f"\n程序异常:{e}") + finally: + # 清理资源 + print("\n清理资源中...") + settings = world.get_settings() + settings.synchronous_mode = False + world.apply_settings(settings) + if 'vehicle' in locals(): + vehicle.destroy() + if 'camera' in locals(): + camera.destroy() + cv2.destroyAllWindows() + plt.close('all') + time.sleep(1) + print("仿真结束") + +if __name__ == '__main__': main() \ No newline at end of file From 9c79eecc021b0aa835c537580c2789d136411399 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Sat, 20 Dec 2025 22:50:45 +0800 Subject: [PATCH 12/26] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=81=BF=E9=9A=9C?= =?UTF-8?q?=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/main.py | 798 ++++++++++++++++++----------- 1 file changed, 488 insertions(+), 310 deletions(-) diff --git a/src/autonomous_driving_car/main.py b/src/autonomous_driving_car/main.py index 2e6e2fd2f2..422e90076b 100644 --- a/src/autonomous_driving_car/main.py +++ b/src/autonomous_driving_car/main.py @@ -1,341 +1,519 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -CARLA Waypoint Following(原生道路航点版) -核心:直接使用CARLA地图的道路航点,车辆100%沿道路行驶 -""" - -import sys -import os import carla +import time import numpy as np -import math -import matplotlib.pyplot as plt import cv2 -import time +import math +from collections import deque +import random +import os -# ===================== 核心配置 ===================== -class Config: - # 速度控制 - TARGET_SPEED = 20.0 # km/h - PID_KP = 0.3 - PID_KI = 0.02 - PID_KD = 0.01 - - # 纯追踪算法参数 - LOOKAHEAD_DISTANCE = 8.0 # 前瞻距离(米) - MAX_STEER_ANGLE = 30.0 # 最大转向角(度) - - # 摄像头配置 - CAMERA_WIDTH = 800 - CAMERA_HEIGHT = 600 - CAMERA_FOV = 90 - - # 车辆配置 - VEHICLE_MODEL = "vehicle.tesla.model3" - - # 可视化配置 - PLOT_SIZE = (12, 10) - WAYPOINT_COUNT = 50 # 预先生成的道路航点数量 - -# ===================== 工具类 ===================== -class Tools: - @staticmethod - def normalize_angle(angle): - """将角度归一化到[-pi, pi]""" - while angle > np.pi: - angle -= 2 * np.pi - while angle < -np.pi: - angle += 2 * np.pi - return angle - - @staticmethod - def get_vehicle_pose(vehicle): - """获取车辆的位置、朝向和速度""" +# -------------------------- +# 1. 障碍物检测器(优化性能+无延迟检测) +# -------------------------- +class ObstacleDetector: + def __init__(self, world, vehicle, max_distance=50.0, detect_interval=1): # 改为1帧检测一次 + self.world = world + self.vehicle = vehicle + self.max_distance = max_distance + self.detect_interval = detect_interval # 检测间隔(帧) + self.frame_count = 0 + self.last_obstacle_info = { + 'has_obstacle': False, + 'distance': float('inf'), + 'relative_angle': 0.0, + 'obstacle_type': None, + 'obstacle_speed': 0.0, + 'relative_speed': 0.0 # 新增:自车与障碍物的相对速度 + } + + def get_vehicle_speed(self, vehicle): + """获取车辆速度(km/h)""" + velocity = vehicle.get_velocity() + speed = math.sqrt(velocity.x ** 2 + velocity.y ** 2 + velocity.z ** 2) + return speed * 3.6 + + def get_obstacle_info(self): + """检测前方障碍物信息(无延迟检测)""" + self.frame_count += 1 + if self.frame_count % self.detect_interval != 0: + return self.last_obstacle_info + + try: + vehicle_transform = self.vehicle.get_transform() + vehicle_location = vehicle_transform.location + forward_vector = vehicle_transform.get_forward_vector() + self_speed = self.get_vehicle_speed(self.vehicle) # 自车速度 + + # 减少get_actors调用频率,只获取车辆 + all_vehicles = self.world.get_actors().filter('vehicle.*') + min_distance = float('inf') + closest_obstacle = None + relative_angle = 0.0 + obstacle_speed = 0.0 + + for other_vehicle in all_vehicles: + if other_vehicle.id == self.vehicle.id: + continue + + other_location = other_vehicle.get_location() + distance = vehicle_location.distance(other_location) + if distance > self.max_distance: + continue + + # 计算相对角度(仅前方±70度,扩大检测范围) + relative_vector = carla.Location( + other_location.x - vehicle_location.x, + other_location.y - vehicle_location.y, + 0 + ) + forward_2d = carla.Vector3D(forward_vector.x, forward_vector.y, 0) + relative_2d = carla.Vector3D(relative_vector.x, relative_vector.y, 0) + + # 向量归一化 + forward_norm = math.sqrt(forward_2d.x ** 2 + forward_2d.y ** 2) + relative_norm = math.sqrt(relative_2d.x ** 2 + relative_2d.y ** 2) + if forward_norm == 0 or relative_norm == 0: + continue + + dot_product = forward_2d.x * relative_2d.x + forward_2d.y * relative_2d.y + cos_angle = dot_product / (forward_norm * relative_norm) + cos_angle = max(-1.0, min(1.0, cos_angle)) + angle_deg = math.degrees(math.acos(cos_angle)) + + # 扩大检测角度到±70度,更早发现障碍物 + if angle_deg <= 70 and distance < min_distance: + min_distance = distance + closest_obstacle = other_vehicle + obstacle_speed = self.get_vehicle_speed(other_vehicle) + # 确定角度方向 + relative_angle = angle_deg if relative_2d.y >= 0 else -angle_deg + + # 计算相对速度(自车速度 - 前车速度,正数表示自车更快) + relative_speed = self_speed - obstacle_speed if closest_obstacle else 0.0 + + # 更新障碍物信息 + if closest_obstacle is not None: + self.last_obstacle_info = { + 'has_obstacle': True, + 'distance': min_distance, + 'relative_angle': relative_angle, + 'obstacle_type': closest_obstacle.type_id, + 'obstacle_speed': obstacle_speed, + 'relative_speed': relative_speed + } + else: + self.last_obstacle_info = { + 'has_obstacle': False, + 'distance': float('inf'), + 'relative_angle': 0.0, + 'obstacle_type': None, + 'obstacle_speed': 0.0, + 'relative_speed': 0.0 + } + + except Exception as e: + print(f"障碍物检测错误: {e}") + + return self.last_obstacle_info + + def visualize_obstacles(self, image, vehicle_transform): + """在图像上可视化障碍物检测结果""" + if not self.last_obstacle_info['has_obstacle']: + return image + + height, width = image.shape[:2] + distance = self.last_obstacle_info['distance'] + angle = self.last_obstacle_info['relative_angle'] + + # 计算障碍物在图像中的位置 + x_pos = int(width / 2 + (angle / 70) * (width / 2)) # 适配70度检测范围 + x_pos = max(0, min(width - 1, x_pos)) + + # 根据距离设置颜色和大小 + if distance < 15: + color = (0, 0, 255) + radius = 15 + elif distance < 30: + color = (0, 165, 255) + radius = 10 + else: + color = (0, 255, 255) + radius = 5 + + # 绘制障碍物指示器 + cv2.circle(image, (x_pos, int(height * 0.8)), radius, color, -1) + cv2.putText(image, f"{distance:.1f}m", (x_pos - 20, int(height * 0.8) - 20), + cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) + # 绘制相对速度 + rel_speed = self.last_obstacle_info['relative_speed'] + cv2.putText(image, f"RelSpeed: {rel_speed:.1f}km/h", (x_pos - 20, int(height * 0.8) + 20), + cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) + return image + +# -------------------------- +# 2. 传统控制器(核心控制逻辑+优化避障) +# -------------------------- +class TraditionalController: + """基于路点的传统控制器,整合避障""" + def __init__(self, world, obstacle_detector): + self.world = world + self.map = world.get_map() + self.waypoint_distance = 10.0 + self.obstacle_detector = obstacle_detector + # 调整避障阈值:扩大距离,提前减速 + self.emergency_brake_distance = 12.0 # 从6米改为12米 + self.safe_following_distance = 18.0 # 从10米改为18米 + self.early_warning_distance = 30.0 # 新增:30米提前预警 + + def apply_obstacle_avoidance(self, throttle, brake, steer, vehicle, obstacle_info): + """传统控制器的避障逻辑(优化刹车力度+相对速度)""" + if not obstacle_info['has_obstacle']: + return throttle, brake, steer + + distance = obstacle_info['distance'] + angle = obstacle_info['relative_angle'] + vehicle_speed = self.obstacle_detector.get_vehicle_speed(vehicle) + relative_speed = obstacle_info['relative_speed'] # 自车与前车的相对速度 + + # 1. 提前预警(30米内):轻微减速,降低油门 + if distance < self.early_warning_distance and relative_speed > 0: + throttle *= 0.5 # 油门减半 + if vehicle_speed > 30: + brake = 0.2 # 轻微刹车 + + # 2. 紧急刹车(12米内):全力刹车+手刹 + if distance < self.emergency_brake_distance: + print(f"紧急刹车!距离前车: {distance:.1f}m, 相对速度: {relative_speed:.1f}km/h") + return 0.0, 1.0, 0.0 # brake=1.0 + 后续拉手刹 + + # 3. 安全跟车(18米内):动态调整刹车力度 + elif distance < self.safe_following_distance: + # 根据距离和相对速度计算所需刹车力度 + required_distance = max(8.0, vehicle_speed * 0.5) # 增加安全车距系数 + distance_ratio = (distance - required_distance) / self.safe_following_distance + distance_ratio = max(0.0, min(1.0, distance_ratio)) + + # 相对速度越大,刹车越重 + brake_strength = (1 - distance_ratio) * 0.8 + (relative_speed / 20) * 0.2 + brake_strength = max(0.3, min(0.8, brake_strength)) + + if distance < required_distance: + throttle = 0.0 + brake = brake_strength + else: + throttle = 0.1 + brake = 0.0 + + # 尝试变道 + if abs(angle) < 15: + location = vehicle.get_location() + waypoint = self.map.get_waypoint(location) + left_lane = waypoint.get_left_lane() + right_lane = waypoint.get_right_lane() + + if left_lane and left_lane.lane_type == carla.LaneType.Driving: + steer = -0.3 + elif right_lane and right_lane.lane_type == carla.LaneType.Driving: + steer = 0.3 + else: + steer = 0.2 if angle >= 0 else -0.2 + + return throttle, brake, steer + + def get_control(self, vehicle): + """生成传统控制指令(优先避障,弱化基础速度控制)""" + # 获取车辆状态 transform = vehicle.get_transform() - loc = transform.location - yaw = math.radians(transform.rotation.yaw) - vel = vehicle.get_velocity() - speed = 3.6 * np.linalg.norm([vel.x, vel.y, vel.z]) - return loc, yaw, speed - - @staticmethod - def clear_all_actors(world): - """清理所有车辆和传感器""" - for actor in world.get_actors(): - try: - if actor.type_id.startswith('vehicle') or actor.type_id.startswith('sensor'): - actor.destroy() - except: - pass - time.sleep(0.5) - print("已清理所有残留Actor") - - @staticmethod - def focus_vehicle(world, vehicle): - """将CARLA客户端视角聚焦到车辆""" - spectator = world.get_spectator() - t = vehicle.get_transform() - spectator.set_transform(carla.Transform(t.location + carla.Location(x=0, y=-8, z=5), t.rotation)) - print("CARLA客户端已聚焦到车辆") - - @staticmethod - def generate_road_waypoints(world, start_loc, count=50, step=2.0): - """ - 从起点沿道路生成连续的原生航点 - :param world: CARLA世界对象 - :param start_loc: 起点位置 - :param count: 航点数量 - :param step: 每个航点的步长(米) - :return: 航点列表[(x, y, z), ...] - """ - waypoints = [] - map = world.get_map() - wp = map.get_waypoint(start_loc) - for i in range(count): - waypoints.append((wp.transform.location.x, wp.transform.location.y, wp.transform.location.z)) - # 沿道路下一个航点(直走,不考虑分叉) - wp = wp.next(step)[0] - print(f"生成了{len(waypoints)}个原生道路航点") - return waypoints - -# ===================== 摄像头回调 ===================== -def camera_callback(image, data_dict): - array = np.frombuffer(image.raw_data, dtype=np.uint8).reshape((image.height, image.width, 4))[:, :, :3] - data_dict['image'] = array - -# ===================== 控制器 ===================== -class PIDSpeedController: - def __init__(self, config): - self.kp = config.PID_KP - self.ki = config.PID_KI - self.kd = config.PID_KD - self.prev_err = 0.0 - self.integral = 0.0 - self.target_speed = config.TARGET_SPEED - - def calculate(self, current_speed): - err = self.target_speed - current_speed - self.integral = np.clip(self.integral + err * 0.05, -1.0, 1.0) - deriv = (err - self.prev_err) / 0.05 - output = self.kp * err + self.ki * self.integral + self.kd * deriv - self.prev_err = err - return np.clip(output, 0.1, 1.0) - -class PurePursuitController: - def __init__(self, config): - self.lookahead_dist = config.LOOKAHEAD_DISTANCE - self.max_steer_rad = math.radians(config.MAX_STEER_ANGLE) - - def calculate_steer(self, vehicle_loc, vehicle_yaw, waypoints): - """ - 纯追踪算法计算转向角 - :param vehicle_loc: 车辆位置 - :param vehicle_yaw: 车辆朝向(弧度) - :param waypoints: 道路航点列表 - :return: 转向角(-1~1) - """ - # 1. 将航点转换为车辆坐标系 - wp_coords = np.array(waypoints) - vehicle_x = vehicle_loc.x - vehicle_y = vehicle_loc.y - - # 旋转和平移(车辆坐标系:x向前,y向左) - cos_yaw = math.cos(vehicle_yaw) - sin_yaw = math.sin(vehicle_yaw) - translated_x = wp_coords[:, 0] - vehicle_x - translated_y = wp_coords[:, 1] - vehicle_y - rotated_x = translated_x * cos_yaw + translated_y * sin_yaw - rotated_y = -translated_x * sin_yaw + translated_y * cos_yaw - - # 2. 找到距离车辆>=前瞻距离的第一个航点 - distances = np.hypot(rotated_x, rotated_y) - valid_wp_indices = np.where(distances >= self.lookahead_dist)[0] - if len(valid_wp_indices) == 0: - return 0.0 - - target_idx = valid_wp_indices[0] - target_x = rotated_x[target_idx] - target_y = rotated_y[target_idx] - - # 3. 计算转向角(纯追踪公式:steer = arctan(2*L*y/(x²+y²)),L为车辆轴距,这里简化为1.0) - L = 1.0 # 车辆轴距(米) - steer_rad = math.atan2(2 * L * target_y, self.lookahead_dist ** 2) - - # 4. 限制转向角 - steer_rad = np.clip(steer_rad, -self.max_steer_rad, self.max_steer_rad) - steer = steer_rad / self.max_steer_rad - - return steer - -# ===================== 可视化 ===================== -class Visualizer: - def __init__(self, config, spawn_loc, initial_waypoints): - self.waypoints = np.array(initial_waypoints) - self.trajectory = [] - self.spawn_loc = (spawn_loc.x, spawn_loc.y) - - plt.rcParams['backend'] = 'TkAgg' - plt.ioff() - self.fig, self.ax = plt.subplots(figsize=config.PLOT_SIZE) - - # 绘制道路航点(CARLA原生) - self.ax.scatter(self.waypoints[:, 0], self.waypoints[:, 1], c='blue', s=50, label='Road Waypoints (CARLA)', zorder=3) - # 绘制生成点 - self.ax.scatter(self.spawn_loc[0], self.spawn_loc[1], c='orange', marker='s', s=150, label='Spawn Point', zorder=5) - # 轨迹和车辆 - self.traj_line, = self.ax.plot([], [], c='red', linewidth=4, label='Vehicle Trajectory', zorder=2) - self.vehicle_dot, = self.ax.plot([], [], c='green', marker='o', markersize=20, label='Vehicle', zorder=6) - - self.ax.set_xlabel('X (m)', fontsize=14) - self.ax.set_ylabel('Y (m)', fontsize=14) - self.ax.set_title('CARLA Road Following (Native Waypoints)', fontsize=16) - self.ax.legend(fontsize=12) - self.ax.grid(True, alpha=0.3) - self.ax.axis('equal') - - plt.show(block=False) - self.fig.canvas.draw() - self.fig.canvas.flush_events() - - def update(self, vehicle_x, vehicle_y, new_waypoints=None): - """更新轨迹和航点""" - self.trajectory.append([vehicle_x, vehicle_y]) - if len(self.trajectory) > 1000: - self.trajectory = self.trajectory[-1000:] - - # 更新轨迹 - traj = np.array(self.trajectory) - self.traj_line.set_data(traj[:, 0], traj[:, 1]) - self.vehicle_dot.set_data(vehicle_x, vehicle_y) - - # 更新航点(如果有新航点) - if new_waypoints is not None: - self.waypoints = np.array(new_waypoints) - self.ax.scatter(self.waypoints[:, 0], self.waypoints[:, 1], c='blue', s=50, zorder=3) - - self.ax.relim() - self.ax.autoscale_view(True, True, True) - self.fig.canvas.draw() - self.fig.canvas.flush_events() - -# ===================== 主函数 ===================== + location = vehicle.get_location() + velocity = vehicle.get_velocity() + speed = math.sqrt(velocity.x ** 2 + velocity.y ** 2 + velocity.z ** 2) * 3.6 + + # 获取障碍物信息 + obstacle_info = self.obstacle_detector.get_obstacle_info() + + # 获取路点 + waypoint = self.map.get_waypoint(location, project_to_road=True) + next_waypoints = waypoint.next(self.waypoint_distance) + target_waypoint = next_waypoints[0] if next_waypoints else waypoint + + # 计算转向 + vehicle_yaw = math.radians(transform.rotation.yaw) + target_loc = target_waypoint.transform.location + + dx = target_loc.x - location.x + dy = target_loc.y - location.y + + local_x = dx * math.cos(vehicle_yaw) + dy * math.sin(vehicle_yaw) + local_y = -dx * math.sin(vehicle_yaw) + dy * math.cos(vehicle_yaw) + + if abs(local_x) < 0.1: + steer = 0.0 + else: + angle = math.atan2(local_y, local_x) + steer = np.clip(angle / math.radians(45), -1.0, 1.0) + + # 基础速度控制(降低优先级) + if speed < 20: + throttle = 0.4 # 从0.6降低到0.4,减少油门 + brake = 0.0 + elif speed < 40: + throttle = 0.2 # 从0.4降低到0.2 + brake = 0.0 + else: + throttle = 0.1 + brake = 0.2 + + # 障碍物调整(优先执行) + throttle, brake, steer = self.apply_obstacle_avoidance(throttle, brake, steer, vehicle, obstacle_info) + + # 低速强油门(仅当无障碍物时生效) + if speed < 5.0 and not obstacle_info['has_obstacle']: + throttle = 0.4 + brake = 0.0 + + return throttle, brake, steer + +# -------------------------- +# 3. CARLA环境初始化(适配0.9.10) +# -------------------------- def main(): - config = Config() - tools = Tools() - - # 初始化OpenCV窗口 - cv2.namedWindow('CARLA Vehicle View', cv2.WINDOW_NORMAL) - cv2.resizeWindow('CARLA Vehicle View', config.CAMERA_WIDTH, config.CAMERA_HEIGHT) - - # 连接CARLA try: + # 连接CARLA服务器(0.9.10兼容) client = carla.Client('localhost', 2000) - client.set_timeout(30.0) - world = client.load_world('Town03') - map = world.get_map() + client.set_timeout(15.0) + world = client.load_world('Town01') # 0.9.10支持Town01 - # 设置同步模式 + # 设置同步模式(0.9.10关键配置) settings = world.get_settings() settings.synchronous_mode = True - settings.fixed_delta_seconds = 0.05 + settings.fixed_delta_seconds = 0.1 world.apply_settings(settings) - # 清理Actor - tools.clear_all_actors(world) + # 设置天气 + weather = carla.WeatherParameters( + cloudiness=30.0, + precipitation=0.0, + sun_altitude_angle=70.0 + ) + world.set_weather(weather) - # 获取道路生成点 + # 获取出生点 + map = world.get_map() spawn_points = map.get_spawn_points() - spawn_transform = spawn_points[0] - print(f"使用道路生成点:{spawn_transform.location}") - - # 生成车辆 - bp_lib = world.get_blueprint_library() - vehicle_bp = bp_lib.find(config.VEHICLE_MODEL) - vehicle = world.spawn_actor(vehicle_bp, spawn_transform) + if not spawn_points: + raise Exception("无可用出生点") + spawn_point = spawn_points[10] + + # 生成主车辆 + blueprint_library = world.get_blueprint_library() + vehicle_bp = blueprint_library.find('vehicle.tesla.model3') + vehicle_bp.set_attribute('color', '255,0,0') + vehicle = world.spawn_actor(vehicle_bp, spawn_point) if not vehicle: - print("车辆生成失败!") - return - print(f"车辆{config.VEHICLE_MODEL}生成成功") - - # 聚焦车辆 - tools.focus_vehicle(world, vehicle) - - # 挂载摄像头 - camera_bp = bp_lib.find('sensor.camera.rgb') - camera_bp.set_attribute('image_size_x', str(config.CAMERA_WIDTH)) - camera_bp.set_attribute('image_size_y', str(config.CAMERA_HEIGHT)) - camera_bp.set_attribute('fov', str(config.CAMERA_FOV)) - camera = world.spawn_actor(camera_bp, carla.Transform(carla.Location(x=2.0, z=1.8)), attach_to=vehicle) - - # 摄像头数据 - camera_data = {'image': np.zeros((config.CAMERA_HEIGHT, config.CAMERA_WIDTH, 3), dtype=np.uint8)} - camera.listen(lambda img: camera_callback(img, camera_data)) - - # 生成初始道路航点 - initial_waypoints = tools.generate_road_waypoints(world, spawn_transform.location, config.WAYPOINT_COUNT) - - # 初始化控制器 - speed_controller = PIDSpeedController(config) - path_controller = PurePursuitController(config) - - # 初始化可视化 - visualizer = Visualizer(config, spawn_transform.location, initial_waypoints) + raise Exception("无法生成主车辆") + vehicle.set_autopilot(False) + print(f"车辆生成在位置: {spawn_point.location}") + + # 生成障碍物车辆(调整生成位置,确保在前车前方) + obstacle_count = 3 + for i in range(obstacle_count): + spawn_idx = (i + 12) % len(spawn_points) # 从15改为12,更靠近主车辆 + other_vehicle_bp = random.choice(blueprint_library.filter('vehicle.*')) + other_vehicle = world.try_spawn_actor(other_vehicle_bp, spawn_points[spawn_idx]) + if other_vehicle: + other_vehicle.set_autopilot(True) + + # 配置传感器(0.9.10兼容) + # 后视角相机 + third_camera_bp = blueprint_library.find('sensor.camera.rgb') + third_camera_bp.set_attribute('image_size_x', '640') + third_camera_bp.set_attribute('image_size_y', '480') + third_camera_bp.set_attribute('fov', '110') + third_camera_transform = carla.Transform( + carla.Location(x=-5.0, y=0.0, z=3.0), + carla.Rotation(pitch=-15.0) + ) + third_camera = world.spawn_actor(third_camera_bp, third_camera_transform, attach_to=vehicle) + + # 前视角相机 + front_camera_bp = blueprint_library.find('sensor.camera.rgb') + front_camera_bp.set_attribute('image_size_x', '640') + front_camera_bp.set_attribute('image_size_y', '480') + front_camera_bp.set_attribute('fov', '90') + front_camera_transform = carla.Transform( + carla.Location(x=2.0, y=0.0, z=1.5), + carla.Rotation(pitch=0.0) + ) + front_camera = world.spawn_actor(front_camera_bp, front_camera_transform, attach_to=vehicle) + + # 传感器数据存储 + third_image = None + front_image = None + + # 传感器回调函数 + def third_camera_callback(image): + nonlocal third_image + array = np.frombuffer(image.raw_data, dtype=np.uint8) + array = np.reshape(array, (image.height, image.width, 4)) + third_image = array[:, :, :3] + + def front_camera_callback(image): + nonlocal front_image + array = np.frombuffer(image.raw_data, dtype=np.uint8) + array = np.reshape(array, (image.height, image.width, 4)) + front_image = array[:, :, :3] + + # 注册回调 + third_camera.listen(third_camera_callback) + front_camera.listen(front_camera_callback) + time.sleep(2.0) # 等待传感器初始化 + + # 初始化核心组件 + obstacle_detector = ObstacleDetector(world, vehicle) + traditional_controller = TraditionalController(world, obstacle_detector) + + # 控制变量 + throttle = 0.3 + steer = 0.0 + brake = 0.0 + frame_count = 0 + stuck_count = 0 + last_position = vehicle.get_location() # 主循环 + print("自动驾驶系统启动 - 仅使用传统控制器") + print("控制键: q-退出, r-重置车辆") + while True: world.tick() + frame_count += 1 # 获取车辆状态 - vehicle_loc, vehicle_yaw, current_speed = tools.get_vehicle_pose(vehicle) - - # 实时更新道路航点(每10帧更新一次,减少计算量) - if world.get_snapshot().frame % 10 == 0: - new_waypoints = tools.generate_road_waypoints(world, vehicle_loc, config.WAYPOINT_COUNT) + vehicle_transform = vehicle.get_transform() + vehicle_location = vehicle.get_transform().location + vehicle_velocity = vehicle.get_velocity() + vehicle_speed = math.sqrt(vehicle_velocity.x ** 2 + vehicle_velocity.y ** 2 + vehicle_velocity.z ** 2) + + # 检测障碍物 + obstacle_info = obstacle_detector.get_obstacle_info() + + # 卡住检测(优化:有障碍物时不触发) + distance_moved = vehicle_location.distance(last_position) + is_moving = distance_moved > 0.2 or vehicle_speed > 1.0 + # 关键修正:通过traditional_controller实例访问属性,而非self + if obstacle_info['has_obstacle'] and obstacle_info['distance'] < traditional_controller.safe_following_distance: + stuck_count = 0 else: - new_waypoints = None - - # 更新可视化 - visualizer.update(vehicle_loc.x, vehicle_loc.y, new_waypoints) - - # 显示摄像头画面 - cv2.imshow('CARLA Vehicle View', camera_data['image']) - if cv2.waitKey(1) & 0xFF == ord('q'): - break - - # 计算控制量 - steer = path_controller.calculate_steer(vehicle_loc, vehicle_yaw, initial_waypoints) - throttle = speed_controller.calculate(current_speed) - brake = 1.0 if current_speed > config.TARGET_SPEED * 2 else 0.0 - - # 控制车辆 - vehicle.apply_control(carla.VehicleControl( - throttle=throttle, - brake=brake, - steer=steer, - hand_brake=False, - reverse=False - )) - - # 打印状态 - print(f"速度:{current_speed:.1f}km/h | 位置:({vehicle_loc.x:.1f}, {vehicle_loc.y:.1f}) | 转向:{steer:.2f}", end='\r') + if not is_moving: + stuck_count += 1 + else: + stuck_count = 0 + last_position = vehicle_location + + # 卡住恢复(仅当无障碍物时执行) + if stuck_count > 20: # 从15帧改为20帧,降低误触发 + print("检测到车辆卡住,执行恢复程序...") + # 紧急刹车 + vehicle.apply_control(carla.VehicleControl(throttle=0.0, steer=0.0, brake=1.0, hand_brake=True)) + time.sleep(0.5) + # 倒车或转向 + if obstacle_info['has_obstacle'] and obstacle_info['distance'] < 15: + vehicle.apply_control(carla.VehicleControl(throttle=0.0, steer=0.0, brake=0.0, reverse=True)) + time.sleep(1.0) + else: + recovery_steer = random.choice([-0.5, 0.5]) + vehicle.apply_control(carla.VehicleControl(throttle=0.6, steer=recovery_steer, brake=0.0)) # 降低油门到0.6 + time.sleep(1.0) + stuck_count = 0 + + # 生成控制指令(仅使用传统控制器) + throttle, brake, steer = traditional_controller.get_control(vehicle) + + # 紧急刹车时拉手刹 + if brake >= 1.0: + vehicle.apply_control(carla.VehicleControl( + throttle=0.0, steer=steer, brake=1.0, hand_brake=True + )) + else: + # 应用控制 + control = carla.VehicleControl( + throttle=throttle, + steer=steer, + brake=brake, + hand_brake=False, + reverse=False + ) + vehicle.apply_control(control) + + # 图像显示 + if third_image is not None: + display_image = third_image.copy() + # 可视化障碍物 + display_image = obstacle_detector.visualize_obstacles(display_image, vehicle_transform) + # 绘制信息 + cv2.putText(display_image, f"Speed: {vehicle_speed * 3.6:.1f} km/h", (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) + cv2.putText(display_image, f"Mode: Traditional", (10, 60), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) + cv2.putText(display_image, f"Throttle: {throttle:.2f}", (10, 90), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) + cv2.putText(display_image, f"Steer: {steer:.2f}", (10, 120), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) + cv2.putText(display_image, f"Brake: {brake:.2f}", (10, 150), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) + # 障碍物信息 + if obstacle_info['has_obstacle']: + cv2.putText(display_image, f"Obstacle: {obstacle_info['distance']:.1f}m", (10, 180), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) + cv2.putText(display_image, f"RelSpeed: {obstacle_info['relative_speed']:.1f}km/h", (10, 210), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2) + else: + cv2.putText(display_image, "Obstacle: None", (10, 180), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) + # 卡住警告 + if stuck_count > 5: + cv2.putText(display_image, "STUCK DETECTED!", (10, 240), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) + + cv2.imshow('CARLA Autopilot (0.9.10)', display_image) + + # 键盘控制 + key = cv2.waitKey(1) & 0xFF + if key == ord('q'): + break + elif key == ord('r'): + vehicle.set_transform(spawn_point) + throttle = 0.3 + steer = 0.0 + brake = 0.0 + stuck_count = 0 + print("车辆已重置") + + time.sleep(0.01) except Exception as e: - print(f"\n程序异常:{e}") + print(f"系统错误: {e}") + import traceback + traceback.print_exc() + finally: - # 清理资源 - print("\n清理资源中...") + # 清理资源(0.9.10兼容) + print("正在清理资源...") + cv2.destroyAllWindows() + # 停止传感器 + if 'third_camera' in locals(): + third_camera.stop() + if 'front_camera' in locals(): + front_camera.stop() + # 销毁所有Actor + for actor in world.get_actors(): + if actor.type_id.startswith('vehicle.') or actor.type_id.startswith('sensor.'): + actor.destroy() + # 关闭同步模式 settings = world.get_settings() settings.synchronous_mode = False world.apply_settings(settings) - if 'vehicle' in locals(): - vehicle.destroy() - if 'camera' in locals(): - camera.destroy() - cv2.destroyAllWindows() - plt.close('all') - time.sleep(1) - print("仿真结束") + print("资源清理完成") -if __name__ == '__main__': +if __name__ == "__main__": main() \ No newline at end of file From 3286110cc839d4b23403a88236ea4f1d28deb5d2 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Sun, 21 Dec 2025 11:40:45 +0800 Subject: [PATCH 13/26] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E4=BA=A4?= =?UTF-8?q?=E9=80=9A=E4=BF=A1=E5=8F=B7=E7=81=AF=E5=93=8D=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/main.py | 1051 +++++++++++++++------------- 1 file changed, 566 insertions(+), 485 deletions(-) diff --git a/src/autonomous_driving_car/main.py b/src/autonomous_driving_car/main.py index 422e90076b..e86acaf6c1 100644 --- a/src/autonomous_driving_car/main.py +++ b/src/autonomous_driving_car/main.py @@ -1,519 +1,600 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +CARLA 多交通灯版:Town04密集交通灯+状态循环+车辆响应 +""" + +import sys +import os import carla -import time import numpy as np -import cv2 import math -from collections import deque -import random -import os - -# -------------------------- -# 1. 障碍物检测器(优化性能+无延迟检测) -# -------------------------- -class ObstacleDetector: - def __init__(self, world, vehicle, max_distance=50.0, detect_interval=1): # 改为1帧检测一次 - self.world = world - self.vehicle = vehicle - self.max_distance = max_distance - self.detect_interval = detect_interval # 检测间隔(帧) - self.frame_count = 0 - self.last_obstacle_info = { - 'has_obstacle': False, - 'distance': float('inf'), - 'relative_angle': 0.0, - 'obstacle_type': None, - 'obstacle_speed': 0.0, - 'relative_speed': 0.0 # 新增:自车与障碍物的相对速度 - } - - def get_vehicle_speed(self, vehicle): - """获取车辆速度(km/h)""" - velocity = vehicle.get_velocity() - speed = math.sqrt(velocity.x ** 2 + velocity.y ** 2 + velocity.z ** 2) - return speed * 3.6 - - def get_obstacle_info(self): - """检测前方障碍物信息(无延迟检测)""" - self.frame_count += 1 - if self.frame_count % self.detect_interval != 0: - return self.last_obstacle_info +import pygame +import traceback +import time - try: - vehicle_transform = self.vehicle.get_transform() - vehicle_location = vehicle_transform.location - forward_vector = vehicle_transform.get_forward_vector() - self_speed = self.get_vehicle_speed(self.vehicle) # 自车速度 - - # 减少get_actors调用频率,只获取车辆 - all_vehicles = self.world.get_actors().filter('vehicle.*') - min_distance = float('inf') - closest_obstacle = None - relative_angle = 0.0 - obstacle_speed = 0.0 - - for other_vehicle in all_vehicles: - if other_vehicle.id == self.vehicle.id: - continue - - other_location = other_vehicle.get_location() - distance = vehicle_location.distance(other_location) - if distance > self.max_distance: - continue - - # 计算相对角度(仅前方±70度,扩大检测范围) - relative_vector = carla.Location( - other_location.x - vehicle_location.x, - other_location.y - vehicle_location.y, - 0 - ) - forward_2d = carla.Vector3D(forward_vector.x, forward_vector.y, 0) - relative_2d = carla.Vector3D(relative_vector.x, relative_vector.y, 0) - - # 向量归一化 - forward_norm = math.sqrt(forward_2d.x ** 2 + forward_2d.y ** 2) - relative_norm = math.sqrt(relative_2d.x ** 2 + relative_2d.y ** 2) - if forward_norm == 0 or relative_norm == 0: - continue - - dot_product = forward_2d.x * relative_2d.x + forward_2d.y * relative_2d.y - cos_angle = dot_product / (forward_norm * relative_norm) - cos_angle = max(-1.0, min(1.0, cos_angle)) - angle_deg = math.degrees(math.acos(cos_angle)) - - # 扩大检测角度到±70度,更早发现障碍物 - if angle_deg <= 70 and distance < min_distance: - min_distance = distance - closest_obstacle = other_vehicle - obstacle_speed = self.get_vehicle_speed(other_vehicle) - # 确定角度方向 - relative_angle = angle_deg if relative_2d.y >= 0 else -angle_deg - - # 计算相对速度(自车速度 - 前车速度,正数表示自车更快) - relative_speed = self_speed - obstacle_speed if closest_obstacle else 0.0 - - # 更新障碍物信息 - if closest_obstacle is not None: - self.last_obstacle_info = { - 'has_obstacle': True, - 'distance': min_distance, - 'relative_angle': relative_angle, - 'obstacle_type': closest_obstacle.type_id, - 'obstacle_speed': obstacle_speed, - 'relative_speed': relative_speed - } - else: - self.last_obstacle_info = { - 'has_obstacle': False, - 'distance': float('inf'), - 'relative_angle': 0.0, - 'obstacle_type': None, - 'obstacle_speed': 0.0, - 'relative_speed': 0.0 - } - - except Exception as e: - print(f"障碍物检测错误: {e}") - - return self.last_obstacle_info - - def visualize_obstacles(self, image, vehicle_transform): - """在图像上可视化障碍物检测结果""" - if not self.last_obstacle_info['has_obstacle']: - return image - - height, width = image.shape[:2] - distance = self.last_obstacle_info['distance'] - angle = self.last_obstacle_info['relative_angle'] - - # 计算障碍物在图像中的位置 - x_pos = int(width / 2 + (angle / 70) * (width / 2)) # 适配70度检测范围 - x_pos = max(0, min(width - 1, x_pos)) - - # 根据距离设置颜色和大小 - if distance < 15: - color = (0, 0, 255) - radius = 15 - elif distance < 30: - color = (0, 165, 255) - radius = 10 - else: - color = (0, 255, 255) - radius = 5 - - # 绘制障碍物指示器 - cv2.circle(image, (x_pos, int(height * 0.8)), radius, color, -1) - cv2.putText(image, f"{distance:.1f}m", (x_pos - 20, int(height * 0.8) - 20), - cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) - # 绘制相对速度 - rel_speed = self.last_obstacle_info['relative_speed'] - cv2.putText(image, f"RelSpeed: {rel_speed:.1f}km/h", (x_pos - 20, int(height * 0.8) + 20), - cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) - return image - -# -------------------------- -# 2. 传统控制器(核心控制逻辑+优化避障) -# -------------------------- -class TraditionalController: - """基于路点的传统控制器,整合避障""" - def __init__(self, world, obstacle_detector): - self.world = world - self.map = world.get_map() - self.waypoint_distance = 10.0 - self.obstacle_detector = obstacle_detector - # 调整避障阈值:扩大距离,提前减速 - self.emergency_brake_distance = 12.0 # 从6米改为12米 - self.safe_following_distance = 18.0 # 从10米改为18米 - self.early_warning_distance = 30.0 # 新增:30米提前预警 - - def apply_obstacle_avoidance(self, throttle, brake, steer, vehicle, obstacle_info): - """传统控制器的避障逻辑(优化刹车力度+相对速度)""" - if not obstacle_info['has_obstacle']: - return throttle, brake, steer - - distance = obstacle_info['distance'] - angle = obstacle_info['relative_angle'] - vehicle_speed = self.obstacle_detector.get_vehicle_speed(vehicle) - relative_speed = obstacle_info['relative_speed'] # 自车与前车的相对速度 - - # 1. 提前预警(30米内):轻微减速,降低油门 - if distance < self.early_warning_distance and relative_speed > 0: - throttle *= 0.5 # 油门减半 - if vehicle_speed > 30: - brake = 0.2 # 轻微刹车 - - # 2. 紧急刹车(12米内):全力刹车+手刹 - if distance < self.emergency_brake_distance: - print(f"紧急刹车!距离前车: {distance:.1f}m, 相对速度: {relative_speed:.1f}km/h") - return 0.0, 1.0, 0.0 # brake=1.0 + 后续拉手刹 - - # 3. 安全跟车(18米内):动态调整刹车力度 - elif distance < self.safe_following_distance: - # 根据距离和相对速度计算所需刹车力度 - required_distance = max(8.0, vehicle_speed * 0.5) # 增加安全车距系数 - distance_ratio = (distance - required_distance) / self.safe_following_distance - distance_ratio = max(0.0, min(1.0, distance_ratio)) - - # 相对速度越大,刹车越重 - brake_strength = (1 - distance_ratio) * 0.8 + (relative_speed / 20) * 0.2 - brake_strength = max(0.3, min(0.8, brake_strength)) - - if distance < required_distance: - throttle = 0.0 - brake = brake_strength - else: - throttle = 0.1 - brake = 0.0 - - # 尝试变道 - if abs(angle) < 15: - location = vehicle.get_location() - waypoint = self.map.get_waypoint(location) - left_lane = waypoint.get_left_lane() - right_lane = waypoint.get_right_lane() - - if left_lane and left_lane.lane_type == carla.LaneType.Driving: - steer = -0.3 - elif right_lane and right_lane.lane_type == carla.LaneType.Driving: - steer = 0.3 - else: - steer = 0.2 if angle >= 0 else -0.2 +# ===================== 全局配置 ====================== +# CARLA连接 +CARLA_HOST = "localhost" +CARLA_PORT = 2000 +CARLA_TIMEOUT = 10.0 + +# 车辆配置 +VEHICLE_MODEL = "vehicle.tesla.model3" +VEHICLE_WHEELBASE = 2.9 +VEHICLE_REAR_AXLE_OFFSET = 1.45 + +# 转向控制 +LOOKAHEAD_DIST_STRAIGHT = 7.0 +LOOKAHEAD_DIST_CURVE = 4.0 +STEER_GAIN_STRAIGHT = 0.7 +STEER_GAIN_CURVE = 1.0 +STEER_DEADZONE = 0.05 +STEER_LOWPASS_ALPHA = 0.6 +MAX_STEER = 1.0 + +# 弯道等级 +DIR_CHANGE_GENTLE = 0.03 +DIR_CHANGE_SHARP = 0.08 + +# 速度控制 +BASE_SPEED = 25.0 +PID_KP = 0.2 +PID_KI = 0.01 +PID_KD = 0.02 + +# 相机 +CAMERA_POS = carla.Transform(carla.Location(x=-5.0, z=2.0)) +CAMERA_WIDTH = 800 +CAMERA_HEIGHT = 600 +CAMERA_FOV = 90 + +# 交通规则 +TRAFFIC_LIGHT_STOP_DISTANCE = 4.0 +TRAFFIC_LIGHT_DETECTION_RANGE = 50.0 # 扩大检测范围,能检测更多交通灯 +TRAFFIC_LIGHT_ANGLE_THRESHOLD = 60.0 # 扩大检测角度 +STOP_SPEED_THRESHOLD = 0.2 +GREEN_LIGHT_ACCEL_FACTOR = 0.25 +STOP_LINE_SIM_DISTANCE = 5.0 + +# 交通灯状态循环配置(单位:秒) +RED_LIGHT_DURATION = 3.0 +GREEN_LIGHT_DURATION = 5.0 +YELLOW_LIGHT_DURATION = 2.0 + +# 车道行驶配置 +ROAD_DIRECTION_DOT_THRESHOLD = 0.0 +LANE_KEEP_STRICTNESS = 1.2 + +# ===================== 核心兼容工具函数 ====================== +def is_actor_alive(actor): + """兼容不同版本的Actor存活状态判断""" + try: + return actor.is_alive() + except TypeError: + return actor.is_alive - return throttle, brake, steer +def get_traffic_light_stop_line(traffic_light): + """兼容不同版本的交通灯停止线获取""" + try: + return traffic_light.get_stop_line_location() + except AttributeError: + tl_transform = traffic_light.get_transform() + forward_vec = tl_transform.get_forward_vector() + stop_line_loc = tl_transform.location - forward_vec * STOP_LINE_SIM_DISTANCE + stop_line_loc.z = tl_transform.location.z + return stop_line_loc + +def get_spawn_point_near_traffic_light(world, map): + """自动查找距离交通灯最近的出生点""" + traffic_lights = world.get_actors().filter("traffic.traffic_light") + if not traffic_lights: + print("警告:当前地图中未找到交通灯,使用默认出生点") + spawn_points = map.get_spawn_points() + return spawn_points[0] if spawn_points else carla.Transform() + + spawn_points = map.get_spawn_points() + if not spawn_points: + print("警告:未找到默认出生点,使用交通灯旁位置") + tl_transform = traffic_lights[0].get_transform() + return carla.Transform(tl_transform.location + carla.Location(x=-5.0, z=0.5), tl_transform.rotation) + + min_distance = float('inf') + best_spawn_point = spawn_points[0] + + for spawn_point in spawn_points: + for tl in traffic_lights: + if not is_actor_alive(tl): + continue + tl_loc = tl.get_transform().location + spawn_loc = spawn_point.location + distance = math.sqrt((tl_loc.x - spawn_loc.x)**2 + (tl_loc.y - spawn_loc.y)**2) + if distance < min_distance: + min_distance = distance + best_spawn_point = spawn_point + + print(f"找到距离交通灯最近的出生点,距离:{min_distance:.2f}米") + return best_spawn_point + +def cycle_traffic_light_states(world): + """ + 循环控制所有交通灯的状态:红→绿→黄→红 + 作为后台线程运行,确保交通灯持续变化 + """ + while True: + traffic_lights = world.get_actors().filter("traffic.traffic_light") + # 设置所有交通灯为红灯 + for tl in traffic_lights: + if is_actor_alive(tl): + try: + tl.set_state(carla.TrafficLightState.Red) + except: + pass + time.sleep(RED_LIGHT_DURATION) + + # 设置所有交通灯为绿灯 + for tl in traffic_lights: + if is_actor_alive(tl): + try: + tl.set_state(carla.TrafficLightState.Green) + except: + pass + time.sleep(GREEN_LIGHT_DURATION) + + # 设置所有交通灯为黄灯 + for tl in traffic_lights: + if is_actor_alive(tl): + try: + tl.set_state(carla.TrafficLightState.Yellow) + except: + pass + time.sleep(YELLOW_LIGHT_DURATION) + +# ===================== 纯追踪控制器 ===================== +class AdaptivePurePursuit: + def __init__(self, wheelbase): + self.wheelbase = wheelbase + self.last_steer = 0.0 + self.last_lookahead = LOOKAHEAD_DIST_STRAIGHT + + def calculate_steer(self, vehicle_transform, target_point, dir_change): + # 1. 后轴位置 + forward_vec = vehicle_transform.get_forward_vector() + rear_axle_loc = carla.Location( + x=vehicle_transform.location.x - forward_vec.x * VEHICLE_REAR_AXLE_OFFSET, + y=vehicle_transform.location.y - forward_vec.y * VEHICLE_REAR_AXLE_OFFSET, + z=vehicle_transform.location.z + ) - def get_control(self, vehicle): - """生成传统控制指令(优先避障,弱化基础速度控制)""" - # 获取车辆状态 - transform = vehicle.get_transform() - location = vehicle.get_location() - velocity = vehicle.get_velocity() - speed = math.sqrt(velocity.x ** 2 + velocity.y ** 2 + velocity.z ** 2) * 3.6 + # 2. 车辆坐标系转换 + dx = target_point.x - rear_axle_loc.x + dy = target_point.y - rear_axle_loc.y + yaw = math.radians(vehicle_transform.rotation.yaw) - # 获取障碍物信息 - obstacle_info = self.obstacle_detector.get_obstacle_info() + dx_vehicle = dx * math.cos(yaw) + dy * math.sin(yaw) + dy_vehicle = -dx * math.sin(yaw) + dy * math.cos(yaw) - # 获取路点 - waypoint = self.map.get_waypoint(location, project_to_road=True) - next_waypoints = waypoint.next(self.waypoint_distance) - target_waypoint = next_waypoints[0] if next_waypoints else waypoint + # 3. 转向增益 + steer_gain = np.interp( + dir_change, + [0, DIR_CHANGE_SHARP], + [STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE] + ) + steer_gain = np.clip(steer_gain, STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE) - # 计算转向 - vehicle_yaw = math.radians(transform.rotation.yaw) - target_loc = target_waypoint.transform.location + # 4. 纯追踪计算 + if dx_vehicle < 0.1: + steer = self.last_steer + else: + steer_rad = math.atan2(2 * self.wheelbase * dy_vehicle * LANE_KEEP_STRICTNESS, dx_vehicle ** 2 + dy_vehicle ** 2) + steer = steer_rad / math.pi + steer *= steer_gain - dx = target_loc.x - location.x - dy = target_loc.y - location.y + # 5. 死区+滤波 + if abs(steer) < STEER_DEADZONE: + steer = 0.0 + steer = STEER_LOWPASS_ALPHA * steer + (1 - STEER_LOWPASS_ALPHA) * self.last_steer + steer = np.clip(steer, -MAX_STEER, MAX_STEER) - local_x = dx * math.cos(vehicle_yaw) + dy * math.sin(vehicle_yaw) - local_y = -dx * math.sin(vehicle_yaw) + dy * math.cos(vehicle_yaw) + self.last_steer = steer + return steer - if abs(local_x) < 0.1: - steer = 0.0 + def get_adaptive_lookahead(self, dir_change): + lookahead_dist = np.interp( + dir_change, + [0, DIR_CHANGE_SHARP], + [LOOKAHEAD_DIST_STRAIGHT, LOOKAHEAD_DIST_CURVE] + ) + lookahead_dist = np.clip(lookahead_dist, LOOKAHEAD_DIST_CURVE, LOOKAHEAD_DIST_STRAIGHT) + self.last_lookahead = lookahead_dist + return lookahead_dist + +# ===================== 速度控制器 ===================== +class SpeedController: + def __init__(self, kp, ki, kd): + self.kp = kp + self.ki = ki + self.kd = kd + self.last_error = 0.0 + self.integral = 0.0 + + def calculate(self, target_speed, current_speed): + error = target_speed - current_speed + p = self.kp * error + self.integral += self.ki * error + self.integral = np.clip(self.integral, -1.0, 1.0) + i = self.integral + d = self.kd * (error - self.last_error) + self.last_error = error + return np.clip(p + i + d, 0.0, 1.0) + +# ===================== 交通灯管理类 ====================== +class TrafficLightManager: + def __init__(self): + self.tracked_light = None + self.is_stopped_at_red = False + self.red_light_stop_time = 0 + + def _calculate_angle_between_vehicle_and_light(self, vehicle_transform, light_transform): + """计算车辆前进方向与交通灯的夹角""" + vehicle_forward = vehicle_transform.get_forward_vector() + vehicle_forward = np.array([vehicle_forward.x, vehicle_forward.y]) + vehicle_forward = vehicle_forward / np.linalg.norm(vehicle_forward) + + light_dir = light_transform.location - vehicle_transform.location + light_dir = np.array([light_dir.x, light_dir.y]) + if np.linalg.norm(light_dir) < 0.1: + return 0.0 + light_dir = light_dir / np.linalg.norm(light_dir) + + angle = math.acos(np.clip(np.dot(vehicle_forward, light_dir), -1.0, 1.0)) + angle = math.degrees(angle) + return angle + + def get_lane_traffic_light(self, vehicle, world): + """扩大检测范围,检测更多交通灯""" + vehicle_transform = vehicle.get_transform() + vehicle_loc = vehicle_transform.location + + # 检查跟踪的交通灯是否存活 + if self.tracked_light and is_actor_alive(self.tracked_light): + dist = self.tracked_light.get_transform().location.distance(vehicle_loc) + angle = self._calculate_angle_between_vehicle_and_light(vehicle_transform, self.tracked_light.get_transform()) + if dist < TRAFFIC_LIGHT_DETECTION_RANGE and angle < TRAFFIC_LIGHT_ANGLE_THRESHOLD: + return self.tracked_light + + # 获取所有交通灯并筛选有效灯 + traffic_lights = world.get_actors().filter("traffic.traffic_light") + valid_lights = [] + + for light in traffic_lights: + if not is_actor_alive(light): + continue + dist = light.get_transform().location.distance(vehicle_loc) + if dist > TRAFFIC_LIGHT_DETECTION_RANGE: + continue + angle = self._calculate_angle_between_vehicle_and_light(vehicle_transform, light.get_transform()) + if angle < TRAFFIC_LIGHT_ANGLE_THRESHOLD: + valid_lights.append((dist, light)) + + if valid_lights: + valid_lights.sort(key=lambda x: x[0]) + self.tracked_light = valid_lights[0][1] + return self.tracked_light + + self.tracked_light = None + return None + + def handle_traffic_light_logic(self, vehicle, current_speed, base_target_speed): + """红灯强制停车,绿灯恢复行驶""" + world = vehicle.get_world() + traffic_light = self.get_lane_traffic_light(vehicle, world) + + if not traffic_light: + self.is_stopped_at_red = False + self.red_light_stop_time = 0 + return base_target_speed, "No Light (Lane)" + + # 核心:使用兼容函数获取停止线位置 + stop_line_loc = get_traffic_light_stop_line(traffic_light) + dist_to_stop_line = vehicle.get_transform().location.distance(stop_line_loc) + + if traffic_light.get_state() == carla.TrafficLightState.Green: + if self.is_stopped_at_red: + recovery_speed = current_speed + (base_target_speed - current_speed) * GREEN_LIGHT_ACCEL_FACTOR + target_speed = max(STOP_SPEED_THRESHOLD, recovery_speed) + if abs(target_speed - base_target_speed) < 0.5: + self.is_stopped_at_red = False + return target_speed, "Green (Recovering)" + return base_target_speed, "Green (Lane)" + + elif traffic_light.get_state() == carla.TrafficLightState.Yellow: + self.is_stopped_at_red = False + yellow_speed = max(5.0, base_target_speed * 0.3) + return yellow_speed, "Yellow (Stop Soon)" + + elif traffic_light.get_state() == carla.TrafficLightState.Red: + if dist_to_stop_line > TRAFFIC_LIGHT_STOP_DISTANCE: + self.is_stopped_at_red = False + red_speed = max(2.0, current_speed * 0.1) + return red_speed, f"Red (Decelerating: {dist_to_stop_line:.1f}m)" + else: + if current_speed <= STOP_SPEED_THRESHOLD: + self.is_stopped_at_red = True + self.red_light_stop_time += 1 + wait_seconds = self.red_light_stop_time // 30 + return 0.0, f"Red (Stopped: {wait_seconds}s)" + else: + return 0.0, "Red (Emergency Stop)" + + return base_target_speed, "Unknown Light" + +# ===================== 车道行驶辅助函数 ====================== +def calculate_dir_change(current_wp): + """计算方向变化量,判断弯道等级""" + waypoints = [current_wp] + for i in range(5): + next_wps = waypoints[-1].next(1.0) + if next_wps: + waypoints.append(next_wps[0]) + else: + break + + if len(waypoints) < 4: + return 0.0, 0 + + dirs = [] + for i in range(1, len(waypoints)): + wp_prev = waypoints[i-1] + wp_curr = waypoints[i] + dir_rad = math.atan2( + wp_curr.transform.location.y - wp_prev.transform.location.y, + wp_curr.transform.location.x - wp_prev.transform.location.x + ) + dirs.append(dir_rad) + + dir_change = 0.0 + for i in range(1, len(dirs)): + dir_change += abs(dirs[i] - dirs[i-1]) * 2 + + if dir_change < DIR_CHANGE_GENTLE: + curve_level = 0 + elif dir_change < DIR_CHANGE_SHARP: + curve_level = 1 + else: + curve_level = 2 + + return dir_change, curve_level + +def get_forward_waypoint(vehicle, map): + """获取车辆当前车道的前进方向路点(极低版本兼容)""" + vehicle_transform = vehicle.get_transform() + # 1. 投影到道路 + current_wp = map.get_waypoint( + vehicle_transform.location, + project_to_road=True + ) + + # 2. 纯数学判断方向是否相反 + road_direction = current_wp.transform.get_forward_vector() + vehicle_direction = vehicle_transform.get_forward_vector() + dot_product = road_direction.x * vehicle_direction.x + road_direction.y * vehicle_direction.y + + # 3. 方向相反则取前方点 + if dot_product < ROAD_DIRECTION_DOT_THRESHOLD: + forward_wps = current_wp.next(10.0) + if forward_wps: + current_wp = forward_wps[0] else: - angle = math.atan2(local_y, local_x) - steer = np.clip(angle / math.radians(45), -1.0, 1.0) - - # 基础速度控制(降低优先级) - if speed < 20: - throttle = 0.4 # 从0.6降低到0.4,减少油门 - brake = 0.0 - elif speed < 40: - throttle = 0.2 # 从0.4降低到0.2 - brake = 0.0 + current_wp = map.get_waypoint( + vehicle_transform.location + vehicle_direction * 5.0, + project_to_road=True + ) + + return current_wp + +# ===================== 相机管理器 ===================== +class CameraManager: + def __init__(self, world, vehicle, display): + self.world = world + self.vehicle = vehicle + self.display = display + self.camera = None + self.traffic_light_status = "No Light" + self._create_camera() + + def _create_camera(self): + bp = self.world.get_blueprint_library().find("sensor.camera.rgb") + bp.set_attribute("image_size_x", str(CAMERA_WIDTH)) + bp.set_attribute("image_size_y", str(CAMERA_HEIGHT)) + bp.set_attribute("fov", str(CAMERA_FOV)) + self.camera = self.world.spawn_actor(bp, CAMERA_POS, attach_to=self.vehicle) + self.camera.listen(self._on_image) + + def _on_image(self, image): + """修正数组处理逻辑""" + array = np.frombuffer(image.raw_data, dtype=np.uint8) + array = array.reshape((CAMERA_HEIGHT, CAMERA_WIDTH, 4)) + array = array[:, :, :3] + array = array[:, :, ::-1].swapaxes(0, 1) + self.display.blit(pygame.surfarray.make_surface(array), (0, 0)) + self._draw_info() + pygame.display.flip() + + def _draw_info(self): + font = pygame.font.SysFont("Arial", 24, bold=True) + if "Red" in self.traffic_light_status: + color = (255, 0, 0) + elif "Green" in self.traffic_light_status: + color = (0, 255, 0) + elif "Yellow" in self.traffic_light_status: + color = (255, 255, 0) else: - throttle = 0.1 - brake = 0.2 + color = (255, 255, 255) - # 障碍物调整(优先执行) - throttle, brake, steer = self.apply_obstacle_avoidance(throttle, brake, steer, vehicle, obstacle_info) + text = font.render(f"Traffic Light: {self.traffic_light_status}", True, color) + bg = pygame.Surface((text.get_width() + 10, text.get_height() + 5)) + bg.fill((0, 0, 0)) + self.display.blit(bg, (5, 5)) + self.display.blit(text, (10, 7)) - # 低速强油门(仅当无障碍物时生效) - if speed < 5.0 and not obstacle_info['has_obstacle']: - throttle = 0.4 - brake = 0.0 + def update_traffic_light_status(self, status): + self.traffic_light_status = status - return throttle, brake, steer + def destroy(self): + if self.camera: + if is_actor_alive(self.camera): + self.camera.stop() + self.camera.destroy() -# -------------------------- -# 3. CARLA环境初始化(适配0.9.10) -# -------------------------- +# ===================== 主函数(核心:多交通灯配置)===================== def main(): + pygame.init() + display = pygame.display.set_mode((CAMERA_WIDTH, CAMERA_HEIGHT)) + pygame.display.set_caption("CARLA 多交通灯版(Town04密集交通灯+状态循环)") + + client = None + world = None + vehicle = None + camera_manager = None + pp_controller = None + speed_controller = None + traffic_light_manager = None + try: - # 连接CARLA服务器(0.9.10兼容) - client = carla.Client('localhost', 2000) - client.set_timeout(15.0) - world = client.load_world('Town01') # 0.9.10支持Town01 - - # 设置同步模式(0.9.10关键配置) - settings = world.get_settings() - settings.synchronous_mode = True - settings.fixed_delta_seconds = 0.1 - world.apply_settings(settings) - - # 设置天气 - weather = carla.WeatherParameters( - cloudiness=30.0, - precipitation=0.0, - sun_altitude_angle=70.0 + # 1. 连接CARLA,加载**Town04**(交通灯最密集的地图) + client = carla.Client(CARLA_HOST, CARLA_PORT) + client.set_timeout(CARLA_TIMEOUT) + try: + client.load_world("Town04") # 替换为Town04,交通灯数量远多于Town03 + except: + world = client.get_world() + print("警告:Town04地图不存在,使用当前地图") + else: + world = client.get_world() + print("成功加载Town04地图(交通灯密集)") + map = world.get_map() + + # 2. 清理残留演员 + for actor in world.get_actors(): + if actor.type_id.startswith(("vehicle.", "walker.", "sensor.", "controller.")): + if is_actor_alive(actor): + actor.destroy() + print("残留演员清理完成") + + # 3. 启动交通灯状态循环线程(后台持续切换交通灯状态) + import threading + tl_cycle_thread = threading.Thread(target=cycle_traffic_light_states, args=(world,), daemon=True) + tl_cycle_thread.start() + print("交通灯状态循环线程已启动(红3秒→绿5秒→黄2秒)") + + # 4. 设置车辆起始位置(Town04交通灯密集区) + vehicle_bp = world.get_blueprint_library().find(VEHICLE_MODEL) + + # 手动指定Town04的核心交通灯路口坐标(经测试:多个交通灯环绕) + spawn_point = carla.Transform( + carla.Location(x=220.0, y=150.0, z=0.5), # Town04核心交通灯密集区 + carla.Rotation(yaw=90.0) ) - world.set_weather(weather) + spawn_point.location.z += 0.2 + vehicle = world.try_spawn_actor(vehicle_bp, spawn_point) - # 获取出生点 - map = world.get_map() - spawn_points = map.get_spawn_points() - if not spawn_points: - raise Exception("无可用出生点") - spawn_point = spawn_points[10] - - # 生成主车辆 - blueprint_library = world.get_blueprint_library() - vehicle_bp = blueprint_library.find('vehicle.tesla.model3') - vehicle_bp.set_attribute('color', '255,0,0') - vehicle = world.spawn_actor(vehicle_bp, spawn_point) + # 备用方案:自动查找交通灯附近的出生点 if not vehicle: - raise Exception("无法生成主车辆") - vehicle.set_autopilot(False) - print(f"车辆生成在位置: {spawn_point.location}") - - # 生成障碍物车辆(调整生成位置,确保在前车前方) - obstacle_count = 3 - for i in range(obstacle_count): - spawn_idx = (i + 12) % len(spawn_points) # 从15改为12,更靠近主车辆 - other_vehicle_bp = random.choice(blueprint_library.filter('vehicle.*')) - other_vehicle = world.try_spawn_actor(other_vehicle_bp, spawn_points[spawn_idx]) - if other_vehicle: - other_vehicle.set_autopilot(True) - - # 配置传感器(0.9.10兼容) - # 后视角相机 - third_camera_bp = blueprint_library.find('sensor.camera.rgb') - third_camera_bp.set_attribute('image_size_x', '640') - third_camera_bp.set_attribute('image_size_y', '480') - third_camera_bp.set_attribute('fov', '110') - third_camera_transform = carla.Transform( - carla.Location(x=-5.0, y=0.0, z=3.0), - carla.Rotation(pitch=-15.0) - ) - third_camera = world.spawn_actor(third_camera_bp, third_camera_transform, attach_to=vehicle) - - # 前视角相机 - front_camera_bp = blueprint_library.find('sensor.camera.rgb') - front_camera_bp.set_attribute('image_size_x', '640') - front_camera_bp.set_attribute('image_size_y', '480') - front_camera_bp.set_attribute('fov', '90') - front_camera_transform = carla.Transform( - carla.Location(x=2.0, y=0.0, z=1.5), - carla.Rotation(pitch=0.0) - ) - front_camera = world.spawn_actor(front_camera_bp, front_camera_transform, attach_to=vehicle) - - # 传感器数据存储 - third_image = None - front_image = None - - # 传感器回调函数 - def third_camera_callback(image): - nonlocal third_image - array = np.frombuffer(image.raw_data, dtype=np.uint8) - array = np.reshape(array, (image.height, image.width, 4)) - third_image = array[:, :, :3] - - def front_camera_callback(image): - nonlocal front_image - array = np.frombuffer(image.raw_data, dtype=np.uint8) - array = np.reshape(array, (image.height, image.width, 4)) - front_image = array[:, :, :3] - - # 注册回调 - third_camera.listen(third_camera_callback) - front_camera.listen(front_camera_callback) - time.sleep(2.0) # 等待传感器初始化 - - # 初始化核心组件 - obstacle_detector = ObstacleDetector(world, vehicle) - traditional_controller = TraditionalController(world, obstacle_detector) - - # 控制变量 - throttle = 0.3 - steer = 0.0 - brake = 0.0 - frame_count = 0 - stuck_count = 0 - last_position = vehicle.get_location() - - # 主循环 - print("自动驾驶系统启动 - 仅使用传统控制器") - print("控制键: q-退出, r-重置车辆") - - while True: - world.tick() - frame_count += 1 + print("手动坐标生成失败,自动查找交通灯附近的出生点...") + spawn_point = get_spawn_point_near_traffic_light(world, map) + vehicle = world.spawn_actor(vehicle_bp, spawn_point) + + # 最终备用方案 + if not vehicle: + spawn_points = map.get_spawn_points() + spawn_point = spawn_points[0] if spawn_points else carla.Transform() + vehicle = world.spawn_actor(vehicle_bp, spawn_point) + + print(f"车辆生成成功:{vehicle.type_id}(起始于交通灯密集区)") + + # 5. 初始化组件 + pp_controller = AdaptivePurePursuit(VEHICLE_WHEELBASE) + speed_controller = SpeedController(PID_KP, PID_KI, PID_KD) + traffic_light_manager = TrafficLightManager() + camera_manager = CameraManager(world, vehicle, display) + + # 6. 主循环 + print("仿真启动,按ESC退出...(车辆将经过多个交通灯)") + clock = pygame.time.Clock() + running = True + + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): + running = False # 获取车辆状态 vehicle_transform = vehicle.get_transform() - vehicle_location = vehicle.get_transform().location - vehicle_velocity = vehicle.get_velocity() - vehicle_speed = math.sqrt(vehicle_velocity.x ** 2 + vehicle_velocity.y ** 2 + vehicle_velocity.z ** 2) - - # 检测障碍物 - obstacle_info = obstacle_detector.get_obstacle_info() - - # 卡住检测(优化:有障碍物时不触发) - distance_moved = vehicle_location.distance(last_position) - is_moving = distance_moved > 0.2 or vehicle_speed > 1.0 - # 关键修正:通过traditional_controller实例访问属性,而非self - if obstacle_info['has_obstacle'] and obstacle_info['distance'] < traditional_controller.safe_following_distance: - stuck_count = 0 - else: - if not is_moving: - stuck_count += 1 - else: - stuck_count = 0 - last_position = vehicle_location - - # 卡住恢复(仅当无障碍物时执行) - if stuck_count > 20: # 从15帧改为20帧,降低误触发 - print("检测到车辆卡住,执行恢复程序...") - # 紧急刹车 - vehicle.apply_control(carla.VehicleControl(throttle=0.0, steer=0.0, brake=1.0, hand_brake=True)) - time.sleep(0.5) - # 倒车或转向 - if obstacle_info['has_obstacle'] and obstacle_info['distance'] < 15: - vehicle.apply_control(carla.VehicleControl(throttle=0.0, steer=0.0, brake=0.0, reverse=True)) - time.sleep(1.0) - else: - recovery_steer = random.choice([-0.5, 0.5]) - vehicle.apply_control(carla.VehicleControl(throttle=0.6, steer=recovery_steer, brake=0.0)) # 降低油门到0.6 - time.sleep(1.0) - stuck_count = 0 - - # 生成控制指令(仅使用传统控制器) - throttle, brake, steer = traditional_controller.get_control(vehicle) - - # 紧急刹车时拉手刹 - if brake >= 1.0: - vehicle.apply_control(carla.VehicleControl( - throttle=0.0, steer=steer, brake=1.0, hand_brake=True - )) - else: - # 应用控制 - control = carla.VehicleControl( - throttle=throttle, - steer=steer, - brake=brake, - hand_brake=False, - reverse=False - ) - vehicle.apply_control(control) - - # 图像显示 - if third_image is not None: - display_image = third_image.copy() - # 可视化障碍物 - display_image = obstacle_detector.visualize_obstacles(display_image, vehicle_transform) - # 绘制信息 - cv2.putText(display_image, f"Speed: {vehicle_speed * 3.6:.1f} km/h", (10, 30), - cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) - cv2.putText(display_image, f"Mode: Traditional", (10, 60), - cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) - cv2.putText(display_image, f"Throttle: {throttle:.2f}", (10, 90), - cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) - cv2.putText(display_image, f"Steer: {steer:.2f}", (10, 120), - cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) - cv2.putText(display_image, f"Brake: {brake:.2f}", (10, 150), - cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) - # 障碍物信息 - if obstacle_info['has_obstacle']: - cv2.putText(display_image, f"Obstacle: {obstacle_info['distance']:.1f}m", (10, 180), - cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) - cv2.putText(display_image, f"RelSpeed: {obstacle_info['relative_speed']:.1f}km/h", (10, 210), - cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2) - else: - cv2.putText(display_image, "Obstacle: None", (10, 180), - cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) - # 卡住警告 - if stuck_count > 5: - cv2.putText(display_image, "STUCK DETECTED!", (10, 240), - cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) - - cv2.imshow('CARLA Autopilot (0.9.10)', display_image) - - # 键盘控制 - key = cv2.waitKey(1) & 0xFF - if key == ord('q'): - break - elif key == ord('r'): - vehicle.set_transform(spawn_point) - throttle = 0.3 - steer = 0.0 - brake = 0.0 - stuck_count = 0 - print("车辆已重置") - - time.sleep(0.01) + vehicle_vel = vehicle.get_velocity() + current_speed = math.hypot(vehicle_vel.x, vehicle_vel.y) * 3.6 + + # 核心:获取前进方向路点 + current_wp = get_forward_waypoint(vehicle, map) + + # 计算方向变化 + dir_change, curve_level = calculate_dir_change(current_wp) + + # 预瞄距离 + lookahead_dist = pp_controller.get_adaptive_lookahead(dir_change) + + # 目标路点 + target_wps = current_wp.next(lookahead_dist) + target_point = target_wps[0].transform.location if target_wps else vehicle_transform.location + + # 基础速度 + curve_speed_factors = [1.0, 0.7, 0.4] + speed_factor = curve_speed_factors[min(curve_level, 2)] + base_target_speed = BASE_SPEED * speed_factor + base_target_speed = max(8.0, base_target_speed) + + # 交通灯处理(检测多个交通灯) + target_speed, traffic_light_status = traffic_light_manager.handle_traffic_light_logic( + vehicle, current_speed, base_target_speed + ) + camera_manager.update_traffic_light_status(traffic_light_status) + + # 控制计算 + steer = pp_controller.calculate_steer(vehicle_transform, target_point, dir_change) + throttle = speed_controller.calculate(target_speed, current_speed) + brake = 1.0 - throttle if current_speed > target_speed + 1 else 0.0 + + # 红灯刹车 + if "Red (Stopped)" in traffic_light_status or target_speed == 0.0: + throttle = 0.0 + brake = 1.0 + + # 应用控制 + control = carla.VehicleControl() + control.steer = steer + control.throttle = throttle + control.brake = brake + vehicle.apply_control(control) + + # 打印状态(显示当前交通灯状态) + curve_names = ["直道", "缓弯", "急弯"] + lane_id = current_wp.lane_id + print(f"速度:{current_speed:5.1f}km/h | 目标:{target_speed:5.1f} | 弯道:{curve_names[curve_level]:<3} | 车道ID:{lane_id} | 灯状态:{traffic_light_status}") + + clock.tick(30) except Exception as e: - print(f"系统错误: {e}") - import traceback + print(f"错误:{e}") traceback.print_exc() finally: - # 清理资源(0.9.10兼容) - print("正在清理资源...") - cv2.destroyAllWindows() - # 停止传感器 - if 'third_camera' in locals(): - third_camera.stop() - if 'front_camera' in locals(): - front_camera.stop() - # 销毁所有Actor - for actor in world.get_actors(): - if actor.type_id.startswith('vehicle.') or actor.type_id.startswith('sensor.'): - actor.destroy() - # 关闭同步模式 - settings = world.get_settings() - settings.synchronous_mode = False - world.apply_settings(settings) - print("资源清理完成") + print("清理资源...") + if camera_manager: + camera_manager.destroy() + if vehicle and is_actor_alive(vehicle): + vehicle.destroy() + pygame.quit() + print("仿真结束") if __name__ == "__main__": main() \ No newline at end of file From 914f120ff3bb1ba7f6e8ee8addd9f63bd8be7096 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Sun, 21 Dec 2025 15:18:24 +0800 Subject: [PATCH 14/26] =?UTF-8?q?=E5=A4=9A=E8=BD=A6=E8=BE=86=E5=8D=8F?= =?UTF-8?q?=E5=90=8C=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/main.py | 976 +++++++++++++++++++---------- 1 file changed, 642 insertions(+), 334 deletions(-) diff --git a/src/autonomous_driving_car/main.py b/src/autonomous_driving_car/main.py index e86acaf6c1..533eccd517 100644 --- a/src/autonomous_driving_car/main.py +++ b/src/autonomous_driving_car/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -CARLA 多交通灯版:Town04密集交通灯+状态循环+车辆响应 +CARLA 多车辆协同控制版:修复出生点索引越界问题 """ import sys @@ -12,19 +12,32 @@ import pygame import traceback import time +import threading +from concurrent.futures import ThreadPoolExecutor, as_completed +import logging +import random -# ===================== 全局配置 ====================== +# ===================== 全局配置 ======================= # CARLA连接 CARLA_HOST = "localhost" CARLA_PORT = 2000 -CARLA_TIMEOUT = 10.0 - -# 车辆配置 -VEHICLE_MODEL = "vehicle.tesla.model3" +CARLA_TIMEOUT = 20.0 + +# 多车辆配置 +VEHICLE_COUNT = 3 +VEHICLE_MODELS = [ + "vehicle.tesla.model3", + "vehicle.bmw.grandtourer", + "vehicle.audi.a2" +] +SPAWN_INTERVAL = 1.0 +SPAWN_RETRY_MAX = 8 +SPAWN_RETRY_DELAY = 0.5 +SPAWN_DISTANCE_LIMIT = 15.0 # 放宽距离限制到15米 + +# 车辆控制参数 VEHICLE_WHEELBASE = 2.9 VEHICLE_REAR_AXLE_OFFSET = 1.45 - -# 转向控制 LOOKAHEAD_DIST_STRAIGHT = 7.0 LOOKAHEAD_DIST_CURVE = 4.0 STEER_GAIN_STRAIGHT = 0.7 @@ -32,50 +45,49 @@ STEER_DEADZONE = 0.05 STEER_LOWPASS_ALPHA = 0.6 MAX_STEER = 1.0 - -# 弯道等级 DIR_CHANGE_GENTLE = 0.03 DIR_CHANGE_SHARP = 0.08 - -# 速度控制 -BASE_SPEED = 25.0 +BASE_SPEEDS = [25.0, 22.0, 20.0] PID_KP = 0.2 PID_KI = 0.01 PID_KD = 0.02 -# 相机 -CAMERA_POS = carla.Transform(carla.Location(x=-5.0, z=2.0)) -CAMERA_WIDTH = 800 -CAMERA_HEIGHT = 600 -CAMERA_FOV = 90 - -# 交通规则 +# 交通规则配置 TRAFFIC_LIGHT_STOP_DISTANCE = 4.0 -TRAFFIC_LIGHT_DETECTION_RANGE = 50.0 # 扩大检测范围,能检测更多交通灯 -TRAFFIC_LIGHT_ANGLE_THRESHOLD = 60.0 # 扩大检测角度 +TRAFFIC_LIGHT_DETECTION_RANGE = 50.0 +TRAFFIC_LIGHT_ANGLE_THRESHOLD = 60.0 STOP_SPEED_THRESHOLD = 0.2 GREEN_LIGHT_ACCEL_FACTOR = 0.25 STOP_LINE_SIM_DISTANCE = 5.0 - -# 交通灯状态循环配置(单位:秒) RED_LIGHT_DURATION = 3.0 GREEN_LIGHT_DURATION = 5.0 YELLOW_LIGHT_DURATION = 2.0 -# 车道行驶配置 -ROAD_DIRECTION_DOT_THRESHOLD = 0.0 -LANE_KEEP_STRICTNESS = 1.2 +# 相机配置 +WINDOW_WIDTH = 1280 +WINDOW_HEIGHT = 720 +CAMERA_FOV = 120 +CAMERA_POS = carla.Transform(carla.Location(x=-6.0, z=2.5), carla.Rotation(pitch=-5)) + +# 全局变量 +current_view_vehicle_id = 1 +vehicle_agents = [] -# ===================== 核心兼容工具函数 ====================== +# 日志配置 +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - 车辆%(vehicle_id)s - %(levelname)s - %(message)s", + handlers=[logging.FileHandler("multi_vehicle_simulation.log"), logging.StreamHandler()] +) + +# ===================== 核心工具函数 ====================== def is_actor_alive(actor): - """兼容不同版本的Actor存活状态判断""" try: return actor.is_alive() except TypeError: return actor.is_alive def get_traffic_light_stop_line(traffic_light): - """兼容不同版本的交通灯停止线获取""" try: return traffic_light.get_stop_line_location() except AttributeError: @@ -85,72 +97,363 @@ def get_traffic_light_stop_line(traffic_light): stop_line_loc.z = tl_transform.location.z return stop_line_loc -def get_spawn_point_near_traffic_light(world, map): - """自动查找距离交通灯最近的出生点""" - traffic_lights = world.get_actors().filter("traffic.traffic_light") - if not traffic_lights: - print("警告:当前地图中未找到交通灯,使用默认出生点") - spawn_points = map.get_spawn_points() - return spawn_points[0] if spawn_points else carla.Transform() +def calculate_dir_change(current_wp): + waypoints = [current_wp] + for i in range(5): + next_wps = waypoints[-1].next(1.0) + if next_wps: + waypoints.append(next_wps[0]) + else: + break - spawn_points = map.get_spawn_points() - if not spawn_points: - print("警告:未找到默认出生点,使用交通灯旁位置") - tl_transform = traffic_lights[0].get_transform() - return carla.Transform(tl_transform.location + carla.Location(x=-5.0, z=0.5), tl_transform.rotation) + if len(waypoints) < 4: + return 0.0, 0 - min_distance = float('inf') - best_spawn_point = spawn_points[0] + dirs = [] + for i in range(1, len(waypoints)): + wp_prev = waypoints[i-1] + wp_curr = waypoints[i] + dir_rad = math.atan2( + wp_curr.transform.location.y - wp_prev.transform.location.y, + wp_curr.transform.location.x - wp_prev.transform.location.x + ) + dirs.append(dir_rad) - for spawn_point in spawn_points: - for tl in traffic_lights: - if not is_actor_alive(tl): - continue - tl_loc = tl.get_transform().location - spawn_loc = spawn_point.location - distance = math.sqrt((tl_loc.x - spawn_loc.x)**2 + (tl_loc.y - spawn_loc.y)**2) - if distance < min_distance: - min_distance = distance - best_spawn_point = spawn_point + dir_change = 0.0 + for i in range(1, len(dirs)): + dir_change += abs(dirs[i] - dirs[i-1]) * 2 - print(f"找到距离交通灯最近的出生点,距离:{min_distance:.2f}米") - return best_spawn_point + if dir_change < DIR_CHANGE_GENTLE: + curve_level = 0 + elif dir_change < DIR_CHANGE_SHARP: + curve_level = 1 + else: + curve_level = 2 + + return dir_change, curve_level + +def get_forward_waypoint(vehicle, map): + vehicle_transform = vehicle.get_transform() + current_wp = map.get_waypoint( + vehicle_transform.location, + project_to_road=True, + lane_type=carla.LaneType.Driving + ) + + # 所有车辆使用同一车道的路点 + global vehicle_agents + if len(vehicle_agents) > 0: + try: + lead_vehicle_wp = map.get_waypoint(vehicle_agents[0].vehicle.get_transform().location, project_to_road=True) + current_wp = map.get_waypoint(vehicle_transform.location, project_to_road=True, lane_id=lead_vehicle_wp.lane_id) + except: + pass + + road_direction = current_wp.transform.get_forward_vector() + vehicle_direction = vehicle_transform.get_forward_vector() + dot_product = road_direction.x * vehicle_direction.x + road_direction.y * vehicle_direction.y -def cycle_traffic_light_states(world): + if dot_product < 0.0: + forward_wps = current_wp.next(10.0) + if forward_wps: + current_wp = forward_wps[0] + else: + current_wp = map.get_waypoint( + vehicle_transform.location + vehicle_direction * 5.0, + project_to_road=True + ) + + return current_wp + +def get_valid_spawn_points(map, count, base_location=None, radius=100.0): """ - 循环控制所有交通灯的状态:红→绿→黄→红 - 作为后台线程运行,确保交通灯持续变化 + 获取有效的出生点(增加容错性,避免索引越界) """ - while True: - traffic_lights = world.get_actors().filter("traffic.traffic_light") - # 设置所有交通灯为红灯 - for tl in traffic_lights: - if is_actor_alive(tl): - try: - tl.set_state(carla.TrafficLightState.Red) - except: - pass - time.sleep(RED_LIGHT_DURATION) + # 1. 获取地图所有出生点 + all_spawn_points = map.get_spawn_points() + if not all_spawn_points: + raise RuntimeError("地图中无任何出生点") + + # 2. 初始化候选点列表 + candidate_points = [] + + # 3. 如果有基准位置,先筛选附近的点;否则直接使用所有点 + if base_location: + filtered_points = [] + for sp in all_spawn_points: + dist = sp.location.distance(base_location) + if dist <= radius: + filtered_points.append((dist, sp)) + # 按距离排序 + filtered_points.sort(key=lambda x: x[0]) + candidate_points = [sp for _, sp in filtered_points] + + # 4. 如果候选点为空,直接使用所有出生点(容错) + if not candidate_points: + candidate_points = all_spawn_points + print(f"警告:基准位置{base_location}附近无出生点,使用全局出生点") + + # 5. 筛选集中的出生点(放宽条件) + valid_points = [] + # 确保基准点存在(核心修复:避免candidate_points[0]索引越界) + if not candidate_points: + candidate_points = all_spawn_points + + base_sp = candidate_points[0] + valid_points.append(base_sp) + + # 6. 筛选其他点,放宽距离限制 + for sp in candidate_points[1:]: + try: + # 检查与已选点的距离(放宽到15米) + if all(sp.location.distance(vp.location) <= SPAWN_DISTANCE_LIMIT for vp in valid_points): + wp = map.get_waypoint(sp.location, project_to_road=True) + if wp.lane_type == carla.LaneType.Driving and 0.0 <= sp.location.z <= 2.0: + valid_points.append(sp) + if len(valid_points) >= count: + break + except: + continue - # 设置所有交通灯为绿灯 - for tl in traffic_lights: - if is_actor_alive(tl): + # 7. 如果数量不够,进一步放宽条件(距离限制到20米) + if len(valid_points) < count: + for sp in candidate_points: + if sp not in valid_points: try: - tl.set_state(carla.TrafficLightState.Green) + if all(sp.location.distance(vp.location) <= SPAWN_DISTANCE_LIMIT * 1.5 for vp in valid_points): + wp = map.get_waypoint(sp.location, project_to_road=True) + if wp.lane_type == carla.LaneType.Driving and 0.0 <= sp.location.z <= 2.0: + valid_points.append(sp) + if len(valid_points) >= count: + break except: - pass - time.sleep(GREEN_LIGHT_DURATION) + continue + + # 8. 如果还是不够,直接取前N个点(最终容错) + if len(valid_points) < count: + print(f"警告:无法找到{count}个集中的出生点,直接取前{count}个可用点") + for sp in candidate_points: + if sp not in valid_points: + wp = map.get_waypoint(sp.location, project_to_road=True) + if wp.lane_type == carla.LaneType.Driving and 0.0 <= sp.location.z <= 2.0: + valid_points.append(sp) + if len(valid_points) >= count: + break + + # 9. 最终检查:确保数量足够 + if len(valid_points) < count: + # 直接取所有可用点,不足的话重复使用(极端情况) + while len(valid_points) < count: + valid_points.append(valid_points[0]) + print(f"警告:出生点数量不足,重复使用已有点") + + # 10. 统一出生点朝向 + try: + forward_vec = valid_points[0].transform.get_forward_vector() + for sp in valid_points: + sp.rotation.yaw = math.degrees(math.atan2(forward_vec.y, forward_vec.x)) + except: + pass + + return valid_points[:count] + +def check_spawn_collision(world, spawn_point, radius=3.0): + # 检查周围车辆和行人 + vehicles = world.get_actors().filter("vehicle.*") + for vehicle in vehicles: + if is_actor_alive(vehicle): + dist = vehicle.get_transform().location.distance(spawn_point.location) + if dist < radius: + return False + + walkers = world.get_actors().filter("walker.*") + for walker in walkers: + if is_actor_alive(walker): + dist = walker.get_transform().location.distance(spawn_point.location) + if dist < radius: + return False + + return True + +# ===================== 相机管理类(多车辆)===================== +class VehicleCamera: + def __init__(self, world, vehicle, vehicle_id): + self.world = world + self.vehicle = vehicle + self.vehicle_id = vehicle_id + self.camera = None + self.image_surface = None - # 设置所有交通灯为黄灯 - for tl in traffic_lights: - if is_actor_alive(tl): + # 创建相机传感器 + self._create_camera() + + def _create_camera(self): + # 加载相机蓝图 + camera_bp = self.world.get_blueprint_library().find("sensor.camera.rgb") + camera_bp.set_attribute("image_size_x", str(640)) + camera_bp.set_attribute("image_size_y", str(360)) + camera_bp.set_attribute("fov", str(CAMERA_FOV)) + + # 生成相机(附加到车辆) + self.camera = self.world.spawn_actor(camera_bp, CAMERA_POS, attach_to=self.vehicle) + + # 注册图像回调函数 + self.camera.listen(self._on_image) + + def _on_image(self, image): + # 将CARLA图像转换为Pygame Surface + array = np.frombuffer(image.raw_data, dtype=np.uint8) + array = array.reshape((image.height, image.width, 4)) + array = array[:, :, :3] + array = array[:, :, ::-1] + array = np.swapaxes(array, 0, 1) + + # 存储为Pygame Surface + self.image_surface = pygame.surfarray.make_surface(array) + + def destroy(self): + if self.camera: + self.camera.stop() + self.camera.destroy() + +# ===================== 车辆控制类 ====================== +class VehicleAgent: + def __init__(self, world, map, vehicle_id, spawn_point, vehicle_model, base_speed): + self.vehicle_id = vehicle_id + self.world = world + self.map = map + self.base_speed = base_speed + self.logger = logging.getLogger(__name__) + self.logger = logging.LoggerAdapter(self.logger, {"vehicle_id": vehicle_id}) + + # 生成车辆 + self.vehicle_bp = self.world.get_blueprint_library().find(vehicle_model) + if self.vehicle_bp.has_attribute("color"): + color = random.choice(self.vehicle_bp.get_attribute("color").recommended_values) + self.vehicle_bp.set_attribute("color", color) + + self.vehicle = self._spawn_vehicle_with_retry(spawn_point) + if not self.vehicle: + raise RuntimeError(f"车辆{vehicle_id}生成失败") + + # 创建相机 + self.camera = VehicleCamera(world, self.vehicle, vehicle_id) + + # 初始化控制器 + self.pp_controller = AdaptivePurePursuit(VEHICLE_WHEELBASE) + self.speed_controller = SpeedController(PID_KP, PID_KI, PID_KD, base_speed) + self.traffic_light_manager = TrafficLightManager(vehicle_id) + + self.is_alive = True + self.logger.info(f"生成成功,车型:{vehicle_model},出生点:({spawn_point.location.x:.1f},{spawn_point.location.y:.1f})") + + def _spawn_vehicle_with_retry(self, initial_spawn_point): + all_spawn_points = self.map.get_spawn_points() + if not all_spawn_points: + self.logger.error("地图中无有效出生点") + return None + + candidate_points = [initial_spawn_point] + candidate_points += random.sample(all_spawn_points, min(10, len(all_spawn_points))) + + for retry in range(SPAWN_RETRY_MAX): + spawn_point = candidate_points[retry % len(candidate_points)] + spawn_point.location.z += 0.3 + spawn_point.rotation.yaw += random.randint(-5, 5) + + if not check_spawn_collision(self.world, spawn_point): + self.logger.warning(f"第{retry+1}次重试:出生点有碰撞风险,跳过") + time.sleep(SPAWN_RETRY_DELAY) + continue + + try: + return self.world.spawn_actor(self.vehicle_bp, spawn_point) + except Exception as e: + self.logger.warning(f"第{retry+1}次重试失败:{e}") + time.sleep(SPAWN_RETRY_DELAY) + + self.logger.error(f"超过{SPAWN_RETRY_MAX}次重试,生成失败") + return None + + def update(self): + if not self.is_alive or not is_actor_alive(self.vehicle): + self.is_alive = False + self.logger.error("车辆已销毁,停止更新") + return False + + try: + # 获取车辆状态 + vehicle_transform = self.vehicle.get_transform() + vehicle_vel = self.vehicle.get_velocity() + current_speed = math.hypot(vehicle_vel.x, vehicle_vel.y) * 3.6 + + # 路径跟踪 + current_wp = get_forward_waypoint(self.vehicle, self.map) + dir_change, curve_level = calculate_dir_change(current_wp) + lookahead_dist = self.pp_controller.get_adaptive_lookahead(dir_change) + target_wps = current_wp.next(lookahead_dist) + target_point = target_wps[0].transform.location if target_wps else vehicle_transform.location + + # 速度控制 + curve_speed_factors = [1.0, 0.7, 0.4] + speed_factor = curve_speed_factors[min(curve_level, 2)] + base_target_speed = self.base_speed * speed_factor + base_target_speed = max(8.0, base_target_speed) + + # 跟车控制 + global vehicle_agents + if self.vehicle_id > 1 and len(vehicle_agents) >= self.vehicle_id: try: - tl.set_state(carla.TrafficLightState.Yellow) + lead_vehicle = vehicle_agents[self.vehicle_id - 2].vehicle + lead_vehicle_transform = lead_vehicle.get_transform() + dist_to_lead = vehicle_transform.location.distance(lead_vehicle_transform.location) + if dist_to_lead < 15.0: + base_target_speed = max(5.0, base_target_speed * 0.5) except: pass - time.sleep(YELLOW_LIGHT_DURATION) -# ===================== 纯追踪控制器 ===================== + # 交通灯处理 + target_speed, traffic_light_status = self.traffic_light_manager.handle_traffic_light_logic( + self.vehicle, current_speed, base_target_speed + ) + + # 计算控制指令 + steer = self.pp_controller.calculate_steer(vehicle_transform, target_point, dir_change) + throttle = self.speed_controller.calculate(target_speed, current_speed) + brake = 1.0 - throttle if current_speed > target_speed + 1 else 0.0 + + if "Red (Stopped)" in traffic_light_status or target_speed == 0.0: + throttle = 0.0 + brake = 1.0 + + # 应用控制 + control = carla.VehicleControl() + control.steer = steer + control.throttle = throttle + control.brake = brake + self.vehicle.apply_control(control) + + # 日志输出 + self.logger.info( + f"速度:{current_speed:5.1f}km/h | 目标:{target_speed:5.1f} | " + f"弯道:{['直道', '缓弯', '急弯'][curve_level]:<3} | 灯状态:{traffic_light_status}" + ) + + return True + + except Exception as e: + self.logger.error(f"更新失败:{e}", exc_info=True) + return False + + def destroy(self): + # 销毁相机 + self.camera.destroy() + # 销毁车辆 + if self.vehicle and is_actor_alive(self.vehicle): + self.vehicle.destroy() + self.logger.info("车辆资源已清理") + +# ===================== 控制器类 ====================== class AdaptivePurePursuit: def __init__(self, wheelbase): self.wheelbase = wheelbase @@ -158,7 +461,6 @@ def __init__(self, wheelbase): self.last_lookahead = LOOKAHEAD_DIST_STRAIGHT def calculate_steer(self, vehicle_transform, target_point, dir_change): - # 1. 后轴位置 forward_vec = vehicle_transform.get_forward_vector() rear_axle_loc = carla.Location( x=vehicle_transform.location.x - forward_vec.x * VEHICLE_REAR_AXLE_OFFSET, @@ -166,7 +468,6 @@ def calculate_steer(self, vehicle_transform, target_point, dir_change): z=vehicle_transform.location.z ) - # 2. 车辆坐标系转换 dx = target_point.x - rear_axle_loc.x dy = target_point.y - rear_axle_loc.y yaw = math.radians(vehicle_transform.rotation.yaw) @@ -174,7 +475,6 @@ def calculate_steer(self, vehicle_transform, target_point, dir_change): dx_vehicle = dx * math.cos(yaw) + dy * math.sin(yaw) dy_vehicle = -dx * math.sin(yaw) + dy * math.cos(yaw) - # 3. 转向增益 steer_gain = np.interp( dir_change, [0, DIR_CHANGE_SHARP], @@ -182,15 +482,13 @@ def calculate_steer(self, vehicle_transform, target_point, dir_change): ) steer_gain = np.clip(steer_gain, STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE) - # 4. 纯追踪计算 if dx_vehicle < 0.1: steer = self.last_steer else: - steer_rad = math.atan2(2 * self.wheelbase * dy_vehicle * LANE_KEEP_STRICTNESS, dx_vehicle ** 2 + dy_vehicle ** 2) + steer_rad = math.atan2(2 * self.wheelbase * dy_vehicle, dx_vehicle ** 2 + dy_vehicle ** 2) steer = steer_rad / math.pi steer *= steer_gain - # 5. 死区+滤波 if abs(steer) < STEER_DEADZONE: steer = 0.0 steer = STEER_LOWPASS_ALPHA * steer + (1 - STEER_LOWPASS_ALPHA) * self.last_steer @@ -209,12 +507,12 @@ def get_adaptive_lookahead(self, dir_change): self.last_lookahead = lookahead_dist return lookahead_dist -# ===================== 速度控制器 ===================== class SpeedController: - def __init__(self, kp, ki, kd): + def __init__(self, kp, ki, kd, base_speed): self.kp = kp self.ki = ki self.kd = kd + self.base_speed = base_speed self.last_error = 0.0 self.integral = 0.0 @@ -228,15 +526,16 @@ def calculate(self, target_speed, current_speed): self.last_error = error return np.clip(p + i + d, 0.0, 1.0) -# ===================== 交通灯管理类 ====================== class TrafficLightManager: - def __init__(self): + def __init__(self, vehicle_id): + self.vehicle_id = vehicle_id self.tracked_light = None self.is_stopped_at_red = False self.red_light_stop_time = 0 + self.logger = logging.getLogger(__name__) + self.logger = logging.LoggerAdapter(self.logger, {"vehicle_id": vehicle_id}) def _calculate_angle_between_vehicle_and_light(self, vehicle_transform, light_transform): - """计算车辆前进方向与交通灯的夹角""" vehicle_forward = vehicle_transform.get_forward_vector() vehicle_forward = np.array([vehicle_forward.x, vehicle_forward.y]) vehicle_forward = vehicle_forward / np.linalg.norm(vehicle_forward) @@ -252,18 +551,15 @@ def _calculate_angle_between_vehicle_and_light(self, vehicle_transform, light_tr return angle def get_lane_traffic_light(self, vehicle, world): - """扩大检测范围,检测更多交通灯""" vehicle_transform = vehicle.get_transform() vehicle_loc = vehicle_transform.location - # 检查跟踪的交通灯是否存活 if self.tracked_light and is_actor_alive(self.tracked_light): dist = self.tracked_light.get_transform().location.distance(vehicle_loc) angle = self._calculate_angle_between_vehicle_and_light(vehicle_transform, self.tracked_light.get_transform()) if dist < TRAFFIC_LIGHT_DETECTION_RANGE and angle < TRAFFIC_LIGHT_ANGLE_THRESHOLD: return self.tracked_light - # 获取所有交通灯并筛选有效灯 traffic_lights = world.get_actors().filter("traffic.traffic_light") valid_lights = [] @@ -286,16 +582,14 @@ def get_lane_traffic_light(self, vehicle, world): return None def handle_traffic_light_logic(self, vehicle, current_speed, base_target_speed): - """红灯强制停车,绿灯恢复行驶""" world = vehicle.get_world() traffic_light = self.get_lane_traffic_light(vehicle, world) if not traffic_light: self.is_stopped_at_red = False self.red_light_stop_time = 0 - return base_target_speed, "No Light (Lane)" + return base_target_speed, "No Light" - # 核心:使用兼容函数获取停止线位置 stop_line_loc = get_traffic_light_stop_line(traffic_light) dist_to_stop_line = vehicle.get_transform().location.distance(stop_line_loc) @@ -305,296 +599,310 @@ def handle_traffic_light_logic(self, vehicle, current_speed, base_target_speed): target_speed = max(STOP_SPEED_THRESHOLD, recovery_speed) if abs(target_speed - base_target_speed) < 0.5: self.is_stopped_at_red = False - return target_speed, "Green (Recovering)" - return base_target_speed, "Green (Lane)" + self.logger.info(f"绿灯恢复行驶,目标速度:{target_speed:.1f}km/h") + return target_speed, "Green" + return base_target_speed, "Green" elif traffic_light.get_state() == carla.TrafficLightState.Yellow: self.is_stopped_at_red = False yellow_speed = max(5.0, base_target_speed * 0.3) - return yellow_speed, "Yellow (Stop Soon)" + self.logger.warning(f"黄灯减速,目标速度:{yellow_speed:.1f}km/h") + return yellow_speed, "Yellow" elif traffic_light.get_state() == carla.TrafficLightState.Red: if dist_to_stop_line > TRAFFIC_LIGHT_STOP_DISTANCE: self.is_stopped_at_red = False red_speed = max(2.0, current_speed * 0.1) - return red_speed, f"Red (Decelerating: {dist_to_stop_line:.1f}m)" + self.logger.warning(f"红灯减速,距离停止线:{dist_to_stop_line:.1f}m,目标速度:{red_speed:.1f}km/h") + return red_speed, "Red" else: if current_speed <= STOP_SPEED_THRESHOLD: self.is_stopped_at_red = True self.red_light_stop_time += 1 wait_seconds = self.red_light_stop_time // 30 - return 0.0, f"Red (Stopped: {wait_seconds}s)" + self.logger.info(f"红灯停车等待:{wait_seconds}s") + return 0.0, "Red (Stopped)" else: - return 0.0, "Red (Emergency Stop)" + self.logger.warning("红灯紧急制动") + return 0.0, "Red (Braking)" - return base_target_speed, "Unknown Light" + return base_target_speed, "Unknown" -# ===================== 车道行驶辅助函数 ====================== -def calculate_dir_change(current_wp): - """计算方向变化量,判断弯道等级""" - waypoints = [current_wp] - for i in range(5): - next_wps = waypoints[-1].next(1.0) - if next_wps: - waypoints.append(next_wps[0]) - else: - break - - if len(waypoints) < 4: - return 0.0, 0 - - dirs = [] - for i in range(1, len(waypoints)): - wp_prev = waypoints[i-1] - wp_curr = waypoints[i] - dir_rad = math.atan2( - wp_curr.transform.location.y - wp_prev.transform.location.y, - wp_curr.transform.location.x - wp_prev.transform.location.x - ) - dirs.append(dir_rad) - - dir_change = 0.0 - for i in range(1, len(dirs)): - dir_change += abs(dirs[i] - dirs[i-1]) * 2 - - if dir_change < DIR_CHANGE_GENTLE: - curve_level = 0 - elif dir_change < DIR_CHANGE_SHARP: - curve_level = 1 - else: - curve_level = 2 - - return dir_change, curve_level - -def get_forward_waypoint(vehicle, map): - """获取车辆当前车道的前进方向路点(极低版本兼容)""" - vehicle_transform = vehicle.get_transform() - # 1. 投影到道路 - current_wp = map.get_waypoint( - vehicle_transform.location, - project_to_road=True - ) - - # 2. 纯数学判断方向是否相反 - road_direction = current_wp.transform.get_forward_vector() - vehicle_direction = vehicle_transform.get_forward_vector() - dot_product = road_direction.x * vehicle_direction.x + road_direction.y * vehicle_direction.y - - # 3. 方向相反则取前方点 - if dot_product < ROAD_DIRECTION_DOT_THRESHOLD: - forward_wps = current_wp.next(10.0) - if forward_wps: - current_wp = forward_wps[0] - else: - current_wp = map.get_waypoint( - vehicle_transform.location + vehicle_direction * 5.0, - project_to_road=True - ) - - return current_wp - -# ===================== 相机管理器 ===================== -class CameraManager: - def __init__(self, world, vehicle, display): - self.world = world - self.vehicle = vehicle - self.display = display - self.camera = None - self.traffic_light_status = "No Light" - self._create_camera() - - def _create_camera(self): - bp = self.world.get_blueprint_library().find("sensor.camera.rgb") - bp.set_attribute("image_size_x", str(CAMERA_WIDTH)) - bp.set_attribute("image_size_y", str(CAMERA_HEIGHT)) - bp.set_attribute("fov", str(CAMERA_FOV)) - self.camera = self.world.spawn_actor(bp, CAMERA_POS, attach_to=self.vehicle) - self.camera.listen(self._on_image) +# ===================== 交通灯控制线程 ====================== +def cycle_traffic_light_states(world, stop_event): + logger = logging.getLogger(__name__) + logger = logging.LoggerAdapter(logger, {"vehicle_id": "系统"}) + while not stop_event.is_set(): + traffic_lights = world.get_actors().filter("traffic.traffic_light") + if not traffic_lights: + time.sleep(1) + continue - def _on_image(self, image): - """修正数组处理逻辑""" - array = np.frombuffer(image.raw_data, dtype=np.uint8) - array = array.reshape((CAMERA_HEIGHT, CAMERA_WIDTH, 4)) - array = array[:, :, :3] - array = array[:, :, ::-1].swapaxes(0, 1) - self.display.blit(pygame.surfarray.make_surface(array), (0, 0)) - self._draw_info() - pygame.display.flip() - - def _draw_info(self): - font = pygame.font.SysFont("Arial", 24, bold=True) - if "Red" in self.traffic_light_status: - color = (255, 0, 0) - elif "Green" in self.traffic_light_status: - color = (0, 255, 0) - elif "Yellow" in self.traffic_light_status: - color = (255, 255, 0) - else: - color = (255, 255, 255) + # 红灯 + for tl in traffic_lights: + if is_actor_alive(tl): + try: + tl.set_state(carla.TrafficLightState.Red) + except: + pass + logger.info(f"所有交通灯切换为红灯,持续{RED_LIGHT_DURATION}秒") + stop_event.wait(RED_LIGHT_DURATION) + if stop_event.is_set(): + break - text = font.render(f"Traffic Light: {self.traffic_light_status}", True, color) - bg = pygame.Surface((text.get_width() + 10, text.get_height() + 5)) - bg.fill((0, 0, 0)) - self.display.blit(bg, (5, 5)) - self.display.blit(text, (10, 7)) + # 绿灯 + for tl in traffic_lights: + if is_actor_alive(tl): + try: + tl.set_state(carla.TrafficLightState.Green) + except: + pass + logger.info(f"所有交通灯切换为绿灯,持续{GREEN_LIGHT_DURATION}秒") + stop_event.wait(GREEN_LIGHT_DURATION) + if stop_event.is_set(): + break - def update_traffic_light_status(self, status): - self.traffic_light_status = status + # 黄灯 + for tl in traffic_lights: + if is_actor_alive(tl): + try: + tl.set_state(carla.TrafficLightState.Yellow) + except: + pass + logger.info(f"所有交通灯切换为黄灯,持续{YELLOW_LIGHT_DURATION}秒") + stop_event.wait(YELLOW_LIGHT_DURATION) + if stop_event.is_set(): + break - def destroy(self): - if self.camera: - if is_actor_alive(self.camera): - self.camera.stop() - self.camera.destroy() + logger.info("交通灯线程停止") -# ===================== 主函数(核心:多交通灯配置)===================== +# ===================== 主函数 ====================== def main(): + global current_view_vehicle_id, vehicle_agents pygame.init() - display = pygame.display.set_mode((CAMERA_WIDTH, CAMERA_HEIGHT)) - pygame.display.set_caption("CARLA 多交通灯版(Town04密集交通灯+状态循环)") + screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) + pygame.display.set_caption(f"CARLA多车辆视角({VEHICLE_COUNT}辆车)- 按1/2/3切换视角,按S切换分屏,按V切换俯视视角") client = None world = None - vehicle = None - camera_manager = None - pp_controller = None - speed_controller = None - traffic_light_manager = None + map = None + tl_cycle_thread = None + tl_stop_event = threading.Event() + show_split_screen = True + show_top_view = False + top_view_camera = None + + # 清理函数 + def cleanup(): + print("\n开始清理资源...") + tl_stop_event.set() + if tl_cycle_thread and tl_cycle_thread.is_alive(): + tl_cycle_thread.join(timeout=2) + + if top_view_camera: + top_view_camera.stop() + top_view_camera.destroy() + + for agent in vehicle_agents: + agent.destroy() + + if world: + for actor in world.get_actors(): + if actor.type_id.startswith(("vehicle.", "walker.", "sensor.")): + if is_actor_alive(actor): + actor.destroy() + + pygame.quit() + print("资源清理完成") + + # 注册退出回调 + import atexit + import signal + atexit.register(cleanup) + signal.signal(signal.SIGINT, lambda sig, frame: sys.exit(0)) try: - # 1. 连接CARLA,加载**Town04**(交通灯最密集的地图) + # 连接CARLA client = carla.Client(CARLA_HOST, CARLA_PORT) client.set_timeout(CARLA_TIMEOUT) try: - client.load_world("Town04") # 替换为Town04,交通灯数量远多于Town03 - except: + world = client.load_world("Town04") + print("成功加载Town04地图") + except Exception as e: world = client.get_world() - print("警告:Town04地图不存在,使用当前地图") - else: - world = client.get_world() - print("成功加载Town04地图(交通灯密集)") + print(f"警告:Town04地图加载失败({e}),使用当前地图") map = world.get_map() - # 2. 清理残留演员 + # 清理残留演员 + print("清理残留演员...") for actor in world.get_actors(): - if actor.type_id.startswith(("vehicle.", "walker.", "sensor.", "controller.")): + if actor.type_id.startswith(("vehicle.", "walker.", "sensor.")): if is_actor_alive(actor): actor.destroy() - print("残留演员清理完成") - - # 3. 启动交通灯状态循环线程(后台持续切换交通灯状态) - import threading - tl_cycle_thread = threading.Thread(target=cycle_traffic_light_states, args=(world,), daemon=True) - tl_cycle_thread.start() - print("交通灯状态循环线程已启动(红3秒→绿5秒→黄2秒)") - - # 4. 设置车辆起始位置(Town04交通灯密集区) - vehicle_bp = world.get_blueprint_library().find(VEHICLE_MODEL) - - # 手动指定Town04的核心交通灯路口坐标(经测试:多个交通灯环绕) - spawn_point = carla.Transform( - carla.Location(x=220.0, y=150.0, z=0.5), # Town04核心交通灯密集区 - carla.Rotation(yaw=90.0) - ) - spawn_point.location.z += 0.2 - vehicle = world.try_spawn_actor(vehicle_bp, spawn_point) - - # 备用方案:自动查找交通灯附近的出生点 - if not vehicle: - print("手动坐标生成失败,自动查找交通灯附近的出生点...") - spawn_point = get_spawn_point_near_traffic_light(world, map) - vehicle = world.spawn_actor(vehicle_bp, spawn_point) - - # 最终备用方案 - if not vehicle: - spawn_points = map.get_spawn_points() - spawn_point = spawn_points[0] if spawn_points else carla.Transform() - vehicle = world.spawn_actor(vehicle_bp, spawn_point) - - print(f"车辆生成成功:{vehicle.type_id}(起始于交通灯密集区)") - - # 5. 初始化组件 - pp_controller = AdaptivePurePursuit(VEHICLE_WHEELBASE) - speed_controller = SpeedController(PID_KP, PID_KI, PID_KD) - traffic_light_manager = TrafficLightManager() - camera_manager = CameraManager(world, vehicle, display) - - # 6. 主循环 - print("仿真启动,按ESC退出...(车辆将经过多个交通灯)") - clock = pygame.time.Clock() - running = True - - while running: - for event in pygame.event.get(): - if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): - running = False + time.sleep(3.0) + print("清理完成") + + # 自动获取地图的第一个出生点作为基准(避免手动坐标无效) + base_location = None + all_spawn_points = map.get_spawn_points() + if all_spawn_points: + base_location = all_spawn_points[0].location + print(f"使用地图第一个出生点作为基准:({base_location.x:.1f}, {base_location.y:.1f})") + else: + base_location = carla.Location(x=220.0, y=150.0, z=0.5) - # 获取车辆状态 - vehicle_transform = vehicle.get_transform() - vehicle_vel = vehicle.get_velocity() - current_speed = math.hypot(vehicle_vel.x, vehicle_vel.y) * 3.6 + # 获取有效的出生点 + print(f"获取{VEHICLE_COUNT}个有效出生点...") + valid_spawn_points = get_valid_spawn_points(map, VEHICLE_COUNT, base_location) + for i, sp in enumerate(valid_spawn_points): + print(f" 出生点{i+1}:({sp.location.x:.1f},{sp.location.y:.1f})") - # 核心:获取前进方向路点 - current_wp = get_forward_waypoint(vehicle, map) + # 生成车辆 + print(f"\n分步生成车辆(间隔{SPAWN_INTERVAL}秒)...") + for i in range(VEHICLE_COUNT): + vehicle_model = VEHICLE_MODELS[i % len(VEHICLE_MODELS)] + base_speed = BASE_SPEEDS[i % len(BASE_SPEEDS)] + spawn_point = valid_spawn_points[i] - # 计算方向变化 - dir_change, curve_level = calculate_dir_change(current_wp) + try: + print(f"\n生成车辆{i+1}(车型:{vehicle_model})...") + agent = VehicleAgent(world, map, i+1, spawn_point, vehicle_model, base_speed) + vehicle_agents.append(agent) + print(f"车辆{i+1}生成成功!") + except Exception as e: + print(f"车辆{i+1}生成失败:{e}") - # 预瞄距离 - lookahead_dist = pp_controller.get_adaptive_lookahead(dir_change) + time.sleep(SPAWN_INTERVAL) - # 目标路点 - target_wps = current_wp.next(lookahead_dist) - target_point = target_wps[0].transform.location if target_wps else vehicle_transform.location + if len(vehicle_agents) == 0: + raise RuntimeError("无车辆生成成功,仿真终止") - # 基础速度 - curve_speed_factors = [1.0, 0.7, 0.4] - speed_factor = curve_speed_factors[min(curve_level, 2)] - base_target_speed = BASE_SPEED * speed_factor - base_target_speed = max(8.0, base_target_speed) + print(f"\n共生成{len(vehicle_agents)}辆车辆!") - # 交通灯处理(检测多个交通灯) - target_speed, traffic_light_status = traffic_light_manager.handle_traffic_light_logic( - vehicle, current_speed, base_target_speed + # 创建全局俯视相机 + try: + top_view_bp = world.get_blueprint_library().find("sensor.camera.rgb") + top_view_bp.set_attribute("image_size_x", str(WINDOW_WIDTH)) + top_view_bp.set_attribute("image_size_y", str(WINDOW_HEIGHT)) + top_view_bp.set_attribute("fov", str(90)) + top_view_transform = carla.Transform( + vehicle_agents[0].vehicle.get_transform().location + carla.Location(z=50), + carla.Rotation(pitch=-90) ) - camera_manager.update_traffic_light_status(traffic_light_status) - - # 控制计算 - steer = pp_controller.calculate_steer(vehicle_transform, target_point, dir_change) - throttle = speed_controller.calculate(target_speed, current_speed) - brake = 1.0 - throttle if current_speed > target_speed + 1 else 0.0 - - # 红灯刹车 - if "Red (Stopped)" in traffic_light_status or target_speed == 0.0: - throttle = 0.0 - brake = 1.0 + top_view_camera = world.spawn_actor(top_view_bp, top_view_transform) + top_view_surface = None + top_view_camera.listen(lambda image: globals().update({ + "top_view_surface": pygame.surfarray.make_surface( + np.swapaxes(np.array(image.raw_data).reshape((image.height, image.width, 4))[:, :, :3][:, :, ::-1], 0, 1) + ) + })) + except: + print("警告:无法创建俯视相机") - # 应用控制 - control = carla.VehicleControl() - control.steer = steer - control.throttle = throttle - control.brake = brake - vehicle.apply_control(control) + # 启动交通灯线程 + tl_cycle_thread = threading.Thread(target=cycle_traffic_light_states, args=(world, tl_stop_event), daemon=True) + tl_cycle_thread.start() + print("交通灯线程启动") - # 打印状态(显示当前交通灯状态) - curve_names = ["直道", "缓弯", "急弯"] - lane_id = current_wp.lane_id - print(f"速度:{current_speed:5.1f}km/h | 目标:{target_speed:5.1f} | 弯道:{curve_names[curve_level]:<3} | 车道ID:{lane_id} | 灯状态:{traffic_light_status}") + # 主循环 + clock = pygame.time.Clock() + running = True + while running: + # 事件处理 + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_1 and len(vehicle_agents) >= 1: + current_view_vehicle_id = 1 + show_split_screen = False + show_top_view = False + elif event.key == pygame.K_2 and len(vehicle_agents) >= 2: + current_view_vehicle_id = 2 + show_split_screen = False + show_top_view = False + elif event.key == pygame.K_3 and len(vehicle_agents) >= 3: + current_view_vehicle_id = 3 + show_split_screen = False + show_top_view = False + elif event.key == pygame.K_s: + show_split_screen = True + show_top_view = False + elif event.key == pygame.K_v: + show_top_view = True + show_split_screen = False + elif event.key == pygame.K_ESCAPE: + running = False + + # 清空屏幕 + screen.fill((0, 0, 0)) + + if show_top_view: + if top_view_surface: + screen.blit(top_view_surface, (0, 0)) + elif show_split_screen: + if len(vehicle_agents) == 1: + agent = vehicle_agents[0] + if agent.camera.image_surface: + surface = pygame.transform.scale(agent.camera.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT)) + screen.blit(surface, (0, 0)) + elif len(vehicle_agents) == 2: + agent1 = vehicle_agents[0] + agent2 = vehicle_agents[1] + + if agent1.camera.image_surface: + surface1 = pygame.transform.scale(agent1.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT)) + screen.blit(surface1, (0, 0)) + + if agent2.camera.image_surface: + surface2 = pygame.transform.scale(agent2.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT)) + screen.blit(surface2, (WINDOW_WIDTH//2, 0)) + elif len(vehicle_agents) >= 3: + agent1 = vehicle_agents[0] + agent2 = vehicle_agents[1] + agent3 = vehicle_agents[2] + + if agent1.camera.image_surface: + surface1 = pygame.transform.scale(agent1.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) + screen.blit(surface1, (0, 0)) + + if agent2.camera.image_surface: + surface2 = pygame.transform.scale(agent2.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) + screen.blit(surface2, (WINDOW_WIDTH//2, 0)) + + if agent3.camera.image_surface: + surface3 = pygame.transform.scale(agent3.camera.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT//2)) + screen.blit(surface3, (0, WINDOW_HEIGHT//2)) + else: + target_agent = None + for agent in vehicle_agents: + if agent.vehicle_id == current_view_vehicle_id: + target_agent = agent + break + + if target_agent and target_agent.camera.image_surface: + surface = pygame.transform.scale(target_agent.camera.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT)) + screen.blit(surface, (0, 0)) + + # 更新车辆状态 + with ThreadPoolExecutor(max_workers=VEHICLE_COUNT) as executor: + futures = [executor.submit(agent.update) for agent in vehicle_agents] + for future in as_completed(futures): + try: + future.result() + except Exception as e: + print(f"车辆更新异常:{e}") + + # 刷新屏幕 + pygame.display.flip() clock.tick(30) except Exception as e: - print(f"错误:{e}") + print(f"仿真异常:{e}") traceback.print_exc() - finally: - print("清理资源...") - if camera_manager: - camera_manager.destroy() - if vehicle and is_actor_alive(vehicle): - vehicle.destroy() - pygame.quit() - print("仿真结束") + cleanup() if __name__ == "__main__": main() \ No newline at end of file From 63165f8b86d1ae9ffe4ff84ef4f14424a5e79c33 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Sun, 21 Dec 2025 20:35:27 +0800 Subject: [PATCH 15/26] =?UTF-8?q?=E6=9B=B4=E6=96=B0README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/README.md | 95 +++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/src/autonomous_driving_car/README.md b/src/autonomous_driving_car/README.md index cffa4a791f..f98897cbad 100644 --- a/src/autonomous_driving_car/README.md +++ b/src/autonomous_driving_car/README.md @@ -1 +1,94 @@ -CARLA 模拟器和自动驾驶基础算法学习 \ No newline at end of file +CARLA 模拟器和自动驾驶基础算法学习 + + + +\# 无人驾驶汽车项目(基于CARLA模拟器) + +\## 项目简介 + +本项目是基于CARLA开源仿真平台、Python和PyCharm开发的无人驾驶仿真系统,融合计算机视觉、路径规划与控制技术,实现虚拟场景中车辆的自主导航、避障与路径跟踪功能,适用于自动驾驶入门实践。 + + + +\## 核心功能 + +\- 路径规划:A\*算法、RRT/RRT\*算法实现起点到终点路径生成 + +\- 障碍物检测:YOLOv8+OpenCV实时识别目标,激光雷达点云聚类定位 + +\- 车辆控制:PID控制器实现转向、速度精准控制 + +\- CARLA交互:加载场景、获取传感器数据(摄像头/激光雷达等)、发送控制指令 + +\- 实时可视化:PyGame显示场景、车辆状态与检测结果 + + + +\## 技术栈 + +| 类别 | 具体技术/工具 | + +|--------------|---------------------------------------| + +| 开发环境 | PyCharm Community Edition 2024+、Windows/Linux | + +| 核心语言 | Python 3.8+ | + +| 仿真平台 | CARLA 0.9.15/0.9.16、CARLA Python API | + +| 计算机视觉 | OpenCV、NumPy、YOLOv8(Ultralytics) | + +| 路径规划 | A\*、RRT/RRT\*算法 | + +| 控制理论 | PID控制器 | + +| 可视化与数据 | PyGame、Matplotlib、Pandas | + + + +\## 快速开始 + +1\. 克隆项目到PyCharm,创建Python 3.8+虚拟环境 + +2\. 安装依赖:`pip install -r requirements.txt`(核心依赖:carla、opencv-python、ultralytics、numpy、pygame) + +3\. 启动CARLA模拟器(运行`CarlaUE4.exe`/`CarlaUE4.sh`) + +4\. 运行主程序:`python main.py`,自动连接CARLA并启动自主导航 + + + +\## 项目结构 + +``` + +self-driving-car-carla/ + +├── carla\_client/ # CARLA连接与场景管理 + +├── perception/ # 目标检测与传感器数据处理 + +├── planning/ # 路径规划算法实现 + +├── control/ # PID控制逻辑 + +├── visualization/ # 实时可视化 + +├── utils/ # 工具函数 + +├── main.py # 项目入口 + +└── requirements.txt # 依赖列表 + +``` + + + +\## 常见问题 + +\- CARLA连接失败:确保模拟器已启动,Python API版本与CARLA一致 + +\- 检测速度慢:使用YOLOv8n轻量模型,或启用GPU加速 + +\- 控制不稳定:调整PID参数或增加路径平滑处理 + From 38a6d8b0803746feda9744f5e33cd592da177e9a Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Sun, 21 Dec 2025 21:30:38 +0800 Subject: [PATCH 16/26] =?UTF-8?q?=E6=9B=B4=E6=96=B0README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/README.md | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/src/autonomous_driving_car/README.md b/src/autonomous_driving_car/README.md index f98897cbad..8125279229 100644 --- a/src/autonomous_driving_car/README.md +++ b/src/autonomous_driving_car/README.md @@ -60,27 +60,11 @@ CARLA 模拟器和自动驾驶基础算法学习 \## 项目结构 -``` - -self-driving-car-carla/ - -├── carla\_client/ # CARLA连接与场景管理 - -├── perception/ # 目标检测与传感器数据处理 - -├── planning/ # 路径规划算法实现 - -├── control/ # PID控制逻辑 - -├── visualization/ # 实时可视化 - -├── utils/ # 工具函数 - -├── main.py # 项目入口 - -└── requirements.txt # 依赖列表 - -``` +autonomous_driving_car/ +├── FPV/ # 第一视角可视化模块:负责车辆摄像头视角、检测结果的实时显示 +├── MCP/ # 主控制与模块集成:包含感知(检测)、规划(路径)、控制(PID)的核心逻辑 +├── main.py # 项目入口:启动CARLA连接、调用MCP与FPV模块 +└── README.md # 项目说明文档 From 4c71825a29dbfec835caf2fd2cb2e139fe76eadd Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Sun, 21 Dec 2025 21:38:54 +0800 Subject: [PATCH 17/26] =?UTF-8?q?=E6=9B=B4=E6=96=B0README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autonomous_driving_car/README.md b/src/autonomous_driving_car/README.md index 8125279229..0bb5ae9ee2 100644 --- a/src/autonomous_driving_car/README.md +++ b/src/autonomous_driving_car/README.md @@ -54,7 +54,7 @@ CARLA 模拟器和自动驾驶基础算法学习 3\. 启动CARLA模拟器(运行`CarlaUE4.exe`/`CarlaUE4.sh`) -4\. 运行主程序:`python main.py`,自动连接CARLA并启动自主导航 +4\. 运行主程序:`python main.py`,自动连接CARLA并自主导航 From 17f2b0cc3d8f1076732245923719eb83b51583d9 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Mon, 22 Dec 2025 09:34:34 +0800 Subject: [PATCH 18/26] =?UTF-8?q?=E8=BF=9B=E8=A1=8Cros=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ros/carla_multi_vehicle_node.py | 938 ++++++++++++++++++ .../ros/carla_vehicle_control.py | 908 +++++++++++++++++ 2 files changed, 1846 insertions(+) create mode 100644 src/autonomous_driving_car/ros/carla_multi_vehicle_node.py create mode 100644 src/autonomous_driving_car/ros/carla_vehicle_control.py diff --git a/src/autonomous_driving_car/ros/carla_multi_vehicle_node.py b/src/autonomous_driving_car/ros/carla_multi_vehicle_node.py new file mode 100644 index 0000000000..8fa2fd0842 --- /dev/null +++ b/src/autonomous_driving_car/ros/carla_multi_vehicle_node.py @@ -0,0 +1,938 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +CARLA多车辆协同控制的ROS节点版 +""" +# ===================== ROS核心导入(新增)====================== +import rospy +from std_msgs.msg import String # 用于发布车辆状态的标准消息 +# ============================================================== + +import sys +import os +import carla +import numpy as np +import math +import pygame +import traceback +import time +import threading +from concurrent.futures import ThreadPoolExecutor, as_completed +import logging +import random + +# ===================== 全局配置 ======================= +# CARLA连接(必须修改为你的主机IP) +CARLA_HOST = "192.168.137.112" +CARLA_PORT = 2000 +CARLA_TIMEOUT = 30.0 # 超时时间延长到30秒,提高容错 + +# ROS配置(新增) +ROS_NODE_NAME = "carla_multi_vehicle_node" # ROS节点名称 +VEHICLE_STATUS_TOPIC = "/carla/vehicle_status" # 发布车辆状态的话题名 + +# 多车辆配置 +VEHICLE_COUNT = 3 +VEHICLE_MODELS = [ + "vehicle.tesla.model3", + "vehicle.bmw.grandtourer", + "vehicle.audi.a2" +] +SPAWN_INTERVAL = 1.0 +SPAWN_RETRY_MAX = 8 +SPAWN_RETRY_DELAY = 0.5 +SPAWN_DISTANCE_LIMIT = 15.0 # 放宽距离限制到15米 + +# 车辆控制参数 +VEHICLE_WHEELBASE = 2.9 +VEHICLE_REAR_AXLE_OFFSET = 1.45 +LOOKAHEAD_DIST_STRAIGHT = 7.0 +LOOKAHEAD_DIST_CURVE = 4.0 +STEER_GAIN_STRAIGHT = 0.7 +STEER_GAIN_CURVE = 1.0 +STEER_DEADZONE = 0.05 +STEER_LOWPASS_ALPHA = 0.6 +MAX_STEER = 1.0 +DIR_CHANGE_GENTLE = 0.03 +DIR_CHANGE_SHARP = 0.08 +BASE_SPEEDS = [25.0, 22.0, 20.0] +PID_KP = 0.2 +PID_KI = 0.01 +PID_KD = 0.02 + +# 交通规则配置 +TRAFFIC_LIGHT_STOP_DISTANCE = 4.0 +TRAFFIC_LIGHT_DETECTION_RANGE = 50.0 +TRAFFIC_LIGHT_ANGLE_THRESHOLD = 60.0 +STOP_SPEED_THRESHOLD = 0.2 +GREEN_LIGHT_ACCEL_FACTOR = 0.25 +STOP_LINE_SIM_DISTANCE = 5.0 +RED_LIGHT_DURATION = 3.0 +GREEN_LIGHT_DURATION = 5.0 +YELLOW_LIGHT_DURATION = 2.0 + +# 相机配置 +WINDOW_WIDTH = 1280 +WINDOW_HEIGHT = 720 +CAMERA_FOV = 120 +CAMERA_POS = carla.Transform(carla.Location(x=-6.0, z=2.5), carla.Rotation(pitch=-5)) + +# 全局变量 +current_view_vehicle_id = 1 +vehicle_agents = [] + +# 日志配置 +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - 车辆%(vehicle_id)s - %(levelname)s - %(message)s", + handlers=[logging.FileHandler("multi_vehicle_simulation.log"), logging.StreamHandler()] +) + +# ===================== 核心工具函数 ====================== +def is_actor_alive(actor): + try: + return actor.is_alive() + except TypeError: + return actor.is_alive + +def get_traffic_light_stop_line(traffic_light): + try: + return traffic_light.get_stop_line_location() + except AttributeError: + tl_transform = traffic_light.get_transform() + forward_vec = tl_transform.get_forward_vector() + stop_line_loc = tl_transform.location - forward_vec * STOP_LINE_SIM_DISTANCE + stop_line_loc.z = tl_transform.location.z + return stop_line_loc + +def calculate_dir_change(current_wp): + waypoints = [current_wp] + for i in range(5): + next_wps = waypoints[-1].next(1.0) + if next_wps: + waypoints.append(next_wps[0]) + else: + break + + if len(waypoints) < 4: + return 0.0, 0 + + dirs = [] + for i in range(1, len(waypoints)): + wp_prev = waypoints[i-1] + wp_curr = waypoints[i] + dir_rad = math.atan2( + wp_curr.transform.location.y - wp_prev.transform.location.y, + wp_curr.transform.location.x - wp_prev.transform.location.x + ) + dirs.append(dir_rad) + + dir_change = 0.0 + for i in range(1, len(dirs)): + dir_change += abs(dirs[i] - dirs[i-1]) * 2 + + if dir_change < DIR_CHANGE_GENTLE: + curve_level = 0 + elif dir_change < DIR_CHANGE_SHARP: + curve_level = 1 + else: + curve_level = 2 + + return dir_change, curve_level + +def get_forward_waypoint(vehicle, map): + vehicle_transform = vehicle.get_transform() + current_wp = map.get_waypoint( + vehicle_transform.location, + project_to_road=True, + lane_type=carla.LaneType.Driving + ) + + # 所有车辆使用同一车道的路点 + global vehicle_agents + if len(vehicle_agents) > 0: + try: + lead_vehicle_wp = map.get_waypoint(vehicle_agents[0].vehicle.get_transform().location, project_to_road=True) + current_wp = map.get_waypoint(vehicle_transform.location, project_to_road=True, lane_id=lead_vehicle_wp.lane_id) + except: + pass + + road_direction = current_wp.transform.get_forward_vector() + vehicle_direction = vehicle_transform.get_forward_vector() + dot_product = road_direction.x * vehicle_direction.x + road_direction.y * vehicle_direction.y + + if dot_product < 0.0: + forward_wps = current_wp.next(10.0) + if forward_wps: + current_wp = forward_wps[0] + else: + current_wp = map.get_waypoint( + vehicle_transform.location + vehicle_direction * 5.0, + project_to_road=True + ) + + return current_wp + +def get_valid_spawn_points(map, count, base_location=None, radius=100.0): + """ + 获取有效的出生点(增加容错性,避免索引越界) + """ + # 1. 获取地图所有出生点 + all_spawn_points = map.get_spawn_points() + if not all_spawn_points: + raise RuntimeError("地图中无任何出生点") + + # 2. 初始化候选点列表 + candidate_points = [] + + # 3. 如果有基准位置,先筛选附近的点;否则直接使用所有点 + if base_location: + filtered_points = [] + for sp in all_spawn_points: + dist = sp.location.distance(base_location) + if dist <= radius: + filtered_points.append((dist, sp)) + # 按距离排序 + filtered_points.sort(key=lambda x: x[0]) + candidate_points = [sp for _, sp in filtered_points] + + # 4. 如果候选点为空,直接使用所有出生点(容错) + if not candidate_points: + candidate_points = all_spawn_points + print(f"警告:基准位置{base_location}附近无出生点,使用全局出生点") + + # 5. 筛选集中的出生点(放宽条件) + valid_points = [] + # 确保基准点存在(核心修复:避免candidate_points[0]索引越界) + if not candidate_points: + candidate_points = all_spawn_points + + base_sp = candidate_points[0] + valid_points.append(base_sp) + + # 6. 筛选其他点,放宽距离限制 + for sp in candidate_points[1:]: + try: + # 检查与已选点的距离(放宽到15米) + if all(sp.location.distance(vp.location) <= SPAWN_DISTANCE_LIMIT for vp in valid_points): + wp = map.get_waypoint(sp.location, project_to_road=True) + if wp.lane_type == carla.LaneType.Driving and 0.0 <= sp.location.z <= 2.0: + valid_points.append(sp) + if len(valid_points) >= count: + break + except: + continue + + # 7. 如果数量不够,进一步放宽条件(距离限制到20米) + if len(valid_points) < count: + for sp in candidate_points: + if sp not in valid_points: + try: + if all(sp.location.distance(vp.location) <= SPAWN_DISTANCE_LIMIT * 1.5 for vp in valid_points): + wp = map.get_waypoint(sp.location, project_to_road=True) + if wp.lane_type == carla.LaneType.Driving and 0.0 <= sp.location.z <= 2.0: + valid_points.append(sp) + if len(valid_points) >= count: + break + except: + continue + + # 8. 如果还是不够,直接取前N个点(最终容错) + if len(valid_points) < count: + print(f"警告:无法找到{count}个集中的出生点,直接取前{count}个可用点") + for sp in candidate_points: + if sp not in valid_points: + wp = map.get_waypoint(sp.location, project_to_road=True) + if wp.lane_type == carla.LaneType.Driving and 0.0 <= sp.location.z <= 2.0: + valid_points.append(sp) + if len(valid_points) >= count: + break + + # 9. 最终检查:确保数量足够 + if len(valid_points) < count: + # 直接取所有可用点,不足的话重复使用(极端情况) + while len(valid_points) < count: + valid_points.append(valid_points[0]) + print(f"警告:出生点数量不足,重复使用已有点") + + # 10. 统一出生点朝向 + try: + forward_vec = valid_points[0].transform.get_forward_vector() + for sp in valid_points: + sp.rotation.yaw = math.degrees(math.atan2(forward_vec.y, forward_vec.x)) + except: + pass + + return valid_points[:count] + +def check_spawn_collision(world, spawn_point, radius=3.0): + # 检查周围车辆和行人 + vehicles = world.get_actors().filter("vehicle.*") + for vehicle in vehicles: + if is_actor_alive(vehicle): + dist = vehicle.get_transform().location.distance(spawn_point.location) + if dist < radius: + return False + + walkers = world.get_actors().filter("walker.*") + for walker in walkers: + if is_actor_alive(walker): + dist = walker.get_transform().location.distance(spawn_point.location) + if dist < radius: + return False + + return True + +# ===================== 相机管理类(多车辆)===================== +class VehicleCamera: + def __init__(self, world, vehicle, vehicle_id): + self.world = world + self.vehicle = vehicle + self.vehicle_id = vehicle_id + self.camera = None + self.image_surface = None + + # 创建相机传感器 + self._create_camera() + + def _create_camera(self): + # 加载相机蓝图 + camera_bp = self.world.get_blueprint_library().find("sensor.camera.rgb") + camera_bp.set_attribute("image_size_x", str(640)) + camera_bp.set_attribute("image_size_y", str(360)) + camera_bp.set_attribute("fov", str(CAMERA_FOV)) + + # 生成相机(附加到车辆) + self.camera = self.world.spawn_actor(camera_bp, CAMERA_POS, attach_to=self.vehicle) + + # 注册图像回调函数 + self.camera.listen(self._on_image) + + def _on_image(self, image): + # 将CARLA图像转换为Pygame Surface + array = np.frombuffer(image.raw_data, dtype=np.uint8) + array = array.reshape((image.height, image.width, 4)) + array = array[:, :, :3] + array = array[:, :, ::-1] + array = np.swapaxes(array, 0, 1) + + # 存储为Pygame Surface + self.image_surface = pygame.surfarray.make_surface(array) + + def destroy(self): + if self.camera: + self.camera.stop() + self.camera.destroy() + +# ===================== 车辆控制类 ====================== +class VehicleAgent: + def __init__(self, world, map, vehicle_id, spawn_point, vehicle_model, base_speed): + self.vehicle_id = vehicle_id + self.world = world + self.map = map + self.base_speed = base_speed + self.logger = logging.getLogger(__name__) + self.logger = logging.LoggerAdapter(self.logger, {"vehicle_id": vehicle_id}) + + # 生成车辆 + self.vehicle_bp = self.world.get_blueprint_library().find(vehicle_model) + if self.vehicle_bp.has_attribute("color"): + color = random.choice(self.vehicle_bp.get_attribute("color").recommended_values) + self.vehicle_bp.set_attribute("color", color) + + self.vehicle = self._spawn_vehicle_with_retry(spawn_point) + if not self.vehicle: + raise RuntimeError(f"车辆{vehicle_id}生成失败") + + # 创建相机 + self.camera = VehicleCamera(world, self.vehicle, vehicle_id) + + # 初始化控制器 + self.pp_controller = AdaptivePurePursuit(VEHICLE_WHEELBASE) + self.speed_controller = SpeedController(PID_KP, PID_KI, PID_KD, base_speed) + self.traffic_light_manager = TrafficLightManager(vehicle_id) + + self.is_alive = True + self.logger.info(f"生成成功,车型:{vehicle_model},出生点:({spawn_point.location.x:.1f},{spawn_point.location.y:.1f})") + + def _spawn_vehicle_with_retry(self, initial_spawn_point): + all_spawn_points = self.map.get_spawn_points() + if not all_spawn_points: + self.logger.error("地图中无有效出生点") + return None + + candidate_points = [initial_spawn_point] + candidate_points += random.sample(all_spawn_points, min(10, len(all_spawn_points))) + + for retry in range(SPAWN_RETRY_MAX): + spawn_point = candidate_points[retry % len(candidate_points)] + spawn_point.location.z += 0.3 + spawn_point.rotation.yaw += random.randint(-5, 5) + + if not check_spawn_collision(self.world, spawn_point): + self.logger.warning(f"第{retry+1}次重试:出生点有碰撞风险,跳过") + time.sleep(SPAWN_RETRY_DELAY) + continue + + try: + return self.world.spawn_actor(self.vehicle_bp, spawn_point) + except Exception as e: + self.logger.warning(f"第{retry+1}次重试失败:{e}") + time.sleep(SPAWN_RETRY_DELAY) + + self.logger.error(f"超过{SPAWN_RETRY_MAX}次重试,生成失败") + return None + + def update(self): + if not self.is_alive or not is_actor_alive(self.vehicle): + self.is_alive = False + self.logger.error("车辆已销毁,停止更新") + return False + + try: + # 获取车辆状态 + vehicle_transform = self.vehicle.get_transform() + vehicle_vel = self.vehicle.get_velocity() + current_speed = math.hypot(vehicle_vel.x, vehicle_vel.y) * 3.6 + + # 路径跟踪 + current_wp = get_forward_waypoint(self.vehicle, self.map) + dir_change, curve_level = calculate_dir_change(current_wp) + lookahead_dist = self.pp_controller.get_adaptive_lookahead(dir_change) + target_wps = current_wp.next(lookahead_dist) + target_point = target_wps[0].transform.location if target_wps else vehicle_transform.location + + # 速度控制 + curve_speed_factors = [1.0, 0.7, 0.4] + speed_factor = curve_speed_factors[min(curve_level, 2)] + base_target_speed = self.base_speed * speed_factor + base_target_speed = max(8.0, base_target_speed) + + # 跟车控制 + global vehicle_agents + if self.vehicle_id > 1 and len(vehicle_agents) >= self.vehicle_id: + try: + lead_vehicle = vehicle_agents[self.vehicle_id - 2].vehicle + lead_vehicle_transform = lead_vehicle.get_transform() + dist_to_lead = vehicle_transform.location.distance(lead_vehicle_transform.location) + if dist_to_lead < 15.0: + base_target_speed = max(5.0, base_target_speed * 0.5) + except: + pass + + # 交通灯处理 + target_speed, traffic_light_status = self.traffic_light_manager.handle_traffic_light_logic( + self.vehicle, current_speed, base_target_speed + ) + + # 计算控制指令 + steer = self.pp_controller.calculate_steer(vehicle_transform, target_point, dir_change) + throttle = self.speed_controller.calculate(target_speed, current_speed) + brake = 1.0 - throttle if current_speed > target_speed + 1 else 0.0 + + if "Red (Stopped)" in traffic_light_status or target_speed == 0.0: + throttle = 0.0 + brake = 1.0 + + # 应用控制 + control = carla.VehicleControl() + control.steer = steer + control.throttle = throttle + control.brake = brake + self.vehicle.apply_control(control) + + # 日志输出 + self.logger.info( + f"速度:{current_speed:5.1f}km/h | 目标:{target_speed:5.1f} | " + f"弯道:{['直道', '缓弯', '急弯'][curve_level]:<3} | 灯状态:{traffic_light_status}" + ) + + return True + + except Exception as e: + self.logger.error(f"更新失败:{e}", exc_info=True) + return False + + def destroy(self): + # 销毁相机 + self.camera.destroy() + # 销毁车辆 + if self.vehicle and is_actor_alive(self.vehicle): + self.vehicle.destroy() + self.logger.info("车辆资源已清理") + +# ===================== 控制器类 ====================== +class AdaptivePurePursuit: + def __init__(self, wheelbase): + self.wheelbase = wheelbase + self.last_steer = 0.0 + self.last_lookahead = LOOKAHEAD_DIST_STRAIGHT + + def calculate_steer(self, vehicle_transform, target_point, dir_change): + forward_vec = vehicle_transform.get_forward_vector() + rear_axle_loc = carla.Location( + x=vehicle_transform.location.x - forward_vec.x * VEHICLE_REAR_AXLE_OFFSET, + y=vehicle_transform.location.y - forward_vec.y * VEHICLE_REAR_AXLE_OFFSET, + z=vehicle_transform.location.z + ) + + dx = target_point.x - rear_axle_loc.x + dy = target_point.y - rear_axle_loc.y + yaw = math.radians(vehicle_transform.rotation.yaw) + + dx_vehicle = dx * math.cos(yaw) + dy * math.sin(yaw) + dy_vehicle = -dx * math.sin(yaw) + dy * math.cos(yaw) + + steer_gain = np.interp( + dir_change, + [0, DIR_CHANGE_SHARP], + [STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE] + ) + steer_gain = np.clip(steer_gain, STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE) + + if dx_vehicle < 0.1: + steer = self.last_steer + else: + steer_rad = math.atan2(2 * self.wheelbase * dy_vehicle, dx_vehicle ** 2 + dy_vehicle ** 2) + steer = steer_rad / math.pi + steer *= steer_gain + + if abs(steer) < STEER_DEADZONE: + steer = 0.0 + steer = STEER_LOWPASS_ALPHA * steer + (1 - STEER_LOWPASS_ALPHA) * self.last_steer + steer = np.clip(steer, -MAX_STEER, MAX_STEER) + + self.last_steer = steer + return steer + + def get_adaptive_lookahead(self, dir_change): + lookahead_dist = np.interp( + dir_change, + [0, DIR_CHANGE_SHARP], + [LOOKAHEAD_DIST_STRAIGHT, LOOKAHEAD_DIST_CURVE] + ) + lookahead_dist = np.clip(lookahead_dist, LOOKAHEAD_DIST_CURVE, LOOKAHEAD_DIST_STRAIGHT) + self.last_lookahead = lookahead_dist + return lookahead_dist + +class SpeedController: + def __init__(self, kp, ki, kd, base_speed): + self.kp = kp + self.ki = ki + self.kd = kd + self.base_speed = base_speed + self.last_error = 0.0 + self.integral = 0.0 + + def calculate(self, target_speed, current_speed): + error = target_speed - current_speed + p = self.kp * error + self.integral += self.ki * error + self.integral = np.clip(self.integral, -1.0, 1.0) + i = self.integral + d = self.kd * (error - self.last_error) + self.last_error = error + return np.clip(p + i + d, 0.0, 1.0) + +class TrafficLightManager: + def __init__(self, vehicle_id): + self.vehicle_id = vehicle_id + self.tracked_light = None + self.is_stopped_at_red = False + self.red_light_stop_time = 0 + self.logger = logging.getLogger(__name__) + self.logger = logging.LoggerAdapter(self.logger, {"vehicle_id": vehicle_id}) + + def _calculate_angle_between_vehicle_and_light(self, vehicle_transform, light_transform): + vehicle_forward = vehicle_transform.get_forward_vector() + vehicle_forward = np.array([vehicle_forward.x, vehicle_forward.y]) + vehicle_forward = vehicle_forward / np.linalg.norm(vehicle_forward) + + light_dir = light_transform.location - vehicle_transform.location + light_dir = np.array([light_dir.x, light_dir.y]) + if np.linalg.norm(light_dir) < 0.1: + return 0.0 + light_dir = light_dir / np.linalg.norm(light_dir) + + angle = math.acos(np.clip(np.dot(vehicle_forward, light_dir), -1.0, 1.0)) + angle = math.degrees(angle) + return angle + + def get_lane_traffic_light(self, vehicle, world): + vehicle_transform = vehicle.get_transform() + vehicle_loc = vehicle_transform.location + + if self.tracked_light and is_actor_alive(self.tracked_light): + dist = self.tracked_light.get_transform().location.distance(vehicle_loc) + angle = self._calculate_angle_between_vehicle_and_light(vehicle_transform, self.tracked_light.get_transform()) + if dist < TRAFFIC_LIGHT_DETECTION_RANGE and angle < TRAFFIC_LIGHT_ANGLE_THRESHOLD: + return self.tracked_light + + traffic_lights = world.get_actors().filter("traffic.traffic_light") + valid_lights = [] + + for light in traffic_lights: + if not is_actor_alive(light): + continue + dist = light.get_transform().location.distance(vehicle_loc) + if dist > TRAFFIC_LIGHT_DETECTION_RANGE: + continue + angle = self._calculate_angle_between_vehicle_and_light(vehicle_transform, light.get_transform()) + if angle < TRAFFIC_LIGHT_ANGLE_THRESHOLD: + valid_lights.append((dist, light)) + + if valid_lights: + valid_lights.sort(key=lambda x: x[0]) + self.tracked_light = valid_lights[0][1] + return self.tracked_light + + self.tracked_light = None + return None + + def handle_traffic_light_logic(self, vehicle, current_speed, base_target_speed): + world = vehicle.get_world() + traffic_light = self.get_lane_traffic_light(vehicle, world) + + if not traffic_light: + self.is_stopped_at_red = False + self.red_light_stop_time = 0 + return base_target_speed, "No Light" + + stop_line_loc = get_traffic_light_stop_line(traffic_light) + dist_to_stop_line = vehicle.get_transform().location.distance(stop_line_loc) + + if traffic_light.get_state() == carla.TrafficLightState.Green: + if self.is_stopped_at_red: + recovery_speed = current_speed + (base_target_speed - current_speed) * GREEN_LIGHT_ACCEL_FACTOR + target_speed = max(STOP_SPEED_THRESHOLD, recovery_speed) + if abs(target_speed - base_target_speed) < 0.5: + self.is_stopped_at_red = False + self.logger.info(f"绿灯恢复行驶,目标速度:{target_speed:.1f}km/h") + return target_speed, "Green" + return base_target_speed, "Green" + + elif traffic_light.get_state() == carla.TrafficLightState.Yellow: + self.is_stopped_at_red = False + yellow_speed = max(5.0, base_target_speed * 0.3) + self.logger.warning(f"黄灯减速,目标速度:{yellow_speed:.1f}km/h") + return yellow_speed, "Yellow" + + elif traffic_light.get_state() == carla.TrafficLightState.Red: + if dist_to_stop_line > TRAFFIC_LIGHT_STOP_DISTANCE: + self.is_stopped_at_red = False + red_speed = max(2.0, current_speed * 0.1) + self.logger.warning(f"红灯减速,距离停止线:{dist_to_stop_line:.1f}m,目标速度:{red_speed:.1f}km/h") + return red_speed, "Red" + else: + if current_speed <= STOP_SPEED_THRESHOLD: + self.is_stopped_at_red = True + self.red_light_stop_time += 1 + wait_seconds = self.red_light_stop_time // 30 + self.logger.info(f"红灯停车等待:{wait_seconds}s") + return 0.0, "Red (Stopped)" + else: + self.logger.warning("红灯紧急制动") + return 0.0, "Red (Braking)" + + return base_target_speed, "Unknown" + +# ===================== 交通灯控制线程 ====================== +def cycle_traffic_light_states(world, stop_event): + logger = logging.getLogger(__name__) + logger = logging.LoggerAdapter(logger, {"vehicle_id": "系统"}) + while not stop_event.is_set(): + traffic_lights = world.get_actors().filter("traffic.traffic_light") + if not traffic_lights: + time.sleep(1) + continue + + # 红灯 + for tl in traffic_lights: + if is_actor_alive(tl): + try: + tl.set_state(carla.TrafficLightState.Red) + except: + pass + logger.info(f"所有交通灯切换为红灯,持续{RED_LIGHT_DURATION}秒") + stop_event.wait(RED_LIGHT_DURATION) + if stop_event.is_set(): + break + + # 绿灯 + for tl in traffic_lights: + if is_actor_alive(tl): + try: + tl.set_state(carla.TrafficLightState.Green) + except: + pass + logger.info(f"所有交通灯切换为绿灯,持续{GREEN_LIGHT_DURATION}秒") + stop_event.wait(GREEN_LIGHT_DURATION) + if stop_event.is_set(): + break + + # 黄灯 + for tl in traffic_lights: + if is_actor_alive(tl): + try: + tl.set_state(carla.TrafficLightState.Yellow) + except: + pass + logger.info(f"所有交通灯切换为黄灯,持续{YELLOW_LIGHT_DURATION}秒") + stop_event.wait(YELLOW_LIGHT_DURATION) + if stop_event.is_set(): + break + + logger.info("交通灯线程停止") + +# ===================== 主函数 ====================== +def main(): + # ===================== ROS节点初始化(新增)====================== + rospy.init_node(ROS_NODE_NAME, anonymous=True) # 初始化ROS节点 + rospy.loginfo("===== Carla多车辆控制ROS节点启动 =====") # ROS日志(替代print) + status_pub = rospy.Publisher(VEHICLE_STATUS_TOPIC, String, queue_size=10) # 创建话题发布者 + rate = rospy.Rate(30) # ROS循环频率(30Hz,和原代码的clock.tick(30)一致) + # ============================================================== + + global current_view_vehicle_id, vehicle_agents + pygame.init() + screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) + pygame.display.set_caption(f"CARLA多车辆视角({VEHICLE_COUNT}辆车)- 按1/2/3切换视角,按S切换分屏,按V切换俯视视角") + + client = None + world = None + map = None + tl_cycle_thread = None + tl_stop_event = threading.Event() + show_split_screen = True + show_top_view = False + top_view_camera = None + top_view_surface = None # 显式定义俯视相机表面变量 + + # 俯视相机更新函数 + def _update_top_view(image): + nonlocal top_view_surface + array = np.frombuffer(image.raw_data, dtype=np.uint8) + array = array.reshape((image.height, image.width, 4)) + array = array[:, :, :3] + array = array[:, :, ::-1] + array = np.swapaxes(array, 0, 1) + top_view_surface = pygame.surfarray.make_surface(array) + + # 清理函数 + def cleanup(): + rospy.loginfo("\n开始清理资源...") # ROS日志 + tl_stop_event.set() + if tl_cycle_thread and tl_cycle_thread.is_alive(): + tl_cycle_thread.join(timeout=2) + + if top_view_camera: + top_view_camera.stop() + top_view_camera.destroy() + + for agent in vehicle_agents: + agent.destroy() + + if world: + for actor in world.get_actors(): + if actor.type_id.startswith(("vehicle.", "walker.", "sensor.")): + if is_actor_alive(actor): + actor.destroy() + + pygame.quit() + rospy.loginfo("资源清理完成") # ROS日志 + + # 注册退出回调 + import atexit + import signal + atexit.register(cleanup) + signal.signal(signal.SIGINT, lambda sig, frame: sys.exit(0)) + + try: + # 连接CARLA + client = carla.Client(CARLA_HOST, CARLA_PORT) + client.set_timeout(CARLA_TIMEOUT) + try: + world = client.load_world("Town04") + rospy.loginfo("成功加载Town04地图") + except Exception as e: + world = client.get_world() + rospy.logwarn(f"警告:Town04地图加载失败({e}),使用当前地图") + map = world.get_map() + + # 清理残留演员 + rospy.loginfo("清理残留演员...") + for actor in world.get_actors(): + if actor.type_id.startswith(("vehicle.", "walker.", "sensor.")): + if is_actor_alive(actor): + actor.destroy() + time.sleep(3.0) + rospy.loginfo("清理完成") + + # 自动获取地图的第一个出生点作为基准(避免手动坐标无效) + base_location = None + all_spawn_points = map.get_spawn_points() + if all_spawn_points: + base_location = all_spawn_points[0].location + rospy.loginfo(f"使用地图第一个出生点作为基准:({base_location.x:.1f}, {base_location.y:.1f})") + else: + base_location = carla.Location(x=220.0, y=150.0, z=0.5) + + # 获取有效的出生点 + rospy.loginfo(f"获取{VEHICLE_COUNT}个有效出生点...") + valid_spawn_points = get_valid_spawn_points(map, VEHICLE_COUNT, base_location) + for i, sp in enumerate(valid_spawn_points): + rospy.loginfo(f" 出生点{i+1}:({sp.location.x:.1f},{sp.location.y:.1f})") + + # 生成车辆 + rospy.loginfo(f"\n分步生成车辆(间隔{SPAWN_INTERVAL}秒)...") + for i in range(VEHICLE_COUNT): + vehicle_model = VEHICLE_MODELS[i % len(VEHICLE_MODELS)] + base_speed = BASE_SPEEDS[i % len(BASE_SPEEDS)] + spawn_point = valid_spawn_points[i] + + try: + rospy.loginfo(f"\n生成车辆{i+1}(车型:{vehicle_model})...") + agent = VehicleAgent(world, map, i+1, spawn_point, vehicle_model, base_speed) + vehicle_agents.append(agent) + rospy.loginfo(f"车辆{i+1}生成成功!") + except Exception as e: + rospy.logerr(f"车辆{i+1}生成失败:{e}") + + time.sleep(SPAWN_INTERVAL) + + if len(vehicle_agents) == 0: + raise RuntimeError("无车辆生成成功,仿真终止") + + rospy.loginfo(f"\n共生成{len(vehicle_agents)}辆车辆!") + + # 创建全局俯视相机 + try: + top_view_bp = world.get_blueprint_library().find("sensor.camera.rgb") + top_view_bp.set_attribute("image_size_x", str(WINDOW_WIDTH)) + top_view_bp.set_attribute("image_size_y", str(WINDOW_HEIGHT)) + top_view_bp.set_attribute("fov", str(90)) + top_view_transform = carla.Transform( + vehicle_agents[0].vehicle.get_transform().location + carla.Location(z=50), + carla.Rotation(pitch=-90) + ) + top_view_camera = world.spawn_actor(top_view_bp, top_view_transform) + top_view_camera.listen(_update_top_view) + except: + rospy.logwarn("警告:无法创建俯视相机") + + # 启动交通灯线程 + tl_cycle_thread = threading.Thread(target=cycle_traffic_light_states, args=(world, tl_stop_event), daemon=True) + tl_cycle_thread.start() + rospy.loginfo("交通灯线程启动") + + # 主循环 + clock = pygame.time.Clock() + running = True + + # 核心修改:加入ROS关闭信号检测 + while not rospy.is_shutdown() and running: + # 事件处理 + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_1 and len(vehicle_agents) >= 1: + current_view_vehicle_id = 1 + show_split_screen = False + show_top_view = False + elif event.key == pygame.K_2 and len(vehicle_agents) >= 2: + current_view_vehicle_id = 2 + show_split_screen = False + show_top_view = False + elif event.key == pygame.K_3 and len(vehicle_agents) >= 3: + current_view_vehicle_id = 3 + show_split_screen = False + show_top_view = False + elif event.key == pygame.K_s: + show_split_screen = True + show_top_view = False + elif event.key == pygame.K_v: + show_top_view = True + show_split_screen = False + elif event.key == pygame.K_ESCAPE: + running = False + + # 清空屏幕 + screen.fill((0, 0, 0)) + + if show_top_view: + if top_view_surface: + screen.blit(top_view_surface, (0, 0)) + elif show_split_screen: + if len(vehicle_agents) == 1: + agent = vehicle_agents[0] + if agent.camera.image_surface: + surface = pygame.transform.scale(agent.camera.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT)) + screen.blit(surface, (0, 0)) + elif len(vehicle_agents) == 2: + agent1 = vehicle_agents[0] + agent2 = vehicle_agents[1] + + if agent1.camera.image_surface: + surface1 = pygame.transform.scale(agent1.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT)) + screen.blit(surface1, (0, 0)) + + if agent2.camera.image_surface: + surface2 = pygame.transform.scale(agent2.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT)) + screen.blit(surface2, (WINDOW_WIDTH//2, 0)) + elif len(vehicle_agents) >= 3: + agent1 = vehicle_agents[0] + agent2 = vehicle_agents[1] + agent3 = vehicle_agents[2] + + if agent1.camera.image_surface: + surface1 = pygame.transform.scale(agent1.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) + screen.blit(surface1, (0, 0)) + + if agent2.camera.image_surface: + surface2 = pygame.transform.scale(agent2.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) + screen.blit(surface2, (WINDOW_WIDTH//2, 0)) + + if agent3.camera.image_surface: + surface3 = pygame.transform.scale(agent3.camera.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT//2)) + screen.blit(surface3, (0, WINDOW_HEIGHT//2)) + else: + target_agent = None + for agent in vehicle_agents: + if agent.vehicle_id == current_view_vehicle_id: + target_agent = agent + break + + if target_agent and target_agent.camera.image_surface: + surface = pygame.transform.scale(target_agent.camera.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT)) + screen.blit(surface, (0, 0)) + + # 更新车辆状态 + with ThreadPoolExecutor(max_workers=VEHICLE_COUNT) as executor: + futures = [executor.submit(agent.update) for agent in vehicle_agents] + for future in as_completed(futures): + try: + future.result() + except Exception as e: + rospy.logerr(f"车辆更新异常:{e}") + + # 刷新屏幕 + pygame.display.flip() + clock.tick(30) + + # ===================== ROS话题发布(核心新增)====================== + status_msg = String() + status_msg.data = f"当前车辆数:{len(vehicle_agents)},当前视角车辆:{current_view_vehicle_id}" + status_pub.publish(status_msg) + rate.sleep() # ROS频率控制 + # ============================================================== + + except Exception as e: + rospy.logerr(f"仿真异常:{e}") # ROS错误日志 + traceback.print_exc() + finally: + cleanup() + rospy.loginfo("===== Carla多车辆控制ROS节点停止 =====") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/autonomous_driving_car/ros/carla_vehicle_control.py b/src/autonomous_driving_car/ros/carla_vehicle_control.py new file mode 100644 index 0000000000..9a002e7bea --- /dev/null +++ b/src/autonomous_driving_car/ros/carla_vehicle_control.py @@ -0,0 +1,908 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +CARLA 多车辆协同控制版:修复出生点索引越界问题 +""" + +import sys +import os +import carla +import numpy as np +import math +import pygame +import traceback +import time +import threading +from concurrent.futures import ThreadPoolExecutor, as_completed +import logging +import random + +# ===================== 全局配置 ======================= +# CARLA连接 +CARLA_HOST = "192.168.137.112" # 替换为你的主机IPv4地址 +CARLA_PORT = 2000 +CARLA_TIMEOUT = 30.0 # 超时时间从20秒增加到30秒,提高容错性 + +# 多车辆配置 +VEHICLE_COUNT = 3 +VEHICLE_MODELS = [ + "vehicle.tesla.model3", + "vehicle.bmw.grandtourer", + "vehicle.audi.a2" +] +SPAWN_INTERVAL = 1.0 +SPAWN_RETRY_MAX = 8 +SPAWN_RETRY_DELAY = 0.5 +SPAWN_DISTANCE_LIMIT = 15.0 # 放宽距离限制到15米 + +# 车辆控制参数 +VEHICLE_WHEELBASE = 2.9 +VEHICLE_REAR_AXLE_OFFSET = 1.45 +LOOKAHEAD_DIST_STRAIGHT = 7.0 +LOOKAHEAD_DIST_CURVE = 4.0 +STEER_GAIN_STRAIGHT = 0.7 +STEER_GAIN_CURVE = 1.0 +STEER_DEADZONE = 0.05 +STEER_LOWPASS_ALPHA = 0.6 +MAX_STEER = 1.0 +DIR_CHANGE_GENTLE = 0.03 +DIR_CHANGE_SHARP = 0.08 +BASE_SPEEDS = [25.0, 22.0, 20.0] +PID_KP = 0.2 +PID_KI = 0.01 +PID_KD = 0.02 + +# 交通规则配置 +TRAFFIC_LIGHT_STOP_DISTANCE = 4.0 +TRAFFIC_LIGHT_DETECTION_RANGE = 50.0 +TRAFFIC_LIGHT_ANGLE_THRESHOLD = 60.0 +STOP_SPEED_THRESHOLD = 0.2 +GREEN_LIGHT_ACCEL_FACTOR = 0.25 +STOP_LINE_SIM_DISTANCE = 5.0 +RED_LIGHT_DURATION = 3.0 +GREEN_LIGHT_DURATION = 5.0 +YELLOW_LIGHT_DURATION = 2.0 + +# 相机配置 +WINDOW_WIDTH = 1280 +WINDOW_HEIGHT = 720 +CAMERA_FOV = 120 +CAMERA_POS = carla.Transform(carla.Location(x=-6.0, z=2.5), carla.Rotation(pitch=-5)) + +# 全局变量 +current_view_vehicle_id = 1 +vehicle_agents = [] + +# 日志配置 +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - 车辆%(vehicle_id)s - %(levelname)s - %(message)s", + handlers=[logging.FileHandler("multi_vehicle_simulation.log"), logging.StreamHandler()] +) + +# ===================== 核心工具函数 ====================== +def is_actor_alive(actor): + try: + return actor.is_alive() + except TypeError: + return actor.is_alive + +def get_traffic_light_stop_line(traffic_light): + try: + return traffic_light.get_stop_line_location() + except AttributeError: + tl_transform = traffic_light.get_transform() + forward_vec = tl_transform.get_forward_vector() + stop_line_loc = tl_transform.location - forward_vec * STOP_LINE_SIM_DISTANCE + stop_line_loc.z = tl_transform.location.z + return stop_line_loc + +def calculate_dir_change(current_wp): + waypoints = [current_wp] + for i in range(5): + next_wps = waypoints[-1].next(1.0) + if next_wps: + waypoints.append(next_wps[0]) + else: + break + + if len(waypoints) < 4: + return 0.0, 0 + + dirs = [] + for i in range(1, len(waypoints)): + wp_prev = waypoints[i-1] + wp_curr = waypoints[i] + dir_rad = math.atan2( + wp_curr.transform.location.y - wp_prev.transform.location.y, + wp_curr.transform.location.x - wp_prev.transform.location.x + ) + dirs.append(dir_rad) + + dir_change = 0.0 + for i in range(1, len(dirs)): + dir_change += abs(dirs[i] - dirs[i-1]) * 2 + + if dir_change < DIR_CHANGE_GENTLE: + curve_level = 0 + elif dir_change < DIR_CHANGE_SHARP: + curve_level = 1 + else: + curve_level = 2 + + return dir_change, curve_level + +def get_forward_waypoint(vehicle, map): + vehicle_transform = vehicle.get_transform() + current_wp = map.get_waypoint( + vehicle_transform.location, + project_to_road=True, + lane_type=carla.LaneType.Driving + ) + + # 所有车辆使用同一车道的路点 + global vehicle_agents + if len(vehicle_agents) > 0: + try: + lead_vehicle_wp = map.get_waypoint(vehicle_agents[0].vehicle.get_transform().location, project_to_road=True) + current_wp = map.get_waypoint(vehicle_transform.location, project_to_road=True, lane_id=lead_vehicle_wp.lane_id) + except: + pass + + road_direction = current_wp.transform.get_forward_vector() + vehicle_direction = vehicle_transform.get_forward_vector() + dot_product = road_direction.x * vehicle_direction.x + road_direction.y * vehicle_direction.y + + if dot_product < 0.0: + forward_wps = current_wp.next(10.0) + if forward_wps: + current_wp = forward_wps[0] + else: + current_wp = map.get_waypoint( + vehicle_transform.location + vehicle_direction * 5.0, + project_to_road=True + ) + + return current_wp + +def get_valid_spawn_points(map, count, base_location=None, radius=100.0): + """ + 获取有效的出生点(增加容错性,避免索引越界) + """ + # 1. 获取地图所有出生点 + all_spawn_points = map.get_spawn_points() + if not all_spawn_points: + raise RuntimeError("地图中无任何出生点") + + # 2. 初始化候选点列表 + candidate_points = [] + + # 3. 如果有基准位置,先筛选附近的点;否则直接使用所有点 + if base_location: + filtered_points = [] + for sp in all_spawn_points: + dist = sp.location.distance(base_location) + if dist <= radius: + filtered_points.append((dist, sp)) + # 按距离排序 + filtered_points.sort(key=lambda x: x[0]) + candidate_points = [sp for _, sp in filtered_points] + + # 4. 如果候选点为空,直接使用所有出生点(容错) + if not candidate_points: + candidate_points = all_spawn_points + print(f"警告:基准位置{base_location}附近无出生点,使用全局出生点") + + # 5. 筛选集中的出生点(放宽条件) + valid_points = [] + # 确保基准点存在(核心修复:避免candidate_points[0]索引越界) + if not candidate_points: + candidate_points = all_spawn_points + + base_sp = candidate_points[0] + valid_points.append(base_sp) + + # 6. 筛选其他点,放宽距离限制 + for sp in candidate_points[1:]: + try: + # 检查与已选点的距离(放宽到15米) + if all(sp.location.distance(vp.location) <= SPAWN_DISTANCE_LIMIT for vp in valid_points): + wp = map.get_waypoint(sp.location, project_to_road=True) + if wp.lane_type == carla.LaneType.Driving and 0.0 <= sp.location.z <= 2.0: + valid_points.append(sp) + if len(valid_points) >= count: + break + except: + continue + + # 7. 如果数量不够,进一步放宽条件(距离限制到20米) + if len(valid_points) < count: + for sp in candidate_points: + if sp not in valid_points: + try: + if all(sp.location.distance(vp.location) <= SPAWN_DISTANCE_LIMIT * 1.5 for vp in valid_points): + wp = map.get_waypoint(sp.location, project_to_road=True) + if wp.lane_type == carla.LaneType.Driving and 0.0 <= sp.location.z <= 2.0: + valid_points.append(sp) + if len(valid_points) >= count: + break + except: + continue + + # 8. 如果还是不够,直接取前N个点(最终容错) + if len(valid_points) < count: + print(f"警告:无法找到{count}个集中的出生点,直接取前{count}个可用点") + for sp in candidate_points: + if sp not in valid_points: + wp = map.get_waypoint(sp.location, project_to_road=True) + if wp.lane_type == carla.LaneType.Driving and 0.0 <= sp.location.z <= 2.0: + valid_points.append(sp) + if len(valid_points) >= count: + break + + # 9. 最终检查:确保数量足够 + if len(valid_points) < count: + # 直接取所有可用点,不足的话重复使用(极端情况) + while len(valid_points) < count: + valid_points.append(valid_points[0]) + print(f"警告:出生点数量不足,重复使用已有点") + + # 10. 统一出生点朝向 + try: + forward_vec = valid_points[0].transform.get_forward_vector() + for sp in valid_points: + sp.rotation.yaw = math.degrees(math.atan2(forward_vec.y, forward_vec.x)) + except: + pass + + return valid_points[:count] + +def check_spawn_collision(world, spawn_point, radius=3.0): + # 检查周围车辆和行人 + vehicles = world.get_actors().filter("vehicle.*") + for vehicle in vehicles: + if is_actor_alive(vehicle): + dist = vehicle.get_transform().location.distance(spawn_point.location) + if dist < radius: + return False + + walkers = world.get_actors().filter("walker.*") + for walker in walkers: + if is_actor_alive(walker): + dist = walker.get_transform().location.distance(spawn_point.location) + if dist < radius: + return False + + return True + +# ===================== 相机管理类(多车辆)===================== +class VehicleCamera: + def __init__(self, world, vehicle, vehicle_id): + self.world = world + self.vehicle = vehicle + self.vehicle_id = vehicle_id + self.camera = None + self.image_surface = None + + # 创建相机传感器 + self._create_camera() + + def _create_camera(self): + # 加载相机蓝图 + camera_bp = self.world.get_blueprint_library().find("sensor.camera.rgb") + camera_bp.set_attribute("image_size_x", str(640)) + camera_bp.set_attribute("image_size_y", str(360)) + camera_bp.set_attribute("fov", str(CAMERA_FOV)) + + # 生成相机(附加到车辆) + self.camera = self.world.spawn_actor(camera_bp, CAMERA_POS, attach_to=self.vehicle) + + # 注册图像回调函数 + self.camera.listen(self._on_image) + + def _on_image(self, image): + # 将CARLA图像转换为Pygame Surface + array = np.frombuffer(image.raw_data, dtype=np.uint8) + array = array.reshape((image.height, image.width, 4)) + array = array[:, :, :3] + array = array[:, :, ::-1] + array = np.swapaxes(array, 0, 1) + + # 存储为Pygame Surface + self.image_surface = pygame.surfarray.make_surface(array) + + def destroy(self): + if self.camera: + self.camera.stop() + self.camera.destroy() + +# ===================== 车辆控制类 ====================== +class VehicleAgent: + def __init__(self, world, map, vehicle_id, spawn_point, vehicle_model, base_speed): + self.vehicle_id = vehicle_id + self.world = world + self.map = map + self.base_speed = base_speed + self.logger = logging.getLogger(__name__) + self.logger = logging.LoggerAdapter(self.logger, {"vehicle_id": vehicle_id}) + + # 生成车辆 + self.vehicle_bp = self.world.get_blueprint_library().find(vehicle_model) + if self.vehicle_bp.has_attribute("color"): + color = random.choice(self.vehicle_bp.get_attribute("color").recommended_values) + self.vehicle_bp.set_attribute("color", color) + + self.vehicle = self._spawn_vehicle_with_retry(spawn_point) + if not self.vehicle: + raise RuntimeError(f"车辆{vehicle_id}生成失败") + + # 创建相机 + self.camera = VehicleCamera(world, self.vehicle, vehicle_id) + + # 初始化控制器 + self.pp_controller = AdaptivePurePursuit(VEHICLE_WHEELBASE) + self.speed_controller = SpeedController(PID_KP, PID_KI, PID_KD, base_speed) + self.traffic_light_manager = TrafficLightManager(vehicle_id) + + self.is_alive = True + self.logger.info(f"生成成功,车型:{vehicle_model},出生点:({spawn_point.location.x:.1f},{spawn_point.location.y:.1f})") + + def _spawn_vehicle_with_retry(self, initial_spawn_point): + all_spawn_points = self.map.get_spawn_points() + if not all_spawn_points: + self.logger.error("地图中无有效出生点") + return None + + candidate_points = [initial_spawn_point] + candidate_points += random.sample(all_spawn_points, min(10, len(all_spawn_points))) + + for retry in range(SPAWN_RETRY_MAX): + spawn_point = candidate_points[retry % len(candidate_points)] + spawn_point.location.z += 0.3 + spawn_point.rotation.yaw += random.randint(-5, 5) + + if not check_spawn_collision(self.world, spawn_point): + self.logger.warning(f"第{retry+1}次重试:出生点有碰撞风险,跳过") + time.sleep(SPAWN_RETRY_DELAY) + continue + + try: + return self.world.spawn_actor(self.vehicle_bp, spawn_point) + except Exception as e: + self.logger.warning(f"第{retry+1}次重试失败:{e}") + time.sleep(SPAWN_RETRY_DELAY) + + self.logger.error(f"超过{SPAWN_RETRY_MAX}次重试,生成失败") + return None + + def update(self): + if not self.is_alive or not is_actor_alive(self.vehicle): + self.is_alive = False + self.logger.error("车辆已销毁,停止更新") + return False + + try: + # 获取车辆状态 + vehicle_transform = self.vehicle.get_transform() + vehicle_vel = self.vehicle.get_velocity() + current_speed = math.hypot(vehicle_vel.x, vehicle_vel.y) * 3.6 + + # 路径跟踪 + current_wp = get_forward_waypoint(self.vehicle, self.map) + dir_change, curve_level = calculate_dir_change(current_wp) + lookahead_dist = self.pp_controller.get_adaptive_lookahead(dir_change) + target_wps = current_wp.next(lookahead_dist) + target_point = target_wps[0].transform.location if target_wps else vehicle_transform.location + + # 速度控制 + curve_speed_factors = [1.0, 0.7, 0.4] + speed_factor = curve_speed_factors[min(curve_level, 2)] + base_target_speed = self.base_speed * speed_factor + base_target_speed = max(8.0, base_target_speed) + + # 跟车控制 + global vehicle_agents + if self.vehicle_id > 1 and len(vehicle_agents) >= self.vehicle_id: + try: + lead_vehicle = vehicle_agents[self.vehicle_id - 2].vehicle + lead_vehicle_transform = lead_vehicle.get_transform() + dist_to_lead = vehicle_transform.location.distance(lead_vehicle_transform.location) + if dist_to_lead < 15.0: + base_target_speed = max(5.0, base_target_speed * 0.5) + except: + pass + + # 交通灯处理 + target_speed, traffic_light_status = self.traffic_light_manager.handle_traffic_light_logic( + self.vehicle, current_speed, base_target_speed + ) + + # 计算控制指令 + steer = self.pp_controller.calculate_steer(vehicle_transform, target_point, dir_change) + throttle = self.speed_controller.calculate(target_speed, current_speed) + brake = 1.0 - throttle if current_speed > target_speed + 1 else 0.0 + + if "Red (Stopped)" in traffic_light_status or target_speed == 0.0: + throttle = 0.0 + brake = 1.0 + + # 应用控制 + control = carla.VehicleControl() + control.steer = steer + control.throttle = throttle + control.brake = brake + self.vehicle.apply_control(control) + + # 日志输出 + self.logger.info( + f"速度:{current_speed:5.1f}km/h | 目标:{target_speed:5.1f} | " + f"弯道:{['直道', '缓弯', '急弯'][curve_level]:<3} | 灯状态:{traffic_light_status}" + ) + + return True + + except Exception as e: + self.logger.error(f"更新失败:{e}", exc_info=True) + return False + + def destroy(self): + # 销毁相机 + self.camera.destroy() + # 销毁车辆 + if self.vehicle and is_actor_alive(self.vehicle): + self.vehicle.destroy() + self.logger.info("车辆资源已清理") + +# ===================== 控制器类 ====================== +class AdaptivePurePursuit: + def __init__(self, wheelbase): + self.wheelbase = wheelbase + self.last_steer = 0.0 + self.last_lookahead = LOOKAHEAD_DIST_STRAIGHT + + def calculate_steer(self, vehicle_transform, target_point, dir_change): + forward_vec = vehicle_transform.get_forward_vector() + rear_axle_loc = carla.Location( + x=vehicle_transform.location.x - forward_vec.x * VEHICLE_REAR_AXLE_OFFSET, + y=vehicle_transform.location.y - forward_vec.y * VEHICLE_REAR_AXLE_OFFSET, + z=vehicle_transform.location.z + ) + + dx = target_point.x - rear_axle_loc.x + dy = target_point.y - rear_axle_loc.y + yaw = math.radians(vehicle_transform.rotation.yaw) + + dx_vehicle = dx * math.cos(yaw) + dy * math.sin(yaw) + dy_vehicle = -dx * math.sin(yaw) + dy * math.cos(yaw) + + steer_gain = np.interp( + dir_change, + [0, DIR_CHANGE_SHARP], + [STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE] + ) + steer_gain = np.clip(steer_gain, STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE) + + if dx_vehicle < 0.1: + steer = self.last_steer + else: + steer_rad = math.atan2(2 * self.wheelbase * dy_vehicle, dx_vehicle ** 2 + dy_vehicle ** 2) + steer = steer_rad / math.pi + steer *= steer_gain + + if abs(steer) < STEER_DEADZONE: + steer = 0.0 + steer = STEER_LOWPASS_ALPHA * steer + (1 - STEER_LOWPASS_ALPHA) * self.last_steer + steer = np.clip(steer, -MAX_STEER, MAX_STEER) + + self.last_steer = steer + return steer + + def get_adaptive_lookahead(self, dir_change): + lookahead_dist = np.interp( + dir_change, + [0, DIR_CHANGE_SHARP], + [LOOKAHEAD_DIST_STRAIGHT, LOOKAHEAD_DIST_CURVE] + ) + lookahead_dist = np.clip(lookahead_dist, LOOKAHEAD_DIST_CURVE, LOOKAHEAD_DIST_STRAIGHT) + self.last_lookahead = lookahead_dist + return lookahead_dist + +class SpeedController: + def __init__(self, kp, ki, kd, base_speed): + self.kp = kp + self.ki = ki + self.kd = kd + self.base_speed = base_speed + self.last_error = 0.0 + self.integral = 0.0 + + def calculate(self, target_speed, current_speed): + error = target_speed - current_speed + p = self.kp * error + self.integral += self.ki * error + self.integral = np.clip(self.integral, -1.0, 1.0) + i = self.integral + d = self.kd * (error - self.last_error) + self.last_error = error + return np.clip(p + i + d, 0.0, 1.0) + +class TrafficLightManager: + def __init__(self, vehicle_id): + self.vehicle_id = vehicle_id + self.tracked_light = None + self.is_stopped_at_red = False + self.red_light_stop_time = 0 + self.logger = logging.getLogger(__name__) + self.logger = logging.LoggerAdapter(self.logger, {"vehicle_id": vehicle_id}) + + def _calculate_angle_between_vehicle_and_light(self, vehicle_transform, light_transform): + vehicle_forward = vehicle_transform.get_forward_vector() + vehicle_forward = np.array([vehicle_forward.x, vehicle_forward.y]) + vehicle_forward = vehicle_forward / np.linalg.norm(vehicle_forward) + + light_dir = light_transform.location - vehicle_transform.location + light_dir = np.array([light_dir.x, light_dir.y]) + if np.linalg.norm(light_dir) < 0.1: + return 0.0 + light_dir = light_dir / np.linalg.norm(light_dir) + + angle = math.acos(np.clip(np.dot(vehicle_forward, light_dir), -1.0, 1.0)) + angle = math.degrees(angle) + return angle + + def get_lane_traffic_light(self, vehicle, world): + vehicle_transform = vehicle.get_transform() + vehicle_loc = vehicle_transform.location + + if self.tracked_light and is_actor_alive(self.tracked_light): + dist = self.tracked_light.get_transform().location.distance(vehicle_loc) + angle = self._calculate_angle_between_vehicle_and_light(vehicle_transform, self.tracked_light.get_transform()) + if dist < TRAFFIC_LIGHT_DETECTION_RANGE and angle < TRAFFIC_LIGHT_ANGLE_THRESHOLD: + return self.tracked_light + + traffic_lights = world.get_actors().filter("traffic.traffic_light") + valid_lights = [] + + for light in traffic_lights: + if not is_actor_alive(light): + continue + dist = light.get_transform().location.distance(vehicle_loc) + if dist > TRAFFIC_LIGHT_DETECTION_RANGE: + continue + angle = self._calculate_angle_between_vehicle_and_light(vehicle_transform, light.get_transform()) + if angle < TRAFFIC_LIGHT_ANGLE_THRESHOLD: + valid_lights.append((dist, light)) + + if valid_lights: + valid_lights.sort(key=lambda x: x[0]) + self.tracked_light = valid_lights[0][1] + return self.tracked_light + + self.tracked_light = None + return None + + def handle_traffic_light_logic(self, vehicle, current_speed, base_target_speed): + world = vehicle.get_world() + traffic_light = self.get_lane_traffic_light(vehicle, world) + + if not traffic_light: + self.is_stopped_at_red = False + self.red_light_stop_time = 0 + return base_target_speed, "No Light" + + stop_line_loc = get_traffic_light_stop_line(traffic_light) + dist_to_stop_line = vehicle.get_transform().location.distance(stop_line_loc) + + if traffic_light.get_state() == carla.TrafficLightState.Green: + if self.is_stopped_at_red: + recovery_speed = current_speed + (base_target_speed - current_speed) * GREEN_LIGHT_ACCEL_FACTOR + target_speed = max(STOP_SPEED_THRESHOLD, recovery_speed) + if abs(target_speed - base_target_speed) < 0.5: + self.is_stopped_at_red = False + self.logger.info(f"绿灯恢复行驶,目标速度:{target_speed:.1f}km/h") + return target_speed, "Green" + return base_target_speed, "Green" + + elif traffic_light.get_state() == carla.TrafficLightState.Yellow: + self.is_stopped_at_red = False + yellow_speed = max(5.0, base_target_speed * 0.3) + self.logger.warning(f"黄灯减速,目标速度:{yellow_speed:.1f}km/h") + return yellow_speed, "Yellow" + + elif traffic_light.get_state() == carla.TrafficLightState.Red: + if dist_to_stop_line > TRAFFIC_LIGHT_STOP_DISTANCE: + self.is_stopped_at_red = False + red_speed = max(2.0, current_speed * 0.1) + self.logger.warning(f"红灯减速,距离停止线:{dist_to_stop_line:.1f}m,目标速度:{red_speed:.1f}km/h") + return red_speed, "Red" + else: + if current_speed <= STOP_SPEED_THRESHOLD: + self.is_stopped_at_red = True + self.red_light_stop_time += 1 + wait_seconds = self.red_light_stop_time // 30 + self.logger.info(f"红灯停车等待:{wait_seconds}s") + return 0.0, "Red (Stopped)" + else: + self.logger.warning("红灯紧急制动") + return 0.0, "Red (Braking)" + + return base_target_speed, "Unknown" + +# ===================== 交通灯控制线程 ====================== +def cycle_traffic_light_states(world, stop_event): + logger = logging.getLogger(__name__) + logger = logging.LoggerAdapter(logger, {"vehicle_id": "系统"}) + while not stop_event.is_set(): + traffic_lights = world.get_actors().filter("traffic.traffic_light") + if not traffic_lights: + time.sleep(1) + continue + + # 红灯 + for tl in traffic_lights: + if is_actor_alive(tl): + try: + tl.set_state(carla.TrafficLightState.Red) + except: + pass + logger.info(f"所有交通灯切换为红灯,持续{RED_LIGHT_DURATION}秒") + stop_event.wait(RED_LIGHT_DURATION) + if stop_event.is_set(): + break + + # 绿灯 + for tl in traffic_lights: + if is_actor_alive(tl): + try: + tl.set_state(carla.TrafficLightState.Green) + except: + pass + logger.info(f"所有交通灯切换为绿灯,持续{GREEN_LIGHT_DURATION}秒") + stop_event.wait(GREEN_LIGHT_DURATION) + if stop_event.is_set(): + break + + # 黄灯 + for tl in traffic_lights: + if is_actor_alive(tl): + try: + tl.set_state(carla.TrafficLightState.Yellow) + except: + pass + logger.info(f"所有交通灯切换为黄灯,持续{YELLOW_LIGHT_DURATION}秒") + stop_event.wait(YELLOW_LIGHT_DURATION) + if stop_event.is_set(): + break + + logger.info("交通灯线程停止") + +# ===================== 主函数 ====================== +def main(): + global current_view_vehicle_id, vehicle_agents + pygame.init() + screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) + pygame.display.set_caption(f"CARLA多车辆视角({VEHICLE_COUNT}辆车)- 按1/2/3切换视角,按S切换分屏,按V切换俯视视角") + + client = None + world = None + map = None + tl_cycle_thread = None + tl_stop_event = threading.Event() + show_split_screen = True + show_top_view = False + top_view_camera = None + + # 清理函数 + def cleanup(): + print("\n开始清理资源...") + tl_stop_event.set() + if tl_cycle_thread and tl_cycle_thread.is_alive(): + tl_cycle_thread.join(timeout=2) + + if top_view_camera: + top_view_camera.stop() + top_view_camera.destroy() + + for agent in vehicle_agents: + agent.destroy() + + if world: + for actor in world.get_actors(): + if actor.type_id.startswith(("vehicle.", "walker.", "sensor.")): + if is_actor_alive(actor): + actor.destroy() + + pygame.quit() + print("资源清理完成") + + # 注册退出回调 + import atexit + import signal + atexit.register(cleanup) + signal.signal(signal.SIGINT, lambda sig, frame: sys.exit(0)) + + try: + # 连接CARLA + client = carla.Client(CARLA_HOST, CARLA_PORT) + client.set_timeout(CARLA_TIMEOUT) + try: + world = client.load_world("Town04") + print("成功加载Town04地图") + except Exception as e: + world = client.get_world() + print(f"警告:Town04地图加载失败({e}),使用当前地图") + map = world.get_map() + + # 清理残留演员 + print("清理残留演员...") + for actor in world.get_actors(): + if actor.type_id.startswith(("vehicle.", "walker.", "sensor.")): + if is_actor_alive(actor): + actor.destroy() + time.sleep(3.0) + print("清理完成") + + # 自动获取地图的第一个出生点作为基准(避免手动坐标无效) + base_location = None + all_spawn_points = map.get_spawn_points() + if all_spawn_points: + base_location = all_spawn_points[0].location + print(f"使用地图第一个出生点作为基准:({base_location.x:.1f}, {base_location.y:.1f})") + else: + base_location = carla.Location(x=220.0, y=150.0, z=0.5) + + # 获取有效的出生点 + print(f"获取{VEHICLE_COUNT}个有效出生点...") + valid_spawn_points = get_valid_spawn_points(map, VEHICLE_COUNT, base_location) + for i, sp in enumerate(valid_spawn_points): + print(f" 出生点{i+1}:({sp.location.x:.1f},{sp.location.y:.1f})") + + # 生成车辆 + print(f"\n分步生成车辆(间隔{SPAWN_INTERVAL}秒)...") + for i in range(VEHICLE_COUNT): + vehicle_model = VEHICLE_MODELS[i % len(VEHICLE_MODELS)] + base_speed = BASE_SPEEDS[i % len(BASE_SPEEDS)] + spawn_point = valid_spawn_points[i] + + try: + print(f"\n生成车辆{i+1}(车型:{vehicle_model})...") + agent = VehicleAgent(world, map, i+1, spawn_point, vehicle_model, base_speed) + vehicle_agents.append(agent) + print(f"车辆{i+1}生成成功!") + except Exception as e: + print(f"车辆{i+1}生成失败:{e}") + + time.sleep(SPAWN_INTERVAL) + + if len(vehicle_agents) == 0: + raise RuntimeError("无车辆生成成功,仿真终止") + + print(f"\n共生成{len(vehicle_agents)}辆车辆!") + + # 创建全局俯视相机 + try: + top_view_bp = world.get_blueprint_library().find("sensor.camera.rgb") + top_view_bp.set_attribute("image_size_x", str(WINDOW_WIDTH)) + top_view_bp.set_attribute("image_size_y", str(WINDOW_HEIGHT)) + top_view_bp.set_attribute("fov", str(90)) + top_view_transform = carla.Transform( + vehicle_agents[0].vehicle.get_transform().location + carla.Location(z=50), + carla.Rotation(pitch=-90) + ) + top_view_camera = world.spawn_actor(top_view_bp, top_view_transform) + top_view_surface = None + top_view_camera.listen(lambda image: globals().update({ + "top_view_surface": pygame.surfarray.make_surface( + np.swapaxes(np.array(image.raw_data).reshape((image.height, image.width, 4))[:, :, :3][:, :, ::-1], 0, 1) + ) + })) + except: + print("警告:无法创建俯视相机") + + # 启动交通灯线程 + tl_cycle_thread = threading.Thread(target=cycle_traffic_light_states, args=(world, tl_stop_event), daemon=True) + tl_cycle_thread.start() + print("交通灯线程启动") + + # 主循环 + clock = pygame.time.Clock() + running = True + + while running: + # 事件处理 + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_1 and len(vehicle_agents) >= 1: + current_view_vehicle_id = 1 + show_split_screen = False + show_top_view = False + elif event.key == pygame.K_2 and len(vehicle_agents) >= 2: + current_view_vehicle_id = 2 + show_split_screen = False + show_top_view = False + elif event.key == pygame.K_3 and len(vehicle_agents) >= 3: + current_view_vehicle_id = 3 + show_split_screen = False + show_top_view = False + elif event.key == pygame.K_s: + show_split_screen = True + show_top_view = False + elif event.key == pygame.K_v: + show_top_view = True + show_split_screen = False + elif event.key == pygame.K_ESCAPE: + running = False + + # 清空屏幕 + screen.fill((0, 0, 0)) + + if show_top_view: + if top_view_surface: + screen.blit(top_view_surface, (0, 0)) + elif show_split_screen: + if len(vehicle_agents) == 1: + agent = vehicle_agents[0] + if agent.camera.image_surface: + surface = pygame.transform.scale(agent.camera.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT)) + screen.blit(surface, (0, 0)) + elif len(vehicle_agents) == 2: + agent1 = vehicle_agents[0] + agent2 = vehicle_agents[1] + + if agent1.camera.image_surface: + surface1 = pygame.transform.scale(agent1.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT)) + screen.blit(surface1, (0, 0)) + + if agent2.camera.image_surface: + surface2 = pygame.transform.scale(agent2.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT)) + screen.blit(surface2, (WINDOW_WIDTH//2, 0)) + elif len(vehicle_agents) >= 3: + agent1 = vehicle_agents[0] + agent2 = vehicle_agents[1] + agent3 = vehicle_agents[2] + + if agent1.camera.image_surface: + surface1 = pygame.transform.scale(agent1.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) + screen.blit(surface1, (0, 0)) + + if agent2.camera.image_surface: + surface2 = pygame.transform.scale(agent2.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) + screen.blit(surface2, (WINDOW_WIDTH//2, 0)) + + if agent3.camera.image_surface: + surface3 = pygame.transform.scale(agent3.camera.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT//2)) + screen.blit(surface3, (0, WINDOW_HEIGHT//2)) + else: + target_agent = None + for agent in vehicle_agents: + if agent.vehicle_id == current_view_vehicle_id: + target_agent = agent + break + + if target_agent and target_agent.camera.image_surface: + surface = pygame.transform.scale(target_agent.camera.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT)) + screen.blit(surface, (0, 0)) + + # 更新车辆状态 + with ThreadPoolExecutor(max_workers=VEHICLE_COUNT) as executor: + futures = [executor.submit(agent.update) for agent in vehicle_agents] + for future in as_completed(futures): + try: + future.result() + except Exception as e: + print(f"车辆更新异常:{e}") + + # 刷新屏幕 + pygame.display.flip() + clock.tick(30) + + except Exception as e: + print(f"仿真异常:{e}") + traceback.print_exc() + finally: + cleanup() + +if __name__ == "__main__": + main() \ No newline at end of file From 585f250d74b602ea92a154f04721eaed49d66548 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Mon, 22 Dec 2025 09:43:09 +0800 Subject: [PATCH 19/26] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/ros/CMakeLists.txt | 206 ++++++++++++++++++ src/autonomous_driving_car/ros/package.xml | 68 ++++++ 2 files changed, 274 insertions(+) create mode 100644 src/autonomous_driving_car/ros/CMakeLists.txt create mode 100644 src/autonomous_driving_car/ros/package.xml diff --git a/src/autonomous_driving_car/ros/CMakeLists.txt b/src/autonomous_driving_car/ros/CMakeLists.txt new file mode 100644 index 0000000000..1627cb3d71 --- /dev/null +++ b/src/autonomous_driving_car/ros/CMakeLists.txt @@ -0,0 +1,206 @@ +cmake_minimum_required(VERSION 3.0.2) +project(my_robot) + +## Compile as C++11, supported in ROS Kinetic and newer +# add_compile_options(-std=c++11) + +## Find catkin macros and libraries +## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) +## is used, also find other catkin packages +find_package(catkin REQUIRED COMPONENTS + roscpp + rospy + std_msgs +) + +## System dependencies are found with CMake's conventions +# find_package(Boost REQUIRED COMPONENTS system) + + +## Uncomment this if the package has a setup.py. This macro ensures +## modules and global scripts declared therein get installed +## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html +# catkin_python_setup() + +################################################ +## Declare ROS messages, services and actions ## +################################################ + +## To declare and build messages, services or actions from within this +## package, follow these steps: +## * Let MSG_DEP_SET be the set of packages whose message types you use in +## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). +## * In the file package.xml: +## * add a build_depend tag for "message_generation" +## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET +## * If MSG_DEP_SET isn't empty the following dependency has been pulled in +## but can be declared for certainty nonetheless: +## * add a exec_depend tag for "message_runtime" +## * In this file (CMakeLists.txt): +## * add "message_generation" and every package in MSG_DEP_SET to +## find_package(catkin REQUIRED COMPONENTS ...) +## * add "message_runtime" and every package in MSG_DEP_SET to +## catkin_package(CATKIN_DEPENDS ...) +## * uncomment the add_*_files sections below as needed +## and list every .msg/.srv/.action file to be processed +## * uncomment the generate_messages entry below +## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) + +## Generate messages in the 'msg' folder +# add_message_files( +# FILES +# Message1.msg +# Message2.msg +# ) + +## Generate services in the 'srv' folder +# add_service_files( +# FILES +# Service1.srv +# Service2.srv +# ) + +## Generate actions in the 'action' folder +# add_action_files( +# FILES +# Action1.action +# Action2.action +# ) + +## Generate added messages and services with any dependencies listed here +# generate_messages( +# DEPENDENCIES +# std_msgs +# ) + +################################################ +## Declare ROS dynamic reconfigure parameters ## +################################################ + +## To declare and build dynamic reconfigure parameters within this +## package, follow these steps: +## * In the file package.xml: +## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" +## * In this file (CMakeLists.txt): +## * add "dynamic_reconfigure" to +## find_package(catkin REQUIRED COMPONENTS ...) +## * uncomment the "generate_dynamic_reconfigure_options" section below +## and list every .cfg file to be processed + +## Generate dynamic reconfigure parameters in the 'cfg' folder +# generate_dynamic_reconfigure_options( +# cfg/DynReconf1.cfg +# cfg/DynReconf2.cfg +# ) + +################################### +## catkin specific configuration ## +################################### +## The catkin_package macro generates cmake config files for your package +## Declare things to be passed to dependent projects +## INCLUDE_DIRS: uncomment this if your package contains header files +## LIBRARIES: libraries you create in this project that dependent projects also need +## CATKIN_DEPENDS: catkin_packages dependent projects also need +## DEPENDS: system dependencies of this project that dependent projects also need +catkin_package( +# INCLUDE_DIRS include +# LIBRARIES my_robot +# CATKIN_DEPENDS roscpp rospy std_msgs +# DEPENDS system_lib +) + +########### +## Build ## +########### + +## Specify additional locations of header files +## Your package locations should be listed before other locations +include_directories( +# include + ${catkin_INCLUDE_DIRS} +) + +## Declare a C++ library +# add_library(${PROJECT_NAME} +# src/${PROJECT_NAME}/my_robot.cpp +# ) + +## Add cmake target dependencies of the library +## as an example, code may need to be generated before libraries +## either from message generation or dynamic reconfigure +# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) + +## Declare a C++ executable +## With catkin_make all packages are built within a single CMake context +## The recommended prefix ensures that target names across packages don't collide +# add_executable(${PROJECT_NAME}_node src/my_robot_node.cpp) + +## Rename C++ executable without prefix +## The above recommended prefix causes long target names, the following renames the +## target back to the shorter version for ease of user use +## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" +# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") + +## Add cmake target dependencies of the executable +## same as for the library above +# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) + +## Specify libraries to link a library or executable target against +# target_link_libraries(${PROJECT_NAME}_node +# ${catkin_LIBRARIES} +# ) + +############# +## Install ## +############# + +# all install targets should use catkin DESTINATION variables +# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html + +## Mark executable scripts (Python etc.) for installation +## in contrast to setup.py, you can choose the destination +# catkin_install_python(PROGRAMS +# scripts/my_python_script +# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +# ) + +## Mark executables for installation +## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html +# install(TARGETS ${PROJECT_NAME}_node +# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +# ) + +## Mark libraries for installation +## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html +# install(TARGETS ${PROJECT_NAME} +# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} +# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} +# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} +# ) + +## Mark cpp header files for installation +# install(DIRECTORY include/${PROJECT_NAME}/ +# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} +# FILES_MATCHING PATTERN "*.h" +# PATTERN ".svn" EXCLUDE +# ) + +## Mark other files for installation (e.g. launch and bag files, etc.) +# install(FILES +# # myfile1 +# # myfile2 +# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} +# ) + +############# +## Testing ## +############# + +## Add gtest based cpp test target and link libraries +# catkin_add_gtest(${PROJECT_NAME}-test test/test_my_robot.cpp) +# if(TARGET ${PROJECT_NAME}-test) +# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) +# endif() + +## Add folders to be run by python nosetests +# catkin_add_nosetests(test) \ No newline at end of file diff --git a/src/autonomous_driving_car/ros/package.xml b/src/autonomous_driving_car/ros/package.xml new file mode 100644 index 0000000000..34abba9a68 --- /dev/null +++ b/src/autonomous_driving_car/ros/package.xml @@ -0,0 +1,68 @@ + + + my_robot + 0.0.0 + The my_robot package + + + + + li-y + + + + + + TODO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + catkin + roscpp + rospy + std_msgs + roscpp + rospy + std_msgs + roscpp + rospy + std_msgs + + + + + + + + \ No newline at end of file From 7b57177b1f12259a421f63bcc1c5fd41d5b21399 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Mon, 22 Dec 2025 18:03:11 +0800 Subject: [PATCH 20/26] =?UTF-8?q?=E4=B8=8A=E4=BC=A0ros=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/ros/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autonomous_driving_car/ros/CMakeLists.txt b/src/autonomous_driving_car/ros/CMakeLists.txt index 1627cb3d71..6b3b73737f 100644 --- a/src/autonomous_driving_car/ros/CMakeLists.txt +++ b/src/autonomous_driving_car/ros/CMakeLists.txt @@ -22,9 +22,9 @@ find_package(catkin REQUIRED COMPONENTS ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html # catkin_python_setup() -################################################ +############################################### ## Declare ROS messages, services and actions ## -################################################ +############################################### ## To declare and build messages, services or actions from within this ## package, follow these steps: From 53c0bd95bb41bf721b685f985371ba61a909230f Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Mon, 22 Dec 2025 18:20:39 +0800 Subject: [PATCH 21/26] =?UTF-8?q?=E5=88=A0=E9=99=A4ros=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E4=B8=8B=E7=9A=84package.xml=E5=92=8CCMakeLists.txt=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/CAS/v4.py | 338 ------------------ src/autonomous_driving_car/README.md | 75 ++-- src/autonomous_driving_car/ros/CMakeLists.txt | 206 ----------- src/autonomous_driving_car/ros/package.xml | 68 ---- 4 files changed, 20 insertions(+), 667 deletions(-) delete mode 100644 src/autonomous_driving_car/CAS/v4.py delete mode 100644 src/autonomous_driving_car/ros/CMakeLists.txt delete mode 100644 src/autonomous_driving_car/ros/package.xml diff --git a/src/autonomous_driving_car/CAS/v4.py b/src/autonomous_driving_car/CAS/v4.py deleted file mode 100644 index 3fbfcf65c4..0000000000 --- a/src/autonomous_driving_car/CAS/v4.py +++ /dev/null @@ -1,338 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -CARLA 弯道自适应转向:根据弯道大小动态调整转弯幅度 -""" - -import sys -import os -import carla -import numpy as np -import math -import pygame -import traceback - -# ===================== 全局配置(动态参数)===================== -# CARLA连接 -CARLA_HOST = "localhost" -CARLA_PORT = 2000 -CARLA_TIMEOUT = 10.0 - -# 车辆配置 -VEHICLE_MODEL = "vehicle.tesla.model3" -VEHICLE_WHEELBASE = 2.9 # 特斯拉Model3轴距(米) -VEHICLE_REAR_AXLE_OFFSET = 1.45 # 后轴偏移(正数) - -# 转向控制(动态参数基准) -LOOKAHEAD_DIST_STRAIGHT = 7.0 # 直道预瞄距离 -LOOKAHEAD_DIST_CURVE = 4.0 # 急弯预瞄距离 -STEER_GAIN_STRAIGHT = 0.7 # 直道转向增益 -STEER_GAIN_CURVE = 1.0 # 急弯转向增益 -STEER_DEADZONE = 0.05 # 转向死区 -STEER_LOWPASS_ALPHA = 0.6 # 低通滤波系数 -STEER_DIR_COEFF = 1.0 # 转向方向校准 -MAX_STEER = 1.0 # 最大转向角 - -# 弯道等级划分(方向变化量阈值) -DIR_CHANGE_GENTLE = 0.03 # 缓弯阈值 -DIR_CHANGE_SHARP = 0.08 # 急弯阈值 - -# 速度控制 -BASE_SPEED = 28.0 # 直道基础速度 -CURVE_SPEED_FACTOR = 0.6 # 弯道速度系数(更明显的速度衰减) -PID_KP = 0.15 -PID_KI = 0.01 -PID_KD = 0.01 - -# 相机 -CAMERA_POS = carla.Transform(carla.Location(x=-5.0, z=2.0)) -CAMERA_WIDTH = 800 -CAMERA_HEIGHT = 600 -CAMERA_FOV = 90 - -# ===================== 纯追踪控制器(动态适配)===================== -class AdaptivePurePursuit: - def __init__(self, wheelbase): - self.wheelbase = wheelbase - self.last_steer = 0.0 # 上一帧转向角 - self.last_lookahead = LOOKAHEAD_DIST_STRAIGHT # 上一帧预瞄距离 - - def calculate_steer(self, vehicle_transform, target_point, dir_change): - """ - 自适应纯追踪计算(根据弯道大小调整参数) - :param vehicle_transform: 车辆变换 - :param target_point: 目标点(carla.Location) - :param dir_change: 方向变化量(弯道大小) - :return: 自适应后的转向角 - """ - # 1. 获取车辆后轴位置 - forward_vec = vehicle_transform.get_forward_vector() - rear_axle_loc = carla.Location( - x=vehicle_transform.location.x - forward_vec.x * VEHICLE_REAR_AXLE_OFFSET, - y=vehicle_transform.location.y - forward_vec.y * VEHICLE_REAR_AXLE_OFFSET, - z=vehicle_transform.location.z - ) - - # 2. 转换到车辆坐标系 - dx = target_point.x - rear_axle_loc.x - dy = target_point.y - rear_axle_loc.y - yaw = math.radians(vehicle_transform.rotation.yaw) - - dx_vehicle = dx * math.cos(yaw) + dy * math.sin(yaw) - dy_vehicle = -dx * math.sin(yaw) + dy * math.cos(yaw) - - # 3. 动态计算转向增益(根据弯道大小线性插值) - # 方向变化量越大,增益越高 - steer_gain = np.interp( - dir_change, - [0, DIR_CHANGE_SHARP], - [STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE] - ) - steer_gain = np.clip(steer_gain, STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE) - - # 4. 纯追踪核心计算 - if dx_vehicle < 0.1: - steer = self.last_steer - else: - # 纯追踪公式 + 弯道大小系数(dir_change*2 放大差异) - steer_rad = math.atan2(2 * self.wheelbase * dy_vehicle * (1 + dir_change * 2), dx_vehicle ** 2 + dy_vehicle ** 2) - steer = steer_rad / math.pi - - # 应用增益和方向校准 - steer *= steer_gain * STEER_DIR_COEFF - - # 5. 应用死区 - if abs(steer) < STEER_DEADZONE: - steer = 0.0 - - # 6. 低通滤波(平滑) - steer = STEER_LOWPASS_ALPHA * steer + (1 - STEER_LOWPASS_ALPHA) * self.last_steer - - # 7. 限制范围 - steer = np.clip(steer, -MAX_STEER, MAX_STEER) - - # 8. 更新状态 - self.last_steer = steer - - return steer - - def get_adaptive_lookahead(self, dir_change): - """ - 动态获取预瞄距离(根据弯道大小线性插值) - :param dir_change: 方向变化量 - :return: 自适应预瞄距离 - """ - lookahead_dist = np.interp( - dir_change, - [0, DIR_CHANGE_SHARP], - [LOOKAHEAD_DIST_STRAIGHT, LOOKAHEAD_DIST_CURVE] - ) - lookahead_dist = np.clip(lookahead_dist, LOOKAHEAD_DIST_CURVE, LOOKAHEAD_DIST_STRAIGHT) - self.last_lookahead = lookahead_dist - return lookahead_dist - -# ===================== 速度控制器(动态调整)===================== -class SpeedController: - def __init__(self, kp, ki, kd): - self.kp = kp - self.ki = ki - self.kd = kd - self.last_error = 0.0 - self.integral = 0.0 - - def calculate(self, target_speed, current_speed): - """PID速度控制""" - error = target_speed - current_speed - - p = self.kp * error - self.integral += self.ki * error - self.integral = np.clip(self.integral, -1.0, 1.0) - i = self.integral - d = self.kd * (error - self.last_error) - self.last_error = error - - output = p + i + d - return np.clip(output, 0.0, 1.0) - -# ===================== 辅助函数:计算方向变化(替代曲率)===================== -def calculate_dir_change(current_wp): - """ - 计算相邻Waypoint的方向变化量(更精细的计算,增加采样点) - :param current_wp: 当前Waypoint - :return: 方向变化的绝对值之和,弯道等级(0=直道,1=缓弯,2=急弯) - """ - waypoints = [current_wp] - # 增加采样点到5个,更准确的判断弯道大小 - for i in range(5): - next_wps = waypoints[-1].next(1.0) - if next_wps: - waypoints.append(next_wps[0]) - else: - break - - if len(waypoints) < 4: - return 0.0, 0 - - # 计算每个相邻点的方向 - dirs = [] - for i in range(1, len(waypoints)): - wp_prev = waypoints[i-1] - wp_curr = waypoints[i] - dir_rad = math.atan2( - wp_curr.transform.location.y - wp_prev.transform.location.y, - wp_curr.transform.location.x - wp_prev.transform.location.x - ) - dirs.append(dir_rad) - - # 计算方向变化的绝对值之和(放大差异) - dir_change = 0.0 - for i in range(1, len(dirs)): - dir_change += abs(dirs[i] - dirs[i-1]) * 2 # 放大差异 - - # 划分弯道等级 - if dir_change < DIR_CHANGE_GENTLE: - curve_level = 0 # 直道 - elif dir_change < DIR_CHANGE_SHARP: - curve_level = 1 # 缓弯 - else: - curve_level = 2 # 急弯 - - return dir_change, curve_level - -# ===================== 相机管理器 ===================== -class CameraManager: - def __init__(self, world, vehicle, display): - self.world = world - self.vehicle = vehicle - self.display = display - self.camera = None - self._create_camera() - - def _create_camera(self): - bp = self.world.get_blueprint_library().find("sensor.camera.rgb") - bp.set_attribute("image_size_x", str(CAMERA_WIDTH)) - bp.set_attribute("image_size_y", str(CAMERA_HEIGHT)) - bp.set_attribute("fov", str(CAMERA_FOV)) - self.camera = self.world.spawn_actor(bp, CAMERA_POS, attach_to=self.vehicle) - self.camera.listen(self._on_image) - - def _on_image(self, image): - array = np.frombuffer(image.raw_data, dtype=np.uint8) - array = array.reshape((CAMERA_HEIGHT, CAMERA_WIDTH, 4))[:, :, :3] - array = array[:, :, ::-1].swapaxes(0, 1) - self.display.blit(pygame.surfarray.make_surface(array), (0, 0)) - pygame.display.flip() - - def destroy(self): - if self.camera: - self.camera.stop() - self.camera.destroy() - -# ===================== 主函数(核心逻辑)===================== -def main(): - pygame.init() - display = pygame.display.set_mode((CAMERA_WIDTH, CAMERA_HEIGHT)) - pygame.display.set_caption("CARLA 弯道自适应转向(最终版)") - - client = None - world = None - vehicle = None - camera_manager = None - pp_controller = None - speed_controller = None - - try: - # 1. 连接CARLA并初始化 - client = carla.Client(CARLA_HOST, CARLA_PORT) - client.set_timeout(CARLA_TIMEOUT) - world = client.get_world() - map = world.get_map() - - # 清理现有车辆 - for actor in world.get_actors().filter("vehicle.*"): - actor.destroy() - - # 生成车辆 - vehicle_bp = world.get_blueprint_library().find(VEHICLE_MODEL) - spawn_points = map.get_spawn_points() - vehicle = world.spawn_actor(vehicle_bp, spawn_points[0]) - print(f"车辆生成成功:{vehicle.type_id}") - - # 初始化控制器 - pp_controller = AdaptivePurePursuit(VEHICLE_WHEELBASE) - speed_controller = SpeedController(PID_KP, PID_KI, PID_KD) - camera_manager = CameraManager(world, vehicle, display) - - # 2. 主循环 - print("仿真启动,按ESC退出...") - clock = pygame.time.Clock() - running = True - - while running: - # 事件处理 - for event in pygame.event.get(): - if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): - running = False - - # 3. 获取关键数据 - vehicle_transform = vehicle.get_transform() - vehicle_vel = vehicle.get_velocity() - current_speed = math.hypot(vehicle_vel.x, vehicle_vel.y) * 3.6 # m/s → km/h - - # 获取当前Waypoint - current_wp = map.get_waypoint(vehicle_transform.location, project_to_road=True) - - # 计算方向变化量和弯道等级 - dir_change, curve_level = calculate_dir_change(current_wp) - - # 动态获取预瞄距离 - lookahead_dist = pp_controller.get_adaptive_lookahead(dir_change) - - # 获取目标Waypoint(动态预瞄距离) - target_wps = current_wp.next(lookahead_dist) - target_point = target_wps[0].transform.location if target_wps else vehicle_transform.location - - # 4. 计算目标速度(根据弯道等级调整) - curve_speed_factors = [1.0, 0.7, 0.4] # 直道、缓弯、急弯的速度系数 - speed_factor = curve_speed_factors[min(curve_level, 2)] - target_speed = BASE_SPEED * speed_factor - target_speed = max(10.0, target_speed) - - # 5. 计算转向角(传入方向变化量,实现自适应) - steer = pp_controller.calculate_steer(vehicle_transform, target_point, dir_change) - - # 6. 计算油门/刹车 - throttle = speed_controller.calculate(target_speed, current_speed) - brake = 1.0 - throttle if current_speed > target_speed + 5 else 0.0 - - # 7. 应用控制 - control = carla.VehicleControl() - control.steer = steer - control.throttle = throttle - control.brake = brake - control.hand_brake = False - vehicle.apply_control(control) - - # 8. 打印状态(调试用,显示弯道等级和动态参数) - curve_names = ["直道", "缓弯", "急弯"] - print(f"速度:{current_speed:.1f}km/h | 目标速度:{target_speed:.1f}km/h | 弯道:{curve_names[curve_level]} | 预瞄:{lookahead_dist:.1f}m | 转向:{steer:.3f}") - - # 控制帧率 - clock.tick(30) - - except Exception as e: - print(f"错误:{e}") - traceback.print_exc() - - finally: - # 清理资源 - print("清理资源...") - if camera_manager: - camera_manager.destroy() - if vehicle: - vehicle.destroy() - pygame.quit() - print("仿真结束") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/src/autonomous_driving_car/README.md b/src/autonomous_driving_car/README.md index 0bb5ae9ee2..019a9c1439 100644 --- a/src/autonomous_driving_car/README.md +++ b/src/autonomous_driving_car/README.md @@ -1,62 +1,32 @@ CARLA 模拟器和自动驾驶基础算法学习 - - -\# 无人驾驶汽车项目(基于CARLA模拟器) - -\## 项目简介 - +# 无人驾驶汽车项目(基于CARLA模拟器) +## 项目简介 本项目是基于CARLA开源仿真平台、Python和PyCharm开发的无人驾驶仿真系统,融合计算机视觉、路径规划与控制技术,实现虚拟场景中车辆的自主导航、避障与路径跟踪功能,适用于自动驾驶入门实践。 +## 核心功能 +- 路径规划:A*算法、RRT/RRT*算法实现起点到终点路径生成 +- 障碍物检测:YOLOv8+OpenCV实时识别目标,激光雷达点云聚类定位 +- 车辆控制:PID控制器实现转向、速度精准控制 +- CARLA交互:加载场景、获取传感器数据(摄像头/激光雷达等)、发送控制指令 +- 实时可视化:PyGame显示场景、车辆状态与检测结果 - -\## 核心功能 - -\- 路径规划:A\*算法、RRT/RRT\*算法实现起点到终点路径生成 - -\- 障碍物检测:YOLOv8+OpenCV实时识别目标,激光雷达点云聚类定位 - -\- 车辆控制:PID控制器实现转向、速度精准控制 - -\- CARLA交互:加载场景、获取传感器数据(摄像头/激光雷达等)、发送控制指令 - -\- 实时可视化:PyGame显示场景、车辆状态与检测结果 - - - -\## 技术栈 - +## 技术栈 | 类别 | 具体技术/工具 | - |--------------|---------------------------------------| - | 开发环境 | PyCharm Community Edition 2024+、Windows/Linux | - | 核心语言 | Python 3.8+ | - | 仿真平台 | CARLA 0.9.15/0.9.16、CARLA Python API | - | 计算机视觉 | OpenCV、NumPy、YOLOv8(Ultralytics) | - -| 路径规划 | A\*、RRT/RRT\*算法 | - +| 路径规划 | A*、RRT/RRT*算法 | | 控制理论 | PID控制器 | - | 可视化与数据 | PyGame、Matplotlib、Pandas | - - -\## 快速开始 - -1\. 克隆项目到PyCharm,创建Python 3.8+虚拟环境 - -2\. 安装依赖:`pip install -r requirements.txt`(核心依赖:carla、opencv-python、ultralytics、numpy、pygame) - -3\. 启动CARLA模拟器(运行`CarlaUE4.exe`/`CarlaUE4.sh`) - -4\. 运行主程序:`python main.py`,自动连接CARLA并自主导航 - - +## 快速开始 +1. 克隆项目到PyCharm,创建Python 3.8+虚拟环境 +2. 安装依赖:`pip install -r requirements.txt`(核心依赖:carla、opencv-python、ultralytics、numpy、pygame) +3. 启动CARLA模拟器(运行`CarlaUE4.exe`/`CarlaUE4.sh`) +4. 运行主程序:`python main.py`,自动连接CARLA并启动自主导航 \## 项目结构 @@ -65,14 +35,9 @@ autonomous_driving_car/ ├── MCP/ # 主控制与模块集成:包含感知(检测)、规划(路径)、控制(PID)的核心逻辑 ├── main.py # 项目入口:启动CARLA连接、调用MCP与FPV模块 └── README.md # 项目说明文档 +``` - - -\## 常见问题 - -\- CARLA连接失败:确保模拟器已启动,Python API版本与CARLA一致 - -\- 检测速度慢:使用YOLOv8n轻量模型,或启用GPU加速 - -\- 控制不稳定:调整PID参数或增加路径平滑处理 - +## 常见问题 +- CARLA连接失败:确保模拟器已启动,Python API版本与CARLA一致 +- 检测速度慢:使用YOLOv8n轻量模型,或启用GPU加速 +- 控制不稳定:调整PID参数或增加路径平滑处理 \ No newline at end of file diff --git a/src/autonomous_driving_car/ros/CMakeLists.txt b/src/autonomous_driving_car/ros/CMakeLists.txt deleted file mode 100644 index 6b3b73737f..0000000000 --- a/src/autonomous_driving_car/ros/CMakeLists.txt +++ /dev/null @@ -1,206 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) -project(my_robot) - -## Compile as C++11, supported in ROS Kinetic and newer -# add_compile_options(-std=c++11) - -## Find catkin macros and libraries -## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) -## is used, also find other catkin packages -find_package(catkin REQUIRED COMPONENTS - roscpp - rospy - std_msgs -) - -## System dependencies are found with CMake's conventions -# find_package(Boost REQUIRED COMPONENTS system) - - -## Uncomment this if the package has a setup.py. This macro ensures -## modules and global scripts declared therein get installed -## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html -# catkin_python_setup() - -############################################### -## Declare ROS messages, services and actions ## -############################################### - -## To declare and build messages, services or actions from within this -## package, follow these steps: -## * Let MSG_DEP_SET be the set of packages whose message types you use in -## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). -## * In the file package.xml: -## * add a build_depend tag for "message_generation" -## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET -## * If MSG_DEP_SET isn't empty the following dependency has been pulled in -## but can be declared for certainty nonetheless: -## * add a exec_depend tag for "message_runtime" -## * In this file (CMakeLists.txt): -## * add "message_generation" and every package in MSG_DEP_SET to -## find_package(catkin REQUIRED COMPONENTS ...) -## * add "message_runtime" and every package in MSG_DEP_SET to -## catkin_package(CATKIN_DEPENDS ...) -## * uncomment the add_*_files sections below as needed -## and list every .msg/.srv/.action file to be processed -## * uncomment the generate_messages entry below -## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) - -## Generate messages in the 'msg' folder -# add_message_files( -# FILES -# Message1.msg -# Message2.msg -# ) - -## Generate services in the 'srv' folder -# add_service_files( -# FILES -# Service1.srv -# Service2.srv -# ) - -## Generate actions in the 'action' folder -# add_action_files( -# FILES -# Action1.action -# Action2.action -# ) - -## Generate added messages and services with any dependencies listed here -# generate_messages( -# DEPENDENCIES -# std_msgs -# ) - -################################################ -## Declare ROS dynamic reconfigure parameters ## -################################################ - -## To declare and build dynamic reconfigure parameters within this -## package, follow these steps: -## * In the file package.xml: -## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" -## * In this file (CMakeLists.txt): -## * add "dynamic_reconfigure" to -## find_package(catkin REQUIRED COMPONENTS ...) -## * uncomment the "generate_dynamic_reconfigure_options" section below -## and list every .cfg file to be processed - -## Generate dynamic reconfigure parameters in the 'cfg' folder -# generate_dynamic_reconfigure_options( -# cfg/DynReconf1.cfg -# cfg/DynReconf2.cfg -# ) - -################################### -## catkin specific configuration ## -################################### -## The catkin_package macro generates cmake config files for your package -## Declare things to be passed to dependent projects -## INCLUDE_DIRS: uncomment this if your package contains header files -## LIBRARIES: libraries you create in this project that dependent projects also need -## CATKIN_DEPENDS: catkin_packages dependent projects also need -## DEPENDS: system dependencies of this project that dependent projects also need -catkin_package( -# INCLUDE_DIRS include -# LIBRARIES my_robot -# CATKIN_DEPENDS roscpp rospy std_msgs -# DEPENDS system_lib -) - -########### -## Build ## -########### - -## Specify additional locations of header files -## Your package locations should be listed before other locations -include_directories( -# include - ${catkin_INCLUDE_DIRS} -) - -## Declare a C++ library -# add_library(${PROJECT_NAME} -# src/${PROJECT_NAME}/my_robot.cpp -# ) - -## Add cmake target dependencies of the library -## as an example, code may need to be generated before libraries -## either from message generation or dynamic reconfigure -# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) - -## Declare a C++ executable -## With catkin_make all packages are built within a single CMake context -## The recommended prefix ensures that target names across packages don't collide -# add_executable(${PROJECT_NAME}_node src/my_robot_node.cpp) - -## Rename C++ executable without prefix -## The above recommended prefix causes long target names, the following renames the -## target back to the shorter version for ease of user use -## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" -# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") - -## Add cmake target dependencies of the executable -## same as for the library above -# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) - -## Specify libraries to link a library or executable target against -# target_link_libraries(${PROJECT_NAME}_node -# ${catkin_LIBRARIES} -# ) - -############# -## Install ## -############# - -# all install targets should use catkin DESTINATION variables -# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html - -## Mark executable scripts (Python etc.) for installation -## in contrast to setup.py, you can choose the destination -# catkin_install_python(PROGRAMS -# scripts/my_python_script -# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -# ) - -## Mark executables for installation -## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html -# install(TARGETS ${PROJECT_NAME}_node -# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} -# ) - -## Mark libraries for installation -## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html -# install(TARGETS ${PROJECT_NAME} -# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} -# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} -# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} -# ) - -## Mark cpp header files for installation -# install(DIRECTORY include/${PROJECT_NAME}/ -# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} -# FILES_MATCHING PATTERN "*.h" -# PATTERN ".svn" EXCLUDE -# ) - -## Mark other files for installation (e.g. launch and bag files, etc.) -# install(FILES -# # myfile1 -# # myfile2 -# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} -# ) - -############# -## Testing ## -############# - -## Add gtest based cpp test target and link libraries -# catkin_add_gtest(${PROJECT_NAME}-test test/test_my_robot.cpp) -# if(TARGET ${PROJECT_NAME}-test) -# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) -# endif() - -## Add folders to be run by python nosetests -# catkin_add_nosetests(test) \ No newline at end of file diff --git a/src/autonomous_driving_car/ros/package.xml b/src/autonomous_driving_car/ros/package.xml deleted file mode 100644 index 34abba9a68..0000000000 --- a/src/autonomous_driving_car/ros/package.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - my_robot - 0.0.0 - The my_robot package - - - - - li-y - - - - - - TODO - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - catkin - roscpp - rospy - std_msgs - roscpp - rospy - std_msgs - roscpp - rospy - std_msgs - - - - - - - - \ No newline at end of file From 684015ac911ae40575331a6609827f820b4a3625 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Mon, 22 Dec 2025 18:25:54 +0800 Subject: [PATCH 22/26] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/ros/CMakeLists.txt | 206 ++++++++++++++++++ src/autonomous_driving_car/ros/package.xml | 68 ++++++ 2 files changed, 274 insertions(+) create mode 100644 src/autonomous_driving_car/ros/CMakeLists.txt create mode 100644 src/autonomous_driving_car/ros/package.xml diff --git a/src/autonomous_driving_car/ros/CMakeLists.txt b/src/autonomous_driving_car/ros/CMakeLists.txt new file mode 100644 index 0000000000..6b3b73737f --- /dev/null +++ b/src/autonomous_driving_car/ros/CMakeLists.txt @@ -0,0 +1,206 @@ +cmake_minimum_required(VERSION 3.0.2) +project(my_robot) + +## Compile as C++11, supported in ROS Kinetic and newer +# add_compile_options(-std=c++11) + +## Find catkin macros and libraries +## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) +## is used, also find other catkin packages +find_package(catkin REQUIRED COMPONENTS + roscpp + rospy + std_msgs +) + +## System dependencies are found with CMake's conventions +# find_package(Boost REQUIRED COMPONENTS system) + + +## Uncomment this if the package has a setup.py. This macro ensures +## modules and global scripts declared therein get installed +## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html +# catkin_python_setup() + +############################################### +## Declare ROS messages, services and actions ## +############################################### + +## To declare and build messages, services or actions from within this +## package, follow these steps: +## * Let MSG_DEP_SET be the set of packages whose message types you use in +## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). +## * In the file package.xml: +## * add a build_depend tag for "message_generation" +## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET +## * If MSG_DEP_SET isn't empty the following dependency has been pulled in +## but can be declared for certainty nonetheless: +## * add a exec_depend tag for "message_runtime" +## * In this file (CMakeLists.txt): +## * add "message_generation" and every package in MSG_DEP_SET to +## find_package(catkin REQUIRED COMPONENTS ...) +## * add "message_runtime" and every package in MSG_DEP_SET to +## catkin_package(CATKIN_DEPENDS ...) +## * uncomment the add_*_files sections below as needed +## and list every .msg/.srv/.action file to be processed +## * uncomment the generate_messages entry below +## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) + +## Generate messages in the 'msg' folder +# add_message_files( +# FILES +# Message1.msg +# Message2.msg +# ) + +## Generate services in the 'srv' folder +# add_service_files( +# FILES +# Service1.srv +# Service2.srv +# ) + +## Generate actions in the 'action' folder +# add_action_files( +# FILES +# Action1.action +# Action2.action +# ) + +## Generate added messages and services with any dependencies listed here +# generate_messages( +# DEPENDENCIES +# std_msgs +# ) + +################################################ +## Declare ROS dynamic reconfigure parameters ## +################################################ + +## To declare and build dynamic reconfigure parameters within this +## package, follow these steps: +## * In the file package.xml: +## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" +## * In this file (CMakeLists.txt): +## * add "dynamic_reconfigure" to +## find_package(catkin REQUIRED COMPONENTS ...) +## * uncomment the "generate_dynamic_reconfigure_options" section below +## and list every .cfg file to be processed + +## Generate dynamic reconfigure parameters in the 'cfg' folder +# generate_dynamic_reconfigure_options( +# cfg/DynReconf1.cfg +# cfg/DynReconf2.cfg +# ) + +################################### +## catkin specific configuration ## +################################### +## The catkin_package macro generates cmake config files for your package +## Declare things to be passed to dependent projects +## INCLUDE_DIRS: uncomment this if your package contains header files +## LIBRARIES: libraries you create in this project that dependent projects also need +## CATKIN_DEPENDS: catkin_packages dependent projects also need +## DEPENDS: system dependencies of this project that dependent projects also need +catkin_package( +# INCLUDE_DIRS include +# LIBRARIES my_robot +# CATKIN_DEPENDS roscpp rospy std_msgs +# DEPENDS system_lib +) + +########### +## Build ## +########### + +## Specify additional locations of header files +## Your package locations should be listed before other locations +include_directories( +# include + ${catkin_INCLUDE_DIRS} +) + +## Declare a C++ library +# add_library(${PROJECT_NAME} +# src/${PROJECT_NAME}/my_robot.cpp +# ) + +## Add cmake target dependencies of the library +## as an example, code may need to be generated before libraries +## either from message generation or dynamic reconfigure +# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) + +## Declare a C++ executable +## With catkin_make all packages are built within a single CMake context +## The recommended prefix ensures that target names across packages don't collide +# add_executable(${PROJECT_NAME}_node src/my_robot_node.cpp) + +## Rename C++ executable without prefix +## The above recommended prefix causes long target names, the following renames the +## target back to the shorter version for ease of user use +## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" +# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") + +## Add cmake target dependencies of the executable +## same as for the library above +# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) + +## Specify libraries to link a library or executable target against +# target_link_libraries(${PROJECT_NAME}_node +# ${catkin_LIBRARIES} +# ) + +############# +## Install ## +############# + +# all install targets should use catkin DESTINATION variables +# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html + +## Mark executable scripts (Python etc.) for installation +## in contrast to setup.py, you can choose the destination +# catkin_install_python(PROGRAMS +# scripts/my_python_script +# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +# ) + +## Mark executables for installation +## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html +# install(TARGETS ${PROJECT_NAME}_node +# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +# ) + +## Mark libraries for installation +## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html +# install(TARGETS ${PROJECT_NAME} +# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} +# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} +# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} +# ) + +## Mark cpp header files for installation +# install(DIRECTORY include/${PROJECT_NAME}/ +# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} +# FILES_MATCHING PATTERN "*.h" +# PATTERN ".svn" EXCLUDE +# ) + +## Mark other files for installation (e.g. launch and bag files, etc.) +# install(FILES +# # myfile1 +# # myfile2 +# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} +# ) + +############# +## Testing ## +############# + +## Add gtest based cpp test target and link libraries +# catkin_add_gtest(${PROJECT_NAME}-test test/test_my_robot.cpp) +# if(TARGET ${PROJECT_NAME}-test) +# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) +# endif() + +## Add folders to be run by python nosetests +# catkin_add_nosetests(test) \ No newline at end of file diff --git a/src/autonomous_driving_car/ros/package.xml b/src/autonomous_driving_car/ros/package.xml new file mode 100644 index 0000000000..34abba9a68 --- /dev/null +++ b/src/autonomous_driving_car/ros/package.xml @@ -0,0 +1,68 @@ + + + my_robot + 0.0.0 + The my_robot package + + + + + li-y + + + + + + TODO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + catkin + roscpp + rospy + std_msgs + roscpp + rospy + std_msgs + roscpp + rospy + std_msgs + + + + + + + + \ No newline at end of file From c5bee8a546d876b786e430d6dce83454278d5972 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Mon, 22 Dec 2025 19:48:01 +0800 Subject: [PATCH 23/26] =?UTF-8?q?=E6=9B=B4=E6=96=B0README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/README.md | 61 +++++++++++++++++----------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/src/autonomous_driving_car/README.md b/src/autonomous_driving_car/README.md index 019a9c1439..022d8eb56a 100644 --- a/src/autonomous_driving_car/README.md +++ b/src/autonomous_driving_car/README.md @@ -1,43 +1,58 @@ -CARLA 模拟器和自动驾驶基础算法学习 - -# 无人驾驶汽车项目(基于CARLA模拟器) +# 无人驾驶汽车项目(基于CARLA 0.9.10+ROS) ## 项目简介 -本项目是基于CARLA开源仿真平台、Python和PyCharm开发的无人驾驶仿真系统,融合计算机视觉、路径规划与控制技术,实现虚拟场景中车辆的自主导航、避障与路径跟踪功能,适用于自动驾驶入门实践。 +本项目是基于**CARLA 0.9.10**开源仿真平台、Python 3.8+及ROS机器人操作系统开发的无人驾驶仿真系统,聚焦自动驾驶“感知-规划-控制”核心流程的落地实现。通过CARLA 0.9.10获取高保真的虚拟交通场景与传感器数据,结合计算机视觉、经典路径规划算法和PID控制技术,实现车辆自主导航、避障与路径跟踪,并利用ROS完成模块间分布式通信,同时通过FPV模块实现第一视角实时可视化,是自动驾驶入门实践的轻量化项目。 ## 核心功能 -- 路径规划:A*算法、RRT/RRT*算法实现起点到终点路径生成 -- 障碍物检测:YOLOv8+OpenCV实时识别目标,激光雷达点云聚类定位 -- 车辆控制:PID控制器实现转向、速度精准控制 -- CARLA交互:加载场景、获取传感器数据(摄像头/激光雷达等)、发送控制指令 -- 实时可视化:PyGame显示场景、车辆状态与检测结果 +1. **CARLA 0.9.10仿真交互**:支持加载城市场景、创建车辆与多传感器(摄像头、激光雷达、GPS/IMU),实时获取传感器数据并下发车辆控制指令,完美适配CARLA 0.9.10的API特性与车辆动力学模型。 +2. **环境感知**:采用YOLOv8+OpenCV实现车辆、行人、障碍物的实时目标检测,结合激光雷达点云聚类完成障碍物的精准定位与距离测算。 +3. **路径规划**:基于A*算法生成全局最优路径,RRT*算法实现动态避障的局部路径规划,输出适配车辆行驶的平滑参考轨迹。 +4. **车辆控制**:通过PID控制器实现转向、速度与制动的精准控制,高效跟踪规划路径,保证车辆行驶的稳定性。 +5. **模块化通信与可视化**:ROS模块实现感知、规划、控制模块的话题订阅与发布,支持分布式调试;FPV模块实时渲染车辆第一视角画面及检测结果。 ## 技术栈 | 类别 | 具体技术/工具 | |--------------|---------------------------------------| | 开发环境 | PyCharm Community Edition 2024+、Windows/Linux | | 核心语言 | Python 3.8+ | -| 仿真平台 | CARLA 0.9.15/0.9.16、CARLA Python API | +| 仿真平台 | CARLA 0.9.10、CARLA Python API | | 计算机视觉 | OpenCV、NumPy、YOLOv8(Ultralytics) | -| 路径规划 | A*、RRT/RRT*算法 | +| 路径规划 | A*、RRT*算法 | | 控制理论 | PID控制器 | -| 可视化与数据 | PyGame、Matplotlib、Pandas | +| 通信框架 | ROS(Noetic/Melodic)、rospy | +| 可视化工具 | PyGame、Matplotlib | ## 快速开始 -1. 克隆项目到PyCharm,创建Python 3.8+虚拟环境 -2. 安装依赖:`pip install -r requirements.txt`(核心依赖:carla、opencv-python、ultralytics、numpy、pygame) -3. 启动CARLA模拟器(运行`CarlaUE4.exe`/`CarlaUE4.sh`) -4. 运行主程序:`python main.py`,自动连接CARLA并启动自主导航 +### 1. 环境准备 +1. 安装Python 3.8+,创建并激活虚拟环境; +2. 安装项目依赖:`pip install -r requirements.txt`(核心依赖:`carla==0.9.10`、opencv-python、ultralytics、numpy、pygame、rospy); +3. 配置ROS环境(可选):安装对应版本ROS并初始化工作空间,将项目中`ros`文件夹链接到ROS工作空间`src`目录; +4. 启动CARLA 0.9.10模拟器:运行`CarlaUE4.exe`(Windows)或`CarlaUE4.sh`(Linux)。 + +### 2. 项目运行 +#### (1)无ROS模式 +直接运行主程序,自动连接CARLA并启动全流程功能: +```bash +python main.py +``` -\## 项目结构 +#### (2)ROS模式 +1. 启动ROS核心:`roscore`; +2. 依次启动CARLA-ROS桥接节点、感知/规划/控制节点及FPV可视化节点(具体命令见项目内注释); +3. 各节点通过ROS话题实现数据交互,完成车辆自主导航。 +## 项目结构 +``` autonomous_driving_car/ -├── FPV/ # 第一视角可视化模块:负责车辆摄像头视角、检测结果的实时显示 -├── MCP/ # 主控制与模块集成:包含感知(检测)、规划(路径)、控制(PID)的核心逻辑 -├── main.py # 项目入口:启动CARLA连接、调用MCP与FPV模块 +├── FPV/ # 第一视角可视化模块 +├── MCP/ # 主控制模块(集成感知、规划、控制核心逻辑) +├── ros/ # ROS通信模块 +├── main.py # 项目入口 └── README.md # 项目说明文档 ``` ## 常见问题 -- CARLA连接失败:确保模拟器已启动,Python API版本与CARLA一致 -- 检测速度慢:使用YOLOv8n轻量模型,或启用GPU加速 -- 控制不稳定:调整PID参数或增加路径平滑处理 \ No newline at end of file +1. **CARLA 0.9.10连接失败**:确保模拟器已启动,且Python安装的`carla`版本为0.9.10(可通过`pip show carla`查看版本); +2. **ROS节点无数据交互**:检查`roscore`是否启动、话题名称是否一致、ROS环境变量是否正确`source`; +3. **YOLO检测速度慢**:使用YOLOv8n轻量级模型,或启用GPU加速(需安装CUDA); +4. **车辆控制不稳定**:调整PID控制器的P/I/D参数,或对规划路径进行平滑处理; +5. **CARLA 0.9.10传感器数据异常**:确认传感器挂载位置与参数设置符合CARLA 0.9.10的Actor生成规则。 \ No newline at end of file From 7638494070ab3e4f3886b20ccd539d2d3807c5b2 Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Wed, 24 Dec 2025 00:16:08 +0800 Subject: [PATCH 24/26] =?UTF-8?q?=E6=B7=BB=E5=8A=A0ACC=E8=B7=9F=E8=BD=A6?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=9A=84=E5=85=A8=E5=B1=80=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/main.py | 74 ++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/src/autonomous_driving_car/main.py b/src/autonomous_driving_car/main.py index 533eccd517..18e0a0ed08 100644 --- a/src/autonomous_driving_car/main.py +++ b/src/autonomous_driving_car/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -CARLA 多车辆协同控制版:修复出生点索引越界问题 +CARLA 多车辆协同控制版:V1.0 增强ACC跟车+紧急避障 """ import sys @@ -52,6 +52,12 @@ PID_KI = 0.01 PID_KD = 0.02 +# ACC跟车配置(新增) +SAFE_TIME_GAP = 1.5 # 安全时距(秒) +MIN_SAFE_DISTANCE = 5.0 # 最小安全距离(米) +EMERGENCY_DECEL_RATE = 5.0 # 紧急制动减速度(km/h/帧) +LEAD_BRAKE_THRESHOLD = -10.0 # 前车急刹加速度阈值(km/h/s) + # 交通规则配置 TRAFFIC_LIGHT_STOP_DISTANCE = 4.0 TRAFFIC_LIGHT_DETECTION_RANGE = 50.0 @@ -72,12 +78,13 @@ # 全局变量 current_view_vehicle_id = 1 vehicle_agents = [] +COLLISION_FLAG = {} # 碰撞标志(新增) # 日志配置 logging.basicConfig( level=logging.INFO, format="%(asctime)s - 车辆%(vehicle_id)s - %(levelname)s - %(message)s", - handlers=[logging.FileHandler("multi_vehicle_simulation.log"), logging.StreamHandler()] + handlers=[logging.FileHandler("multi_vehicle_simulation_v1.log"), logging.StreamHandler()] ) # ===================== 核心工具函数 ====================== @@ -326,6 +333,11 @@ def __init__(self, world, map, vehicle_id, spawn_point, vehicle_model, base_spee self.logger = logging.getLogger(__name__) self.logger = logging.LoggerAdapter(self.logger, {"vehicle_id": vehicle_id}) + # 新增:ACC跟车相关属性 + self.last_lead_speed = 0.0 # 前车上次速度 + self.stuck_count = 0 # 卡死计数 + COLLISION_FLAG[self.vehicle_id] = False # 初始化碰撞标志 + # 生成车辆 self.vehicle_bp = self.world.get_blueprint_library().find(vehicle_model) if self.vehicle_bp.has_attribute("color"): @@ -400,17 +412,42 @@ def update(self): base_target_speed = self.base_speed * speed_factor base_target_speed = max(8.0, base_target_speed) - # 跟车控制 - global vehicle_agents + # ========== 新增:精细化ACC跟车+紧急避障逻辑 ========== if self.vehicle_id > 1 and len(vehicle_agents) >= self.vehicle_id: try: - lead_vehicle = vehicle_agents[self.vehicle_id - 2].vehicle + lead_agent = vehicle_agents[self.vehicle_id - 2] + lead_vehicle = lead_agent.vehicle lead_vehicle_transform = lead_vehicle.get_transform() + + # 计算前车速度和加速度 + lead_vel = lead_vehicle.get_velocity() + lead_speed = math.hypot(lead_vel.x, lead_vel.y) * 3.6 + lead_acc = (lead_speed - lead_agent.last_lead_speed) / 0.03 # 30Hz刷新率,计算加速度 + lead_agent.last_lead_speed = lead_speed # 更新前车上次速度 + + # 计算安全跟车距离(安全时距+最小安全距) + safe_dist = (current_speed / 3.6) * SAFE_TIME_GAP + MIN_SAFE_DISTANCE dist_to_lead = vehicle_transform.location.distance(lead_vehicle_transform.location) - if dist_to_lead < 15.0: - base_target_speed = max(5.0, base_target_speed * 0.5) - except: - pass + + # 动态调整目标速度 + if dist_to_lead < safe_dist - 2: + # 过近:减速至前车速度-2(不低于5km/h) + base_target_speed = max(5.0, lead_speed - 2) + elif dist_to_lead > safe_dist + 2: + # 过远:加速至前车速度+2(不超基础速度) + base_target_speed = min(self.base_speed * speed_factor, lead_speed + 2) + else: + # 安全距离:与前车速度同步 + base_target_speed = lead_speed + + # 紧急避障:前车急刹(加速度<阈值) + if lead_acc < LEAD_BRAKE_THRESHOLD: + base_target_speed = max(0.0, current_speed - EMERGENCY_DECEL_RATE) + self.logger.warning(f"前车急刹(加速度{lead_acc:.1f}km/h/s)!紧急减速至{base_target_speed:.1f}km/h") + + except Exception as e: + self.logger.warning(f"ACC跟车计算异常:{e}") + # ========== ACC跟车逻辑结束 ========== # 交通灯处理 target_speed, traffic_light_status = self.traffic_light_manager.handle_traffic_light_logic( @@ -426,6 +463,12 @@ def update(self): throttle = 0.0 brake = 1.0 + # 新增:碰撞后紧急停车 + if COLLISION_FLAG.get(self.vehicle_id, False): + throttle = 0.0 + brake = 1.0 + self.logger.error("检测到碰撞,紧急停车!") + # 应用控制 control = carla.VehicleControl() control.steer = steer @@ -433,10 +476,11 @@ def update(self): control.brake = brake self.vehicle.apply_control(control) - # 日志输出 + # 日志输出(新增ACC相关信息) self.logger.info( f"速度:{current_speed:5.1f}km/h | 目标:{target_speed:5.1f} | " - f"弯道:{['直道', '缓弯', '急弯'][curve_level]:<3} | 灯状态:{traffic_light_status}" + f"弯道:{['直道', '缓弯', '急弯'][curve_level]:<3} | 灯状态:{traffic_light_status} | " + f"ACC:{'激活' if self.vehicle_id>1 else '未激活'}" ) return True @@ -521,10 +565,10 @@ def calculate(self, target_speed, current_speed): p = self.kp * error self.integral += self.ki * error self.integral = np.clip(self.integral, -1.0, 1.0) - i = self.integral d = self.kd * (error - self.last_error) self.last_error = error - return np.clip(p + i + d, 0.0, 1.0) + # 修复:将 i 改为 self.integral + return np.clip(p + self.integral + d, 0.0, 1.0) class TrafficLightManager: def __init__(self, vehicle_id): @@ -681,7 +725,7 @@ def main(): global current_view_vehicle_id, vehicle_agents pygame.init() screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) - pygame.display.set_caption(f"CARLA多车辆视角({VEHICLE_COUNT}辆车)- 按1/2/3切换视角,按S切换分屏,按V切换俯视视角") + pygame.display.set_caption(f"CARLA多车辆视角({VEHICLE_COUNT}辆车)- V1.0 ACC跟车 - 按1/2/3切换视角,按S切换分屏,按V切换俯视视角") client = None world = None @@ -777,7 +821,7 @@ def cleanup(): if len(vehicle_agents) == 0: raise RuntimeError("无车辆生成成功,仿真终止") - print(f"\n共生成{len(vehicle_agents)}辆车辆!") + print(f"\n共生成{len(vehicle_agents)}辆车辆!V1.0 ACC跟车功能已启用") # 创建全局俯视相机 try: From 7f13466d586da80ac72cd2cb0762a7902c9108af Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Wed, 24 Dec 2025 19:20:23 +0800 Subject: [PATCH 25/26] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=84=9F=E7=9F=A5?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/main.py | 222 ++++++++++++++++++++++------- 1 file changed, 171 insertions(+), 51 deletions(-) diff --git a/src/autonomous_driving_car/main.py b/src/autonomous_driving_car/main.py index 18e0a0ed08..45ddb3e1b7 100644 --- a/src/autonomous_driving_car/main.py +++ b/src/autonomous_driving_car/main.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -CARLA 多车辆协同控制版:V1.0 增强ACC跟车+紧急避障 +CARLA 多车辆协同控制版:V2.0 增强感知(LiDAR+碰撞检测+障碍物避障) """ import sys @@ -52,12 +52,22 @@ PID_KI = 0.01 PID_KD = 0.02 -# ACC跟车配置(新增) +# ACC跟车配置(V1.0保留) SAFE_TIME_GAP = 1.5 # 安全时距(秒) MIN_SAFE_DISTANCE = 5.0 # 最小安全距离(米) EMERGENCY_DECEL_RATE = 5.0 # 紧急制动减速度(km/h/帧) LEAD_BRAKE_THRESHOLD = -10.0 # 前车急刹加速度阈值(km/h/s) +# LiDAR与障碍物检测配置(V2.0新增) +LIDAR_RANGE = 30.0 # LiDAR检测范围(米) +LIDAR_POINTS_PER_SECOND = 100000 # LiDAR点云密度 +LIDAR_ROTATION_FREQ = 30 # LiDAR刷新率(Hz) +OBSTACLE_DETECTION_WIDTH = 2.0 # 检测宽度(左右各2米) +OBSTACLE_MIN_HEIGHT = 0.5 # 障碍物最小高度(过滤地面) +OBSTACLE_WARNING_DIST = 8.0 # 障碍物预警距离(米) +OBSTACLE_EMERGENCY_DIST = 5.0 # 障碍物紧急制动距离(米) +OBSTACLE_DECEL_RATE = 8.0 # 障碍物制动减速度(km/h/帧) + # 交通规则配置 TRAFFIC_LIGHT_STOP_DISTANCE = 4.0 TRAFFIC_LIGHT_DETECTION_RANGE = 50.0 @@ -78,13 +88,14 @@ # 全局变量 current_view_vehicle_id = 1 vehicle_agents = [] -COLLISION_FLAG = {} # 碰撞标志(新增) +COLLISION_FLAG = {} # 碰撞标志(V2.0扩展) +OBSTACLE_FLAG = {} # 障碍物标志(V2.0新增) # 日志配置 logging.basicConfig( level=logging.INFO, format="%(asctime)s - 车辆%(vehicle_id)s - %(levelname)s - %(message)s", - handlers=[logging.FileHandler("multi_vehicle_simulation_v1.log"), logging.StreamHandler()] + handlers=[logging.FileHandler("multi_vehicle_simulation_v2.log"), logging.StreamHandler()] ) # ===================== 核心工具函数 ====================== @@ -282,20 +293,32 @@ def check_spawn_collision(world, spawn_point, radius=3.0): return True -# ===================== 相机管理类(多车辆)===================== -class VehicleCamera: +# ===================== 传感器管理类(V2.0重构:相机+LiDAR+碰撞)===================== +class VehicleSensors: def __init__(self, world, vehicle, vehicle_id): self.world = world self.vehicle = vehicle self.vehicle_id = vehicle_id + self.logger = logging.getLogger(__name__) + self.logger = logging.LoggerAdapter(self.logger, {"vehicle_id": vehicle_id}) + + # 传感器实例 self.camera = None + self.lidar = None + self.collision_sensor = None + + # 数据存储 self.image_surface = None + self.obstacle_distances = [] # 前方障碍物距离列表 + self.last_obstacle_dist = float('inf') # 最近障碍物距离 - # 创建相机传感器 + # 创建所有传感器 self._create_camera() + self._create_lidar() + self._create_collision_sensor() def _create_camera(self): - # 加载相机蓝图 + """创建RGB相机传感器""" camera_bp = self.world.get_blueprint_library().find("sensor.camera.rgb") camera_bp.set_attribute("image_size_x", str(640)) camera_bp.set_attribute("image_size_y", str(360)) @@ -303,25 +326,104 @@ def _create_camera(self): # 生成相机(附加到车辆) self.camera = self.world.spawn_actor(camera_bp, CAMERA_POS, attach_to=self.vehicle) - # 注册图像回调函数 self.camera.listen(self._on_image) + def _create_lidar(self): + """创建LiDAR传感器""" + lidar_bp = self.world.get_blueprint_library().find("sensor.lidar.ray_cast") + # 设置LiDAR参数 + lidar_bp.set_attribute("range", str(LIDAR_RANGE)) + lidar_bp.set_attribute("points_per_second", str(LIDAR_POINTS_PER_SECOND)) + lidar_bp.set_attribute("rotation_frequency", str(LIDAR_ROTATION_FREQ)) + lidar_bp.set_attribute("channels", "32") # 32线LiDAR + lidar_bp.set_attribute("upper_fov", "15") + lidar_bp.set_attribute("lower_fov", "-25") + lidar_bp.set_attribute("points_per_second", str(LIDAR_POINTS_PER_SECOND)) + + # LiDAR挂载位置(车顶) + lidar_transform = carla.Transform(carla.Location(x=0.0, z=2.0)) + self.lidar = self.world.spawn_actor(lidar_bp, lidar_transform, attach_to=self.vehicle) + # 注册LiDAR回调函数 + self.lidar.listen(self._on_lidar_data) + + def _create_collision_sensor(self): + """创建碰撞传感器""" + collision_bp = self.world.get_blueprint_library().find("sensor.other.collision") + self.collision_sensor = self.world.spawn_actor(collision_bp, carla.Transform(), attach_to=self.vehicle) + # 注册碰撞回调函数 + self.collision_sensor.listen(self._on_collision) + def _on_image(self, image): - # 将CARLA图像转换为Pygame Surface + """相机图像回调:转换为Pygame Surface""" array = np.frombuffer(image.raw_data, dtype=np.uint8) array = array.reshape((image.height, image.width, 4)) array = array[:, :, :3] array = array[:, :, ::-1] array = np.swapaxes(array, 0, 1) - - # 存储为Pygame Surface self.image_surface = pygame.surfarray.make_surface(array) + def _on_lidar_data(self, data): + """LiDAR点云回调:解析并提取前方障碍物""" + try: + # 将点云数据转换为numpy数组 (x, y, z, intensity) + points = np.frombuffer(data.raw_data, dtype=np.float32).reshape(-1, 4) + + # 过滤条件: + # 1. 前方(x>0) + # 2. 左右范围内(|y| < 检测宽度) + # 3. 非地面(z > 最小高度) + front_obstacle_points = points[ + (points[:, 0] > 0) & + (np.abs(points[:, 1]) < OBSTACLE_DETECTION_WIDTH) & + (points[:, 2] > OBSTACLE_MIN_HEIGHT) + ] + + # 计算最近障碍物距离 + if len(front_obstacle_points) > 0: + self.last_obstacle_dist = np.min(front_obstacle_points[:, 0]) + self.obstacle_distances.append(self.last_obstacle_dist) + # 只保留最近10帧数据(平滑滤波) + if len(self.obstacle_distances) > 10: + self.obstacle_distances.pop(0) + else: + self.last_obstacle_dist = float('inf') + self.obstacle_distances.clear() + + except Exception as e: + self.logger.error(f"LiDAR数据解析失败:{e}") + + def _on_collision(self, event): + """碰撞回调:记录碰撞信息并触发紧急停车""" + try: + collision_actor_type = event.other_actor.type_id + collision_location = event.transform.location + self.logger.error( + f"发生碰撞!碰撞对象:{collision_actor_type} | 碰撞位置:({collision_location.x:.1f}, {collision_location.y:.1f})" + ) + # 设置碰撞标志 + global COLLISION_FLAG + COLLISION_FLAG[self.vehicle_id] = True + except Exception as e: + self.logger.error(f"碰撞检测回调失败:{e}") + + def get_average_obstacle_distance(self): + """获取平滑后的障碍物距离""" + if len(self.obstacle_distances) == 0: + return float('inf') + return np.mean(self.obstacle_distances) + def destroy(self): - if self.camera: - self.camera.stop() - self.camera.destroy() + """销毁所有传感器""" + sensors = [self.camera, self.lidar, self.collision_sensor] + for sensor in sensors: + if sensor: + try: + sensor.stop() + sensor.destroy() + except: + pass + self.logger.info("所有传感器已销毁") # ===================== 车辆控制类 ====================== class VehicleAgent: @@ -333,10 +435,14 @@ def __init__(self, world, map, vehicle_id, spawn_point, vehicle_model, base_spee self.logger = logging.getLogger(__name__) self.logger = logging.LoggerAdapter(self.logger, {"vehicle_id": vehicle_id}) - # 新增:ACC跟车相关属性 + # ACC跟车相关属性(V1.0保留) self.last_lead_speed = 0.0 # 前车上次速度 self.stuck_count = 0 # 卡死计数 - COLLISION_FLAG[self.vehicle_id] = False # 初始化碰撞标志 + + # 全局状态初始化(V2.0扩展) + global COLLISION_FLAG, OBSTACLE_FLAG + COLLISION_FLAG[self.vehicle_id] = False + OBSTACLE_FLAG[self.vehicle_id] = False # 生成车辆 self.vehicle_bp = self.world.get_blueprint_library().find(vehicle_model) @@ -348,8 +454,8 @@ def __init__(self, world, map, vehicle_id, spawn_point, vehicle_model, base_spee if not self.vehicle: raise RuntimeError(f"车辆{vehicle_id}生成失败") - # 创建相机 - self.camera = VehicleCamera(world, self.vehicle, vehicle_id) + # 创建传感器(V2.0替换原相机类) + self.sensors = VehicleSensors(world, self.vehicle, vehicle_id) # 初始化控制器 self.pp_controller = AdaptivePurePursuit(VEHICLE_WHEELBASE) @@ -406,13 +512,13 @@ def update(self): target_wps = current_wp.next(lookahead_dist) target_point = target_wps[0].transform.location if target_wps else vehicle_transform.location - # 速度控制 + # 速度控制(基础弯道速度) curve_speed_factors = [1.0, 0.7, 0.4] speed_factor = curve_speed_factors[min(curve_level, 2)] base_target_speed = self.base_speed * speed_factor base_target_speed = max(8.0, base_target_speed) - # ========== 新增:精细化ACC跟车+紧急避障逻辑 ========== + # ========== V1.0保留:精细化ACC跟车+紧急避障逻辑 ========== if self.vehicle_id > 1 and len(vehicle_agents) >= self.vehicle_id: try: lead_agent = vehicle_agents[self.vehicle_id - 2] @@ -447,7 +553,19 @@ def update(self): except Exception as e: self.logger.warning(f"ACC跟车计算异常:{e}") - # ========== ACC跟车逻辑结束 ========== + + # ========== V2.0新增:LiDAR障碍物检测与避障 ========== + obstacle_dist = self.sensors.get_average_obstacle_distance() + OBSTACLE_FLAG[self.vehicle_id] = obstacle_dist < OBSTACLE_WARNING_DIST + + if obstacle_dist < OBSTACLE_EMERGENCY_DIST: + # 紧急制动:直接减速至0 + base_target_speed = max(0.0, current_speed - OBSTACLE_DECEL_RATE) + self.logger.warning(f"前方{obstacle_dist:.1f}米检测到障碍物!紧急制动,目标速度:{base_target_speed:.1f}km/h") + elif obstacle_dist < OBSTACLE_WARNING_DIST: + # 预警减速:降低至基础速度的50% + base_target_speed = max(8.0, base_target_speed * 0.5) + self.logger.warning(f"前方{obstacle_dist:.1f}米检测到障碍物!预警减速,目标速度:{base_target_speed:.1f}km/h") # 交通灯处理 target_speed, traffic_light_status = self.traffic_light_manager.handle_traffic_light_logic( @@ -459,15 +577,17 @@ def update(self): throttle = self.speed_controller.calculate(target_speed, current_speed) brake = 1.0 - throttle if current_speed > target_speed + 1 else 0.0 - if "Red (Stopped)" in traffic_light_status or target_speed == 0.0: - throttle = 0.0 - brake = 1.0 - - # 新增:碰撞后紧急停车 + # 状态优先级:碰撞 > 红灯 > 障碍物 > 正常行驶 if COLLISION_FLAG.get(self.vehicle_id, False): throttle = 0.0 brake = 1.0 self.logger.error("检测到碰撞,紧急停车!") + elif "Red (Stopped)" in traffic_light_status or target_speed == 0.0: + throttle = 0.0 + brake = 1.0 + elif obstacle_dist < OBSTACLE_EMERGENCY_DIST: + throttle = 0.0 + brake = 1.0 # 应用控制 control = carla.VehicleControl() @@ -476,11 +596,12 @@ def update(self): control.brake = brake self.vehicle.apply_control(control) - # 日志输出(新增ACC相关信息) + # 日志输出(新增障碍物信息) + obstacle_status = f"障碍物{obstacle_dist:.1f}m" if obstacle_dist < OBSTACLE_WARNING_DIST else "无障碍物" self.logger.info( f"速度:{current_speed:5.1f}km/h | 目标:{target_speed:5.1f} | " f"弯道:{['直道', '缓弯', '急弯'][curve_level]:<3} | 灯状态:{traffic_light_status} | " - f"ACC:{'激活' if self.vehicle_id>1 else '未激活'}" + f"ACC:{'激活' if self.vehicle_id>1 else '未激活'} | {obstacle_status}" ) return True @@ -490,12 +611,12 @@ def update(self): return False def destroy(self): - # 销毁相机 - self.camera.destroy() + # 销毁传感器 + self.sensors.destroy() # 销毁车辆 if self.vehicle and is_actor_alive(self.vehicle): self.vehicle.destroy() - self.logger.info("车辆资源已清理") + self.logger.info("车辆及传感器资源已清理") # ===================== 控制器类 ====================== class AdaptivePurePursuit: @@ -567,8 +688,7 @@ def calculate(self, target_speed, current_speed): self.integral = np.clip(self.integral, -1.0, 1.0) d = self.kd * (error - self.last_error) self.last_error = error - # 修复:将 i 改为 self.integral - return np.clip(p + self.integral + d, 0.0, 1.0) + return np.clip(p + self.integral + d, 0.0, 1.0) # 修复原代码i未定义问题 class TrafficLightManager: def __init__(self, vehicle_id): @@ -725,7 +845,7 @@ def main(): global current_view_vehicle_id, vehicle_agents pygame.init() screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) - pygame.display.set_caption(f"CARLA多车辆视角({VEHICLE_COUNT}辆车)- V1.0 ACC跟车 - 按1/2/3切换视角,按S切换分屏,按V切换俯视视角") + pygame.display.set_caption(f"CARLA多车辆视角({VEHICLE_COUNT}辆车)- V2.0 LiDAR感知 - 按1/2/3切换视角,按S切换分屏,按V切换俯视视角") client = None world = None @@ -812,7 +932,7 @@ def cleanup(): print(f"\n生成车辆{i+1}(车型:{vehicle_model})...") agent = VehicleAgent(world, map, i+1, spawn_point, vehicle_model, base_speed) vehicle_agents.append(agent) - print(f"车辆{i+1}生成成功!") + print(f"车辆{i+1}生成成功!已挂载LiDAR+碰撞传感器") except Exception as e: print(f"车辆{i+1}生成失败:{e}") @@ -821,7 +941,7 @@ def cleanup(): if len(vehicle_agents) == 0: raise RuntimeError("无车辆生成成功,仿真终止") - print(f"\n共生成{len(vehicle_agents)}辆车辆!V1.0 ACC跟车功能已启用") + print(f"\n共生成{len(vehicle_agents)}辆车辆!V2.0 LiDAR感知+障碍物避障功能已启用") # 创建全局俯视相机 try: @@ -888,35 +1008,35 @@ def cleanup(): elif show_split_screen: if len(vehicle_agents) == 1: agent = vehicle_agents[0] - if agent.camera.image_surface: - surface = pygame.transform.scale(agent.camera.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT)) + if agent.sensors.image_surface: + surface = pygame.transform.scale(agent.sensors.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT)) screen.blit(surface, (0, 0)) elif len(vehicle_agents) == 2: agent1 = vehicle_agents[0] agent2 = vehicle_agents[1] - if agent1.camera.image_surface: - surface1 = pygame.transform.scale(agent1.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT)) + if agent1.sensors.image_surface: + surface1 = pygame.transform.scale(agent1.sensors.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT)) screen.blit(surface1, (0, 0)) - if agent2.camera.image_surface: - surface2 = pygame.transform.scale(agent2.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT)) + if agent2.sensors.image_surface: + surface2 = pygame.transform.scale(agent2.sensors.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT)) screen.blit(surface2, (WINDOW_WIDTH//2, 0)) elif len(vehicle_agents) >= 3: agent1 = vehicle_agents[0] agent2 = vehicle_agents[1] agent3 = vehicle_agents[2] - if agent1.camera.image_surface: - surface1 = pygame.transform.scale(agent1.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) + if agent1.sensors.image_surface: + surface1 = pygame.transform.scale(agent1.sensors.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) screen.blit(surface1, (0, 0)) - if agent2.camera.image_surface: - surface2 = pygame.transform.scale(agent2.camera.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) + if agent2.sensors.image_surface: + surface2 = pygame.transform.scale(agent2.sensors.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) screen.blit(surface2, (WINDOW_WIDTH//2, 0)) - if agent3.camera.image_surface: - surface3 = pygame.transform.scale(agent3.camera.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT//2)) + if agent3.sensors.image_surface: + surface3 = pygame.transform.scale(agent3.sensors.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT//2)) screen.blit(surface3, (0, WINDOW_HEIGHT//2)) else: target_agent = None @@ -925,8 +1045,8 @@ def cleanup(): target_agent = agent break - if target_agent and target_agent.camera.image_surface: - surface = pygame.transform.scale(target_agent.camera.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT)) + if target_agent and target_agent.sensors.image_surface: + surface = pygame.transform.scale(target_agent.sensors.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT)) screen.blit(surface, (0, 0)) # 更新车辆状态 From 0c8e7fedcdfaa1245164fa7e973fd7e4a8c86c2e Mon Sep 17 00:00:00 2001 From: Liyang2302 <2358507952@qq.com> Date: Wed, 24 Dec 2025 21:37:54 +0800 Subject: [PATCH 26/26] =?UTF-8?q?=E6=8F=90=E5=8D=87=E5=8F=AF=E8=A7=86?= =?UTF-8?q?=E5=8C=96=E4=B8=8E=E8=B0=83=E8=AF=95=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/autonomous_driving_car/main.py | 992 +++++++++++++++++------------ 1 file changed, 580 insertions(+), 412 deletions(-) diff --git a/src/autonomous_driving_car/main.py b/src/autonomous_driving_car/main.py index 45ddb3e1b7..61676adc4d 100644 --- a/src/autonomous_driving_car/main.py +++ b/src/autonomous_driving_car/main.py @@ -1,7 +1,13 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -CARLA 多车辆协同控制版:V2.0 增强感知(LiDAR+碰撞检测+障碍物避障) +CARLA 多车辆协同控制版:V3.0 终极稳定版 +核心特性: +1. 彻底修复传感器销毁警告 +2. LiDAR点云处理性能优化(降采样+缓存) +3. 车辆状态实时监控与故障自动恢复 +4. 精准障碍物避障+ACC跟车+交通灯合规 +5. 多视角流畅切换+性能监控 """ import sys @@ -16,8 +22,9 @@ from concurrent.futures import ThreadPoolExecutor, as_completed import logging import random +from collections import deque -# ===================== 全局配置 ======================= +# ===================== 全局配置(V3.0优化)======================= # CARLA连接 CARLA_HOST = "localhost" CARLA_PORT = 2000 @@ -33,9 +40,9 @@ SPAWN_INTERVAL = 1.0 SPAWN_RETRY_MAX = 8 SPAWN_RETRY_DELAY = 0.5 -SPAWN_DISTANCE_LIMIT = 15.0 # 放宽距离限制到15米 +SPAWN_DISTANCE_LIMIT = 15.0 -# 车辆控制参数 +# 车辆控制参数(V3.0优化) VEHICLE_WHEELBASE = 2.9 VEHICLE_REAR_AXLE_OFFSET = 1.45 LOOKAHEAD_DIST_STRAIGHT = 7.0 @@ -49,24 +56,26 @@ DIR_CHANGE_SHARP = 0.08 BASE_SPEEDS = [25.0, 22.0, 20.0] PID_KP = 0.2 -PID_KI = 0.01 +PID_KI = 0.008 # 降低积分系数,减少饱和 PID_KD = 0.02 -# ACC跟车配置(V1.0保留) -SAFE_TIME_GAP = 1.5 # 安全时距(秒) -MIN_SAFE_DISTANCE = 5.0 # 最小安全距离(米) -EMERGENCY_DECEL_RATE = 5.0 # 紧急制动减速度(km/h/帧) -LEAD_BRAKE_THRESHOLD = -10.0 # 前车急刹加速度阈值(km/h/s) - -# LiDAR与障碍物检测配置(V2.0新增) -LIDAR_RANGE = 30.0 # LiDAR检测范围(米) -LIDAR_POINTS_PER_SECOND = 100000 # LiDAR点云密度 -LIDAR_ROTATION_FREQ = 30 # LiDAR刷新率(Hz) -OBSTACLE_DETECTION_WIDTH = 2.0 # 检测宽度(左右各2米) -OBSTACLE_MIN_HEIGHT = 0.5 # 障碍物最小高度(过滤地面) -OBSTACLE_WARNING_DIST = 8.0 # 障碍物预警距离(米) -OBSTACLE_EMERGENCY_DIST = 5.0 # 障碍物紧急制动距离(米) -OBSTACLE_DECEL_RATE = 8.0 # 障碍物制动减速度(km/h/帧) +# ACC跟车配置 +SAFE_TIME_GAP = 1.5 +MIN_SAFE_DISTANCE = 5.0 +EMERGENCY_DECEL_RATE = 5.0 +LEAD_BRAKE_THRESHOLD = -10.0 + +# LiDAR与障碍物检测配置(V3.0性能优化) +LIDAR_RANGE = 30.0 +LIDAR_POINTS_PER_SECOND = 50000 # 降采样,减少计算量 +LIDAR_ROTATION_FREQ = 20 # 降低刷新率,提升性能 +OBSTACLE_DETECTION_WIDTH = 2.0 +OBSTACLE_MIN_HEIGHT = 0.5 +OBSTACLE_MAX_HEIGHT = 3.0 # 新增最大高度过滤,避免误检高空物体 +OBSTACLE_WARNING_DIST = 8.0 +OBSTACLE_EMERGENCY_DIST = 5.0 +OBSTACLE_DECEL_RATE = 8.0 +OBSTACLE_CACHE_SIZE = 5 # 障碍物距离缓存大小,平滑滤波 # 交通规则配置 TRAFFIC_LIGHT_STOP_DISTANCE = 4.0 @@ -85,27 +94,74 @@ CAMERA_FOV = 120 CAMERA_POS = carla.Transform(carla.Location(x=-6.0, z=2.5), carla.Rotation(pitch=-5)) +# 性能监控配置 +PERF_MONITOR_INTERVAL = 1.0 # 性能监控输出间隔(秒) +VEHICLE_RESTART_THRESHOLD = 5 # 车辆连续故障次数阈值,超过则重启 + # 全局变量 current_view_vehicle_id = 1 vehicle_agents = [] -COLLISION_FLAG = {} # 碰撞标志(V2.0扩展) -OBSTACLE_FLAG = {} # 障碍物标志(V2.0新增) +COLLISION_FLAG = {} +OBSTACLE_FLAG = {} +last_perf_time = time.time() +perf_stats = {"frame_count": 0, "avg_fps": 0.0} -# 日志配置 +# 日志配置(V3.0增强) logging.basicConfig( level=logging.INFO, format="%(asctime)s - 车辆%(vehicle_id)s - %(levelname)s - %(message)s", - handlers=[logging.FileHandler("multi_vehicle_simulation_v2.log"), logging.StreamHandler()] + handlers=[ + logging.FileHandler("multi_vehicle_simulation_v3.log", encoding="utf-8"), + logging.StreamHandler(sys.stdout) + ] ) -# ===================== 核心工具函数 ====================== +# ===================== 核心工具函数(V3.0重构)===================== def is_actor_alive(actor): + """安全检查actor是否存活(终极版)""" + if actor is None: + return False try: return actor.is_alive() - except TypeError: - return actor.is_alive + except (TypeError, AttributeError): + try: + return actor.is_alive + except: + return False + +def is_sensor_listening(sensor): + """检查传感器是否在监听数据""" + if sensor is None or not is_actor_alive(sensor): + return False + try: + sensor.listen(lambda data: None) + return True + except: + return False + +def safe_sensor_stop(sensor, sensor_name, logger): + """安全停止传感器监听""" + if sensor is None or not is_actor_alive(sensor): + return + try: + if is_sensor_listening(sensor): + sensor.stop() + logger.debug(f"{sensor_name}监听已停止") + except Exception as e: + logger.warning(f"停止{sensor_name}监听忽略异常:{str(e)[:50]}") + +def safe_actor_destroy(actor, actor_name, logger): + """安全销毁actor""" + if actor is None or not is_actor_alive(actor): + return + try: + actor.destroy() + logger.debug(f"{actor_name}已销毁") + except Exception as e: + logger.warning(f"销毁{actor_name}忽略异常:{str(e)[:50]}") def get_traffic_light_stop_line(traffic_light): + """获取交通灯停止线位置(容错版)""" try: return traffic_light.get_stop_line_location() except AttributeError: @@ -116,6 +172,7 @@ def get_traffic_light_stop_line(traffic_light): return stop_line_loc def calculate_dir_change(current_wp): + """计算道路方向变化(优化版)""" waypoints = [current_wp] for i in range(5): next_wps = waypoints[-1].next(1.0) @@ -141,32 +198,46 @@ def calculate_dir_change(current_wp): for i in range(1, len(dirs)): dir_change += abs(dirs[i] - dirs[i-1]) * 2 - if dir_change < DIR_CHANGE_GENTLE: - curve_level = 0 - elif dir_change < DIR_CHANGE_SHARP: + curve_level = 0 + if dir_change >= DIR_CHANGE_GENTLE: curve_level = 1 - else: + if dir_change >= DIR_CHANGE_SHARP: curve_level = 2 return dir_change, curve_level -def get_forward_waypoint(vehicle, map): +def get_forward_waypoint(vehicle, map, wp_cache=None): + """获取前进方向路点(带缓存优化)""" vehicle_transform = vehicle.get_transform() + cache_key = (round(vehicle_transform.location.x, 1), round(vehicle_transform.location.y, 1)) + + # 缓存命中直接返回 + if wp_cache and cache_key in wp_cache: + return wp_cache[cache_key] + current_wp = map.get_waypoint( vehicle_transform.location, project_to_road=True, lane_type=carla.LaneType.Driving ) - # 所有车辆使用同一车道的路点 + # 所有车辆使用同一车道 global vehicle_agents if len(vehicle_agents) > 0: try: - lead_vehicle_wp = map.get_waypoint(vehicle_agents[0].vehicle.get_transform().location, project_to_road=True) - current_wp = map.get_waypoint(vehicle_transform.location, project_to_road=True, lane_id=lead_vehicle_wp.lane_id) + lead_vehicle_wp = map.get_waypoint( + vehicle_agents[0].vehicle.get_transform().location, + project_to_road=True + ) + current_wp = map.get_waypoint( + vehicle_transform.location, + project_to_road=True, + lane_id=lead_vehicle_wp.lane_id + ) except: pass + # 方向检查与修正 road_direction = current_wp.transform.get_forward_vector() vehicle_direction = vehicle_transform.get_forward_vector() dot_product = road_direction.x * vehicle_direction.x + road_direction.y * vehicle_direction.y @@ -180,92 +251,52 @@ def get_forward_waypoint(vehicle, map): vehicle_transform.location + vehicle_direction * 5.0, project_to_road=True ) - + + # 更新缓存(有效期短,避免过时) + if wp_cache: + wp_cache[cache_key] = current_wp + # 缓存清理:只保留最近100个 + if len(wp_cache) > 100: + wp_cache.pop(next(iter(wp_cache))) + return current_wp def get_valid_spawn_points(map, count, base_location=None, radius=100.0): - """ - 获取有效的出生点(增加容错性,避免索引越界) - """ - # 1. 获取地图所有出生点 + """获取有效出生点(终极容错版)""" all_spawn_points = map.get_spawn_points() if not all_spawn_points: raise RuntimeError("地图中无任何出生点") - # 2. 初始化候选点列表 candidate_points = [] - - # 3. 如果有基准位置,先筛选附近的点;否则直接使用所有点 if base_location: - filtered_points = [] - for sp in all_spawn_points: - dist = sp.location.distance(base_location) - if dist <= radius: - filtered_points.append((dist, sp)) - # 按距离排序 - filtered_points.sort(key=lambda x: x[0]) - candidate_points = [sp for _, sp in filtered_points] - - # 4. 如果候选点为空,直接使用所有出生点(容错) - if not candidate_points: + filtered_points = [(sp.location.distance(base_location), sp) for sp in all_spawn_points] + filtered_points = [sp for dist, sp in sorted(filtered_points) if dist <= radius] + candidate_points = filtered_points if filtered_points else all_spawn_points + else: candidate_points = all_spawn_points - print(f"警告:基准位置{base_location}附近无出生点,使用全局出生点") - # 5. 筛选集中的出生点(放宽条件) + # 筛选集中的出生点 valid_points = [] - # 确保基准点存在(核心修复:避免candidate_points[0]索引越界) - if not candidate_points: - candidate_points = all_spawn_points + if candidate_points: + base_sp = candidate_points[0] + valid_points.append(base_sp) - base_sp = candidate_points[0] - valid_points.append(base_sp) - - # 6. 筛选其他点,放宽距离限制 - for sp in candidate_points[1:]: - try: - # 检查与已选点的距离(放宽到15米) - if all(sp.location.distance(vp.location) <= SPAWN_DISTANCE_LIMIT for vp in valid_points): - wp = map.get_waypoint(sp.location, project_to_road=True) - if wp.lane_type == carla.LaneType.Driving and 0.0 <= sp.location.z <= 2.0: - valid_points.append(sp) - if len(valid_points) >= count: - break - except: - continue - - # 7. 如果数量不够,进一步放宽条件(距离限制到20米) - if len(valid_points) < count: - for sp in candidate_points: - if sp not in valid_points: - try: - if all(sp.location.distance(vp.location) <= SPAWN_DISTANCE_LIMIT * 1.5 for vp in valid_points): - wp = map.get_waypoint(sp.location, project_to_road=True) - if wp.lane_type == carla.LaneType.Driving and 0.0 <= sp.location.z <= 2.0: - valid_points.append(sp) - if len(valid_points) >= count: - break - except: - continue - - # 8. 如果还是不够,直接取前N个点(最终容错) - if len(valid_points) < count: - print(f"警告:无法找到{count}个集中的出生点,直接取前{count}个可用点") - for sp in candidate_points: - if sp not in valid_points: - wp = map.get_waypoint(sp.location, project_to_road=True) - if wp.lane_type == carla.LaneType.Driving and 0.0 <= sp.location.z <= 2.0: - valid_points.append(sp) + for sp in candidate_points[1:]: if len(valid_points) >= count: break + try: + if all(sp.location.distance(vp.location) <= SPAWN_DISTANCE_LIMIT for vp in valid_points): + wp = map.get_waypoint(sp.location, project_to_road=True) + if wp.lane_type == carla.LaneType.Driving and 0.0 <= sp.location.z <= 2.0: + valid_points.append(sp) + except: + continue - # 9. 最终检查:确保数量足够 - if len(valid_points) < count: - # 直接取所有可用点,不足的话重复使用(极端情况) - while len(valid_points) < count: - valid_points.append(valid_points[0]) - print(f"警告:出生点数量不足,重复使用已有点") + # 最终容错 + while len(valid_points) < count: + valid_points.append(valid_points[0] if valid_points else all_spawn_points[0]) - # 10. 统一出生点朝向 + # 统一朝向 try: forward_vec = valid_points[0].transform.get_forward_vector() for sp in valid_points: @@ -276,303 +307,376 @@ def get_valid_spawn_points(map, count, base_location=None, radius=100.0): return valid_points[:count] def check_spawn_collision(world, spawn_point, radius=3.0): - # 检查周围车辆和行人 - vehicles = world.get_actors().filter("vehicle.*") - for vehicle in vehicles: - if is_actor_alive(vehicle): - dist = vehicle.get_transform().location.distance(spawn_point.location) - if dist < radius: - return False - - walkers = world.get_actors().filter("walker.*") - for walker in walkers: - if is_actor_alive(walker): - dist = walker.get_transform().location.distance(spawn_point.location) - if dist < radius: - return False - + """检查出生点碰撞""" + for vehicle in world.get_actors().filter("vehicle.*"): + if is_actor_alive(vehicle) and vehicle.get_transform().location.distance(spawn_point.location) < radius: + return False + for walker in world.get_actors().filter("walker.*"): + if is_actor_alive(walker) and walker.get_transform().location.distance(spawn_point.location) < radius: + return False return True -# ===================== 传感器管理类(V2.0重构:相机+LiDAR+碰撞)===================== +def print_performance_stats(): + """打印性能统计信息""" + global perf_stats, last_perf_time + current_time = time.time() + if current_time - last_perf_time < PERF_MONITOR_INTERVAL: + return + + # 计算FPS + elapsed = current_time - last_perf_time + fps = perf_stats["frame_count"] / elapsed if elapsed > 0 else 0 + perf_stats["avg_fps"] = (perf_stats["avg_fps"] * 0.9) + (fps * 0.1) # 指数平滑 + + # 车辆状态统计 + alive_vehicles = sum(1 for agent in vehicle_agents if agent.is_alive) + collision_count = sum(1 for v_id in COLLISION_FLAG if COLLISION_FLAG[v_id]) + obstacle_count = sum(1 for v_id in OBSTACLE_FLAG if OBSTACLE_FLAG[v_id]) + + # 控制台输出 + print(f"\n=== 性能监控 [{time.strftime('%H:%M:%S')}] ===") + print(f"平均FPS: {perf_stats['avg_fps']:.1f} | 活跃车辆: {alive_vehicles}/{VEHICLE_COUNT}") + print(f"碰撞车辆数: {collision_count} | 障碍物预警数: {obstacle_count}") + print("="*50) + + # 重置统计 + perf_stats["frame_count"] = 0 + last_perf_time = current_time + +# ===================== 传感器管理类(V3.0终极版)===================== class VehicleSensors: def __init__(self, world, vehicle, vehicle_id): self.world = world self.vehicle = vehicle self.vehicle_id = vehicle_id - self.logger = logging.getLogger(__name__) - self.logger = logging.LoggerAdapter(self.logger, {"vehicle_id": vehicle_id}) - + self.logger = logging.LoggerAdapter(logging.getLogger(__name__), {"vehicle_id": vehicle_id}) + + # 状态标记 + self.is_destroyed = False + self.init_success = False + # 传感器实例 self.camera = None self.lidar = None self.collision_sensor = None - - # 数据存储 + + # 数据存储(V3.0优化) self.image_surface = None - self.obstacle_distances = [] # 前方障碍物距离列表 - self.last_obstacle_dist = float('inf') # 最近障碍物距离 - - # 创建所有传感器 - self._create_camera() - self._create_lidar() - self._create_collision_sensor() + self.obstacle_dist_cache = deque(maxlen=OBSTACLE_CACHE_SIZE) # 环形缓存 + self.last_obstacle_dist = float('inf') + + # 初始化传感器 + try: + self._create_camera() + self._create_lidar() + self._create_collision_sensor() + self.init_success = True + self.logger.info("所有传感器初始化成功") + except Exception as e: + self.logger.error(f"传感器初始化失败:{str(e)[:100]}") + self.destroy() def _create_camera(self): - """创建RGB相机传感器""" + """创建RGB相机""" camera_bp = self.world.get_blueprint_library().find("sensor.camera.rgb") - camera_bp.set_attribute("image_size_x", str(640)) - camera_bp.set_attribute("image_size_y", str(360)) + camera_bp.set_attribute("image_size_x", "640") + camera_bp.set_attribute("image_size_y", "360") camera_bp.set_attribute("fov", str(CAMERA_FOV)) - - # 生成相机(附加到车辆) + camera_bp.set_attribute("sensor_tick", "0.033") # 30Hz + self.camera = self.world.spawn_actor(camera_bp, CAMERA_POS, attach_to=self.vehicle) - # 注册图像回调函数 self.camera.listen(self._on_image) def _create_lidar(self): - """创建LiDAR传感器""" + """创建LiDAR传感器(V3.0性能优化)""" lidar_bp = self.world.get_blueprint_library().find("sensor.lidar.ray_cast") - # 设置LiDAR参数 + # 性能优化参数 lidar_bp.set_attribute("range", str(LIDAR_RANGE)) lidar_bp.set_attribute("points_per_second", str(LIDAR_POINTS_PER_SECOND)) lidar_bp.set_attribute("rotation_frequency", str(LIDAR_ROTATION_FREQ)) - lidar_bp.set_attribute("channels", "32") # 32线LiDAR - lidar_bp.set_attribute("upper_fov", "15") - lidar_bp.set_attribute("lower_fov", "-25") - lidar_bp.set_attribute("points_per_second", str(LIDAR_POINTS_PER_SECOND)) - - # LiDAR挂载位置(车顶) + lidar_bp.set_attribute("channels", "16") # 16线代替32线,降低计算量 + lidar_bp.set_attribute("upper_fov", "10") + lidar_bp.set_attribute("lower_fov", "-20") + lidar_bp.set_attribute("sensor_tick", str(1.0/LIDAR_ROTATION_FREQ)) + lidar_transform = carla.Transform(carla.Location(x=0.0, z=2.0)) self.lidar = self.world.spawn_actor(lidar_bp, lidar_transform, attach_to=self.vehicle) - # 注册LiDAR回调函数 self.lidar.listen(self._on_lidar_data) def _create_collision_sensor(self): """创建碰撞传感器""" collision_bp = self.world.get_blueprint_library().find("sensor.other.collision") self.collision_sensor = self.world.spawn_actor(collision_bp, carla.Transform(), attach_to=self.vehicle) - # 注册碰撞回调函数 self.collision_sensor.listen(self._on_collision) def _on_image(self, image): - """相机图像回调:转换为Pygame Surface""" - array = np.frombuffer(image.raw_data, dtype=np.uint8) - array = array.reshape((image.height, image.width, 4)) - array = array[:, :, :3] - array = array[:, :, ::-1] - array = np.swapaxes(array, 0, 1) - self.image_surface = pygame.surfarray.make_surface(array) + """相机图像回调(非阻塞)""" + if self.is_destroyed: + return + try: + array = np.frombuffer(image.raw_data, dtype=np.uint8).reshape((image.height, image.width, 4)) + array = array[:, :, :3][:, :, ::-1] # BGR转RGB + array = np.swapaxes(array, 0, 1) + self.image_surface = pygame.surfarray.make_surface(array) + except Exception as e: + self.logger.error(f"图像处理失败:{str(e)[:50]}") def _on_lidar_data(self, data): - """LiDAR点云回调:解析并提取前方障碍物""" + """LiDAR点云回调(V3.0优化)""" + if self.is_destroyed: + return try: - # 将点云数据转换为numpy数组 (x, y, z, intensity) - points = np.frombuffer(data.raw_data, dtype=np.float32).reshape(-1, 4) + # 点云降采样(每N个点取1个) + points = np.frombuffer(data.raw_data, dtype=np.float32).reshape(-1, 4)[::2] # 降采样50% - # 过滤条件: - # 1. 前方(x>0) - # 2. 左右范围内(|y| < 检测宽度) - # 3. 非地面(z > 最小高度) + # 精准过滤障碍物 front_obstacle_points = points[ - (points[:, 0] > 0) & - (np.abs(points[:, 1]) < OBSTACLE_DETECTION_WIDTH) & - (points[:, 2] > OBSTACLE_MIN_HEIGHT) + (points[:, 0] > 0) & # 前方 + (np.abs(points[:, 1]) < OBSTACLE_DETECTION_WIDTH) & # 左右范围 + (points[:, 2] > OBSTACLE_MIN_HEIGHT) & # 最小高度 + (points[:, 2] < OBSTACLE_MAX_HEIGHT) # 最大高度 ] - # 计算最近障碍物距离 + # 更新障碍物距离 if len(front_obstacle_points) > 0: self.last_obstacle_dist = np.min(front_obstacle_points[:, 0]) - self.obstacle_distances.append(self.last_obstacle_dist) - # 只保留最近10帧数据(平滑滤波) - if len(self.obstacle_distances) > 10: - self.obstacle_distances.pop(0) + self.obstacle_dist_cache.append(self.last_obstacle_dist) else: self.last_obstacle_dist = float('inf') - self.obstacle_distances.clear() + if self.obstacle_dist_cache: + self.obstacle_dist_cache.popleft() except Exception as e: - self.logger.error(f"LiDAR数据解析失败:{e}") + self.logger.error(f"LiDAR处理失败:{str(e)[:50]}") def _on_collision(self, event): - """碰撞回调:记录碰撞信息并触发紧急停车""" + """碰撞回调""" + if self.is_destroyed: + return try: - collision_actor_type = event.other_actor.type_id - collision_location = event.transform.location + collision_actor = event.other_actor + collision_type = collision_actor.type_id if collision_actor else "未知" + collision_loc = event.transform.location self.logger.error( - f"发生碰撞!碰撞对象:{collision_actor_type} | 碰撞位置:({collision_location.x:.1f}, {collision_location.y:.1f})" + f"碰撞发生!对象:{collision_type} | 位置:({collision_loc.x:.1f}, {collision_loc.y:.1f})" ) - # 设置碰撞标志 global COLLISION_FLAG COLLISION_FLAG[self.vehicle_id] = True except Exception as e: - self.logger.error(f"碰撞检测回调失败:{e}") + self.logger.error(f"碰撞检测失败:{str(e)[:50]}") - def get_average_obstacle_distance(self): + def get_smooth_obstacle_distance(self): """获取平滑后的障碍物距离""" - if len(self.obstacle_distances) == 0: + if not self.obstacle_dist_cache: return float('inf') - return np.mean(self.obstacle_distances) + return np.mean(self.obstacle_dist_cache) def destroy(self): - """销毁所有传感器""" - sensors = [self.camera, self.lidar, self.collision_sensor] - for sensor in sensors: - if sensor: - try: - sensor.stop() - sensor.destroy() - except: - pass - self.logger.info("所有传感器已销毁") + """安全销毁传感器(终极版)""" + if self.is_destroyed: + return + + self.is_destroyed = True + self.logger.info("开始销毁传感器") + + # 停止监听 + safe_sensor_stop(self.camera, "RGB相机", self.logger) + safe_sensor_stop(self.lidar, "LiDAR", self.logger) + safe_sensor_stop(self.collision_sensor, "碰撞传感器", self.logger) + + # 销毁传感器 + safe_actor_destroy(self.camera, "RGB相机", self.logger) + safe_actor_destroy(self.lidar, "LiDAR", self.logger) + safe_actor_destroy(self.collision_sensor, "碰撞传感器", self.logger) + + # 清空引用 + self.camera = None + self.lidar = None + self.collision_sensor = None + self.image_surface = None + + self.logger.info("传感器销毁完成") -# ===================== 车辆控制类 ====================== +# ===================== 车辆控制类(V3.0终极版)===================== class VehicleAgent: def __init__(self, world, map, vehicle_id, spawn_point, vehicle_model, base_speed): self.vehicle_id = vehicle_id self.world = world self.map = map self.base_speed = base_speed - self.logger = logging.getLogger(__name__) - self.logger = logging.LoggerAdapter(self.logger, {"vehicle_id": vehicle_id}) - - # ACC跟车相关属性(V1.0保留) - self.last_lead_speed = 0.0 # 前车上次速度 - self.stuck_count = 0 # 卡死计数 + self.logger = logging.LoggerAdapter(logging.getLogger(__name__), {"vehicle_id": vehicle_id}) + + # 状态管理 + self.is_alive = True + self.fault_count = 0 # 故障计数 + self.wp_cache = {} # 路点缓存 + self.last_update_success = True + + # ACC跟车属性 + self.last_lead_speed = 0.0 + self.last_lead_acc = 0.0 - # 全局状态初始化(V2.0扩展) + # 全局状态初始化 global COLLISION_FLAG, OBSTACLE_FLAG COLLISION_FLAG[self.vehicle_id] = False OBSTACLE_FLAG[self.vehicle_id] = False # 生成车辆 - self.vehicle_bp = self.world.get_blueprint_library().find(vehicle_model) - if self.vehicle_bp.has_attribute("color"): - color = random.choice(self.vehicle_bp.get_attribute("color").recommended_values) - self.vehicle_bp.set_attribute("color", color) - - self.vehicle = self._spawn_vehicle_with_retry(spawn_point) - if not self.vehicle: - raise RuntimeError(f"车辆{vehicle_id}生成失败") - - # 创建传感器(V2.0替换原相机类) - self.sensors = VehicleSensors(world, self.vehicle, vehicle_id) - - # 初始化控制器 - self.pp_controller = AdaptivePurePursuit(VEHICLE_WHEELBASE) - self.speed_controller = SpeedController(PID_KP, PID_KI, PID_KD, base_speed) - self.traffic_light_manager = TrafficLightManager(vehicle_id) - - self.is_alive = True - self.logger.info(f"生成成功,车型:{vehicle_model},出生点:({spawn_point.location.x:.1f},{spawn_point.location.y:.1f})") - - def _spawn_vehicle_with_retry(self, initial_spawn_point): - all_spawn_points = self.map.get_spawn_points() - if not all_spawn_points: - self.logger.error("地图中无有效出生点") - return None + self.vehicle = None + self.sensors = None + try: + self._spawn_vehicle(spawn_point, vehicle_model) + self._init_sensors() + self._init_controllers() + self.logger.info(f"车辆生成成功 | 车型:{vehicle_model} | 出生点:({spawn_point.location.x:.1f},{spawn_point.location.y:.1f})") + except Exception as e: + self.logger.error(f"车辆初始化失败:{str(e)[:100]}") + self.is_alive = False - candidate_points = [initial_spawn_point] - candidate_points += random.sample(all_spawn_points, min(10, len(all_spawn_points))) + def _spawn_vehicle(self, spawn_point, vehicle_model): + """生成车辆(带重试)""" + vehicle_bp = self.world.get_blueprint_library().find(vehicle_model) + if vehicle_bp.has_attribute("color"): + color = random.choice(vehicle_bp.get_attribute("color").recommended_values) + vehicle_bp.set_attribute("color", color) + candidate_points = [spawn_point] + random.sample(self.map.get_spawn_points(), min(5, len(self.map.get_spawn_points()))) + for retry in range(SPAWN_RETRY_MAX): - spawn_point = candidate_points[retry % len(candidate_points)] - spawn_point.location.z += 0.3 - spawn_point.rotation.yaw += random.randint(-5, 5) - - if not check_spawn_collision(self.world, spawn_point): - self.logger.warning(f"第{retry+1}次重试:出生点有碰撞风险,跳过") + current_sp = candidate_points[retry % len(candidate_points)] + current_sp.location.z += 0.3 + current_sp.rotation.yaw += random.randint(-5, 5) + + if not check_spawn_collision(self.world, current_sp): + self.logger.warning(f"出生点碰撞风险,重试{retry+1}/{SPAWN_RETRY_MAX}") time.sleep(SPAWN_RETRY_DELAY) continue - + try: - return self.world.spawn_actor(self.vehicle_bp, spawn_point) + self.vehicle = self.world.spawn_actor(vehicle_bp, current_sp) + self.logger.debug(f"车辆生成重试{retry+1}成功") + return except Exception as e: - self.logger.warning(f"第{retry+1}次重试失败:{e}") + self.logger.warning(f"车辆生成重试{retry+1}失败:{str(e)[:50]}") time.sleep(SPAWN_RETRY_DELAY) + + raise RuntimeError(f"超过{SPAWN_RETRY_MAX}次重试,车辆生成失败") + + def _init_sensors(self): + """初始化传感器""" + self.sensors = VehicleSensors(self.world, self.vehicle, self.vehicle_id) + if not self.sensors.init_success: + raise RuntimeError("传感器初始化失败") - self.logger.error(f"超过{SPAWN_RETRY_MAX}次重试,生成失败") - return None + def _init_controllers(self): + """初始化控制器""" + self.pp_controller = AdaptivePurePursuit(VEHICLE_WHEELBASE) + self.speed_controller = SpeedController(PID_KP, PID_KI, PID_KD, self.base_speed) + self.traffic_light_manager = TrafficLightManager(self.vehicle_id) + + def _restart_vehicle(self): + """重启故障车辆""" + self.logger.warning(f"车辆故障次数达到阈值,尝试重启") + + # 销毁旧车辆 + self.destroy() + + # 重新生成 + try: + spawn_points = get_valid_spawn_points(self.map, 1, self.vehicle.get_transform().location if self.vehicle else None) + self._spawn_vehicle(spawn_points[0], VEHICLE_MODELS[self.vehicle_id % len(VEHICLE_MODELS)]) + self._init_sensors() + self._init_controllers() + + # 重置状态 + self.is_alive = True + self.fault_count = 0 + self.last_update_success = True + COLLISION_FLAG[self.vehicle_id] = False + OBSTACLE_FLAG[self.vehicle_id] = False + + self.logger.info("车辆重启成功") + except Exception as e: + self.logger.error(f"车辆重启失败:{str(e)[:100]}") + self.is_alive = False def update(self): + """更新车辆状态(V3.0增强)""" if not self.is_alive or not is_actor_alive(self.vehicle): self.is_alive = False self.logger.error("车辆已销毁,停止更新") return False try: - # 获取车辆状态 + # 获取车辆基础状态 vehicle_transform = self.vehicle.get_transform() vehicle_vel = self.vehicle.get_velocity() current_speed = math.hypot(vehicle_vel.x, vehicle_vel.y) * 3.6 # 路径跟踪 - current_wp = get_forward_waypoint(self.vehicle, self.map) + current_wp = get_forward_waypoint(self.vehicle, self.map, self.wp_cache) dir_change, curve_level = calculate_dir_change(current_wp) lookahead_dist = self.pp_controller.get_adaptive_lookahead(dir_change) + target_wps = current_wp.next(lookahead_dist) target_point = target_wps[0].transform.location if target_wps else vehicle_transform.location - # 速度控制(基础弯道速度) + # 基础速度计算(弯道减速) curve_speed_factors = [1.0, 0.7, 0.4] speed_factor = curve_speed_factors[min(curve_level, 2)] - base_target_speed = self.base_speed * speed_factor - base_target_speed = max(8.0, base_target_speed) + base_target_speed = max(8.0, self.base_speed * speed_factor) - # ========== V1.0保留:精细化ACC跟车+紧急避障逻辑 ========== + # ========== ACC跟车逻辑(V3.0优化)========== if self.vehicle_id > 1 and len(vehicle_agents) >= self.vehicle_id: try: lead_agent = vehicle_agents[self.vehicle_id - 2] - lead_vehicle = lead_agent.vehicle - lead_vehicle_transform = lead_vehicle.get_transform() - - # 计算前车速度和加速度 - lead_vel = lead_vehicle.get_velocity() - lead_speed = math.hypot(lead_vel.x, lead_vel.y) * 3.6 - lead_acc = (lead_speed - lead_agent.last_lead_speed) / 0.03 # 30Hz刷新率,计算加速度 - lead_agent.last_lead_speed = lead_speed # 更新前车上次速度 - - # 计算安全跟车距离(安全时距+最小安全距) - safe_dist = (current_speed / 3.6) * SAFE_TIME_GAP + MIN_SAFE_DISTANCE - dist_to_lead = vehicle_transform.location.distance(lead_vehicle_transform.location) - - # 动态调整目标速度 - if dist_to_lead < safe_dist - 2: - # 过近:减速至前车速度-2(不低于5km/h) - base_target_speed = max(5.0, lead_speed - 2) - elif dist_to_lead > safe_dist + 2: - # 过远:加速至前车速度+2(不超基础速度) - base_target_speed = min(self.base_speed * speed_factor, lead_speed + 2) - else: - # 安全距离:与前车速度同步 - base_target_speed = lead_speed - - # 紧急避障:前车急刹(加速度<阈值) - if lead_acc < LEAD_BRAKE_THRESHOLD: - base_target_speed = max(0.0, current_speed - EMERGENCY_DECEL_RATE) - self.logger.warning(f"前车急刹(加速度{lead_acc:.1f}km/h/s)!紧急减速至{base_target_speed:.1f}km/h") + if lead_agent.is_alive and is_actor_alive(lead_agent.vehicle): + lead_vehicle = lead_agent.vehicle + lead_transform = lead_vehicle.get_transform() + lead_vel = lead_vehicle.get_velocity() + lead_speed = math.hypot(lead_vel.x, lead_vel.y) * 3.6 + + # 加速度平滑 + lead_acc = (lead_speed - lead_agent.last_lead_speed) / (1.0/30) + self.last_lead_acc = 0.8 * self.last_lead_acc + 0.2 * lead_acc + lead_agent.last_lead_speed = lead_speed + # 安全距离计算 + safe_dist = (current_speed / 3.6) * SAFE_TIME_GAP + MIN_SAFE_DISTANCE + dist_to_lead = vehicle_transform.location.distance(lead_transform.location) + + # 动态速度调整 + if dist_to_lead < safe_dist - 2: + base_target_speed = max(5.0, lead_speed - 2) + elif dist_to_lead > safe_dist + 2: + base_target_speed = min(self.base_speed * speed_factor, lead_speed + 2) + else: + base_target_speed = lead_speed + + # 前车急刹检测 + if self.last_lead_acc < LEAD_BRAKE_THRESHOLD: + base_target_speed = max(0.0, current_speed - EMERGENCY_DECEL_RATE) + self.logger.warning(f"前车急刹!加速度{self.last_lead_acc:.1f}km/h/s,紧急减速") except Exception as e: - self.logger.warning(f"ACC跟车计算异常:{e}") + self.logger.warning(f"ACC跟车异常:{str(e)[:50]}") - # ========== V2.0新增:LiDAR障碍物检测与避障 ========== - obstacle_dist = self.sensors.get_average_obstacle_distance() + # ========== 障碍物避障逻辑(V3.0优化)========== + obstacle_dist = self.sensors.get_smooth_obstacle_distance() OBSTACLE_FLAG[self.vehicle_id] = obstacle_dist < OBSTACLE_WARNING_DIST if obstacle_dist < OBSTACLE_EMERGENCY_DIST: - # 紧急制动:直接减速至0 base_target_speed = max(0.0, current_speed - OBSTACLE_DECEL_RATE) - self.logger.warning(f"前方{obstacle_dist:.1f}米检测到障碍物!紧急制动,目标速度:{base_target_speed:.1f}km/h") + self.logger.warning(f"前方{obstacle_dist:.1f}米障碍物!紧急制动") elif obstacle_dist < OBSTACLE_WARNING_DIST: - # 预警减速:降低至基础速度的50% base_target_speed = max(8.0, base_target_speed * 0.5) - self.logger.warning(f"前方{obstacle_dist:.1f}米检测到障碍物!预警减速,目标速度:{base_target_speed:.1f}km/h") + self.logger.warning(f"前方{obstacle_dist:.1f}米障碍物!预警减速") - # 交通灯处理 + # ========== 交通灯处理 ========== target_speed, traffic_light_status = self.traffic_light_manager.handle_traffic_light_logic( self.vehicle, current_speed, base_target_speed ) - # 计算控制指令 + # ========== 控制指令计算 ========== steer = self.pp_controller.calculate_steer(vehicle_transform, target_point, dir_change) throttle = self.speed_controller.calculate(target_speed, current_speed) brake = 1.0 - throttle if current_speed > target_speed + 1 else 0.0 @@ -581,8 +685,8 @@ def update(self): if COLLISION_FLAG.get(self.vehicle_id, False): throttle = 0.0 brake = 1.0 - self.logger.error("检测到碰撞,紧急停车!") - elif "Red (Stopped)" in traffic_light_status or target_speed == 0.0: + self.logger.error("碰撞触发紧急停车") + elif "Red (Stopped)" in traffic_light_status or target_speed <= STOP_SPEED_THRESHOLD: throttle = 0.0 brake = 1.0 elif obstacle_dist < OBSTACLE_EMERGENCY_DIST: @@ -596,7 +700,7 @@ def update(self): control.brake = brake self.vehicle.apply_control(control) - # 日志输出(新增障碍物信息) + # 日志输出 obstacle_status = f"障碍物{obstacle_dist:.1f}m" if obstacle_dist < OBSTACLE_WARNING_DIST else "无障碍物" self.logger.info( f"速度:{current_speed:5.1f}km/h | 目标:{target_speed:5.1f} | " @@ -604,28 +708,50 @@ def update(self): f"ACC:{'激活' if self.vehicle_id>1 else '未激活'} | {obstacle_status}" ) + self.last_update_success = True + self.fault_count = 0 # 重置故障计数 return True except Exception as e: - self.logger.error(f"更新失败:{e}", exc_info=True) + self.logger.error(f"更新失败:{str(e)[:100]}", exc_info=False) + self.last_update_success = False + self.fault_count += 1 + + # 故障重启逻辑 + if self.fault_count >= VEHICLE_RESTART_THRESHOLD: + self._restart_vehicle() + return False def destroy(self): + """安全销毁车辆(V3.0终极版)""" + self.logger.info("开始销毁车辆资源") + # 销毁传感器 - self.sensors.destroy() + if self.sensors: + self.sensors.destroy() + # 销毁车辆 - if self.vehicle and is_actor_alive(self.vehicle): - self.vehicle.destroy() - self.logger.info("车辆及传感器资源已清理") + safe_actor_destroy(self.vehicle, f"车辆{self.vehicle_id}", self.logger) + + # 清空状态 + self.vehicle = None + self.sensors = None + self.is_alive = False + self.wp_cache.clear() + + self.logger.info("车辆资源销毁完成") -# ===================== 控制器类 ====================== +# ===================== 控制器类(V3.0优化)===================== class AdaptivePurePursuit: + """自适应纯追踪控制器""" def __init__(self, wheelbase): self.wheelbase = wheelbase self.last_steer = 0.0 self.last_lookahead = LOOKAHEAD_DIST_STRAIGHT def calculate_steer(self, vehicle_transform, target_point, dir_change): + """计算转向角(优化滤波)""" forward_vec = vehicle_transform.get_forward_vector() rear_axle_loc = carla.Location( x=vehicle_transform.location.x - forward_vec.x * VEHICLE_REAR_AXLE_OFFSET, @@ -640,6 +766,7 @@ def calculate_steer(self, vehicle_transform, target_point, dir_change): dx_vehicle = dx * math.cos(yaw) + dy * math.sin(yaw) dy_vehicle = -dx * math.sin(yaw) + dy * math.cos(yaw) + # 转向增益自适应 steer_gain = np.interp( dir_change, [0, DIR_CHANGE_SHARP], @@ -647,6 +774,7 @@ def calculate_steer(self, vehicle_transform, target_point, dir_change): ) steer_gain = np.clip(steer_gain, STEER_GAIN_STRAIGHT, STEER_GAIN_CURVE) + # 计算转向角 if dx_vehicle < 0.1: steer = self.last_steer else: @@ -654,6 +782,7 @@ def calculate_steer(self, vehicle_transform, target_point, dir_change): steer = steer_rad / math.pi steer *= steer_gain + # 死区和低通滤波 if abs(steer) < STEER_DEADZONE: steer = 0.0 steer = STEER_LOWPASS_ALPHA * steer + (1 - STEER_LOWPASS_ALPHA) * self.last_steer @@ -663,6 +792,7 @@ def calculate_steer(self, vehicle_transform, target_point, dir_change): return steer def get_adaptive_lookahead(self, dir_change): + """自适应前瞻距离""" lookahead_dist = np.interp( dir_change, [0, DIR_CHANGE_SHARP], @@ -673,6 +803,7 @@ def get_adaptive_lookahead(self, dir_change): return lookahead_dist class SpeedController: + """PID速度控制器(V3.0优化)""" def __init__(self, kp, ki, kd, base_speed): self.kp = kp self.ki = ki @@ -680,26 +811,37 @@ def __init__(self, kp, ki, kd, base_speed): self.base_speed = base_speed self.last_error = 0.0 self.integral = 0.0 + self.integral_limit = 0.5 # 积分限幅 def calculate(self, target_speed, current_speed): + """计算油门""" error = target_speed - current_speed + + # PID计算 p = self.kp * error self.integral += self.ki * error - self.integral = np.clip(self.integral, -1.0, 1.0) + self.integral = np.clip(self.integral, -self.integral_limit, self.integral_limit) # 积分限幅 d = self.kd * (error - self.last_error) + + # 输出限幅 + output = np.clip(p + self.integral + d, 0.0, 1.0) + self.last_error = error - return np.clip(p + self.integral + d, 0.0, 1.0) # 修复原代码i未定义问题 + return output class TrafficLightManager: + """交通灯管理器(V3.0缓存优化)""" def __init__(self, vehicle_id): self.vehicle_id = vehicle_id self.tracked_light = None self.is_stopped_at_red = False self.red_light_stop_time = 0 - self.logger = logging.getLogger(__name__) - self.logger = logging.LoggerAdapter(self.logger, {"vehicle_id": vehicle_id}) + self.tl_state_cache = {} # 交通灯状态缓存 + self.tl_cache_time = {} # 缓存时间 + self.logger = logging.LoggerAdapter(logging.getLogger(__name__), {"vehicle_id": vehicle_id}) def _calculate_angle_between_vehicle_and_light(self, vehicle_transform, light_transform): + """计算车辆与交通灯的夹角""" vehicle_forward = vehicle_transform.get_forward_vector() vehicle_forward = np.array([vehicle_forward.x, vehicle_forward.y]) vehicle_forward = vehicle_forward / np.linalg.norm(vehicle_forward) @@ -711,19 +853,24 @@ def _calculate_angle_between_vehicle_and_light(self, vehicle_transform, light_tr light_dir = light_dir / np.linalg.norm(light_dir) angle = math.acos(np.clip(np.dot(vehicle_forward, light_dir), -1.0, 1.0)) - angle = math.degrees(angle) - return angle + return math.degrees(angle) def get_lane_traffic_light(self, vehicle, world): + """获取车道对应的交通灯(带缓存)""" vehicle_transform = vehicle.get_transform() vehicle_loc = vehicle_transform.location + current_time = time.time() + # 缓存检查 if self.tracked_light and is_actor_alive(self.tracked_light): - dist = self.tracked_light.get_transform().location.distance(vehicle_loc) - angle = self._calculate_angle_between_vehicle_and_light(vehicle_transform, self.tracked_light.get_transform()) - if dist < TRAFFIC_LIGHT_DETECTION_RANGE and angle < TRAFFIC_LIGHT_ANGLE_THRESHOLD: - return self.tracked_light - + tl_id = self.tracked_light.id + if tl_id in self.tl_state_cache and current_time - self.tl_cache_time.get(tl_id, 0) < 1.0: + dist = self.tracked_light.get_transform().location.distance(vehicle_loc) + angle = self._calculate_angle_between_vehicle_and_light(vehicle_transform, self.tracked_light.get_transform()) + if dist < TRAFFIC_LIGHT_DETECTION_RANGE and angle < TRAFFIC_LIGHT_ANGLE_THRESHOLD: + return self.tracked_light + + # 重新查找交通灯 traffic_lights = world.get_actors().filter("traffic.traffic_light") valid_lights = [] @@ -737,15 +884,19 @@ def get_lane_traffic_light(self, vehicle, world): if angle < TRAFFIC_LIGHT_ANGLE_THRESHOLD: valid_lights.append((dist, light)) + # 更新追踪的交通灯 if valid_lights: valid_lights.sort(key=lambda x: x[0]) self.tracked_light = valid_lights[0][1] - return self.tracked_light + self.tl_state_cache[self.tracked_light.id] = self.tracked_light.get_state() + self.tl_cache_time[self.tracked_light.id] = current_time + else: + self.tracked_light = None - self.tracked_light = None - return None + return self.tracked_light def handle_traffic_light_logic(self, vehicle, current_speed, base_target_speed): + """处理交通灯逻辑""" world = vehicle.get_world() traffic_light = self.get_lane_traffic_light(vehicle, world) @@ -754,10 +905,13 @@ def handle_traffic_light_logic(self, vehicle, current_speed, base_target_speed): self.red_light_stop_time = 0 return base_target_speed, "No Light" + # 获取停止线和距离 stop_line_loc = get_traffic_light_stop_line(traffic_light) dist_to_stop_line = vehicle.get_transform().location.distance(stop_line_loc) - if traffic_light.get_state() == carla.TrafficLightState.Green: + # 交通灯状态处理 + tl_state = traffic_light.get_state() + if tl_state == carla.TrafficLightState.Green: if self.is_stopped_at_red: recovery_speed = current_speed + (base_target_speed - current_speed) * GREEN_LIGHT_ACCEL_FACTOR target_speed = max(STOP_SPEED_THRESHOLD, recovery_speed) @@ -767,17 +921,17 @@ def handle_traffic_light_logic(self, vehicle, current_speed, base_target_speed): return target_speed, "Green" return base_target_speed, "Green" - elif traffic_light.get_state() == carla.TrafficLightState.Yellow: + elif tl_state == carla.TrafficLightState.Yellow: self.is_stopped_at_red = False yellow_speed = max(5.0, base_target_speed * 0.3) self.logger.warning(f"黄灯减速,目标速度:{yellow_speed:.1f}km/h") return yellow_speed, "Yellow" - elif traffic_light.get_state() == carla.TrafficLightState.Red: + elif tl_state == carla.TrafficLightState.Red: if dist_to_stop_line > TRAFFIC_LIGHT_STOP_DISTANCE: self.is_stopped_at_red = False red_speed = max(2.0, current_speed * 0.1) - self.logger.warning(f"红灯减速,距离停止线:{dist_to_stop_line:.1f}m,目标速度:{red_speed:.1f}km/h") + self.logger.warning(f"红灯减速,距离停止线:{dist_to_stop_line:.1f}m") return red_speed, "Red" else: if current_speed <= STOP_SPEED_THRESHOLD: @@ -794,8 +948,8 @@ def handle_traffic_light_logic(self, vehicle, current_speed, base_target_speed): # ===================== 交通灯控制线程 ====================== def cycle_traffic_light_states(world, stop_event): - logger = logging.getLogger(__name__) - logger = logging.LoggerAdapter(logger, {"vehicle_id": "系统"}) + """交通灯状态循环""" + logger = logging.LoggerAdapter(logging.getLogger(__name__), {"vehicle_id": "系统"}) while not stop_event.is_set(): traffic_lights = world.get_actors().filter("traffic.traffic_light") if not traffic_lights: @@ -840,13 +994,17 @@ def cycle_traffic_light_states(world, stop_event): logger.info("交通灯线程停止") -# ===================== 主函数 ====================== +# ===================== 主函数(V3.0终极版)===================== def main(): - global current_view_vehicle_id, vehicle_agents + global current_view_vehicle_id, vehicle_agents, perf_stats pygame.init() screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) - pygame.display.set_caption(f"CARLA多车辆视角({VEHICLE_COUNT}辆车)- V2.0 LiDAR感知 - 按1/2/3切换视角,按S切换分屏,按V切换俯视视角") + pygame.display.set_caption( + f"CARLA多车辆控制 V3.0 | 车辆数:{VEHICLE_COUNT} | " + f"按1/2/3切换视角 | S分屏 | V俯视 | ESC退出" + ) + # 初始化核心变量 client = None world = None map = None @@ -855,38 +1013,53 @@ def main(): show_split_screen = True show_top_view = False top_view_camera = None + top_view_surface = None - # 清理函数 + # 安全清理函数 def cleanup(): - print("\n开始清理资源...") + print("\n=== 开始安全清理资源 ===") tl_stop_event.set() + + # 等待交通灯线程结束 if tl_cycle_thread and tl_cycle_thread.is_alive(): tl_cycle_thread.join(timeout=2) + # 销毁俯视相机 if top_view_camera: - top_view_camera.stop() - top_view_camera.destroy() + safe_sensor_stop(top_view_camera, "俯视相机", logging.getLogger(__name__)) + safe_actor_destroy(top_view_camera, "俯视相机", logging.getLogger(__name__)) + # 销毁所有车辆 + global vehicle_agents for agent in vehicle_agents: - agent.destroy() + try: + agent.destroy() + except Exception as e: + print(f"销毁车辆{agent.vehicle_id}忽略异常:{str(e)[:50]}") + # 清理残留actor if world: for actor in world.get_actors(): if actor.type_id.startswith(("vehicle.", "walker.", "sensor.")): - if is_actor_alive(actor): - actor.destroy() + safe_actor_destroy(actor, actor.type_id, logging.getLogger(__name__)) + # 退出pygame pygame.quit() - print("资源清理完成") + print("=== 资源清理完成 ===") - # 注册退出回调 + # 注册退出处理 import atexit import signal atexit.register(cleanup) - signal.signal(signal.SIGINT, lambda sig, frame: sys.exit(0)) + + def signal_handler(sig, frame): + print("\n接收到退出信号,开始清理...") + cleanup() + sys.exit(0) + signal.signal(signal.SIGINT, signal_handler) try: - # 连接CARLA + # 连接CARLA服务器 client = carla.Client(CARLA_HOST, CARLA_PORT) client.set_timeout(CARLA_TIMEOUT) try: @@ -894,83 +1067,92 @@ def cleanup(): print("成功加载Town04地图") except Exception as e: world = client.get_world() - print(f"警告:Town04地图加载失败({e}),使用当前地图") + print(f"警告:Town04加载失败({e}),使用当前地图") map = world.get_map() # 清理残留演员 print("清理残留演员...") for actor in world.get_actors(): if actor.type_id.startswith(("vehicle.", "walker.", "sensor.")): - if is_actor_alive(actor): - actor.destroy() - time.sleep(3.0) - print("清理完成") - - # 自动获取地图的第一个出生点作为基准(避免手动坐标无效) - base_location = None - all_spawn_points = map.get_spawn_points() - if all_spawn_points: - base_location = all_spawn_points[0].location - print(f"使用地图第一个出生点作为基准:({base_location.x:.1f}, {base_location.y:.1f})") - else: - base_location = carla.Location(x=220.0, y=150.0, z=0.5) + safe_actor_destroy(actor, actor.type_id, logging.getLogger(__name__)) + time.sleep(2.0) - # 获取有效的出生点 - print(f"获取{VEHICLE_COUNT}个有效出生点...") + # 获取出生点 + base_location = map.get_spawn_points()[0].location if map.get_spawn_points() else carla.Location(x=220.0, y=150.0) + print(f"基准出生点:({base_location.x:.1f}, {base_location.y:.1f})") + valid_spawn_points = get_valid_spawn_points(map, VEHICLE_COUNT, base_location) for i, sp in enumerate(valid_spawn_points): - print(f" 出生点{i+1}:({sp.location.x:.1f},{sp.location.y:.1f})") + print(f"出生点{i+1}:({sp.location.x:.1f}, {sp.location.y:.1f})") # 生成车辆 - print(f"\n分步生成车辆(间隔{SPAWN_INTERVAL}秒)...") + print(f"\n分步生成{VEHICLE_COUNT}辆车辆...") for i in range(VEHICLE_COUNT): vehicle_model = VEHICLE_MODELS[i % len(VEHICLE_MODELS)] base_speed = BASE_SPEEDS[i % len(BASE_SPEEDS)] spawn_point = valid_spawn_points[i] try: - print(f"\n生成车辆{i+1}(车型:{vehicle_model})...") agent = VehicleAgent(world, map, i+1, spawn_point, vehicle_model, base_speed) - vehicle_agents.append(agent) - print(f"车辆{i+1}生成成功!已挂载LiDAR+碰撞传感器") + if agent.is_alive: + vehicle_agents.append(agent) + print(f"车辆{i+1}生成成功") + else: + print(f"车辆{i+1}生成失败") except Exception as e: - print(f"车辆{i+1}生成失败:{e}") + print(f"车辆{i+1}生成异常:{str(e)[:100]}") time.sleep(SPAWN_INTERVAL) if len(vehicle_agents) == 0: raise RuntimeError("无车辆生成成功,仿真终止") - print(f"\n共生成{len(vehicle_agents)}辆车辆!V2.0 LiDAR感知+障碍物避障功能已启用") - - # 创建全局俯视相机 + # 创建俯视相机 try: top_view_bp = world.get_blueprint_library().find("sensor.camera.rgb") top_view_bp.set_attribute("image_size_x", str(WINDOW_WIDTH)) top_view_bp.set_attribute("image_size_y", str(WINDOW_HEIGHT)) - top_view_bp.set_attribute("fov", str(90)) + top_view_bp.set_attribute("fov", "90") + top_view_transform = carla.Transform( vehicle_agents[0].vehicle.get_transform().location + carla.Location(z=50), carla.Rotation(pitch=-90) ) top_view_camera = world.spawn_actor(top_view_bp, top_view_transform) - top_view_surface = None - top_view_camera.listen(lambda image: globals().update({ - "top_view_surface": pygame.surfarray.make_surface( - np.swapaxes(np.array(image.raw_data).reshape((image.height, image.width, 4))[:, :, :3][:, :, ::-1], 0, 1) - ) - })) - except: - print("警告:无法创建俯视相机") + + def top_view_callback(image): + nonlocal top_view_surface + try: + array = np.frombuffer(image.raw_data, dtype=np.uint8).reshape((image.height, image.width, 4)) + array = array[:, :, :3][:, :, ::-1] + array = np.swapaxes(array, 0, 1) + top_view_surface = pygame.surfarray.make_surface(array) + except: + pass + + top_view_camera.listen(top_view_callback) + print("俯视相机创建成功") + except Exception as e: + print(f"俯视相机创建失败:{e}") + top_view_camera = None # 启动交通灯线程 tl_cycle_thread = threading.Thread(target=cycle_traffic_light_states, args=(world, tl_stop_event), daemon=True) tl_cycle_thread.start() - print("交通灯线程启动") + print("交通灯控制线程启动") # 主循环 clock = pygame.time.Clock() running = True + executor = ThreadPoolExecutor(max_workers=VEHICLE_COUNT) # 复用线程池,提升性能 + + print("\n=== 仿真开始 ===") + print("操作说明:") + print(" 1/2/3 - 切换单车辆视角") + print(" S - 切换分屏视角") + print(" V - 切换俯视视角") + print(" ESC - 退出仿真") + print("="*50) while running: # 事件处理 @@ -1002,68 +1184,54 @@ def cleanup(): # 清空屏幕 screen.fill((0, 0, 0)) - if show_top_view: - if top_view_surface: - screen.blit(top_view_surface, (0, 0)) + # 视角渲染 + if show_top_view and top_view_surface: + screen.blit(top_view_surface, (0, 0)) elif show_split_screen: - if len(vehicle_agents) == 1: - agent = vehicle_agents[0] - if agent.sensors.image_surface: - surface = pygame.transform.scale(agent.sensors.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT)) - screen.blit(surface, (0, 0)) - elif len(vehicle_agents) == 2: - agent1 = vehicle_agents[0] - agent2 = vehicle_agents[1] - - if agent1.sensors.image_surface: - surface1 = pygame.transform.scale(agent1.sensors.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT)) - screen.blit(surface1, (0, 0)) - - if agent2.sensors.image_surface: - surface2 = pygame.transform.scale(agent2.sensors.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT)) - screen.blit(surface2, (WINDOW_WIDTH//2, 0)) - elif len(vehicle_agents) >= 3: - agent1 = vehicle_agents[0] - agent2 = vehicle_agents[1] - agent3 = vehicle_agents[2] - - if agent1.sensors.image_surface: - surface1 = pygame.transform.scale(agent1.sensors.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) - screen.blit(surface1, (0, 0)) - - if agent2.sensors.image_surface: - surface2 = pygame.transform.scale(agent2.sensors.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) - screen.blit(surface2, (WINDOW_WIDTH//2, 0)) - - if agent3.sensors.image_surface: - surface3 = pygame.transform.scale(agent3.sensors.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT//2)) - screen.blit(surface3, (0, WINDOW_HEIGHT//2)) + # 分屏渲染 + if len(vehicle_agents) >= 1 and vehicle_agents[0].sensors and vehicle_agents[0].sensors.image_surface: + surf1 = pygame.transform.scale(vehicle_agents[0].sensors.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) + screen.blit(surf1, (0, 0)) + + if len(vehicle_agents) >= 2 and vehicle_agents[1].sensors and vehicle_agents[1].sensors.image_surface: + surf2 = pygame.transform.scale(vehicle_agents[1].sensors.image_surface, (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)) + screen.blit(surf2, (WINDOW_WIDTH//2, 0)) + + if len(vehicle_agents) >= 3 and vehicle_agents[2].sensors and vehicle_agents[2].sensors.image_surface: + surf3 = pygame.transform.scale(vehicle_agents[2].sensors.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT//2)) + screen.blit(surf3, (0, WINDOW_HEIGHT//2)) else: - target_agent = None - for agent in vehicle_agents: - if agent.vehicle_id == current_view_vehicle_id: - target_agent = agent - break - - if target_agent and target_agent.sensors.image_surface: - surface = pygame.transform.scale(target_agent.sensors.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT)) - screen.blit(surface, (0, 0)) - - # 更新车辆状态 - with ThreadPoolExecutor(max_workers=VEHICLE_COUNT) as executor: - futures = [executor.submit(agent.update) for agent in vehicle_agents] - for future in as_completed(futures): - try: - future.result() - except Exception as e: - print(f"车辆更新异常:{e}") + # 单车辆视角 + target_agent = next((a for a in vehicle_agents if a.vehicle_id == current_view_vehicle_id), None) + if target_agent and target_agent.sensors and target_agent.sensors.image_surface: + surf = pygame.transform.scale(target_agent.sensors.image_surface, (WINDOW_WIDTH, WINDOW_HEIGHT)) + screen.blit(surf, (0, 0)) + + # 更新车辆状态(复用线程池) + futures = [] + for agent in vehicle_agents: + if agent.is_alive: + futures.append(executor.submit(agent.update)) + + for future in as_completed(futures): + try: + future.result() + except Exception as e: + print(f"车辆更新异常:{str(e)[:50]}") + + # 性能监控 + perf_stats["frame_count"] += 1 + print_performance_stats() # 刷新屏幕 pygame.display.flip() clock.tick(30) + # 关闭线程池 + executor.shutdown(wait=True) + except Exception as e: - print(f"仿真异常:{e}") + print(f"\n仿真异常:{e}") traceback.print_exc() finally: cleanup()