From a2648e76807ce5f46a82b8a9af8f4cca81e80d12 Mon Sep 17 00:00:00 2001
From: 725921 <3407246504@qq.com>
Date: Sun, 21 Dec 2025 12:30:32 +0800
Subject: [PATCH 01/11] =?UTF-8?q?=E6=97=A0=E4=BA=BA=E6=9C=BA=E5=AF=BC?=
=?UTF-8?q?=E8=88=AA=E7=B3=BB=E7=BB=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
简化app
---
src/UAV_navigation_system/app.py | 1963 +++++++++++++++++++++++++++++-
1 file changed, 1945 insertions(+), 18 deletions(-)
diff --git a/src/UAV_navigation_system/app.py b/src/UAV_navigation_system/app.py
index e6a0cdbc11..a6066686fc 100644
--- a/src/UAV_navigation_system/app.py
+++ b/src/UAV_navigation_system/app.py
@@ -1,33 +1,1960 @@
-# app.py - Flask Web应用主程序
-from flask import Flask, render_template, Response, jsonify
+#!/usr/bin/env python3
+"""
+🚁 无人机导航系统 - 完整演示版
+无需真实数据,立即展示效果
+"""
+
+from flask import Flask, render_template, Response, jsonify, request, send_file
import cv2
+import numpy as np
import threading
+import time
+import json
+import os
+import io
+from datetime import datetime
+from PIL import Image, ImageDraw, ImageFont
+import random
app = Flask(__name__)
-def generate_frames():
+
+# ==================== 配置参数 ====================
+class Config:
+ """演示配置"""
+ # 演示模式配置
+ DEMO_MODE = True # 演示模式,使用虚拟数据
+ USE_VIRTUAL_CAMERA = True # 使用虚拟摄像头
+
+ # 类别配置
+ CLASS_NAMES = ['森林 Forest', '火灾 Fire', '城市 City', '动物 Animal', '车辆 Vehicle', '水域 Water']
+ CLASS_COLORS = {
+ '森林 Forest': (0, 128, 0),
+ '火灾 Fire': (255, 0, 0),
+ '城市 City': (128, 128, 128),
+ '动物 Animal': (255, 165, 0),
+ '车辆 Vehicle': (255, 0, 255),
+ '水域 Water': (0, 191, 255)
+ }
+
+ # 无人机状态
+ DRONE_STATUS = {
+ 'battery': 100,
+ 'altitude': 0,
+ 'speed': 0,
+ 'location': {'x': 0, 'y': 0, 'z': 0},
+ 'mode': 'LANDED', # LANDED, TAKEOFF, FLYING, LANDING
+ 'detected_class': '正在检测...',
+ 'confidence': 0,
+ 'timestamp': None,
+ 'temperature': 25,
+ 'wind_speed': 5,
+ 'gps_signal': '强'
+ }
+
+
+config = Config()
+
+
+# ==================== 虚拟摄像头和检测系统 ====================
+class VirtualCamera:
+ """虚拟摄像头系统 - 生成模拟的无人机画面"""
+
+ def __init__(self):
+ self.frame_width = 640
+ self.frame_height = 480
+ self.frame_count = 0
+ self.current_scene = '城市 City'
+ self.scene_transition = 0
+ self.scene_history = []
+
+ # 场景切换概率
+ self.scene_change_prob = 0.05
+
+ # 创建虚拟场景图像
+ self.scene_images = self.create_scene_images()
+
+ print("🎥 虚拟摄像头初始化完成")
+ print("📊 可检测场景:", ", ".join(config.CLASS_NAMES))
+
+ def create_scene_images(self):
+ """创建虚拟场景图像"""
+ images = {}
+
+ for scene in config.CLASS_NAMES:
+ # 创建基础图像
+ img = np.zeros((self.frame_height, self.frame_width, 3), dtype=np.uint8)
+
+ # 根据场景类型设置不同颜色和模式
+ if '森林' in scene:
+ # 森林 - 绿色系
+ img[:, :, 1] = random.randint(100, 200) # 绿色
+ # 添加树木纹理
+ for i in range(20):
+ x = random.randint(50, self.frame_width - 50)
+ y = random.randint(50, self.frame_height - 50)
+ cv2.circle(img, (x, y), 15, (0, random.randint(150, 255), 0), -1)
+
+ elif '火灾' in scene:
+ # 火灾 - 红色系
+ img[:, :, 2] = random.randint(150, 255) # 红色
+ img[:, :, 1] = random.randint(50, 150) # 黄色
+ # 添加火焰效果
+ for i in range(30):
+ x = random.randint(50, self.frame_width - 50)
+ y = random.randint(50, self.frame_height - 50)
+ size = random.randint(5, 20)
+ cv2.circle(img, (x, y), size, (0, random.randint(100, 200), random.randint(200, 255)), -1)
+
+ elif '城市' in scene:
+ # 城市 - 灰色系
+ gray = random.randint(100, 200)
+ img[:, :, 0] = gray
+ img[:, :, 1] = gray
+ img[:, :, 2] = gray
+ # 添加建筑
+ for i in range(10):
+ x = random.randint(50, self.frame_width - 50)
+ width = random.randint(20, 60)
+ height = random.randint(40, 150)
+ cv2.rectangle(img, (x, self.frame_height - height),
+ (x + width, self.frame_height),
+ (gray + 20, gray + 20, gray + 20), -1)
+
+ elif '动物' in scene:
+ # 动物 - 棕色系
+ img[:, :, 0] = random.randint(30, 60) # 蓝色通道(棕色偏黄)
+ img[:, :, 1] = random.randint(80, 120) # 绿色通道
+ img[:, :, 2] = random.randint(140, 180) # 红色通道
+ # 添加动物轮廓
+ for i in range(5):
+ x = random.randint(50, self.frame_width - 50)
+ y = random.randint(50, self.frame_height - 50)
+ cv2.ellipse(img, (x, y), (30, 20), 0, 0, 360, (100, 70, 40), -1)
+
+ elif '车辆' in scene:
+ # 车辆 - 各种颜色
+ img[:, :, 0] = random.randint(50, 100) # 蓝色
+ img[:, :, 1] = random.randint(50, 100) # 绿色
+ img[:, :, 2] = random.randint(50, 100) # 红色
+ # 添加车辆
+ for i in range(8):
+ x = random.randint(50, self.frame_width - 50)
+ y = random.randint(100, self.frame_height - 50)
+ cv2.rectangle(img, (x - 25, y - 15), (x + 25, y + 15),
+ (random.randint(100, 255), random.randint(100, 255), random.randint(100, 255)), -1)
+
+ elif '水域' in scene:
+ # 水域 - 蓝色系
+ img[:, :, 0] = random.randint(150, 255) # 蓝色
+ # 添加波纹效果
+ for i in range(15):
+ x = random.randint(50, self.frame_width - 50)
+ y = random.randint(50, self.frame_height - 50)
+ cv2.circle(img, (x, y), random.randint(10, 40),
+ (random.randint(100, 200), random.randint(100, 200), 255), 2)
+
+ images[scene] = img
+
+ return images
+
+ def get_frame(self):
+ """获取虚拟摄像头帧"""
+ self.frame_count += 1
+
+ # 随机切换场景(模拟摄像头移动)
+ if random.random() < self.scene_change_prob and self.scene_transition == 0:
+ self.scene_history.append(self.current_scene)
+ if len(self.scene_history) > 5:
+ self.scene_history.pop(0)
+
+ # 随机选择新场景(排除当前场景)
+ available_scenes = [s for s in config.CLASS_NAMES if s != self.current_scene]
+ self.current_scene = random.choice(available_scenes)
+ self.scene_transition = 30 # 30帧的过渡效果
+
+ # 获取当前场景图像
+ frame = self.scene_images[self.current_scene].copy()
+
+ # 添加过渡效果
+ if self.scene_transition > 0:
+ old_scene = self.scene_history[-1] if self.scene_history else self.current_scene
+ old_frame = self.scene_images[old_scene].copy()
+
+ alpha = self.scene_transition / 30.0
+ frame = cv2.addWeighted(frame, 1 - alpha, old_frame, alpha, 0)
+ self.scene_transition -= 1
+
+ # 模拟摄像头噪声
+ noise = np.random.normal(0, 3, frame.shape).astype(np.uint8)
+ frame = cv2.add(frame, noise)
+
+ # 添加时间戳
+ cv2.putText(frame, f"帧: {self.frame_count}", (10, 20),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
+
+ return frame, self.current_scene
+
+ def simulate_detection(self, scene):
+ """模拟深度学习检测结果"""
+ # 基础置信度
+ if scene in config.CLASS_NAMES:
+ base_confidence = 0.85 + random.random() * 0.12 # 85-97%
+ else:
+ base_confidence = 0.3 + random.random() * 0.4 # 30-70%
+
+ # 添加一些随机波动
+ confidence = max(0.1, min(0.99, base_confidence + (random.random() - 0.5) * 0.1))
+
+ return scene, confidence
+
+
+# 创建虚拟摄像头实例
+virtual_camera = VirtualCamera()
+
+
+# ==================== 无人机智能系统 ====================
+class IntelligentDroneSystem:
+ """无人机智能控制系统"""
+
+ def __init__(self):
+ self.emergency_level = 0 # 0-10,10为最高紧急级别
+ self.flight_log = []
+ self.last_detection_time = time.time()
+ self.detection_interval = 2.0 # 每2秒检测一次
+ self.response_actions = {
+ '森林 Forest': self.normal_flight,
+ '火灾 Fire': self.emergency_response,
+ '城市 City': self.urban_flight,
+ '动物 Animal': self.avoid_obstacle,
+ '车辆 Vehicle': self.traffic_awareness,
+ '水域 Water': self.water_precaution
+ }
+
+ print("🤖 无人机智能系统初始化完成")
+
+ def normal_flight(self):
+ """正常飞行模式"""
+ return {
+ 'action': '正常飞行',
+ 'speed': 5,
+ 'altitude': '维持',
+ 'message': '森林环境,保持正常飞行模式'
+ }
+
+ def emergency_response(self):
+ """火灾应急响应"""
+ self.emergency_level = min(10, self.emergency_level + 2)
+ return {
+ 'action': '紧急响应',
+ 'speed': 8,
+ 'altitude': '升高',
+ 'message': '检测到火灾!正在升高避让并发送警报'
+ }
+
+ def urban_flight(self):
+ """城市飞行模式"""
+ return {
+ 'action': '谨慎飞行',
+ 'speed': 3,
+ 'altitude': '维持',
+ 'message': '城市环境,注意建筑物和人群'
+ }
+
+ def avoid_obstacle(self):
+ """避障模式"""
+ return {
+ 'action': '避障飞行',
+ 'speed': 4,
+ 'altitude': '微调',
+ 'message': '检测到动物,保持安全距离'
+ }
+
+ def traffic_awareness(self):
+ """交通感知模式"""
+ return {
+ 'action': '交通感知',
+ 'speed': 2,
+ 'altitude': '维持',
+ 'message': '检测到车辆,注意交通状况'
+ }
+
+ def water_precaution(self):
+ """水域预防模式"""
+ return {
+ 'action': '水域预防',
+ 'speed': 3,
+ 'altitude': '升高',
+ 'message': '检测到水域,升高飞行高度避免接触'
+ }
+
+ def analyze_scene(self, scene, confidence):
+ """分析场景并制定飞行策略"""
+ current_time = time.time()
+
+ # 检查是否需要更新检测
+ if current_time - self.last_detection_time < self.detection_interval:
+ return None
+
+ self.last_detection_time = current_time
+
+ # 根据置信度调整响应
+ if confidence < 0.6:
+ response = {
+ 'action': '待确认',
+ 'speed': 1,
+ 'altitude': '悬停',
+ 'message': '检测置信度较低,悬停待确认'
+ }
+ else:
+ # 获取对应场景的响应
+ if scene in self.response_actions:
+ response = self.response_actions[scene]()
+ else:
+ response = self.normal_flight()
+
+ # 记录飞行日志
+ log_entry = {
+ 'timestamp': datetime.now().strftime("%H:%M:%S"),
+ 'scene': scene,
+ 'confidence': confidence,
+ 'action': response['action'],
+ 'message': response['message']
+ }
+ self.flight_log.append(log_entry)
+
+ # 保持日志大小
+ if len(self.flight_log) > 20:
+ self.flight_log = self.flight_log[-20:]
+
+ return response
+
+
+# 创建无人机智能系统
+drone_system = IntelligentDroneSystem()
+
+
+# ==================== 无人机模拟器 ====================
+class DroneSimulator(threading.Thread):
+ """无人机状态模拟器线程"""
+
+ def __init__(self):
+ super().__init__()
+ self.running = True
+ self.daemon = True
+ self.simulation_speed = 1.0
+ self.last_update = time.time()
+
+ print("🚁 无人机模拟器启动")
+
+ def run(self):
+ """运行模拟器"""
+ while self.running:
+ try:
+ current_time = time.time()
+ delta_time = min(1.0, current_time - self.last_update) * self.simulation_speed
+ self.last_update = current_time
+
+ # 更新无人机状态
+ self.update_drone_status(delta_time)
+
+ # 根据当前模式更新状态
+ self.update_by_mode(delta_time)
+
+ # 更新环境参数
+ self.update_environment()
+
+ time.sleep(0.1)
+
+ except Exception as e:
+ print(f"模拟器错误: {e}")
+ time.sleep(1)
+
+ def update_drone_status(self, delta_time):
+ """更新无人机状态"""
+ # 根据飞行模式更新电池
+ if config.DRONE_STATUS['mode'] == 'FLYING':
+ config.DRONE_STATUS['battery'] = max(0, config.DRONE_STATUS['battery'] - 0.1 * delta_time)
+
+ # 自动充电(如果着陆且电量低于20%)
+ elif config.DRONE_STATUS['mode'] == 'LANDED' and config.DRONE_STATUS['battery'] < 20:
+ config.DRONE_STATUS['battery'] = min(100, config.DRONE_STATUS['battery'] + 0.5 * delta_time)
+
+ def update_by_mode(self, delta_time):
+ """根据飞行模式更新"""
+ mode = config.DRONE_STATUS['mode']
+
+ if mode == 'TAKEOFF':
+ config.DRONE_STATUS['altitude'] = min(50, config.DRONE_STATUS['altitude'] + 10 * delta_time)
+ config.DRONE_STATUS['speed'] = 2
+
+ if config.DRONE_STATUS['altitude'] >= 50:
+ config.DRONE_STATUS['mode'] = 'FLYING'
+ print("🛫 起飞完成,进入飞行模式")
+
+ elif mode == 'FLYING':
+ # 随机飞行路径
+ config.DRONE_STATUS['altitude'] = 50 + random.uniform(-3, 3)
+ config.DRONE_STATUS['speed'] = 3 + random.uniform(-1, 1)
+
+ # 随机位置变化
+ config.DRONE_STATUS['location']['x'] += random.uniform(-2, 2) * delta_time
+ config.DRONE_STATUS['location']['y'] += random.uniform(-2, 2) * delta_time
+
+ elif mode == 'LANDING':
+ config.DRONE_STATUS['altitude'] = max(0, config.DRONE_STATUS['altitude'] - 8 * delta_time)
+ config.DRONE_STATUS['speed'] = 1
+
+ if config.DRONE_STATUS['altitude'] <= 0:
+ config.DRONE_STATUS['mode'] = 'LANDED'
+ config.DRONE_STATUS['speed'] = 0
+ print("🛬 降落完成")
+
+ def update_environment(self):
+ """更新环境参数"""
+ # 模拟温度变化
+ config.DRONE_STATUS['temperature'] = 20 + random.uniform(-2, 2)
+
+ # 模拟风速变化
+ config.DRONE_STATUS['wind_speed'] = max(0, 3 + random.uniform(-1, 1))
+
+ # 更新GPS信号(受环境影响)
+ if config.DRONE_STATUS['detected_class'] == '城市 City':
+ config.DRONE_STATUS['gps_signal'] = random.choice(['中', '强'])
+ else:
+ config.DRONE_STATUS['gps_signal'] = '强'
+
+ def stop(self):
+ """停止模拟器"""
+ self.running = False
+
+
+# 启动无人机模拟器
+drone_simulator = DroneSimulator()
+drone_simulator.start()
+
+
+# ==================== Flask路由和功能 ====================
+def generate_video_feed():
"""生成视频流"""
- camera = cv2.VideoCapture(0) # 使用默认摄像头
while True:
- success, frame = camera.read()
- if not success:
- break
- else:
- ret, buffer = cv2.imencode('.jpg', frame)
- frame = buffer.tobytes()
- yield (b'--frame\r\n'
- b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
+ # 获取虚拟摄像头帧
+ frame, current_scene = virtual_camera.get_frame()
+
+ # 模拟AI检测
+ detected_scene, confidence = virtual_camera.simulate_detection(current_scene)
+
+ # 更新无人机状态
+ config.DRONE_STATUS['detected_class'] = detected_scene
+ config.DRONE_STATUS['confidence'] = confidence
+ config.DRONE_STATUS['timestamp'] = datetime.now().strftime("%H:%M:%S")
+
+ # 获取智能响应
+ response = drone_system.analyze_scene(detected_scene, confidence)
+ if response:
+ # 更新飞行模式(如果响应建议)
+ if response['action'] == '紧急响应' and config.DRONE_STATUS['mode'] == 'FLYING':
+ config.DRONE_STATUS['mode'] = 'LANDING'
+
+ # 添加检测信息到帧
+ self.add_detection_overlay(frame, detected_scene, confidence)
+
+ # 编码为JPEG
+ ret, buffer = cv2.imencode('.jpg', frame)
+ frame_bytes = buffer.tobytes()
+
+ yield (b'--frame\r\n'
+ b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
+
+
+def add_detection_overlay(frame, scene, confidence):
+ """添加检测信息到视频帧"""
+ height, width = frame.shape[:2]
+
+ # 添加半透明状态栏
+ overlay = frame.copy()
+ cv2.rectangle(overlay, (0, 0), (width, 100), (0, 0, 0), -1)
+ cv2.addWeighted(overlay, 0.6, frame, 0.4, 0, frame)
+
+ # 添加标题
+ cv2.putText(frame, "🚁 无人机视觉导航演示系统", (10, 30),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
+
+ # 添加检测结果
+ color = config.CLASS_COLORS.get(scene, (255, 255, 255))
+ detection_text = f"检测: {scene}"
+ confidence_text = f"置信度: {confidence:.1%}"
+
+ cv2.putText(frame, detection_text, (10, 60),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2)
+ cv2.putText(frame, confidence_text, (10, 85),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)
+
+ # 添加无人机状态
+ status_text = f"状态: {config.DRONE_STATUS['mode']} | 电量: {config.DRONE_STATUS['battery']:.1f}%"
+ cv2.putText(frame, status_text, (width - 300, 30),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1)
+
+ # 添加场景指示器
+ for i, scene_name in enumerate(config.CLASS_NAMES):
+ x = 10 + (i * 100)
+ if x + 90 < width:
+ color = config.CLASS_COLORS.get(scene_name, (128, 128, 128))
+ thickness = 3 if scene_name == scene else 1
+ cv2.rectangle(frame, (x, height - 30), (x + 90, height - 10), color, thickness)
+
+ # 简化显示
+ scene_short = scene_name.split()[0]
+ cv2.putText(frame, scene_short, (x + 5, height - 15),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.4, color, 1)
+
@app.route('/')
def index():
- """主页"""
- return render_template('index.html')
+ """主页面"""
+ return render_template('index.html',
+ class_names=config.CLASS_NAMES,
+ drone_status=config.DRONE_STATUS,
+ class_colors=config.CLASS_COLORS)
+
@app.route('/video_feed')
def video_feed():
- """视频流路由"""
- return Response(generate_frames(),
- mimetype='multipart/x-mixed-replace; boundary=frame')
+ """视频流端点"""
+ return Response(generate_video_feed(),
+ mimetype='multipart/x-mixed-replace; boundary=frame')
+
+
+@app.route('/drone_status')
+def get_drone_status():
+ """获取无人机状态"""
+ return jsonify(config.DRONE_STATUS)
+
+
+@app.route('/flight_log')
+def get_flight_log():
+ """获取飞行日志"""
+ return jsonify(drone_system.flight_log)
+
+
+@app.route('/system_info')
+def get_system_info():
+ """获取系统信息"""
+ info = {
+ 'demo_mode': config.DEMO_MODE,
+ 'virtual_camera': config.USE_VIRTUAL_CAMERA,
+ 'fps': 30,
+ 'detection_accuracy': '演示模式',
+ 'system_time': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
+ 'uptime': time.time() - drone_simulator.last_update,
+ 'emergency_level': drone_system.emergency_level
+ }
+ return jsonify(info)
+
+
+@app.route('/control', methods=['POST'])
+def control_drone():
+ """控制无人机"""
+ data = request.json
+ command = data.get('command', '')
+
+ response = {
+ 'success': True,
+ 'message': '',
+ 'command': command,
+ 'timestamp': datetime.now().strftime("%H:%M:%S")
+ }
+
+ try:
+ current_mode = config.DRONE_STATUS['mode']
+
+ if command == 'takeoff':
+ if current_mode == 'LANDED':
+ config.DRONE_STATUS['mode'] = 'TAKEOFF'
+ response['message'] = '无人机正在起飞...'
+ else:
+ response['success'] = False
+ response['message'] = f'无法起飞,当前状态: {current_mode}'
+
+ elif command == 'land':
+ if current_mode in ['FLYING', 'TAKEOFF']:
+ config.DRONE_STATUS['mode'] = 'LANDING'
+ response['message'] = '无人机正在降落...'
+ else:
+ response['success'] = False
+ response['message'] = f'无法降落,当前状态: {current_mode}'
+
+ elif command == 'emergency_land':
+ config.DRONE_STATUS['mode'] = 'LANDING'
+ response['message'] = '紧急降落已启动'
+
+ elif command == 'hover':
+ response['message'] = '悬停模式已激活'
+
+ elif command == 'charge':
+ config.DRONE_STATUS['battery'] = 100
+ response['message'] = '电池已充满'
+
+ elif command == 'auto_pilot':
+ response['message'] = '自动驾驶模式已激活'
+
+ elif command == 'return_home':
+ config.DRONE_STATUS['location'] = {'x': 0, 'y': 0, 'z': config.DRONE_STATUS['altitude']}
+ response['message'] = '正在返回起始点'
+
+ else:
+ response['success'] = False
+ response['message'] = f'未知命令: {command}'
+ except Exception as e:
+ response['success'] = False
+ response['message'] = f'控制错误: {str(e)}'
+
+ return jsonify(response)
+
+
+@app.route('/simulate_scene', methods=['POST'])
+def simulate_scene():
+ """手动模拟特定场景"""
+ data = request.json
+ scene = data.get('scene', '')
+
+ if scene in config.CLASS_NAMES:
+ virtual_camera.current_scene = scene
+ virtual_camera.scene_transition = 15
+
+ response = {
+ 'success': True,
+ 'message': f'已切换到 {scene} 场景',
+ 'scene': scene
+ }
+ else:
+ response = {
+ 'success': False,
+ 'message': f'未知场景: {scene}',
+ 'available_scenes': config.CLASS_NAMES
+ }
+
+ return jsonify(response)
+
+
+@app.route('/capture_image')
+def capture_image():
+ """捕获当前帧图像"""
+ frame, _ = virtual_camera.get_frame()
+
+ # 编码为PNG
+ ret, buffer = cv2.imencode('.png', frame)
+
+ # 创建内存文件
+ img_io = io.BytesIO(buffer.tobytes())
+ img_io.seek(0)
+
+ return send_file(img_io, mimetype='image/png',
+ as_attachment=True,
+ download_name=f'drone_capture_{datetime.now().strftime("%Y%m%d_%H%M%S")}.png')
+
+
+# ==================== 主函数 ====================
if __name__ == '__main__':
- app.run(debug=True, host='0.0.0.0', port=5000)
\ No newline at end of file
+ print("=" * 70)
+ print("🚁 无人机导航系统 - 完整演示版")
+ print("=" * 70)
+ print("🎯 无需真实数据,立即展示效果")
+ print("📊 检测场景:", ", ".join(config.CLASS_NAMES))
+ print("🌐 访问地址: http://localhost:5000")
+ print("=" * 70)
+ print("🎮 控制功能:")
+ print(" - 起飞/降落/紧急降落")
+ print(" - 自动驾驶/返航")
+ print(" - 手动切换场景")
+ print(" - 实时视频流")
+ print("=" * 70)
+
+ # 检查模板是否存在
+ if not os.path.exists("templates/index.html"):
+ print("⚠️ 未找到模板文件,正在创建...")
+ create_default_template()
+
+ # 检查静态目录
+ os.makedirs("static/css", exist_ok=True)
+ os.makedirs("static/js", exist_ok=True)
+
+ # 创建静态文件
+ create_static_files()
+
+ # 运行Flask应用
+ app.run(debug=True, host='0.0.0.0', port=5000, threaded=True)
+
+
+# ==================== 辅助函数 ====================
+def create_default_template():
+ """创建默认HTML模板"""
+ template_dir = "templates"
+ os.makedirs(template_dir, exist_ok=True)
+
+ html_content = """
+
+
+
+
+ 🚁 无人机视觉导航演示系统
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 }})
+
+
+
实时检测结果
+
+ {{ drone_status.detected_class }}
+ {{ "%.1f"|format(drone_status.confidence * 100) }}%
+
+
+
+
+
+
+
+
场景识别面板
+
+
检测置信度
+
+
+ {{ "%.1f"|format(drone_status.confidence * 100) }}%
+
+
+
+
+
手动场景切换
+
+ {% for scene in class_names %}
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
电池电量
+
{{ drone_status.battery|round(1) }}%
+
+
+
+
+
+
+
+
飞行高度
+
{{ drone_status.altitude|round(1) }} m
+
+
+
+
+
+
+
飞行速度
+
{{ drone_status.speed|round(1) }} m/s
+
+
+
+
+
+
+
位置坐标
+
+ ({{ drone_status.location.x|round(1) }}, {{ drone_status.location.y|round(1) }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
环境监测
+
+
+
+ 温度: {{ drone_status.temperature|round(1) }}°C
+
+
+
+ 风速: {{ drone_status.wind_speed|round(1) }} m/s
+
+
+
+ GPS: {{ drone_status.gps_signal }}
+
+
+
+ 紧急等级: 0/10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+ template_path = os.path.join(template_dir, "index.html")
+ with open(template_path, "w", encoding="utf-8") as f:
+ f.write(html_content)
+
+ print(f"✅ 已创建模板文件: {template_path}")
+
+
+def create_static_files():
+ """创建静态CSS和JS文件"""
+ # 创建CSS文件
+ css_content = """/* 无人机导航系统样式 */
+:root {
+ --primary-color: #00b4d8;
+ --secondary-color: #0077b6;
+ --success-color: #00b894;
+ --warning-color: #fdcb6e;
+ --danger-color: #e17055;
+ --dark-color: #2d3436;
+ --light-color: #f5f5f5;
+ --gray-color: #636e72;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background: linear-gradient(135deg, #0a1929 0%, #1a365d 50%, #2d3748 100%);
+ color: white;
+ min-height: 100vh;
+ overflow-x: hidden;
+}
+
+.container {
+ max-width: 1800px;
+ margin: 0 auto;
+ padding: 20px;
+}
+
+/* 头部样式 */
+.header {
+ background: rgba(10, 25, 47, 0.9);
+ border-radius: 20px;
+ padding: 25px;
+ margin-bottom: 25px;
+ border: 2px solid rgba(0, 180, 216, 0.3);
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(10px);
+}
+
+.header-content {
+ text-align: center;
+}
+
+.header h1 {
+ font-size: 2.8rem;
+ margin-bottom: 10px;
+ background: linear-gradient(90deg, #00b4d8, #0077b6);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ text-shadow: 0 2px 10px rgba(0, 180, 216, 0.3);
+}
+
+.subtitle {
+ color: #88ffdd;
+ font-size: 1.2rem;
+ margin-bottom: 15px;
+}
+
+.demo-badge {
+ display: inline-block;
+ background: linear-gradient(90deg, #00b894, #00cec9);
+ color: white;
+ padding: 8px 20px;
+ border-radius: 25px;
+ font-weight: bold;
+ box-shadow: 0 4px 15px rgba(0, 184, 148, 0.4);
+}
+
+/* 主内容区 */
+.main-content {
+ display: grid;
+ grid-template-columns: 2fr 1fr;
+ gap: 25px;
+ margin-bottom: 25px;
+}
+
+@media (max-width: 1200px) {
+ .main-content {
+ grid-template-columns: 1fr;
+ }
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ padding-bottom: 15px;
+ border-bottom: 2px solid var(--primary-color);
+}
+
+.section-header h2 {
+ font-size: 1.8rem;
+ color: var(--primary-color);
+}
+
+.fps-indicator, .mode-indicator {
+ background: rgba(0, 0, 0, 0.3);
+ padding: 8px 15px;
+ border-radius: 15px;
+ font-weight: bold;
+ border: 1px solid var(--primary-color);
+}
+
+/* 视频区域 */
+.video-section {
+ background: rgba(10, 25, 47, 0.8);
+ border-radius: 20px;
+ padding: 25px;
+ border: 2px solid rgba(0, 180, 216, 0.2);
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
+}
+
+.video-container {
+ position: relative;
+ width: 100%;
+ border-radius: 15px;
+ overflow: hidden;
+ background: black;
+ margin-bottom: 20px;
+ border: 3px solid rgba(255, 255, 255, 0.1);
+}
+
+#video-feed {
+ width: 100%;
+ display: block;
+ transition: transform 0.3s;
+}
+
+#video-feed:hover {
+ transform: scale(1.01);
+}
+
+.video-overlay {
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ right: 20px;
+ background: rgba(0, 0, 0, 0.7);
+ padding: 15px;
+ border-radius: 10px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ backdrop-filter: blur(5px);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.detection-info {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+}
+
+.detection-title {
+ font-size: 0.9rem;
+ color: #aaa;
+}
+
+.detection-result {
+ display: flex;
+ gap: 20px;
+ font-size: 1.2rem;
+ font-weight: bold;
+}
+
+#live-class {
+ color: #00ff88;
+}
+
+#live-confidence {
+ color: #ffcc00;
+}
+
+.btn-capture {
+ background: linear-gradient(90deg, #6c5ce7, #a29bfe);
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: bold;
+ transition: all 0.3s;
+}
+
+.btn-capture:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 5px 15px rgba(108, 92, 231, 0.4);
+}
+
+/* 检测面板 */
+.detection-panel {
+ background: rgba(20, 40, 80, 0.6);
+ padding: 20px;
+ border-radius: 15px;
+ border: 1px solid rgba(0, 150, 255, 0.3);
+}
+
+.confidence-meter {
+ margin: 20px 0;
+}
+
+.meter-label {
+ margin-bottom: 8px;
+ color: #88ffcc;
+}
+
+.meter-bar {
+ height: 20px;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 10px;
+ overflow: hidden;
+ position: relative;
+}
+
+.meter-fill {
+ height: 100%;
+ background: linear-gradient(90deg, #ff0000, #ff9900, #00ff00);
+ border-radius: 10px;
+ transition: width 0.5s;
+}
+
+.meter-value {
+ text-align: center;
+ margin-top: 5px;
+ font-weight: bold;
+ font-size: 1.2rem;
+}
+
+.scene-controls {
+ margin-top: 25px;
+}
+
+.scene-controls h4 {
+ margin-bottom: 15px;
+ color: #00ccff;
+}
+
+.scene-buttons {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 10px;
+}
+
+.scene-btn {
+ padding: 12px;
+ background: rgba(255, 255, 255, 0.1);
+ border: 2px solid;
+ border-radius: 8px;
+ color: white;
+ cursor: pointer;
+ transition: all 0.3s;
+ font-size: 0.9rem;
+}
+
+.scene-btn:hover {
+ background: rgba(255, 255, 255, 0.2);
+ transform: translateY(-3px);
+}
+
+/* 控制区域 */
+.control-section {
+ background: rgba(10, 25, 47, 0.8);
+ border-radius: 20px;
+ padding: 25px;
+ border: 2px solid rgba(0, 180, 216, 0.2);
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
+}
+
+.status-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 15px;
+ margin-bottom: 25px;
+}
+
+.status-card {
+ background: rgba(20, 40, 80, 0.6);
+ padding: 15px;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ border-left: 4px solid var(--primary-color);
+ transition: transform 0.3s;
+}
+
+.status-card:hover {
+ transform: translateY(-5px);
+ background: rgba(20, 40, 80, 0.8);
+}
+
+.status-icon {
+ font-size: 2rem;
+ color: var(--primary-color);
+}
+
+.status-content {
+ flex: 1;
+}
+
+.status-label {
+ font-size: 0.9rem;
+ color: #88ffcc;
+ margin-bottom: 5px;
+}
+
+.status-value {
+ font-size: 1.4rem;
+ font-weight: bold;
+ margin-bottom: 8px;
+}
+
+.status-bar {
+ height: 8px;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.bar-fill {
+ height: 100%;
+ background: linear-gradient(90deg, #ff0000, #ff9900, #00ff00);
+ border-radius: 4px;
+ transition: width 0.5s;
+}
+
+/* 控制面板 */
+.control-panel {
+ margin: 25px 0;
+}
+
+.control-row {
+ display: flex;
+ gap: 15px;
+ margin-bottom: 15px;
+}
+
+.control-btn {
+ flex: 1;
+ padding: 20px 10px;
+ border: none;
+ border-radius: 12px;
+ color: white;
+ cursor: pointer;
+ transition: all 0.3s;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+ font-size: 1rem;
+ font-weight: bold;
+}
+
+.control-btn i {
+ font-size: 1.8rem;
+}
+
+.btn-takeoff {
+ background: linear-gradient(145deg, #00b09b, #96c93d);
+}
+
+.btn-land {
+ background: linear-gradient(145deg, #2193b0, #6dd5ed);
+}
+
+.btn-emergency {
+ background: linear-gradient(145deg, #ff416c, #ff4b2b);
+}
+
+.btn-direction {
+ background: linear-gradient(145deg, #2a5298, #1e3c72);
+}
+
+.btn-action {
+ background: linear-gradient(145deg, #8a2387, #f27121);
+}
+
+.control-btn:hover {
+ transform: translateY(-5px) scale(1.05);
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
+}
+
+.control-btn:active {
+ transform: translateY(-2px);
+}
+
+/* 环境面板 */
+.environment-panel {
+ background: rgba(20, 40, 80, 0.6);
+ padding: 20px;
+ border-radius: 15px;
+ border: 1px solid rgba(0, 150, 255, 0.3);
+}
+
+.env-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 15px;
+ margin-top: 15px;
+}
+
+.env-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 12px;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 8px;
+}
+
+.env-item i {
+ color: var(--primary-color);
+ font-size: 1.2rem;
+}
+
+/* 底部区域 */
+.footer-section {
+ display: grid;
+ grid-template-columns: 2fr 1fr;
+ gap: 25px;
+ margin-bottom: 25px;
+}
+
+@media (max-width: 1200px) {
+ .footer-section {
+ grid-template-columns: 1fr;
+ }
+}
+
+.logs-panel, .system-info {
+ background: rgba(10, 25, 47, 0.8);
+ border-radius: 20px;
+ padding: 25px;
+ border: 2px solid rgba(0, 180, 216, 0.2);
+}
+
+.logs-container {
+ height: 200px;
+ overflow-y: auto;
+ margin-top: 15px;
+ background: rgba(0, 0, 0, 0.3);
+ border-radius: 10px;
+ padding: 15px;
+}
+
+.log-entry {
+ padding: 8px 12px;
+ margin-bottom: 8px;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 6px;
+ border-left: 3px solid var(--primary-color);
+}
+
+.log-time {
+ color: #ffcc00;
+ font-weight: bold;
+ margin-right: 15px;
+}
+
+.log-message {
+ color: white;
+}
+
+.info-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 15px;
+ margin: 20px 0;
+}
+
+.info-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 12px;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 8px;
+}
+
+.info-item i {
+ color: var(--primary-color);
+}
+
+.system-controls {
+ display: flex;
+ gap: 10px;
+ margin-top: 20px;
+}
+
+.sys-btn {
+ flex: 1;
+ padding: 12px;
+ background: rgba(0, 150, 255, 0.3);
+ border: 1px solid rgba(0, 150, 255, 0.5);
+ border-radius: 8px;
+ color: white;
+ cursor: pointer;
+ transition: all 0.3s;
+}
+
+.sys-btn:hover {
+ background: rgba(0, 150, 255, 0.5);
+}
+
+/* 页脚 */
+.page-footer {
+ text-align: center;
+ padding: 25px;
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
+ color: #88aaff;
+ background: rgba(0, 20, 40, 0.5);
+ border-radius: 15px;
+}
+
+/* 通知样式 */
+#notification-container {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ z-index: 1000;
+}
+
+.notification {
+ padding: 15px 25px;
+ margin-bottom: 10px;
+ border-radius: 10px;
+ color: white;
+ font-weight: bold;
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+ animation: slideIn 0.3s ease-out;
+ max-width: 300px;
+}
+
+.notification.success {
+ background: linear-gradient(90deg, #00b09b, #96c93d);
+}
+
+.notification.error {
+ background: linear-gradient(90deg, #ff416c, #ff4b2b);
+}
+
+.notification.info {
+ background: linear-gradient(90deg, #2193b0, #6dd5ed);
+}
+
+@keyframes slideIn {
+ from { transform: translateX(100%); opacity: 0; }
+ to { transform: translateX(0); opacity: 1; }
+}
+
+@keyframes slideOut {
+ from { transform: translateX(0); opacity: 1; }
+ to { transform: translateX(100%); opacity: 0; }
+}
+
+/* 滚动条样式 */
+::-webkit-scrollbar {
+ width: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--primary-color);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--secondary-color);
+}
+"""
+
+ css_path = "static/css/style.css"
+ with open(css_path, "w", encoding="utf-8") as f:
+ f.write(css_content)
+
+ # 创建JS文件
+ js_content = """// 无人机导航系统交互脚本
+document.addEventListener('DOMContentLoaded', function() {
+ // 全局变量
+ let updateInterval;
+ let logsInterval;
+
+ // 元素引用
+ const videoFeed = document.getElementById('video-feed');
+ const liveClass = document.getElementById('live-class');
+ const liveConfidence = document.getElementById('live-confidence');
+ const confidenceFill = document.getElementById('confidence-fill');
+ const confidenceValue = document.getElementById('confidence-value');
+ const modeIndicator = document.getElementById('mode-indicator');
+ const batteryValue = document.getElementById('battery-value');
+ const batteryFill = document.getElementById('battery-fill');
+ const altitudeValue = document.getElementById('altitude-value');
+ const speedValue = document.getElementById('speed-value');
+ const positionValue = document.getElementById('position-value');
+ const tempValue = document.getElementById('temp-value');
+ const windValue = document.getElementById('wind-value');
+ const gpsValue = document.getElementById('gps-value');
+ const emergencyValue = document.getElementById('emergency-value');
+ const systemTime = document.getElementById('system-time');
+ const logsContainer = document.getElementById('logs-container');
+ const lastUpdate = document.getElementById('last-update');
+
+ // 初始化函数
+ function init() {
+ console.log('🚀 无人机导航系统初始化...');
+
+ // 开始更新循环
+ startUpdateLoop();
+
+ // 绑定事件
+ bindEvents();
+
+ // 初始加载
+ updateDroneStatus();
+ updateFlightLog();
+ updateSystemInfo();
+
+ // 显示欢迎通知
+ showNotification('欢迎使用无人机导航演示系统!', 'info');
+ }
+
+ // 开始更新循环
+ function startUpdateLoop() {
+ // 更新无人机状态(每秒)
+ updateInterval = setInterval(updateDroneStatus, 1000);
+
+ // 更新飞行日志(每2秒)
+ logsInterval = setInterval(updateFlightLog, 2000);
+
+ // 更新系统时间(每秒)
+ setInterval(updateSystemTime, 1000);
+ }
+
+ // 绑定所有事件
+ function bindEvents() {
+ // 控制按钮
+ document.getElementById('takeoff-btn').addEventListener('click', () => sendCommand('takeoff'));
+ document.getElementById('land-btn').addEventListener('click', () => sendCommand('land'));
+ document.getElementById('emergency-btn').addEventListener('click', () => sendCommand('emergency_land'));
+ document.getElementById('hover-btn').addEventListener('click', () => sendCommand('hover'));
+ document.getElementById('charge-btn').addEventListener('click', () => sendCommand('charge'));
+ document.getElementById('auto-btn').addEventListener('click', () => sendCommand('auto_pilot'));
+ document.getElementById('home-btn').addEventListener('click', () => sendCommand('return_home'));
+
+ // 方向控制按钮
+ document.getElementById('forward-btn').addEventListener('click', () => showNotification('向前飞行', 'info'));
+ document.getElementById('backward-btn').addEventListener('click', () => showNotification('向后飞行', 'info'));
+ document.getElementById('left-btn').addEventListener('click', () => showNotification('向左转', 'info'));
+ document.getElementById('right-btn').addEventListener('click', () => showNotification('向右转', 'info'));
+
+ // 场景切换按钮
+ document.querySelectorAll('.scene-btn').forEach(btn => {
+ btn.addEventListener('click', function() {
+ const scene = this.getAttribute('data-scene');
+ simulateScene(scene);
+ });
+ });
+
+ // 截图按钮
+ document.getElementById('capture-btn').addEventListener('click', captureImage);
+
+ // 系统按钮
+ document.getElementById('refresh-btn').addEventListener('click', refreshAll);
+ document.getElementById('help-btn').addEventListener('click', showHelp);
+ document.getElementById('fullscreen-btn').addEventListener('click', toggleFullscreen);
+
+ // 视频点击全屏
+ videoFeed.addEventListener('click', function() {
+ if (this.requestFullscreen) {
+ this.requestFullscreen();
+ }
+ });
+ }
+
+ // 更新无人机状态
+ async function updateDroneStatus() {
+ try {
+ const response = await fetch('/drone_status');
+ const data = await response.json();
+
+ // 更新状态显示
+ liveClass.textContent = data.detected_class;
+ liveConfidence.textContent = (data.confidence * 100).toFixed(1) + '%';
+
+ confidenceFill.style.width = (data.confidence * 100) + '%';
+ confidenceValue.textContent = (data.confidence * 100).toFixed(1) + '%';
+
+ modeIndicator.textContent = data.mode;
+ batteryValue.textContent = data.battery.toFixed(1) + '%';
+ batteryFill.style.width = data.battery + '%';
+
+ altitudeValue.textContent = data.altitude.toFixed(1) + ' m';
+ speedValue.textContent = data.speed.toFixed(1) + ' m/s';
+ positionValue.textContent = `(${data.location.x.toFixed(1)}, ${data.location.y.toFixed(1)})`;
+
+ tempValue.textContent = data.temperature.toFixed(1) + '°C';
+ windValue.textContent = data.wind_speed.toFixed(1) + ' m/s';
+ gpsValue.textContent = data.gps_signal;
+
+ // 更新最后更新时间
+ if (data.timestamp) {
+ lastUpdate.textContent = data.timestamp;
+ }
+
+ } catch (error) {
+ console.error('更新状态失败:', error);
+ }
+ }
+
+ // 更新飞行日志
+ async function updateFlightLog() {
+ try {
+ const response = await fetch('/flight_log');
+ const logs = await response.json();
+
+ // 清空当前日志
+ logsContainer.innerHTML = '';
+
+ // 添加日志条目(最多显示10条)
+ const displayLogs = logs.slice(-10);
+
+ displayLogs.forEach(log => {
+ const logEntry = document.createElement('div');
+ logEntry.className = 'log-entry';
+
+ const timeSpan = document.createElement('span');
+ timeSpan.className = 'log-time';
+ timeSpan.textContent = log.timestamp;
+
+ const messageSpan = document.createElement('span');
+ messageSpan.className = 'log-message';
+ messageSpan.textContent = `${log.scene} → ${log.action}: ${log.message}`;
+
+ // 根据动作类型添加颜色
+ if (log.action.includes('紧急')) {
+ messageSpan.style.color = '#ff5555';
+ } else if (log.action.includes('正常')) {
+ messageSpan.style.color = '#55ff55';
+ }
+
+ logEntry.appendChild(timeSpan);
+ logEntry.appendChild(messageSpan);
+ logsContainer.appendChild(logEntry);
+ });
+
+ // 滚动到底部
+ logsContainer.scrollTop = logsContainer.scrollHeight;
+
+ } catch (error) {
+ console.error('更新日志失败:', error);
+ }
+ }
+
+ // 更新系统信息
+ async function updateSystemInfo() {
+ try {
+ const response = await fetch('/system_info');
+ const info = await response.json();
+
+ document.getElementById('system-mode').textContent = info.demo_mode ? '演示模式' : '实战模式';
+ document.getElementById('detection-accuracy').textContent = info.detection_accuracy;
+ document.getElementById('connection-status').textContent = '已连接';
+
+ // 更新紧急等级
+ emergencyValue.textContent = info.emergency_level;
+ if (info.emergency_level > 5) {
+ emergencyValue.style.color = '#ff5555';
+ } else {
+ emergencyValue.style.color = '#55ff55';
+ }
+
+ } catch (error) {
+ console.error('更新系统信息失败:', error);
+ }
+ }
+
+ // 更新系统时间
+ function updateSystemTime() {
+ const now = new Date();
+ const timeStr = now.toLocaleTimeString('zh-CN');
+ systemTime.textContent = timeStr;
+ }
+
+ // 发送控制命令
+ async function sendCommand(command) {
+ try {
+ const response = await fetch('/control', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ command: command })
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ showNotification(data.message, 'success');
+ console.log('命令成功:', data.message);
+ } else {
+ showNotification(data.message, 'error');
+ console.error('命令失败:', data.message);
+ }
+
+ } catch (error) {
+ showNotification('网络连接错误', 'error');
+ console.error('请求失败:', error);
+ }
+ }
+
+ // 模拟场景切换
+ async function simulateScene(scene) {
+ try {
+ const response = await fetch('/simulate_scene', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ scene: scene })
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ showNotification(`已切换到 ${scene} 场景`, 'info');
+ } else {
+ showNotification(data.message, 'error');
+ }
+
+ } catch (error) {
+ console.error('场景切换失败:', error);
+ }
+ }
+
+ // 捕获图像
+ async function captureImage() {
+ try {
+ const response = await fetch('/capture_image');
+ const blob = await response.blob();
+
+ // 创建下载链接
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `drone_capture_${new Date().getTime()}.png`;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ window.URL.revokeObjectURL(url);
+
+ showNotification('图像已保存', 'success');
+
+ } catch (error) {
+ showNotification('截图失败', 'error');
+ console.error('截图失败:', error);
+ }
+ }
+
+ // 刷新所有数据
+ function refreshAll() {
+ updateDroneStatus();
+ updateFlightLog();
+ updateSystemInfo();
+ showNotification('系统状态已刷新', 'info');
+ }
+
+ // 显示帮助信息
+ function showHelp() {
+ const helpMessage = `
+无人机导航系统使用说明:
+
+🎮 控制功能:
+• 起飞/降落 - 控制无人机起降
+• 紧急降落 - 立即安全降落
+• 自动驾驶 - 启用自动飞行模式
+• 返航 - 返回起始位置
+
+🌍 场景模拟:
+• 点击场景按钮可手动切换环境
+• 系统会自动模拟环境变化
+• 每个环境都有独特的视觉特征
+
+📊 状态监控:
+• 实时显示无人机状态
+• 环境检测结果
+• 飞行日志记录
+
+💡 提示:
+• 点击视频可全屏显示
+• 使用截图功能保存当前画面
+• 系统使用虚拟数据演示
+ `;
+
+ alert(helpMessage);
+ }
+
+ // 切换全屏
+ function toggleFullscreen() {
+ const elem = document.documentElement;
+
+ if (!document.fullscreenElement) {
+ if (elem.requestFullscreen) {
+ elem.requestFullscreen();
+ }
+ } else {
+ if (document.exitFullscreen) {
+ document.exitFullscreen();
+ }
+ }
+ }
+
+ // 显示通知
+ function showNotification(message, type = 'info') {
+ const container = document.getElementById('notification-container');
+
+ const notification = document.createElement('div');
+ notification.className = `notification ${type}`;
+ notification.textContent = message;
+
+ container.appendChild(notification);
+
+ // 3秒后移除
+ setTimeout(() => {
+ notification.style.animation = 'slideOut 0.3s ease-out';
+ setTimeout(() => {
+ if (notification.parentNode) {
+ notification.parentNode.removeChild(notification);
+ }
+ }, 300);
+ }, 3000);
+ }
+
+ // 键盘快捷键
+ document.addEventListener('keydown', function(event) {
+ switch(event.key) {
+ case ' ':
+ // 空格键 - 起飞/降落切换
+ const mode = modeIndicator.textContent;
+ if (mode === 'LANDED') {
+ sendCommand('takeoff');
+ } else if (mode === 'FLYING') {
+ sendCommand('land');
+ }
+ event.preventDefault();
+ break;
+
+ case 'Escape':
+ // ESC键 - 紧急降落
+ sendCommand('emergency_land');
+ break;
+
+ case 'h':
+ // H键 - 返航
+ sendCommand('return_home');
+ break;
+
+ case 'c':
+ // C键 - 截图
+ captureImage();
+ break;
+ }
+ });
+
+ // 页面卸载时清理
+ window.addEventListener('beforeunload', function() {
+ if (updateInterval) clearInterval(updateInterval);
+ if (logsInterval) clearInterval(logsInterval);
+ });
+
+ // 初始化应用
+ init();
+});
+"""
+
+ js_path = "static/js/main.js"
+ with open(js_path, "w", encoding="utf-8") as f:
+ f.write(js_content)
+
+ print(f"✅ 已创建静态文件: {css_path}, {js_path}")
\ No newline at end of file
From 64752edae9a37b68638200d9383114b1a24bd84d Mon Sep 17 00:00:00 2001
From: 725921 <3407246504@qq.com>
Date: Mon, 22 Dec 2025 10:21:11 +0800
Subject: [PATCH 02/11] =?UTF-8?q?=E6=97=A0=E4=BA=BA=E6=9C=BA=E5=AF=BC?=
=?UTF-8?q?=E8=88=AA=E7=B3=BB=E7=BB=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
完善了readme
---
src/UAV_navigation_system/README.md | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/UAV_navigation_system/README.md b/src/UAV_navigation_system/README.md
index 1a062b6c3f..476a6cfaf3 100644
--- a/src/UAV_navigation_system/README.md
+++ b/src/UAV_navigation_system/README.md
@@ -1,9 +1,15 @@
## 自主无人机导航系统
### 项目简介
-使用深度学习对周围环境进行分类,并根据视觉输入做出导航决策
+该项目实现了一种利用深度学习的实时无人机视觉系统,用于对周围环境进行分类,并基于视觉输入做出导航决策。无人机能检测森林、火灾、城市、动物、车辆和水域等场景,并执行特定的导航响应。
### 运行步骤
在Android 手机上安装 DroidCam 并将其连接到与计算机相同的 Wi-Fi。
打开应用程序并记下显示的 Wi-Fi IP(例如 )。192.168.147.233
在代码(函数)中,更新 :main()VIDEO_SOURCE
+
+### 安装说明:
+在你的安卓手机上安装DroidCam,并连接到与电脑相同的Wi-Fi。
+打开应用,记录显示的Wi-Fi IP(例如,)。192.168.147.233
+在代码(函数)中,更新:main()VIDEO_SOURCE
+VIDEO_SOURCE = "http://192.168.147.233:4747/video"
From a20a3d7ca0f3013f553d294ab447467d3c8c69b1 Mon Sep 17 00:00:00 2001
From: 725921 <3407246504@qq.com>
Date: Wed, 24 Dec 2025 16:10:00 +0800
Subject: [PATCH 03/11] =?UTF-8?q?=E6=97=A0=E4=BA=BA=E6=9C=BA=E5=AF=BC?=
=?UTF-8?q?=E8=88=AA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
代码添加了注释
---
src/UAV_navigation_system/pytorch_model.py | 267 ++++++++++++++-------
1 file changed, 186 insertions(+), 81 deletions(-)
diff --git a/src/UAV_navigation_system/pytorch_model.py b/src/UAV_navigation_system/pytorch_model.py
index dc52bf40fa..315993751c 100644
--- a/src/UAV_navigation_system/pytorch_model.py
+++ b/src/UAV_navigation_system/pytorch_model.py
@@ -1,75 +1,104 @@
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
"""
PyTorch模型工具类
-用于加载和运行PyTorch模型
+用于加载和运行无人机场景分类的PyTorch模型
+核心功能:
+1. 支持自定义CNN/ResNet18/MobileNetV2三种模型架构加载
+2. 实现图像预处理、单张/批量图像预测
+3. 适配CPU/GPU设备,自动检测硬件环境
"""
-import torch
-import torch.nn as nn
-import torchvision.transforms as transforms
-from PIL import Image
-import numpy as np
-import cv2
-import os
+# 导入核心依赖库
+import torch # PyTorch核心库,用于模型构建和推理
+import torch.nn as nn # 神经网络层模块
+import torchvision.transforms as transforms # 图像预处理工具
+from PIL import Image # PIL库,用于图像读取和格式转换
+import numpy as np # 数值计算库,处理图像数组
+import cv2 # OpenCV库,处理视频/图像数据
+import os # 文件路径操作库
class PyTorchDroneModel:
- """PyTorch无人机视觉模型类"""
+ """
+ PyTorch无人机视觉场景分类模型类
+ 封装模型加载、图像预处理、场景分类预测等核心功能
+ 支持的场景类别:Forest(森林)、Fire(火灾)、City(城市)、Animal(动物)、Vehicle(车辆)、Water(水域)
+ """
def __init__(self, model_path=None, device=None):
+ """
+ 初始化模型类
+
+ Args:
+ model_path (str, optional): 预训练模型权重文件路径,默认None
+ device (torch.device, optional): 模型运行设备(cpu/cuda),默认自动检测
+ """
+ # 模型实例初始化
self.model = None
+ # 运行设备初始化
self.device = None
+ # 场景分类类别名称(与训练时标签对应)
self.class_names = ['Forest', 'Fire', 'City', 'Animal', 'Vehicle', 'Water']
+ # 模型输入图像尺寸(需与训练时一致)
self.img_size = (224, 224)
- # 设置设备
+ # 自动检测/指定运行设备
if device is None:
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
else:
self.device = device
-
print(f"✅ 使用设备: {self.device}")
- # 图像预处理变换
+ # 定义图像预处理流水线(与训练时预处理逻辑一致)
self.transform = transforms.Compose([
- transforms.Resize(self.img_size),
- transforms.ToTensor(),
+ transforms.Resize(self.img_size), # 调整图像尺寸
+ transforms.ToTensor(), # 转换为Tensor(0-1归一化)
+ # 标准化(使用ImageNet均值/标准差,适配预训练模型)
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
- # 如果提供了模型路径,自动加载
+ # 如果传入有效模型路径,自动加载模型
if model_path and os.path.exists(model_path):
self.load_model(model_path)
def define_model_architecture(self):
- """定义PyTorch模型架构(需要与训练时一致)"""
+ """
+ 定义自定义CNN模型架构(需与训练时的模型结构完全一致)
+ 适用于无人机场景分类的轻量级卷积神经网络
+
+ Returns:
+ nn.Module: 自定义CNN模型实例
+ """
class DroneCNN(nn.Module):
+ """内部自定义CNN模型类"""
def __init__(self, num_classes=6):
super(DroneCNN, self).__init__()
+ # 特征提取层(4层卷积+批归一化+激活+池化+dropout)
self.features = nn.Sequential(
- # 第一层卷积
+ # 第一层卷积:3通道输入→32通道输出,3×3卷积核,填充1
nn.Conv2d(3, 32, kernel_size=3, padding=1),
- nn.BatchNorm2d(32),
- nn.ReLU(inplace=True),
- nn.MaxPool2d(kernel_size=2, stride=2),
- nn.Dropout(0.25),
+ nn.BatchNorm2d(32), # 批归一化,加速训练,防止过拟合
+ nn.ReLU(inplace=True), # ReLU激活函数,inplace=True节省内存
+ nn.MaxPool2d(kernel_size=2, stride=2), # 2×2最大池化,步长2
+ nn.Dropout(0.25), # Dropout层,随机丢弃25%神经元,防止过拟合
- # 第二层卷积
+ # 第二层卷积:32通道→64通道
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Dropout(0.25),
- # 第三层卷积
+ # 第三层卷积:64通道→128通道
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Dropout(0.25),
- # 第四层卷积
+ # 第四层卷积:128通道→256通道
nn.Conv2d(128, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
@@ -77,127 +106,179 @@ def __init__(self, num_classes=6):
nn.Dropout(0.25),
)
+ # 分类器层(全连接层)
self.classifier = nn.Sequential(
- nn.Flatten(),
- nn.Linear(256 * 14 * 14, 512), # 224/2/2/2/2 = 14
+ nn.Flatten(), # 展平特征图:256×14×14 → 256×14×14
+ # 全连接层1:特征展平后→512维隐藏层(224/2^4=14,4次池化后尺寸)
+ nn.Linear(256 * 14 * 14, 512),
nn.ReLU(inplace=True),
- nn.Dropout(0.5),
- nn.Linear(512, num_classes)
+ nn.Dropout(0.5), # 丢弃50%神经元,防止过拟合
+ nn.Linear(512, num_classes) # 输出层:512→分类类别数
)
def forward(self, x):
- x = self.features(x)
- x = self.classifier(x)
+ """前向传播逻辑"""
+ x = self.features(x) # 特征提取
+ x = self.classifier(x) # 分类预测
return x
+ # 返回自定义模型实例(分类类别数与场景类别数一致)
return DroneCNN(num_classes=len(self.class_names))
def load_resnet18_model(self):
- """加载预训练的ResNet18模型"""
+ """
+ 加载预训练的ResNet18模型并适配自定义分类任务
+ 修改最后一层全连接层,适配无人机6类场景分类
+
+ Returns:
+ nn.Module: 适配后的ResNet18模型实例
+ """
from torchvision import models
+ # 加载ResNet18骨架(不加载ImageNet预训练权重,避免与自定义任务冲突)
model = models.resnet18(pretrained=False)
+ # 获取最后一层全连接层的输入特征数
num_features = model.fc.in_features
+ # 替换最后一层全连接层:原1000类→自定义6类
model.fc = nn.Linear(num_features, len(self.class_names))
return model
def load_mobilenetv2_model(self):
- """加载预训练的MobileNetV2模型"""
+ """
+ 加载预训练的MobileNetV2模型并适配自定义分类任务
+ 轻量级模型,适配无人机嵌入式设备
+
+ Returns:
+ nn.Module: 适配后的MobileNetV2模型实例
+ """
from torchvision import models
+ # 加载MobileNetV2骨架(不加载预训练权重)
model = models.mobilenet_v2(pretrained=False)
+ # 替换分类器最后一层:原1000类→自定义6类
model.classifier[1] = nn.Linear(model.classifier[1].in_features, len(self.class_names))
return model
def load_model(self, model_path, model_type='custom'):
- """加载PyTorch模型"""
+ """
+ 加载预训练PyTorch模型权重
+
+ Args:
+ model_path (str): 模型权重文件路径(.pth/.pt)
+ model_type (str): 模型架构类型,可选['custom', 'resnet18', 'mobilenet']
+
+ Returns:
+ bool: 加载成功返回True,失败返回False
+ """
print(f"🔄 正在加载PyTorch模型: {model_path}")
try:
- # 根据类型创建模型架构
+ # 根据模型类型创建对应架构的模型实例
if model_type == 'resnet18':
self.model = self.load_resnet18_model()
elif model_type == 'mobilenet':
self.model = self.load_mobilenetv2_model()
- else:
+ else: # 默认加载自定义CNN
self.model = self.define_model_architecture()
- # 加载模型权重
+ # 加载模型权重文件(兼容多种保存格式)
checkpoint = torch.load(model_path, map_location=self.device)
if isinstance(checkpoint, dict):
- # 如果保存的是检查点字典
+ # 情况1:保存的是检查点字典(包含state_dict/优化器参数等)
if 'model_state_dict' in checkpoint:
self.model.load_state_dict(checkpoint['model_state_dict'])
elif 'state_dict' in checkpoint:
self.model.load_state_dict(checkpoint['state_dict'])
else:
- # 尝试直接加载
+ # 情况2:字典仅包含模型权重
self.model.load_state_dict(checkpoint)
else:
- # 如果保存的是模型本身
+ # 情况3:直接保存的模型实例
self.model = checkpoint
- # 移动到设备
+ # 将模型移动到指定设备(CPU/GPU)
self.model = self.model.to(self.device)
- # 设置为评估模式
+ # 设置模型为评估模式(禁用Dropout/BatchNorm的训练行为)
self.model.eval()
+ # 打印加载成功信息
print(f"✅ PyTorch模型加载成功")
print(f"📊 模型结构: {self.model.__class__.__name__}")
- print(f"📊 参数数量: {sum(p.numel() for p in self.model.parameters()):,}")
+ # 计算并打印模型总参数数量
+ total_params = sum(p.numel() for p in self.model.parameters())
+ print(f"📊 参数数量: {total_params:,}")
return True
except Exception as e:
+ # 捕获加载过程中的所有异常
print(f"❌ 模型加载失败: {e}")
self.model = None
return False
def preprocess_image(self, image):
- """预处理图像以供PyTorch模型使用"""
- # 转换OpenCV BGR图像为PIL RGB图像
+ """
+ 图像预处理:将输入图像转换为模型可接受的Tensor格式
+
+ Args:
+ image (np.ndarray/PIL.Image): 输入图像(OpenCV格式(BGR)或PIL格式(RGB))
+
+ Returns:
+ torch.Tensor: 预处理后的4维Tensor (batch_size, channels, height, width)
+ """
+ # 处理OpenCV格式图像(BGR→RGB,转换为PIL图像)
if isinstance(image, np.ndarray):
- # OpenCV图像 (BGR) -> PIL图像 (RGB)
- image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
- pil_image = Image.fromarray(image_rgb)
+ image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # BGR→RGB转换
+ pil_image = Image.fromarray(image_rgb) # 数组→PIL图像
else:
+ # 直接使用PIL图像
pil_image = image
- # 应用变换
+ # 应用预处理流水线
tensor = self.transform(pil_image)
- # 添加批次维度
+ # 添加批次维度(模型要求批量输入,单张图像batch_size=1)
tensor = tensor.unsqueeze(0)
- # 移动到设备
+ # 将Tensor移动到指定设备
tensor = tensor.to(self.device)
return tensor
def predict(self, image):
- """对图像进行预测"""
+ """
+ 单张图像场景分类预测
+
+ Args:
+ image (np.ndarray/PIL.Image): 输入图像(OpenCV/PIL格式)
+
+ Returns:
+ tuple: (预测类别名称, 置信度),预测失败返回(None, 0)
+ """
+ # 检查模型是否加载
if self.model is None:
- print("⚠️ 模型未加载")
+ print("⚠️ 模型未加载,无法预测")
return None, 0
try:
- # 预处理图像
+ # 图像预处理
input_tensor = self.preprocess_image(image)
- # 禁用梯度计算
+ # 禁用梯度计算(推理阶段无需计算梯度,提升速度,节省内存)
with torch.no_grad():
- # 前向传播
+ # 模型前向传播,获取预测logits
outputs = self.model(input_tensor)
- # 获取预测结果
+ # 计算softmax概率(将logits转换为0-1的概率分布)
probabilities = torch.nn.functional.softmax(outputs, dim=1)
+ # 获取最大概率的类别索引和置信度
confidence, predicted = torch.max(probabilities, 1)
- # 转换为Python标量
+ # 转换为Python标量(从Tensor→数值)
class_idx = predicted.item()
confidence_value = confidence.item()
@@ -205,42 +286,52 @@ def predict(self, image):
if 0 <= class_idx < len(self.class_names):
class_name = self.class_names[class_idx]
else:
- class_name = f"Class_{class_idx}"
+ class_name = f"Class_{class_idx}" # 未知类别兜底
return class_name, confidence_value
except Exception as e:
+ # 捕获预测过程中的异常
print(f"❌ 预测失败: {e}")
return None, 0
def predict_batch(self, images):
- """批量预测图像"""
+ """
+ 批量图像场景分类预测(提升批量处理效率)
+
+ Args:
+ images (list): 图像列表,每个元素为np.ndarray/PIL.Image格式
+
+ Returns:
+ tuple: (预测类别名称列表, 置信度列表),失败返回([], [])
+ """
if self.model is None:
+ print("⚠️ 模型未加载,无法批量预测")
return [], []
try:
- # 预处理所有图像
+ # 预处理所有图像,生成Tensor列表
tensors = []
for img in images:
tensor = self.preprocess_image(img)
tensors.append(tensor)
- # 堆叠为批次
+ # 堆叠为批量Tensor(batch_size=N)
batch = torch.cat(tensors, dim=0)
batch = batch.to(self.device)
- # 预测
+ # 推理阶段禁用梯度计算
with torch.no_grad():
outputs = self.model(batch)
probabilities = torch.nn.functional.softmax(outputs, dim=1)
confidences, predicted = torch.max(probabilities, 1)
- # 转换结果
+ # 解析批量预测结果
results = []
conf_values = []
-
for i in range(len(images)):
class_idx = predicted[i].item()
+ # 映射类别索引到名称
if 0 <= class_idx < len(self.class_names):
class_name = self.class_names[class_idx]
else:
@@ -256,42 +347,56 @@ def predict_batch(self, images):
return [], []
-# 模型工厂函数
def load_pytorch_model(model_path, model_type='custom'):
- """加载PyTorch模型的便捷函数"""
+ """
+ 加载PyTorch模型的便捷工厂函数
+
+ Args:
+ model_path (str): 模型权重文件路径
+ model_type (str): 模型架构类型,可选['custom', 'resnet18', 'mobilenet']
+
+ Returns:
+ PyTorchDroneModel: 模型实例(加载成功)/None(加载失败)
+ """
model = PyTorchDroneModel()
success = model.load_model(model_path, model_type)
return model if success else None
-# 测试函数
def test_model():
- """测试模型加载和预测"""
- print("🧪 测试PyTorch模型...")
+ """
+ 测试函数:验证模型架构创建、加载等核心功能
+ 无需实际权重文件,仅测试模型结构完整性
+ """
+ print("🧪 开始测试PyTorch模型工具类...")
- # 创建一个测试图像
+ # 创建随机测试图像(224×224×3,模拟RGB图像)
test_image = np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8)
- # 加载模型
+ # 初始化模型实例
model = PyTorchDroneModel()
- # 测试自定义模型
- print("\n1. 测试自定义模型架构...")
+ # 测试1:自定义CNN模型架构创建
+ print("\n1. 测试自定义CNN模型架构...")
custom_model = model.define_model_architecture()
- print(f"✅ 自定义模型创建成功,参数数量: {sum(p.numel() for p in custom_model.parameters()):,}")
+ total_params = sum(p.numel() for p in custom_model.parameters())
+ print(f"✅ 自定义模型创建成功,参数数量: {total_params:,}")
- # 测试ResNet18
- print("\n2. 测试ResNet18架构...")
+ # 测试2:ResNet18模型架构加载
+ print("\n2. 测试ResNet18模型架构...")
resnet_model = model.load_resnet18_model()
- print(f"✅ ResNet18模型创建成功,参数数量: {sum(p.numel() for p in resnet_model.parameters()):,}")
+ total_params = sum(p.numel() for p in resnet_model.parameters())
+ print(f"✅ ResNet18模型创建成功,参数数量: {total_params:,}")
- # 测试MobileNetV2
- print("\n3. 测试MobileNetV2架构...")
+ # 测试3:MobileNetV2模型架构加载
+ print("\n3. 测试MobileNetV2模型架构...")
mobilenet_model = model.load_mobilenetv2_model()
- print(f"✅ MobileNetV2模型创建成功,参数数量: {sum(p.numel() for p in mobilenet_model.parameters()):,}")
+ total_params = sum(p.numel() for p in mobilenet_model.parameters())
+ print(f"✅ MobileNetV2模型创建成功,参数数量: {total_params:,}")
- print("\n🧪 测试完成!")
+ print("\n🧪 所有测试完成!")
+# 主函数:仅在直接运行脚本时执行测试
if __name__ == "__main__":
test_model()
\ No newline at end of file
From d410ad58b8f0667f134b9671bcb5b3f424582655 Mon Sep 17 00:00:00 2001
From: 725921 <3407246504@qq.com>
Date: Wed, 24 Dec 2025 20:01:38 +0800
Subject: [PATCH 04/11] =?UTF-8?q?=E6=97=A0=E4=BA=BA=E6=9C=BA=E5=AF=BC?=
=?UTF-8?q?=E8=88=AA=E7=B3=BB=E7=BB=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
添加了代码注释
---
src/UAV_navigation_system/pytorch_model.py | 26 ++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/src/UAV_navigation_system/pytorch_model.py b/src/UAV_navigation_system/pytorch_model.py
index 315993751c..636540156b 100644
--- a/src/UAV_navigation_system/pytorch_model.py
+++ b/src/UAV_navigation_system/pytorch_model.py
@@ -3,6 +3,32 @@
"""
PyTorch模型工具类
用于加载和运行无人机场景分类的PyTorch模型
+"""
+
+# 临时修复:在导入 torch 前尝试处理 typing_extensions 问题
+try:
+ import typing_extensions
+ # 检查 TypeIs 是否可用,如果不可用则模拟一个
+ if not hasattr(typing_extensions, 'TypeIs'):
+ typing_extensions.TypeIs = type(lambda: None)
+except ImportError:
+ pass
+
+# 导入核心依赖库
+import torch # PyTorch核心库,用于模型构建和推理
+import torch.nn as nn # 神经网络层模块
+import torchvision.transforms as transforms # 图像预处理工具
+from PIL import Image # PIL库,用于图像读取和格式转换
+import numpy as np # 数值计算库,处理图像数组
+import cv2 # OpenCV库,处理视频/图像数据
+import os # 文件路径操作库
+
+# ... 其余代码保持不变 ...
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+PyTorch模型工具类
+用于加载和运行无人机场景分类的PyTorch模型
核心功能:
1. 支持自定义CNN/ResNet18/MobileNetV2三种模型架构加载
2. 实现图像预处理、单张/批量图像预测
From 720f56a9fb4516a120b6284e2ce57ba2ebb8c2ec Mon Sep 17 00:00:00 2001
From: 725921 <3407246504@qq.com>
Date: Wed, 24 Dec 2025 21:42:28 +0800
Subject: [PATCH 05/11] =?UTF-8?q?=E6=97=A0=E4=BA=BA=E6=9C=BA=E5=AF=BC?=
=?UTF-8?q?=E8=88=AA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
添加了导航系统的初代码
---
src/UAV_navigation_system/drone_main.py | 212 ++++++++++++++++++++++++
1 file changed, 212 insertions(+)
create mode 100644 src/UAV_navigation_system/drone_main.py
diff --git a/src/UAV_navigation_system/drone_main.py b/src/UAV_navigation_system/drone_main.py
new file mode 100644
index 0000000000..64aced148d
--- /dev/null
+++ b/src/UAV_navigation_system/drone_main.py
@@ -0,0 +1,212 @@
+"""
+无人机视觉导航系统 - 简化版
+可以立即运行测试
+"""
+
+import os
+import sys
+import time
+import cv2
+import numpy as np
+
+# 添加项目路径,让Python能找到src模块
+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+
+
+def ensure_directories():
+ """确保所有目录都存在"""
+ dirs = ['data/images', 'data/videos', 'data/logs', 'data/config']
+ for d in dirs:
+ os.makedirs(d, exist_ok=True)
+ print(f"✓ 目录已就绪: {d}")
+
+
+class SimpleDroneCamera:
+ """简单的无人机摄像头类"""
+
+ def __init__(self, camera_id=0):
+ self.camera_id = camera_id
+ self.cap = None
+
+ def open(self):
+ """打开摄像头"""
+ print(f"尝试打开摄像头 {self.camera_id}...")
+ self.cap = cv2.VideoCapture(self.camera_id)
+
+ if not self.cap.isOpened():
+ print("⚠️ 无法打开物理摄像头,使用模拟模式")
+ return False
+ else:
+ print("✓ 摄像头已连接")
+ return True
+
+ def read_frame(self):
+ """读取一帧"""
+ if self.cap and self.cap.isOpened():
+ ret, frame = self.cap.read()
+ if ret:
+ return frame
+
+ # 如果没有摄像头或读取失败,返回模拟图像
+ return self.simulate_frame()
+
+ def simulate_frame(self):
+ """生成模拟图像"""
+ width, height = 640, 480
+ frame = np.zeros((height, width, 3), dtype=np.uint8)
+
+ # 添加一些图形
+ cv2.putText(frame, "无人机模拟视图", (50, 50),
+ cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
+ cv2.rectangle(frame, (100, 100), (300, 300), (255, 0, 0), 2)
+ cv2.circle(frame, (400, 200), 50, (0, 0, 255), -1)
+
+ return frame
+
+ def release(self):
+ """释放摄像头"""
+ if self.cap:
+ self.cap.release()
+ print("摄像头已释放")
+
+
+def analyze_scene_simple(frame):
+ """简单场景分析(基于颜色)"""
+ if frame is None:
+ return "未知", 0.5
+
+ # 转换为HSV
+ hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
+
+ # 检测绿色(植被)
+ green_mask = cv2.inRange(hsv, (35, 40, 40), (85, 255, 255))
+ green_pct = np.sum(green_mask > 0) / green_mask.size
+
+ # 检测蓝色(水域)
+ blue_mask = cv2.inRange(hsv, (100, 40, 40), (140, 255, 255))
+ blue_pct = np.sum(blue_mask > 0) / blue_mask.size
+
+ # 判断场景
+ if green_pct > 0.3:
+ return "森林/草地", green_pct
+ elif blue_pct > 0.2:
+ return "水域", blue_pct
+ else:
+ return "城市/建筑", max(green_pct, blue_pct)
+
+
+def get_decision(scene_type, confidence):
+ """根据场景类型做出决策"""
+ decisions = {
+ "森林/草地": "✓ 安全区域,继续飞行",
+ "水域": "⚠️ 接近水域,提高飞行高度",
+ "城市/建筑": "⚠️ 城市区域,降低速度并避让",
+ "未知": "? 无法识别,保持警戒"
+ }
+ return decisions.get(scene_type, "保持当前状态")
+
+
+def main():
+ """主函数"""
+ print("=" * 50)
+ print("无人机视觉导航系统")
+ print("版本: 1.0.0")
+ print("按 'q' 键退出,按 's' 键保存图像")
+ print("=" * 50)
+
+ # 确保目录存在
+ ensure_directories()
+
+ # 创建无人机摄像头
+ drone_cam = SimpleDroneCamera(camera_id=0)
+ drone_cam.open()
+
+ # 初始化状态
+ battery = 100
+ flight_time = 0
+ frame_count = 0
+ start_time = time.time()
+
+ print("\n开始飞行...")
+
+ while True:
+ # 读取帧
+ frame = drone_cam.read_frame()
+ frame_count += 1
+
+ # 分析场景
+ scene_type, confidence = analyze_scene_simple(frame)
+
+ # 获取决策
+ decision = get_decision(scene_type, confidence)
+
+ # 更新状态
+ flight_time = time.time() - start_time
+ battery = max(0, battery - 0.05) # 慢慢消耗电池
+
+ # 在图像上显示信息
+ info_lines = [
+ f"场景: {scene_type} ({confidence:.1%})",
+ f"决策: {decision}",
+ f"飞行时间: {flight_time:.1f}s",
+ f"电池: {battery:.1f}%",
+ f"帧数: {frame_count}"
+ ]
+
+ y_offset = 30
+ for line in info_lines:
+ cv2.putText(frame, line, (10, y_offset),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1)
+ y_offset += 25
+
+ # 显示图像
+ cv2.imshow('无人机视觉导航', frame)
+
+ # 检查按键
+ key = cv2.waitKey(1) & 0xFF
+ if key == ord('q'): # 按 q 退出
+ break
+ elif key == ord('s'): # 按 s 保存图像
+ filename = f"data/images/capture_{frame_count}.jpg"
+ cv2.imwrite(filename, frame)
+ print(f"✓ 保存图像: {filename}")
+
+ # 检查电池
+ if battery <= 0:
+ print("\n⚠️ 电池耗尽!紧急降落...")
+ break
+
+ # 限制运行时间(可选)
+ if flight_time > 60: # 60秒后自动停止
+ print("\n⏰ 飞行时间到,安全降落...")
+ break
+
+ # 清理
+ drone_cam.release()
+ cv2.destroyAllWindows()
+
+ # 显示统计信息
+ print("\n" + "=" * 50)
+ print("飞行统计:")
+ print(f"- 总飞行时间: {flight_time:.1f} 秒")
+ print(f"- 处理帧数: {frame_count}")
+ print(f"- 平均帧率: {frame_count / flight_time:.1f} FPS" if flight_time > 0 else "- 平均帧率: N/A")
+ print(f"- 最终电池: {battery:.1f}%")
+ print("=" * 50)
+
+ print("\n飞行结束!")
+ return 0
+
+
+if __name__ == "__main__":
+ try:
+ exit_code = main()
+ except KeyboardInterrupt:
+ print("\n程序被用户中断")
+ exit_code = 0
+ except Exception as e:
+ print(f"\n程序出错: {e}")
+ exit_code = 1
+
+ input("\n按 Enter 键退出...")
+ sys.exit(exit_code)
\ No newline at end of file
From 3bc591a785094e6d9d7e5143538856f9ac8b7583 Mon Sep 17 00:00:00 2001
From: 725921 <3407246504@qq.com>
Date: Fri, 26 Dec 2025 15:25:06 +0800
Subject: [PATCH 06/11] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
给文件添加注释
---
.../drone_nav_with_vision.py | 154 +++++++++++++-----
1 file changed, 115 insertions(+), 39 deletions(-)
diff --git a/src/UAV_navigation_system/drone_nav_with_vision.py b/src/UAV_navigation_system/drone_nav_with_vision.py
index cfd02296cd..6e77d16970 100644
--- a/src/UAV_navigation_system/drone_nav_with_vision.py
+++ b/src/UAV_navigation_system/drone_nav_with_vision.py
@@ -1,63 +1,122 @@
-import cv2
-import numpy as np
-from tensorflow.keras.models import load_model
-from tensorflow.keras.preprocessing.image import img_to_array
-import random # For simulating low battery and emergency conditions
-import time # For the timeout condition
-
-# DroneBattery Class to manage battery
+# Import necessary libraries
+import cv2 # OpenCV for computer vision tasks (video capture, image processing)
+import numpy as np # NumPy for numerical operations and array manipulation
+from tensorflow.keras.models import load_model # Load pre-trained Keras model
+from tensorflow.keras.preprocessing.image import img_to_array # Convert image to array for model input
+import random # For simulating random emergency conditions (low battery, etc.)
+import time # For time-related operations (timeouts, sleep)
+
+
+# DroneBattery Class to manage battery state and operations
class DroneBattery:
+ """Class to simulate and manage drone battery operations"""
+
def __init__(self, max_capacity=100, current_charge=100):
+ """
+ Initialize battery parameters
+ Args:
+ max_capacity: Maximum battery capacity (default 100%)
+ current_charge: Current battery charge level (default 100%)
+ """
self.max_capacity = max_capacity
self.current_charge = current_charge
-
+
def display_battery_status(self):
+ """Display current battery percentage"""
print(f"Battery Status: {self.current_charge}%")
-
+
def charge_battery(self, charge_rate=10):
+ """
+ Simulate battery charging process
+ Args:
+ charge_rate: Percentage to charge per iteration (default 10%)
+ """
while self.current_charge < self.max_capacity:
self.current_charge += charge_rate
if self.current_charge > self.max_capacity:
self.current_charge = self.max_capacity
print(f"Charging... {self.current_charge}%")
- time.sleep(1)
+ time.sleep(1) # Simulate time delay for charging
print("Battery fully charged!")
-
+
def discharge_battery(self, discharge_rate=10):
+ """
+ Simulate battery discharging process
+ Args:
+ discharge_rate: Percentage to discharge per iteration (default 10%)
+ """
while self.current_charge > 0:
self.current_charge -= discharge_rate
if self.current_charge < 0:
self.current_charge = 0
print(f"Discharging... {self.current_charge}%")
- time.sleep(1)
+ time.sleep(1) # Simulate time delay for discharging
print("Battery completely drained!")
-
+
def is_battery_low(self):
+ """
+ Check if battery level is critically low
+ Returns:
+ Boolean: True if battery < 20%, False otherwise
+ """
return self.current_charge < 20
-# ✅ Load and compile model
+
+# Load and compile the pre-trained machine learning model
print("📦 Loading model...")
+# Load model from specified file path
model = load_model(r"C:\Users\hp\DroneNavVision\data\best_model.h5")
-model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) # ✅ Compile step added
+# Compile model with optimizer and loss function for inference
+model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
+# Define class names corresponding to model output categories
class_names = ['Animal', 'City', 'Fire', 'Forest', 'Vehicle', 'Water']
+
def check_emergency():
+ """
+ Simulate random emergency condition checks
+ Returns:
+ String: Randomly selected emergency condition
+ """
emergency_conditions = ['low_battery', 'emergency']
return random.choice(emergency_conditions)
-# Handle low battery condition
+
def handle_low_battery(drone_battery):
+ """
+ Handle low battery emergency procedure
+ Args:
+ drone_battery: DroneBattery instance to manage charging
+ """
print("🔋 Low battery! Returning to base.")
- drone_battery.charge_battery(charge_rate=15)
- exit()
+ drone_battery.charge_battery(charge_rate=15) # Fast charge at 15% per second
+ exit() # Exit program after charging
+
def preprocess_frame(frame):
+ """
+ Preprocess video frame for model input
+ Args:
+ frame: Raw image frame from camera
+ Returns:
+ numpy array: Preprocessed image ready for model prediction
+ """
+ # Resize frame to match model input dimensions (128x128)
resized = cv2.resize(frame, (128, 128))
+ # Normalize pixel values to [0, 1] and convert to array
img_array = img_to_array(resized) / 255.0
+ # Add batch dimension (1, 128, 128, 3)
return np.expand_dims(img_array, axis=0)
+
def decide_navigation(predicted_class):
+ """
+ Determine navigation action based on detected class
+ Args:
+ predicted_class: String of detected object/environment class
+ """
+ # Define navigation strategies for each detected class
if predicted_class == 'Fire':
print("🔥 Fire detected! Navigate away.")
elif predicted_class == 'Animal':
@@ -73,62 +132,79 @@ def decide_navigation(predicted_class):
else:
print("✅ Clear path. Continue normal navigation.")
+
def main():
+ """
+ Main function to run drone vision system
+ Handles video capture, frame processing, prediction, and navigation decisions
+ """
print("🚁 Starting the drone vision process...")
- start_time = time.time()
-
- # Use the correct video source based on your setup
- VIDEO_SOURCE = "http://192.168.1.3:4747/video" # Or set your IP stream (e.g., "rtsp:///stream")
+ start_time = time.time() # Record start time for timeout condition
- cap = cv2.VideoCapture(VIDEO_SOURCE, cv2.CAP_FFMPEG)
- cap.set(cv2.CAP_PROP_BUFFERSIZE, 10) # Increase buffer size
- cap.set(cv2.CAP_PROP_FPS, 30) # Set the desired FPS (adjust as needed)
+ # Video source configuration - adjust based on drone camera setup
+ # Options: RTSP stream, IP camera, or local camera
+ VIDEO_SOURCE = "http://192.168.1.3:4747/video" # IP camera stream URL
+ # Initialize video capture with FFMPEG backend for streaming
+ cap = cv2.VideoCapture(VIDEO_SOURCE, cv2.CAP_FFMPEG)
+ # Configure video capture properties
+ cap.set(cv2.CAP_PROP_BUFFERSIZE, 10) # Increase buffer size for smoother streaming
+ cap.set(cv2.CAP_PROP_FPS, 30) # Set frame rate to 30 FPS
- # Check if video stream is opened successfully
+ # Validate video stream connection
if not cap.isOpened():
print("❌ Failed to open video source. Check connection or URL.")
return
else:
print("✅ Video source opened successfully.")
-
+ # Initialize drone battery management
drone_battery = DroneBattery()
+ # Main processing loop
while True:
print("📸 Processing frame...")
-
- # Check if battery is low
+
+ # Emergency condition check: Low battery
if drone_battery.is_battery_low():
handle_low_battery(drone_battery)
+ # Timeout condition: Stop after 5 minutes (300 seconds)
elapsed_time = time.time() - start_time
if elapsed_time > 300:
print("⏰ Timeout reached! Stopping the drone.")
break
+ # Capture frame from video stream
ret, frame = cap.read()
- if not ret:
+ if not ret: # Check if frame capture was successful
print("❌ Failed to capture frame.")
break
- processed = preprocess_frame(frame)
- pred = model.predict(processed)
- predicted_class = class_names[np.argmax(pred)]
- confidence = np.max(pred) * 100
+ # Process frame and make prediction
+ processed = preprocess_frame(frame) # Preprocess for model
+ pred = model.predict(processed) # Run model inference
+ predicted_class = class_names[np.argmax(pred)] # Get class with highest probability
+ confidence = np.max(pred) * 100 # Convert confidence to percentage
+ # Display prediction on video feed
cv2.putText(frame, f"{predicted_class} ({confidence:.2f}%)", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
- cv2.imshow("Drone Vision Feed", frame)
+ cv2.imshow("Drone Vision Feed", frame) # Show annotated video
+ # Make navigation decision based on prediction
decide_navigation(predicted_class)
+ # Exit condition: Press 'q' to quit
if cv2.waitKey(1) & 0xFF == ord('q'):
print("🛑 Manual stop initiated by the user.")
break
- cap.release()
- cv2.destroyAllWindows()
+ # Cleanup resources
+ cap.release() # Release video capture device
+ cv2.destroyAllWindows() # Close all OpenCV windows
+
+# Standard Python idiom to run main function when script is executed directly
if __name__ == "__main__":
- main()
+ main()
\ No newline at end of file
From e55f1beadbda5e7e26dee230497d1718ed151ef0 Mon Sep 17 00:00:00 2001
From: 725921 <3407246504@qq.com>
Date: Fri, 26 Dec 2025 16:01:09 +0800
Subject: [PATCH 07/11] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
代码添加注释
---
src/UAV_navigation_system/drone_main.py | 140 ++++++++++++++----------
1 file changed, 83 insertions(+), 57 deletions(-)
diff --git a/src/UAV_navigation_system/drone_main.py b/src/UAV_navigation_system/drone_main.py
index 64aced148d..5b0afa90d7 100644
--- a/src/UAV_navigation_system/drone_main.py
+++ b/src/UAV_navigation_system/drone_main.py
@@ -9,53 +9,64 @@
import cv2
import numpy as np
-# 添加项目路径,让Python能找到src模块
+# 添加项目路径,让Python能找到src模块(如果需要)
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
def ensure_directories():
- """确保所有目录都存在"""
+ """确保所有目录都存在(用于存储数据)"""
+ # 定义需要创建的目录列表
dirs = ['data/images', 'data/videos', 'data/logs', 'data/config']
for d in dirs:
+ # 如果目录不存在则创建
os.makedirs(d, exist_ok=True)
print(f"✓ 目录已就绪: {d}")
class SimpleDroneCamera:
- """简单的无人机摄像头类"""
+ """简单的无人机摄像头类 - 模拟或真实摄像头"""
def __init__(self, camera_id=0):
+ """
+ 初始化摄像头
+ Args:
+ camera_id: 摄像头ID,0通常表示默认摄像头
+ """
self.camera_id = camera_id
- self.cap = None
+ self.cap = None # OpenCV视频捕获对象
def open(self):
"""打开摄像头"""
print(f"尝试打开摄像头 {self.camera_id}...")
+ # 尝试打开指定ID的摄像头
self.cap = cv2.VideoCapture(self.camera_id)
+ # 检查摄像头是否成功打开
if not self.cap.isOpened():
print("⚠️ 无法打开物理摄像头,使用模拟模式")
- return False
+ return False # 打开失败
else:
print("✓ 摄像头已连接")
- return True
+ return True # 打开成功
def read_frame(self):
- """读取一帧"""
+ """读取一帧图像"""
+ # 如果有摄像头且已打开,尝试读取帧
if self.cap and self.cap.isOpened():
ret, frame = self.cap.read()
- if ret:
+ if ret: # 读取成功
return frame
# 如果没有摄像头或读取失败,返回模拟图像
return self.simulate_frame()
def simulate_frame(self):
- """生成模拟图像"""
+ """生成模拟图像(在没有摄像头时使用)"""
+ # 创建黑色背景图像
width, height = 640, 480
frame = np.zeros((height, width, 3), dtype=np.uint8)
- # 添加一些图形
+ # 在图像上添加一些图形作为模拟内容
cv2.putText(frame, "无人机模拟视图", (50, 50),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.rectangle(frame, (100, 100), (300, 300), (255, 0, 0), 2)
@@ -64,87 +75,94 @@ def simulate_frame(self):
return frame
def release(self):
- """释放摄像头"""
+ """释放摄像头资源"""
if self.cap:
self.cap.release()
print("摄像头已释放")
def analyze_scene_simple(frame):
- """简单场景分析(基于颜色)"""
+ """简单场景分析(基于颜色特征)"""
+ # 检查输入帧是否有效
if frame is None:
return "未知", 0.5
- # 转换为HSV
+ # 将BGR颜色空间转换为HSV(色调、饱和度、明度)便于颜色分析
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
- # 检测绿色(植被)
+ # 检测绿色(植被)范围
green_mask = cv2.inRange(hsv, (35, 40, 40), (85, 255, 255))
+ # 计算绿色像素占比
green_pct = np.sum(green_mask > 0) / green_mask.size
- # 检测蓝色(水域)
+ # 检测蓝色(水域)范围
blue_mask = cv2.inRange(hsv, (100, 40, 40), (140, 255, 255))
+ # 计算蓝色像素占比
blue_pct = np.sum(blue_mask > 0) / blue_mask.size
- # 判断场景
- if green_pct > 0.3:
+ # 根据颜色占比判断场景类型
+ if green_pct > 0.3: # 绿色占比超过30%认为是森林/草地
return "森林/草地", green_pct
- elif blue_pct > 0.2:
+ elif blue_pct > 0.2: # 蓝色占比超过20%认为是水域
return "水域", blue_pct
- else:
+ else: # 其他情况认为是城市/建筑区域
return "城市/建筑", max(green_pct, blue_pct)
def get_decision(scene_type, confidence):
- """根据场景类型做出决策"""
+ """根据场景类型做出飞行决策"""
+ # 决策映射表:不同场景类型对应的飞行决策
decisions = {
"森林/草地": "✓ 安全区域,继续飞行",
"水域": "⚠️ 接近水域,提高飞行高度",
"城市/建筑": "⚠️ 城市区域,降低速度并避让",
"未知": "? 无法识别,保持警戒"
}
+ # 获取对应决策,如果没有匹配则返回默认决策
return decisions.get(scene_type, "保持当前状态")
def main():
- """主函数"""
+ """主函数 - 无人机视觉导航系统主循环"""
+ # 打印程序标题和说明
print("=" * 50)
print("无人机视觉导航系统")
print("版本: 1.0.0")
print("按 'q' 键退出,按 's' 键保存图像")
print("=" * 50)
- # 确保目录存在
+ # 确保所需目录存在
ensure_directories()
- # 创建无人机摄像头
+ # 创建无人机摄像头对象
drone_cam = SimpleDroneCamera(camera_id=0)
- drone_cam.open()
+ drone_cam.open() # 打开摄像头
- # 初始化状态
- battery = 100
- flight_time = 0
- frame_count = 0
- start_time = time.time()
+ # 初始化飞行状态变量
+ battery = 100 # 电池电量(百分比)
+ flight_time = 0 # 飞行时间(秒)
+ frame_count = 0 # 已处理帧数
+ start_time = time.time() # 飞行开始时间
print("\n开始飞行...")
+ # 主循环:处理每一帧图像
while True:
- # 读取帧
+ # 1. 读取当前帧
frame = drone_cam.read_frame()
- frame_count += 1
+ frame_count += 1 # 帧数计数器递增
- # 分析场景
+ # 2. 分析场景类型
scene_type, confidence = analyze_scene_simple(frame)
- # 获取决策
+ # 3. 根据场景类型做出决策
decision = get_decision(scene_type, confidence)
- # 更新状态
- flight_time = time.time() - start_time
- battery = max(0, battery - 0.05) # 慢慢消耗电池
+ # 4. 更新飞行状态
+ flight_time = time.time() - start_time # 计算已飞行时间
+ battery = max(0, battery - 0.05) # 模拟电池消耗(每帧减少0.05%)
- # 在图像上显示信息
+ # 5. 在图像上显示状态信息
info_lines = [
f"场景: {scene_type} ({confidence:.1%})",
f"决策: {decision}",
@@ -153,60 +171,68 @@ def main():
f"帧数: {frame_count}"
]
- y_offset = 30
+ # 逐行绘制状态信息到图像上
+ y_offset = 30 # 文字起始y坐标
for line in info_lines:
cv2.putText(frame, line, (10, y_offset),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1)
- y_offset += 25
+ y_offset += 25 # 每行文字间隔25像素
- # 显示图像
+ # 6. 显示处理后的图像
cv2.imshow('无人机视觉导航', frame)
- # 检查按键
- key = cv2.waitKey(1) & 0xFF
- if key == ord('q'): # 按 q 退出
+ # 7. 检查键盘输入
+ key = cv2.waitKey(1) & 0xFF # 等待1毫秒并获取按键
+ if key == ord('q'): # 按 'q' 键退出程序
+ print("\n用户请求退出...")
break
- elif key == ord('s'): # 按 s 保存图像
+ elif key == ord('s'): # 按 's' 键保存当前图像
filename = f"data/images/capture_{frame_count}.jpg"
cv2.imwrite(filename, frame)
print(f"✓ 保存图像: {filename}")
- # 检查电池
+ # 8. 检查电池电量(安全条件)
if battery <= 0:
print("\n⚠️ 电池耗尽!紧急降落...")
break
- # 限制运行时间(可选)
- if flight_time > 60: # 60秒后自动停止
+ # 9. 检查飞行时间限制(可选安全限制)
+ if flight_time > 60: # 运行60秒后自动停止(演示用途)
print("\n⏰ 飞行时间到,安全降落...")
break
- # 清理
- drone_cam.release()
- cv2.destroyAllWindows()
+ # 10. 清理资源
+ drone_cam.release() # 释放摄像头
+ cv2.destroyAllWindows() # 关闭所有OpenCV窗口
- # 显示统计信息
+ # 11. 打印飞行统计信息
print("\n" + "=" * 50)
print("飞行统计:")
print(f"- 总飞行时间: {flight_time:.1f} 秒")
print(f"- 处理帧数: {frame_count}")
- print(f"- 平均帧率: {frame_count / flight_time:.1f} FPS" if flight_time > 0 else "- 平均帧率: N/A")
+ # 计算平均帧率(FPS)
+ if flight_time > 0:
+ print(f"- 平均帧率: {frame_count / flight_time:.1f} FPS")
+ else:
+ print("- 平均帧率: N/A")
print(f"- 最终电池: {battery:.1f}%")
print("=" * 50)
print("\n飞行结束!")
- return 0
+ return 0 # 返回成功状态码
+# Python标准入口点
if __name__ == "__main__":
try:
- exit_code = main()
- except KeyboardInterrupt:
+ exit_code = main() # 运行主函数
+ except KeyboardInterrupt: # 处理Ctrl+C中断
print("\n程序被用户中断")
exit_code = 0
- except Exception as e:
+ except Exception as e: # 处理其他异常
print(f"\n程序出错: {e}")
exit_code = 1
+ # 等待用户确认退出(便于查看输出结果)
input("\n按 Enter 键退出...")
- sys.exit(exit_code)
\ No newline at end of file
+ sys.exit(exit_code) # 退出程序
\ No newline at end of file
From 68040a4bef0172e63ca991d93f8a1f200af12f8b Mon Sep 17 00:00:00 2001
From: 725921 <3407246504@qq.com>
Date: Sat, 27 Dec 2025 14:55:56 +0800
Subject: [PATCH 08/11] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E7=9B=AE?=
=?UTF-8?q?=E5=BD=95=E6=96=87=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
添加了文件
---
src/UAV_navigation_system/mulu.test | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
create mode 100644 src/UAV_navigation_system/mulu.test
diff --git a/src/UAV_navigation_system/mulu.test b/src/UAV_navigation_system/mulu.test
new file mode 100644
index 0000000000..2af639ca9c
--- /dev/null
+++ b/src/UAV_navigation_system/mulu.test
@@ -0,0 +1,17 @@
+Drone_Vision_Navigation/
+├── main.py # 主程序
+├── config.yaml # 配置文件
+├── requirements.txt # 依赖包
+├── data/
+│ ├── captured/ # 捕获的图像
+│ ├── training/ # 训练数据
+│ └── models/ # 模型文件(best_model.hs)
+├── scripts/
+│ ├── droidcam_stream.py # DroidCam集成
+│ ├── battery_manager.py # 电池管理
+│ └── emergency_handler.py # 紧急处理
+├── utils/
+│ ├── image_processor.py # 图像处理
+│ ├── classifier.py # 分类器
+│ └── navigation_logic.py # 导航逻辑
+└── logs/ # 系统日志
\ No newline at end of file
From 00288e9fff04ee49b265cc079d8985a8b24ce081 Mon Sep 17 00:00:00 2001
From: 725921 <3407246504@qq.com>
Date: Sat, 27 Dec 2025 17:15:22 +0800
Subject: [PATCH 09/11] =?UTF-8?q?=E6=93=8D=E4=BD=9C=E9=94=AE=E4=BD=8D?=
=?UTF-8?q?=E6=96=87=E4=BB=B6=E8=A1=A5=E5=85=85?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
文件补充
---
src/UAV_navigation_system/caozuo | 9 +++++++++
1 file changed, 9 insertions(+)
create mode 100644 src/UAV_navigation_system/caozuo
diff --git a/src/UAV_navigation_system/caozuo b/src/UAV_navigation_system/caozuo
new file mode 100644
index 0000000000..0ddfb63988
--- /dev/null
+++ b/src/UAV_navigation_system/caozuo
@@ -0,0 +1,9 @@
+F1 - 显示帮助
+F - 切换到第一人称视角
+B - 第三人称视角
+R - 开始/停止录制
+退格键 - 重置无人机位置
+方向键 - 控制无人机移动
+q - 退出程序
+s - 保存当前快照
+p - 暂停/继续
\ No newline at end of file
From 424fbd0c31d0d1dbe948369e3569d52dddb6d2c6 Mon Sep 17 00:00:00 2001
From: 725921 <3407246504@qq.com>
Date: Sat, 27 Dec 2025 17:55:40 +0800
Subject: [PATCH 10/11] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E6=93=8D?=
=?UTF-8?q?=E4=BD=9C=E6=8C=89=E9=94=AE=E8=A1=A5=E5=85=85=E7=9A=84=E6=96=87?=
=?UTF-8?q?=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
操作按键补充
---
src/UAV_navigation_system/{caozuo => caozuo.test} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename src/UAV_navigation_system/{caozuo => caozuo.test} (100%)
diff --git a/src/UAV_navigation_system/caozuo b/src/UAV_navigation_system/caozuo.test
similarity index 100%
rename from src/UAV_navigation_system/caozuo
rename to src/UAV_navigation_system/caozuo.test
From fa2917ad91afeea638c6135bb4f9a8d8f7df4b75 Mon Sep 17 00:00:00 2001
From: 725921 <3407246504@qq.com>
Date: Tue, 30 Dec 2025 17:57:45 +0800
Subject: [PATCH 11/11] =?UTF-8?q?=E6=97=A0=E4=BA=BA=E6=9C=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
添加了主程序代码 和检测airsim的程序 还有错误直接执行的bat
---
src/UAV_navigation_system/drone_main.py | 981 +++++++++++++++++-----
src/UAV_navigation_system/start_fixed.bat | 56 ++
src/UAV_navigation_system/test_airsim.py | 10 +
3 files changed, 822 insertions(+), 225 deletions(-)
create mode 100644 src/UAV_navigation_system/start_fixed.bat
create mode 100644 src/UAV_navigation_system/test_airsim.py
diff --git a/src/UAV_navigation_system/drone_main.py b/src/UAV_navigation_system/drone_main.py
index 5b0afa90d7..4b2481d88f 100644
--- a/src/UAV_navigation_system/drone_main.py
+++ b/src/UAV_navigation_system/drone_main.py
@@ -1,238 +1,769 @@
+# drone_vision_system_en_fixed.py
"""
-无人机视觉导航系统 - 简化版
-可以立即运行测试
+Drone Vision Navigation System
+Optimized for Abandoned Park Environment
+Pure English interface to avoid encoding issues
+Fixed KeyboardInterrupt handling
"""
-import os
-import sys
-import time
+import airsim
import cv2
import numpy as np
-
-# 添加项目路径,让Python能找到src模块(如果需要)
-sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
-
-
-def ensure_directories():
- """确保所有目录都存在(用于存储数据)"""
- # 定义需要创建的目录列表
- dirs = ['data/images', 'data/videos', 'data/logs', 'data/config']
- for d in dirs:
- # 如果目录不存在则创建
- os.makedirs(d, exist_ok=True)
- print(f"✓ 目录已就绪: {d}")
-
-
-class SimpleDroneCamera:
- """简单的无人机摄像头类 - 模拟或真实摄像头"""
-
- def __init__(self, camera_id=0):
- """
- 初始化摄像头
- Args:
- camera_id: 摄像头ID,0通常表示默认摄像头
+import time
+import json
+import os
+import sys
+from datetime import datetime
+import random
+
+class DroneVisionSystem:
+ """Drone vision navigation system"""
+
+ def __init__(self):
+ """Initialize system"""
+ self.clear_screen()
+ print("=" * 70)
+ print("DRONE VISION NAVIGATION SYSTEM v2.0")
+ print("=" * 70)
+ print("Optimized for Abandoned Park Environment")
+ print("-" * 70)
+
+ # Try to connect to simulator
+ try:
+ print("Connecting to AirSim simulator...")
+ self.client = airsim.MultirotorClient()
+ self.client.confirmConnection()
+ print("✓ Connection successful!")
+ except Exception as e:
+ print(f"✗ Connection failed: {str(e)}")
+ print("\nPlease ensure:")
+ print("1. AbandonedPark.exe is running")
+ print("2. Simulator is fully loaded")
+ print("3. Switched to drone mode if needed")
+ self.client = None
+
+ # System status
+ self.running = False
+ self.flying = False
+ self.battery = 100.0
+ self.current_env = "Unknown"
+ self.env_confidence = 0.0
+ self.emergency = False
+
+ # Create directories
+ self.create_folders()
+
+ # Initialize classifier
+ self.classifier = EnvironmentClassifier()
+
+ print("System initialization complete!")
+ print("=" * 70)
+
+ def clear_screen(self):
+ """Clear console screen"""
+ if sys.platform == 'win32':
+ os.system('cls')
+ else:
+ os.system('clear')
+
+ def create_folders(self):
+ """Create project folders"""
+ folders = ['data/images', 'data/logs', 'models', 'debug']
+ for folder in folders:
+ os.makedirs(folder, exist_ok=True)
+
+ def log(self, message):
+ """Log message"""
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ log_msg = f"[{timestamp}] {message}"
+
+ # Print to console
+ print(log_msg)
+
+ # Save to file
+ log_file = f"data/logs/drone_{datetime.now().strftime('%Y%m%d')}.log"
+ with open(log_file, 'a') as f:
+ f.write(log_msg + "\n")
+
+ def show_status(self):
+ """Display system status"""
+ status_text = f"""
+Current Status:
+ Flight Status: {'Flying' if self.flying else 'Landed'}
+ Environment: {self.current_env} ({self.env_confidence:.1%})
+ Battery: {self.battery:.1f}%
+ Emergency Mode: {'Yes' if self.emergency else 'No'}
+ System Running: {'Yes' if self.running else 'No'}
"""
- self.camera_id = camera_id
- self.cap = None # OpenCV视频捕获对象
-
- def open(self):
- """打开摄像头"""
- print(f"尝试打开摄像头 {self.camera_id}...")
- # 尝试打开指定ID的摄像头
- self.cap = cv2.VideoCapture(self.camera_id)
-
- # 检查摄像头是否成功打开
- if not self.cap.isOpened():
- print("⚠️ 无法打开物理摄像头,使用模拟模式")
- return False # 打开失败
+ print(status_text)
+
+ def takeoff(self, height=15):
+ """Take off to specified height"""
+ if not self.client:
+ print("Not connected to simulator!")
+ return False
+
+ try:
+ self.log(f"Taking off to {height} meters...")
+
+ # Unlock drone
+ self.client.enableApiControl(True)
+ self.client.armDisarm(True)
+ time.sleep(1)
+
+ # Take off
+ self.client.takeoffAsync().join()
+ time.sleep(2)
+
+ # Ascend to specified height
+ self.client.moveToZAsync(-height, 3).join()
+
+ self.flying = True
+ self.log("Takeoff successful!")
+ return True
+
+ except Exception as e:
+ self.log(f"Takeoff failed: {str(e)}")
+ return False
+
+ def land(self):
+ """Land the drone"""
+ try:
+ self.log("Landing...")
+ self.client.landAsync().join()
+ time.sleep(2)
+
+ # Lock drone
+ self.client.armDisarm(False)
+ self.client.enableApiControl(False)
+
+ self.flying = False
+ self.log("Landing successful!")
+ return True
+
+ except Exception as e:
+ self.log(f"Landing failed: {str(e)}")
+ return False
+
+ def capture_image(self):
+ """Capture image from drone camera"""
+ try:
+ responses = self.client.simGetImages([
+ airsim.ImageRequest("0", airsim.ImageType.Scene, False, False)
+ ])
+
+ if responses:
+ response = responses[0]
+ img1d = np.frombuffer(response.image_data_uint8, dtype=np.uint8)
+ img_rgb = img1d.reshape(response.height, response.width, 3)
+
+ # Convert to BGR format
+ img_bgr = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)
+ return img_bgr
+
+ except Exception as e:
+ self.log(f"Image capture failed: {str(e)}")
+
+ return None
+
+ def analyze_environment(self, image):
+ """Analyze environment using classifier"""
+ return self.classifier.classify(image)
+
+ def update_battery(self):
+ """Update battery status"""
+ if self.flying:
+ self.battery -= 0.05 # Flying consumption
else:
- print("✓ 摄像头已连接")
- return True # 打开成功
-
- def read_frame(self):
- """读取一帧图像"""
- # 如果有摄像头且已打开,尝试读取帧
- if self.cap and self.cap.isOpened():
- ret, frame = self.cap.read()
- if ret: # 读取成功
- return frame
-
- # 如果没有摄像头或读取失败,返回模拟图像
- return self.simulate_frame()
-
- def simulate_frame(self):
- """生成模拟图像(在没有摄像头时使用)"""
- # 创建黑色背景图像
- width, height = 640, 480
- frame = np.zeros((height, width, 3), dtype=np.uint8)
-
- # 在图像上添加一些图形作为模拟内容
- cv2.putText(frame, "无人机模拟视图", (50, 50),
- cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
- cv2.rectangle(frame, (100, 100), (300, 300), (255, 0, 0), 2)
- cv2.circle(frame, (400, 200), 50, (0, 0, 255), -1)
-
- return frame
-
- def release(self):
- """释放摄像头资源"""
- if self.cap:
- self.cap.release()
- print("摄像头已释放")
-
-
-def analyze_scene_simple(frame):
- """简单场景分析(基于颜色特征)"""
- # 检查输入帧是否有效
- if frame is None:
- return "未知", 0.5
-
- # 将BGR颜色空间转换为HSV(色调、饱和度、明度)便于颜色分析
- hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
-
- # 检测绿色(植被)范围
- green_mask = cv2.inRange(hsv, (35, 40, 40), (85, 255, 255))
- # 计算绿色像素占比
- green_pct = np.sum(green_mask > 0) / green_mask.size
-
- # 检测蓝色(水域)范围
- blue_mask = cv2.inRange(hsv, (100, 40, 40), (140, 255, 255))
- # 计算蓝色像素占比
- blue_pct = np.sum(blue_mask > 0) / blue_mask.size
-
- # 根据颜色占比判断场景类型
- if green_pct > 0.3: # 绿色占比超过30%认为是森林/草地
- return "森林/草地", green_pct
- elif blue_pct > 0.2: # 蓝色占比超过20%认为是水域
- return "水域", blue_pct
- else: # 其他情况认为是城市/建筑区域
- return "城市/建筑", max(green_pct, blue_pct)
-
-
-def get_decision(scene_type, confidence):
- """根据场景类型做出飞行决策"""
- # 决策映射表:不同场景类型对应的飞行决策
- decisions = {
- "森林/草地": "✓ 安全区域,继续飞行",
- "水域": "⚠️ 接近水域,提高飞行高度",
- "城市/建筑": "⚠️ 城市区域,降低速度并避让",
- "未知": "? 无法识别,保持警戒"
- }
- # 获取对应决策,如果没有匹配则返回默认决策
- return decisions.get(scene_type, "保持当前状态")
-
-
-def main():
- """主函数 - 无人机视觉导航系统主循环"""
- # 打印程序标题和说明
- print("=" * 50)
- print("无人机视觉导航系统")
- print("版本: 1.0.0")
- print("按 'q' 键退出,按 's' 键保存图像")
- print("=" * 50)
-
- # 确保所需目录存在
- ensure_directories()
-
- # 创建无人机摄像头对象
- drone_cam = SimpleDroneCamera(camera_id=0)
- drone_cam.open() # 打开摄像头
-
- # 初始化飞行状态变量
- battery = 100 # 电池电量(百分比)
- flight_time = 0 # 飞行时间(秒)
- frame_count = 0 # 已处理帧数
- start_time = time.time() # 飞行开始时间
-
- print("\n开始飞行...")
-
- # 主循环:处理每一帧图像
- while True:
- # 1. 读取当前帧
- frame = drone_cam.read_frame()
- frame_count += 1 # 帧数计数器递增
-
- # 2. 分析场景类型
- scene_type, confidence = analyze_scene_simple(frame)
-
- # 3. 根据场景类型做出决策
- decision = get_decision(scene_type, confidence)
-
- # 4. 更新飞行状态
- flight_time = time.time() - start_time # 计算已飞行时间
- battery = max(0, battery - 0.05) # 模拟电池消耗(每帧减少0.05%)
-
- # 5. 在图像上显示状态信息
- info_lines = [
- f"场景: {scene_type} ({confidence:.1%})",
- f"决策: {decision}",
- f"飞行时间: {flight_time:.1f}s",
- f"电池: {battery:.1f}%",
- f"帧数: {frame_count}"
+ self.battery -= 0.01 # Standby consumption
+
+ if self.battery < 0:
+ self.battery = 0
+
+ # Check low battery
+ if self.battery < 20 and not self.emergency:
+ self.log(f"Warning: Low battery ({self.battery:.1f}%)")
+ if self.battery < 10:
+ self.log("Critical: Very low battery!")
+ self.emergency = True
+
+ def save_image_data(self, image, environment, confidence):
+ """Save image data"""
+ if image is None:
+ return
+
+ # Generate filename
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
+ filename = f"data/images/img_{timestamp}_{environment}.jpg"
+
+ # Save image
+ cv2.imwrite(filename, image)
+
+ # Record to JSON
+ data = {
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
+ "filename": filename,
+ "environment": environment,
+ "confidence": float(confidence),
+ "battery": float(self.battery),
+ "flying": self.flying
+ }
+
+ json_file = "data/images/images_log.json"
+
+ try:
+ # Read existing data
+ if os.path.exists(json_file):
+ with open(json_file, 'r') as f:
+ all_data = json.load(f)
+ else:
+ all_data = []
+
+ # Add new data
+ all_data.append(data)
+
+ # Keep only recent 1000 records
+ if len(all_data) > 1000:
+ all_data = all_data[-1000:]
+
+ # Save
+ with open(json_file, 'w') as f:
+ json.dump(all_data, f, indent=2)
+
+ except Exception as e:
+ self.log(f"Data save failed: {str(e)}")
+
+ def navigate(self, environment, confidence):
+ """Navigate based on environment"""
+ if not self.flying:
+ return
+
+ # Choose action based on environment
+ actions = {
+ "Ruins": self.navigate_ruins,
+ "Building": self.navigate_building,
+ "Forest": self.navigate_forest,
+ "Road": self.navigate_road,
+ "Sky": self.navigate_sky,
+ "Water": self.navigate_water,
+ "Fire": self.navigate_fire,
+ "Animal": self.navigate_animal,
+ "Vehicle": self.navigate_vehicle
+ }
+
+ # Execute corresponding navigation function
+ action_func = actions.get(environment, self.navigate_default)
+ action_func(confidence)
+
+ def navigate_ruins(self, confidence):
+ """Navigate in ruins"""
+ self.log("Exploring ruins...")
+ # Move slowly to avoid collisions
+ self.client.moveByVelocityAsync(2, 0, 0, 2).join()
+ time.sleep(1)
+
+ def navigate_building(self, confidence):
+ """Navigate around buildings"""
+ self.log("Avoiding building...")
+ # Rise to avoid collision
+ self.client.moveByVelocityAsync(0, 0, -1, 1).join()
+ # Move right
+ self.client.moveByVelocityAsync(0, 2, 0, 2).join()
+
+ def navigate_forest(self, confidence):
+ """Navigate through forest"""
+ self.log("Moving through forest...")
+ # Rise slightly and move slowly
+ self.client.moveByVelocityAsync(1.5, 0, -0.5, 2).join()
+
+ def navigate_road(self, confidence):
+ """Navigate along road"""
+ self.log("Following road...")
+ # Move at normal speed
+ self.client.moveByVelocityAsync(3, 0, 0, 3).join()
+
+ def navigate_sky(self, confidence):
+ """Navigate in open sky"""
+ self.log("Open sky, normal flight...")
+ # Move faster
+ self.client.moveByVelocityAsync(4, 0, 0, 3).join()
+
+ def navigate_water(self, confidence):
+ """Avoid water"""
+ self.log("Avoiding water area...")
+ # Rise immediately
+ self.client.moveByVelocityAsync(0, 0, -2, 1).join()
+ # Move backward
+ self.client.moveByVelocityAsync(-2, 0, 0, 2).join()
+
+ def navigate_fire(self, confidence):
+ """Navigate fire emergency"""
+ self.log("Fire detected! Emergency response...")
+ self.emergency = True
+ # Emergency ascent
+ self.client.moveToZAsync(-30, 5).join()
+ # Send alert
+ self.log("Fire alert!")
+
+ def navigate_animal(self, confidence):
+ """Navigate around animals"""
+ self.log("Animal detected, keeping distance...")
+ # Hover and observe
+ self.client.hoverAsync().join()
+ time.sleep(3)
+ # Move back slowly
+ self.client.moveByVelocityAsync(-1, 0, 0, 2).join()
+
+ def navigate_vehicle(self, confidence):
+ """Navigate around vehicles"""
+ self.log("Vehicle detected, following...")
+ # Follow at distance
+ self.client.moveByVelocityAsync(2, 0, 0, 2).join()
+
+ def navigate_default(self, confidence):
+ """Default navigation"""
+ self.log("Unknown environment, conservative exploration...")
+ # Move slowly
+ self.client.moveByVelocityAsync(1, 0, 0, 2).join()
+
+ def display_image(self, image, environment, confidence):
+ """Display image with information"""
+ if image is None:
+ return
+
+ # Create display image
+ display_img = image.copy()
+ height, width = display_img.shape[:2]
+
+ # Add environment info
+ env_text = f"Env: {environment}"
+ conf_text = f"Conf: {confidence:.1%}"
+ bat_text = f"Battery: {self.battery:.1f}%"
+
+ # Set text color
+ color = (0, 255, 0) # Green
+ if confidence < 0.6:
+ color = (0, 255, 255) # Yellow
+ if confidence < 0.4:
+ color = (0, 0, 255) # Red
+
+ # Add text to image
+ cv2.putText(display_img, env_text, (20, 40),
+ cv2.FONT_HERSHEY_SIMPLEX, 1.2, color, 3)
+ cv2.putText(display_img, conf_text, (20, 80),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2)
+ cv2.putText(display_img, bat_text, (20, 110),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
+
+ # Add timestamp
+ timestamp = datetime.now().strftime("%H:%M:%S")
+ cv2.putText(display_img, timestamp, (width - 150, 40),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
+
+ # Show image
+ cv2.imshow('Drone Vision System', display_img)
+
+ def run_mission(self, mission_time=300):
+ """Run main mission loop"""
+ if not self.client:
+ print("Cannot run: Not connected to simulator")
+ return
+
+ self.clear_screen()
+ print("=" * 70)
+ print("STARTING DRONE MISSION")
+ print(f"Mission time: {mission_time} seconds")
+ print("=" * 70)
+ print("CONTROLS:")
+ print(" Q - Quit program")
+ print(" L - Manual landing")
+ print(" R - Return home")
+ print(" S - Save current image")
+ print(" P - Pause/Resume")
+ print("-" * 70)
+
+ # Take off
+ if not self.takeoff():
+ return
+
+ self.running = True
+ start_time = time.time()
+ frame_count = 0
+ last_env = "Unknown"
+
+ try:
+ while self.running and (time.time() - start_time) < mission_time:
+ frame_count += 1
+
+ # Update battery
+ self.update_battery()
+
+ # Check emergency
+ if self.emergency and self.flying:
+ self.log("Emergency! Returning home...")
+ self.client.moveToPositionAsync(0, 0, -20, 5).join()
+ self.land()
+ break
+
+ # Capture image
+ image = self.capture_image()
+
+ if image is not None:
+ # Analyze environment
+ try:
+ environment, confidence = self.analyze_environment(image)
+ last_env = environment
+ self.current_env = environment
+ self.env_confidence = confidence
+ except Exception as e:
+ self.log(f"Environment analysis failed: {str(e)}")
+ environment, confidence = "Unknown", 0.0
+ self.current_env = "Unknown"
+ self.env_confidence = 0.0
+
+ # Save data
+ if confidence > 0.7 or frame_count % 5 == 0:
+ self.save_image_data(image, environment, confidence)
+
+ # Display image
+ self.display_image(image, environment, confidence)
+
+ # Navigation decision
+ try:
+ self.navigate(environment, confidence)
+ except Exception as e:
+ self.log(f"Navigation error: {str(e)}")
+
+ # Show status (每20帧更新一次,减少屏幕闪烁)
+ if frame_count % 20 == 0:
+ elapsed = int(time.time() - start_time)
+ remaining = mission_time - elapsed
+ self.clear_screen()
+ print(f"MISSION IN PROGRESS...")
+ print(f"Elapsed: {elapsed} sec | Remaining: {remaining} sec")
+ print(f"Environment: {last_env} ({self.env_confidence:.1%})")
+ print(f"Battery: {self.battery:.1f}%")
+ print(f"Frames processed: {frame_count}")
+ print("-" * 40)
+ print("CONTROLS: Q-Quit, L-Land, R-Return, S-Save, P-Pause")
+
+ # Check keyboard input
+ key = cv2.waitKey(30) & 0xFF
+ if key == ord('q'):
+ self.log("User quit")
+ break
+ elif key == ord('l'):
+ self.land()
+ break
+ elif key == ord('r'):
+ self.log("Manual return home")
+ self.client.moveToPositionAsync(0, 0, -20, 5).join()
+ self.land()
+ break
+ elif key == ord('s'):
+ if image is not None:
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ filename = f"debug/snapshot_{timestamp}.jpg"
+ cv2.imwrite(filename, image)
+ self.log(f"Saved snapshot: {filename}")
+ elif key == ord('p'):
+ self.running = not self.running
+ status = "Paused" if not self.running else "Resumed"
+ self.log(f"Mission {status}")
+
+ # Small delay to control processing speed
+ time.sleep(0.1) # 从0.5秒减少到0.1秒,提高响应性
+
+ # Mission complete
+ self.log("Mission complete!")
+
+ except KeyboardInterrupt:
+ self.log("Mission interrupted by user (Ctrl+C)")
+ print("\nMission interrupted. Landing drone...")
+ except Exception as e:
+ self.log(f"Runtime error: {str(e)}")
+ finally:
+ # Cleanup
+ if self.flying:
+ try:
+ self.land()
+ except:
+ pass
+ cv2.destroyAllWindows()
+ try:
+ self.generate_report()
+ except:
+ pass
+
+ def generate_report(self):
+ """Generate mission report"""
+ report = {
+ "mission": "Drone Vision Navigation",
+ "date": datetime.now().strftime("%Y-%m-%d"),
+ "time": datetime.now().strftime("%H:%M:%S"),
+ "summary": {
+ "battery_final": round(self.battery, 1),
+ "environment_detected": self.current_env,
+ "emergency_activated": self.emergency
+ },
+ "files": {
+ "images": "data/images/",
+ "logs": "data/logs/",
+ "debug": "debug/"
+ }
+ }
+
+ report_file = f"data/logs/report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
+
+ try:
+ with open(report_file, 'w') as f:
+ json.dump(report, f, indent=2)
+ self.log(f"Report saved: {report_file}")
+ except Exception as e:
+ self.log(f"Report save failed: {str(e)}")
+
+
+class EnvironmentClassifier:
+ """Environment classifier for abandoned park"""
+
+ def __init__(self):
+ self.environments = [
+ "Ruins", "Building", "Forest", "Road",
+ "Sky", "Water", "Fire", "Animal", "Vehicle"
]
+
+ # Feature weights for abandoned park
+ self.weights = {
+ "Ruins": 0.35, # Highest probability for ruins
+ "Building": 0.20,
+ "Forest": 0.15,
+ "Road": 0.10,
+ "Sky": 0.08,
+ "Water": 0.05, # Lower water weight
+ "Fire": 0.02,
+ "Animal": 0.03,
+ "Vehicle": 0.02
+ }
+
+ def classify(self, image):
+ """Classify image"""
+ if image is None:
+ return "Unknown", 0.0
+
+ # Extract features
+ features = self.extract_features(image)
+
+ # Rule-based classification
+ env, conf = self.rule_based(features)
+
+ # If rules unclear, use weighted random
+ if env == "Unknown":
+ env, conf = self.weighted_random(features)
+
+ return env, conf
+
+ def extract_features(self, image):
+ """Extract image features"""
+ features = {}
+
+ height, width = image.shape[:2]
+
+ # Color features
+ hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
+
+ # Blue regions
+ blue_low = np.array([100, 50, 50])
+ blue_high = np.array([130, 255, 255])
+ blue_mask = cv2.inRange(hsv, blue_low, blue_high)
+ features['blue_ratio'] = np.sum(blue_mask > 0) / (height * width)
+
+ # Green regions
+ green_low = np.array([40, 50, 50])
+ green_high = np.array([80, 255, 255]) # 修复:添加 np.
+ green_mask = cv2.inRange(hsv, green_low, green_high)
+ features['green_ratio'] = np.sum(green_mask > 0) / (height * width)
+
+ # Red regions
+ red_low1 = np.array([0, 50, 50])
+ red_high1 = np.array([10, 255, 255])
+ red_low2 = np.array([170, 50, 50])
+ red_high2 = np.array([180, 255, 255])
+ red_mask1 = cv2.inRange(hsv, red_low1, red_high1)
+ red_mask2 = cv2.inRange(hsv, red_low2, red_high2)
+ red_mask = cv2.bitwise_or(red_mask1, red_mask2)
+ features['red_ratio'] = np.sum(red_mask > 0) / (height * width)
+
+ # Texture features
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
+ edges = cv2.Canny(gray, 50, 150)
+ features['edge_density'] = np.sum(edges > 0) / (height * width)
+ features['gray_variance'] = np.var(gray)
+ features['brightness'] = np.mean(gray)
+
+ # Sky detection
+ if height > 10:
+ top_blue = np.sum(blue_mask[:height//3, :] > 0) / (width * height//3)
+ bottom_blue = np.sum(blue_mask[2*height//3:, :] > 0) / (width * height//3)
+ features['sky_ratio'] = top_blue
+ features['is_sky'] = top_blue > 0.3 and top_blue > bottom_blue * 2
+
+ return features
+
+ def rule_based(self, features):
+ """Rule-based classification"""
+ blue = features.get('blue_ratio', 0)
+ green = features.get('green_ratio', 0)
+ red = features.get('red_ratio', 0)
+ edges = features.get('edge_density', 0)
+ variance = features.get('gray_variance', 0)
+ bright = features.get('brightness', 0)
+ is_sky = features.get('is_sky', False)
+
+ # Rules for abandoned park
+ if edges > 0.06 and variance > 1000:
+ return "Ruins", 0.85
+
+ if green > 0.25:
+ return "Forest", 0.80
+
+ if blue > 0.3:
+ if is_sky:
+ return "Sky", 0.75
+ else:
+ return "Water", 0.65
+
+ if edges < 0.03 and 100 < bright < 180:
+ return "Road", 0.70
+
+ if red > 0.15:
+ if bright > 180:
+ return "Fire", 0.60
+ else:
+ return "Ruins", 0.65
+
+ if edges > 0.04 and bright < 120:
+ return "Building", 0.75
+
+ return "Unknown", 0.0
+
+ def weighted_random(self, features):
+ """Weighted random selection"""
+ # Adjust weights based on features
+ adj_weights = self.weights.copy()
+
+ blue = features.get('blue_ratio', 0)
+ green = features.get('green_ratio', 0)
+ edges = features.get('edge_density', 0)
+
+ # Adjust based on features
+ if blue > 0.2:
+ adj_weights["Sky"] *= 1.5
+ if blue > 0.3:
+ adj_weights["Water"] *= 0.5 # Reduce water weight
+
+ if green > 0.15:
+ adj_weights["Forest"] *= 2.0
+
+ if edges > 0.05:
+ adj_weights["Ruins"] *= 1.8
+
+ # Normalize
+ total = sum(adj_weights.values())
+ if total > 0:
+ probs = [adj_weights[env]/total for env in self.environments]
+ else:
+ probs = [1/len(self.environments)] * len(self.environments)
+
+ # Random selection
+ env_idx = random.choices(range(len(self.environments)), weights=probs)[0]
+ env = self.environments[env_idx]
+
+ # Calculate confidence
+ base_conf = 0.6
+
+ # Increase confidence based on features
+ if env == "Ruins" and edges > 0.04:
+ base_conf += 0.15
+ elif env == "Forest" and green > 0.15:
+ base_conf += 0.1
+ elif env == "Sky" and blue > 0.2:
+ base_conf += 0.1
+
+ conf = min(base_conf, 0.9)
+
+ return env, conf
+
+
+def safe_input(prompt):
+ """Safe input function with KeyboardInterrupt handling"""
+ try:
+ return input(prompt).strip()
+ except KeyboardInterrupt:
+ print("\n\nOperation cancelled by user (Ctrl+C)")
+ print("Exiting program...")
+ sys.exit(0)
- # 逐行绘制状态信息到图像上
- y_offset = 30 # 文字起始y坐标
- for line in info_lines:
- cv2.putText(frame, line, (10, y_offset),
- cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1)
- y_offset += 25 # 每行文字间隔25像素
-
- # 6. 显示处理后的图像
- cv2.imshow('无人机视觉导航', frame)
-
- # 7. 检查键盘输入
- key = cv2.waitKey(1) & 0xFF # 等待1毫秒并获取按键
- if key == ord('q'): # 按 'q' 键退出程序
- print("\n用户请求退出...")
- break
- elif key == ord('s'): # 按 's' 键保存当前图像
- filename = f"data/images/capture_{frame_count}.jpg"
- cv2.imwrite(filename, frame)
- print(f"✓ 保存图像: {filename}")
-
- # 8. 检查电池电量(安全条件)
- if battery <= 0:
- print("\n⚠️ 电池耗尽!紧急降落...")
- break
-
- # 9. 检查飞行时间限制(可选安全限制)
- if flight_time > 60: # 运行60秒后自动停止(演示用途)
- print("\n⏰ 飞行时间到,安全降落...")
- break
-
- # 10. 清理资源
- drone_cam.release() # 释放摄像头
- cv2.destroyAllWindows() # 关闭所有OpenCV窗口
-
- # 11. 打印飞行统计信息
- print("\n" + "=" * 50)
- print("飞行统计:")
- print(f"- 总飞行时间: {flight_time:.1f} 秒")
- print(f"- 处理帧数: {frame_count}")
- # 计算平均帧率(FPS)
- if flight_time > 0:
- print(f"- 平均帧率: {frame_count / flight_time:.1f} FPS")
- else:
- print("- 平均帧率: N/A")
- print(f"- 最终电池: {battery:.1f}%")
- print("=" * 50)
- print("\n飞行结束!")
- return 0 # 返回成功状态码
+def main():
+ """Main function with improved exception handling"""
+ try:
+ print("DRONE VISION NAVIGATION SYSTEM")
+ print("-" * 50)
+
+ # Create system
+ drone = DroneVisionSystem()
+
+ if drone.client is None:
+ print("Cannot connect to simulator, exiting")
+ return
+
+ # User configuration
+ print("\nMISSION CONFIGURATION:")
+ print("1. Test mode (60 seconds)")
+ print("2. Short mission (5 minutes)")
+ print("3. Long mission (10 minutes)")
+ print("4. Custom time")
+
+ choice = safe_input("\nSelect mode (1-4): ")
+
+ if choice == '1':
+ mission_time = 60
+ elif choice == '2':
+ mission_time = 300
+ elif choice == '3':
+ mission_time = 600
+ elif choice == '4':
+ try:
+ time_input = safe_input("Enter mission time (seconds): ")
+ mission_time = int(time_input)
+ except ValueError:
+ mission_time = 300
+ print(f"Invalid input, using default: {mission_time} seconds")
+ else:
+ mission_time = 300
+ print(f"Using default: {mission_time} seconds")
+
+ print(f"\nMISSION SETTINGS:")
+ print(f" Time: {mission_time} seconds ({mission_time/60:.1f} minutes)")
+ print(f" Image saving: Auto")
+ print(f" Logging: Enabled")
+
+ confirm = safe_input("\nStart mission? (y/n): ").lower()
+
+ if confirm == 'y':
+ drone.run_mission(mission_time)
+ else:
+ print("Mission cancelled")
+
+ print("\nProgram ended gracefully")
+
+ except KeyboardInterrupt:
+ print("\n\nProgram interrupted by user (Ctrl+C)")
+ print("Exiting...")
+ except Exception as e:
+ print(f"\nUnexpected error: {str(e)}")
+ print("Please check your setup and try again.")
-# Python标准入口点
if __name__ == "__main__":
- try:
- exit_code = main() # 运行主函数
- except KeyboardInterrupt: # 处理Ctrl+C中断
- print("\n程序被用户中断")
- exit_code = 0
- except Exception as e: # 处理其他异常
- print(f"\n程序出错: {e}")
- exit_code = 1
-
- # 等待用户确认退出(便于查看输出结果)
- input("\n按 Enter 键退出...")
- sys.exit(exit_code) # 退出程序
\ No newline at end of file
+ main()
\ No newline at end of file
diff --git a/src/UAV_navigation_system/start_fixed.bat b/src/UAV_navigation_system/start_fixed.bat
new file mode 100644
index 0000000000..58693749b2
--- /dev/null
+++ b/src/UAV_navigation_system/start_fixed.bat
@@ -0,0 +1,56 @@
+@echo off
+cls
+
+echo ============================================
+echo DRONE VISION NAVIGATION SYSTEM
+echo ============================================
+echo FIXED VERSION - KeyboardInterrupt Handled
+echo ============================================
+echo.
+
+echo Step 1: Checking simulator...
+tasklist | findstr "AbandonedPark.exe" > nul
+if errorlevel 1 (
+ echo Simulator not running!
+ echo Please run AbandonedPark.exe manually
+ echo Waiting 20 seconds...
+ timeout /t 20 /nobreak > nul
+) else (
+ echo ✓ Simulator is running
+)
+
+echo.
+echo Step 2: Activating Python environment...
+if exist "venv\Scripts\activate.bat" (
+ call venv\Scripts\activate.bat
+ echo ✓ Virtual environment activated
+) else (
+ echo Creating virtual environment...
+ python -m venv venv
+ call venv\Scripts\activate.bat
+ echo Installing dependencies...
+ pip install airsim opencv-python numpy
+)
+
+echo.
+echo Step 3: Checking Python packages...
+python -c "import airsim; print('✓ AirSim installed')" 2>nul
+if errorlevel 1 (
+ echo Installing AirSim...
+ pip install airsim
+)
+
+python -c "import cv2; print('✓ OpenCV installed')" 2>nul
+if errorlevel 1 (
+ echo Installing OpenCV...
+ pip install opencv-python
+)
+
+echo.
+echo Step 4: Starting drone system...
+echo Note: Press Ctrl+C at any time to safely exit
+echo.
+python drone_vision_system_en_fixed.py
+
+echo.
+pause
\ No newline at end of file
diff --git a/src/UAV_navigation_system/test_airsim.py b/src/UAV_navigation_system/test_airsim.py
new file mode 100644
index 0000000000..8a23a4dd4b
--- /dev/null
+++ b/src/UAV_navigation_system/test_airsim.py
@@ -0,0 +1,10 @@
+import airsim
+import sys
+
+print("Python 版本:", sys.version)
+print("AirSim 版本:", airsim.__version__)
+print("AirSim 导入成功!")
+
+# 检查客户端是否可以创建
+client = airsim.MultirotorClient()
+print("客户端创建成功")
\ No newline at end of file