From a1a6ffec0e2f211e704b7706c7901763c8ab6f4b Mon Sep 17 00:00:00 2001 From: Pan-j-l <43541417288@qq.com> Date: Thu, 18 Dec 2025 21:34:07 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E8=A7=84=E8=8C=83=20ROS=20=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=8C=85=E7=9A=84=E6=A0=B8=E5=BF=83=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ros/CMakeLists.txt | 37 +++++++++++++++++++ .../ros/package.xml | 24 ++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/self_driving_car_navigation/ros/CMakeLists.txt create mode 100644 src/self_driving_car_navigation/ros/package.xml diff --git a/src/self_driving_car_navigation/ros/CMakeLists.txt b/src/self_driving_car_navigation/ros/CMakeLists.txt new file mode 100644 index 0000000000..51673553ab --- /dev/null +++ b/src/self_driving_car_navigation/ros/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.0.2) +# 功能包名称(必须与package.xml中的name一致) +project(mmap_test) + +# -------------------------- 适配Python3.7.5 -------------------------- +# 指定你的虚拟环境Python解释器路径(关键!) +set(PYTHON_EXECUTABLE /home/pan-j-l/my_ros_project/venv_py37/bin/python3) +# 系统Python3.7的头文件路径(Ubuntu20.04默认路径,一般无需修改) +set(PYTHON_INCLUDE_DIR /usr/include/python3.7m) +# 系统Python3.7的库文件路径(Ubuntu20.04默认路径,一般无需修改) +set(PYTHON_LIBRARY /usr/lib/x86_64-linux-gnu/libpython3.7m.so) + +# -------------------------- 查找ROS依赖包 -------------------------- +find_package(catkin REQUIRED COMPONENTS + rospy + std_msgs + sensor_msgs + cv_bridge +) + +# -------------------------- 声明Catkin包 -------------------------- +catkin_package( + # 声明该包依赖的其他ROS包,供其他包引用 + CATKIN_DEPENDS rospy std_msgs sensor_msgs cv_bridge +) + +# -------------------------- 安装Python脚本 -------------------------- +# 将scripts目录下的Python脚本安装到ROS的devel目录,实现全局调用 +catkin_install_python(PROGRAMS + scripts/ros_test_node.py + DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +) + +# -------------------------- 包含目录 -------------------------- +include_directories( + ${catkin_INCLUDE_DIRS} +) \ No newline at end of file diff --git a/src/self_driving_car_navigation/ros/package.xml b/src/self_driving_car_navigation/ros/package.xml new file mode 100644 index 0000000000..2be4bc3fea --- /dev/null +++ b/src/self_driving_car_navigation/ros/package.xml @@ -0,0 +1,24 @@ + + + + mmap_test + 0.0.1 + ROS Noetic功能包:集成感知模块与决策模块的测试节点 + + pan-j-l + MIT + + + catkin + + + rospy + std_msgs + sensor_msgs + cv_bridge + + + + catkin + + \ No newline at end of file From ce343b90116217cd0daa5420b90424a7a39baa81 Mon Sep 17 00:00:00 2001 From: Pan-j-l <43541417288@qq.com> Date: Fri, 19 Dec 2025 10:56:33 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E9=87=8D=E6=9E=84ROS=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E4=B8=BA=E7=B1=BB=E5=BC=8F=E5=B0=81=E8=A3=85=EF=BC=8C=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=A8=A1=E6=8B=9F=E6=95=B0=E6=8D=AE=E7=89=88perceptio?= =?UTF-8?q?n=5Fdecision=5Fnode=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ros/perception_decision_node.py | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/self_driving_car_navigation/ros/perception_decision_node.py diff --git a/src/self_driving_car_navigation/ros/perception_decision_node.py b/src/self_driving_car_navigation/ros/perception_decision_node.py new file mode 100644 index 0000000000..df5031e0b8 --- /dev/null +++ b/src/self_driving_car_navigation/ros/perception_decision_node.py @@ -0,0 +1,106 @@ +#!/home/pan-j-l/my_ros_project/venv_py37/bin/python3 +# 适配你的虚拟环境Python解释器路径(关键!避免环境依赖问题) +import rospy +import torch +from geometry_msgs.msg import Twist +from sensor_msgs.msg import Image, Imu, LaserScan +# 导入自定义感知/决策模块(路径适配你的scripts/models目录) +from models.perception_module import PerceptionModule +from models.decision_module import DecisionModule + +class PerceptionDecisionNode: + def __init__(self): + # 节点初始化 + rospy.init_node('perception_decision_node', anonymous=True) + self.node_name = rospy.get_name() + rospy.loginfo(f"[{self.node_name}] 节点初始化成功!") + + # 加载参数(带默认值,可通过命令行/launch动态修改) + self.image_shape = rospy.get_param('~image_shape', (3, 128, 128)) + self.feature_dim = rospy.get_param('~feature_dim', 128) + self.cmd_dim = rospy.get_param('~cmd_dim', 2) + self.timer_freq = rospy.get_param('~timer_freq', 10.0) + + # 打印加载的参数(日志格式和你预期的一致) + rospy.loginfo(f"[{self.node_name}] 加载参数完成:") + rospy.loginfo(f" - 图像形状:{self.image_shape}") + rospy.loginfo(f" - 特征维度:{self.feature_dim}") + rospy.loginfo(f" - 指令维度:{self.cmd_dim}") + rospy.loginfo(f" - 定时器频率:{self.timer_freq}Hz") + + # 初始化感知/决策模块 + try: + self.perception = PerceptionModule( + image_input_shape=self.image_shape, + output_feature_dim=self.feature_dim + ) + self.decision = DecisionModule( + input_feature_dim=self.feature_dim, + output_cmd_dim=self.cmd_dim + ) + rospy.loginfo(f"[{self.node_name}] 感知/决策模块初始化成功!") + except Exception as e: + rospy.logfatal(f"[{self.node_name}] 模块初始化失败:{str(e)}") + exit(1) # 模块初始化失败直接退出 + + # 初始化控制指令发布者(发布到/cmd_vel话题) + self.cmd_pub = rospy.Publisher('/cmd_vel', Twist, queue_size=10) + rospy.loginfo(f"[{self.node_name}] 发布者初始化成功:/cmd_vel话题") + + # 初始化定时器(按设定频率执行数据处理逻辑) + self.timer = rospy.Timer( + rospy.Duration(1.0 / self.timer_freq), + self.timer_callback + ) + rospy.loginfo(f"[{self.node_name}] 定时器初始化成功:{self.timer_freq}Hz") + rospy.loginfo(f"[{self.node_name}] 节点开始运行(按Ctrl+C退出)...") + + def timer_callback(self, event): + """定时器回调:核心逻辑(模拟数据→感知→决策→发布指令)""" + # ========== 跳过传感器等待:直接生成模拟数据 ========== + # 模拟IMU数据(1个batch,6维:线加速度x/y/z + 角速度x/y/z) + self.imu_data = torch.randn(1, 6) + # 模拟图像数据(1个batch,对应image_shape参数) + self.image_data = torch.randn(1, *self.image_shape) + # 模拟激光雷达数据(1个batch,360个扫描点) + self.lidar_data = torch.randn(1, 360) + + try: + # 1. 感知模块:融合多传感器数据生成特征 + fused_feature = self.perception.process( + self.imu_data, + self.image_data, + self.lidar_data + ) + + # 2. 决策模块:根据融合特征生成控制指令 + control_cmd = self.decision.get_control_cmd(fused_feature) + + # 3. 封装并发布ROS控制指令(Twist消息) + twist_msg = Twist() + twist_msg.linear.x = control_cmd['linear_x'] # 线速度x + twist_msg.angular.z = control_cmd['angular_z'] # 角速度z + self.cmd_pub.publish(twist_msg) + + # 4. 打印日志(格式和你预期的完全一致) + rospy.loginfo( + f"[{self.node_name}] 生成控制指令:线速度x={control_cmd['linear_x']:.2f}m/s," + f"角速度z={control_cmd['angular_z']:.2f}rad/s" + ) + + except Exception as e: + # 异常捕获:避免单个错误导致节点崩溃 + rospy.logerr(f"[{self.node_name}] 指令生成失败:{str(e)}") + +if __name__ == '__main__': + try: + # 创建节点实例并运行 + node = PerceptionDecisionNode() + rospy.spin() + except rospy.ROSInterruptException: + # 捕获Ctrl+C中断,友好退出 + rospy.loginfo(f"[{rospy.get_name()}] 节点被中断,正常退出!") + except Exception as e: + # 捕获其他致命错误 + rospy.logfatal(f"节点启动失败:{str(e)}") + exit(1) \ No newline at end of file From aaeaaf7374d3a25253f6a2e8ff81d0ba5327174f Mon Sep 17 00:00:00 2001 From: Pan-j-l <43541417288@qq.com> Date: Fri, 19 Dec 2025 15:31:51 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E6=8F=90=E4=BA=A4.launch=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E5=AE=9E=E7=8E=B0ROS=E8=8A=82=E7=82=B9=E4=B8=80?= =?UTF-8?q?=E9=94=AE=E5=90=AF=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ros/launch/perception_decision.launch | 23 +++++++++++++++++++ .../ros/perception_decision_node.py | 13 +++++++---- 2 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 src/self_driving_car_navigation/ros/launch/perception_decision.launch diff --git a/src/self_driving_car_navigation/ros/launch/perception_decision.launch b/src/self_driving_car_navigation/ros/launch/perception_decision.launch new file mode 100644 index 0000000000..f176b67d96 --- /dev/null +++ b/src/self_driving_car_navigation/ros/launch/perception_decision.launch @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/self_driving_car_navigation/ros/perception_decision_node.py b/src/self_driving_car_navigation/ros/perception_decision_node.py index df5031e0b8..be0990594d 100644 --- a/src/self_driving_car_navigation/ros/perception_decision_node.py +++ b/src/self_driving_car_navigation/ros/perception_decision_node.py @@ -15,11 +15,14 @@ def __init__(self): self.node_name = rospy.get_name() rospy.loginfo(f"[{self.node_name}] 节点初始化成功!") - # 加载参数(带默认值,可通过命令行/launch动态修改) - self.image_shape = rospy.get_param('~image_shape', (3, 128, 128)) - self.feature_dim = rospy.get_param('~feature_dim', 128) - self.cmd_dim = rospy.get_param('~cmd_dim', 2) - self.timer_freq = rospy.get_param('~timer_freq', 10.0) + + image_shape_str = rospy.get_param('~image_shape', "[3, 128, 128]") # 读字符串 + self.image_shape = tuple(map(int, image_shape_str.strip('[]').replace(' ', '').split(','))) # 核心转换 + + # 2. 处理数值型参数(字符串→int/float) + self.feature_dim = int(rospy.get_param('~feature_dim', 128)) # 转整数 + self.cmd_dim = int(rospy.get_param('~cmd_dim', 2)) # 转整数 + self.timer_freq = float(rospy.get_param('~timer_freq', 10.0)) # 转浮点数 # 打印加载的参数(日志格式和你预期的一致) rospy.loginfo(f"[{self.node_name}] 加载参数完成:") From 484104fea2dc31f4f8a11189f697c42caecb6f00 Mon Sep 17 00:00:00 2001 From: Pan-j-l <43541417288@qq.com> Date: Sat, 20 Dec 2025 10:35:10 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=88=91=E7=9A=84README.?= =?UTF-8?q?md=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/self_driving_car_navigation/README.md | 188 +++++++++++----------- 1 file changed, 92 insertions(+), 96 deletions(-) diff --git a/src/self_driving_car_navigation/README.md b/src/self_driving_car_navigation/README.md index 8fb3467c36..36bca70ea0 100644 --- a/src/self_driving_car_navigation/README.md +++ b/src/self_driving_car_navigation/README.md @@ -1,7 +1,11 @@ -# 自动驾驶仿真学习环境(基于Carla与Python3.7) +# 自动驾驶仿真学习环境(基于Carla与Python3.7,集成ROS) + +本项目基于[MMAP-DRL-Nav](https://github.com/CV4RA/MMAP-DRL-Nav)的核心架构,实现了一套“多模态感知+深度强化学习(DRL)+ROS部署”的自动驾驶导航系统。项目分为**Win11环境下的机器学习核心模块**(模型训练、CARLA仿真交互)和**Ubuntu ROS环境下的节点封装模块**(实时感知决策),可完成从多传感器数据融合到车辆控制指令输出的全流程,适用于自动驾驶导航算法的学习、验证与二次开发。 + + +## 项目核心目标 +通过融合视觉图像、激光雷达(LiDAR)、IMU等多源异构传感器数据,利用注意力机制完成特征融合,结合深度强化学习(DQN)训练决策模型,最终通过ROS节点输出车辆线速度/角速度控制指令,在CARLA仿真环境中验证“避障、路径跟踪、目标导航”等核心导航能力。 -## 项目概述 -本项目构建了基于Carla 0.9.11仿真平台和Python 3.7的自动驾驶学习环境,适用于车辆控制、场景感知、路径规划等自动驾驶相关算法的学习与实践。通过VSCode开发环境实现代码编写、调试与运行的一体化流程,支持从基础车辆控制到复杂场景仿真的完整学习路径。 ## 环境准备 @@ -25,111 +29,103 @@ pip install numpy opencv-python matplotlib vscode-debugpy 2. 安装[VSCode](https://code.visualstudio.com/)并配置Python 3.7解释器 3. 推荐插件:Python、Pylance、Code Runner(提升开发效率) -## 项目结构 - -| 文件名 | 功能描述 | -|-------------------|--------------------------------------------------------------| -| `main.py` | 核心程序入口,实现Carla客户端连接、世界初始化与主循环控制 | -| `vehicle_control.py`| 车辆控制模块,实现油门、刹车、转向等基础控制逻辑 | -| `scene_generation.py`| 场景生成工具,支持随机障碍物、天气变化与交通参与者生成 | -| `sensor_manager.py`| 传感器管理模块,处理摄像头、激光雷达等数据采集与解析 | -| `utils.py` | 通用工具函数,包含坐标转换、数据可视化等辅助功能 | -| `config.yaml` | 配置文件,存储仿真参数(如帧率、传感器类型、车辆模型等) | -| `README.md` | 项目说明文档 | - -## 核心功能 - -### 1. 基础车辆控制(main.py & vehicle_control.py) -- 客户端连接管理:自动连接Carla服务器,支持断开重连机制 -- 多车辆控制:同时控制多辆自动驾驶车辆,实现编队行驶模拟 -- 控制模式切换:支持手动控制(键盘)与自动控制(程序)模式切换 -- 状态实时反馈:在VSCode终端输出车辆速度、位置等关键信息 - -```python -# 车辆控制逻辑示例 -import carla - -# 连接到Carla服务器 -client = carla.Client('localhost', 2000) -client.set_timeout(10.0) -world = client.get_world() - -# 获取车辆控制器 -vehicle = world.get_actors().filter('*vehicle*')[0] -control = carla.VehicleControl() - -# 设定前进指令(油门0.5,转向0) -control.throttle = 0.5 -control.steer = 0.0 -vehicle.apply_control(control) -``` +# 自动驾驶车辆导航系统 -### 2. 场景仿真与传感器(scene_generation.py & sensor_manager.py) -- 动态场景生成:支持随机天气(雨、雾、时间)、障碍物与交通灯配置 -- 多传感器集成:摄像头(RGB/深度)、激光雷达、毫米波雷达数据采集 -- 数据同步存储:传感器数据与车辆状态时间戳同步,便于离线分析 -- VSCode调试支持:断点调试传感器数据处理流程,直观查看数据格式 - -```python -# 传感器配置示例 -def setup_camera(world, vehicle): - camera_bp = world.get_blueprint_library().find('sensor.camera.rgb') - camera_bp.set_attribute('image_size_x', '1280') - camera_bp.set_attribute('image_size_y', '720') - # 安装在车辆前方 - transform = carla.Transform(carla.Location(x=1.5, z=2.4)) - camera = world.spawn_actor(camera_bp, transform, attach_to=vehicle) - # 定义数据回调函数 - camera.listen(lambda image: process_image(image)) - return camera -``` -### 3. 学习任务支持 -- 路径跟踪练习:预设参考路径,实现PID等控制算法跟踪 -- 避障场景训练:生成动态障碍物,练习碰撞检测与规避逻辑 -- 数据采集工具:批量采集不同场景下的传感器数据,用于模型训练 +## 核心模块 -## 使用方法 +### 1. 感知模块 +- `perception_module.py`:处理多传感器输入(IMU、相机图像、激光雷达)的神经网络模块,包括: + - 用于视觉场景理解的语义分割网络 + - 基于1D卷积的激光雷达特征提取 + - 处理激光雷达数据的障碍物检测子网络 + - 输出场景信息、分割结果、里程计、障碍物和边界特征 -1. 启动Carla服务器: -```bash -# 在Carla安装目录下执行 -./CarlaUE4.sh # Linux/Mac -CarlaUE4.exe # Windows -``` +- `ros/perception_module.py`:ROS优化的感知模块,具有: + - 传感器数据扁平化与拼接处理 + - 基于线性层的特征融合 + - 使用`cv_bridge`实现ROS图像与张量的转换 -2. 基础车辆控制示例: -```bash -python main.py --mode manual # 手动控制模式 -python main.py --mode auto # 自动控制模式 -``` -3. 场景仿真运行: -```bash -python scene_generation.py --weather rain --obstacles 5 -``` +### 2. 跨域注意力融合 +- `attention_module.py`:实现`CrossDomainAttention`类用于多模态特征融合: + - 将异质输入(4D/2D张量)调整为统一的3D格式`[batch, seq_len, features]` + - 通过动态线性层对齐特征维度 + - 使用带残差连接和层归一化的多头自注意力块 + - 拼接并融合场景、分割、里程计、障碍物和边界数据的特征 -4. 传感器数据采集: -```bash -python sensor_manager.py --record --output ./data -``` -### VSCode开发提示 -- 按`F5`启动调试模式(需配置`.vscode/launch.json`) -- 使用Code Runner插件(右键`Run Code`)快速执行单文件 -- 推荐使用VSCode的Jupyter插件进行分步调试与数据可视化 +### 3. 决策模块 +- `decision_module.py`:核心决策网络,具有: + - 生成动作分布(转向角、油门)的策略网络 + - 从融合特征估计状态价值的价值网络 + - 通过均值池化聚合时序特征 + +- `ros/decision_module.py`:兼容ROS的决策模块: + - 用于生成控制指令的序列线性层 + - 将输出限制在安全范围内(线速度:0~2m/s,角速度:-1~1rad/s) + - 返回字典形式的控制指令,便于转换为ROS消息 + + +### 4. 自评估梯度模型 +- `sagm.py`:实现`SelfAssessmentGradientModel`用于动作价值估计: + - 处理拼接的状态-动作特征的全连接网络 + - 输出Q值以评估动作质量 + - 与actor-critic框架集成,用于强化学习更新 + + +### 5. 系统集成 +- `main.py`:定义`IntegratedSystem`类,整合所有模块: + - 端到端流程:多模态输入→感知→注意力融合→决策→自评估 + - 支持MSE损失训练(策略回归+价值估计+Q值匹配) + - 包含带设备管理(CPU/GPU)的训练/测试循环 + + +## ROS集成 +- `ros/CMakeLists.txt`:配置ROS功能包编译: + - 指定Python 3.7环境及依赖(rospy、std_msgs、sensor_msgs、cv_bridge) + - 将Python脚本安装到ROS环境 + - 设置Catkin的包含目录 + +- `ros/package.xml`:ROS功能包元数据,包含依赖和维护者信息 + +- `ros/perception_decision_node.py`:主ROS节点: + - 订阅传感器话题(模拟或真实数据) + - 按指定频率(默认10Hz)运行感知/决策流程 + - 发布控制指令到`/cmd_vel`话题 + +- `ros/ros_test_node.py`:模块验证测试节点: + - 使用模拟传感器数据验证感知/决策工作流 + - 记录特征形状和控制指令日志 + + +## 仿真与训练 +- `carla_environment.py`:CARLA模拟器接口(兼容Gym): + - 管理模拟器连接、车辆生成和传感器设置 + - 配置交通管理器(兼容CARLA 0.9.11)以控制NPC行为 + - 提供观测空间(图像、激光雷达、IMU)和动作接口 + +- `dataloader.py`:数据加载工具: + - `CarlaDataset`类用于处理模拟的CARLA数据 + - 生成图像、激光雷达、IMU和动作数据的批次 + +- `dqn_agent.py`:DQN强化学习实现: + - 经验回放缓冲区 + - ε-贪婪动作选择 + - 带伽马折扣因子的目标网络更新 + +- `run_simulation.py`:CARLA中平滑 spectator 相机控制工具,使用线性插值实现 -## 参数调整指南 -| 参数 | 调整范围 | 效果说明 | -|-------------------|----------|----------------------------------| -| `throttle_gain` | 0.1~1.0 | 增大会提高加速响应,过大会导致打滑 | -| `sensor_fps` | 10~60 | 提高值增加数据精度(增加计算量) | -| `obstacle_density`| 0~20 | 增大会增加场景复杂度 | -| `simulation_delta_seconds`| 0.01~0.1 | 减小值提高仿真精度(降低运行速度)| +## 核心工作流程 +1. **感知**:多传感器数据(图像/激光雷达/IMU)被处理为结构化特征 +2. **融合**:跨域注意力将异质特征合并为统一表示 +3. **决策**:策略网络从融合特征生成控制指令 +4. **评估**:SAGM评估动作质量以指导学习 +5. **ROS集成**:指令发布到车辆控制话题,实现实时执行 ## 参考资料 - [Carla 0.9.11官方文档](https://carla.readthedocs.io/en/0.9.11/) - [Python 3.7官方文档](https://docs.python.org/3.7/) - [VSCode Python开发指南](https://code.visualstudio.com/docs/languages/python) -- [Carla自动驾驶教程](https://carla.readthedocs.io/en/latest/tutorials/) \ No newline at end of file +- [项目来源](https://github.com/CV4RA/MMAP-DRL-Nav) \ No newline at end of file From ce801452e079cc5490e9e406fb67994e95262098 Mon Sep 17 00:00:00 2001 From: Pan-j-l <43541417288@qq.com> Date: Wed, 24 Dec 2025 14:17:59 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E5=90=88=E5=B9=B6=20run=5Fsimulation=20?= =?UTF-8?q?=E4=B8=8E=20carla=5Fenvironment=20=E5=B9=B6=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E8=AD=A6=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{carla_environment.py => run_carla.py} | 418 +++++++++++------- .../run_simulation.py | 121 ----- 2 files changed, 263 insertions(+), 276 deletions(-) rename src/self_driving_car_navigation/{carla_environment.py => run_carla.py} (57%) delete mode 100644 src/self_driving_car_navigation/run_simulation.py diff --git a/src/self_driving_car_navigation/carla_environment.py b/src/self_driving_car_navigation/run_carla.py similarity index 57% rename from src/self_driving_car_navigation/carla_environment.py rename to src/self_driving_car_navigation/run_carla.py index 5d6cc466b4..480ac95f99 100644 --- a/src/self_driving_car_navigation/carla_environment.py +++ b/src/self_driving_car_navigation/run_carla.py @@ -4,8 +4,11 @@ import time import sys import random +import pygame +import os from queue import Queue from gym import spaces +from collections import deque class CarlaEnvironment(gym.Env): def __init__(self): @@ -21,7 +24,7 @@ def __init__(self): self.lidar = None self.imu = None self.spawn_points = [] - # TM核心配置(0.9.11专用) + # TM核心配置(0.9.11专用 - 仅保留全局配置) self.traffic_manager = None self.tm_port = 8000 self.tm_seed = 0 # 固定TM种子,保证行为一致 @@ -29,13 +32,10 @@ def __init__(self): self.image_queue = Queue(maxsize=1) self.lidar_queue = Queue(maxsize=1) self.imu_queue = Queue(maxsize=1) - # 车辆控制相关 - self.vehicle_control = carla.VehicleControl() - self.last_steer = 0.0 - # 连接CARLA + # 连接CARLA(强化严格同步模式) self._connect_carla() - # 初始化TM(适配0.9.11 API) + # 初始化TM(仅用0.9.11确实验证的全局API) self._init_traffic_manager() # 定义观测空间 self.observation_space = gym.spaces.Dict({ @@ -43,33 +43,38 @@ def __init__(self): 'lidar_distances': gym.spaces.Box(low=0, high=50, shape=(360,), dtype=np.float32), 'imu': gym.spaces.Box(low=-10, high=10, shape=(6,), dtype=np.float32) }) - # 获取有效生成点(仅保留道路上的点,修改:补充到60个以适配60辆NPC) + # 获取有效生成点(仅保留道路上的点,补充到60个以适配60辆NPC) self.spawn_points = self._get_valid_road_spawn_points() print(f"[场景初始化] 有效道路生成点数量: {len(self.spawn_points)}") sys.stdout.flush() def _connect_carla(self): - """连接CARLA(增加版本检查)""" + """连接CARLA(极致同步配置)""" retry_count = 3 for i in range(retry_count): try: print(f"[CARLA连接] 尝试第{i+1}次连接(localhost:2000)...") self.client = carla.Client('localhost', 2000) - self.client.set_timeout(20.0) + self.client.set_timeout(30.0) self.world = self.client.get_world() self.blueprint_library = self.world.get_blueprint_library() - # 同步模式配置(0.9.11最优参数) + # 终极同步配置(消除所有帧差) self.settings = self.world.get_settings() - self.settings.synchronous_mode = True # 启用同步模式 - self.settings.fixed_delta_seconds = 1/20 # 降低帧率,提升稳定性 - self.settings.no_rendering_mode = False # 必须开启渲染,否则TM可能失效 + self.settings.synchronous_mode = True + self.settings.fixed_delta_seconds = 1/60 # 60FPS仿真,物理更平滑 + self.settings.no_rendering_mode = False + self.settings.substepping = True + self.settings.max_substep_delta_time = 0.003 # 更小的子步(3ms),过滤微米级抖动 + self.settings.max_substeps = 30 self.world.apply_settings(self.settings) + time.sleep(1.5) # 延长等待时间,确保配置完全生效 + current_settings = self.world.get_settings() + print(f"[同步模式] 生效状态: {current_settings.synchronous_mode}, 固定帧间隔: {current_settings.fixed_delta_seconds}s") # 双重清理 self._clear_all_non_ego_actors() - time.sleep(0.5) - self.world.tick() # 显式推进仿真帧 + self.world.tick() self._clear_all_non_ego_actors() # 版本检查 @@ -85,84 +90,31 @@ def _connect_carla(self): time.sleep(2) def _init_traffic_manager(self): - """初始化交通管理器(严格适配0.9.11 API)""" + """初始化交通管理器(强制同步)""" self.traffic_manager = self.client.get_trafficmanager(self.tm_port) - # 全局TM参数(0.9.11支持的全局方法) - self.traffic_manager.set_global_distance_to_leading_vehicle(2.0) # 跟车距离(float) - self.traffic_manager.set_synchronous_mode(True) # 同步模式(bool) - self.traffic_manager.set_random_device_seed(self.tm_seed) # 随机种子(int) - self.traffic_manager.global_percentage_speed_difference(0.0) # 全速行驶(float) - # 混合物理模式(0.9.11支持) + self.traffic_manager.set_global_distance_to_leading_vehicle(2.0) + self.traffic_manager.set_synchronous_mode(True) + self.traffic_manager.set_random_device_seed(self.tm_seed) + self.traffic_manager.global_percentage_speed_difference(0.0) self.traffic_manager.set_hybrid_physics_mode(True) self.traffic_manager.set_hybrid_physics_radius(50.0) - print("[TM配置] 交通管理器初始化完成(适配0.9.11 API)") + print("[TM配置] 交通管理器同步模式已启用") def _set_actor_tm_params(self, actor): - """为单个Actor设置TM参数(核心修改:提升主车辆速度,遵守交通规则)""" + """彻底移除所有Actor级TM配置""" if not self._is_actor_alive(actor): return - try: - # 关键修改:设置为0%忽略交通规则,使车辆遵守红绿灯和标志 - self.traffic_manager.ignore_lights_percentage(actor, 0.0) # 不忽略交通灯 - self.traffic_manager.ignore_signs_percentage(actor, 0.0) # 不忽略交通标志 - self.traffic_manager.ignore_walkers_percentage(actor, 0.0) # 不忽略行人 - # 允许变道(float百分比) - self.traffic_manager.allow_vehicle_lane_change(actor, 100.0) - - # 核心修改:提升速度参数(区分主车辆和NPC) - if actor.attributes.get('role_name') == 'ego_vehicle': - # 主车辆:速度限制因子提升到1.8(超速80%),最高速度设为100km/h - self.traffic_manager.set_speed_limit_factor(actor, 1.8) - self.traffic_manager.set_speed_limit(actor, 100.0) - else: - # NPC车辆:保持原有参数(可选:也可适当提升) - self.traffic_manager.set_speed_limit_factor(actor, 1.2) - self.traffic_manager.set_speed_limit(actor, 60.0) - - except Exception as e: - print(f"[TM Actor配置警告] Actor ID {actor.id}: {str(e)}") - - def _update_vehicle_steering(self, vehicle): - """更新车辆转向角,使轮胎转动跟随车辆运动""" - if not self._is_actor_alive(vehicle): - return - - try: - # 获取车辆当前速度和变换 - velocity = vehicle.get_velocity() - speed = np.linalg.norm([velocity.x, velocity.y, velocity.z]) - - # 获取车辆的物理控制 - physics_control = vehicle.get_physics_control() - - # 根据自动驾驶的控制指令获取转向角 - control = vehicle.get_control() - - # 平滑转向角变化,避免突变 - steer_factor = 0.3 # 转向灵敏度 - self.last_steer = self.last_steer * (1 - steer_factor) + control.steer * steer_factor - - # 应用转向角到所有车轮 - for wheel in physics_control.wheels: - if wheel.type == carla.WheelType.Front: # 只控制前轮转向 - wheel.steer_angle = self.last_steer * 70 # 70度最大转向角 - - # 应用物理控制 - vehicle.apply_physics_control(physics_control) - - except Exception as e: - print(f"[车辆转向更新错误] {str(e)}") + pass def _get_valid_road_spawn_points(self): - """过滤生成点:仅保留道路网络上的有效点(修改:补充到60个以适配60辆NPC)""" + """过滤生成点:仅保留道路网络上的有效点""" map = self.world.get_map() valid_points = [] for sp in map.get_spawn_points(): - # 获取生成点对应的道路点 waypoint = map.get_waypoint(sp.location) - if waypoint and waypoint.road_id != -1: # 确保在道路上 + if waypoint and waypoint.road_id != -1: valid_points.append(sp) - # 若有效点不足60,补充随机道路点(修改:从30改为60) + # 补充到60个 if len(valid_points) < 60: for _ in range(60 - len(valid_points)): random_loc = self.world.get_random_location_from_navigation() @@ -216,13 +168,12 @@ def _clear_all_non_ego_actors(self): if "has been destroyed" not in str(e) and "not found" not in str(e): print(f"[销毁Actor警告] {str(e)}") - self.world.tick() # 显式推进仿真帧 + self.world.tick() print(f"[清理] 车辆{cleared_count['vehicle']} | 自行车{cleared_count['bicycle']} | 静态车辆{cleared_count['static_vehicle']}") def process_image(self, image): - """处理摄像头数据 - 修复frame属性访问问题""" + """处理摄像头数据""" try: - # 检查是否为有效的图像对象 if not hasattr(image, 'raw_data') or not hasattr(image, 'height') or not hasattr(image, 'width'): print("[图像处理错误] 无效的图像数据") return @@ -236,9 +187,8 @@ def process_image(self, image): print(f"[图像处理错误] {str(e)}") def process_lidar(self, data): - """处理激光雷达数据 - 修复frame属性访问问题""" + """处理激光雷达数据""" try: - # 检查是否为有效的激光雷达数据 if not hasattr(data, 'raw_data'): print("[激光雷达处理错误] 无效的激光雷达数据") return @@ -261,9 +211,8 @@ def process_lidar(self, data): print(f"[激光雷达处理错误] {str(e)}") def process_imu(self, data): - """处理IMU数据 - 修复frame属性访问问题""" + """处理IMU数据""" try: - # 检查是否为有效的IMU数据 if not hasattr(data, 'accelerometer') or not hasattr(data, 'gyroscope'): print("[IMU处理错误] 无效的IMU数据") return @@ -279,7 +228,7 @@ def process_imu(self, data): print(f"[IMU处理错误] {str(e)}") def reset(self): - """重置环境(修改:生成主车辆+60辆NPC)""" + """重置环境""" self.close() time.sleep(1.0) self._clear_all_non_ego_actors() @@ -287,28 +236,44 @@ def reset(self): if self.vehicle: self._spawn_sensors() - # 启用物理模拟 self.vehicle.set_simulate_physics(True) - # 主车辆自动驾驶(延迟绑定+TM参数配置) + # 优化车辆物理参数(彻底消除微小抖动) + self._optimize_vehicle_physics() time.sleep(0.5) - self.vehicle.set_autopilot(True, self.tm_port) # 启用自动控制指令输入 - self._set_actor_tm_params(self.vehicle) # 为主车辆配置TM参数 - # 生成NPC车辆(核心修改:从20辆改为60辆) + self.vehicle.set_autopilot(True, self.tm_port) self._spawn_npcs(60) self._clear_all_non_ego_actors() - # 多次同步,确保物理生效 - for _ in range(5): - self.world.tick() # 显式推进仿真帧 - time.sleep(0.2) + # 多次同步,物理稳定(延长时间) + for _ in range(15): + self.world.tick() + time.sleep(0.05) print(f"[环境重置] 完成,主车辆1辆,NPC车辆{len(self.npc_vehicles)}辆") return self.get_observation() + def _optimize_vehicle_physics(self): + """终极物理优化:彻底消除车辆微小震动""" + if not self._is_actor_alive(self.vehicle): + return + try: + physics_control = self.vehicle.get_physics_control() + # 进一步降低悬挂刚度(从1000→800),减少微米级震动 + for wheel in physics_control.wheels: + wheel.suspension_stiffness = 800.0 # 更低的刚度,过滤路面微小颠簸 + wheel.suspension_damping = 250.0 # 更高的阻尼,快速衰减震动 + wheel.suspension_compression = 300.0 # 增加压缩阻尼 + wheel.max_suspension_travel = 0.08 # 减小悬挂行程,避免过度晃动 + wheel.friction_slip = 1.2 # 增加轮胎抓地力,减少打滑抖动 + self.vehicle.apply_physics_control(physics_control) + print("[物理优化] 车辆悬挂+轮胎参数已终极优化,消除微小震动") + except Exception as e: + print(f"[物理优化警告] {str(e)}") + def _spawn_vehicle(self): - """生成主车辆(特斯拉Model3)""" + """生成主车辆""" self._safe_destroy_actor(self.vehicle) - self.world.tick() # 显式推进仿真帧 + self.world.tick() vehicle_bp = self.blueprint_library.find('vehicle.tesla.model3') vehicle_bp.set_attribute('color', '255,0,0') @@ -321,19 +286,18 @@ def _spawn_vehicle(self): for spawn_point in self.spawn_points[:10]: self.vehicle = self.world.try_spawn_actor(vehicle_bp, spawn_point) if self.vehicle: - self.vehicle.set_simulate_physics(True) # 启用物理模拟 + self.vehicle.set_simulate_physics(True) print(f"[主车辆生成] 成功(ID: {self.vehicle.id})") return raise RuntimeError("主车辆生成失败,请重启CARLA") def _spawn_npcs(self, count): - """生成NPC车辆(0.9.11关键:生成后延迟启用自动驾驶+单个Actor配置TM)""" + """生成NPC车辆""" if not self.spawn_points: print("[NPC生成] 无可用生成点") return - # 过滤主车辆附近的点(修改:将距离从20米改为15米,释放更多生成点) ego_transform = self.vehicle.get_transform() available_spawn_points = [] for sp in self.spawn_points: @@ -341,21 +305,19 @@ def _spawn_npcs(self, count): sp.location.x - ego_transform.location.x, sp.location.y - ego_transform.location.y ]) - if distance > 15.0: # 从20米→15米,增加可用生成点数量 + if distance > 15.0: available_spawn_points.append(sp) if len(available_spawn_points) < count: count = len(available_spawn_points) print(f"[NPC生成] 可用点不足,生成{count}辆") - # 随机车辆蓝图 vehicle_bps = [bp for bp in self.blueprint_library.filter('vehicle.*') if bp.has_attribute('color') and bp.id != 'vehicle.tesla.model3'] random.shuffle(vehicle_bps) if not vehicle_bps: vehicle_bps = self.blueprint_library.filter('vehicle.*') - # 生成NPC spawned_count = 0 for i, spawn_point in enumerate(random.sample(available_spawn_points, count)): bp = vehicle_bps[i % len(vehicle_bps)] @@ -366,19 +328,16 @@ def _spawn_npcs(self, count): npc_vehicle = self.world.try_spawn_actor(bp, spawn_point) if npc_vehicle: - npc_vehicle.set_simulate_physics(True) # 启用物理模拟 - # 0.9.11核心:生成后延迟0.1秒启用自动驾驶,让物理模拟生效 - time.sleep(0.1) - npc_vehicle.set_autopilot(True, self.tm_port) # 启用自动控制指令输入 - # 为单个NPC配置TM参数(关键:解决不动问题) - self._set_actor_tm_params(npc_vehicle) + npc_vehicle.set_simulate_physics(True) + time.sleep(0.05) + npc_vehicle.set_autopilot(True, self.tm_port) self.npc_vehicles.append(npc_vehicle) spawned_count += 1 if spawned_count % 5 == 0: - self.world.tick() # 显式推进仿真帧 + self.world.tick() - self.world.tick() # 显式推进仿真帧 + self.world.tick() print(f"[NPC生成] 成功生成{spawned_count}辆(目标:{count}辆)") def _spawn_sensors(self): @@ -400,8 +359,8 @@ def _spawn_sensors(self): lidar_bp = self.blueprint_library.find('sensor.lidar.ray_cast') lidar_bp.set_attribute('channels', '32') lidar_bp.set_attribute('range', '50') - lidar_bp.set_attribute('points_per_second', '100000') - lidar_bp.set_attribute('rotation_frequency', '10') + lidar_bp.set_attribute('points_per_second', '200000') + lidar_bp.set_attribute('rotation_frequency', '60') # 与仿真帧率一致 self.lidar = self.world.spawn_actor( lidar_bp, carla.Transform(carla.Location(x=0.0, z=2.0)), attach_to=self.vehicle ) @@ -413,12 +372,12 @@ def _spawn_sensors(self): imu_bp, carla.Transform(), attach_to=self.vehicle ) self.imu.listen(self.process_imu) - print("[传感器] 初始化完成") + print("[传感器] 初始化完成(帧率与仿真同步)") def get_observation(self): """获取观测数据""" while self.image_queue.empty() or self.lidar_queue.empty() or self.imu_queue.empty(): - time.sleep(0.01) + time.sleep(0.001) return { 'image': self.image_queue.get(), 'lidar_distances': self.lidar_queue.get(), @@ -439,33 +398,22 @@ def get_obstacle_directions(self, lidar_distances): } def step(self, action=None): - """环境交互步骤(0.9.11同步关键)""" - # 同步世界(TM会自动同步) - self.world.tick() # 显式推进仿真帧 - - # 更新主车辆轮胎转向 - if self._is_actor_alive(self.vehicle): - self._update_vehicle_steering(self.vehicle) - - # 更新NPC车辆轮胎转向 - for npc in self.npc_vehicles: - if self._is_actor_alive(npc): - self._update_vehicle_steering(npc) + """环境交互步骤""" + self.world.tick() # 清理无效NPC self.npc_vehicles = [npc for npc in self.npc_vehicles if self._is_actor_alive(npc)] - # 打印NPC速度(调试用) - if random.random() < 0.1: # 10%概率打印 + # 打印NPC速度(调试用,降低频率) + if random.random() < 0.02: for npc in self.npc_vehicles[:1]: try: velocity = npc.get_velocity() - speed = np.linalg.norm([velocity.x, velocity.y, velocity.z]) * 3.6 # m/s → km/h + speed = np.linalg.norm([velocity.x, velocity.y, velocity.z]) * 3.6 print(f"[NPC速度] ID:{npc.id} 速度:{speed:.1f}km/h") except Exception: pass - # 获取观测 observation = self.get_observation() reward = 1.0 done = False @@ -473,25 +421,19 @@ def step(self, action=None): def close(self): """清理资源""" - # 停止TM if self.traffic_manager: try: self.traffic_manager.set_synchronous_mode(False) except Exception as e: print(f"[TM清理警告] {str(e)}") - # 销毁传感器 for sensor in [self.camera, self.lidar, self.imu]: self._safe_destroy_actor(sensor) - # 销毁NPC for npc in self.npc_vehicles: self._safe_destroy_actor(npc) self.npc_vehicles = [] - # 销毁主车辆 self._safe_destroy_actor(self.vehicle) self.vehicle = None - # 最后清理 self._clear_all_non_ego_actors() - # 清空队列 for q in [self.image_queue, self.lidar_queue, self.imu_queue]: while not q.empty(): q.get() @@ -499,30 +441,196 @@ def close(self): if self.settings: try: self.settings.synchronous_mode = False + self.settings.substepping = False self.world.apply_settings(self.settings) except Exception as e: print(f"[世界设置恢复警告] {str(e)}") print("[资源清理] 所有资源已销毁") + def init_spectator_smoother(self, window_size=15): + """初始化镜头平滑器(增大滑动窗口到15帧)""" + self.vehicle_pose_buffer = deque(maxlen=window_size) # 15帧缓存,过滤更多高频抖动 + self.window_size = window_size + # 初始化加权平均权重(近期帧权重更高,兼顾平滑和响应) + self.weights = np.linspace(0.1, 1.0, window_size) # 权重从0.1→1.0递增 + self.weights /= np.sum(self.weights) # 归一化 + + def update_spectator_ultra_smooth(self, spectator): + """ + 终极平滑镜头更新:加权滑动平均+微米级死区+完全锁定旋转 + """ + if not self._is_actor_alive(self.vehicle): + return + + # 1. 获取同步帧快照的车辆位姿(仅用快照,杜绝异步) + snapshot = self.world.get_snapshot() + vehicle_snapshot = snapshot.find(self.vehicle.id) + if not vehicle_snapshot: + return + current_pose = vehicle_snapshot.get_transform() + + # 2. 初始化关键变量 + avg_yaw = current_pose.rotation.yaw + avg_pose = current_pose + + # 3. 加入滑动缓存并计算「加权平均」位姿(核心优化) + self.vehicle_pose_buffer.append(current_pose) + if len(self.vehicle_pose_buffer) >= self.window_size: + # 提取缓存中的位姿 + poses = list(self.vehicle_pose_buffer) + count = len(poses) + + # 加权平均位置(近期帧权重更高) + avg_loc = carla.Location() + for i in range(count): + weight = self.weights[i] + avg_loc.x += poses[i].location.x * weight + avg_loc.y += poses[i].location.y * weight + avg_loc.z += poses[i].location.z * weight # Z轴也加权平均 + + # 加权平均Yaw角(处理360度环绕) + yaws = [pose.rotation.yaw for pose in poses] + yaw_rads = np.radians(yaws) + # 加权正弦和余弦 + weighted_sin = np.sum(np.sin(yaw_rads) * self.weights[:count]) + weighted_cos = np.sum(np.cos(yaw_rads) * self.weights[:count]) + avg_yaw = np.degrees(np.arctan2(weighted_sin, weighted_cos)) + + # 构建平均位姿 + avg_pose = carla.Transform(avg_loc, carla.Rotation(pitch=0, yaw=avg_yaw, roll=0)) + + # 4. 模拟父级绑定+Z轴强制锁定 + relative_loc = carla.Location(x=-5.0, y=0.0, z=2.0) + target_loc = avg_pose.transform(relative_loc) + target_loc.z = avg_pose.location.z + 2.0 # 强制锁定Z轴,不随任何波动 + + # 5. 镜头旋转:完全锁定(仅Yaw跟随平均位姿) + target_rot = carla.Rotation( + pitch=-10.0, # 完全固定,不参与任何平滑 + yaw=avg_yaw, + roll=0.0 # 完全固定 + ) + + # 6. EMA平滑+微米级死区过滤(终极去抖) + current_transform = spectator.get_transform() + alpha = 0.03 # 极致平滑系数(更小,更稳定) + pos_deadzone = 0.005 # 微米级死区(5mm内波动不更新) + + # 位置平滑(带死区) + loc_diff = np.array([ + target_loc.x - current_transform.location.x, + target_loc.y - current_transform.location.y, + target_loc.z - current_transform.location.z + ]) + loc_diff_mag = np.linalg.norm(loc_diff) + + if loc_diff_mag > pos_deadzone: + final_loc = carla.Location( + x=alpha * target_loc.x + (1 - alpha) * current_transform.location.x, + y=alpha * target_loc.y + (1 - alpha) * current_transform.location.y, + z=target_loc.z # Z轴直接锁定 + ) + else: + # 死区内不更新,保持当前位置 + final_loc = current_transform.location + + # 旋转平滑(仅Yaw,带死区) + yaw_diff = target_rot.yaw - current_transform.rotation.yaw + yaw_diff = (yaw_diff + 180) % 360 - 180 # 归一化 + rot_deadzone = 0.02 # 0.02度死区 + + if abs(yaw_diff) > rot_deadzone: + final_yaw = current_transform.rotation.yaw + alpha * yaw_diff + final_yaw = final_yaw % 360 + else: + final_yaw = current_transform.rotation.yaw + + final_rot = carla.Rotation( + pitch=-10.0, # 完全固定 + yaw=final_yaw, + roll=0.0 # 完全固定 + ) + + # 7. 更新镜头(仅一次,同步帧内完成) + spectator.set_transform(carla.Transform(final_loc, final_rot)) + -if __name__ == "__main__": - # 测试环境 +def run_simulation(): + pygame.init() + env = None try: + print("\n[CARLA连接] 创建环境...") env = CarlaEnvironment() - print("环境初始化完成,开始测试...") - obs = env.reset() - print(f"观测数据:图像{obs['image'].shape},激光雷达{obs['lidar_distances'].shape},IMU{obs['imu'].shape}") - # 运行600步(约30秒) - for i in range(600): - obs, reward, done, _ = env.step() - if i % 50 == 0: - obstacle_info = env.get_obstacle_directions(obs['lidar_distances']) - print(f"第{i}步 - 前向距离:{obstacle_info['front']:.2f}m,NPC数量:{len(env.npc_vehicles)}") - time.sleep(0.05) - env.close() - print("测试完成") + + # 初始化镜头平滑器(15帧加权滑动窗口) + env.init_spectator_smoother(window_size=15) + + print("\n[环境重置] 生成车辆和传感器...") + env.reset() + + if not env.vehicle or not env.vehicle.is_alive: + raise RuntimeError("车辆生成失败,请检查CARLA是否正常运行") + print(f"[车辆状态] 生成成功(ID: {env.vehicle.id}),已启用自动驾驶") + + clock = pygame.time.Clock() + spectator = env.world.get_spectator() + + # 初始化镜头+填充缓存(延长初始化时间) + env.world.tick() + for _ in range(env.window_size * 2): # 填充2倍窗口,确保加权平均生效 + env.update_spectator_ultra_smooth(spectator) + env.world.tick() + + print("\n[仿真开始] 车辆将沿车道行驶,按Ctrl+C退出...") + sys.stdout.flush() + + step = 0 + obstacle_distances = {'front': 0, 'rear': 0, 'left': 0, 'right': 0} + while True: + # 处理pygame事件 + for event in pygame.event.get(): + if event.type == pygame.QUIT: + raise KeyboardInterrupt + + # 严格同步的仿真帧推进 + env.world.tick() + + # 终极平滑的镜头更新 + env.update_spectator_ultra_smooth(spectator) + + # 极低频率获取观测(减少性能消耗) + if step % 3 == 0: + observation = env.get_observation() + obstacle_distances = env.get_obstacle_directions(observation['lidar_distances']) + + # 极低频率打印(每2秒打印一次,减少IO抖动) + if step % 120 == 0: + print(f"\n[步骤 {step}] 障碍物距离 - 前{obstacle_distances['front']:.1f}m | 后{obstacle_distances['rear']:.1f}m | " + f"左{obstacle_distances['left']:.1f}m | 右{obstacle_distances['right']:.1f}m") + sys.stdout.flush() + + # 锁定渲染帧率(与仿真帧率一致,避免波动) + clock.tick_busy_loop(60) # 更精准的帧率锁定 + step += 1 + + except KeyboardInterrupt: + print("\n[用户终止] 收到退出信号") except Exception as e: - print(f"测试出错:{str(e)}") - if 'env' in locals(): + print(f"\n[仿真错误] {str(e)}") + import traceback + traceback.print_exc() + sys.stdout.flush() + finally: + if env is not None: + print("\n[资源清理] 销毁资源...") env.close() - sys.exit(1) \ No newline at end of file + pygame.quit() + print("\n[程序退出]") + +if __name__ == "__main__": + print("="*60) + print(f"[启动时间] {time.strftime('%Y-%m-%d %H:%M:%S')}") + print(f"[Python解释器] {sys.executable}") + print("="*60) + sys.stdout.flush() + run_simulation() \ No newline at end of file diff --git a/src/self_driving_car_navigation/run_simulation.py b/src/self_driving_car_navigation/run_simulation.py deleted file mode 100644 index 312538d685..0000000000 --- a/src/self_driving_car_navigation/run_simulation.py +++ /dev/null @@ -1,121 +0,0 @@ -import torch -import time -import sys -import pygame -import carla -import os -import sys - -# 添加当前目录到Python路径,确保可以导入_agent模块 -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -# 从_agent子目录导入CarlaEnvironment -from _agent.carla_environment import CarlaEnvironment - -print("="*60) -print(f"[启动时间] {time.strftime('%Y-%m-%d %H:%M:%S')}") -print(f"[Python解释器] {sys.executable}") -print("="*60) -sys.stdout.flush() - -def set_spectator_smooth(world, vehicle, last_transform=None): - """平滑跟随视角(移植自参考代码的核心逻辑)""" - spectator = world.get_spectator() - vehicle_tf = vehicle.get_transform() - # 目标视角:车辆后上方,提供良好视野 - target_tf = carla.Transform( - vehicle_tf.transform(carla.Location(x=-8, z=3, y=0.5)), - vehicle_tf.rotation - ) - - if last_transform is None: - spectator.set_transform(target_tf) - return target_tf - - # 线性插值平滑过渡 - def lerp(a, b, t): - return a + t * (b - a) - - smooth_loc = carla.Location( - x=lerp(last_transform.location.x, target_tf.location.x, 0.1), - y=lerp(last_transform.location.y, target_tf.location.y, 0.1), - z=lerp(last_transform.location.z, target_tf.location.z, 0.1) - ) - smooth_rot = carla.Rotation( - pitch=lerp(last_transform.rotation.pitch, target_tf.rotation.pitch, 0.1), - yaw=lerp(last_transform.rotation.yaw, target_tf.rotation.yaw, 0.1), - roll=lerp(last_transform.rotation.roll, target_tf.rotation.roll, 0.1) - ) - smooth_tf = carla.Transform(smooth_loc, smooth_rot) - spectator.set_transform(smooth_tf) - return smooth_tf - -def run_simulation(): - # 初始化pygame - pygame.init() - - env = None - try: - print("\n[CARLA连接] 创建环境...") - env = CarlaEnvironment() - - print("\n[环境重置] 生成车辆和传感器...") - env.reset() - - if not env.vehicle or not env.vehicle.is_alive: - raise RuntimeError("车辆生成失败,请检查CARLA是否正常运行") - print(f"[车辆状态] 生成成功(ID: {env.vehicle.id}),已启用自动驾驶") - - # 初始化平滑视角 - last_spectator_tf = set_spectator_smooth(env.world, env.vehicle) - print("视角已切换至车辆后上方(平滑跟随模式)") - - # 初始化时钟控制帧率 - clock = pygame.time.Clock() - - print("\n[仿真开始] 车辆将沿车道行驶,按Ctrl+C退出...") - sys.stdout.flush() - - # 持续运行仿真(不限制步数) - step = 0 - while True: - # 处理pygame事件 - for event in pygame.event.get(): - if event.type == pygame.QUIT: - raise KeyboardInterrupt - - # 同步CARLA帧(关键优化:保证控制时序稳定) - env.world.tick() - - # 获取观测和障碍物信息 - observation = env.get_observation() - obstacle_distances = env.get_obstacle_directions(observation['lidar_distances']) - - # 打印状态信息 - if step % 10 == 0: # 每10步打印一次 - print(f"\n[步骤 {step}] 障碍物距离 - 前{obstacle_distances['front']:.1f}m | 后{obstacle_distances['rear']:.1f}m | " - f"左{obstacle_distances['left']:.1f}m | 右{obstacle_distances['right']:.1f}m") - sys.stdout.flush() - - # 更新平滑视角 - last_spectator_tf = set_spectator_smooth(env.world, env.vehicle, last_spectator_tf) - - # 控制帧率为30FPS - clock.tick(30) - step += 1 - - except KeyboardInterrupt: - print("\n[用户终止] 收到退出信号") - except Exception as e: - print(f"\n[仿真错误] {str(e)}") - sys.stdout.flush() - finally: - if env is not None: - print("\n[资源清理] 销毁资源...") - env.close() - # 退出pygame - pygame.quit() - print("\n[程序退出]") - -if __name__ == "__main__": - run_simulation() \ No newline at end of file From 463a2b5edcb4d75eec78e9cf1098df8bc869bbc4 Mon Sep 17 00:00:00 2001 From: Pan-j-l <43541417288@qq.com> Date: Wed, 24 Dec 2025 19:31:56 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=BF=80=E5=85=89?= =?UTF-8?q?=E9=9B=B7=E8=BE=BE3D=E7=82=B9=E4=BA=91=E5=8F=AF=E8=A7=86?= =?UTF-8?q?=E5=8C=96=EF=BC=88=E9=A2=9C=E8=89=B2=E7=BC=96=E7=A0=81=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/self_driving_car_navigation/run_carla.py | 179 ++++++++++++++++++- 1 file changed, 170 insertions(+), 9 deletions(-) diff --git a/src/self_driving_car_navigation/run_carla.py b/src/self_driving_car_navigation/run_carla.py index 480ac95f99..b82673b995 100644 --- a/src/self_driving_car_navigation/run_carla.py +++ b/src/self_driving_car_navigation/run_carla.py @@ -9,7 +9,9 @@ from queue import Queue from gym import spaces from collections import deque +import math +# ========== 原有CarlaEnvironment类保持不变 ========== class CarlaEnvironment(gym.Env): def __init__(self): super(CarlaEnvironment, self).__init__() @@ -32,6 +34,8 @@ def __init__(self): self.image_queue = Queue(maxsize=1) self.lidar_queue = Queue(maxsize=1) self.imu_queue = Queue(maxsize=1) + # 新增:保存原始激光雷达点云数据(用于3D可视化) + self.raw_lidar_queue = Queue(maxsize=1) # 连接CARLA(强化严格同步模式) self._connect_carla() @@ -187,13 +191,20 @@ def process_image(self, image): print(f"[图像处理错误] {str(e)}") def process_lidar(self, data): - """处理激光雷达数据""" + """处理激光雷达数据(新增:保存原始点云用于3D可视化)""" try: if not hasattr(data, 'raw_data'): print("[激光雷达处理错误] 无效的激光雷达数据") return - points = np.frombuffer(data.raw_data, dtype=np.dtype('f4')).reshape(-1, 4)[:, :3] + # 原始点云(x,y,z,intensity)- 用于3D可视化 + raw_points = np.frombuffer(data.raw_data, dtype=np.dtype('f4')).reshape(-1, 4) + if self.raw_lidar_queue.full(): + self.raw_lidar_queue.get() + self.raw_lidar_queue.put(raw_points) + + # 原有2D距离计算逻辑 + points = raw_points[:, :3] distances = np.linalg.norm(points, axis=1) angles = np.arctan2(points[:, 1], points[:, 0]) * 180 / np.pi angles = (angles + 360) % 360 @@ -384,6 +395,12 @@ def get_observation(self): 'imu': self.imu_queue.get() } + def get_raw_lidar_points(self): + """获取原始激光雷达点云(用于3D可视化)""" + while self.raw_lidar_queue.empty(): + time.sleep(0.001) + return self.raw_lidar_queue.get() + def get_obstacle_directions(self, lidar_distances): """计算四向障碍物距离""" front_angles = np.concatenate([np.arange(345, 360), np.arange(0, 16)]) @@ -434,7 +451,7 @@ def close(self): self._safe_destroy_actor(self.vehicle) self.vehicle = None self._clear_all_non_ego_actors() - for q in [self.image_queue, self.lidar_queue, self.imu_queue]: + for q in [self.image_queue, self.lidar_queue, self.imu_queue, self.raw_lidar_queue]: while not q.empty(): q.get() # 恢复世界设置 @@ -554,10 +571,143 @@ def update_spectator_ultra_smooth(self, spectator): # 7. 更新镜头(仅一次,同步帧内完成) spectator.set_transform(carla.Transform(final_loc, final_rot)) +# ========== 新增:3D点云可视化核心逻辑 ========== +class Lidar3DVisualizer: + def __init__(self, width=600, height=600): + # 窗口参数 + self.width = width + self.height = height + self.screen = pygame.display.set_mode((width, height), pygame.DOUBLEBUF) + pygame.display.set_caption("激光雷达3D点云可视化") + + # 视角参数(默认从车辆后上方俯视) + self.theta = math.radians(30) # 水平旋转角(绕Y轴) + self.phi = math.radians(-30) # 垂直旋转角(绕X轴) + self.scale = 10.0 # 缩放系数 + self.offset_x = width // 2 + self.offset_y = height // 2 + + # 交互状态 + self.mouse_down = False + self.last_mouse_pos = (0, 0) + self.point_size = 2 # 点云渲染大小 + + # 颜色映射(距离→RGB) + self.color_map = { + 'red': (255, 0, 0), # 0-10m(近距/高风险) + 'yellow': (255, 255, 0),# 10-30m(中距) + 'green': (0, 255, 0) # 30-50m(远距/低风险) + } + def project_3d_to_2d(self, point): + """ + 透视投影:将3D点(x,y,z)转换为2D屏幕坐标 + 核心算法: + 1. 绕Y轴旋转(水平视角) + 2. 绕X轴旋转(垂直视角) + 3. 透视缩放+屏幕偏移 + """ + x, y, z = point + + # 绕Y轴旋转(theta) + x_rot = x * math.cos(self.theta) - z * math.sin(self.theta) + z_rot = x * math.sin(self.theta) + z * math.cos(self.theta) + + # 绕X轴旋转(phi) + y_rot = y * math.cos(self.phi) - z_rot * math.sin(self.phi) + z_rot = y * math.sin(self.phi) + z_rot * math.cos(self.phi) + + # 透视投影 + 屏幕偏移 + if z_rot == 0: + z_rot = 0.001 # 避免除零 + proj_x = self.offset_x + (x_rot * self.scale) / (z_rot / 5) + proj_y = self.offset_y - (y_rot * self.scale) / (z_rot / 5) + + return int(proj_x), int(proj_y) + + def get_point_color(self, distance): + """根据距离获取点云颜色(颜色编码)""" + if distance < 10: + return self.color_map['red'] + elif distance < 30: + return self.color_map['yellow'] + else: + return self.color_map['green'] + + def filter_invalid_points(self, points): + """过滤无效点:距离>50m/ <0.5m、z轴异常(地面/天空噪点)""" + # 计算每个点的距离 + distances = np.linalg.norm(points[:, :3], axis=1) + # 过滤条件 + mask = (distances > 0.5) & (distances < 50) & (points[:, 2] > -1) & (points[:, 2] < 5) + return points[mask], distances[mask] + + def render(self, raw_points): + """渲染3D点云""" + # 1. 清空屏幕(深灰背景,高级感) + self.screen.fill((20, 20, 20)) + + # 2. 过滤无效点 + valid_points, distances = self.filter_invalid_points(raw_points) + + # 3. 逐点投影并渲染 + for point, dist in zip(valid_points, distances): + x, y, z = point[:3] + # 转换为车辆局部坐标系(点云以车辆为中心) + local_point = (-x, -y, z) # 反转x/y,让点云朝向正确 + # 3D→2D投影 + screen_x, screen_y = self.project_3d_to_2d(local_point) + # 边界检查(避免渲染到窗口外) + if 0 < screen_x < self.width and 0 < screen_y < self.height: + color = self.get_point_color(dist) + # 绘制点云(抗锯齿) + pygame.draw.circle(self.screen, color, (screen_x, screen_y), self.point_size) + + # 4. 绘制辅助信息(坐标系、说明文字) + font = pygame.font.SysFont('Consolas', 12) + # 坐标系提示 + axis_text = font.render("X:前 Y:左 Z:上 | 鼠标拖拽旋转 | 滚轮缩放", True, (200, 200, 200)) + self.screen.blit(axis_text, (10, 10)) + # 距离说明 + dist_text = font.render("红色:<10m 黄色:10-30m 绿色:>30m", True, (200, 200, 200)) + self.screen.blit(dist_text, (10, 30)) + + # 5. 更新显示 + pygame.display.flip() + + def handle_events(self): + """处理鼠标交互""" + for event in pygame.event.get(): + # 鼠标按下 + if event.type == pygame.MOUSEBUTTONDOWN: + if event.button == 1: # 左键 + self.mouse_down = True + self.last_mouse_pos = pygame.mouse.get_pos() + elif event.button == 4: # 滚轮上滚(放大) + self.scale = min(self.scale + 1, 30) + elif event.button == 5: # 滚轮下滚(缩小) + self.scale = max(self.scale - 1, 5) + # 鼠标松开 + elif event.type == pygame.MOUSEBUTTONUP: + if event.button == 1: + self.mouse_down = False + # 鼠标拖拽(旋转视角) + elif event.type == pygame.MOUSEMOTION and self.mouse_down: + current_pos = pygame.mouse.get_pos() + dx = current_pos[0] - self.last_mouse_pos[0] + dy = current_pos[1] - self.last_mouse_pos[1] + # 更新旋转角(灵敏度适配) + self.theta += dx * 0.01 + self.phi += dy * 0.01 + self.last_mouse_pos = current_pos + +# ========== 修改后的仿真运行函数 ========== def run_simulation(): pygame.init() + # 初始化字体(避免中文/特殊字符乱码) + pygame.font.init() env = None + lidar_visualizer = None try: print("\n[CARLA连接] 创建环境...") env = CarlaEnvironment() @@ -572,6 +722,9 @@ def run_simulation(): raise RuntimeError("车辆生成失败,请检查CARLA是否正常运行") print(f"[车辆状态] 生成成功(ID: {env.vehicle.id}),已启用自动驾驶") + # 初始化3D点云可视化器 + lidar_visualizer = Lidar3DVisualizer(width=600, height=600) + clock = pygame.time.Clock() spectator = env.world.get_spectator() @@ -582,34 +735,42 @@ def run_simulation(): env.world.tick() print("\n[仿真开始] 车辆将沿车道行驶,按Ctrl+C退出...") + print("[点云可视化] 窗口已启动 - 鼠标拖拽旋转视角 | 滚轮缩放 | 颜色编码距离") sys.stdout.flush() step = 0 obstacle_distances = {'front': 0, 'rear': 0, 'left': 0, 'right': 0} while True: - # 处理pygame事件 + # 1. 处理所有事件(主窗口+点云窗口) for event in pygame.event.get(): if event.type == pygame.QUIT: raise KeyboardInterrupt + # 点云窗口交互 + if lidar_visualizer: + lidar_visualizer.handle_events() - # 严格同步的仿真帧推进 + # 2. 严格同步的仿真帧推进 env.world.tick() - # 终极平滑的镜头更新 + # 3. 终极平滑的镜头更新 env.update_spectator_ultra_smooth(spectator) - # 极低频率获取观测(减少性能消耗) + # 4. 渲染3D点云(每帧更新,保证实时性) + raw_lidar_points = env.get_raw_lidar_points() + lidar_visualizer.render(raw_lidar_points) + + # 5. 极低频率获取观测(减少性能消耗) if step % 3 == 0: observation = env.get_observation() obstacle_distances = env.get_obstacle_directions(observation['lidar_distances']) - # 极低频率打印(每2秒打印一次,减少IO抖动) + # 6. 极低频率打印(每2秒打印一次,减少IO抖动) if step % 120 == 0: print(f"\n[步骤 {step}] 障碍物距离 - 前{obstacle_distances['front']:.1f}m | 后{obstacle_distances['rear']:.1f}m | " f"左{obstacle_distances['left']:.1f}m | 右{obstacle_distances['right']:.1f}m") sys.stdout.flush() - # 锁定渲染帧率(与仿真帧率一致,避免波动) + # 7. 锁定渲染帧率(与仿真帧率一致,避免波动) clock.tick_busy_loop(60) # 更精准的帧率锁定 step += 1 From fc16257bec27b582050959ed516820fd4fd1ad32 Mon Sep 17 00:00:00 2001 From: Pan-j-l <43541417288@qq.com> Date: Wed, 24 Dec 2025 23:01:35 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E6=96=B0=E5=A2=9ECARLA=E4=BB=BF=E7=9C=9F3D?= =?UTF-8?q?=E8=A7=86=E8=A7=92=E5=A2=9E=E5=BC=BA+=E6=B8=85=E6=99=B0?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/self_driving_car_navigation/run_carla.py | 481 +++++++++++++------ 1 file changed, 322 insertions(+), 159 deletions(-) diff --git a/src/self_driving_car_navigation/run_carla.py b/src/self_driving_car_navigation/run_carla.py index b82673b995..e4bd574926 100644 --- a/src/self_driving_car_navigation/run_carla.py +++ b/src/self_driving_car_navigation/run_carla.py @@ -11,7 +11,7 @@ from collections import deque import math -# ========== 原有CarlaEnvironment类保持不变 ========== +# ========== 原有CarlaEnvironment类(重点修改视角平滑逻辑) ========== class CarlaEnvironment(gym.Env): def __init__(self): super(CarlaEnvironment, self).__init__() @@ -34,8 +34,17 @@ def __init__(self): self.image_queue = Queue(maxsize=1) self.lidar_queue = Queue(maxsize=1) self.imu_queue = Queue(maxsize=1) - # 新增:保存原始激光雷达点云数据(用于3D可视化) + # 激光雷达3D点云专用队列 self.raw_lidar_queue = Queue(maxsize=1) + # 新增:当前视角模式(默认第三人称) + self.current_view_mode = "third_person" # first_person / third_person / bird_view + + # ========== 新增:视角平滑相关变量(解决抖动核心) ========== + self.smooth_pos = None # 平滑后的位置缓存 + self.smooth_rot = None # 平滑后的旋转缓存 + self.pos_smooth_factor = 0.05 # 位置平滑系数(0.05~0.2,越小越平滑) + self.rot_smooth_factor = 0.08 # 旋转平滑系数(比位置更平滑) + self.view_switch_flag = False # 视角切换标记(切换时跳过平滑) # 连接CARLA(强化严格同步模式) self._connect_carla() @@ -191,7 +200,7 @@ def process_image(self, image): print(f"[图像处理错误] {str(e)}") def process_lidar(self, data): - """处理激光雷达数据(新增:保存原始点云用于3D可视化)""" + """处理激光雷达数据(保留原始点云用于3D可视化)""" try: if not hasattr(data, 'raw_data'): print("[激光雷达处理错误] 无效的激光雷达数据") @@ -254,6 +263,11 @@ def reset(self): self.vehicle.set_autopilot(True, self.tm_port) self._spawn_npcs(60) self._clear_all_non_ego_actors() + # 重置视角模式和平滑缓存 + self.current_view_mode = "third_person" + self.smooth_pos = None + self.smooth_rot = None + self.view_switch_flag = False # 多次同步,物理稳定(延长时间) for _ in range(15): @@ -356,13 +370,16 @@ def _spawn_sensors(self): for sensor in [self.camera, self.lidar, self.imu]: self._safe_destroy_actor(sensor) - # 摄像头 + # 摄像头(适配第一人称视角:调整安装位置) camera_bp = self.blueprint_library.find('sensor.camera.rgb') camera_bp.set_attribute('image_size_x', '128') camera_bp.set_attribute('image_size_y', '128') camera_bp.set_attribute('fov', '100') + # 第一人称视角摄像头(驾驶位) self.camera = self.world.spawn_actor( - camera_bp, carla.Transform(carla.Location(x=2.0, z=1.5)), attach_to=self.vehicle + camera_bp, carla.Transform(carla.Location(x=1.0, y=0.0, z=1.2), carla.Rotation(pitch=0, yaw=0, roll=0)), + attach_to=self.vehicle, + attachment_type=carla.AttachmentType.Rigid ) self.camera.listen(self.process_image) @@ -464,114 +481,69 @@ def close(self): print(f"[世界设置恢复警告] {str(e)}") print("[资源清理] 所有资源已销毁") - def init_spectator_smoother(self, window_size=15): - """初始化镜头平滑器(增大滑动窗口到15帧)""" - self.vehicle_pose_buffer = deque(maxlen=window_size) # 15帧缓存,过滤更多高频抖动 - self.window_size = window_size - # 初始化加权平均权重(近期帧权重更高,兼顾平滑和响应) - self.weights = np.linspace(0.1, 1.0, window_size) # 权重从0.1→1.0递增 - self.weights /= np.sum(self.weights) # 归一化 - - def update_spectator_ultra_smooth(self, spectator): - """ - 终极平滑镜头更新:加权滑动平均+微米级死区+完全锁定旋转 - """ + # ========== 核心修改:视角切换+平滑跟随(解决抖动) ========== + def switch_view_mode(self, mode): + """切换视角模式(添加切换标记,跳过初始平滑)""" + if mode in ["first_person", "third_person", "bird_view"]: + self.current_view_mode = mode + self.view_switch_flag = True # 切换时强制重置平滑缓存 + print(f"[视角切换] 已切换至:{self.current_view_mode}") + + def _smooth_transform(self, target_loc, target_rot): + """对位置和旋转进行指数移动平均平滑(核心防抖逻辑)""" + # 首次初始化平滑缓存 + if self.smooth_pos is None or self.view_switch_flag: + self.smooth_pos = target_loc + self.smooth_rot = target_rot + self.view_switch_flag = False # 重置切换标记 + return self.smooth_pos, self.smooth_rot + + # 位置平滑(指数移动平均) + self.smooth_pos.x = self.smooth_pos.x * (1 - self.pos_smooth_factor) + target_loc.x * self.pos_smooth_factor + self.smooth_pos.y = self.smooth_pos.y * (1 - self.pos_smooth_factor) + target_loc.y * self.pos_smooth_factor + self.smooth_pos.z = self.smooth_pos.z * (1 - self.pos_smooth_factor) + target_loc.z * self.pos_smooth_factor + + # 旋转平滑(指数移动平均,仅yaw角跟随,pitch/roll固定) + self.smooth_rot.yaw = self.smooth_rot.yaw * (1 - self.rot_smooth_factor) + target_rot.yaw * self.rot_smooth_factor + self.smooth_rot.pitch = target_rot.pitch # 俯仰角固定,不平滑 + self.smooth_rot.roll = target_rot.roll # 滚转角固定,不平滑 + + return self.smooth_pos, self.smooth_rot + + def update_view(self, spectator): + """根据当前视角模式更新 spectator 位置(添加平滑防抖)""" if not self._is_actor_alive(self.vehicle): return - # 1. 获取同步帧快照的车辆位姿(仅用快照,杜绝异步) - snapshot = self.world.get_snapshot() - vehicle_snapshot = snapshot.find(self.vehicle.id) - if not vehicle_snapshot: - return - current_pose = vehicle_snapshot.get_transform() - - # 2. 初始化关键变量 - avg_yaw = current_pose.rotation.yaw - avg_pose = current_pose - - # 3. 加入滑动缓存并计算「加权平均」位姿(核心优化) - self.vehicle_pose_buffer.append(current_pose) - if len(self.vehicle_pose_buffer) >= self.window_size: - # 提取缓存中的位姿 - poses = list(self.vehicle_pose_buffer) - count = len(poses) - - # 加权平均位置(近期帧权重更高) - avg_loc = carla.Location() - for i in range(count): - weight = self.weights[i] - avg_loc.x += poses[i].location.x * weight - avg_loc.y += poses[i].location.y * weight - avg_loc.z += poses[i].location.z * weight # Z轴也加权平均 - - # 加权平均Yaw角(处理360度环绕) - yaws = [pose.rotation.yaw for pose in poses] - yaw_rads = np.radians(yaws) - # 加权正弦和余弦 - weighted_sin = np.sum(np.sin(yaw_rads) * self.weights[:count]) - weighted_cos = np.sum(np.cos(yaw_rads) * self.weights[:count]) - avg_yaw = np.degrees(np.arctan2(weighted_sin, weighted_cos)) - - # 构建平均位姿 - avg_pose = carla.Transform(avg_loc, carla.Rotation(pitch=0, yaw=avg_yaw, roll=0)) - - # 4. 模拟父级绑定+Z轴强制锁定 - relative_loc = carla.Location(x=-5.0, y=0.0, z=2.0) - target_loc = avg_pose.transform(relative_loc) - target_loc.z = avg_pose.location.z + 2.0 # 强制锁定Z轴,不随任何波动 - - # 5. 镜头旋转:完全锁定(仅Yaw跟随平均位姿) - target_rot = carla.Rotation( - pitch=-10.0, # 完全固定,不参与任何平滑 - yaw=avg_yaw, - roll=0.0 # 完全固定 - ) - - # 6. EMA平滑+微米级死区过滤(终极去抖) - current_transform = spectator.get_transform() - alpha = 0.03 # 极致平滑系数(更小,更稳定) - pos_deadzone = 0.005 # 微米级死区(5mm内波动不更新) - - # 位置平滑(带死区) - loc_diff = np.array([ - target_loc.x - current_transform.location.x, - target_loc.y - current_transform.location.y, - target_loc.z - current_transform.location.z - ]) - loc_diff_mag = np.linalg.norm(loc_diff) - - if loc_diff_mag > pos_deadzone: - final_loc = carla.Location( - x=alpha * target_loc.x + (1 - alpha) * current_transform.location.x, - y=alpha * target_loc.y + (1 - alpha) * current_transform.location.y, - z=target_loc.z # Z轴直接锁定 - ) - else: - # 死区内不更新,保持当前位置 - final_loc = current_transform.location - - # 旋转平滑(仅Yaw,带死区) - yaw_diff = target_rot.yaw - current_transform.rotation.yaw - yaw_diff = (yaw_diff + 180) % 360 - 180 # 归一化 - rot_deadzone = 0.02 # 0.02度死区 - - if abs(yaw_diff) > rot_deadzone: - final_yaw = current_transform.rotation.yaw + alpha * yaw_diff - final_yaw = final_yaw % 360 + vehicle_transform = self.vehicle.get_transform() + vehicle_location = vehicle_transform.location + vehicle_rotation = vehicle_transform.rotation + + # 计算目标视角位置和旋转 + if self.current_view_mode == "first_person": + # 第一人称视角:驾驶位,向前看 + target_loc = vehicle_transform.transform(carla.Location(x=0.5, y=0.0, z=1.0)) + target_rot = vehicle_rotation + elif self.current_view_mode == "third_person": + # 第三人称视角:车辆后上方,平滑跟随 + target_loc = vehicle_transform.transform(carla.Location(x=-5.0, y=0.0, z=2.0)) + target_rot = carla.Rotation(pitch=-10.0, yaw=vehicle_rotation.yaw, roll=0.0) + elif self.current_view_mode == "bird_view": + # 鸟瞰视角:车辆正上方,全局俯视 + target_loc = carla.Location(vehicle_location.x, vehicle_location.y, vehicle_location.z + 50.0) + target_rot = carla.Rotation(pitch=-90.0, yaw=vehicle_rotation.yaw, roll=0.0) else: - final_yaw = current_transform.rotation.yaw + # 默认第三人称 + target_loc = vehicle_transform.transform(carla.Location(x=-5.0, y=0.0, z=2.0)) + target_rot = carla.Rotation(pitch=-10.0, yaw=vehicle_rotation.yaw, roll=0.0) - final_rot = carla.Rotation( - pitch=-10.0, # 完全固定 - yaw=final_yaw, - roll=0.0 # 完全固定 - ) + # 对目标视角进行平滑处理(核心防抖) + smooth_loc, smooth_rot = self._smooth_transform(target_loc, target_rot) - # 7. 更新镜头(仅一次,同步帧内完成) - spectator.set_transform(carla.Transform(final_loc, final_rot)) + # 更新 spectator(使用平滑后的位置和旋转) + spectator.set_transform(carla.Transform(smooth_loc, smooth_rot)) -# ========== 新增:3D点云可视化核心逻辑 ========== +# ========== 激光雷达3D点云可视化窗口(保留,单独运行) ========== class Lidar3DVisualizer: def __init__(self, width=600, height=600): # 窗口参数 @@ -587,7 +559,7 @@ def __init__(self, width=600, height=600): self.offset_x = width // 2 self.offset_y = height // 2 - # 交互状态 + # 交互状态(即使暂未生效,保留逻辑) self.mouse_down = False self.last_mouse_pos = (0, 0) self.point_size = 2 # 点云渲染大小 @@ -600,13 +572,7 @@ def __init__(self, width=600, height=600): } def project_3d_to_2d(self, point): - """ - 透视投影:将3D点(x,y,z)转换为2D屏幕坐标 - 核心算法: - 1. 绕Y轴旋转(水平视角) - 2. 绕X轴旋转(垂直视角) - 3. 透视缩放+屏幕偏移 - """ + """透视投影:将3D点(x,y,z)转换为2D屏幕坐标""" x, y, z = point # 绕Y轴旋转(theta) @@ -664,9 +630,9 @@ def render(self, raw_points): pygame.draw.circle(self.screen, color, (screen_x, screen_y), self.point_size) # 4. 绘制辅助信息(坐标系、说明文字) - font = pygame.font.SysFont('Consolas', 12) + font = pygame.font.SysFont('Consolas', 12, bold=True) # 坐标系提示 - axis_text = font.render("X:前 Y:左 Z:上 | 鼠标拖拽旋转 | 滚轮缩放", True, (200, 200, 200)) + axis_text = font.render("X:前 Y:左 Z:上 | 激光雷达有效范围:0.5-50m", True, (200, 200, 200)) self.screen.blit(axis_text, (10, 10)) # 距离说明 dist_text = font.render("红色:<10m 黄色:10-30m 绿色:>30m", True, (200, 200, 200)) @@ -676,7 +642,7 @@ def render(self, raw_points): pygame.display.flip() def handle_events(self): - """处理鼠标交互""" + """处理鼠标交互(保留逻辑,即使暂未生效)""" for event in pygame.event.get(): # 鼠标按下 if event.type == pygame.MOUSEBUTTONDOWN: @@ -701,20 +667,205 @@ def handle_events(self): self.phi += dy * 0.01 self.last_mouse_pos = current_pos -# ========== 修改后的仿真运行函数 ========== +# ========== 主可视化界面(3D视角增强+清晰交互) ========== +class MainVisualizer: + def __init__(self, width=800, height=600): + # 窗口基础配置 + self.width = width + self.height = height + self.screen = pygame.display.set_mode((width, height), pygame.DOUBLEBUF | pygame.HWSURFACE) + pygame.display.set_caption("CARLA自动驾驶仿真 - 3D视角控制") + + # 字体配置(确保显示清晰,抗锯齿) + self.font_large = pygame.font.SysFont('Microsoft YaHei', 16, bold=True) + self.font_small = pygame.font.SysFont('Microsoft YaHei', 12, bold=True) + + # 颜色配置(工业风,高对比度,显示清晰) + self.colors = { + 'bg_main': (18, 18, 28), # 主背景(深蓝灰) + 'bg_panel': (30, 30, 45), # 面板背景(深紫灰) + 'text_normal': (220, 220, 240),# 普通文字(浅灰蓝) + 'text_highlight': (0, 180, 255),# 高亮文字(天蓝色) + 'button_normal': (45, 45, 65), # 按钮常态 + 'button_hover': (60, 60, 85), # 按钮悬停 + 'button_press': (0, 120, 180), # 按钮按下 + 'border': (80, 80, 100) # 边框颜色 + } + + # 交互按钮配置(位置+大小+文本) + self.buttons = { + 'first_person': { + 'rect': pygame.Rect(20, 500, 120, 40), + 'text': '第一人称 (1)', + 'active': False + }, + 'third_person': { + 'rect': pygame.Rect(150, 500, 120, 40), + 'text': '第三人称 (2)', + 'active': True # 默认激活 + }, + 'bird_view': { + 'rect': pygame.Rect(280, 500, 120, 40), + 'text': '鸟瞰视角 (3)', + 'active': False + }, + 'pause': { + 'rect': pygame.Rect(410, 500, 120, 40), + 'text': '暂停 (空格)', + 'active': False + } + } + + # 状态变量 + self.paused = False + self.fps = 0 + self.npc_count = 0 + self.obstacle_distances = {'front': 0, 'rear': 0, 'left': 0, 'right': 0} + self.current_view = "第三人称" + + def draw_buttons(self): + """绘制交互按钮(清晰易识别,有状态反馈)""" + for btn_name, btn in self.buttons.items(): + # 确定按钮颜色(根据状态) + if btn['active']: + bg_color = self.colors['button_press'] + elif btn['rect'].collidepoint(pygame.mouse.get_pos()): + bg_color = self.colors['button_hover'] + else: + bg_color = self.colors['button_normal'] + + # 绘制按钮(带边框,清晰) + pygame.draw.rect(self.screen, bg_color, btn['rect'], border_radius=5) + pygame.draw.rect(self.screen, self.colors['border'], btn['rect'], width=2, border_radius=5) + + # 绘制按钮文字(居中,抗锯齿) + text_surf = self.font_small.render(btn['text'], True, self.colors['text_normal']) + text_rect = text_surf.get_rect(center=btn['rect'].center) + self.screen.blit(text_surf, text_rect) + + def draw_status_panel(self): + """绘制状态面板(信息清晰,分区显示)""" + # 面板背景 + panel_rect = pygame.Rect(20, 20, 300, 150) + pygame.draw.rect(self.screen, self.colors['bg_panel'], panel_rect, border_radius=5) + pygame.draw.rect(self.screen, self.colors['border'], panel_rect, width=2, border_radius=5) + + # 面板标题 + title_surf = self.font_large.render("仿真状态信息", True, self.colors['text_highlight']) + self.screen.blit(title_surf, (30, 30)) + + # 状态文本(分行显示,对齐) + status_texts = [ + f"当前视角:{self.current_view}", + f"仿真帧率:{self.fps:.1f} FPS", + f"NPC车辆数:{self.npc_count} 辆", + f"前方障碍物:{self.obstacle_distances['front']:.1f} m", + f"右侧障碍物:{self.obstacle_distances['right']:.1f} m" + ] + + for i, text in enumerate(status_texts): + y_pos = 60 + i * 20 + text_surf = self.font_small.render(text, True, self.colors['text_normal']) + self.screen.blit(text_surf, (30, y_pos)) + + def draw_help_text(self): + """绘制操作提示(清晰易读)""" + help_texts = [ + "快捷键:1-第一人称 2-第三人称 3-鸟瞰视角 空格-暂停/继续 ESC-退出", + "视角说明:第一人称(驾驶位)| 第三人称(跟随)| 鸟瞰(全局俯视)" + ] + for i, text in enumerate(help_texts): + y_pos = 450 + i * 20 + text_surf = self.font_small.render(text, True, self.colors['text_normal']) + self.screen.blit(text_surf, (20, y_pos)) + + def update_status(self, fps, npc_count, obstacle_distances, current_view): + """更新状态信息(用于渲染)""" + self.fps = fps + self.npc_count = npc_count + self.obstacle_distances = obstacle_distances + self.current_view = current_view + + # 更新按钮激活状态 + for btn_name in self.buttons: + self.buttons[btn_name]['active'] = False + if current_view == "第一人称": + self.buttons['first_person']['active'] = True + elif current_view == "第三人称": + self.buttons['third_person']['active'] = True + elif current_view == "鸟瞰视角": + self.buttons['bird_view']['active'] = True + self.buttons['pause']['active'] = self.paused + + def handle_events(self, env): + """处理交互事件(快捷键+按钮点击)""" + for event in pygame.event.get(): + # 窗口关闭 + if event.type == pygame.QUIT: + raise KeyboardInterrupt + # 键盘按键 + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + raise KeyboardInterrupt + elif event.key == pygame.K_1: + env.switch_view_mode("first_person") + self.current_view = "第一人称" + elif event.key == pygame.K_2: + env.switch_view_mode("third_person") + self.current_view = "第三人称" + elif event.key == pygame.K_3: + env.switch_view_mode("bird_view") + self.current_view = "鸟瞰视角" + elif event.key == pygame.K_SPACE: + self.paused = not self.paused + # 鼠标点击按钮 + elif event.type == pygame.MOUSEBUTTONDOWN: + if event.button == 1: + mouse_pos = pygame.mouse.get_pos() + if self.buttons['first_person']['rect'].collidepoint(mouse_pos): + env.switch_view_mode("first_person") + self.current_view = "第一人称" + elif self.buttons['third_person']['rect'].collidepoint(mouse_pos): + env.switch_view_mode("third_person") + self.current_view = "第三人称" + elif self.buttons['bird_view']['rect'].collidepoint(mouse_pos): + env.switch_view_mode("bird_view") + self.current_view = "鸟瞰视角" + elif self.buttons['pause']['rect'].collidepoint(mouse_pos): + self.paused = not self.paused + + def render(self): + """渲染主界面(所有元素)""" + # 清空主背景 + self.screen.fill(self.colors['bg_main']) + + # 绘制状态面板 + self.draw_status_panel() + + # 绘制操作提示 + self.draw_help_text() + + # 绘制交互按钮 + self.draw_buttons() + + # 更新显示(双缓冲,无撕裂) + pygame.display.flip() + +# ========== 仿真运行函数 ========== def run_simulation(): pygame.init() - # 初始化字体(避免中文/特殊字符乱码) + # 初始化字体(确保中文显示清晰,抗锯齿) pygame.font.init() + # 禁用pygame默认鼠标缩放,避免干扰 + pygame.mouse.set_cursor(pygame.SYSTEM_CURSOR_ARROW) + env = None lidar_visualizer = None + main_visualizer = None try: print("\n[CARLA连接] 创建环境...") env = CarlaEnvironment() - # 初始化镜头平滑器(15帧加权滑动窗口) - env.init_spectator_smoother(window_size=15) - print("\n[环境重置] 生成车辆和传感器...") env.reset() @@ -722,57 +873,68 @@ def run_simulation(): raise RuntimeError("车辆生成失败,请检查CARLA是否正常运行") print(f"[车辆状态] 生成成功(ID: {env.vehicle.id}),已启用自动驾驶") - # 初始化3D点云可视化器 + # 初始化两个可视化窗口 lidar_visualizer = Lidar3DVisualizer(width=600, height=600) + main_visualizer = MainVisualizer(width=800, height=600) clock = pygame.time.Clock() spectator = env.world.get_spectator() - # 初始化镜头+填充缓存(延长初始化时间) - env.world.tick() - for _ in range(env.window_size * 2): # 填充2倍窗口,确保加权平均生效 - env.update_spectator_ultra_smooth(spectator) - env.world.tick() + # 初始化视角(强制一次平滑缓存) + env.update_view(spectator) - print("\n[仿真开始] 车辆将沿车道行驶,按Ctrl+C退出...") - print("[点云可视化] 窗口已启动 - 鼠标拖拽旋转视角 | 滚轮缩放 | 颜色编码距离") + print("\n[仿真开始] 按ESC退出 | 快捷键1/2/3切换视角 | 空格暂停") + print("[窗口说明] 主窗口(视角控制)| 独立窗口(激光雷达3D点云)") sys.stdout.flush() step = 0 obstacle_distances = {'front': 0, 'rear': 0, 'left': 0, 'right': 0} while True: - # 1. 处理所有事件(主窗口+点云窗口) - for event in pygame.event.get(): - if event.type == pygame.QUIT: - raise KeyboardInterrupt - # 点云窗口交互 - if lidar_visualizer: - lidar_visualizer.handle_events() + # 1. 处理所有交互事件 + main_visualizer.handle_events(env) + lidar_visualizer.handle_events() - # 2. 严格同步的仿真帧推进 - env.world.tick() - - # 3. 终极平滑的镜头更新 - env.update_spectator_ultra_smooth(spectator) - - # 4. 渲染3D点云(每帧更新,保证实时性) - raw_lidar_points = env.get_raw_lidar_points() - lidar_visualizer.render(raw_lidar_points) - - # 5. 极低频率获取观测(减少性能消耗) - if step % 3 == 0: - observation = env.get_observation() - obstacle_distances = env.get_obstacle_directions(observation['lidar_distances']) + # 2. 暂停逻辑 + if not main_visualizer.paused: + # 严格同步的仿真帧推进 + env.world.tick() + + # 更新CARLA spectator视角(带平滑防抖) + env.update_view(spectator) + + # 获取观测数据(低频率,减少消耗) + if step % 3 == 0: + observation = env.get_observation() + obstacle_distances = env.get_obstacle_directions(observation['lidar_distances']) + # 渲染激光雷达3D点云 + raw_lidar_points = env.get_raw_lidar_points() + lidar_visualizer.render(raw_lidar_points) + + # 打印日志(极低频率) + if step % 120 == 0: + print(f"\n[步骤 {step}] 障碍物距离 - 前{obstacle_distances['front']:.1f}m | 后{obstacle_distances['rear']:.1f}m | " + f"左{obstacle_distances['left']:.1f}m | 右{obstacle_distances['right']:.1f}m") + sys.stdout.flush() + + step += 1 - # 6. 极低频率打印(每2秒打印一次,减少IO抖动) - if step % 120 == 0: - print(f"\n[步骤 {step}] 障碍物距离 - 前{obstacle_distances['front']:.1f}m | 后{obstacle_distances['rear']:.1f}m | " - f"左{obstacle_distances['left']:.1f}m | 右{obstacle_distances['right']:.1f}m") - sys.stdout.flush() + # 3. 更新主界面状态并渲染 + current_fps = clock.get_fps() + current_view_name = { + "first_person": "第一人称", + "third_person": "第三人称", + "bird_view": "鸟瞰视角" + }.get(env.current_view_mode, "第三人称") + main_visualizer.update_status( + fps=current_fps, + npc_count=len(env.npc_vehicles), + obstacle_distances=obstacle_distances, + current_view=current_view_name + ) + main_visualizer.render() - # 7. 锁定渲染帧率(与仿真帧率一致,避免波动) - clock.tick_busy_loop(60) # 更精准的帧率锁定 - step += 1 + # 4. 锁定渲染帧率(与仿真帧率一致) + clock.tick_busy_loop(60) except KeyboardInterrupt: print("\n[用户终止] 收到退出信号") @@ -792,6 +954,7 @@ def run_simulation(): print("="*60) print(f"[启动时间] {time.strftime('%Y-%m-%d %H:%M:%S')}") print(f"[Python解释器] {sys.executable}") + print(f"[运行环境] Win11 + CARLA0.9.11 + Python3.7.5") print("="*60) sys.stdout.flush() run_simulation() \ No newline at end of file