From cb17a62df359d75b1201964866da52937fa83bc3 Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Sun, 21 Dec 2025 16:01:58 +0800
Subject: [PATCH 01/16] Create README.md
---
src/Safe_navigation/README.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 src/Safe_navigation/README.md
diff --git a/src/Safe_navigation/README.md b/src/Safe_navigation/README.md
new file mode 100644
index 0000000000..e1ca3e8b66
--- /dev/null
+++ b/src/Safe_navigation/README.md
@@ -0,0 +1,2 @@
+首次提交选题为无人车安全导航
+
From 35f609b2655e6d4f7f92a2b4957089102c813508 Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Sun, 21 Dec 2025 21:45:29 +0800
Subject: [PATCH 02/16] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/.gitignore | 5 +
.../inspectionProfiles/profiles_settings.xml | 6 +
.idea/misc.xml | 7 +
.idea/modules.xml | 8 +
.idea/nn.iml | 18 +
.idea/vcs.xml | 6 +
.../car.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 36820 bytes
...onnect_airsim.cpython-312-pytest-9.0.2.pyc | Bin 0 -> 6417 bytes
src/Safe_navigation/car.py | 846 ++++++++++++++++++
src/Safe_navigation/check_version.py | 30 +
src/Safe_navigation/connect_airsim.py | 51 ++
.../events.out.tfevents.1766316918.YD.1800.0 | Bin 0 -> 88 bytes
src/Safe_navigation/run_main.py | 506 +++++++++++
13 files changed, 1483 insertions(+)
create mode 100644 .idea/.gitignore
create mode 100644 .idea/inspectionProfiles/profiles_settings.xml
create mode 100644 .idea/misc.xml
create mode 100644 .idea/modules.xml
create mode 100644 .idea/nn.iml
create mode 100644 .idea/vcs.xml
create mode 100644 src/Safe_navigation/__pycache__/car.cpython-312-pytest-9.0.2.pyc
create mode 100644 src/Safe_navigation/__pycache__/connect_airsim.cpython-312-pytest-9.0.2.pyc
create mode 100644 src/Safe_navigation/car.py
create mode 100644 src/Safe_navigation/check_version.py
create mode 100644 src/Safe_navigation/connect_airsim.py
create mode 100644 src/Safe_navigation/logs/airsim_dqn_20251221_193518/events.out.tfevents.1766316918.YD.1800.0
create mode 100644 src/Safe_navigation/run_main.py
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000000..10b731c518
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,5 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000000..105ce2da2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000000..77773ffb78
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000000..0e01bed9d6
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/nn.iml b/.idea/nn.iml
new file mode 100644
index 0000000000..28462e1238
--- /dev/null
+++ b/.idea/nn.iml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000000..35eb1ddfbb
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Safe_navigation/__pycache__/car.cpython-312-pytest-9.0.2.pyc b/src/Safe_navigation/__pycache__/car.cpython-312-pytest-9.0.2.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..639510e71852f9ac75bb3e29bfb82d18c606b6e8
GIT binary patch
literal 36820
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
Xi^>ZpA}TAahOaT 0:
+ img1d = np.frombuffer(responses[0].image_data_uint8, dtype=np.uint8)
+
+ if img1d.size > 0:
+ img_rgb = img1d.reshape(responses[0].height, responses[0].width, 3)
+ resized = cv2.resize(img_rgb, self.config.image_size)
+ normalized = resized.astype(np.float32) / 255.0
+ normalized = np.transpose(normalized, (2, 0, 1))
+
+ return normalized
+
+ except Exception as e:
+ print(f"获取摄像头图像失败: {e}")
+
+ # 返回空白图像
+ return np.zeros((3, *self.config.image_size), dtype=np.float32)
+
+ def get_vehicle_state(self):
+ """获取车辆状态"""
+ try:
+ # 获取车辆状态
+ car_state = self.client.getCarState()
+
+ # 获取碰撞信息
+ collision_info = self.client.simGetCollisionInfo()
+
+ state_info = {
+ 'speed': car_state.speed,
+ 'velocity': [
+ car_state.kinematics_estimated.linear_velocity.x_val,
+ car_state.kinematics_estimated.linear_velocity.y_val,
+ car_state.kinematics_estimated.linear_velocity.z_val
+ ],
+ 'position': [
+ car_state.kinematics_estimated.position.x_val,
+ car_state.kinematics_estimated.position.y_val,
+ car_state.kinematics_estimated.position.z_val
+ ],
+ 'collision': collision_info.has_collided,
+ 'collision_count': collision_info.collision_count,
+ }
+
+ return state_info
+
+ except Exception as e:
+ print(f"获取车辆状态失败: {e}")
+ return None
+
+ def create_state_vector(self, state_info):
+ """创建状态向量"""
+ if state_info is None:
+ return np.zeros(self.config.state_dim, dtype=np.float32)
+
+ state_vector = []
+
+ # 速度信息
+ state_vector.append(state_info['speed'] / self.config.max_speed)
+ state_vector.extend([v / 10.0 for v in state_info['velocity'][:2]])
+
+ # 位置信息
+ state_vector.extend([p / 100.0 for p in state_info['position'][:2]])
+
+ # 碰撞信息
+ state_vector.append(float(state_info['collision']))
+
+ # 补全到指定维度
+ while len(state_vector) < self.config.state_dim:
+ state_vector.append(np.random.uniform(-0.1, 0.1))
+
+ state_vector = state_vector[:self.config.state_dim]
+
+ return np.array(state_vector, dtype=np.float32)
+
+ def get_state(self):
+ """获取当前状态"""
+ try:
+ image_state = self.get_camera_image()
+ state_info = self.get_vehicle_state()
+ state_vector = self.create_state_vector(state_info)
+
+ safety_flags = {
+ 'collision': state_info['collision'] if state_info else False,
+ }
+
+ return image_state, state_vector, safety_flags
+
+ except Exception as e:
+ print(f"获取状态失败: {e}")
+ # 返回默认状态
+ image_state = np.zeros((3, *self.config.image_size), dtype=np.float32)
+ state_vector = np.zeros(self.config.state_dim, dtype=np.float32)
+ safety_flags = {'collision': False}
+ return image_state, state_vector, safety_flags
+
+ def apply_action(self, action_idx):
+ """应用动作到车辆"""
+ # 简化动作空间
+ steer_actions = [-0.3, -0.1, 0.0, 0.1, 0.3]
+ throttle_actions = [0.0, 0.2, 0.5, 0.8, 1.0]
+
+ # 确保动作索引在范围内
+ action_idx = min(action_idx, len(steer_actions) * len(throttle_actions) - 1)
+ steer_idx = action_idx % len(steer_actions)
+ throttle_idx = min(action_idx // len(steer_actions), len(throttle_actions) - 1)
+
+ # 创建控制命令
+ car_controls = airsim.CarControls()
+ car_controls.steering = steer_actions[steer_idx]
+ car_controls.throttle = throttle_actions[throttle_idx]
+ car_controls.brake = 0.0
+
+ # 应用控制
+ try:
+ self.client.setCarControls(car_controls)
+ return car_controls
+ except Exception as e:
+ print(f"应用控制命令失败: {e}")
+ return car_controls
+
+ def calculate_reward(self, current_state_info, safety_flags):
+ """计算奖励函数"""
+ if current_state_info is None:
+ return 0.0
+
+ reward = 0.0
+ speed = current_state_info['speed']
+
+ # 基础移动奖励
+ if speed > 0.1:
+ reward += 0.1
+
+ # 碰撞惩罚
+ if safety_flags['collision']:
+ reward += self.config.collision_penalty
+ self.collisions += 1
+ print(f"⚠️ 发生碰撞! 惩罚: {self.config.collision_penalty}")
+
+ # 生存奖励
+ reward += 0.01
+
+ return reward
+
+ def step(self, action_idx):
+ """执行一步环境交互"""
+ self.step_count += 1
+
+ # 获取当前状态
+ prev_image, prev_vector, prev_safety = self.get_state()
+
+ # 应用动作
+ control = self.apply_action(action_idx)
+
+ # 等待环境响应
+ time.sleep(0.1)
+
+ # 获取新状态
+ current_image, current_vector, current_safety = self.get_state()
+
+ # 获取状态信息用于计算奖励
+ current_state_info = self.get_vehicle_state()
+
+ # 计算奖励
+ reward = self.calculate_reward(current_state_info, current_safety)
+ self.total_reward += reward
+
+ # 检查是否终止
+ done = False
+ if current_safety['collision']:
+ done = True
+ print("💥 终止: 发生碰撞")
+ elif self.step_count >= self.config.max_steps:
+ done = True
+ print("⏱️ 终止: 达到最大步数")
+
+ return (current_image, current_vector, reward,
+ prev_image, prev_vector, done, current_safety)
+
+ def reset(self):
+ """重置环境"""
+ self.step_count = 0
+ self.total_reward = 0
+ self.collisions = 0
+
+ try:
+ self.client.reset()
+ time.sleep(1.0) # 等待重置完成
+ except Exception as e:
+ print(f"重置环境失败: {e}")
+
+ image_state, vector_state, safety_flags = self.get_state()
+
+ return image_state, vector_state, safety_flags
+
+ def close(self):
+ """关闭环境"""
+ try:
+ print("AirSim环境已关闭")
+ except:
+ pass
+
+
+# ==================== 训练函数 ====================
+def train_dqn_safety_navigation(resume_model=None):
+ """主训练函数"""
+ config = TrainingConfig()
+
+ # 创建TensorBoard记录器
+ log_dir = f"./logs/airsim_dqn_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
+ writer = SummaryWriter(log_dir)
+
+ # 初始化环境和智能体
+ env = AirSimSafetyEnv(config)
+ agent = DQNAgent(config, device='cpu')
+
+ # 恢复训练(如果指定)
+ if resume_model and os.path.exists(resume_model):
+ try:
+ agent.load_model(resume_model)
+ print(f"从模型恢复训练: {resume_model}")
+ except:
+ print("模型加载失败,从头开始训练")
+ else:
+ print("从头开始训练")
+
+ # 连接AirSim服务器
+ print("\n" + "=" * 60)
+ print("正在连接AirSim服务器...")
+
+ if not env.connect():
+ print("无法连接到AirSim服务器,退出训练")
+ return
+
+ print("=" * 60)
+
+ # 训练循环
+ print(f"\n开始深度强化学习安全导航训练")
+ print(f"总回合数: {config.total_episodes}")
+ print(f"最大步数: {config.max_steps}")
+ print(f"设备: {agent.device}")
+ print("=" * 60)
+
+ for episode in range(config.total_episodes):
+ # 重置环境
+ try:
+ image_state, vector_state, safety_flags = env.reset()
+ except Exception as e:
+ print(f"重置环境失败: {e}")
+ break
+
+ episode_reward = 0
+ episode_steps = 0
+ episode_losses = []
+ episode_start_time = time.time()
+
+ # 回合循环
+ done = False
+ while not done and episode_steps < config.max_steps:
+ try:
+ # 选择动作
+ action = agent.select_action(image_state, vector_state)
+
+ # 执行动作,获取新状态和奖励
+ (next_image, next_vector, reward,
+ prev_image, prev_vector, done, next_safety) = env.step(action)
+
+ # 存储经验
+ experience = (
+ prev_image, prev_vector, action, reward,
+ next_image, next_vector, done
+ )
+ agent.memory.push(experience)
+
+ # 训练智能体
+ if agent.steps_done % config.update_every == 0:
+ loss = agent.train_step()
+ if loss > 0:
+ episode_losses.append(loss)
+
+ # 更新状态
+ image_state, vector_state = next_image, next_vector
+ episode_reward += reward
+ episode_steps += 1
+ agent.steps_done += 1
+
+ # 简单进度显示
+ if episode_steps % 10 == 0:
+ print(f" 步数: {episode_steps}, 奖励: {episode_reward:.2f}, 探索率: {agent.epsilon:.3f}")
+
+ except Exception as e:
+ print(f"回合执行出错: {e}")
+ done = True
+
+ # 计算回合统计
+ episode_time = time.time() - episode_start_time
+ avg_loss = np.mean(episode_losses) if episode_losses else 0
+
+ # 记录训练数据
+ writer.add_scalar('Reward/Episode', episode_reward, episode)
+ writer.add_scalar('Loss/Episode', avg_loss, episode)
+ writer.add_scalar('Exploration/Epsilon', agent.epsilon, episode)
+ writer.add_scalar('Steps/Episode_Steps', episode_steps, episode)
+
+ # 打印回合总结
+ print(f"\n回合 {episode + 1}/{config.total_episodes}")
+ print(f" 总奖励: {episode_reward:.2f}")
+ print(f" 步数: {episode_steps}")
+ print(f" 时间: {episode_time:.1f}s")
+ print(f" 平均损失: {avg_loss:.4f}")
+ print(f" 碰撞次数: {env.collisions}")
+ print(f" 探索率: {agent.epsilon:.3f}")
+
+ # 保存模型
+ if (episode + 1) % config.save_interval == 0:
+ agent.save_model(episode + 1)
+
+ # 更新目标网络
+ if (episode + 1) % 5 == 0:
+ agent.update_target_network()
+
+ # 保存最终模型
+ agent.save_model(config.total_episodes)
+
+ # 关闭环境
+ env.close()
+ writer.close()
+
+ print("\n" + "=" * 60)
+ print("🎉 训练完成!")
+ print(f"模型已保存至: {config.model_save_path}")
+ print(f"训练日志: {log_dir}")
+ print("=" * 60)
+
+
+# ==================== 评估函数 ====================
+def evaluate_model(model_path, eval_episodes=3):
+ """评估训练好的模型"""
+ config = TrainingConfig()
+ env = AirSimSafetyEnv(config)
+ agent = DQNAgent(config, device='cpu')
+
+ # 加载模型
+ try:
+ agent.load_model(model_path)
+ agent.epsilon = 0.01 # 评估时探索率低
+ except Exception as e:
+ print(f"模型加载失败: {e}")
+ return
+
+ # 连接环境
+ if not env.connect():
+ print("无法连接到AirSim服务器")
+ return
+
+ results = []
+
+ for episode in range(eval_episodes):
+ # 重置环境
+ image_state, vector_state, _ = env.reset()
+
+ episode_reward = 0
+ episode_steps = 0
+
+ done = False
+ while not done and episode_steps < config.max_steps:
+ try:
+ # 选择动作(评估模式)
+ action = agent.select_action(image_state, vector_state, eval_mode=True)
+
+ # 执行动作
+ (next_image, next_vector, reward,
+ _, _, done, _) = env.step(action)
+
+ # 更新
+ image_state, vector_state = next_image, next_vector
+ episode_reward += reward
+ episode_steps += 1
+
+ # 简单显示
+ if episode_steps % 10 == 0:
+ print(f" 评估步数: {episode_steps}, 奖励: {episode_reward:.2f}")
+
+ except Exception as e:
+ print(f"评估出错: {e}")
+ done = True
+
+ results.append({
+ 'episode': episode + 1,
+ 'reward': episode_reward,
+ 'steps': episode_steps,
+ })
+
+ print(f"评估回合 {episode + 1}/{eval_episodes}: 奖励={episode_reward:.2f}")
+
+ # 计算平均指标
+ if results:
+ avg_reward = np.mean([r['reward'] for r in results])
+
+ print("\n" + "=" * 60)
+ print("评估结果总结:")
+ print(f"平均回合奖励: {avg_reward:.2f}")
+ print("=" * 60)
+
+ env.close()
+
+
+# ==================== 主程序入口 ====================
+if __name__ == "__main__":
+ import warnings
+ warnings.filterwarnings('ignore')
+
+ print("=" * 70)
+ print("基于深度强化学习的AirSim自动驾驶安全导航系统")
+ print("版本:兼容旧版AirSim服务器 (1.2.6)")
+ print("=" * 70)
+
+ # 创建必要的目录
+ os.makedirs('./models', exist_ok=True)
+ os.makedirs('./logs', exist_ok=True)
+
+ # 检查PyTorch
+ print(f"PyTorch版本: {torch.__version__}")
+ if torch.cuda.is_available():
+ print(f"检测到GPU: {torch.cuda.get_device_name(0)}")
+ else:
+ print("将使用CPU运行")
+
+ # 用户选择
+ choice = None
+ valid_choices = ['1', '2', '3']
+
+ while choice not in valid_choices:
+ print("\n请选择运行模式:")
+ print("1. 训练新模型 (推荐先测试连接)")
+ print("2. 恢复训练")
+ print("3. 评估模型")
+
+ user_input = input("\n请输入选择 (1-3): ").strip()
+
+ if user_input:
+ first_char = user_input[0]
+ if first_char in valid_choices:
+ choice = first_char
+ else:
+ print(f"错误: 输入 '{user_input}' 无效。请输入 1, 2 或 3。")
+ else:
+ print("错误: 输入不能为空。请输入 1, 2 或 3。")
+
+ # 根据选择执行
+ if choice == '1':
+ print("\n" + "=" * 60)
+ print("重要提示:")
+ print("1. 请确保AirSim仿真环境正在运行")
+ print("2. 已选择汽车模式 (Car Mode)")
+ print("3. 初始训练只有50回合,用于测试连接")
+ print("=" * 60)
+
+ confirm = input("\n确认AirSim环境已启动?(y/n): ").strip().lower()
+ if confirm == 'y':
+ train_dqn_safety_navigation()
+ else:
+ print("请先启动AirSim环境再运行程序")
+ elif choice == '2':
+ model_path = None
+ while not model_path:
+ model_path = input("请输入模型路径 (例如: ./models/airsim_dqn_episode_10.pth): ").strip()
+ if not model_path:
+ print("错误: 输入不能为空。")
+ continue
+
+ if os.path.exists(model_path):
+ print(f"从模型恢复训练: {model_path}")
+ train_dqn_safety_navigation(resume_model=model_path)
+ else:
+ print(f"错误: 模型文件不存在: {model_path}")
+ model_path = None
+ elif choice == '3':
+ model_path = None
+ while not model_path:
+ model_path = input("请输入要评估的模型路径: ").strip()
+ if not model_path:
+ print("错误: 输入不能为空。")
+ continue
+
+ if os.path.exists(model_path):
+ print(f"评估模型: {model_path}")
+ evaluate_model(model_path)
+ else:
+ print(f"错误: 模型文件不存在: {model_path}")
+ model_path = None
+
+
+ # 在代码开头添加测试函数
+ def test_airsim_connection():
+ """测试AirSim连接"""
+ try:
+ import airsim
+ client = airsim.CarClient()
+ client.confirmConnection()
+ print("✓ 成功连接到AirSim服务器!")
+
+ # 获取车辆状态
+ state = client.getCarState()
+ print(f"车辆速度: {state.speed}")
+
+ client.enableApiControl(True)
+ print("API控制已启用")
+
+ return True
+ except Exception as e:
+ print(f"❌ 连接失败: {e}")
+ print("\n请确保:")
+ print("1. AirSim仿真环境正在运行")
+ print("2. 已选择汽车模式")
+ print("3. AirSim服务器IP地址正确")
+ return False
+
+
+ # 在主程序中调用
+ if __name__ == "__main__":
+ print("测试AirSim连接...")
+ if test_airsim_connection():
+ print("\n连接测试通过!可以开始训练。")
+ else:
+ print("\n连接测试失败,请检查AirSim环境。")
\ No newline at end of file
diff --git a/src/Safe_navigation/check_version.py b/src/Safe_navigation/check_version.py
new file mode 100644
index 0000000000..d36e7ce000
--- /dev/null
+++ b/src/Safe_navigation/check_version.py
@@ -0,0 +1,30 @@
+import airsim
+import pkg_resources
+import inspect
+
+print("=== AirSim 环境诊断 ===")
+
+# 1. 检查安装的版本
+try:
+ version = pkg_resources.get_distribution("airsim").version
+ print(f"1. 已安装的 airsim 包版本: {version}")
+except Exception as e:
+ print(f"1. 无法获取版本: {e}")
+
+# 2. 查看 CarClient 的构造函数签名
+print("\n2. CarClient.__init__ 接受的参数:")
+try:
+ # 获取构造函数签名,跳过第一个参数'self'
+ sig = inspect.signature(airsim.CarClient.__init__)
+ params = list(sig.parameters.keys())[1:] # 移除'self'
+ print(" ", params)
+except Exception as e:
+ print(f" 获取失败: {e}")
+
+# 3. 尝试导入其他可能相关的模块
+print("\n3. 尝试直接导入常用客户端...")
+try:
+ from airsim import CarClient
+ print(" ✓ 成功从 airsim 导入 CarClient")
+except ImportError as e:
+ print(f" ✗ 导入失败: {e}")
\ No newline at end of file
diff --git a/src/Safe_navigation/connect_airsim.py b/src/Safe_navigation/connect_airsim.py
new file mode 100644
index 0000000000..f1561402f2
--- /dev/null
+++ b/src/Safe_navigation/connect_airsim.py
@@ -0,0 +1,51 @@
+import airsim
+import time
+
+
+def main():
+ print("=== 尝试连接 AirSimNH (使用 airsim 1.8.1) ===")
+ print("重要:请先确保虚幻引擎中的 AirSimNH 已点击播放(Play)!\n")
+
+ try:
+ # 1. 创建客户端(1.8.1版本的唯一正确方式)
+ client = airsim.CarClient()
+ print("✓ 客户端对象创建成功")
+
+ # 2. 确认连接(这会尝试与仿真器通信)
+ client.confirmConnection()
+ print("✓ 已连接到AirSim仿真服务器")
+
+ # 3. 启用控制
+ client.enableApiControl(True)
+ print("✓ API控制已启用")
+
+ # 4. 获取车辆状态,验证一切正常
+ car_state = client.getCarState()
+ print(f"✓ 车辆状态获取成功 - 速度: {car_state.speed} km/h")
+
+ # 5. 【可选】简单控制演示
+ print("\n>>> 连接成功!开始简单控制演示...")
+ controls = airsim.CarControls()
+ controls.throttle = 0.5
+ client.setCarControls(controls)
+ time.sleep(2)
+ controls.brake = 1.0
+ controls.throttle = 0.0
+ client.setCarControls(controls)
+ time.sleep(1)
+ print("演示结束。")
+ # 6. 释放控制
+ client.enableApiControl(False)
+ print("控制权已释放。")
+
+ except ConnectionRefusedError:
+ print("\n✗ 连接被拒绝。")
+ print(" 最可能的原因:虚幻引擎中的 AirSimNH 仿真没有启动。")
+ print(" 请打开虚幻引擎,加载AirSimNH项目,并点击顶部工具栏的蓝色【播放】(▶)按钮。")
+ except Exception as e:
+ print(f"\n✗ 连接过程中出错: {e}")
+ print(" 其他可能原因:防火墙阻止、端口占用或配置文件错误。")
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/src/Safe_navigation/logs/airsim_dqn_20251221_193518/events.out.tfevents.1766316918.YD.1800.0 b/src/Safe_navigation/logs/airsim_dqn_20251221_193518/events.out.tfevents.1766316918.YD.1800.0
new file mode 100644
index 0000000000000000000000000000000000000000..039f139958d42e73ad137673c1b26c7bd8a465b0
GIT binary patch
literal 88
zcmeZZfPjCKJmzx#6rOYUTi`86Dc+=_#LPTB*Rs^S5-X!1JuaP+)V$*SqNM!9q7=R2
h(%js{qDsB;qRf)iBE3|Qs`#|boYZ)T$m5PPM*w1oA|n6*
literal 0
HcmV?d00001
diff --git a/src/Safe_navigation/run_main.py b/src/Safe_navigation/run_main.py
new file mode 100644
index 0000000000..ba4c30b1ac
--- /dev/null
+++ b/src/Safe_navigation/run_main.py
@@ -0,0 +1,506 @@
+#!/usr/bin/env python3
+"""
+AirSimNH 无人车完整仿真控制脚本
+功能:连接仿真器、手动控制车辆、采集传感器数据、监控车辆状态
+"""
+
+import airsim
+import time
+import numpy as np
+import cv2
+import json
+import os
+from datetime import datetime
+import threading
+from collections import deque
+
+
+class AirSimNHCarSimulator:
+ """AirSim无人车仿真主类"""
+
+ def __init__(self, ip="127.0.0.1", port=41451, vehicle_name="PhysXCar"):
+ """
+ 初始化仿真器连接
+
+ 参数:
+ ip: AirSim服务器IP地址
+ port: AirSim服务器端口
+ vehicle_name: 车辆名称,需与settings.json中一致
+ """
+ self.ip = ip
+ self.port = port
+ self.vehicle_name = vehicle_name
+ self.client = None
+ self.is_connected = False
+ self.is_api_control_enabled = False
+ self.running = False
+ self.data_log = []
+ self.data_file = None
+
+ # 传感器数据缓存
+ self.sensor_data = {
+ "camera": deque(maxlen=100),
+ "imu": deque(maxlen=1000),
+ "gps": deque(maxlen=1000),
+ "lidar": deque(maxlen=100)
+ }
+
+ # 创建数据保存目录
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ self.data_dir = f"simulation_data_{timestamp}"
+ os.makedirs(self.data_dir, exist_ok=True)
+
+ print(f"数据保存目录: {self.data_dir}")
+
+ def connect(self):
+ """连接到AirSim仿真器"""
+ try:
+ print(f"正在连接到AirSim仿真器 {self.ip}:{self.port}...")
+
+ # 创建客户端连接
+ self.client = airsim.CarClient(ip=self.ip, port=self.port)
+ self.client.confirmConnection()
+
+ # 检查车辆是否存在
+ vehicles = self.client.listVehicles()
+ if self.vehicle_name not in vehicles:
+ print(f"警告: 车辆 '{self.vehicle_name}' 未找到,可用车辆: {vehicles}")
+ # 尝试使用找到的第一个车辆
+ if vehicles:
+ self.vehicle_name = vehicles[0]
+ print(f"使用车辆: {self.vehicle_name}")
+
+ self.is_connected = True
+ print("✓ 成功连接到AirSim仿真器!")
+ return True
+
+ except Exception as e:
+ print(f"✗ 连接失败: {e}")
+ print("请确保:")
+ print("1. AirSimNH环境正在运行 (在虚幻引擎中启动)")
+ print("2. settings.json配置正确")
+ print("3. 网络连接正常")
+ return False
+
+ def enable_api_control(self, enable=True):
+ """启用/禁用API控制"""
+ try:
+ self.client.enableApiControl(enable, vehicle_name=self.vehicle_name)
+ self.is_api_control_enabled = enable
+
+ if enable:
+ print("✓ API控制已启用")
+ # 重置控制到初始状态
+ controls = airsim.CarControls()
+ controls.throttle = 0
+ controls.steering = 0
+ controls.brake = 0
+ controls.handbrake = False
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ else:
+ print("✓ API控制已禁用")
+
+ return True
+
+ except Exception as e:
+ print(f"✗ API控制设置失败: {e}")
+ return False
+
+ def get_vehicle_state(self):
+ """获取完整的车辆状态信息"""
+ try:
+ state = self.client.getCarState(vehicle_name=self.vehicle_name)
+
+ # 获取车辆物理信息
+ kinematics = self.client.simGetVehiclePose(vehicle_name=self.vehicle_name)
+
+ state_info = {
+ "timestamp": time.time(),
+ "speed_kmh": state.speed,
+ "speed_ms": state.speed / 3.6,
+ "position": {
+ "x": kinematics.position.x_val,
+ "y": kinematics.position.y_val,
+ "z": kinematics.position.z_val
+ },
+ "orientation": {
+ "w": kinematics.orientation.w_val,
+ "x": kinematics.orientation.x_val,
+ "y": kinematics.orientation.y_val,
+ "z": kinematics.orientation.z_val
+ },
+ "gear": state.gear,
+ "rpm": state.rpm,
+ "max_rpm": state.maxrpm,
+ "handbrake": state.handbrake,
+ "collision": state.collision.has_collided,
+ "collision_count": state.collision.collision_count
+ }
+
+ return state_info
+
+ except Exception as e:
+ print(f"获取车辆状态失败: {e}")
+ return None
+
+ def capture_camera_images(self, camera_names=["front", "back", "left", "right"]):
+ """从多个摄像头捕获图像"""
+ images = {}
+
+ for cam_name in camera_names:
+ try:
+ # 请求RGB图像
+ responses = self.client.simGetImages([
+ airsim.ImageRequest(cam_name, airsim.ImageType.Scene, False, False)
+ ], vehicle_name=self.vehicle_name)
+
+ if responses and responses[0]:
+ img_response = responses[0]
+
+ # 将图像数据转换为numpy数组
+ img1d = np.frombuffer(img_response.image_data_uint8, dtype=np.uint8)
+ img_rgb = img1d.reshape(img_response.height, img_response.width, 3)
+
+ # 保存图像到文件
+ timestamp = datetime.now().strftime("%H%M%S_%f")[:-3]
+ filename = f"{self.data_dir}/{cam_name}_{timestamp}.png"
+ cv2.imwrite(filename, cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR))
+
+ images[cam_name] = {
+ "filename": filename,
+ "shape": img_rgb.shape,
+ "timestamp": time.time()
+ }
+
+ # 缓存数据
+ self.sensor_data["camera"].append({
+ "camera": cam_name,
+ "timestamp": time.time(),
+ "filename": filename
+ })
+
+ except Exception as e:
+ print(f"摄像头 '{cam_name}' 捕获失败: {e}")
+
+ return images
+
+ def get_imu_data(self):
+ """获取IMU传感器数据"""
+ try:
+ imu_data = self.client.getImuData(imu_name="Imu", vehicle_name=self.vehicle_name)
+
+ data = {
+ "timestamp": time.time(),
+ "linear_acceleration": {
+ "x": imu_data.linear_acceleration.x_val,
+ "y": imu_data.linear_acceleration.y_val,
+ "z": imu_data.linear_acceleration.z_val
+ },
+ "angular_velocity": {
+ "x": imu_data.angular_velocity.x_val,
+ "y": imu_data.angular_velocity.y_val,
+ "z": imu_data.angular_velocity.z_val
+ },
+ "orientation": {
+ "w": imu_data.orientation.w_val,
+ "x": imu_data.orientation.x_val,
+ "y": imu_data.orientation.y_val,
+ "z": imu_data.orientation.z_val
+ }
+ }
+
+ self.sensor_data["imu"].append(data)
+ return data
+
+ except Exception as e:
+ print(f"获取IMU数据失败: {e}")
+ return None
+
+ def get_gps_data(self):
+ """获取GPS数据"""
+ try:
+ gps_data = self.client.getGpsData(gps_name="Gps", vehicle_name=self.vehicle_name)
+
+ data = {
+ "timestamp": time.time(),
+ "latitude": gps_data.gnss.geopoint.latitude,
+ "longitude": gps_data.gnss.geopoint.longitude,
+ "altitude": gps_data.gnss.geopoint.altitude,
+ "velocity": {
+ "x": gps_data.gnss.velocity.x_val,
+ "y": gps_data.gnss.velocity.y_val,
+ "z": gps_data.gnss.velocity.z_val
+ }
+ }
+
+ self.sensor_data["gps"].append(data)
+ return data
+
+ except Exception as e:
+ print(f"获取GPS数据失败: {e}")
+ return None
+
+ def manual_control_demo(self, duration=10):
+ """
+ 手动控制演示:前进、转向、停止
+
+ 参数:
+ duration: 演示总时长(秒)
+ """
+ if not self.is_connected or not self.is_api_control_enabled:
+ print("错误: 请先连接并启用API控制")
+ return False
+
+ print(f"\n开始手动控制演示 ({duration}秒)...")
+ print("操作序列: 加速 → 左转 → 右转 → 刹车停止")
+
+ start_time = time.time()
+ sequence = 0
+
+ try:
+ while time.time() - start_time < duration:
+ elapsed = time.time() - start_time
+
+ # 根据时间执行不同控制序列
+ if elapsed < duration * 0.25: # 第一阶段:直线加速
+ controls = airsim.CarControls()
+ controls.throttle = 0.7
+ controls.steering = 0.0
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ if sequence < 1:
+ print(" 阶段1: 直线加速")
+ sequence = 1
+
+ elif elapsed < duration * 0.5: # 第二阶段:左转
+ controls = airsim.CarControls()
+ controls.throttle = 0.5
+ controls.steering = 0.3 # 左转
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ if sequence < 2:
+ print(" 阶段2: 左转")
+ sequence = 2
+
+ elif elapsed < duration * 0.75: # 第三阶段:右转
+ controls = airsim.CarControls()
+ controls.throttle = 0.5
+ controls.steering = -0.3 # 右转
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ if sequence < 3:
+ print(" 阶段3: 右转")
+ sequence = 3
+
+ else: # 第四阶段:减速停止
+ controls = airsim.CarControls()
+ controls.throttle = 0.0
+ controls.brake = 1.0
+ controls.steering = 0.0
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ if sequence < 4:
+ print(" 阶段4: 刹车停止")
+ sequence = 4
+
+ # 实时显示车辆状态
+ state = self.get_vehicle_state()
+ if state:
+ print(f"\r速度: {state['speed_kmh']:.1f} km/h | "
+ f"位置: ({state['position']['x']:.1f}, "
+ f"{state['position']['y']:.1f})", end="")
+
+ # 采集传感器数据
+ self.capture_camera_images(["front"]) # 只采集前摄像头
+ self.get_imu_data()
+ self.get_gps_data()
+
+ time.sleep(0.1) # 控制频率 10Hz
+
+ print("\n✓ 手动控制演示完成")
+ return True
+
+ except Exception as e:
+ print(f"\n✗ 控制演示出错: {e}")
+ return False
+
+ def data_collection_demo(self, duration=5):
+ """数据采集演示:采集所有传感器数据"""
+ print(f"\n开始数据采集演示 ({duration}秒)...")
+
+ start_time = time.time()
+ frame_count = 0
+
+ try:
+ while time.time() - start_time < duration:
+ frame_count += 1
+
+ # 采集所有摄像头图像
+ images = self.capture_camera_images()
+
+ # 采集IMU数据
+ imu_data = self.get_imu_data()
+
+ # 采集GPS数据
+ gps_data = self.get_gps_data()
+
+ # 获取车辆状态
+ vehicle_state = self.get_vehicle_state()
+
+ # 记录到日志
+ log_entry = {
+ "frame": frame_count,
+ "timestamp": time.time(),
+ "images": len(images),
+ "imu_data": imu_data is not None,
+ "gps_data": gps_data is not None,
+ "vehicle_state": vehicle_state is not None
+ }
+ self.data_log.append(log_entry)
+
+ print(f"\r采集帧: {frame_count} | "
+ f"图像: {len(images)} | "
+ f"速度: {vehicle_state['speed_kmh'] if vehicle_state else 'N/A':.1f} km/h", end="")
+
+ time.sleep(0.2) # 5Hz采集频率
+
+ print(f"\n✓ 数据采集完成,共采集 {frame_count} 帧")
+ return True
+
+ except Exception as e:
+ print(f"\n✗ 数据采集出错: {e}")
+ return False
+
+ def save_simulation_data(self):
+ """保存所有仿真数据到文件"""
+ try:
+ # 保存车辆状态日志
+ log_file = f"{self.data_dir}/simulation_log.json"
+ with open(log_file, 'w') as f:
+ json.dump(self.data_log, f, indent=2)
+
+ # 保存传感器数据统计
+ stats = {
+ "timestamp": datetime.now().isoformat(),
+ "vehicle_name": self.vehicle_name,
+ "camera_frames": len(self.sensor_data["camera"]),
+ "imu_samples": len(self.sensor_data["imu"]),
+ "gps_samples": len(self.sensor_data["gps"]),
+ "total_log_entries": len(self.data_log)
+ }
+
+ stats_file = f"{self.data_dir}/simulation_stats.json"
+ with open(stats_file, 'w') as f:
+ json.dump(stats, f, indent=2)
+
+ # 生成数据报告
+ report_file = f"{self.data_dir}/report.txt"
+ with open(report_file, 'w') as f:
+ f.write("=" * 50 + "\n")
+ f.write("AirSim无人车仿真数据报告\n")
+ f.write("=" * 50 + "\n\n")
+ f.write(f"仿真时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
+ f.write(f"车辆名称: {self.vehicle_name}\n")
+ f.write(f"摄像头帧数: {stats['camera_frames']}\n")
+ f.write(f"IMU采样数: {stats['imu_samples']}\n")
+ f.write(f"GPS采样数: {stats['gps_samples']}\n")
+ f.write(f"日志条目: {stats['total_log_entries']}\n\n")
+ f.write("数据文件:\n")
+ for file in os.listdir(self.data_dir):
+ f.write(f" - {file}\n")
+
+ print(f"\n✓ 仿真数据已保存到: {self.data_dir}")
+ print(f" 日志文件: {log_file}")
+ print(f" 统计数据: {stats_file}")
+ print(f" 报告文件: {report_file}")
+
+ return True
+
+ except Exception as e:
+ print(f"✗ 保存数据失败: {e}")
+ return False
+
+ def run_full_demo(self, control_duration=15, data_duration=8):
+ """运行完整演示"""
+ print("=" * 60)
+ print("AirSimNH 无人车完整仿真演示")
+ print("=" * 60)
+
+ # 步骤1: 连接仿真器
+ if not self.connect():
+ return False
+
+ try:
+ # 步骤2: 启用API控制
+ if not self.enable_api_control(True):
+ return False
+
+ # 步骤3: 手动控制演示
+ if not self.manual_control_demo(control_duration):
+ print("手动控制演示失败,继续其他演示...")
+
+ # 短暂暂停,让车辆完全停止
+ time.sleep(2)
+
+ # 步骤4: 数据采集演示
+ if not self.data_collection_demo(data_duration):
+ print("数据采集演示失败,继续保存数据...")
+
+ # 步骤5: 保存数据
+ self.save_simulation_data()
+
+ return True
+
+ finally:
+ # 步骤6: 清理和退出
+ self.cleanup()
+
+ def cleanup(self):
+ """清理资源"""
+ print("\n正在清理资源...")
+
+ # 停止车辆
+ if self.is_api_control_enabled:
+ controls = airsim.CarControls()
+ controls.brake = 1.0
+ controls.handbrake = True
+ try:
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ except:
+ pass
+
+ # 禁用API控制
+ try:
+ self.enable_api_control(False)
+ except:
+ pass
+
+ print("✓ 清理完成")
+
+
+def main():
+ """主函数"""
+ # 创建仿真器实例
+ simulator = AirSimNHCarSimulator(
+ ip="127.0.0.1",
+ port=41451,
+ vehicle_name="PhysXCar"
+ )
+
+ # 运行完整演示
+ try:
+ simulator.run_full_demo(
+ control_duration=20, # 控制演示时长(秒)
+ data_duration=10 # 数据采集时长(秒)
+ )
+
+ print("\n" + "=" * 60)
+ print("仿真演示完成!")
+ print("=" * 60)
+
+ except KeyboardInterrupt:
+ print("\n\n仿真被用户中断")
+ simulator.cleanup()
+ except Exception as e:
+ print(f"\n仿真出错: {e}")
+ simulator.cleanup()
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
From 6e69898fc21c5077088db77ab8bc5a2b266f2429 Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Mon, 22 Dec 2025 09:46:35 +0800
Subject: [PATCH 03/16] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?=
=?UTF-8?q?=E5=B9=B6=E5=88=A0=E9=99=A4.idea=E5=92=8C=5Fpycache?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/.gitignore | 5 --
.../inspectionProfiles/profiles_settings.xml | 6 --
.idea/misc.xml | 7 --
.idea/modules.xml | 8 --
.idea/nn.iml | 18 -----
.idea/vcs.xml | 6 --
.idea/workspace.xml | 75 ++++++++++++++++++
.../car.cpython-312-pytest-9.0.2.pyc | Bin 36820 -> 0 bytes
...onnect_airsim.cpython-312-pytest-9.0.2.pyc | Bin 6417 -> 0 bytes
9 files changed, 75 insertions(+), 50 deletions(-)
delete mode 100644 .idea/.gitignore
delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml
delete mode 100644 .idea/misc.xml
delete mode 100644 .idea/modules.xml
delete mode 100644 .idea/nn.iml
delete mode 100644 .idea/vcs.xml
create mode 100644 .idea/workspace.xml
delete mode 100644 src/Safe_navigation/__pycache__/car.cpython-312-pytest-9.0.2.pyc
delete mode 100644 src/Safe_navigation/__pycache__/connect_airsim.cpython-312-pytest-9.0.2.pyc
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 10b731c518..0000000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-# 默认忽略的文件
-/shelf/
-/workspace.xml
-# 基于编辑器的 HTTP 客户端请求
-/httpRequests/
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2da2d..0000000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 77773ffb78..0000000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 0e01bed9d6..0000000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/nn.iml b/.idea/nn.iml
deleted file mode 100644
index 28462e1238..0000000000
--- a/.idea/nn.iml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1ddfbb..0000000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000000..ee967e3f4d
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ "associatedIndex": 3
+}
+
+
+
+
+
+ {
+ "keyToString": {
+ "ModuleVcsDetector.initialDetectionPerformed": "true",
+ "Python 测试.pytest (car.py 内).executor": "Run",
+ "Python 测试.pytest (connect_airsim.py 内).executor": "Run",
+ "Python 测试.pytest (test_airsim.py 内).executor": "Run",
+ "Python.car.executor": "Run",
+ "Python.check_version.executor": "Run",
+ "Python.connect_airsim.executor": "Run",
+ "Python.run_main.executor": "Run",
+ "Python.代码.executor": "Run",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
+ "RunOnceActivity.git.unshallow": "true",
+ "git-widget-placeholder": "github",
+ "ignore.virus.scanning.warn.message": "true",
+ "last_opened_file_path": "C:/nn",
+ "settings.editor.selected.configurable": "reference.settings.ide.settings.file-colors"
+ }
+}
+
+
+
+
+
+
+
+
+
+
+ 1766305867927
+
+
+ 1766305867927
+
+
+
+
\ No newline at end of file
diff --git a/src/Safe_navigation/__pycache__/car.cpython-312-pytest-9.0.2.pyc b/src/Safe_navigation/__pycache__/car.cpython-312-pytest-9.0.2.pyc
deleted file mode 100644
index 639510e71852f9ac75bb3e29bfb82d18c606b6e8..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 36820
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
Xi^>ZpA}TAahOaT
Date: Mon, 22 Dec 2025 11:08:02 +0800
Subject: [PATCH 04/16] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=A0=81?=
=?UTF-8?q?=E5=B9=B6=E8=BF=90=E8=A1=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/workspace.xml | 9 +-
src/Safe_navigation/car.py | 846 ------------------
src/Safe_navigation/check_version.py | 30 -
.../events.out.tfevents.1766316918.YD.1800.0 | Bin 88 -> 0 bytes
.../report.txt | 15 +
.../simulation_log.json | 10 +
.../simulation_stats.json | 8 +
7 files changed, 36 insertions(+), 882 deletions(-)
delete mode 100644 src/Safe_navigation/car.py
delete mode 100644 src/Safe_navigation/check_version.py
delete mode 100644 src/Safe_navigation/logs/airsim_dqn_20251221_193518/events.out.tfevents.1766316918.YD.1800.0
create mode 100644 src/Safe_navigation/simulation_data_20251222_110207/report.txt
create mode 100644 src/Safe_navigation/simulation_data_20251222_110207/simulation_log.json
create mode 100644 src/Safe_navigation/simulation_data_20251222_110207/simulation_stats.json
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index ee967e3f4d..30e0b698e5 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,12 +5,9 @@
-
-
-
-
-
-
+
+
+
diff --git a/src/Safe_navigation/car.py b/src/Safe_navigation/car.py
deleted file mode 100644
index 5c8e3e4e70..0000000000
--- a/src/Safe_navigation/car.py
+++ /dev/null
@@ -1,846 +0,0 @@
-"""
-基于深度强化学习(DQN)的AirSim自动驾驶安全导航训练系统
-兼容旧版AirSim服务器(版本1)的修复版
-"""
-
-import os
-import sys
-import time
-import random
-import argparse
-from collections import deque
-from datetime import datetime
-
-import numpy as np
-import cv2
-import torch
-import torch.nn as nn
-import torch.optim as optim
-import torch.nn.functional as F
-from torch.utils.tensorboard import SummaryWriter
-
-# AirSim导入
-try:
- import airsim
- print(f"✓ AirSim模块导入成功,版本: {airsim.__version__}")
-except ImportError as e:
- print(f"AirSim模块导入失败: {e}")
- sys.exit(1)
-
-
-# ==================== 配置类 ====================
-class TrainingConfig:
- """训练配置参数"""
-
- def __init__(self):
- # 网络参数
- self.image_size = (84, 84) # 输入图像尺寸
- self.state_dim = 15 # 简化状态维度
- self.action_dim = 5 # 简化动作空间
-
- # 训练参数
- self.total_episodes = 50 # 测试阶段用少量回合
- self.max_steps = 100
- self.batch_size = 16
- self.learning_rate = 1e-3
- self.gamma = 0.99
- self.tau = 1e-3
- self.update_every = 4
-
- # 经验回放
- self.buffer_size = 2000
- self.pretrain_length = 100
-
- # 探索策略
- self.epsilon_start = 1.0
- self.epsilon_end = 0.1
- self.epsilon_decay = 0.99
-
- # 安全参数
- self.collision_penalty = -5.0
- self.max_speed = 10.0
-
- # 路径参数
- self.model_save_path = "./models"
- self.log_path = "./logs"
- self.save_interval = 10
-
- # AirSim参数
- self.ip_address = "127.0.0.1"
-
-
-# ==================== 神经网络架构 ====================
-class SimpleDQN(nn.Module):
- """简化版DQN网络"""
-
- def __init__(self, state_dim, action_dim, image_channels=3):
- super(SimpleDQN, self).__init__()
-
- # 视觉编码器
- self.visual_encoder = nn.Sequential(
- nn.Conv2d(image_channels, 8, kernel_size=4, stride=2),
- nn.ReLU(),
- nn.Conv2d(8, 16, kernel_size=3, stride=1),
- nn.ReLU(),
- nn.Flatten()
- )
-
- # 计算卷积层输出维度
- with torch.no_grad():
- sample = torch.zeros(1, image_channels, 84, 84)
- conv_out = self.visual_encoder(sample)
- self.visual_feature_dim = conv_out.shape[1]
-
- # 状态处理器
- self.state_processor = nn.Sequential(
- nn.Linear(state_dim, 32),
- nn.ReLU(),
- )
-
- # 特征融合层
- fusion_input_dim = self.visual_feature_dim + 32
- self.fusion_layer = nn.Sequential(
- nn.Linear(fusion_input_dim, 64),
- nn.ReLU(),
- )
-
- # 动作价值头
- self.value_stream = nn.Sequential(
- nn.Linear(64, 32),
- nn.ReLU(),
- nn.Linear(32, action_dim)
- )
-
- def forward(self, image, state):
- # 处理视觉输入
- visual_features = self.visual_encoder(image)
-
- # 处理状态输入
- state_features = self.state_processor(state)
-
- # 特征融合
- combined = torch.cat([visual_features, state_features], dim=1)
- fused_features = self.fusion_layer(combined)
-
- # 输出动作价值
- q_values = self.value_stream(fused_features)
-
- return q_values
-
-
-# ==================== 经验回放缓冲区 ====================
-class ReplayBuffer:
- """简化版经验回放缓冲区"""
-
- def __init__(self, capacity):
- self.buffer = deque(maxlen=capacity)
-
- def push(self, experience):
- self.buffer.append(experience)
-
- def sample(self, batch_size):
- if len(self.buffer) < batch_size:
- return None
-
- indices = np.random.choice(len(self.buffer), batch_size, replace=False)
- return [self.buffer[idx] for idx in indices]
-
- def __len__(self):
- return len(self.buffer)
-
-
-# ==================== DQN智能体 ====================
-class DQNAgent:
- """DQN智能体"""
-
- def __init__(self, config, device='cpu'):
- self.config = config
- self.device = torch.device(device)
-
- # 初始化网络
- self.policy_net = SimpleDQN(config.state_dim, config.action_dim).to(self.device)
- self.target_net = SimpleDQN(config.state_dim, config.action_dim).to(self.device)
- self.target_net.load_state_dict(self.policy_net.state_dict())
- self.target_net.eval()
-
- # 优化器
- self.optimizer = optim.Adam(self.policy_net.parameters(), lr=config.learning_rate)
-
- # 经验回放缓冲区
- self.memory = ReplayBuffer(config.buffer_size)
-
- # 训练参数
- self.epsilon = config.epsilon_start
- self.steps_done = 0
-
- print(f"初始化DQN智能体,设备: {self.device}")
-
- def select_action(self, state_image, state_vector, eval_mode=False):
- """选择动作"""
- if not eval_mode and random.random() < self.epsilon:
- return random.randrange(self.config.action_dim)
-
- with torch.no_grad():
- image_tensor = torch.FloatTensor(state_image).unsqueeze(0).to(self.device)
- vector_tensor = torch.FloatTensor(state_vector).unsqueeze(0).to(self.device)
-
- q_values = self.policy_net(image_tensor, vector_tensor)
- return q_values.argmax(1).item()
-
- def train_step(self):
- """训练步骤"""
- if len(self.memory) < self.config.pretrain_length:
- return 0
-
- batch = self.memory.sample(self.config.batch_size)
- if batch is None:
- return 0
-
- # 解构批数据
- states_img, states_vec, actions, rewards, next_states_img, next_states_vec, dones = zip(*batch)
-
- # 转换为张量
- states_img = torch.FloatTensor(np.array(states_img)).to(self.device)
- states_vec = torch.FloatTensor(np.array(states_vec)).to(self.device)
- actions = torch.LongTensor(actions).unsqueeze(1).to(self.device)
- rewards = torch.FloatTensor(rewards).unsqueeze(1).to(self.device)
- next_states_img = torch.FloatTensor(np.array(next_states_img)).to(self.device)
- next_states_vec = torch.FloatTensor(np.array(next_states_vec)).to(self.device)
- dones = torch.FloatTensor(dones).unsqueeze(1).to(self.device)
-
- # 计算当前Q值
- current_q = self.policy_net(states_img, states_vec)
- current_q = current_q.gather(1, actions)
-
- # 计算目标Q值
- with torch.no_grad():
- next_q = self.target_net(next_states_img, next_states_vec)
- next_q_max = next_q.max(1)[0].unsqueeze(1)
- target_q = rewards + (1 - dones) * self.config.gamma * next_q_max
-
- # 计算损失
- loss = F.smooth_l1_loss(current_q, target_q)
-
- # 反向传播
- self.optimizer.zero_grad()
- loss.backward()
- torch.nn.utils.clip_grad_norm_(self.policy_net.parameters(), 1.0)
- self.optimizer.step()
-
- # 更新探索率
- self.epsilon = max(self.config.epsilon_end, self.epsilon * self.config.epsilon_decay)
-
- return loss.item()
-
- def update_target_network(self):
- """更新目标网络"""
- self.target_net.load_state_dict(self.policy_net.state_dict())
-
- def save_model(self, episode, path=None):
- """保存模型"""
- if path is None:
- path = self.config.model_save_path
-
- os.makedirs(path, exist_ok=True)
- model_path = os.path.join(path, f'airsim_dqn_episode_{episode}.pth')
-
- torch.save({
- 'episode': episode,
- 'policy_state_dict': self.policy_net.state_dict(),
- 'optimizer_state_dict': self.optimizer.state_dict(),
- 'epsilon': self.epsilon,
- }, model_path)
-
- print(f"模型已保存: {model_path}")
-
- def load_model(self, model_path):
- """加载模型"""
- checkpoint = torch.load(model_path, map_location=self.device)
-
- self.policy_net.load_state_dict(checkpoint['policy_state_dict'])
- self.optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
- self.epsilon = checkpoint['epsilon']
-
- print(f"模型已加载: {model_path}")
-
-
-# ==================== AirSim环境封装(兼容旧版) ====================
-class AirSimSafetyEnv:
- """兼容旧版AirSim服务器的环境封装"""
-
- def __init__(self, config):
- self.config = config
- self.client = None
- self.step_count = 0
- self.total_reward = 0
- self.collisions = 0
-
- print("初始化AirSim环境...")
-
- def connect(self):
- """连接到AirSim服务器(兼容旧版)"""
- try:
- print(f"尝试连接AirSim服务器 {self.config.ip_address}...")
-
- # 创建客户端
- self.client = airsim.CarClient()
- self.client.confirmConnection()
- print("✓ 连接成功!")
-
- # 尝试启用API控制(兼容旧版)
- try:
- self.client.enableApiControl(True)
- print("✓ API控制已启用")
- except Exception as api_error:
- print(f"⚠️ API控制启用失败(可能是旧版): {api_error}")
- print("尝试继续运行...")
-
- # 重置车辆
- self.client.reset()
- print("✓ 车辆已重置")
-
- # 等待稳定
- time.sleep(1.0)
-
- return True
-
- except Exception as e:
- print(f"❌ 连接失败: {e}")
- print("\n请确保:")
- print("1. AirSim仿真环境正在运行(如AirSimNH.exe)")
- print("2. 已选择汽车模式")
- print("3. 服务器版本兼容(使用AirSim 1.2.6客户端)")
- return False
-
- def get_camera_image(self):
- """获取摄像头图像"""
- try:
- # 获取场景图像
- responses = self.client.simGetImages([
- airsim.ImageRequest("0", airsim.ImageType.Scene, False, False)
- ])
-
- if responses and len(responses) > 0:
- img1d = np.frombuffer(responses[0].image_data_uint8, dtype=np.uint8)
-
- if img1d.size > 0:
- img_rgb = img1d.reshape(responses[0].height, responses[0].width, 3)
- resized = cv2.resize(img_rgb, self.config.image_size)
- normalized = resized.astype(np.float32) / 255.0
- normalized = np.transpose(normalized, (2, 0, 1))
-
- return normalized
-
- except Exception as e:
- print(f"获取摄像头图像失败: {e}")
-
- # 返回空白图像
- return np.zeros((3, *self.config.image_size), dtype=np.float32)
-
- def get_vehicle_state(self):
- """获取车辆状态"""
- try:
- # 获取车辆状态
- car_state = self.client.getCarState()
-
- # 获取碰撞信息
- collision_info = self.client.simGetCollisionInfo()
-
- state_info = {
- 'speed': car_state.speed,
- 'velocity': [
- car_state.kinematics_estimated.linear_velocity.x_val,
- car_state.kinematics_estimated.linear_velocity.y_val,
- car_state.kinematics_estimated.linear_velocity.z_val
- ],
- 'position': [
- car_state.kinematics_estimated.position.x_val,
- car_state.kinematics_estimated.position.y_val,
- car_state.kinematics_estimated.position.z_val
- ],
- 'collision': collision_info.has_collided,
- 'collision_count': collision_info.collision_count,
- }
-
- return state_info
-
- except Exception as e:
- print(f"获取车辆状态失败: {e}")
- return None
-
- def create_state_vector(self, state_info):
- """创建状态向量"""
- if state_info is None:
- return np.zeros(self.config.state_dim, dtype=np.float32)
-
- state_vector = []
-
- # 速度信息
- state_vector.append(state_info['speed'] / self.config.max_speed)
- state_vector.extend([v / 10.0 for v in state_info['velocity'][:2]])
-
- # 位置信息
- state_vector.extend([p / 100.0 for p in state_info['position'][:2]])
-
- # 碰撞信息
- state_vector.append(float(state_info['collision']))
-
- # 补全到指定维度
- while len(state_vector) < self.config.state_dim:
- state_vector.append(np.random.uniform(-0.1, 0.1))
-
- state_vector = state_vector[:self.config.state_dim]
-
- return np.array(state_vector, dtype=np.float32)
-
- def get_state(self):
- """获取当前状态"""
- try:
- image_state = self.get_camera_image()
- state_info = self.get_vehicle_state()
- state_vector = self.create_state_vector(state_info)
-
- safety_flags = {
- 'collision': state_info['collision'] if state_info else False,
- }
-
- return image_state, state_vector, safety_flags
-
- except Exception as e:
- print(f"获取状态失败: {e}")
- # 返回默认状态
- image_state = np.zeros((3, *self.config.image_size), dtype=np.float32)
- state_vector = np.zeros(self.config.state_dim, dtype=np.float32)
- safety_flags = {'collision': False}
- return image_state, state_vector, safety_flags
-
- def apply_action(self, action_idx):
- """应用动作到车辆"""
- # 简化动作空间
- steer_actions = [-0.3, -0.1, 0.0, 0.1, 0.3]
- throttle_actions = [0.0, 0.2, 0.5, 0.8, 1.0]
-
- # 确保动作索引在范围内
- action_idx = min(action_idx, len(steer_actions) * len(throttle_actions) - 1)
- steer_idx = action_idx % len(steer_actions)
- throttle_idx = min(action_idx // len(steer_actions), len(throttle_actions) - 1)
-
- # 创建控制命令
- car_controls = airsim.CarControls()
- car_controls.steering = steer_actions[steer_idx]
- car_controls.throttle = throttle_actions[throttle_idx]
- car_controls.brake = 0.0
-
- # 应用控制
- try:
- self.client.setCarControls(car_controls)
- return car_controls
- except Exception as e:
- print(f"应用控制命令失败: {e}")
- return car_controls
-
- def calculate_reward(self, current_state_info, safety_flags):
- """计算奖励函数"""
- if current_state_info is None:
- return 0.0
-
- reward = 0.0
- speed = current_state_info['speed']
-
- # 基础移动奖励
- if speed > 0.1:
- reward += 0.1
-
- # 碰撞惩罚
- if safety_flags['collision']:
- reward += self.config.collision_penalty
- self.collisions += 1
- print(f"⚠️ 发生碰撞! 惩罚: {self.config.collision_penalty}")
-
- # 生存奖励
- reward += 0.01
-
- return reward
-
- def step(self, action_idx):
- """执行一步环境交互"""
- self.step_count += 1
-
- # 获取当前状态
- prev_image, prev_vector, prev_safety = self.get_state()
-
- # 应用动作
- control = self.apply_action(action_idx)
-
- # 等待环境响应
- time.sleep(0.1)
-
- # 获取新状态
- current_image, current_vector, current_safety = self.get_state()
-
- # 获取状态信息用于计算奖励
- current_state_info = self.get_vehicle_state()
-
- # 计算奖励
- reward = self.calculate_reward(current_state_info, current_safety)
- self.total_reward += reward
-
- # 检查是否终止
- done = False
- if current_safety['collision']:
- done = True
- print("💥 终止: 发生碰撞")
- elif self.step_count >= self.config.max_steps:
- done = True
- print("⏱️ 终止: 达到最大步数")
-
- return (current_image, current_vector, reward,
- prev_image, prev_vector, done, current_safety)
-
- def reset(self):
- """重置环境"""
- self.step_count = 0
- self.total_reward = 0
- self.collisions = 0
-
- try:
- self.client.reset()
- time.sleep(1.0) # 等待重置完成
- except Exception as e:
- print(f"重置环境失败: {e}")
-
- image_state, vector_state, safety_flags = self.get_state()
-
- return image_state, vector_state, safety_flags
-
- def close(self):
- """关闭环境"""
- try:
- print("AirSim环境已关闭")
- except:
- pass
-
-
-# ==================== 训练函数 ====================
-def train_dqn_safety_navigation(resume_model=None):
- """主训练函数"""
- config = TrainingConfig()
-
- # 创建TensorBoard记录器
- log_dir = f"./logs/airsim_dqn_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
- writer = SummaryWriter(log_dir)
-
- # 初始化环境和智能体
- env = AirSimSafetyEnv(config)
- agent = DQNAgent(config, device='cpu')
-
- # 恢复训练(如果指定)
- if resume_model and os.path.exists(resume_model):
- try:
- agent.load_model(resume_model)
- print(f"从模型恢复训练: {resume_model}")
- except:
- print("模型加载失败,从头开始训练")
- else:
- print("从头开始训练")
-
- # 连接AirSim服务器
- print("\n" + "=" * 60)
- print("正在连接AirSim服务器...")
-
- if not env.connect():
- print("无法连接到AirSim服务器,退出训练")
- return
-
- print("=" * 60)
-
- # 训练循环
- print(f"\n开始深度强化学习安全导航训练")
- print(f"总回合数: {config.total_episodes}")
- print(f"最大步数: {config.max_steps}")
- print(f"设备: {agent.device}")
- print("=" * 60)
-
- for episode in range(config.total_episodes):
- # 重置环境
- try:
- image_state, vector_state, safety_flags = env.reset()
- except Exception as e:
- print(f"重置环境失败: {e}")
- break
-
- episode_reward = 0
- episode_steps = 0
- episode_losses = []
- episode_start_time = time.time()
-
- # 回合循环
- done = False
- while not done and episode_steps < config.max_steps:
- try:
- # 选择动作
- action = agent.select_action(image_state, vector_state)
-
- # 执行动作,获取新状态和奖励
- (next_image, next_vector, reward,
- prev_image, prev_vector, done, next_safety) = env.step(action)
-
- # 存储经验
- experience = (
- prev_image, prev_vector, action, reward,
- next_image, next_vector, done
- )
- agent.memory.push(experience)
-
- # 训练智能体
- if agent.steps_done % config.update_every == 0:
- loss = agent.train_step()
- if loss > 0:
- episode_losses.append(loss)
-
- # 更新状态
- image_state, vector_state = next_image, next_vector
- episode_reward += reward
- episode_steps += 1
- agent.steps_done += 1
-
- # 简单进度显示
- if episode_steps % 10 == 0:
- print(f" 步数: {episode_steps}, 奖励: {episode_reward:.2f}, 探索率: {agent.epsilon:.3f}")
-
- except Exception as e:
- print(f"回合执行出错: {e}")
- done = True
-
- # 计算回合统计
- episode_time = time.time() - episode_start_time
- avg_loss = np.mean(episode_losses) if episode_losses else 0
-
- # 记录训练数据
- writer.add_scalar('Reward/Episode', episode_reward, episode)
- writer.add_scalar('Loss/Episode', avg_loss, episode)
- writer.add_scalar('Exploration/Epsilon', agent.epsilon, episode)
- writer.add_scalar('Steps/Episode_Steps', episode_steps, episode)
-
- # 打印回合总结
- print(f"\n回合 {episode + 1}/{config.total_episodes}")
- print(f" 总奖励: {episode_reward:.2f}")
- print(f" 步数: {episode_steps}")
- print(f" 时间: {episode_time:.1f}s")
- print(f" 平均损失: {avg_loss:.4f}")
- print(f" 碰撞次数: {env.collisions}")
- print(f" 探索率: {agent.epsilon:.3f}")
-
- # 保存模型
- if (episode + 1) % config.save_interval == 0:
- agent.save_model(episode + 1)
-
- # 更新目标网络
- if (episode + 1) % 5 == 0:
- agent.update_target_network()
-
- # 保存最终模型
- agent.save_model(config.total_episodes)
-
- # 关闭环境
- env.close()
- writer.close()
-
- print("\n" + "=" * 60)
- print("🎉 训练完成!")
- print(f"模型已保存至: {config.model_save_path}")
- print(f"训练日志: {log_dir}")
- print("=" * 60)
-
-
-# ==================== 评估函数 ====================
-def evaluate_model(model_path, eval_episodes=3):
- """评估训练好的模型"""
- config = TrainingConfig()
- env = AirSimSafetyEnv(config)
- agent = DQNAgent(config, device='cpu')
-
- # 加载模型
- try:
- agent.load_model(model_path)
- agent.epsilon = 0.01 # 评估时探索率低
- except Exception as e:
- print(f"模型加载失败: {e}")
- return
-
- # 连接环境
- if not env.connect():
- print("无法连接到AirSim服务器")
- return
-
- results = []
-
- for episode in range(eval_episodes):
- # 重置环境
- image_state, vector_state, _ = env.reset()
-
- episode_reward = 0
- episode_steps = 0
-
- done = False
- while not done and episode_steps < config.max_steps:
- try:
- # 选择动作(评估模式)
- action = agent.select_action(image_state, vector_state, eval_mode=True)
-
- # 执行动作
- (next_image, next_vector, reward,
- _, _, done, _) = env.step(action)
-
- # 更新
- image_state, vector_state = next_image, next_vector
- episode_reward += reward
- episode_steps += 1
-
- # 简单显示
- if episode_steps % 10 == 0:
- print(f" 评估步数: {episode_steps}, 奖励: {episode_reward:.2f}")
-
- except Exception as e:
- print(f"评估出错: {e}")
- done = True
-
- results.append({
- 'episode': episode + 1,
- 'reward': episode_reward,
- 'steps': episode_steps,
- })
-
- print(f"评估回合 {episode + 1}/{eval_episodes}: 奖励={episode_reward:.2f}")
-
- # 计算平均指标
- if results:
- avg_reward = np.mean([r['reward'] for r in results])
-
- print("\n" + "=" * 60)
- print("评估结果总结:")
- print(f"平均回合奖励: {avg_reward:.2f}")
- print("=" * 60)
-
- env.close()
-
-
-# ==================== 主程序入口 ====================
-if __name__ == "__main__":
- import warnings
- warnings.filterwarnings('ignore')
-
- print("=" * 70)
- print("基于深度强化学习的AirSim自动驾驶安全导航系统")
- print("版本:兼容旧版AirSim服务器 (1.2.6)")
- print("=" * 70)
-
- # 创建必要的目录
- os.makedirs('./models', exist_ok=True)
- os.makedirs('./logs', exist_ok=True)
-
- # 检查PyTorch
- print(f"PyTorch版本: {torch.__version__}")
- if torch.cuda.is_available():
- print(f"检测到GPU: {torch.cuda.get_device_name(0)}")
- else:
- print("将使用CPU运行")
-
- # 用户选择
- choice = None
- valid_choices = ['1', '2', '3']
-
- while choice not in valid_choices:
- print("\n请选择运行模式:")
- print("1. 训练新模型 (推荐先测试连接)")
- print("2. 恢复训练")
- print("3. 评估模型")
-
- user_input = input("\n请输入选择 (1-3): ").strip()
-
- if user_input:
- first_char = user_input[0]
- if first_char in valid_choices:
- choice = first_char
- else:
- print(f"错误: 输入 '{user_input}' 无效。请输入 1, 2 或 3。")
- else:
- print("错误: 输入不能为空。请输入 1, 2 或 3。")
-
- # 根据选择执行
- if choice == '1':
- print("\n" + "=" * 60)
- print("重要提示:")
- print("1. 请确保AirSim仿真环境正在运行")
- print("2. 已选择汽车模式 (Car Mode)")
- print("3. 初始训练只有50回合,用于测试连接")
- print("=" * 60)
-
- confirm = input("\n确认AirSim环境已启动?(y/n): ").strip().lower()
- if confirm == 'y':
- train_dqn_safety_navigation()
- else:
- print("请先启动AirSim环境再运行程序")
- elif choice == '2':
- model_path = None
- while not model_path:
- model_path = input("请输入模型路径 (例如: ./models/airsim_dqn_episode_10.pth): ").strip()
- if not model_path:
- print("错误: 输入不能为空。")
- continue
-
- if os.path.exists(model_path):
- print(f"从模型恢复训练: {model_path}")
- train_dqn_safety_navigation(resume_model=model_path)
- else:
- print(f"错误: 模型文件不存在: {model_path}")
- model_path = None
- elif choice == '3':
- model_path = None
- while not model_path:
- model_path = input("请输入要评估的模型路径: ").strip()
- if not model_path:
- print("错误: 输入不能为空。")
- continue
-
- if os.path.exists(model_path):
- print(f"评估模型: {model_path}")
- evaluate_model(model_path)
- else:
- print(f"错误: 模型文件不存在: {model_path}")
- model_path = None
-
-
- # 在代码开头添加测试函数
- def test_airsim_connection():
- """测试AirSim连接"""
- try:
- import airsim
- client = airsim.CarClient()
- client.confirmConnection()
- print("✓ 成功连接到AirSim服务器!")
-
- # 获取车辆状态
- state = client.getCarState()
- print(f"车辆速度: {state.speed}")
-
- client.enableApiControl(True)
- print("API控制已启用")
-
- return True
- except Exception as e:
- print(f"❌ 连接失败: {e}")
- print("\n请确保:")
- print("1. AirSim仿真环境正在运行")
- print("2. 已选择汽车模式")
- print("3. AirSim服务器IP地址正确")
- return False
-
-
- # 在主程序中调用
- if __name__ == "__main__":
- print("测试AirSim连接...")
- if test_airsim_connection():
- print("\n连接测试通过!可以开始训练。")
- else:
- print("\n连接测试失败,请检查AirSim环境。")
\ No newline at end of file
diff --git a/src/Safe_navigation/check_version.py b/src/Safe_navigation/check_version.py
deleted file mode 100644
index d36e7ce000..0000000000
--- a/src/Safe_navigation/check_version.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import airsim
-import pkg_resources
-import inspect
-
-print("=== AirSim 环境诊断 ===")
-
-# 1. 检查安装的版本
-try:
- version = pkg_resources.get_distribution("airsim").version
- print(f"1. 已安装的 airsim 包版本: {version}")
-except Exception as e:
- print(f"1. 无法获取版本: {e}")
-
-# 2. 查看 CarClient 的构造函数签名
-print("\n2. CarClient.__init__ 接受的参数:")
-try:
- # 获取构造函数签名,跳过第一个参数'self'
- sig = inspect.signature(airsim.CarClient.__init__)
- params = list(sig.parameters.keys())[1:] # 移除'self'
- print(" ", params)
-except Exception as e:
- print(f" 获取失败: {e}")
-
-# 3. 尝试导入其他可能相关的模块
-print("\n3. 尝试直接导入常用客户端...")
-try:
- from airsim import CarClient
- print(" ✓ 成功从 airsim 导入 CarClient")
-except ImportError as e:
- print(f" ✗ 导入失败: {e}")
\ No newline at end of file
diff --git a/src/Safe_navigation/logs/airsim_dqn_20251221_193518/events.out.tfevents.1766316918.YD.1800.0 b/src/Safe_navigation/logs/airsim_dqn_20251221_193518/events.out.tfevents.1766316918.YD.1800.0
deleted file mode 100644
index 039f139958d42e73ad137673c1b26c7bd8a465b0..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 88
zcmeZZfPjCKJmzx#6rOYUTi`86Dc+=_#LPTB*Rs^S5-X!1JuaP+)V$*SqNM!9q7=R2
h(%js{qDsB;qRf)iBE3|Qs`#|boYZ)T$m5PPM*w1oA|n6*
diff --git a/src/Safe_navigation/simulation_data_20251222_110207/report.txt b/src/Safe_navigation/simulation_data_20251222_110207/report.txt
new file mode 100644
index 0000000000..ccfb028ad9
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251222_110207/report.txt
@@ -0,0 +1,15 @@
+==================================================
+AirSim˳ݱ
+==================================================
+
+ʱ: 2025-12-22 11:02:29
+: PhysXCar
+ͷ֡: 0
+IMU: 0
+GPS: 0
+־Ŀ: 1
+
+ļ:
+ - report.txt
+ - simulation_log.json
+ - simulation_stats.json
diff --git a/src/Safe_navigation/simulation_data_20251222_110207/simulation_log.json b/src/Safe_navigation/simulation_data_20251222_110207/simulation_log.json
new file mode 100644
index 0000000000..f3f361ee3d
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251222_110207/simulation_log.json
@@ -0,0 +1,10 @@
+[
+ {
+ "frame": 1,
+ "timestamp": 1766372549.3210802,
+ "images": 0,
+ "imu_data": false,
+ "gps_data": false,
+ "vehicle_state": false
+ }
+]
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251222_110207/simulation_stats.json b/src/Safe_navigation/simulation_data_20251222_110207/simulation_stats.json
new file mode 100644
index 0000000000..dea4c41c9d
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251222_110207/simulation_stats.json
@@ -0,0 +1,8 @@
+{
+ "timestamp": "2025-12-22T11:02:29.322536",
+ "vehicle_name": "PhysXCar",
+ "camera_frames": 0,
+ "imu_samples": 0,
+ "gps_samples": 0,
+ "total_log_entries": 1
+}
\ No newline at end of file
From 74be3b62207de08aec11c7d4295d531242ac24f8 Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Mon, 22 Dec 2025 11:19:08 +0800
Subject: [PATCH 05/16] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?=
=?UTF-8?q?=E5=B9=B6=E8=BF=90=E8=A1=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/workspace.xml | 6 +-----
.../simulation_data_20251222_110207/report.txt | 15 ---------------
.../simulation_log.json | 10 ----------
.../simulation_stats.json | 8 --------
4 files changed, 1 insertion(+), 38 deletions(-)
delete mode 100644 src/Safe_navigation/simulation_data_20251222_110207/report.txt
delete mode 100644 src/Safe_navigation/simulation_data_20251222_110207/simulation_log.json
delete mode 100644 src/Safe_navigation/simulation_data_20251222_110207/simulation_stats.json
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 30e0b698e5..c9974c9ba1 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,11 +4,7 @@
-
-
-
-
-
+
diff --git a/src/Safe_navigation/simulation_data_20251222_110207/report.txt b/src/Safe_navigation/simulation_data_20251222_110207/report.txt
deleted file mode 100644
index ccfb028ad9..0000000000
--- a/src/Safe_navigation/simulation_data_20251222_110207/report.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-==================================================
-AirSim˳ݱ
-==================================================
-
-ʱ: 2025-12-22 11:02:29
-: PhysXCar
-ͷ֡: 0
-IMU: 0
-GPS: 0
-־Ŀ: 1
-
-ļ:
- - report.txt
- - simulation_log.json
- - simulation_stats.json
diff --git a/src/Safe_navigation/simulation_data_20251222_110207/simulation_log.json b/src/Safe_navigation/simulation_data_20251222_110207/simulation_log.json
deleted file mode 100644
index f3f361ee3d..0000000000
--- a/src/Safe_navigation/simulation_data_20251222_110207/simulation_log.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
- {
- "frame": 1,
- "timestamp": 1766372549.3210802,
- "images": 0,
- "imu_data": false,
- "gps_data": false,
- "vehicle_state": false
- }
-]
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251222_110207/simulation_stats.json b/src/Safe_navigation/simulation_data_20251222_110207/simulation_stats.json
deleted file mode 100644
index dea4c41c9d..0000000000
--- a/src/Safe_navigation/simulation_data_20251222_110207/simulation_stats.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "timestamp": "2025-12-22T11:02:29.322536",
- "vehicle_name": "PhysXCar",
- "camera_frames": 0,
- "imu_samples": 0,
- "gps_samples": 0,
- "total_log_entries": 1
-}
\ No newline at end of file
From bcee9c0cc10419623e74d8a0342595de0ae7deaa Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Mon, 22 Dec 2025 18:45:17 +0800
Subject: [PATCH 06/16] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?=
=?UTF-8?q?=E5=B9=B6=E8=BF=90=E8=A1=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
;了
---
src/Safe_navigation/run_main.py | 506 --------------------------------
1 file changed, 506 deletions(-)
delete mode 100644 src/Safe_navigation/run_main.py
diff --git a/src/Safe_navigation/run_main.py b/src/Safe_navigation/run_main.py
deleted file mode 100644
index ba4c30b1ac..0000000000
--- a/src/Safe_navigation/run_main.py
+++ /dev/null
@@ -1,506 +0,0 @@
-#!/usr/bin/env python3
-"""
-AirSimNH 无人车完整仿真控制脚本
-功能:连接仿真器、手动控制车辆、采集传感器数据、监控车辆状态
-"""
-
-import airsim
-import time
-import numpy as np
-import cv2
-import json
-import os
-from datetime import datetime
-import threading
-from collections import deque
-
-
-class AirSimNHCarSimulator:
- """AirSim无人车仿真主类"""
-
- def __init__(self, ip="127.0.0.1", port=41451, vehicle_name="PhysXCar"):
- """
- 初始化仿真器连接
-
- 参数:
- ip: AirSim服务器IP地址
- port: AirSim服务器端口
- vehicle_name: 车辆名称,需与settings.json中一致
- """
- self.ip = ip
- self.port = port
- self.vehicle_name = vehicle_name
- self.client = None
- self.is_connected = False
- self.is_api_control_enabled = False
- self.running = False
- self.data_log = []
- self.data_file = None
-
- # 传感器数据缓存
- self.sensor_data = {
- "camera": deque(maxlen=100),
- "imu": deque(maxlen=1000),
- "gps": deque(maxlen=1000),
- "lidar": deque(maxlen=100)
- }
-
- # 创建数据保存目录
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
- self.data_dir = f"simulation_data_{timestamp}"
- os.makedirs(self.data_dir, exist_ok=True)
-
- print(f"数据保存目录: {self.data_dir}")
-
- def connect(self):
- """连接到AirSim仿真器"""
- try:
- print(f"正在连接到AirSim仿真器 {self.ip}:{self.port}...")
-
- # 创建客户端连接
- self.client = airsim.CarClient(ip=self.ip, port=self.port)
- self.client.confirmConnection()
-
- # 检查车辆是否存在
- vehicles = self.client.listVehicles()
- if self.vehicle_name not in vehicles:
- print(f"警告: 车辆 '{self.vehicle_name}' 未找到,可用车辆: {vehicles}")
- # 尝试使用找到的第一个车辆
- if vehicles:
- self.vehicle_name = vehicles[0]
- print(f"使用车辆: {self.vehicle_name}")
-
- self.is_connected = True
- print("✓ 成功连接到AirSim仿真器!")
- return True
-
- except Exception as e:
- print(f"✗ 连接失败: {e}")
- print("请确保:")
- print("1. AirSimNH环境正在运行 (在虚幻引擎中启动)")
- print("2. settings.json配置正确")
- print("3. 网络连接正常")
- return False
-
- def enable_api_control(self, enable=True):
- """启用/禁用API控制"""
- try:
- self.client.enableApiControl(enable, vehicle_name=self.vehicle_name)
- self.is_api_control_enabled = enable
-
- if enable:
- print("✓ API控制已启用")
- # 重置控制到初始状态
- controls = airsim.CarControls()
- controls.throttle = 0
- controls.steering = 0
- controls.brake = 0
- controls.handbrake = False
- self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- else:
- print("✓ API控制已禁用")
-
- return True
-
- except Exception as e:
- print(f"✗ API控制设置失败: {e}")
- return False
-
- def get_vehicle_state(self):
- """获取完整的车辆状态信息"""
- try:
- state = self.client.getCarState(vehicle_name=self.vehicle_name)
-
- # 获取车辆物理信息
- kinematics = self.client.simGetVehiclePose(vehicle_name=self.vehicle_name)
-
- state_info = {
- "timestamp": time.time(),
- "speed_kmh": state.speed,
- "speed_ms": state.speed / 3.6,
- "position": {
- "x": kinematics.position.x_val,
- "y": kinematics.position.y_val,
- "z": kinematics.position.z_val
- },
- "orientation": {
- "w": kinematics.orientation.w_val,
- "x": kinematics.orientation.x_val,
- "y": kinematics.orientation.y_val,
- "z": kinematics.orientation.z_val
- },
- "gear": state.gear,
- "rpm": state.rpm,
- "max_rpm": state.maxrpm,
- "handbrake": state.handbrake,
- "collision": state.collision.has_collided,
- "collision_count": state.collision.collision_count
- }
-
- return state_info
-
- except Exception as e:
- print(f"获取车辆状态失败: {e}")
- return None
-
- def capture_camera_images(self, camera_names=["front", "back", "left", "right"]):
- """从多个摄像头捕获图像"""
- images = {}
-
- for cam_name in camera_names:
- try:
- # 请求RGB图像
- responses = self.client.simGetImages([
- airsim.ImageRequest(cam_name, airsim.ImageType.Scene, False, False)
- ], vehicle_name=self.vehicle_name)
-
- if responses and responses[0]:
- img_response = responses[0]
-
- # 将图像数据转换为numpy数组
- img1d = np.frombuffer(img_response.image_data_uint8, dtype=np.uint8)
- img_rgb = img1d.reshape(img_response.height, img_response.width, 3)
-
- # 保存图像到文件
- timestamp = datetime.now().strftime("%H%M%S_%f")[:-3]
- filename = f"{self.data_dir}/{cam_name}_{timestamp}.png"
- cv2.imwrite(filename, cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR))
-
- images[cam_name] = {
- "filename": filename,
- "shape": img_rgb.shape,
- "timestamp": time.time()
- }
-
- # 缓存数据
- self.sensor_data["camera"].append({
- "camera": cam_name,
- "timestamp": time.time(),
- "filename": filename
- })
-
- except Exception as e:
- print(f"摄像头 '{cam_name}' 捕获失败: {e}")
-
- return images
-
- def get_imu_data(self):
- """获取IMU传感器数据"""
- try:
- imu_data = self.client.getImuData(imu_name="Imu", vehicle_name=self.vehicle_name)
-
- data = {
- "timestamp": time.time(),
- "linear_acceleration": {
- "x": imu_data.linear_acceleration.x_val,
- "y": imu_data.linear_acceleration.y_val,
- "z": imu_data.linear_acceleration.z_val
- },
- "angular_velocity": {
- "x": imu_data.angular_velocity.x_val,
- "y": imu_data.angular_velocity.y_val,
- "z": imu_data.angular_velocity.z_val
- },
- "orientation": {
- "w": imu_data.orientation.w_val,
- "x": imu_data.orientation.x_val,
- "y": imu_data.orientation.y_val,
- "z": imu_data.orientation.z_val
- }
- }
-
- self.sensor_data["imu"].append(data)
- return data
-
- except Exception as e:
- print(f"获取IMU数据失败: {e}")
- return None
-
- def get_gps_data(self):
- """获取GPS数据"""
- try:
- gps_data = self.client.getGpsData(gps_name="Gps", vehicle_name=self.vehicle_name)
-
- data = {
- "timestamp": time.time(),
- "latitude": gps_data.gnss.geopoint.latitude,
- "longitude": gps_data.gnss.geopoint.longitude,
- "altitude": gps_data.gnss.geopoint.altitude,
- "velocity": {
- "x": gps_data.gnss.velocity.x_val,
- "y": gps_data.gnss.velocity.y_val,
- "z": gps_data.gnss.velocity.z_val
- }
- }
-
- self.sensor_data["gps"].append(data)
- return data
-
- except Exception as e:
- print(f"获取GPS数据失败: {e}")
- return None
-
- def manual_control_demo(self, duration=10):
- """
- 手动控制演示:前进、转向、停止
-
- 参数:
- duration: 演示总时长(秒)
- """
- if not self.is_connected or not self.is_api_control_enabled:
- print("错误: 请先连接并启用API控制")
- return False
-
- print(f"\n开始手动控制演示 ({duration}秒)...")
- print("操作序列: 加速 → 左转 → 右转 → 刹车停止")
-
- start_time = time.time()
- sequence = 0
-
- try:
- while time.time() - start_time < duration:
- elapsed = time.time() - start_time
-
- # 根据时间执行不同控制序列
- if elapsed < duration * 0.25: # 第一阶段:直线加速
- controls = airsim.CarControls()
- controls.throttle = 0.7
- controls.steering = 0.0
- self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- if sequence < 1:
- print(" 阶段1: 直线加速")
- sequence = 1
-
- elif elapsed < duration * 0.5: # 第二阶段:左转
- controls = airsim.CarControls()
- controls.throttle = 0.5
- controls.steering = 0.3 # 左转
- self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- if sequence < 2:
- print(" 阶段2: 左转")
- sequence = 2
-
- elif elapsed < duration * 0.75: # 第三阶段:右转
- controls = airsim.CarControls()
- controls.throttle = 0.5
- controls.steering = -0.3 # 右转
- self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- if sequence < 3:
- print(" 阶段3: 右转")
- sequence = 3
-
- else: # 第四阶段:减速停止
- controls = airsim.CarControls()
- controls.throttle = 0.0
- controls.brake = 1.0
- controls.steering = 0.0
- self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- if sequence < 4:
- print(" 阶段4: 刹车停止")
- sequence = 4
-
- # 实时显示车辆状态
- state = self.get_vehicle_state()
- if state:
- print(f"\r速度: {state['speed_kmh']:.1f} km/h | "
- f"位置: ({state['position']['x']:.1f}, "
- f"{state['position']['y']:.1f})", end="")
-
- # 采集传感器数据
- self.capture_camera_images(["front"]) # 只采集前摄像头
- self.get_imu_data()
- self.get_gps_data()
-
- time.sleep(0.1) # 控制频率 10Hz
-
- print("\n✓ 手动控制演示完成")
- return True
-
- except Exception as e:
- print(f"\n✗ 控制演示出错: {e}")
- return False
-
- def data_collection_demo(self, duration=5):
- """数据采集演示:采集所有传感器数据"""
- print(f"\n开始数据采集演示 ({duration}秒)...")
-
- start_time = time.time()
- frame_count = 0
-
- try:
- while time.time() - start_time < duration:
- frame_count += 1
-
- # 采集所有摄像头图像
- images = self.capture_camera_images()
-
- # 采集IMU数据
- imu_data = self.get_imu_data()
-
- # 采集GPS数据
- gps_data = self.get_gps_data()
-
- # 获取车辆状态
- vehicle_state = self.get_vehicle_state()
-
- # 记录到日志
- log_entry = {
- "frame": frame_count,
- "timestamp": time.time(),
- "images": len(images),
- "imu_data": imu_data is not None,
- "gps_data": gps_data is not None,
- "vehicle_state": vehicle_state is not None
- }
- self.data_log.append(log_entry)
-
- print(f"\r采集帧: {frame_count} | "
- f"图像: {len(images)} | "
- f"速度: {vehicle_state['speed_kmh'] if vehicle_state else 'N/A':.1f} km/h", end="")
-
- time.sleep(0.2) # 5Hz采集频率
-
- print(f"\n✓ 数据采集完成,共采集 {frame_count} 帧")
- return True
-
- except Exception as e:
- print(f"\n✗ 数据采集出错: {e}")
- return False
-
- def save_simulation_data(self):
- """保存所有仿真数据到文件"""
- try:
- # 保存车辆状态日志
- log_file = f"{self.data_dir}/simulation_log.json"
- with open(log_file, 'w') as f:
- json.dump(self.data_log, f, indent=2)
-
- # 保存传感器数据统计
- stats = {
- "timestamp": datetime.now().isoformat(),
- "vehicle_name": self.vehicle_name,
- "camera_frames": len(self.sensor_data["camera"]),
- "imu_samples": len(self.sensor_data["imu"]),
- "gps_samples": len(self.sensor_data["gps"]),
- "total_log_entries": len(self.data_log)
- }
-
- stats_file = f"{self.data_dir}/simulation_stats.json"
- with open(stats_file, 'w') as f:
- json.dump(stats, f, indent=2)
-
- # 生成数据报告
- report_file = f"{self.data_dir}/report.txt"
- with open(report_file, 'w') as f:
- f.write("=" * 50 + "\n")
- f.write("AirSim无人车仿真数据报告\n")
- f.write("=" * 50 + "\n\n")
- f.write(f"仿真时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
- f.write(f"车辆名称: {self.vehicle_name}\n")
- f.write(f"摄像头帧数: {stats['camera_frames']}\n")
- f.write(f"IMU采样数: {stats['imu_samples']}\n")
- f.write(f"GPS采样数: {stats['gps_samples']}\n")
- f.write(f"日志条目: {stats['total_log_entries']}\n\n")
- f.write("数据文件:\n")
- for file in os.listdir(self.data_dir):
- f.write(f" - {file}\n")
-
- print(f"\n✓ 仿真数据已保存到: {self.data_dir}")
- print(f" 日志文件: {log_file}")
- print(f" 统计数据: {stats_file}")
- print(f" 报告文件: {report_file}")
-
- return True
-
- except Exception as e:
- print(f"✗ 保存数据失败: {e}")
- return False
-
- def run_full_demo(self, control_duration=15, data_duration=8):
- """运行完整演示"""
- print("=" * 60)
- print("AirSimNH 无人车完整仿真演示")
- print("=" * 60)
-
- # 步骤1: 连接仿真器
- if not self.connect():
- return False
-
- try:
- # 步骤2: 启用API控制
- if not self.enable_api_control(True):
- return False
-
- # 步骤3: 手动控制演示
- if not self.manual_control_demo(control_duration):
- print("手动控制演示失败,继续其他演示...")
-
- # 短暂暂停,让车辆完全停止
- time.sleep(2)
-
- # 步骤4: 数据采集演示
- if not self.data_collection_demo(data_duration):
- print("数据采集演示失败,继续保存数据...")
-
- # 步骤5: 保存数据
- self.save_simulation_data()
-
- return True
-
- finally:
- # 步骤6: 清理和退出
- self.cleanup()
-
- def cleanup(self):
- """清理资源"""
- print("\n正在清理资源...")
-
- # 停止车辆
- if self.is_api_control_enabled:
- controls = airsim.CarControls()
- controls.brake = 1.0
- controls.handbrake = True
- try:
- self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- except:
- pass
-
- # 禁用API控制
- try:
- self.enable_api_control(False)
- except:
- pass
-
- print("✓ 清理完成")
-
-
-def main():
- """主函数"""
- # 创建仿真器实例
- simulator = AirSimNHCarSimulator(
- ip="127.0.0.1",
- port=41451,
- vehicle_name="PhysXCar"
- )
-
- # 运行完整演示
- try:
- simulator.run_full_demo(
- control_duration=20, # 控制演示时长(秒)
- data_duration=10 # 数据采集时长(秒)
- )
-
- print("\n" + "=" * 60)
- print("仿真演示完成!")
- print("=" * 60)
-
- except KeyboardInterrupt:
- print("\n\n仿真被用户中断")
- simulator.cleanup()
- except Exception as e:
- print(f"\n仿真出错: {e}")
- simulator.cleanup()
-
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
From e493b9554e7f0faca09a8fc2b55bef093c02a753 Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Mon, 22 Dec 2025 19:46:38 +0800
Subject: [PATCH 07/16] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?=
=?UTF-8?q?=E5=B9=B6=E8=BF=90=E8=A1=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/workspace.xml | 68 ---------------------------------------------
1 file changed, 68 deletions(-)
delete mode 100644 .idea/workspace.xml
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
deleted file mode 100644
index c9974c9ba1..0000000000
--- a/.idea/workspace.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- "associatedIndex": 3
-}
-
-
-
-
-
- {
- "keyToString": {
- "ModuleVcsDetector.initialDetectionPerformed": "true",
- "Python 测试.pytest (car.py 内).executor": "Run",
- "Python 测试.pytest (connect_airsim.py 内).executor": "Run",
- "Python 测试.pytest (test_airsim.py 内).executor": "Run",
- "Python.car.executor": "Run",
- "Python.check_version.executor": "Run",
- "Python.connect_airsim.executor": "Run",
- "Python.run_main.executor": "Run",
- "Python.代码.executor": "Run",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
- "RunOnceActivity.git.unshallow": "true",
- "git-widget-placeholder": "github",
- "ignore.virus.scanning.warn.message": "true",
- "last_opened_file_path": "C:/nn",
- "settings.editor.selected.configurable": "reference.settings.ide.settings.file-colors"
- }
-}
-
-
-
-
-
-
-
-
-
-
- 1766305867927
-
-
- 1766305867927
-
-
-
-
\ No newline at end of file
From 841ecf4043c1f9607224c56f7a2f8aa4a0cf7f40 Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Wed, 24 Dec 2025 17:12:33 +0800
Subject: [PATCH 08/16] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
无人车行驶距离更长,更稳定
---
src/Safe_navigation/run_main.py | 506 ++++++++++++++++++++++++++++++++
1 file changed, 506 insertions(+)
create mode 100644 src/Safe_navigation/run_main.py
diff --git a/src/Safe_navigation/run_main.py b/src/Safe_navigation/run_main.py
new file mode 100644
index 0000000000..ba4c30b1ac
--- /dev/null
+++ b/src/Safe_navigation/run_main.py
@@ -0,0 +1,506 @@
+#!/usr/bin/env python3
+"""
+AirSimNH 无人车完整仿真控制脚本
+功能:连接仿真器、手动控制车辆、采集传感器数据、监控车辆状态
+"""
+
+import airsim
+import time
+import numpy as np
+import cv2
+import json
+import os
+from datetime import datetime
+import threading
+from collections import deque
+
+
+class AirSimNHCarSimulator:
+ """AirSim无人车仿真主类"""
+
+ def __init__(self, ip="127.0.0.1", port=41451, vehicle_name="PhysXCar"):
+ """
+ 初始化仿真器连接
+
+ 参数:
+ ip: AirSim服务器IP地址
+ port: AirSim服务器端口
+ vehicle_name: 车辆名称,需与settings.json中一致
+ """
+ self.ip = ip
+ self.port = port
+ self.vehicle_name = vehicle_name
+ self.client = None
+ self.is_connected = False
+ self.is_api_control_enabled = False
+ self.running = False
+ self.data_log = []
+ self.data_file = None
+
+ # 传感器数据缓存
+ self.sensor_data = {
+ "camera": deque(maxlen=100),
+ "imu": deque(maxlen=1000),
+ "gps": deque(maxlen=1000),
+ "lidar": deque(maxlen=100)
+ }
+
+ # 创建数据保存目录
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ self.data_dir = f"simulation_data_{timestamp}"
+ os.makedirs(self.data_dir, exist_ok=True)
+
+ print(f"数据保存目录: {self.data_dir}")
+
+ def connect(self):
+ """连接到AirSim仿真器"""
+ try:
+ print(f"正在连接到AirSim仿真器 {self.ip}:{self.port}...")
+
+ # 创建客户端连接
+ self.client = airsim.CarClient(ip=self.ip, port=self.port)
+ self.client.confirmConnection()
+
+ # 检查车辆是否存在
+ vehicles = self.client.listVehicles()
+ if self.vehicle_name not in vehicles:
+ print(f"警告: 车辆 '{self.vehicle_name}' 未找到,可用车辆: {vehicles}")
+ # 尝试使用找到的第一个车辆
+ if vehicles:
+ self.vehicle_name = vehicles[0]
+ print(f"使用车辆: {self.vehicle_name}")
+
+ self.is_connected = True
+ print("✓ 成功连接到AirSim仿真器!")
+ return True
+
+ except Exception as e:
+ print(f"✗ 连接失败: {e}")
+ print("请确保:")
+ print("1. AirSimNH环境正在运行 (在虚幻引擎中启动)")
+ print("2. settings.json配置正确")
+ print("3. 网络连接正常")
+ return False
+
+ def enable_api_control(self, enable=True):
+ """启用/禁用API控制"""
+ try:
+ self.client.enableApiControl(enable, vehicle_name=self.vehicle_name)
+ self.is_api_control_enabled = enable
+
+ if enable:
+ print("✓ API控制已启用")
+ # 重置控制到初始状态
+ controls = airsim.CarControls()
+ controls.throttle = 0
+ controls.steering = 0
+ controls.brake = 0
+ controls.handbrake = False
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ else:
+ print("✓ API控制已禁用")
+
+ return True
+
+ except Exception as e:
+ print(f"✗ API控制设置失败: {e}")
+ return False
+
+ def get_vehicle_state(self):
+ """获取完整的车辆状态信息"""
+ try:
+ state = self.client.getCarState(vehicle_name=self.vehicle_name)
+
+ # 获取车辆物理信息
+ kinematics = self.client.simGetVehiclePose(vehicle_name=self.vehicle_name)
+
+ state_info = {
+ "timestamp": time.time(),
+ "speed_kmh": state.speed,
+ "speed_ms": state.speed / 3.6,
+ "position": {
+ "x": kinematics.position.x_val,
+ "y": kinematics.position.y_val,
+ "z": kinematics.position.z_val
+ },
+ "orientation": {
+ "w": kinematics.orientation.w_val,
+ "x": kinematics.orientation.x_val,
+ "y": kinematics.orientation.y_val,
+ "z": kinematics.orientation.z_val
+ },
+ "gear": state.gear,
+ "rpm": state.rpm,
+ "max_rpm": state.maxrpm,
+ "handbrake": state.handbrake,
+ "collision": state.collision.has_collided,
+ "collision_count": state.collision.collision_count
+ }
+
+ return state_info
+
+ except Exception as e:
+ print(f"获取车辆状态失败: {e}")
+ return None
+
+ def capture_camera_images(self, camera_names=["front", "back", "left", "right"]):
+ """从多个摄像头捕获图像"""
+ images = {}
+
+ for cam_name in camera_names:
+ try:
+ # 请求RGB图像
+ responses = self.client.simGetImages([
+ airsim.ImageRequest(cam_name, airsim.ImageType.Scene, False, False)
+ ], vehicle_name=self.vehicle_name)
+
+ if responses and responses[0]:
+ img_response = responses[0]
+
+ # 将图像数据转换为numpy数组
+ img1d = np.frombuffer(img_response.image_data_uint8, dtype=np.uint8)
+ img_rgb = img1d.reshape(img_response.height, img_response.width, 3)
+
+ # 保存图像到文件
+ timestamp = datetime.now().strftime("%H%M%S_%f")[:-3]
+ filename = f"{self.data_dir}/{cam_name}_{timestamp}.png"
+ cv2.imwrite(filename, cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR))
+
+ images[cam_name] = {
+ "filename": filename,
+ "shape": img_rgb.shape,
+ "timestamp": time.time()
+ }
+
+ # 缓存数据
+ self.sensor_data["camera"].append({
+ "camera": cam_name,
+ "timestamp": time.time(),
+ "filename": filename
+ })
+
+ except Exception as e:
+ print(f"摄像头 '{cam_name}' 捕获失败: {e}")
+
+ return images
+
+ def get_imu_data(self):
+ """获取IMU传感器数据"""
+ try:
+ imu_data = self.client.getImuData(imu_name="Imu", vehicle_name=self.vehicle_name)
+
+ data = {
+ "timestamp": time.time(),
+ "linear_acceleration": {
+ "x": imu_data.linear_acceleration.x_val,
+ "y": imu_data.linear_acceleration.y_val,
+ "z": imu_data.linear_acceleration.z_val
+ },
+ "angular_velocity": {
+ "x": imu_data.angular_velocity.x_val,
+ "y": imu_data.angular_velocity.y_val,
+ "z": imu_data.angular_velocity.z_val
+ },
+ "orientation": {
+ "w": imu_data.orientation.w_val,
+ "x": imu_data.orientation.x_val,
+ "y": imu_data.orientation.y_val,
+ "z": imu_data.orientation.z_val
+ }
+ }
+
+ self.sensor_data["imu"].append(data)
+ return data
+
+ except Exception as e:
+ print(f"获取IMU数据失败: {e}")
+ return None
+
+ def get_gps_data(self):
+ """获取GPS数据"""
+ try:
+ gps_data = self.client.getGpsData(gps_name="Gps", vehicle_name=self.vehicle_name)
+
+ data = {
+ "timestamp": time.time(),
+ "latitude": gps_data.gnss.geopoint.latitude,
+ "longitude": gps_data.gnss.geopoint.longitude,
+ "altitude": gps_data.gnss.geopoint.altitude,
+ "velocity": {
+ "x": gps_data.gnss.velocity.x_val,
+ "y": gps_data.gnss.velocity.y_val,
+ "z": gps_data.gnss.velocity.z_val
+ }
+ }
+
+ self.sensor_data["gps"].append(data)
+ return data
+
+ except Exception as e:
+ print(f"获取GPS数据失败: {e}")
+ return None
+
+ def manual_control_demo(self, duration=10):
+ """
+ 手动控制演示:前进、转向、停止
+
+ 参数:
+ duration: 演示总时长(秒)
+ """
+ if not self.is_connected or not self.is_api_control_enabled:
+ print("错误: 请先连接并启用API控制")
+ return False
+
+ print(f"\n开始手动控制演示 ({duration}秒)...")
+ print("操作序列: 加速 → 左转 → 右转 → 刹车停止")
+
+ start_time = time.time()
+ sequence = 0
+
+ try:
+ while time.time() - start_time < duration:
+ elapsed = time.time() - start_time
+
+ # 根据时间执行不同控制序列
+ if elapsed < duration * 0.25: # 第一阶段:直线加速
+ controls = airsim.CarControls()
+ controls.throttle = 0.7
+ controls.steering = 0.0
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ if sequence < 1:
+ print(" 阶段1: 直线加速")
+ sequence = 1
+
+ elif elapsed < duration * 0.5: # 第二阶段:左转
+ controls = airsim.CarControls()
+ controls.throttle = 0.5
+ controls.steering = 0.3 # 左转
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ if sequence < 2:
+ print(" 阶段2: 左转")
+ sequence = 2
+
+ elif elapsed < duration * 0.75: # 第三阶段:右转
+ controls = airsim.CarControls()
+ controls.throttle = 0.5
+ controls.steering = -0.3 # 右转
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ if sequence < 3:
+ print(" 阶段3: 右转")
+ sequence = 3
+
+ else: # 第四阶段:减速停止
+ controls = airsim.CarControls()
+ controls.throttle = 0.0
+ controls.brake = 1.0
+ controls.steering = 0.0
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ if sequence < 4:
+ print(" 阶段4: 刹车停止")
+ sequence = 4
+
+ # 实时显示车辆状态
+ state = self.get_vehicle_state()
+ if state:
+ print(f"\r速度: {state['speed_kmh']:.1f} km/h | "
+ f"位置: ({state['position']['x']:.1f}, "
+ f"{state['position']['y']:.1f})", end="")
+
+ # 采集传感器数据
+ self.capture_camera_images(["front"]) # 只采集前摄像头
+ self.get_imu_data()
+ self.get_gps_data()
+
+ time.sleep(0.1) # 控制频率 10Hz
+
+ print("\n✓ 手动控制演示完成")
+ return True
+
+ except Exception as e:
+ print(f"\n✗ 控制演示出错: {e}")
+ return False
+
+ def data_collection_demo(self, duration=5):
+ """数据采集演示:采集所有传感器数据"""
+ print(f"\n开始数据采集演示 ({duration}秒)...")
+
+ start_time = time.time()
+ frame_count = 0
+
+ try:
+ while time.time() - start_time < duration:
+ frame_count += 1
+
+ # 采集所有摄像头图像
+ images = self.capture_camera_images()
+
+ # 采集IMU数据
+ imu_data = self.get_imu_data()
+
+ # 采集GPS数据
+ gps_data = self.get_gps_data()
+
+ # 获取车辆状态
+ vehicle_state = self.get_vehicle_state()
+
+ # 记录到日志
+ log_entry = {
+ "frame": frame_count,
+ "timestamp": time.time(),
+ "images": len(images),
+ "imu_data": imu_data is not None,
+ "gps_data": gps_data is not None,
+ "vehicle_state": vehicle_state is not None
+ }
+ self.data_log.append(log_entry)
+
+ print(f"\r采集帧: {frame_count} | "
+ f"图像: {len(images)} | "
+ f"速度: {vehicle_state['speed_kmh'] if vehicle_state else 'N/A':.1f} km/h", end="")
+
+ time.sleep(0.2) # 5Hz采集频率
+
+ print(f"\n✓ 数据采集完成,共采集 {frame_count} 帧")
+ return True
+
+ except Exception as e:
+ print(f"\n✗ 数据采集出错: {e}")
+ return False
+
+ def save_simulation_data(self):
+ """保存所有仿真数据到文件"""
+ try:
+ # 保存车辆状态日志
+ log_file = f"{self.data_dir}/simulation_log.json"
+ with open(log_file, 'w') as f:
+ json.dump(self.data_log, f, indent=2)
+
+ # 保存传感器数据统计
+ stats = {
+ "timestamp": datetime.now().isoformat(),
+ "vehicle_name": self.vehicle_name,
+ "camera_frames": len(self.sensor_data["camera"]),
+ "imu_samples": len(self.sensor_data["imu"]),
+ "gps_samples": len(self.sensor_data["gps"]),
+ "total_log_entries": len(self.data_log)
+ }
+
+ stats_file = f"{self.data_dir}/simulation_stats.json"
+ with open(stats_file, 'w') as f:
+ json.dump(stats, f, indent=2)
+
+ # 生成数据报告
+ report_file = f"{self.data_dir}/report.txt"
+ with open(report_file, 'w') as f:
+ f.write("=" * 50 + "\n")
+ f.write("AirSim无人车仿真数据报告\n")
+ f.write("=" * 50 + "\n\n")
+ f.write(f"仿真时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
+ f.write(f"车辆名称: {self.vehicle_name}\n")
+ f.write(f"摄像头帧数: {stats['camera_frames']}\n")
+ f.write(f"IMU采样数: {stats['imu_samples']}\n")
+ f.write(f"GPS采样数: {stats['gps_samples']}\n")
+ f.write(f"日志条目: {stats['total_log_entries']}\n\n")
+ f.write("数据文件:\n")
+ for file in os.listdir(self.data_dir):
+ f.write(f" - {file}\n")
+
+ print(f"\n✓ 仿真数据已保存到: {self.data_dir}")
+ print(f" 日志文件: {log_file}")
+ print(f" 统计数据: {stats_file}")
+ print(f" 报告文件: {report_file}")
+
+ return True
+
+ except Exception as e:
+ print(f"✗ 保存数据失败: {e}")
+ return False
+
+ def run_full_demo(self, control_duration=15, data_duration=8):
+ """运行完整演示"""
+ print("=" * 60)
+ print("AirSimNH 无人车完整仿真演示")
+ print("=" * 60)
+
+ # 步骤1: 连接仿真器
+ if not self.connect():
+ return False
+
+ try:
+ # 步骤2: 启用API控制
+ if not self.enable_api_control(True):
+ return False
+
+ # 步骤3: 手动控制演示
+ if not self.manual_control_demo(control_duration):
+ print("手动控制演示失败,继续其他演示...")
+
+ # 短暂暂停,让车辆完全停止
+ time.sleep(2)
+
+ # 步骤4: 数据采集演示
+ if not self.data_collection_demo(data_duration):
+ print("数据采集演示失败,继续保存数据...")
+
+ # 步骤5: 保存数据
+ self.save_simulation_data()
+
+ return True
+
+ finally:
+ # 步骤6: 清理和退出
+ self.cleanup()
+
+ def cleanup(self):
+ """清理资源"""
+ print("\n正在清理资源...")
+
+ # 停止车辆
+ if self.is_api_control_enabled:
+ controls = airsim.CarControls()
+ controls.brake = 1.0
+ controls.handbrake = True
+ try:
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ except:
+ pass
+
+ # 禁用API控制
+ try:
+ self.enable_api_control(False)
+ except:
+ pass
+
+ print("✓ 清理完成")
+
+
+def main():
+ """主函数"""
+ # 创建仿真器实例
+ simulator = AirSimNHCarSimulator(
+ ip="127.0.0.1",
+ port=41451,
+ vehicle_name="PhysXCar"
+ )
+
+ # 运行完整演示
+ try:
+ simulator.run_full_demo(
+ control_duration=20, # 控制演示时长(秒)
+ data_duration=10 # 数据采集时长(秒)
+ )
+
+ print("\n" + "=" * 60)
+ print("仿真演示完成!")
+ print("=" * 60)
+
+ except KeyboardInterrupt:
+ print("\n\n仿真被用户中断")
+ simulator.cleanup()
+ except Exception as e:
+ print(f"\n仿真出错: {e}")
+ simulator.cleanup()
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
From 83363a4b4676a03dbd91644cbbeac69bd87480a0 Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Thu, 25 Dec 2025 03:21:18 +0800
Subject: [PATCH 09/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
解决了无人车往右撞墙的问题
---
src/Safe_navigation/run_main.py | 138 ++++++++++++++----
.../report.txt | 15 ++
.../simulation_log.json | 10 ++
.../simulation_stats.json | 8 +
.../report.txt | 16 ++
.../simulation_log.json | 10 ++
.../simulation_stats.json | 9 ++
.../report.txt | 15 ++
.../simulation_log.json | 10 ++
.../simulation_stats.json | 8 +
.../report.txt | 15 ++
.../simulation_log.json | 10 ++
.../simulation_stats.json | 8 +
.../report.txt | 15 ++
.../simulation_log.json | 10 ++
.../simulation_stats.json | 8 +
16 files changed, 277 insertions(+), 28 deletions(-)
create mode 100644 src/Safe_navigation/simulation_data_20251225_015806/report.txt
create mode 100644 src/Safe_navigation/simulation_data_20251225_015806/simulation_log.json
create mode 100644 src/Safe_navigation/simulation_data_20251225_015806/simulation_stats.json
create mode 100644 src/Safe_navigation/simulation_data_20251225_024958/report.txt
create mode 100644 src/Safe_navigation/simulation_data_20251225_024958/simulation_log.json
create mode 100644 src/Safe_navigation/simulation_data_20251225_024958/simulation_stats.json
create mode 100644 src/Safe_navigation/simulation_data_20251225_030801/report.txt
create mode 100644 src/Safe_navigation/simulation_data_20251225_030801/simulation_log.json
create mode 100644 src/Safe_navigation/simulation_data_20251225_030801/simulation_stats.json
create mode 100644 src/Safe_navigation/simulation_data_20251225_031408/report.txt
create mode 100644 src/Safe_navigation/simulation_data_20251225_031408/simulation_log.json
create mode 100644 src/Safe_navigation/simulation_data_20251225_031408/simulation_stats.json
create mode 100644 src/Safe_navigation/simulation_data_20251225_031927/report.txt
create mode 100644 src/Safe_navigation/simulation_data_20251225_031927/simulation_log.json
create mode 100644 src/Safe_navigation/simulation_data_20251225_031927/simulation_stats.json
diff --git a/src/Safe_navigation/run_main.py b/src/Safe_navigation/run_main.py
index ba4c30b1ac..d75b9f78c3 100644
--- a/src/Safe_navigation/run_main.py
+++ b/src/Safe_navigation/run_main.py
@@ -240,7 +240,7 @@ def get_gps_data(self):
print(f"获取GPS数据失败: {e}")
return None
- def manual_control_demo(self, duration=10):
+ def manual_control_demo(self, duration=20): # 增加总时长到20秒
"""
手动控制演示:前进、转向、停止
@@ -252,64 +252,146 @@ def manual_control_demo(self, duration=10):
return False
print(f"\n开始手动控制演示 ({duration}秒)...")
- print("操作序列: 加速 → 左转 → 右转 → 刹车停止")
+ print("操作序列: 直线加速 → 保持直线 → 缓左转 → 直线回正 → 缓右转 → 直线行驶 → 平滑刹车")
start_time = time.time()
sequence = 0
+ # 添加速度监控
+ current_speed = 0
+ max_speed_kmh = 40 # 提高最大速度到40km/h
+
try:
while time.time() - start_time < duration:
elapsed = time.time() - start_time
+ # 获取当前速度
+ state = self.get_vehicle_state()
+ if state:
+ current_speed = state['speed_kmh']
+ else:
+ current_speed = 0
+
# 根据时间执行不同控制序列
- if elapsed < duration * 0.25: # 第一阶段:直线加速
+ if elapsed < duration * 0.25: # 第一阶段:直线加速(25%时间)
controls = airsim.CarControls()
- controls.throttle = 0.7
+ # 初始加速使用较高油门
+ if elapsed < 3: # 前3秒全力加速
+ controls.throttle = 0.8
+ else: # 3秒后保持中等油门
+ controls.throttle = 0.6
controls.steering = 0.0
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
if sequence < 1:
- print(" 阶段1: 直线加速")
+ print(f" 阶段1: 直线加速 (目标速度: {max_speed_kmh} km/h)")
sequence = 1
- elif elapsed < duration * 0.5: # 第二阶段:左转
+ elif elapsed < duration * 0.35: # 第二阶段:保持直线行驶(10%时间)
controls = airsim.CarControls()
- controls.throttle = 0.5
- controls.steering = 0.3 # 左转
+ # 根据速度调整油门,保持稳定速度
+ if current_speed < max_speed_kmh * 0.7:
+ controls.throttle = 0.6
+ elif current_speed < max_speed_kmh:
+ controls.throttle = 0.5
+ else:
+ controls.throttle = 0.4 # 速度过高时减小油门
+ controls.steering = 0.0
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
if sequence < 2:
- print(" 阶段2: 左转")
+ print(f" 阶段2: 保持直线 (当前速度: {current_speed:.1f} km/h)")
sequence = 2
- elif elapsed < duration * 0.75: # 第三阶段:右转
+ elif elapsed < duration * 0.45: # 第三阶段:缓左转(10%时间)
controls = airsim.CarControls()
+ # 转向时减小油门
controls.throttle = 0.5
- controls.steering = -0.3 # 右转
+ # 使用小角度左转
+ controls.steering = 0.2
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
if sequence < 3:
- print(" 阶段3: 右转")
+ print(f" 阶段3: 缓左转 (速度: {current_speed:.1f} km/h)")
sequence = 3
- else: # 第四阶段:减速停止
+ elif elapsed < duration * 0.55: # 第四阶段:直线回正(10%时间)
controls = airsim.CarControls()
- controls.throttle = 0.0
- controls.brake = 1.0
- controls.steering = 0.0
+ controls.throttle = 0.5
+ controls.steering = 0.0 # 回正方向
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
if sequence < 4:
- print(" 阶段4: 刹车停止")
+ print(" 阶段4: 直线回正")
sequence = 4
+ elif elapsed < duration * 0.65: # 第五阶段:缓右转(10%时间)
+ controls = airsim.CarControls()
+ controls.throttle = 0.5
+ # 使用小角度右转
+ controls.steering = -0.2
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ if sequence < 5:
+ print(f" 阶段5: 缓右转 (速度: {current_speed:.1f} km/h)")
+ sequence = 5
+
+ elif elapsed < duration * 0.85: # 第六阶段:直线行驶并准备减速(20%时间)
+ controls = airsim.CarControls()
+ # 逐步减小油门
+ progress = (elapsed - duration * 0.65) / (duration * 0.2)
+ controls.throttle = max(0.2, 0.5 * (1.0 - progress))
+ controls.steering = 0.0
+ # 速度仍然较高时轻微刹车
+ if current_speed > 25:
+ controls.brake = 0.2
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ if sequence < 6:
+ print(" 阶段6: 直线行驶并准备减速")
+ sequence = 6
+
+ else: # 第七阶段:平滑刹车停止(15%时间)
+ controls = airsim.CarControls()
+ controls.throttle = 0.0
+ # 根据当前速度调整刹车力度
+ if current_speed > 20:
+ controls.brake = 0.8
+ elif current_speed > 10:
+ controls.brake = 0.6
+ else:
+ controls.brake = 1.0 # 低速时完全刹车
+ controls.steering = 0.0
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ if sequence < 7:
+ print(" 阶段7: 平滑刹车停止")
+ sequence = 7
+
# 实时显示车辆状态
- state = self.get_vehicle_state()
if state:
- print(f"\r速度: {state['speed_kmh']:.1f} km/h | "
- f"位置: ({state['position']['x']:.1f}, "
- f"{state['position']['y']:.1f})", end="")
-
- # 采集传感器数据
- self.capture_camera_images(["front"]) # 只采集前摄像头
- self.get_imu_data()
- self.get_gps_data()
+ # 显示转向状态
+ steering_status = ""
+ if controls.steering > 0.05:
+ steering_status = f"左转{controls.steering:.2f}"
+ elif controls.steering < -0.05:
+ steering_status = f"右转{abs(controls.steering):.2f}"
+ else:
+ steering_status = "直行"
+
+ # 显示油门/刹车状态
+ control_status = ""
+ if controls.brake > 0:
+ control_status = f"刹车:{controls.brake:.1f}"
+ else:
+ control_status = f"油门:{controls.throttle:.1f}"
+
+ # 计算行驶距离(假设从起点开始)
+ distance = (state['position']['x'] ** 2 + state['position']['y'] ** 2) ** 0.5
+
+ print(f"\r速度: {current_speed:5.1f} km/h | "
+ f"转向: {steering_status:8} | "
+ f"{control_status:8} | "
+ f"距离: {distance:6.1f} m", end="")
+
+ # 采集传感器数据(每0.3秒采集一次)
+ if elapsed % 0.3 < 0.05:
+ self.capture_camera_images(["front"])
+ self.get_imu_data()
+ self.get_gps_data()
time.sleep(0.1) # 控制频率 10Hz
@@ -416,7 +498,7 @@ def save_simulation_data(self):
print(f"✗ 保存数据失败: {e}")
return False
- def run_full_demo(self, control_duration=15, data_duration=8):
+ def run_full_demo(self, control_duration=25, data_duration=10):
"""运行完整演示"""
print("=" * 60)
print("AirSimNH 无人车完整仿真演示")
@@ -486,7 +568,7 @@ def main():
# 运行完整演示
try:
simulator.run_full_demo(
- control_duration=20, # 控制演示时长(秒)
+ control_duration=30, # 增加控制演示时长到30秒
data_duration=10 # 数据采集时长(秒)
)
diff --git a/src/Safe_navigation/simulation_data_20251225_015806/report.txt b/src/Safe_navigation/simulation_data_20251225_015806/report.txt
new file mode 100644
index 0000000000..166d37194b
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_015806/report.txt
@@ -0,0 +1,15 @@
+==================================================
+AirSim˳ݱ
+==================================================
+
+ʱ: 2025-12-25 01:58:28
+: PhysXCar
+ͷ֡: 0
+IMU: 0
+GPS: 0
+־Ŀ: 1
+
+ļ:
+ - report.txt
+ - simulation_log.json
+ - simulation_stats.json
diff --git a/src/Safe_navigation/simulation_data_20251225_015806/simulation_log.json b/src/Safe_navigation/simulation_data_20251225_015806/simulation_log.json
new file mode 100644
index 0000000000..d57765df91
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_015806/simulation_log.json
@@ -0,0 +1,10 @@
+[
+ {
+ "frame": 1,
+ "timestamp": 1766599108.978677,
+ "images": 0,
+ "imu_data": false,
+ "gps_data": false,
+ "vehicle_state": false
+ }
+]
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_015806/simulation_stats.json b/src/Safe_navigation/simulation_data_20251225_015806/simulation_stats.json
new file mode 100644
index 0000000000..c9f149f6d0
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_015806/simulation_stats.json
@@ -0,0 +1,8 @@
+{
+ "timestamp": "2025-12-25T01:58:28.979769",
+ "vehicle_name": "PhysXCar",
+ "camera_frames": 0,
+ "imu_samples": 0,
+ "gps_samples": 0,
+ "total_log_entries": 1
+}
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_024958/report.txt b/src/Safe_navigation/simulation_data_20251225_024958/report.txt
new file mode 100644
index 0000000000..93fbf2949a
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_024958/report.txt
@@ -0,0 +1,16 @@
+==================================================
+AirSim˳ݱ
+==================================================
+
+ʱ: 2025-12-25 02:50:20
+: PhysXCar
+ͷ֡: 0
+IMU: 0
+GPS: 0
+LiDARɨ: 0
+־Ŀ: 1
+
+ļ:
+ - report.txt
+ - simulation_log.json
+ - simulation_stats.json
diff --git a/src/Safe_navigation/simulation_data_20251225_024958/simulation_log.json b/src/Safe_navigation/simulation_data_20251225_024958/simulation_log.json
new file mode 100644
index 0000000000..004800fcaa
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_024958/simulation_log.json
@@ -0,0 +1,10 @@
+[
+ {
+ "frame": 1,
+ "timestamp": 1766602220.4502654,
+ "images": 0,
+ "imu_data": false,
+ "gps_data": false,
+ "vehicle_state": false
+ }
+]
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_024958/simulation_stats.json b/src/Safe_navigation/simulation_data_20251225_024958/simulation_stats.json
new file mode 100644
index 0000000000..0ceb9e7a22
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_024958/simulation_stats.json
@@ -0,0 +1,9 @@
+{
+ "timestamp": "2025-12-25T02:50:20.451376",
+ "vehicle_name": "PhysXCar",
+ "camera_frames": 0,
+ "imu_samples": 0,
+ "gps_samples": 0,
+ "lidar_scans": 0,
+ "total_log_entries": 1
+}
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_030801/report.txt b/src/Safe_navigation/simulation_data_20251225_030801/report.txt
new file mode 100644
index 0000000000..14a53543c5
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_030801/report.txt
@@ -0,0 +1,15 @@
+==================================================
+AirSim˳ݱ
+==================================================
+
+ʱ: 2025-12-25 03:08:23
+: PhysXCar
+ͷ֡: 0
+IMU: 0
+GPS: 0
+־Ŀ: 1
+
+ļ:
+ - report.txt
+ - simulation_log.json
+ - simulation_stats.json
diff --git a/src/Safe_navigation/simulation_data_20251225_030801/simulation_log.json b/src/Safe_navigation/simulation_data_20251225_030801/simulation_log.json
new file mode 100644
index 0000000000..29fb10ca4b
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_030801/simulation_log.json
@@ -0,0 +1,10 @@
+[
+ {
+ "frame": 1,
+ "timestamp": 1766603303.6942003,
+ "images": 0,
+ "imu_data": false,
+ "gps_data": false,
+ "vehicle_state": false
+ }
+]
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_030801/simulation_stats.json b/src/Safe_navigation/simulation_data_20251225_030801/simulation_stats.json
new file mode 100644
index 0000000000..1bbacc777a
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_030801/simulation_stats.json
@@ -0,0 +1,8 @@
+{
+ "timestamp": "2025-12-25T03:08:23.695242",
+ "vehicle_name": "PhysXCar",
+ "camera_frames": 0,
+ "imu_samples": 0,
+ "gps_samples": 0,
+ "total_log_entries": 1
+}
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_031408/report.txt b/src/Safe_navigation/simulation_data_20251225_031408/report.txt
new file mode 100644
index 0000000000..f1b109abc7
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_031408/report.txt
@@ -0,0 +1,15 @@
+==================================================
+AirSim˳ݱ
+==================================================
+
+ʱ: 2025-12-25 03:14:30
+: PhysXCar
+ͷ֡: 0
+IMU: 0
+GPS: 0
+־Ŀ: 1
+
+ļ:
+ - report.txt
+ - simulation_log.json
+ - simulation_stats.json
diff --git a/src/Safe_navigation/simulation_data_20251225_031408/simulation_log.json b/src/Safe_navigation/simulation_data_20251225_031408/simulation_log.json
new file mode 100644
index 0000000000..4d2a766ee3
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_031408/simulation_log.json
@@ -0,0 +1,10 @@
+[
+ {
+ "frame": 1,
+ "timestamp": 1766603670.6398323,
+ "images": 0,
+ "imu_data": false,
+ "gps_data": false,
+ "vehicle_state": false
+ }
+]
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_031408/simulation_stats.json b/src/Safe_navigation/simulation_data_20251225_031408/simulation_stats.json
new file mode 100644
index 0000000000..c2ad4e6dcf
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_031408/simulation_stats.json
@@ -0,0 +1,8 @@
+{
+ "timestamp": "2025-12-25T03:14:30.641265",
+ "vehicle_name": "PhysXCar",
+ "camera_frames": 0,
+ "imu_samples": 0,
+ "gps_samples": 0,
+ "total_log_entries": 1
+}
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_031927/report.txt b/src/Safe_navigation/simulation_data_20251225_031927/report.txt
new file mode 100644
index 0000000000..48115474c0
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_031927/report.txt
@@ -0,0 +1,15 @@
+==================================================
+AirSim˳ݱ
+==================================================
+
+ʱ: 2025-12-25 03:19:59
+: PhysXCar
+ͷ֡: 0
+IMU: 0
+GPS: 0
+־Ŀ: 1
+
+ļ:
+ - report.txt
+ - simulation_log.json
+ - simulation_stats.json
diff --git a/src/Safe_navigation/simulation_data_20251225_031927/simulation_log.json b/src/Safe_navigation/simulation_data_20251225_031927/simulation_log.json
new file mode 100644
index 0000000000..e0d843e48c
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_031927/simulation_log.json
@@ -0,0 +1,10 @@
+[
+ {
+ "frame": 1,
+ "timestamp": 1766603999.9366477,
+ "images": 0,
+ "imu_data": false,
+ "gps_data": false,
+ "vehicle_state": false
+ }
+]
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_031927/simulation_stats.json b/src/Safe_navigation/simulation_data_20251225_031927/simulation_stats.json
new file mode 100644
index 0000000000..a5b501f2bf
--- /dev/null
+++ b/src/Safe_navigation/simulation_data_20251225_031927/simulation_stats.json
@@ -0,0 +1,8 @@
+{
+ "timestamp": "2025-12-25T03:19:59.937658",
+ "vehicle_name": "PhysXCar",
+ "camera_frames": 0,
+ "imu_samples": 0,
+ "gps_samples": 0,
+ "total_log_entries": 1
+}
\ No newline at end of file
From 086a7eb7554279f1f317dff4ec458d04f68a989c Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Thu, 25 Dec 2025 03:31:55 +0800
Subject: [PATCH 10/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
优化了无人车走一段路往右偏移的问题
---
.../simulation_data_20251225_015806/report.txt | 15 ---------------
.../simulation_log.json | 10 ----------
.../simulation_stats.json | 8 --------
.../simulation_data_20251225_024958/report.txt | 16 ----------------
.../simulation_log.json | 10 ----------
.../simulation_stats.json | 9 ---------
.../simulation_data_20251225_030801/report.txt | 15 ---------------
.../simulation_log.json | 10 ----------
.../simulation_stats.json | 8 --------
.../simulation_data_20251225_031408/report.txt | 15 ---------------
.../simulation_log.json | 10 ----------
.../simulation_stats.json | 8 --------
.../simulation_data_20251225_031927/report.txt | 15 ---------------
.../simulation_log.json | 10 ----------
.../simulation_stats.json | 8 --------
15 files changed, 167 deletions(-)
delete mode 100644 src/Safe_navigation/simulation_data_20251225_015806/report.txt
delete mode 100644 src/Safe_navigation/simulation_data_20251225_015806/simulation_log.json
delete mode 100644 src/Safe_navigation/simulation_data_20251225_015806/simulation_stats.json
delete mode 100644 src/Safe_navigation/simulation_data_20251225_024958/report.txt
delete mode 100644 src/Safe_navigation/simulation_data_20251225_024958/simulation_log.json
delete mode 100644 src/Safe_navigation/simulation_data_20251225_024958/simulation_stats.json
delete mode 100644 src/Safe_navigation/simulation_data_20251225_030801/report.txt
delete mode 100644 src/Safe_navigation/simulation_data_20251225_030801/simulation_log.json
delete mode 100644 src/Safe_navigation/simulation_data_20251225_030801/simulation_stats.json
delete mode 100644 src/Safe_navigation/simulation_data_20251225_031408/report.txt
delete mode 100644 src/Safe_navigation/simulation_data_20251225_031408/simulation_log.json
delete mode 100644 src/Safe_navigation/simulation_data_20251225_031408/simulation_stats.json
delete mode 100644 src/Safe_navigation/simulation_data_20251225_031927/report.txt
delete mode 100644 src/Safe_navigation/simulation_data_20251225_031927/simulation_log.json
delete mode 100644 src/Safe_navigation/simulation_data_20251225_031927/simulation_stats.json
diff --git a/src/Safe_navigation/simulation_data_20251225_015806/report.txt b/src/Safe_navigation/simulation_data_20251225_015806/report.txt
deleted file mode 100644
index 166d37194b..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_015806/report.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-==================================================
-AirSim˳ݱ
-==================================================
-
-ʱ: 2025-12-25 01:58:28
-: PhysXCar
-ͷ֡: 0
-IMU: 0
-GPS: 0
-־Ŀ: 1
-
-ļ:
- - report.txt
- - simulation_log.json
- - simulation_stats.json
diff --git a/src/Safe_navigation/simulation_data_20251225_015806/simulation_log.json b/src/Safe_navigation/simulation_data_20251225_015806/simulation_log.json
deleted file mode 100644
index d57765df91..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_015806/simulation_log.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
- {
- "frame": 1,
- "timestamp": 1766599108.978677,
- "images": 0,
- "imu_data": false,
- "gps_data": false,
- "vehicle_state": false
- }
-]
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_015806/simulation_stats.json b/src/Safe_navigation/simulation_data_20251225_015806/simulation_stats.json
deleted file mode 100644
index c9f149f6d0..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_015806/simulation_stats.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "timestamp": "2025-12-25T01:58:28.979769",
- "vehicle_name": "PhysXCar",
- "camera_frames": 0,
- "imu_samples": 0,
- "gps_samples": 0,
- "total_log_entries": 1
-}
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_024958/report.txt b/src/Safe_navigation/simulation_data_20251225_024958/report.txt
deleted file mode 100644
index 93fbf2949a..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_024958/report.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-==================================================
-AirSim˳ݱ
-==================================================
-
-ʱ: 2025-12-25 02:50:20
-: PhysXCar
-ͷ֡: 0
-IMU: 0
-GPS: 0
-LiDARɨ: 0
-־Ŀ: 1
-
-ļ:
- - report.txt
- - simulation_log.json
- - simulation_stats.json
diff --git a/src/Safe_navigation/simulation_data_20251225_024958/simulation_log.json b/src/Safe_navigation/simulation_data_20251225_024958/simulation_log.json
deleted file mode 100644
index 004800fcaa..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_024958/simulation_log.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
- {
- "frame": 1,
- "timestamp": 1766602220.4502654,
- "images": 0,
- "imu_data": false,
- "gps_data": false,
- "vehicle_state": false
- }
-]
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_024958/simulation_stats.json b/src/Safe_navigation/simulation_data_20251225_024958/simulation_stats.json
deleted file mode 100644
index 0ceb9e7a22..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_024958/simulation_stats.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "timestamp": "2025-12-25T02:50:20.451376",
- "vehicle_name": "PhysXCar",
- "camera_frames": 0,
- "imu_samples": 0,
- "gps_samples": 0,
- "lidar_scans": 0,
- "total_log_entries": 1
-}
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_030801/report.txt b/src/Safe_navigation/simulation_data_20251225_030801/report.txt
deleted file mode 100644
index 14a53543c5..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_030801/report.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-==================================================
-AirSim˳ݱ
-==================================================
-
-ʱ: 2025-12-25 03:08:23
-: PhysXCar
-ͷ֡: 0
-IMU: 0
-GPS: 0
-־Ŀ: 1
-
-ļ:
- - report.txt
- - simulation_log.json
- - simulation_stats.json
diff --git a/src/Safe_navigation/simulation_data_20251225_030801/simulation_log.json b/src/Safe_navigation/simulation_data_20251225_030801/simulation_log.json
deleted file mode 100644
index 29fb10ca4b..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_030801/simulation_log.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
- {
- "frame": 1,
- "timestamp": 1766603303.6942003,
- "images": 0,
- "imu_data": false,
- "gps_data": false,
- "vehicle_state": false
- }
-]
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_030801/simulation_stats.json b/src/Safe_navigation/simulation_data_20251225_030801/simulation_stats.json
deleted file mode 100644
index 1bbacc777a..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_030801/simulation_stats.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "timestamp": "2025-12-25T03:08:23.695242",
- "vehicle_name": "PhysXCar",
- "camera_frames": 0,
- "imu_samples": 0,
- "gps_samples": 0,
- "total_log_entries": 1
-}
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_031408/report.txt b/src/Safe_navigation/simulation_data_20251225_031408/report.txt
deleted file mode 100644
index f1b109abc7..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_031408/report.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-==================================================
-AirSim˳ݱ
-==================================================
-
-ʱ: 2025-12-25 03:14:30
-: PhysXCar
-ͷ֡: 0
-IMU: 0
-GPS: 0
-־Ŀ: 1
-
-ļ:
- - report.txt
- - simulation_log.json
- - simulation_stats.json
diff --git a/src/Safe_navigation/simulation_data_20251225_031408/simulation_log.json b/src/Safe_navigation/simulation_data_20251225_031408/simulation_log.json
deleted file mode 100644
index 4d2a766ee3..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_031408/simulation_log.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
- {
- "frame": 1,
- "timestamp": 1766603670.6398323,
- "images": 0,
- "imu_data": false,
- "gps_data": false,
- "vehicle_state": false
- }
-]
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_031408/simulation_stats.json b/src/Safe_navigation/simulation_data_20251225_031408/simulation_stats.json
deleted file mode 100644
index c2ad4e6dcf..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_031408/simulation_stats.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "timestamp": "2025-12-25T03:14:30.641265",
- "vehicle_name": "PhysXCar",
- "camera_frames": 0,
- "imu_samples": 0,
- "gps_samples": 0,
- "total_log_entries": 1
-}
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_031927/report.txt b/src/Safe_navigation/simulation_data_20251225_031927/report.txt
deleted file mode 100644
index 48115474c0..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_031927/report.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-==================================================
-AirSim˳ݱ
-==================================================
-
-ʱ: 2025-12-25 03:19:59
-: PhysXCar
-ͷ֡: 0
-IMU: 0
-GPS: 0
-־Ŀ: 1
-
-ļ:
- - report.txt
- - simulation_log.json
- - simulation_stats.json
diff --git a/src/Safe_navigation/simulation_data_20251225_031927/simulation_log.json b/src/Safe_navigation/simulation_data_20251225_031927/simulation_log.json
deleted file mode 100644
index e0d843e48c..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_031927/simulation_log.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
- {
- "frame": 1,
- "timestamp": 1766603999.9366477,
- "images": 0,
- "imu_data": false,
- "gps_data": false,
- "vehicle_state": false
- }
-]
\ No newline at end of file
diff --git a/src/Safe_navigation/simulation_data_20251225_031927/simulation_stats.json b/src/Safe_navigation/simulation_data_20251225_031927/simulation_stats.json
deleted file mode 100644
index a5b501f2bf..0000000000
--- a/src/Safe_navigation/simulation_data_20251225_031927/simulation_stats.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "timestamp": "2025-12-25T03:19:59.937658",
- "vehicle_name": "PhysXCar",
- "camera_frames": 0,
- "imu_samples": 0,
- "gps_samples": 0,
- "total_log_entries": 1
-}
\ No newline at end of file
From b4c91d7ccae835c9ba93a397246b05203256632e Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Thu, 25 Dec 2025 20:48:25 +0800
Subject: [PATCH 11/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/Safe_navigation/run_main.py | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/Safe_navigation/run_main.py b/src/Safe_navigation/run_main.py
index d75b9f78c3..5d55f35966 100644
--- a/src/Safe_navigation/run_main.py
+++ b/src/Safe_navigation/run_main.py
@@ -240,7 +240,7 @@ def get_gps_data(self):
print(f"获取GPS数据失败: {e}")
return None
- def manual_control_demo(self, duration=20): # 增加总时长到20秒
+ def manual_control_demo(self, duration=20):
"""
手动控制演示:前进、转向、停止
@@ -257,6 +257,9 @@ def manual_control_demo(self, duration=20): # 增加总时长到20秒
start_time = time.time()
sequence = 0
+ # 添加初始控制变量,避免未定义错误
+ controls = airsim.CarControls()
+
# 添加速度监控
current_speed = 0
max_speed_kmh = 40 # 提高最大速度到40km/h
@@ -388,7 +391,9 @@ def manual_control_demo(self, duration=20): # 增加总时长到20秒
f"距离: {distance:6.1f} m", end="")
# 采集传感器数据(每0.3秒采集一次)
- if elapsed % 0.3 < 0.05:
+ # 使用计数器替代浮点数取模,避免浮点精度问题
+ frame_counter = int(elapsed * 10) # 10Hz循环
+ if frame_counter % 3 == 0: # 每3帧采集一次 = 0.3秒
self.capture_camera_images(["front"])
self.get_imu_data()
self.get_gps_data()
From 4bf88876b8da937d499b8e65ee2bcd70111f994a Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Fri, 26 Dec 2025 04:03:32 +0800
Subject: [PATCH 12/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/workspace.xml | 53 +++++++++++++++++++++++++++++++++
src/Safe_navigation/run_main.py | 4 +--
2 files changed, 55 insertions(+), 2 deletions(-)
create mode 100644 .idea/workspace.xml
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000000..04bd0bd940
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1766691828502
+
+
+ 1766691828502
+
+
+
+
\ No newline at end of file
diff --git a/src/Safe_navigation/run_main.py b/src/Safe_navigation/run_main.py
index 5d55f35966..ec94c94dab 100644
--- a/src/Safe_navigation/run_main.py
+++ b/src/Safe_navigation/run_main.py
@@ -503,7 +503,7 @@ def save_simulation_data(self):
print(f"✗ 保存数据失败: {e}")
return False
- def run_full_demo(self, control_duration=25, data_duration=10):
+ def run_full_demo(self, control_duration=20, data_duration=10):
"""运行完整演示"""
print("=" * 60)
print("AirSimNH 无人车完整仿真演示")
@@ -573,7 +573,7 @@ def main():
# 运行完整演示
try:
simulator.run_full_demo(
- control_duration=30, # 增加控制演示时长到30秒
+ control_duration=20, # 使用20秒控制演示时长
data_duration=10 # 数据采集时长(秒)
)
From 3816b42d08bf33e5dd45ec4c2caab7268411be59 Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Fri, 26 Dec 2025 04:35:29 +0800
Subject: [PATCH 13/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/workspace.xml | 53 ------
src/Safe_navigation/run_main.py | 288 ++++++++++++++++++++++++++------
2 files changed, 233 insertions(+), 108 deletions(-)
delete mode 100644 .idea/workspace.xml
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
deleted file mode 100644
index 04bd0bd940..0000000000
--- a/.idea/workspace.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1766691828502
-
-
- 1766691828502
-
-
-
-
\ No newline at end of file
diff --git a/src/Safe_navigation/run_main.py b/src/Safe_navigation/run_main.py
index ec94c94dab..866573ee6d 100644
--- a/src/Safe_navigation/run_main.py
+++ b/src/Safe_navigation/run_main.py
@@ -13,6 +13,7 @@
from datetime import datetime
import threading
from collections import deque
+import math
class AirSimNHCarSimulator:
@@ -37,6 +38,11 @@ def __init__(self, ip="127.0.0.1", port=41451, vehicle_name="PhysXCar"):
self.data_log = []
self.data_file = None
+ # 车辆状态跟踪
+ self.initial_position = None
+ self.initial_yaw = None
+ self.path_history = []
+
# 传感器数据缓存
self.sensor_data = {
"camera": deque(maxlen=100),
@@ -72,6 +78,13 @@ def connect(self):
self.is_connected = True
print("✓ 成功连接到AirSim仿真器!")
+
+ # 获取初始位置和方向
+ self.initial_position = self.get_position()
+ self.initial_yaw = self.get_yaw()
+ print(f"初始位置: {self.initial_position}")
+ print(f"初始偏航角: {self.initial_yaw:.2f}°")
+
return True
except Exception as e:
@@ -82,6 +95,44 @@ def connect(self):
print("3. 网络连接正常")
return False
+ def get_position(self):
+ """获取车辆位置"""
+ try:
+ kinematics = self.client.simGetVehiclePose(vehicle_name=self.vehicle_name)
+ return {
+ "x": kinematics.position.x_val,
+ "y": kinematics.position.y_val,
+ "z": kinematics.position.z_val
+ }
+ except:
+ return {"x": 0, "y": 0, "z": 0}
+
+ def get_yaw(self):
+ """获取车辆偏航角(绕Z轴的旋转角度)"""
+ try:
+ kinematics = self.client.simGetVehiclePose(vehicle_name=self.vehicle_name)
+ orientation = kinematics.orientation
+
+ # 将四元数转换为欧拉角
+ # 使用四元数到欧拉角的转换公式
+ q0, q1, q2, q3 = orientation.w_val, orientation.x_val, orientation.y_val, orientation.z_val
+
+ # 计算偏航角 (yaw)
+ siny_cosp = 2 * (q0 * q3 + q1 * q2)
+ cosy_cosp = 1 - 2 * (q2 * q2 + q3 * q3)
+ yaw = math.atan2(siny_cosp, cosy_cosp)
+
+ # 转换为角度
+ yaw_deg = math.degrees(yaw)
+
+ # 标准化到0-360度
+ if yaw_deg < 0:
+ yaw_deg += 360
+
+ return yaw_deg
+ except:
+ return 0.0
+
def enable_api_control(self, enable=True):
"""启用/禁用API控制"""
try:
@@ -114,15 +165,34 @@ def get_vehicle_state(self):
# 获取车辆物理信息
kinematics = self.client.simGetVehiclePose(vehicle_name=self.vehicle_name)
+ # 获取偏航角
+ yaw = self.get_yaw()
+
+ # 获取当前位置
+ current_position = {
+ "x": kinematics.position.x_val,
+ "y": kinematics.position.y_val,
+ "z": kinematics.position.z_val
+ }
+
+ # 记录路径历史
+ self.path_history.append({
+ "timestamp": time.time(),
+ "position": current_position.copy(),
+ "yaw": yaw,
+ "speed": state.speed
+ })
+
+ # 只保留最近100个点
+ if len(self.path_history) > 100:
+ self.path_history.pop(0)
+
state_info = {
"timestamp": time.time(),
"speed_kmh": state.speed,
"speed_ms": state.speed / 3.6,
- "position": {
- "x": kinematics.position.x_val,
- "y": kinematics.position.y_val,
- "z": kinematics.position.z_val
- },
+ "position": current_position,
+ "yaw": yaw,
"orientation": {
"w": kinematics.orientation.w_val,
"x": kinematics.orientation.x_val,
@@ -240,9 +310,46 @@ def get_gps_data(self):
print(f"获取GPS数据失败: {e}")
return None
+ def calculate_path_deviation(self):
+ """计算路径偏离程度"""
+ if len(self.path_history) < 10:
+ return 0, 0, 0
+
+ # 计算最近路径点的平均位置和方向
+ recent_points = self.path_history[-10:]
+
+ # 计算位置偏离(与起始点的直线距离)
+ start_pos = self.initial_position
+ current_pos = recent_points[-1]["position"]
+
+ pos_deviation = math.sqrt(
+ (current_pos["x"] - start_pos["x"]) ** 2 +
+ (current_pos["y"] - start_pos["y"]) ** 2
+ )
+
+ # 计算角度偏离(与初始角度的差异)
+ current_yaw = recent_points[-1]["yaw"]
+ yaw_deviation = abs(current_yaw - self.initial_yaw)
+ if yaw_deviation > 180:
+ yaw_deviation = 360 - yaw_deviation
+
+ # 计算直线性(最近路径点的标准差)
+ x_coords = [p["position"]["x"] for p in recent_points]
+ y_coords = [p["position"]["y"] for p in recent_points]
+
+ if len(x_coords) > 1:
+ x_std = np.std(x_coords)
+ y_std = np.std(y_coords)
+ linearity = math.sqrt(x_std ** 2 + y_std ** 2)
+ else:
+ linearity = 0
+
+ return pos_deviation, yaw_deviation, linearity
+
def manual_control_demo(self, duration=20):
"""
手动控制演示:前进、转向、停止
+ 优化控制逻辑,防止车辆偏移
参数:
duration: 演示总时长(秒)
@@ -257,12 +364,20 @@ def manual_control_demo(self, duration=20):
start_time = time.time()
sequence = 0
- # 添加初始控制变量,避免未定义错误
+ # 添加初始控制变量
controls = airsim.CarControls()
# 添加速度监控
current_speed = 0
- max_speed_kmh = 40 # 提高最大速度到40km/h
+ max_speed_kmh = 30 # 降低最大速度到30km/h以提高稳定性
+
+ # 转向控制参数
+ steering_correction = 0.0
+ last_yaw = self.get_yaw()
+
+ # 偏移检测和修正
+ deviation_history = []
+ last_correction_time = 0
try:
while time.time() - start_time < duration:
@@ -272,33 +387,51 @@ def manual_control_demo(self, duration=20):
state = self.get_vehicle_state()
if state:
current_speed = state['speed_kmh']
+ current_yaw = state.get('yaw', 0)
else:
current_speed = 0
+ current_yaw = last_yaw
+
+ # 计算路径偏离
+ pos_dev, yaw_dev, linearity = self.calculate_path_deviation()
+ deviation_history.append({
+ "time": elapsed,
+ "pos_dev": pos_dev,
+ "yaw_dev": yaw_dev,
+ "linearity": linearity
+ })
# 根据时间执行不同控制序列
if elapsed < duration * 0.25: # 第一阶段:直线加速(25%时间)
controls = airsim.CarControls()
- # 初始加速使用较高油门
- if elapsed < 3: # 前3秒全力加速
- controls.throttle = 0.8
- else: # 3秒后保持中等油门
- controls.throttle = 0.6
+ # 使用温和的加速曲线
+ if elapsed < 2: # 前2秒逐渐加速
+ controls.throttle = 0.5 * (elapsed / 2)
+ else: # 2秒后保持中等油门
+ controls.throttle = 0.5
controls.steering = 0.0
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
if sequence < 1:
print(f" 阶段1: 直线加速 (目标速度: {max_speed_kmh} km/h)")
sequence = 1
- elif elapsed < duration * 0.35: # 第二阶段:保持直线行驶(10%时间)
+ elif elapsed < duration * 0.35: # 第二阶段:保持直线(10%时间)
controls = airsim.CarControls()
# 根据速度调整油门,保持稳定速度
- if current_speed < max_speed_kmh * 0.7:
- controls.throttle = 0.6
- elif current_speed < max_speed_kmh:
+ if current_speed < max_speed_kmh * 0.8:
controls.throttle = 0.5
+ elif current_speed < max_speed_kmh:
+ controls.throttle = 0.4
else:
- controls.throttle = 0.4 # 速度过高时减小油门
- controls.steering = 0.0
+ controls.throttle = 0.3
+
+ # 轻微修正转向,保持直线
+ if yaw_dev > 2.0: # 如果偏航角偏差大于2度
+ steering_correction = -0.05 * (yaw_dev / 10)
+ else:
+ steering_correction = 0.0
+
+ controls.steering = steering_correction
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
if sequence < 2:
print(f" 阶段2: 保持直线 (当前速度: {current_speed:.1f} km/h)")
@@ -307,9 +440,9 @@ def manual_control_demo(self, duration=20):
elif elapsed < duration * 0.45: # 第三阶段:缓左转(10%时间)
controls = airsim.CarControls()
# 转向时减小油门
- controls.throttle = 0.5
- # 使用小角度左转
- controls.steering = 0.2
+ controls.throttle = 0.4
+ # 使用非常小的角度左转
+ controls.steering = 0.1 # 减小转向角度
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
if sequence < 3:
print(f" 阶段3: 缓左转 (速度: {current_speed:.1f} km/h)")
@@ -317,18 +450,28 @@ def manual_control_demo(self, duration=20):
elif elapsed < duration * 0.55: # 第四阶段:直线回正(10%时间)
controls = airsim.CarControls()
- controls.throttle = 0.5
- controls.steering = 0.0 # 回正方向
+ controls.throttle = 0.4
+
+ # 主动回正,使用负向转向修正
+ if current_yaw > last_yaw + 1.0:
+ controls.steering = -0.05
+ elif current_yaw < last_yaw - 1.0:
+ controls.steering = 0.05
+ else:
+ controls.steering = 0.0
+
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
if sequence < 4:
print(" 阶段4: 直线回正")
sequence = 4
+ last_yaw = current_yaw
+
elif elapsed < duration * 0.65: # 第五阶段:缓右转(10%时间)
controls = airsim.CarControls()
- controls.throttle = 0.5
- # 使用小角度右转
- controls.steering = -0.2
+ controls.throttle = 0.4
+ # 使用非常小的角度右转
+ controls.steering = -0.1 # 减小转向角度
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
if sequence < 5:
print(f" 阶段5: 缓右转 (速度: {current_speed:.1f} km/h)")
@@ -338,11 +481,14 @@ def manual_control_demo(self, duration=20):
controls = airsim.CarControls()
# 逐步减小油门
progress = (elapsed - duration * 0.65) / (duration * 0.2)
- controls.throttle = max(0.2, 0.5 * (1.0 - progress))
- controls.steering = 0.0
- # 速度仍然较高时轻微刹车
- if current_speed > 25:
- controls.brake = 0.2
+ controls.throttle = max(0.1, 0.4 * (1.0 - progress))
+
+ # 直线行驶时进行轻微修正
+ if pos_dev > 5.0: # 如果位置偏离大于5米
+ controls.steering = -0.02 * (pos_dev / 10)
+ else:
+ controls.steering = 0.0
+
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
if sequence < 6:
print(" 阶段6: 直线行驶并准备减速")
@@ -353,11 +499,11 @@ def manual_control_demo(self, duration=20):
controls.throttle = 0.0
# 根据当前速度调整刹车力度
if current_speed > 20:
- controls.brake = 0.8
- elif current_speed > 10:
controls.brake = 0.6
+ elif current_speed > 10:
+ controls.brake = 0.4
else:
- controls.brake = 1.0 # 低速时完全刹车
+ controls.brake = 0.2 # 降低刹车力度,避免突然停车
controls.steering = 0.0
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
if sequence < 7:
@@ -368,39 +514,52 @@ def manual_control_demo(self, duration=20):
if state:
# 显示转向状态
steering_status = ""
- if controls.steering > 0.05:
- steering_status = f"左转{controls.steering:.2f}"
- elif controls.steering < -0.05:
- steering_status = f"右转{abs(controls.steering):.2f}"
+ if controls.steering > 0.02:
+ steering_status = f"左转{controls.steering:.3f}"
+ elif controls.steering < -0.02:
+ steering_status = f"右转{abs(controls.steering):.3f}"
else:
steering_status = "直行"
# 显示油门/刹车状态
control_status = ""
if controls.brake > 0:
- control_status = f"刹车:{controls.brake:.1f}"
+ control_status = f"刹车:{controls.brake:.2f}"
else:
- control_status = f"油门:{controls.throttle:.1f}"
+ control_status = f"油门:{controls.throttle:.2f}"
+
+ # 显示偏航角
+ yaw_status = f"{current_yaw:.1f}°"
- # 计算行驶距离(假设从起点开始)
- distance = (state['position']['x'] ** 2 + state['position']['y'] ** 2) ** 0.5
+ # 显示偏离信息
+ deviation_status = f"偏离:{pos_dev:.1f}m"
print(f"\r速度: {current_speed:5.1f} km/h | "
- f"转向: {steering_status:8} | "
- f"{control_status:8} | "
- f"距离: {distance:6.1f} m", end="")
-
- # 采集传感器数据(每0.3秒采集一次)
- # 使用计数器替代浮点数取模,避免浮点精度问题
- frame_counter = int(elapsed * 10) # 10Hz循环
- if frame_counter % 3 == 0: # 每3帧采集一次 = 0.3秒
+ f"转向: {steering_status:10} | "
+ f"{control_status:10} | "
+ f"偏航: {yaw_status:8} | "
+ f"{deviation_status:12}", end="")
+
+ # 采集传感器数据(每0.5秒采集一次)
+ if elapsed % 0.5 < 0.05: # 降低采集频率以减少计算负载
self.capture_camera_images(["front"])
self.get_imu_data()
self.get_gps_data()
time.sleep(0.1) # 控制频率 10Hz
+ # 更新最后的偏航角
+ last_yaw = current_yaw
+
print("\n✓ 手动控制演示完成")
+
+ # 分析路径数据
+ if deviation_history:
+ avg_pos_dev = sum(d["pos_dev"] for d in deviation_history) / len(deviation_history)
+ avg_yaw_dev = sum(d["yaw_dev"] for d in deviation_history) / len(deviation_history)
+ print(f"平均位置偏离: {avg_pos_dev:.2f}米")
+ print(f"平均角度偏离: {avg_yaw_dev:.2f}度")
+
return True
except Exception as e:
@@ -469,13 +628,20 @@ def save_simulation_data(self):
"camera_frames": len(self.sensor_data["camera"]),
"imu_samples": len(self.sensor_data["imu"]),
"gps_samples": len(self.sensor_data["gps"]),
- "total_log_entries": len(self.data_log)
+ "total_log_entries": len(self.data_log),
+ "path_history_length": len(self.path_history)
}
stats_file = f"{self.data_dir}/simulation_stats.json"
with open(stats_file, 'w') as f:
json.dump(stats, f, indent=2)
+ # 保存路径历史数据
+ if self.path_history:
+ path_file = f"{self.data_dir}/path_history.json"
+ with open(path_file, 'w') as f:
+ json.dump(self.path_history, f, indent=2)
+
# 生成数据报告
report_file = f"{self.data_dir}/report.txt"
with open(report_file, 'w') as f:
@@ -487,7 +653,8 @@ def save_simulation_data(self):
f.write(f"摄像头帧数: {stats['camera_frames']}\n")
f.write(f"IMU采样数: {stats['imu_samples']}\n")
f.write(f"GPS采样数: {stats['gps_samples']}\n")
- f.write(f"日志条目: {stats['total_log_entries']}\n\n")
+ f.write(f"日志条目: {stats['total_log_entries']}\n")
+ f.write(f"路径记录: {stats['path_history_length']}\n\n")
f.write("数据文件:\n")
for file in os.listdir(self.data_dir):
f.write(f" - {file}\n")
@@ -497,6 +664,9 @@ def save_simulation_data(self):
print(f" 统计数据: {stats_file}")
print(f" 报告文件: {report_file}")
+ if self.path_history:
+ print(f" 路径历史: {path_file}")
+
return True
except Exception as e:
@@ -518,6 +688,10 @@ def run_full_demo(self, control_duration=20, data_duration=10):
if not self.enable_api_control(True):
return False
+ # 等待车辆稳定
+ print("等待车辆稳定...")
+ time.sleep(2)
+
# 步骤3: 手动控制演示
if not self.manual_control_demo(control_duration):
print("手动控制演示失败,继续其他演示...")
@@ -545,10 +719,14 @@ def cleanup(self):
# 停止车辆
if self.is_api_control_enabled:
controls = airsim.CarControls()
- controls.brake = 1.0
- controls.handbrake = True
+ controls.brake = 0.5 # 温和刹车
+ controls.handbrake = False
try:
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ time.sleep(1)
+ controls.brake = 1.0
+ controls.handbrake = True
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
except:
pass
@@ -573,7 +751,7 @@ def main():
# 运行完整演示
try:
simulator.run_full_demo(
- control_duration=20, # 使用20秒控制演示时长
+ control_duration=20, # 控制演示时长20秒
data_duration=10 # 数据采集时长(秒)
)
From accb68b37c189ab79063b23c7f5e80fafd788932 Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Fri, 26 Dec 2025 17:19:11 +0800
Subject: [PATCH 14/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/Safe_navigation/run_main.py | 809 +++++++++++++-------------------
1 file changed, 329 insertions(+), 480 deletions(-)
diff --git a/src/Safe_navigation/run_main.py b/src/Safe_navigation/run_main.py
index 866573ee6d..a23e4c7759 100644
--- a/src/Safe_navigation/run_main.py
+++ b/src/Safe_navigation/run_main.py
@@ -1,7 +1,6 @@
#!/usr/bin/env python3
"""
-AirSimNH 无人车完整仿真控制脚本
-功能:连接仿真器、手动控制车辆、采集传感器数据、监控车辆状态
+AirSimNH 无人车仿真控制脚本 - 强力防碰撞修复版本
"""
import airsim
@@ -11,7 +10,6 @@
import json
import os
from datetime import datetime
-import threading
from collections import deque
import math
@@ -20,36 +18,21 @@ class AirSimNHCarSimulator:
"""AirSim无人车仿真主类"""
def __init__(self, ip="127.0.0.1", port=41451, vehicle_name="PhysXCar"):
- """
- 初始化仿真器连接
-
- 参数:
- ip: AirSim服务器IP地址
- port: AirSim服务器端口
- vehicle_name: 车辆名称,需与settings.json中一致
- """
self.ip = ip
self.port = port
self.vehicle_name = vehicle_name
self.client = None
self.is_connected = False
self.is_api_control_enabled = False
- self.running = False
- self.data_log = []
- self.data_file = None
# 车辆状态跟踪
self.initial_position = None
self.initial_yaw = None
self.path_history = []
- # 传感器数据缓存
- self.sensor_data = {
- "camera": deque(maxlen=100),
- "imu": deque(maxlen=1000),
- "gps": deque(maxlen=1000),
- "lidar": deque(maxlen=100)
- }
+ # 碰撞计数器
+ self.collision_count = 0
+ self.last_collision_state = False
# 创建数据保存目录
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
@@ -62,16 +45,12 @@ def connect(self):
"""连接到AirSim仿真器"""
try:
print(f"正在连接到AirSim仿真器 {self.ip}:{self.port}...")
-
- # 创建客户端连接
self.client = airsim.CarClient(ip=self.ip, port=self.port)
self.client.confirmConnection()
- # 检查车辆是否存在
vehicles = self.client.listVehicles()
if self.vehicle_name not in vehicles:
print(f"警告: 车辆 '{self.vehicle_name}' 未找到,可用车辆: {vehicles}")
- # 尝试使用找到的第一个车辆
if vehicles:
self.vehicle_name = vehicles[0]
print(f"使用车辆: {self.vehicle_name}")
@@ -79,20 +58,17 @@ def connect(self):
self.is_connected = True
print("✓ 成功连接到AirSim仿真器!")
- # 获取初始位置和方向
self.initial_position = self.get_position()
self.initial_yaw = self.get_yaw()
- print(f"初始位置: {self.initial_position}")
+ print(
+ f"初始位置: x={self.initial_position['x']:.3f}, y={self.initial_position['y']:.3f}, z={self.initial_position['z']:.3f}")
print(f"初始偏航角: {self.initial_yaw:.2f}°")
return True
except Exception as e:
print(f"✗ 连接失败: {e}")
- print("请确保:")
- print("1. AirSimNH环境正在运行 (在虚幻引擎中启动)")
- print("2. settings.json配置正确")
- print("3. 网络连接正常")
+ print("请确保AirSimNH环境正在运行")
return False
def get_position(self):
@@ -108,24 +84,17 @@ def get_position(self):
return {"x": 0, "y": 0, "z": 0}
def get_yaw(self):
- """获取车辆偏航角(绕Z轴的旋转角度)"""
+ """获取车辆偏航角"""
try:
kinematics = self.client.simGetVehiclePose(vehicle_name=self.vehicle_name)
orientation = kinematics.orientation
- # 将四元数转换为欧拉角
- # 使用四元数到欧拉角的转换公式
q0, q1, q2, q3 = orientation.w_val, orientation.x_val, orientation.y_val, orientation.z_val
-
- # 计算偏航角 (yaw)
siny_cosp = 2 * (q0 * q3 + q1 * q2)
cosy_cosp = 1 - 2 * (q2 * q2 + q3 * q3)
yaw = math.atan2(siny_cosp, cosy_cosp)
-
- # 转换为角度
yaw_deg = math.degrees(yaw)
- # 标准化到0-360度
if yaw_deg < 0:
yaw_deg += 360
@@ -141,41 +110,40 @@ def enable_api_control(self, enable=True):
if enable:
print("✓ API控制已启用")
- # 重置控制到初始状态
controls = airsim.CarControls()
controls.throttle = 0
controls.steering = 0
controls.brake = 0
- controls.handbrake = False
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
else:
print("✓ API控制已禁用")
return True
-
except Exception as e:
print(f"✗ API控制设置失败: {e}")
return False
def get_vehicle_state(self):
- """获取完整的车辆状态信息"""
+ """获取车辆状态 - 修复了collision_count错误"""
try:
state = self.client.getCarState(vehicle_name=self.vehicle_name)
-
- # 获取车辆物理信息
kinematics = self.client.simGetVehiclePose(vehicle_name=self.vehicle_name)
-
- # 获取偏航角
yaw = self.get_yaw()
- # 获取当前位置
current_position = {
"x": kinematics.position.x_val,
"y": kinematics.position.y_val,
"z": kinematics.position.z_val
}
- # 记录路径历史
+ # 检查碰撞状态并更新计数器
+ current_collision = state.collision.has_collided
+ if current_collision and not self.last_collision_state:
+ self.collision_count += 1
+ print(f"\n!!! 检测到碰撞!碰撞次数: {self.collision_count}")
+ self.last_collision_state = current_collision
+
+ # 记录路径
self.path_history.append({
"timestamp": time.time(),
"position": current_position.copy(),
@@ -183,8 +151,7 @@ def get_vehicle_state(self):
"speed": state.speed
})
- # 只保留最近100个点
- if len(self.path_history) > 100:
+ if len(self.path_history) > 200:
self.path_history.pop(0)
state_info = {
@@ -193,163 +160,33 @@ def get_vehicle_state(self):
"speed_ms": state.speed / 3.6,
"position": current_position,
"yaw": yaw,
- "orientation": {
- "w": kinematics.orientation.w_val,
- "x": kinematics.orientation.x_val,
- "y": kinematics.orientation.y_val,
- "z": kinematics.orientation.z_val
- },
- "gear": state.gear,
"rpm": state.rpm,
"max_rpm": state.maxrpm,
+ "gear": state.gear,
"handbrake": state.handbrake,
- "collision": state.collision.has_collided,
- "collision_count": state.collision.collision_count
+ "collision": current_collision,
+ "collision_count": self.collision_count # 使用我们自己的计数器
}
return state_info
-
except Exception as e:
print(f"获取车辆状态失败: {e}")
return None
- def capture_camera_images(self, camera_names=["front", "back", "left", "right"]):
- """从多个摄像头捕获图像"""
- images = {}
-
- for cam_name in camera_names:
- try:
- # 请求RGB图像
- responses = self.client.simGetImages([
- airsim.ImageRequest(cam_name, airsim.ImageType.Scene, False, False)
- ], vehicle_name=self.vehicle_name)
-
- if responses and responses[0]:
- img_response = responses[0]
-
- # 将图像数据转换为numpy数组
- img1d = np.frombuffer(img_response.image_data_uint8, dtype=np.uint8)
- img_rgb = img1d.reshape(img_response.height, img_response.width, 3)
-
- # 保存图像到文件
- timestamp = datetime.now().strftime("%H%M%S_%f")[:-3]
- filename = f"{self.data_dir}/{cam_name}_{timestamp}.png"
- cv2.imwrite(filename, cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR))
-
- images[cam_name] = {
- "filename": filename,
- "shape": img_rgb.shape,
- "timestamp": time.time()
- }
-
- # 缓存数据
- self.sensor_data["camera"].append({
- "camera": cam_name,
- "timestamp": time.time(),
- "filename": filename
- })
-
- except Exception as e:
- print(f"摄像头 '{cam_name}' 捕获失败: {e}")
-
- return images
-
- def get_imu_data(self):
- """获取IMU传感器数据"""
- try:
- imu_data = self.client.getImuData(imu_name="Imu", vehicle_name=self.vehicle_name)
-
- data = {
- "timestamp": time.time(),
- "linear_acceleration": {
- "x": imu_data.linear_acceleration.x_val,
- "y": imu_data.linear_acceleration.y_val,
- "z": imu_data.linear_acceleration.z_val
- },
- "angular_velocity": {
- "x": imu_data.angular_velocity.x_val,
- "y": imu_data.angular_velocity.y_val,
- "z": imu_data.angular_velocity.z_val
- },
- "orientation": {
- "w": imu_data.orientation.w_val,
- "x": imu_data.orientation.x_val,
- "y": imu_data.orientation.y_val,
- "z": imu_data.orientation.z_val
- }
- }
-
- self.sensor_data["imu"].append(data)
- return data
-
- except Exception as e:
- print(f"获取IMU数据失败: {e}")
- return None
-
- def get_gps_data(self):
- """获取GPS数据"""
- try:
- gps_data = self.client.getGpsData(gps_name="Gps", vehicle_name=self.vehicle_name)
-
- data = {
- "timestamp": time.time(),
- "latitude": gps_data.gnss.geopoint.latitude,
- "longitude": gps_data.gnss.geopoint.longitude,
- "altitude": gps_data.gnss.geopoint.altitude,
- "velocity": {
- "x": gps_data.gnss.velocity.x_val,
- "y": gps_data.gnss.velocity.y_val,
- "z": gps_data.gnss.velocity.z_val
- }
- }
-
- self.sensor_data["gps"].append(data)
- return data
-
- except Exception as e:
- print(f"获取GPS数据失败: {e}")
- return None
-
- def calculate_path_deviation(self):
- """计算路径偏离程度"""
- if len(self.path_history) < 10:
- return 0, 0, 0
-
- # 计算最近路径点的平均位置和方向
- recent_points = self.path_history[-10:]
-
- # 计算位置偏离(与起始点的直线距离)
- start_pos = self.initial_position
- current_pos = recent_points[-1]["position"]
-
- pos_deviation = math.sqrt(
- (current_pos["x"] - start_pos["x"]) ** 2 +
- (current_pos["y"] - start_pos["y"]) ** 2
- )
-
- # 计算角度偏离(与初始角度的差异)
- current_yaw = recent_points[-1]["yaw"]
- yaw_deviation = abs(current_yaw - self.initial_yaw)
- if yaw_deviation > 180:
- yaw_deviation = 360 - yaw_deviation
-
- # 计算直线性(最近路径点的标准差)
- x_coords = [p["position"]["x"] for p in recent_points]
- y_coords = [p["position"]["y"] for p in recent_points]
+ def calculate_lateral_offset(self, current_position):
+ """计算横向偏移(改进版本)"""
+ if self.initial_position is None:
+ return 0.0
- if len(x_coords) > 1:
- x_std = np.std(x_coords)
- y_std = np.std(y_coords)
- linearity = math.sqrt(x_std ** 2 + y_std ** 2)
- else:
- linearity = 0
+ # 计算绝对偏移
+ absolute_offset = current_position["y"] - self.initial_position["y"]
- return pos_deviation, yaw_deviation, linearity
+ return absolute_offset
- def manual_control_demo(self, duration=20):
+ def safe_control_demo(self, duration=30):
"""
- 手动控制演示:前进、转向、停止
- 优化控制逻辑,防止车辆偏移
+ 安全控制演示:主动避免右侧碰撞
+ 使用强力左转修正策略
参数:
duration: 演示总时长(秒)
@@ -358,358 +195,374 @@ def manual_control_demo(self, duration=20):
print("错误: 请先连接并启用API控制")
return False
- print(f"\n开始手动控制演示 ({duration}秒)...")
- print("操作序列: 直线加速 → 保持直线 → 缓左转 → 直线回正 → 缓右转 → 直线行驶 → 平滑刹车")
+ print(f"\n开始安全控制演示 ({duration}秒)...")
+ print("策略: 强力左转修正,防止向右偏移和碰撞")
start_time = time.time()
- sequence = 0
-
- # 添加初始控制变量
controls = airsim.CarControls()
- # 添加速度监控
- current_speed = 0
- max_speed_kmh = 30 # 降低最大速度到30km/h以提高稳定性
+ # 控制参数
+ target_speed_kmh = 18
+ base_throttle = 0.45
+
+ # 偏移监控
+ max_right_offset = 0
+ offset_history = deque(maxlen=5)
- # 转向控制参数
- steering_correction = 0.0
- last_yaw = self.get_yaw()
+ # 状态跟踪
+ emergency_left_turn = False
+ emergency_turn_start_time = 0
+ last_good_position = self.initial_position.copy()
- # 偏移检测和修正
- deviation_history = []
- last_correction_time = 0
+ # 强力左转参数
+ strong_left_steering = 0.35 # 强力左转角度
+ moderate_left_steering = 0.2 # 中等左转角度
+ slight_left_steering = 0.1 # 轻微左转角度
try:
while time.time() - start_time < duration:
elapsed = time.time() - start_time
- # 获取当前速度
+ # 获取当前状态
state = self.get_vehicle_state()
- if state:
- current_speed = state['speed_kmh']
- current_yaw = state.get('yaw', 0)
- else:
- current_speed = 0
- current_yaw = last_yaw
-
- # 计算路径偏离
- pos_dev, yaw_dev, linearity = self.calculate_path_deviation()
- deviation_history.append({
- "time": elapsed,
- "pos_dev": pos_dev,
- "yaw_dev": yaw_dev,
- "linearity": linearity
- })
-
- # 根据时间执行不同控制序列
- if elapsed < duration * 0.25: # 第一阶段:直线加速(25%时间)
- controls = airsim.CarControls()
- # 使用温和的加速曲线
- if elapsed < 2: # 前2秒逐渐加速
- controls.throttle = 0.5 * (elapsed / 2)
- else: # 2秒后保持中等油门
- controls.throttle = 0.5
- controls.steering = 0.0
- self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- if sequence < 1:
- print(f" 阶段1: 直线加速 (目标速度: {max_speed_kmh} km/h)")
- sequence = 1
+ if not state:
+ print(" ! 获取状态失败,继续尝试...")
+ time.sleep(0.1)
+ continue
- elif elapsed < duration * 0.35: # 第二阶段:保持直线(10%时间)
- controls = airsim.CarControls()
- # 根据速度调整油门,保持稳定速度
- if current_speed < max_speed_kmh * 0.8:
- controls.throttle = 0.5
- elif current_speed < max_speed_kmh:
- controls.throttle = 0.4
- else:
- controls.throttle = 0.3
+ current_speed = state['speed_kmh']
+ current_position = state['position']
+ current_yaw = state['yaw']
- # 轻微修正转向,保持直线
- if yaw_dev > 2.0: # 如果偏航角偏差大于2度
- steering_correction = -0.05 * (yaw_dev / 10)
- else:
- steering_correction = 0.0
-
- controls.steering = steering_correction
- self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- if sequence < 2:
- print(f" 阶段2: 保持直线 (当前速度: {current_speed:.1f} km/h)")
- sequence = 2
-
- elif elapsed < duration * 0.45: # 第三阶段:缓左转(10%时间)
- controls = airsim.CarControls()
- # 转向时减小油门
- controls.throttle = 0.4
- # 使用非常小的角度左转
- controls.steering = 0.1 # 减小转向角度
- self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- if sequence < 3:
- print(f" 阶段3: 缓左转 (速度: {current_speed:.1f} km/h)")
- sequence = 3
+ # 计算偏移
+ absolute_offset = self.calculate_lateral_offset(current_position)
- elif elapsed < duration * 0.55: # 第四阶段:直线回正(10%时间)
- controls = airsim.CarControls()
- controls.throttle = 0.4
+ # 更新历史
+ offset_history.append(absolute_offset)
- # 主动回正,使用负向转向修正
- if current_yaw > last_yaw + 1.0:
- controls.steering = -0.05
- elif current_yaw < last_yaw - 1.0:
- controls.steering = 0.05
- else:
- controls.steering = 0.0
+ # 更新最大右偏移
+ if absolute_offset > max_right_offset:
+ max_right_offset = absolute_offset
- self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- if sequence < 4:
- print(" 阶段4: 直线回正")
- sequence = 4
+ # 计算偏移趋势
+ offset_trend = 0
+ if len(offset_history) >= 3:
+ offset_trend = sum(offset_history) / len(offset_history)
- last_yaw = current_yaw
+ # 1. 紧急情况检测和处理
+ collision_detected = state.get('collision', False)
- elif elapsed < duration * 0.65: # 第五阶段:缓右转(10%时间)
+ # 如果已经发生碰撞
+ if collision_detected:
+ print(f"\n!!! 发生碰撞!执行紧急避障程序")
+ # 紧急刹车+强力左转
controls = airsim.CarControls()
- controls.throttle = 0.4
- # 使用非常小的角度右转
- controls.steering = -0.1 # 减小转向角度
+ controls.throttle = 0
+ controls.brake = 1.0
+ controls.steering = -strong_left_steering # 强力左转摆脱
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- if sequence < 5:
- print(f" 阶段5: 缓右转 (速度: {current_speed:.1f} km/h)")
- sequence = 5
-
- elif elapsed < duration * 0.85: # 第六阶段:直线行驶并准备减速(20%时间)
- controls = airsim.CarControls()
- # 逐步减小油门
- progress = (elapsed - duration * 0.65) / (duration * 0.2)
- controls.throttle = max(0.1, 0.4 * (1.0 - progress))
-
- # 直线行驶时进行轻微修正
- if pos_dev > 5.0: # 如果位置偏离大于5米
- controls.steering = -0.02 * (pos_dev / 10)
- else:
- controls.steering = 0.0
+ time.sleep(1.5) # 紧急避障1.5秒
+ # 尝试回退到安全位置
+ print(" 尝试回到安全位置...")
+ controls.brake = 0
+ controls.throttle = -0.3 # 倒车
+ controls.steering = 0.1 # 稍微右转
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- if sequence < 6:
- print(" 阶段6: 直线行驶并准备减速")
- sequence = 6
+ time.sleep(2.0)
- else: # 第七阶段:平滑刹车停止(15%时间)
- controls = airsim.CarControls()
- controls.throttle = 0.0
- # 根据当前速度调整刹车力度
- if current_speed > 20:
- controls.brake = 0.6
- elif current_speed > 10:
- controls.brake = 0.4
- else:
- controls.brake = 0.2 # 降低刹车力度,避免突然停车
- controls.steering = 0.0
+ controls.throttle = 0
+ controls.brake = 0.5
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- if sequence < 7:
- print(" 阶段7: 平滑刹车停止")
- sequence = 7
-
- # 实时显示车辆状态
- if state:
- # 显示转向状态
- steering_status = ""
- if controls.steering > 0.02:
- steering_status = f"左转{controls.steering:.3f}"
- elif controls.steering < -0.02:
- steering_status = f"右转{abs(controls.steering):.3f}"
+ time.sleep(1.0)
+
+ # 重置初始位置为当前位置
+ self.initial_position = self.get_position()
+ print(f" 重置初始位置: y={self.initial_position['y']:.3f}")
+ continue
+
+ # 2. 基于偏移量的强力修正逻辑
+ base_steering = 0.0
+ collision_risk = False
+
+ # 强力修正逻辑:基于偏移量决定左转力度
+ if absolute_offset > 0.15: # 向右偏移超过15厘米 - 紧急情况!
+ collision_risk = True
+ base_steering = -strong_left_steering * 1.2 # 超强力左转
+ print(f"\n!!! 紧急!向右偏移{absolute_offset:.3f}米,执行超强力左转!")
+ emergency_left_turn = True
+ emergency_turn_start_time = elapsed
+
+ elif absolute_offset > 0.10: # 向右偏移超过10厘米
+ collision_risk = True
+ base_steering = -strong_left_steering # 强力左转
+ print(f" !! 危险!向右偏移{absolute_offset:.3f}米,执行强力左转")
+ if not emergency_left_turn:
+ emergency_left_turn = True
+ emergency_turn_start_time = elapsed
+
+ elif absolute_offset > 0.05: # 向右偏移超过5厘米
+ collision_risk = True
+ base_steering = -moderate_left_steering # 中等左转
+ print(f" ! 警告!向右偏移{absolute_offset:.3f}米,执行中等左转")
+ if emergency_left_turn:
+ # 检查是否可以退出紧急模式
+ if elapsed - emergency_turn_start_time > 3.0 and absolute_offset < 0.03:
+ emergency_left_turn = False
+ print(" ✓ 危险解除")
+
+ elif absolute_offset > 0.02: # 向右偏移超过2厘米
+ base_steering = -slight_left_steering # 轻微左转
+ if elapsed % 2.0 < 0.1: # 每2秒显示一次
+ print(f" > 注意:向右偏移{absolute_offset:.3f}米,轻微左转修正")
+
+ elif absolute_offset < -0.05: # 向左偏移超过5厘米
+ base_steering = 0.05 # 轻微右转修正
+ if elapsed % 2.0 < 0.1:
+ print(f" < 注意:向左偏移{abs(absolute_offset):.3f}米,轻微右转修正")
+
+ else: # 偏移在安全范围内
+ base_steering = -0.03 # 始终轻微左倾,预防向右偏移
+ emergency_left_turn = False
+
+ # 3. 基于趋势的额外修正
+ if offset_trend > 0.01: # 偏移趋势向右
+ base_steering -= 0.08 # 增加左转力度
+ if elapsed % 1.0 < 0.1:
+ print(f" ↗ 趋势向右,增加左转修正")
+
+ # 4. 油门控制策略
+ if collision_risk or emergency_left_turn:
+ # 危险情况下减速
+ controls.throttle = base_throttle * 0.3
+ controls.brake = 0.1 # 轻微刹车
+ else:
+ # 正常情况下的速度控制
+ if current_speed < target_speed_kmh * 0.7:
+ controls.throttle = base_throttle
+ controls.brake = 0
+ elif current_speed < target_speed_kmh:
+ controls.throttle = base_throttle * 0.6
+ controls.brake = 0
else:
- steering_status = "直行"
-
- # 显示油门/刹车状态
- control_status = ""
- if controls.brake > 0:
- control_status = f"刹车:{controls.brake:.2f}"
+ controls.throttle = base_throttle * 0.4
+ controls.brake = 0.05 # 轻微刹车控制速度
+
+ # 5. 阶段控制(根据时间调整策略)
+ if elapsed < 6.0: # 起步阶段(6秒)
+ controls.throttle = base_throttle * 0.7
+ base_steering = -0.05 # 轻微左转起步
+
+ elif elapsed < 18.0: # 主要行驶阶段(12秒)
+ # 保持主动左转修正
+ pass
+
+ elif elapsed < 24.0: # 测试阶段(6秒) - 尝试轻微右转但受安全约束
+ # 只有在绝对安全时才允许轻微右转
+ if absolute_offset < 0.01 and not collision_risk and not emergency_left_turn:
+ test_steering = 0.04 # 非常轻微的右转
+ base_steering = test_steering
+ if elapsed % 2.0 < 0.1:
+ print(" → 安全条件下测试轻微右转")
else:
- control_status = f"油门:{controls.throttle:.2f}"
-
- # 显示偏航角
- yaw_status = f"{current_yaw:.1f}°"
+ if elapsed % 2.0 < 0.1:
+ print(" × 条件不满足,取消右转测试,保持左转")
- # 显示偏离信息
- deviation_status = f"偏离:{pos_dev:.1f}m"
+ else: # 减速停止阶段(最后6秒)
+ # 逐渐减速
+ stop_progress = (elapsed - 24.0) / 6.0
+ controls.throttle = max(0, base_throttle * (1.0 - stop_progress))
- print(f"\r速度: {current_speed:5.1f} km/h | "
- f"转向: {steering_status:10} | "
- f"{control_status:10} | "
- f"偏航: {yaw_status:8} | "
- f"{deviation_status:12}", end="")
+ if current_speed > 12:
+ controls.brake = 0.4
+ elif current_speed > 6:
+ controls.brake = 0.2
+ else:
+ controls.brake = 0.1
- # 采集传感器数据(每0.5秒采集一次)
- if elapsed % 0.5 < 0.05: # 降低采集频率以减少计算负载
- self.capture_camera_images(["front"])
- self.get_imu_data()
- self.get_gps_data()
+ # 停止阶段更积极的左转,确保停在安全位置
+ base_steering = -0.08
- time.sleep(0.1) # 控制频率 10Hz
+ # 6. 应用控制
+ steering = max(-1.0, min(1.0, base_steering))
+ controls.steering = steering
- # 更新最后的偏航角
- last_yaw = current_yaw
+ # 发送控制命令
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- print("\n✓ 手动控制演示完成")
+ # 7. 显示状态
+ status_symbol = "✓"
+ if collision_risk:
+ status_symbol = "⚠️"
+ if emergency_left_turn:
+ status_symbol = "🚨"
+ if collision_detected:
+ status_symbol = "💥"
+
+ status_line = (f"{status_symbol} 速度: {current_speed:5.1f} km/h | "
+ f"转向: {controls.steering:+.3f} | "
+ f"油门: {controls.throttle:.2f} | "
+ f"刹车: {controls.brake:.2f} | "
+ f"偏航: {current_yaw:6.1f}° | "
+ f"偏移: {absolute_offset:+.3f}m | "
+ f"最大偏移: {max_right_offset:+.3f}m")
+
+ print(f"\r{status_line}", end="")
+
+ # 8. 慢速采集数据
+ if elapsed % 0.5 < 0.05: # 每0.5秒采集一次
+ try:
+ # 简单状态检查
+ pass
+ except:
+ pass
+
+ time.sleep(0.08) # 12.5Hz控制频率
+
+ # 9. 保存最后一个好位置
+ if not collision_risk and absolute_offset < 0.03:
+ last_good_position = current_position.copy()
+
+ print("\n✓ 安全控制演示完成")
+
+ # 最终分析
+ print(f"\n最终统计:")
+ print(f"最大向右偏移: {max_right_offset:.3f}米")
+ print(f"碰撞次数: {self.collision_count}")
+ print(f"路径点数量: {len(self.path_history)}")
+
+ if max_right_offset > 0.15:
+ print(" ⚠️⚠️⚠️ 严重警告:车辆明显向右偏移,碰撞风险高!")
+ elif max_right_offset > 0.08:
+ print(" ⚠️⚠️ 警告:车辆有向右偏移趋势")
+ elif max_right_offset > 0.03:
+ print(" ⚠️ 注意:车辆轻微向右偏移")
+ else:
+ print(" ✓ 优秀:车辆保持在安全范围内")
- # 分析路径数据
- if deviation_history:
- avg_pos_dev = sum(d["pos_dev"] for d in deviation_history) / len(deviation_history)
- avg_yaw_dev = sum(d["yaw_dev"] for d in deviation_history) / len(deviation_history)
- print(f"平均位置偏离: {avg_pos_dev:.2f}米")
- print(f"平均角度偏离: {avg_yaw_dev:.2f}度")
+ if self.collision_count > 0:
+ print(f" ⚠️ 发生碰撞: {self.collision_count}次")
+ else:
+ print(" ✓ 安全:无碰撞发生")
return True
- except Exception as e:
- print(f"\n✗ 控制演示出错: {e}")
+ except KeyboardInterrupt:
+ print("\n\n演示被用户中断")
return False
-
- def data_collection_demo(self, duration=5):
- """数据采集演示:采集所有传感器数据"""
- print(f"\n开始数据采集演示 ({duration}秒)...")
-
- start_time = time.time()
- frame_count = 0
-
- try:
- while time.time() - start_time < duration:
- frame_count += 1
-
- # 采集所有摄像头图像
- images = self.capture_camera_images()
-
- # 采集IMU数据
- imu_data = self.get_imu_data()
-
- # 采集GPS数据
- gps_data = self.get_gps_data()
-
- # 获取车辆状态
- vehicle_state = self.get_vehicle_state()
-
- # 记录到日志
- log_entry = {
- "frame": frame_count,
- "timestamp": time.time(),
- "images": len(images),
- "imu_data": imu_data is not None,
- "gps_data": gps_data is not None,
- "vehicle_state": vehicle_state is not None
- }
- self.data_log.append(log_entry)
-
- print(f"\r采集帧: {frame_count} | "
- f"图像: {len(images)} | "
- f"速度: {vehicle_state['speed_kmh'] if vehicle_state else 'N/A':.1f} km/h", end="")
-
- time.sleep(0.2) # 5Hz采集频率
-
- print(f"\n✓ 数据采集完成,共采集 {frame_count} 帧")
- return True
-
except Exception as e:
- print(f"\n✗ 数据采集出错: {e}")
+ print(f"\n✗ 控制演示出错: {e}")
+ import traceback
+ traceback.print_exc()
return False
def save_simulation_data(self):
- """保存所有仿真数据到文件"""
+ """保存仿真数据"""
try:
- # 保存车辆状态日志
- log_file = f"{self.data_dir}/simulation_log.json"
- with open(log_file, 'w') as f:
- json.dump(self.data_log, f, indent=2)
+ # 保存路径历史
+ if self.path_history:
+ path_file = f"{self.data_dir}/path_history.json"
+ with open(path_file, 'w') as f:
+ json.dump(self.path_history, f, indent=2)
+ print(f"✓ 路径历史已保存: {path_file}")
- # 保存传感器数据统计
+ # 保存统计数据
stats = {
"timestamp": datetime.now().isoformat(),
"vehicle_name": self.vehicle_name,
- "camera_frames": len(self.sensor_data["camera"]),
- "imu_samples": len(self.sensor_data["imu"]),
- "gps_samples": len(self.sensor_data["gps"]),
- "total_log_entries": len(self.data_log),
- "path_history_length": len(self.path_history)
+ "collision_count": self.collision_count,
+ "path_history_length": len(self.path_history),
+ "initial_position": self.initial_position,
+ "initial_yaw": self.initial_yaw
}
stats_file = f"{self.data_dir}/simulation_stats.json"
with open(stats_file, 'w') as f:
json.dump(stats, f, indent=2)
- # 保存路径历史数据
- if self.path_history:
- path_file = f"{self.data_dir}/path_history.json"
- with open(path_file, 'w') as f:
- json.dump(self.path_history, f, indent=2)
-
- # 生成数据报告
+ # 生成报告
report_file = f"{self.data_dir}/report.txt"
with open(report_file, 'w') as f:
- f.write("=" * 50 + "\n")
- f.write("AirSim无人车仿真数据报告\n")
- f.write("=" * 50 + "\n\n")
- f.write(f"仿真时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
+ f.write("=" * 60 + "\n")
+ f.write("AirSim无人车安全控制演示报告\n")
+ f.write("强力防碰撞版本\n")
+ f.write("=" * 60 + "\n\n")
+ f.write(f"演示时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"车辆名称: {self.vehicle_name}\n")
- f.write(f"摄像头帧数: {stats['camera_frames']}\n")
- f.write(f"IMU采样数: {stats['imu_samples']}\n")
- f.write(f"GPS采样数: {stats['gps_samples']}\n")
- f.write(f"日志条目: {stats['total_log_entries']}\n")
- f.write(f"路径记录: {stats['path_history_length']}\n\n")
- f.write("数据文件:\n")
- for file in os.listdir(self.data_dir):
- f.write(f" - {file}\n")
-
- print(f"\n✓ 仿真数据已保存到: {self.data_dir}")
- print(f" 日志文件: {log_file}")
- print(f" 统计数据: {stats_file}")
- print(f" 报告文件: {report_file}")
-
- if self.path_history:
- print(f" 路径历史: {path_file}")
+ f.write(f"碰撞次数: {self.collision_count}\n")
+ f.write(f"路径点数量: {len(self.path_history)}\n")
+
+ if self.path_history and len(self.path_history) > 10:
+ first_pos = self.path_history[0]['position']
+ last_pos = self.path_history[-1]['position']
+ y_offset = last_pos['y'] - first_pos['y']
+ f.write(f"最终横向偏移(Y轴): {y_offset:.3f}米\n")
+
+ # 分析偏移范围
+ y_values = [p['position']['y'] for p in self.path_history]
+ min_y = min(y_values)
+ max_y = max(y_values)
+ avg_y = sum(y_values) / len(y_values)
+
+ f.write(f"Y坐标范围: {min_y:.3f} 到 {max_y:.3f} 米\n")
+ f.write(f"平均Y坐标: {avg_y:.3f} 米\n")
+
+ if y_offset > 0.1:
+ f.write("结论: 车辆明显向右偏移,需要加强左转修正\n")
+ elif y_offset > 0.05:
+ f.write("结论: 车辆有向右偏移趋势\n")
+ elif y_offset > 0:
+ f.write("结论: 车辆轻微向右偏移\n")
+ elif y_offset < -0.05:
+ f.write("结论: 车辆向左偏移\n")
+ else:
+ f.write("结论: 车辆基本保持在车道中央\n")
+ print(f"✓ 报告已保存: {report_file}")
+ print(f"✓ 统计数据已保存: {stats_file}")
return True
except Exception as e:
print(f"✗ 保存数据失败: {e}")
return False
- def run_full_demo(self, control_duration=20, data_duration=10):
- """运行完整演示"""
+ def run_safe_demo(self, duration=30):
+ """运行安全演示"""
print("=" * 60)
- print("AirSimNH 无人车完整仿真演示")
+ print("AirSimNH 无人车安全控制演示")
+ print("强力防碰撞修复版本")
print("=" * 60)
- # 步骤1: 连接仿真器
+ # 连接仿真器
if not self.connect():
return False
try:
- # 步骤2: 启用API控制
+ # 启用API控制
if not self.enable_api_control(True):
return False
- # 等待车辆稳定
- print("等待车辆稳定...")
+ print("\n等待车辆稳定...")
time.sleep(2)
- # 步骤3: 手动控制演示
- if not self.manual_control_demo(control_duration):
- print("手动控制演示失败,继续其他演示...")
-
- # 短暂暂停,让车辆完全停止
- time.sleep(2)
+ # 运行安全控制演示
+ print("\n" + "=" * 60)
+ print("开始安全控制演示")
+ print("策略: 强力左转修正,主动防止向右偏移")
+ print("=" * 60)
- # 步骤4: 数据采集演示
- if not self.data_collection_demo(data_duration):
- print("数据采集演示失败,继续保存数据...")
+ success = self.safe_control_demo(duration)
- # 步骤5: 保存数据
- self.save_simulation_data()
+ if success:
+ print("\n" + "=" * 60)
+ print("演示完成,保存数据...")
+ print("=" * 60)
+ self.save_simulation_data()
- return True
+ return success
finally:
- # 步骤6: 清理和退出
+ # 清理
self.cleanup()
def cleanup(self):
@@ -719,14 +572,13 @@ def cleanup(self):
# 停止车辆
if self.is_api_control_enabled:
controls = airsim.CarControls()
- controls.brake = 0.5 # 温和刹车
- controls.handbrake = False
+ controls.throttle = 0
+ controls.brake = 1.0
+ controls.steering = 0
+ controls.handbrake = True
try:
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
time.sleep(1)
- controls.brake = 1.0
- controls.handbrake = True
- self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
except:
pass
@@ -741,29 +593,26 @@ def cleanup(self):
def main():
"""主函数"""
- # 创建仿真器实例
simulator = AirSimNHCarSimulator(
ip="127.0.0.1",
port=41451,
vehicle_name="PhysXCar"
)
- # 运行完整演示
try:
- simulator.run_full_demo(
- control_duration=20, # 控制演示时长20秒
- data_duration=10 # 数据采集时长(秒)
- )
+ simulator.run_safe_demo(duration=30)
print("\n" + "=" * 60)
- print("仿真演示完成!")
+ print("安全控制演示完成!")
print("=" * 60)
except KeyboardInterrupt:
- print("\n\n仿真被用户中断")
+ print("\n\n演示被用户中断")
simulator.cleanup()
except Exception as e:
- print(f"\n仿真出错: {e}")
+ print(f"\n演示出错: {e}")
+ import traceback
+ traceback.print_exc()
simulator.cleanup()
From 76b5fe76d70808056a1403e14757a5afd77bac36 Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Sat, 27 Dec 2025 17:22:11 +0800
Subject: [PATCH 15/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/Safe_navigation/run_main.py | 1207 ++++++++++++++++++++++++-------
1 file changed, 933 insertions(+), 274 deletions(-)
diff --git a/src/Safe_navigation/run_main.py b/src/Safe_navigation/run_main.py
index 56ce1edbe2..234a1d6b6e 100644
--- a/src/Safe_navigation/run_main.py
+++ b/src/Safe_navigation/run_main.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""
-AirSimNH 无人车完整仿真控制脚本
-功能:连接仿真器、手动控制车辆、采集传感器数据、监控车辆状态
+AirSimNH 无人车仿真控制脚本 - 丁字路口通过版本 V2.0
+智能防碰撞 + 路口导航 + 动态路径调整 + 改进左转策略
"""
import airsim
@@ -11,80 +11,194 @@
import json
import os
from datetime import datetime
+from collections import deque, defaultdict
+import math
+import random
import threading
-from collections import deque
-
class AirSimNHCarSimulator:
- """AirSim无人车仿真主类"""
+ """AirSim无人车仿真主类 - V2.0 改进版"""
def __init__(self, ip="127.0.0.1", port=41451, vehicle_name="PhysXCar"):
- """
- 初始化仿真器连接
-
- 参数:
- ip: AirSim服务器IP地址
- port: AirSim服务器端口
- vehicle_name: 车辆名称,需与settings.json中一致
- """
self.ip = ip
self.port = port
self.vehicle_name = vehicle_name
self.client = None
self.is_connected = False
self.is_api_control_enabled = False
- self.running = False
- self.data_log = []
- self.data_file = None
-
- # 传感器数据缓存
- self.sensor_data = {
- "camera": deque(maxlen=100),
- "imu": deque(maxlen=1000),
- "gps": deque(maxlen=1000),
- "lidar": deque(maxlen=100)
- }
+ # 车辆状态跟踪
+ self.initial_position = None
+ self.initial_yaw = None
+ self.path_history = []
+ self.velocity_history = deque(maxlen=100)
+
+ # 碰撞计数器
+ self.collision_count = 0
+ self.last_collision_state = False
+ self.last_collision_time = 0
+ self.collision_recovery_mode = False
+ self.recovery_start_time = 0
+
+ # 路口导航参数 - 改进版
+ self.intersection_detected = False
+ self.approaching_intersection = False
+ self.in_intersection = False
+ self.intersection_passed = False
+ self.intersection_decision_made = False
+ self.intersection_turn_direction = None # 'left', 'right', 'straight'
+ self.intersection_approach_distance = 20.0 # 增加检测距离
+ self.intersection_pass_distance = 12.0 # 增加通过距离
+
+ # 路口检测状态
+ self.intersection_check_counter = 0
+ self.straight_line_counter = 0
+ self.side_obstacle_detected = False
+
+ # 左转避障参数
+ self.left_turn_obstacle_avoidance = False
+ self.left_turn_safe_distance = 3.5 # 安全距离阈值
+ self.left_turn_adjustment_count = 0
+ self.max_left_adjustments = 3
+
+ # 路径记忆和学习
+ self.position_history = deque(maxlen=500)
+ self.yaw_history = deque(maxlen=500)
+ self.successful_paths = []
+ self.failed_paths = []
+
+ # 动态调整参数
+ self.aggressiveness = 0.4 # 降低侵略性,更谨慎
+ self.steering_adjustment_rate = 0.08 # 降低转向调整速率
+ self.last_steering = 0
+ self.target_speed = 18 # km/h,降低目标速度
+
+ # 环境感知参数
+ self.right_obstacle_distance = float('inf')
+ self.left_obstacle_distance = float('inf')
+ self.front_obstacle_distance = float('inf')
+
+ # 改进的偏移控制
+ self.offset_history = deque(maxlen=20)
+ self.target_offset_range = (-0.05, 0.05) # 目标偏移范围
+ self.offset_adjustment_gain = 0.3 # 偏移调整增益
# 创建数据保存目录
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
self.data_dir = f"simulation_data_{timestamp}"
os.makedirs(self.data_dir, exist_ok=True)
+ # 日志文件
+ self.log_file = None
+ self._init_log_file()
+
+ # 状态监视器
+ self.monitor_running = False
+ self.monitor_thread = None
+
print(f"数据保存目录: {self.data_dir}")
+ def _init_log_file(self):
+ """初始化日志文件"""
+ try:
+ self.log_file = open(f"{self.data_dir}/simulation_log.txt", "w")
+ except Exception as e:
+ print(f"无法创建日志文件: {e}")
+ self.log_file = None
+
+ def log_message(self, message):
+ """记录日志信息"""
+ timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
+ log_entry = f"[{timestamp}] {message}"
+ print(log_entry)
+ if self.log_file and not self.log_file.closed:
+ try:
+ self.log_file.write(log_entry + "\n")
+ self.log_file.flush()
+ except Exception as e:
+ print(f"写入日志失败: {e}")
+
def connect(self):
"""连接到AirSim仿真器"""
try:
- print(f"正在连接到AirSim仿真器 {self.ip}:{self.port}...")
-
- # 创建客户端连接
+ self.log_message(f"正在连接到AirSim仿真器 {self.ip}:{self.port}...")
self.client = airsim.CarClient(ip=self.ip, port=self.port)
self.client.confirmConnection()
- # 检查车辆是否存在
vehicles = self.client.listVehicles()
if self.vehicle_name not in vehicles:
- print(f"警告: 车辆 '{self.vehicle_name}' 未找到,可用车辆: {vehicles}")
- # 尝试使用找到的第一个车辆
+ self.log_message(f"警告: 车辆 '{self.vehicle_name}' 未找到,可用车辆: {vehicles}")
if vehicles:
self.vehicle_name = vehicles[0]
- print(f"使用车辆: {self.vehicle_name}")
+ self.log_message(f"使用车辆: {self.vehicle_name}")
self.is_connected = True
- print("✓ 成功连接到AirSim仿真器!")
+ self.log_message("✓ 成功连接到AirSim仿真器!")
+
+ self.initial_position = self.get_position()
+ self.initial_yaw = self.get_yaw()
+ self.log_message(
+ f"初始位置: x={self.initial_position['x']:.3f}, y={self.initial_position['y']:.3f}, z={self.initial_position['z']:.3f}")
+ self.log_message(f"初始偏航角: {self.initial_yaw:.2f}°")
return True
except Exception as e:
- print(f"✗ 连接失败: {e}")
- print("请确保:")
- print("1. AirSimNH环境正在运行 (在虚幻引擎中启动)")
- print("2. settings.json配置正确")
- print("3. 网络连接正常")
+ self.log_message(f"✗ 连接失败: {e}")
+ self.log_message("请确保AirSimNH环境正在运行")
return False
+ def get_position(self):
+ """获取车辆位置"""
+ try:
+ kinematics = self.client.simGetVehiclePose(vehicle_name=self.vehicle_name)
+ return {
+ "x": kinematics.position.x_val,
+ "y": kinematics.position.y_val,
+ "z": kinematics.position.z_val
+ }
+ except:
+ return {"x": 0, "y": 0, "z": 0}
+
+ def get_yaw(self):
+ """获取车辆偏航角"""
+ try:
+ kinematics = self.client.simGetVehiclePose(vehicle_name=self.vehicle_name)
+ orientation = kinematics.orientation
+
+ q0, q1, q2, q3 = orientation.w_val, orientation.x_val, orientation.y_val, orientation.z_val
+ siny_cosp = 2 * (q0 * q3 + q1 * q2)
+ cosy_cosp = 1 - 2 * (q2 * q2 + q3 * q3)
+ yaw = math.atan2(siny_cosp, cosy_cosp)
+ yaw_deg = math.degrees(yaw)
+
+ if yaw_deg < 0:
+ yaw_deg += 360
+
+ return yaw_deg
+ except:
+ return 0.0
+
+ def get_velocity(self):
+ """获取车辆速度向量"""
+ try:
+ state = self.client.getCarState(vehicle_name=self.vehicle_name)
+ return {
+ "x": state.kinematics_estimated.linear_velocity.x_val,
+ "y": state.kinematics_estimated.linear_velocity.y_val,
+ "z": state.kinematics_estimated.linear_velocity.z_val
+ }
+ except:
+ return {"x": 0, "y": 0, "z": 0}
+
+ def get_speed_kmh(self):
+ """获取速度(km/h)"""
+ try:
+ state = self.client.getCarState(vehicle_name=self.vehicle_name)
+ return state.speed
+ except:
+ return 0.0
def enable_api_control(self, enable=True):
"""启用/禁用API控制"""
@@ -93,359 +207,904 @@ def enable_api_control(self, enable=True):
self.is_api_control_enabled = enable
if enable:
- print("✓ API控制已启用")
- # 重置控制到初始状态
+ self.log_message("✓ API控制已启用")
controls = airsim.CarControls()
controls.throttle = 0
controls.steering = 0
controls.brake = 0
- controls.handbrake = False
self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
else:
- print("✓ API控制已禁用")
+ self.log_message("✓ API控制已禁用")
return True
-
except Exception as e:
- print(f"✗ API控制设置失败: {e}")
+ self.log_message(f"✗ API控制设置失败: {e}")
+ return False
+
+ def detect_intersection_improved(self, current_position, current_yaw, speed):
+ """改进的路口检测算法"""
+ if self.intersection_passed or self.in_intersection:
+ return False
+
+ # 记录位置历史
+ self.position_history.append(current_position)
+ self.yaw_history.append(current_yaw)
+
+ if len(self.position_history) < 30:
return False
+ # 分析行驶特征
+ recent_positions = list(self.position_history)[-30:]
+ recent_yaws = list(self.yaw_history)[-30:]
+
+ # 计算方向稳定性
+ yaw_variance = np.var(recent_yaws)
+ if yaw_variance < 3.0:
+ self.straight_line_counter += 1
+ else:
+ self.straight_line_counter = max(0, self.straight_line_counter - 2)
+
+ # 计算行驶距离
+ distance_traveled = self.calculate_distance_traveled()
+
+ # 路口检测条件:直行一定距离后
+ if (distance_traveled > self.intersection_approach_distance and
+ self.straight_line_counter > 25 and
+ not self.intersection_detected):
+
+ self.approaching_intersection = True
+ self.intersection_detected = True
+
+ # 改进的转向决策逻辑
+ if len(recent_positions) > 40:
+ # 分析路径趋势
+ start_idx = max(0, len(recent_positions) - 40)
+ end_idx = len(recent_positions) - 1
+
+ start_pos = recent_positions[start_idx]
+ end_pos = recent_positions[end_idx]
+
+ # 计算总体偏移
+ y_offset = end_pos['y'] - start_pos['y']
+ x_offset = end_pos['x'] - start_pos['x']
+
+ self.log_message(
+ f"路口检测: 行驶距离{distance_traveled:.1f}m, Y偏移{y_offset:.2f}m, X偏移{x_offset:.2f}m")
+
+ # 基于历史偏移的智能决策
+ if y_offset > 1.5: # 明显右偏
+ self.intersection_turn_direction = 'left'
+ self.log_message("检测到右偏趋势,路口左转")
+ elif y_offset < -1.5: # 明显左偏
+ self.intersection_turn_direction = 'right'
+ self.log_message("检测到左偏趋势,路口右转")
+ else:
+ # 基于相对位置决策
+ if current_position['x'] < 0: # 在左侧区域
+ self.intersection_turn_direction = 'right'
+ self.log_message("位于左侧区域,路口右转")
+ else: # 在右侧区域
+ self.intersection_turn_direction = 'left'
+ self.log_message("位于右侧区域,路口左转")
+
+ # 记录路口信息
+ self.intersection_entry_position = current_position.copy()
+ self.intersection_entry_yaw = current_yaw
+
+ return True
+
+ return False
+
+ def calculate_distance_traveled(self):
+ """计算行驶距离"""
+ if len(self.position_history) < 2:
+ return 0
+
+ total_distance = 0
+ positions = list(self.position_history)
+
+ for i in range(1, len(positions)):
+ p1 = positions[i - 1]
+ p2 = positions[i]
+ dx = p2['x'] - p1['x']
+ dy = p2['y'] - p1['y']
+ dz = p2['z'] - p1['z']
+ segment_distance = math.sqrt(dx * dx + dy * dy + dz * dz)
+ total_distance += segment_distance
+
+ return total_distance
+
def get_vehicle_state(self):
- """获取完整的车辆状态信息"""
+ """获取车辆状态 - 增强版"""
try:
state = self.client.getCarState(vehicle_name=self.vehicle_name)
-
- # 获取车辆物理信息
kinematics = self.client.simGetVehiclePose(vehicle_name=self.vehicle_name)
+ yaw = self.get_yaw()
+ velocity = self.get_velocity()
+
+ current_position = {
+ "x": kinematics.position.x_val,
+ "y": kinematics.position.y_val,
+ "z": kinematics.position.z_val
+ }
+
+ # 检查碰撞状态并更新计数器
+ current_time = time.time()
+ current_collision = state.collision.has_collided
+
+ if current_collision and not self.last_collision_state:
+ self.collision_count += 1
+ self.last_collision_time = current_time
+
+ # 进入碰撞恢复模式
+ self.collision_recovery_mode = True
+ self.recovery_start_time = current_time
+
+ self.log_message(f"!!! 检测到碰撞!碰撞次数: {self.collision_count}")
+
+ # 记录失败路径
+ if len(self.position_history) > 10:
+ self.failed_paths.append(list(self.position_history)[-50:])
+ elif not current_collision and self.last_collision_state:
+ self.log_message("✓ 碰撞状态解除")
+
+ self.last_collision_state = current_collision
+
+ # 退出碰撞恢复模式
+ if self.collision_recovery_mode and current_time - self.recovery_start_time > 3.0:
+ self.collision_recovery_mode = False
+ self.log_message("退出碰撞恢复模式")
+
+ # 记录路径
+ path_point = {
+ "timestamp": current_time,
+ "position": current_position.copy(),
+ "yaw": yaw,
+ "speed": state.speed,
+ "velocity": velocity,
+ "collision": current_collision
+ }
+
+ self.path_history.append(path_point)
+ self.velocity_history.append(velocity)
+
+ # 限制历史记录长度
+ if len(self.path_history) > 1000:
+ self.path_history.pop(0)
state_info = {
- "timestamp": time.time(),
+ "timestamp": current_time,
"speed_kmh": state.speed,
"speed_ms": state.speed / 3.6,
-
- "orientation": {
- "w": kinematics.orientation.w_val,
- "x": kinematics.orientation.x_val,
- "y": kinematics.orientation.y_val,
- "z": kinematics.orientation.z_val
- },
- "gear": state.gear,
+ "position": current_position,
+ "yaw": yaw,
+ "velocity": velocity,
"rpm": state.rpm,
"max_rpm": state.maxrpm,
+ "gear": state.gear,
"handbrake": state.handbrake,
- "collision": state.collision.has_collided,
- "collision_count": state.collision.collision_count
+ "collision": current_collision,
+ "collision_count": self.collision_count,
+ "collision_recovery_mode": self.collision_recovery_mode,
+ "intersection_detected": self.intersection_detected,
+ "intersection_turn_direction": self.intersection_turn_direction,
+ "in_intersection": self.in_intersection
}
return state_info
-
except Exception as e:
- print(f"获取车辆状态失败: {e}")
+ self.log_message(f"获取车辆状态失败: {e}")
return None
- def capture_camera_images(self, camera_names=["front", "back", "left", "right"]):
- """从多个摄像头捕获图像"""
- images = {}
+ def calculate_lateral_offset(self, current_position):
+ """计算横向偏移(改进版本)"""
+ if self.initial_position is None:
+ return 0.0
- for cam_name in camera_names:
- try:
- # 请求RGB图像
- responses = self.client.simGetImages([
- airsim.ImageRequest(cam_name, airsim.ImageType.Scene, False, False)
- ], vehicle_name=self.vehicle_name)
-
- if responses and responses[0]:
- img_response = responses[0]
-
- # 将图像数据转换为numpy数组
- img1d = np.frombuffer(img_response.image_data_uint8, dtype=np.uint8)
- img_rgb = img1d.reshape(img_response.height, img_response.width, 3)
-
- # 保存图像到文件
- timestamp = datetime.now().strftime("%H%M%S_%f")[:-3]
- filename = f"{self.data_dir}/{cam_name}_{timestamp}.png"
- cv2.imwrite(filename, cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR))
-
- images[cam_name] = {
- "filename": filename,
- "shape": img_rgb.shape,
- "timestamp": time.time()
- }
+ # 计算绝对偏移
+ absolute_offset = current_position["y"] - self.initial_position["y"]
- # 缓存数据
- self.sensor_data["camera"].append({
- "camera": cam_name,
- "timestamp": time.time(),
- "filename": filename
- })
+ # 记录偏移历史
+ self.offset_history.append(absolute_offset)
- except Exception as e:
- print(f"摄像头 '{cam_name}' 捕获失败: {e}")
-
- return images
+ return absolute_offset
- def get_imu_data(self):
- """获取IMU传感器数据"""
- try:
- imu_data = self.client.getImuData(imu_name="Imu", vehicle_name=self.vehicle_name)
-
- data = {
- "timestamp": time.time(),
- "linear_acceleration": {
- "x": imu_data.linear_acceleration.x_val,
- "y": imu_data.linear_acceleration.y_val,
- "z": imu_data.linear_acceleration.z_val
- },
- "angular_velocity": {
- "x": imu_data.angular_velocity.x_val,
- "y": imu_data.angular_velocity.y_val,
- "z": imu_data.angular_velocity.z_val
- },
- "orientation": {
- "w": imu_data.orientation.w_val,
- "x": imu_data.orientation.x_val,
- "y": imu_data.orientation.y_val,
- "z": imu_data.orientation.z_val
- }
- }
-
- self.sensor_data["imu"].append(data)
- return data
+ def navigate_intersection_improved(self, current_state, elapsed_time):
+ """改进的路口导航算法"""
+ if not self.intersection_detected or self.intersection_passed:
+ return None
- except Exception as e:
- print(f"获取IMU数据失败: {e}")
+ current_position = current_state['position']
+ current_yaw = current_state['yaw']
+ current_speed = current_state['speed_kmh']
+
+ # 首次进入路口
+ if not self.in_intersection:
+ self.in_intersection = True
+ self.intersection_entry_position = current_position.copy()
+ self.intersection_entry_yaw = current_yaw
+ self.left_turn_adjustment_count = 0
+ self.log_message(f"进入丁字路口,决策: {self.intersection_turn_direction}")
+
+ # 计算在路口内的距离
+ distance_in_intersection = self.calculate_distance_from_point(
+ current_position, self.intersection_entry_position)
+
+ # 检查是否通过路口
+ if distance_in_intersection > self.intersection_pass_distance:
+ self.intersection_passed = True
+ self.in_intersection = False
+ self.left_turn_obstacle_avoidance = False
+ self.log_message("✓ 成功通过丁字路口!")
return None
- def get_gps_data(self):
- """获取GPS数据"""
- try:
- gps_data = self.client.getGpsData(gps_name="Gps", vehicle_name=self.vehicle_name)
-
- data = {
- "timestamp": time.time(),
- "latitude": gps_data.gnss.geopoint.latitude,
- "longitude": gps_data.gnss.geopoint.longitude,
- "altitude": gps_data.gnss.geopoint.altitude,
- "velocity": {
- "x": gps_data.gnss.velocity.x_val,
- "y": gps_data.gnss.velocity.y_val,
- "z": gps_data.gnss.velocity.z_val
- }
- }
+ # 路口控制逻辑
+ controls = airsim.CarControls()
+
+ # 根据转向决策调整控制
+ if self.intersection_turn_direction == 'left':
+ # 改进的左转策略 - 防止撞上左侧车辆
+ self.navigate_left_turn_improved(controls, distance_in_intersection, current_speed)
+
+ elif self.intersection_turn_direction == 'right':
+ # 右转策略
+ self.navigate_right_turn(controls, distance_in_intersection, current_speed)
+ else:
+ # 直行策略
+ controls.throttle = 0.4
+ controls.brake = 0
+ controls.steering = -0.05 # 轻微左倾
+
+ return controls
+
+ def navigate_left_turn_improved(self, controls, distance_in_intersection, current_speed):
+ """改进的左转导航策略"""
+ # 阶段1: 进入路口,减速观察 (0-4米)
+ if distance_in_intersection < 4.0:
+ controls.throttle = 0.25
+ controls.brake = 0.05
+ controls.steering = -0.05 # 轻微左转准备
+
+ # 阶段2: 开始左转,增加观察 (4-6米)
+ elif distance_in_intersection < 6.0:
+ # 检查是否需要避障
+ if not self.left_turn_obstacle_avoidance:
+ controls.throttle = 0.2
+ controls.brake = 0
+ controls.steering = -0.25 # 中等左转
+ else:
+ # 避障模式:减少左转,保持距离
+ controls.throttle = 0.15
+ controls.brake = 0.05
+ controls.steering = -0.15
+
+ # 阶段3: 主要左转阶段 (6-9米)
+ elif distance_in_intersection < 9.0:
+ # 如果之前有碰撞风险,调整策略
+ if self.collision_count > 0 and self.left_turn_adjustment_count < self.max_left_adjustments:
+ # 调整策略:增加转向或调整路径
+ controls.throttle = 0.15
+ controls.brake = 0
+ controls.steering = -0.4 # 更急的左转
+ self.left_turn_adjustment_count += 1
+ self.log_message(f"左转避障调整 {self.left_turn_adjustment_count}/{self.max_left_adjustments}")
+ else:
+ controls.throttle = 0.2
+ controls.brake = 0
+ controls.steering = -0.3 # 标准左转
+
+ # 阶段4: 出路口 (9-12米)
+ else:
+ controls.throttle = 0.3
+ controls.brake = 0
+ controls.steering = -0.15 # 减少转向
+
+ def navigate_right_turn(self, controls, distance_in_intersection, current_speed):
+ """右转导航策略"""
+ # 阶段1: 进入路口,减速 (0-3米)
+ if distance_in_intersection < 3.0:
+ controls.throttle = 0.2
+ controls.brake = 0.1
+ controls.steering = 0.05 # 轻微右转准备
+
+ # 阶段2: 执行右转 (3-7米)
+ elif distance_in_intersection < 7.0:
+ controls.throttle = 0.25
+ controls.brake = 0
+ controls.steering = 0.25 # 右转
+
+ # 阶段3: 完成右转 (7-12米)
+ else:
+ controls.throttle = 0.35
+ controls.brake = 0
+ controls.steering = 0.1 # 减少右转
+
+ def calculate_distance_from_point(self, position1, position2):
+ """计算两点之间的距离"""
+ dx = position1['x'] - position2['x']
+ dy = position1['y'] - position2['y']
+ dz = position1['z'] - position2['z']
+ return math.sqrt(dx * dx + dy * dy + dz * dz)
+
+ def execute_collision_recovery_improved(self, current_state):
+ """改进的碰撞恢复程序"""
+ controls = airsim.CarControls()
+ current_time = time.time()
+ recovery_duration = current_time - self.recovery_start_time
+
+ # 获取当前状态信息
+ current_yaw = current_state['yaw']
+ current_position = current_state['position']
+
+ # 阶段1: 紧急刹车和后退 (0-1.5秒)
+ if recovery_duration < 1.5:
+ controls.throttle = -0.4 # 倒车
+ controls.brake = 0.6
+ # 根据碰撞位置决定转向方向
+ controls.steering = 0.2 # 向右转向摆脱
+ self.log_message("碰撞恢复:倒车脱离")
+
+ # 阶段2: 停车观察 (1.5-2.5秒)
+ elif recovery_duration < 2.5:
+ controls.throttle = 0
+ controls.brake = 0.3
+ controls.steering = -0.1 # 轻微左转调整
+ self.log_message("碰撞恢复:停车观察")
+
+ # 阶段3: 小幅度前进,调整方向 (2.5-3.5秒)
+ elif recovery_duration < 3.5:
+ # 尝试不同的方向
+ if self.collision_count % 2 == 0:
+ controls.steering = -0.2 # 左转尝试
+ else:
+ controls.steering = 0.15 # 右转尝试
- self.sensor_data["gps"].append(data)
- return data
+ controls.throttle = 0.15
+ controls.brake = 0
+ self.log_message("碰撞恢复:小幅度前进")
- except Exception as e:
- print(f"获取GPS数据失败: {e}")
- return None
+ else:
+ # 恢复完成
+ self.collision_recovery_mode = False
+ self.log_message("碰撞恢复完成")
+ # 根据碰撞情况调整策略
+ if self.collision_count > 0:
+ # 降低目标速度
+ self.target_speed = max(12, self.target_speed - 2)
+ self.log_message(f"调整目标速度至: {self.target_speed} km/h")
+ return None
+ return controls
+
+ def smart_offset_correction(self, current_offset):
+ """智能偏移校正"""
+ # 计算平均偏移
+ if len(self.offset_history) >= 5:
+ avg_offset = sum(self.offset_history) / len(self.offset_history)
+ else:
+ avg_offset = current_offset
+
+ # 目标偏移控制
+ target_offset = 0.0 # 目标是在中心
+
+ # 计算偏移误差
+ offset_error = current_offset - target_offset
+
+ # 根据误差大小决定转向
+ if offset_error > 0.15: # 严重右偏
+ return -0.3
+ elif offset_error > 0.08: # 中度右偏
+ return -0.18
+ elif offset_error > 0.03: # 轻微右偏
+ return -0.08
+ elif offset_error < -0.12: # 严重左偏
+ return 0.2
+ elif offset_error < -0.06: # 中度左偏
+ return 0.1
+ elif offset_error < -0.02: # 轻微左偏
+ return 0.05
+ else: # 良好位置
+ return -0.02 # 轻微左倾预防
+
+ def advanced_safe_control_improved(self, duration=70):
+ """
+ 改进的增强安全控制:防碰撞 + 智能路口导航
参数:
duration: 演示总时长(秒)
"""
if not self.is_connected or not self.is_api_control_enabled:
- print("错误: 请先连接并启用API控制")
+ self.log_message("错误: 请先连接并启用API控制")
return False
- print(f"\n开始手动控制演示 ({duration}秒)...")
+ self.log_message(f"\n开始改进版增强安全控制演示 ({duration}秒)...")
+ self.log_message("策略: 智能防碰撞 + 改进路口导航 + 动态调整")
start_time = time.time()
- sequence = 0
-
+ controls = airsim.CarControls()
- try:
- while time.time() - start_time < duration:
- elapsed = time.time() - start_time
+ # 控制参数
+ target_speed_kmh = self.target_speed
+ base_throttle = 0.45 # 降低基础油门
+ # 状态跟踪
+ offset_history = deque(maxlen=10)
+ max_right_offset = 0
+ last_good_position = self.initial_position.copy()
- return True
+ # 路口相关
+ intersection_navigation_started = False
+ intersection_navigation_complete = False
- except Exception as e:
- print(f"\n✗ 控制演示出错: {e}")
- return False
-
- def data_collection_demo(self, duration=5):
- """数据采集演示:采集所有传感器数据"""
- print(f"\n开始数据采集演示 ({duration}秒)...")
-
- start_time = time.time()
- frame_count = 0
+ # 改进的碰撞预防
+ collision_prevention_active = False
+ collision_prevention_timer = 0
try:
while time.time() - start_time < duration:
- frame_count += 1
-
- # 采集所有摄像头图像
- images = self.capture_camera_images()
-
- # 采集IMU数据
- imu_data = self.get_imu_data()
-
- # 采集GPS数据
- gps_data = self.get_gps_data()
-
- # 获取车辆状态
- vehicle_state = self.get_vehicle_state()
-
- # 记录到日志
- log_entry = {
- "frame": frame_count,
- "timestamp": time.time(),
- "images": len(images),
- "imu_data": imu_data is not None,
- "gps_data": gps_data is not None,
- "vehicle_state": vehicle_state is not None
- }
- self.data_log.append(log_entry)
+ elapsed = time.time() - start_time
- print(f"\r采集帧: {frame_count} | "
- f"图像: {len(images)} | "
- f"速度: {vehicle_state['speed_kmh'] if vehicle_state else 'N/A':.1f} km/h", end="")
+ # 获取当前状态
+ state = self.get_vehicle_state()
+ if not state:
+ time.sleep(0.1)
+ continue
+
+ current_speed = state['speed_kmh']
+ current_position = state['position']
+ current_yaw = state['yaw']
+ collision_detected = state['collision']
+
+ # 1. 碰撞恢复处理(最高优先级)
+ if self.collision_recovery_mode:
+ recovery_controls = self.execute_collision_recovery_improved(state)
+ if recovery_controls:
+ self.client.setCarControls(recovery_controls, vehicle_name=self.vehicle_name)
+ time.sleep(0.12) # 降低控制频率
+ continue
+
+ # 2. 检测路口(改进版)
+ if not self.intersection_passed and elapsed > 15.0: # 行驶15秒后开始检测
+ self.detect_intersection_improved(current_position, current_yaw, current_speed)
+
+ # 3. 路口导航处理(改进版)
+ if self.intersection_detected and not self.intersection_passed:
+ intersection_controls = self.navigate_intersection_improved(state, elapsed)
+ if intersection_controls:
+ self.client.setCarControls(intersection_controls, vehicle_name=self.vehicle_name)
+
+ # 显示状态
+ status_line = (f"🚦路口导航 | 转向: {self.intersection_turn_direction} | "
+ f"速度: {current_speed:5.1f} km/h | "
+ f"转向角: {intersection_controls.steering:+.3f}")
+ print(f"\r{status_line}", end="")
+ time.sleep(0.1) # 降低控制频率
+ continue
+
+ # 4. 正常行驶防碰撞控制
+ absolute_offset = self.calculate_lateral_offset(current_position)
+ offset_history.append(absolute_offset)
+
+ if absolute_offset > max_right_offset:
+ max_right_offset = absolute_offset
+
+ # 动态调整目标速度
+ if self.intersection_detected and not self.intersection_passed:
+ target_speed_kmh = 12 # 路口更慢的速度
+ elif collision_detected or self.collision_count > 0:
+ target_speed_kmh = max(10, self.target_speed - 5) # 碰撞后低速
+ else:
+ target_speed_kmh = self.target_speed # 正常速度
+
+ # 智能转向决策(改进版)
+ base_steering = self.smart_offset_correction(absolute_offset)
+ collision_risk = False
+
+ # 根据偏移历史预测碰撞风险
+ if len(offset_history) >= 3:
+ recent_trend = offset_history[-1] - offset_history[-3]
+ if recent_trend > 0.05: # 快速右偏趋势
+ collision_risk = True
+ base_steering = max(base_steering, -0.25) # 增加左转力度
+
+ # 油门控制(改进版)
+ speed_error = target_speed_kmh - current_speed
+
+ if collision_risk or self.collision_recovery_mode:
+ controls.throttle = base_throttle * 0.25
+ controls.brake = 0.15
+ elif self.collision_count > 0: # 之前发生过碰撞,更谨慎
+ if speed_error > 3:
+ controls.throttle = base_throttle * 0.6
+ controls.brake = 0
+ elif speed_error > -2:
+ controls.throttle = base_throttle * 0.4
+ controls.brake = 0.05
+ else:
+ controls.throttle = 0
+ controls.brake = 0.15
+ else:
+ # 正常速度控制
+ if speed_error > 5:
+ controls.throttle = min(0.6, base_throttle + 0.15)
+ controls.brake = 0
+ elif speed_error > 0:
+ controls.throttle = base_throttle
+ controls.brake = 0
+ elif speed_error > -4:
+ controls.throttle = base_throttle * 0.6
+ controls.brake = 0.05
+ else:
+ controls.throttle = 0
+ controls.brake = 0.2
+
+ # 应用转向(平滑处理)
+ steering_change = base_steering - self.last_steering
+ steering_change = max(-0.08, min(0.08, steering_change)) # 限制变化率
+ controls.steering = self.last_steering + steering_change
+ self.last_steering = controls.steering
+
+ # 5. 阶段控制(基于时间的策略调整)
+ phase_control_factor = 1.0
+
+ if elapsed < 10.0: # 起步阶段,更谨慎
+ phase_control_factor = 0.6
+ controls.steering = -0.03 # 轻微左倾
+
+ elif elapsed > duration - 15.0: # 结束前15秒
+ # 逐渐减速停车
+ stop_progress = (elapsed - (duration - 15.0)) / 15.0
+ controls.throttle *= (1.0 - stop_progress)
+ controls.brake += stop_progress * 0.4
+
+ # 尝试回到中心
+ if absolute_offset > 0.03:
+ controls.steering = -0.08
+ elif absolute_offset < -0.03:
+ controls.steering = 0.05
+
+ # 应用阶段控制
+ controls.throttle *= phase_control_factor
+
+ # 6. 发送控制命令
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
- time.sleep(0.2) # 5Hz采集频率
+ # 7. 显示状态
+ status_symbol = "✓"
+ if collision_risk:
+ status_symbol = "⚠️"
+ if collision_detected:
+ status_symbol = "💥"
+ if self.intersection_detected:
+ status_symbol = "🚦"
+ if self.collision_recovery_mode:
+ status_symbol = "🔄"
+
+ status_line = (f"{status_symbol} 速度: {current_speed:5.1f} km/h | "
+ f"转向: {controls.steering:+.3f} | "
+ f"油门: {controls.throttle:.2f} | "
+ f"刹车: {controls.brake:.2f} | "
+ f"偏航: {current_yaw:6.1f}° | "
+ f"偏移: {absolute_offset:+.3f}m")
+
+ if self.intersection_detected:
+ status_line += f" | 路口: {self.intersection_turn_direction}"
+ if self.in_intersection:
+ distance_in_intersection = self.calculate_distance_from_point(
+ current_position, self.intersection_entry_position)
+ status_line += f" ({distance_in_intersection:.1f}m)"
+
+ print(f"\r{status_line}", end="")
+
+ time.sleep(0.1) # 10Hz控制频率,更稳定
+
+ print("\n✓ 改进版增强安全控制演示完成")
+
+ # 最终分析
+ self.log_message(f"\n最终统计:")
+ self.log_message(f"最大向右偏移: {max_right_offset:.3f}米")
+ self.log_message(f"碰撞次数: {self.collision_count}")
+ self.log_message(f"路径点数量: {len(self.path_history)}")
+ self.log_message(f"总行驶距离: {self.calculate_distance_traveled():.1f}米")
+
+ if self.intersection_detected:
+ self.log_message(f"路口检测: {'成功' if self.intersection_passed else '失败'}")
+ if self.intersection_passed:
+ self.log_message(f"路口转向: {self.intersection_turn_direction}")
+ self.log_message(f"左转调整次数: {self.left_turn_adjustment_count}")
+
+ if self.collision_count > 0:
+ self.log_message(f"⚠️ 发生碰撞: {self.collision_count}次")
+ if self.collision_count <= 2:
+ self.log_message("碰撞次数在可接受范围内")
+ else:
+ self.log_message("碰撞次数较多,需要进一步优化")
+ else:
+ self.log_message("✓ 安全:无碰撞发生")
- print(f"\n✓ 数据采集完成,共采集 {frame_count} 帧")
return True
+ except KeyboardInterrupt:
+ self.log_message("\n\n演示被用户中断")
+ return False
except Exception as e:
- print(f"\n✗ 数据采集出错: {e}")
+ self.log_message(f"\n✗ 控制演示出错: {e}")
+ import traceback
+ traceback.print_exc()
return False
def save_simulation_data(self):
- """保存所有仿真数据到文件"""
+ """保存仿真数据 - 增强版"""
try:
- # 保存车辆状态日志
- log_file = f"{self.data_dir}/simulation_log.json"
- with open(log_file, 'w') as f:
- json.dump(self.data_log, f, indent=2)
+ # 保存路径历史
+ if self.path_history:
+ # 转换为可序列化格式
+ serializable_history = []
+ for point in self.path_history:
+ serializable_point = {
+ "timestamp": point["timestamp"],
+ "position": point["position"],
+ "yaw": point["yaw"],
+ "speed": point["speed"],
+ "velocity": point.get("velocity", {"x": 0, "y": 0, "z": 0}),
+ "collision": point.get("collision", False)
+ }
+ serializable_history.append(serializable_point)
+
+ path_file = f"{self.data_dir}/path_history.json"
+ with open(path_file, 'w', encoding='utf-8') as f:
+ json.dump(serializable_history, f, indent=2, ensure_ascii=False)
+ self.log_message(f"✓ 路径历史已保存: {path_file}")
+
+ # 保存路口决策数据
+ intersection_data = {
+ "intersection_detected": self.intersection_detected,
+ "intersection_passed": self.intersection_passed,
+ "turn_direction": self.intersection_turn_direction,
+ "collision_count": self.collision_count,
+ "left_turn_adjustments": self.left_turn_adjustment_count,
+ "total_distance": self.calculate_distance_traveled() if self.position_history else 0
+ }
+
+ intersection_file = f"{self.data_dir}/intersection_data.json"
+ with open(intersection_file, 'w', encoding='utf-8') as f:
+ json.dump(intersection_data, f, indent=2, ensure_ascii=False)
- # 保存传感器数据统计
+ # 保存统计数据
stats = {
"timestamp": datetime.now().isoformat(),
"vehicle_name": self.vehicle_name,
- "camera_frames": len(self.sensor_data["camera"]),
- "imu_samples": len(self.sensor_data["imu"]),
- "gps_samples": len(self.sensor_data["gps"]),
-
+ "collision_count": self.collision_count,
+ "path_history_length": len(self.path_history),
+ "initial_position": self.initial_position,
+ "initial_yaw": self.initial_yaw,
+ "intersection_data": intersection_data,
+ "target_speed": self.target_speed,
+ "aggressiveness": self.aggressiveness
}
stats_file = f"{self.data_dir}/simulation_stats.json"
- with open(stats_file, 'w') as f:
- json.dump(stats, f, indent=2)
-
-
- # 生成数据报告
- report_file = f"{self.data_dir}/report.txt"
- with open(report_file, 'w') as f:
- f.write("=" * 50 + "\n")
- f.write("AirSim无人车仿真数据报告\n")
- f.write("=" * 50 + "\n\n")
- f.write(f"仿真时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
- f.write(f"车辆名称: {self.vehicle_name}\n")
- f.write(f"摄像头帧数: {stats['camera_frames']}\n")
- f.write(f"IMU采样数: {stats['imu_samples']}\n")
- f.write(f"GPS采样数: {stats['gps_samples']}\n")
-
- f.write("数据文件:\n")
- for file in os.listdir(self.data_dir):
- f.write(f" - {file}\n")
-
- print(f"\n✓ 仿真数据已保存到: {self.data_dir}")
- print(f" 日志文件: {log_file}")
- print(f" 统计数据: {stats_file}")
- print(f" 报告文件: {report_file}")
+ with open(stats_file, 'w', encoding='utf-8') as f:
+ json.dump(stats, f, indent=2, ensure_ascii=False)
+ # 生成详细报告
+ self.generate_detailed_report()
return True
except Exception as e:
- print(f"✗ 保存数据失败: {e}")
+ self.log_message(f"✗ 保存数据失败: {e}")
return False
+ def generate_detailed_report(self):
+ """生成详细报告"""
+ report_file = f"{self.data_dir}/detailed_report.txt"
+
+ with open(report_file, 'w', encoding='utf-8') as f:
+ f.write("=" * 70 + "\n")
+ f.write("AirSim无人车智能控制演示详细报告 V2.0\n")
+ f.write("丁字路口通过改进版本\n")
+ f.write("=" * 70 + "\n\n")
+
+ f.write(f"演示时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
+ f.write(f"车辆名称: {self.vehicle_name}\n")
+ f.write(f"碰撞次数: {self.collision_count}\n")
+ f.write(f"路径点数量: {len(self.path_history)}\n")
+ f.write(f"行驶距离: {self.calculate_distance_traveled():.1f}米\n")
+ f.write(f"目标速度: {self.target_speed} km/h\n")
+ f.write(f"驾驶侵略性: {self.aggressiveness}\n\n")
+
+ f.write("路口导航统计:\n")
+ f.write(f" 路口检测: {'是' if self.intersection_detected else '否'}\n")
+ if self.intersection_detected:
+ f.write(f" 路口通过: {'是' if self.intersection_passed else '否'}\n")
+ f.write(f" 转向决策: {self.intersection_turn_direction}\n")
+ f.write(f" 左转调整次数: {self.left_turn_adjustment_count}\n")
+
+ f.write("\n路径分析:\n")
+ if self.path_history:
+ y_values = [p['position']['y'] for p in self.path_history]
+ x_values = [p['position']['x'] for p in self.path_history]
+
+ min_y = min(y_values)
+ max_y = max(y_values)
+ avg_y = sum(y_values) / len(y_values)
+
+ f.write(f" Y坐标范围: {min_y:.3f} 到 {max_y:.3f} 米\n")
+ f.write(f" 平均Y坐标: {avg_y:.3f} 米\n")
+ f.write(f" 初始Y坐标: {self.initial_position['y']:.3f} 米\n")
+ f.write(f" 最大右偏移: {max_y - self.initial_position['y']:.3f} 米\n")
+
+ # 分析偏移趋势
+ if len(y_values) > 20:
+ start_avg = sum(y_values[:10]) / 10
+ middle_avg = sum(y_values[len(y_values) // 2 - 5:len(y_values) // 2 + 5]) / 10
+ end_avg = sum(y_values[-10:]) / 10
+
+ f.write(f" 起始平均Y: {start_avg:.3f} 米\n")
+ f.write(f" 中间平均Y: {middle_avg:.3f} 米\n")
+ f.write(f" 结束平均Y: {end_avg:.3f} 米\n")
+
+ start_to_mid = middle_avg - start_avg
+ mid_to_end = end_avg - middle_avg
+
+ f.write(f" 第一阶段偏移: {start_to_mid:+.3f} 米\n")
+ f.write(f" 第二阶段偏移: {mid_to_end:+.3f} 米\n")
+
+ if abs(start_to_mid) < 0.5 and abs(mid_to_end) < 0.5:
+ f.write(" 结论: 车辆基本保持稳定\n")
+ elif start_to_mid > 0.5:
+ f.write(" 结论: 第一阶段有明显右偏\n")
+ elif mid_to_end < -0.5:
+ f.write(" 结论: 第二阶段有明显左偏\n")
+
+ f.write("\n性能评估:\n")
+ if self.collision_count == 0:
+ f.write(" ✓ 碰撞避免: 优秀\n")
+ elif self.collision_count <= 1:
+ f.write(" ⚠️ 碰撞避免: 良好\n")
+ elif self.collision_count <= 3:
+ f.write(" ⚠️ 碰撞避免: 一般\n")
+ else:
+ f.write(" ✗ 碰撞避免: 需要改进\n")
- """运行完整演示"""
- print("=" * 60)
- print("AirSimNH 无人车完整仿真演示")
- print("=" * 60)
-
- # 步骤1: 连接仿真器
+ if self.intersection_passed:
+ f.write(" ✓ 路口通过: 成功\n")
+ elif self.intersection_detected:
+ f.write(" ⚠️ 路口通过: 部分成功\n")
+ else:
+ f.write(" ? 路口通过: 未检测到\n")
+
+ f.write("\n改进建议:\n")
+ if self.collision_count > 0:
+ f.write(" 1. 进一步降低目标速度\n")
+ f.write(" 2. 增加左转避障检测\n")
+ f.write(" 3. 优化碰撞恢复策略\n")
+ f.write(" 4. 调整转向增益参数\n")
+
+ if not self.intersection_passed and self.collision_count == 0:
+ f.write(" 1. 延长演示时间\n")
+ f.write(" 2. 提高路口检测灵敏度\n")
+ f.write(" 3. 优化转向决策逻辑\n")
+
+ if self.left_turn_adjustment_count > 0:
+ f.write(" 1. 左转策略需要进一步优化\n")
+ f.write(" 2. 考虑增加传感器检测\n")
+ f.write(" 3. 调整左转阶段参数\n")
+
+ self.log_message(f"✓ 详细报告已保存: {report_file}")
+
+ def run_enhanced_demo(self, duration=70):
+ """运行增强演示 - 支持路口导航"""
+ self.log_message("=" * 70)
+ self.log_message("AirSimNH 无人车智能控制演示 V2.0")
+ self.log_message("丁字路口通过改进版本")
+ self.log_message("=" * 70)
+
+ # 连接仿真器
if not self.connect():
return False
try:
- # 步骤2: 启用API控制
+ # 启用API控制
if not self.enable_api_control(True):
return False
+ self.log_message("\n等待车辆稳定...")
+ time.sleep(3) # 增加等待时间
- # 步骤3: 手动控制演示
- if not self.manual_control_demo(control_duration):
- print("手动控制演示失败,继续其他演示...")
-
- # 短暂暂停,让车辆完全停止
- time.sleep(2)
+ # 运行改进版增强安全控制演示
+ self.log_message("\n" + "=" * 70)
+ self.log_message("开始改进版增强安全控制演示")
+ self.log_message("策略: 智能防碰撞 + 改进路口导航 + 动态调整")
+ self.log_message("=" * 70)
- # 步骤4: 数据采集演示
- if not self.data_collection_demo(data_duration):
- print("数据采集演示失败,继续保存数据...")
+ success = self.advanced_safe_control_improved(duration)
- # 步骤5: 保存数据
- self.save_simulation_data()
+ if success:
+ self.log_message("\n" + "=" * 70)
+ self.log_message("演示完成,保存数据...")
+ self.log_message("=" * 70)
+ self.save_simulation_data()
- return True
+ return success
+ except Exception as e:
+ print(f"\n演示过程中出错: {e}")
+ import traceback
+ traceback.print_exc()
+ return False
finally:
- # 步骤6: 清理和退出
+ # 清理
self.cleanup()
def cleanup(self):
"""清理资源"""
- print("\n正在清理资源...")
-
- # 停止车辆
- if self.is_api_control_enabled:
- controls = airsim.CarControls()
-
- except:
- pass
+ try:
+ # 先记录清理开始
+ print("\n正在清理资源...")
- # 禁用API控制
- try:
- self.enable_api_control(False)
- except:
- pass
+ # 停止车辆
+ if self.is_api_control_enabled and self.client:
+ controls = airsim.CarControls()
+ controls.throttle = 0
+ controls.brake = 1.0
+ controls.steering = 0
+ controls.handbrake = True
+ try:
+ self.client.setCarControls(controls, vehicle_name=self.vehicle_name)
+ time.sleep(0.5)
+ except:
+ pass
+
+ # 禁用API控制
+ try:
+ if self.client:
+ self.client.enableApiControl(False, vehicle_name=self.vehicle_name)
+ except:
+ pass
+
+ # 关闭日志文件
+ if self.log_file and not self.log_file.closed:
+ try:
+ # 先写入清理完成信息
+ self.log_file.write(f"[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}] ✓ 清理完成\n")
+ self.log_file.flush()
+ self.log_file.close()
+ except:
+ pass
- print("✓ 清理完成")
+ except Exception as e:
+ print(f"清理过程中出错: {e}")
+ finally:
+ print("✓ 清理完成")
def main():
"""主函数"""
- # 创建仿真器实例
simulator = AirSimNHCarSimulator(
ip="127.0.0.1",
port=41451,
vehicle_name="PhysXCar"
)
- # 运行完整演示
try:
- simulator.run_full_demo(
-
- data_duration=10 # 数据采集时长(秒)
- )
+ # 运行增强演示,时间延长到70秒
+ simulator.run_enhanced_demo(duration=70)
- print("\n" + "=" * 60)
- print("仿真演示完成!")
- print("=" * 60)
+ print("\n" + "=" * 70)
+ print("改进版智能控制演示完成!")
+ print("=" * 70)
except KeyboardInterrupt:
- print("\n\n仿真被用户中断")
+ print("\n\n演示被用户中断")
simulator.cleanup()
except Exception as e:
- print(f"\n仿真出错: {e}")
+ print(f"\n演示出错: {e}")
+ import traceback
+ traceback.print_exc()
simulator.cleanup()
From 7e7a05016cbef0ca640a25dca5ff040fbc925cb2 Mon Sep 17 00:00:00 2001
From: solfdd <1006434858@qq.com>
Date: Sun, 28 Dec 2025 14:27:45 +0800
Subject: [PATCH 16/16] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/Safe_navigation/run_main.py | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/src/Safe_navigation/run_main.py b/src/Safe_navigation/run_main.py
index 234a1d6b6e..82cdc5d3a4 100644
--- a/src/Safe_navigation/run_main.py
+++ b/src/Safe_navigation/run_main.py
@@ -103,6 +103,8 @@ def _init_log_file(self):
"""初始化日志文件"""
try:
self.log_file = open(f"{self.data_dir}/simulation_log.txt", "w")
+ self.log_file.write(f"AirSimNH仿真日志 - 开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
+ self.log_file.write("=" * 50 + "\n")
except Exception as e:
print(f"无法创建日志文件: {e}")
self.log_file = None
@@ -371,7 +373,8 @@ def get_vehicle_state(self):
# 限制历史记录长度
if len(self.path_history) > 1000:
- self.path_history.pop(0)
+ # 改为删除前100个元素,提高效率
+ del self.path_history[:100]
state_info = {
"timestamp": current_time,
@@ -657,6 +660,10 @@ def advanced_safe_control_improved(self, duration=70):
current_yaw = state['yaw']
collision_detected = state['collision']
+ # 记录偏移历史 - 确保每次循环都记录
+ absolute_offset = self.calculate_lateral_offset(current_position)
+ offset_history.append(absolute_offset)
+
# 1. 碰撞恢复处理(最高优先级)
if self.collision_recovery_mode:
recovery_controls = self.execute_collision_recovery_improved(state)
@@ -684,9 +691,6 @@ def advanced_safe_control_improved(self, duration=70):
continue
# 4. 正常行驶防碰撞控制
- absolute_offset = self.calculate_lateral_offset(current_position)
- offset_history.append(absolute_offset)
-
if absolute_offset > max_right_offset:
max_right_offset = absolute_offset