diff --git a/distance-judgement/.idea/.gitignore b/distance-judgement/.idea/.gitignore
new file mode 100644
index 00000000..f649f0f6
--- /dev/null
+++ b/distance-judgement/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/distance-judgement/.idea/inspectionProfiles/profiles_settings.xml b/distance-judgement/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 00000000..105ce2da
--- /dev/null
+++ b/distance-judgement/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/distance-judgement/.idea/misc.xml b/distance-judgement/.idea/misc.xml
new file mode 100644
index 00000000..2e9d95a7
--- /dev/null
+++ b/distance-judgement/.idea/misc.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/distance-judgement/.idea/modules.xml b/distance-judgement/.idea/modules.xml
new file mode 100644
index 00000000..b7e21735
--- /dev/null
+++ b/distance-judgement/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/distance-judgement/.idea/pythonProject2.iml b/distance-judgement/.idea/pythonProject2.iml
new file mode 100644
index 00000000..d0876a78
--- /dev/null
+++ b/distance-judgement/.idea/pythonProject2.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/distance-judgement/CAMERA_ICON_OVERLAP_FIX.md b/distance-judgement/CAMERA_ICON_OVERLAP_FIX.md
new file mode 100644
index 00000000..20f9893f
--- /dev/null
+++ b/distance-judgement/CAMERA_ICON_OVERLAP_FIX.md
@@ -0,0 +1,137 @@
+# 摄像头图标重叠问题修复报告 🔧
+
+## 问题描述
+
+在摄像头图标更新时,没有清除之前的图标,导致地图上出现图标重叠的现象。
+
+## 问题根源分析
+
+### 1. 固定摄像头视野扇形重叠
+- **问题位置**: `src/web_server.py` 第3730行附近
+- **原因**: 摄像头位置更新时,只更新了`cameraMarker`的位置,但没有同步更新`fixedCameraFOV`视野扇形
+- **表现**: 旧的视野扇形仍然显示在原位置,新的视野扇形在新位置,造成重叠
+
+### 2. 移动设备朝向标记重叠
+- **问题位置**: `src/web_server.py` 第2491行附近
+- **原因**: 移动设备朝向更新时,`orientationMarker`是复合对象(包含`deviceMarker`和`viewSector`),但只简单调用了`map.remove()`
+- **表现**: 设备标记和视野扇形没有被完全清除,导致重叠
+
+### 3. 变量作用域问题
+- **问题位置**: `src/web_server.py` 第1647行
+- **原因**: `fixedCameraFOV`使用`const`声明,无法在其他函数中重新赋值
+- **影响**: 摄像头位置更新函数无法更新全局视野扇形引用
+
+## 修复内容
+
+### ✅ 修复1:自动配置时的视野扇形同步更新
+```javascript
+// 🔧 修复:同步更新视野扇形位置,避免图标重叠
+if (fixedCameraFOV) {
+ // 移除旧的视野扇形
+ map.remove(fixedCameraFOV);
+
+ // 重新创建视野扇形在新位置
+ const newFOV = createGeographicSector(
+ lng, lat,
+ result.data.camera_heading || config.CAMERA_HEADING,
+ config.CAMERA_FOV,
+ 100, // 100米检测范围
+ '#2196F3' // 蓝色,与固定摄像头标记颜色匹配
+ );
+ map.add(newFOV);
+
+ // 更新全局变量引用
+ fixedCameraFOV = newFOV;
+}
+```
+
+### ✅ 修复2:手动配置时的视野扇形同步更新
+```javascript
+// 🔧 修复:手动配置时也要同步更新视野扇形
+// 同步更新视野扇形
+if (fixedCameraFOV) {
+ map.remove(fixedCameraFOV);
+
+ const newFOV = createGeographicSector(
+ lng, lat, heading, config.CAMERA_FOV,
+ 100, '#2196F3'
+ );
+ map.add(newFOV);
+ fixedCameraFOV = newFOV;
+}
+```
+
+### ✅ 修复3:移动设备朝向标记的正确清除
+```javascript
+// 🔧 修复:正确移除旧的视野扇形标记,避免重叠
+if (mobileDeviceMarkers[deviceId].orientationMarker) {
+ // orientationMarker是一个复合对象,包含deviceMarker和viewSector
+ const oldOrientation = mobileDeviceMarkers[deviceId].orientationMarker;
+ if (oldOrientation.deviceMarker) {
+ map.remove(oldOrientation.deviceMarker);
+ }
+ if (oldOrientation.viewSector) {
+ map.remove(oldOrientation.viewSector);
+ }
+}
+```
+
+### ✅ 修复4:变量作用域调整
+```javascript
+// 将 const 改为 var,允许重新赋值
+var fixedCameraFOV = createGeographicSector(...);
+```
+
+## 测试验证
+
+修复后,以下操作不再出现图标重叠:
+
+1. **自动配置摄像头位置** - 视野扇形会同步移动到新位置
+2. **手动配置摄像头位置** - 视野扇形会同步更新位置和朝向
+3. **移动设备朝向更新** - 旧的设备标记和视野扇形会被完全清除
+4. **摄像头朝向变更** - 视野扇形会反映新的朝向角度
+
+## 影响范围
+
+✅ **已修复的功能**:
+- 固定摄像头位置更新
+- 固定摄像头朝向更新
+- 移动设备位置更新
+- 移动设备朝向更新
+- 手动配置摄像头
+
+✅ **无影响的功能**:
+- 人员检测标记更新(原本就有正确的清除逻辑)
+- 远程设备标记更新(原本就有正确的清除逻辑)
+- 其他地图功能
+
+## 技术细节
+
+- **修改文件**: `src/web_server.py`
+- **修改行数**: 约15行代码修改
+- **兼容性**: 完全向后兼容,不影响现有功能
+- **性能影响**: 无负面影响,实际上减少了地图上的冗余元素
+
+## 📝 补充修复:重复无人机图标问题
+
+### 问题描述
+用户反映地图上出现了2个无人机图标,但应该只有1个无人机图标和1个电脑图标。
+
+### 根源分析
+移动设备同时显示了两个独立的🚁标记:
+- `locationMarker`:GPS位置标记
+- `orientationMarker`:朝向标记(包含视野扇形)
+
+### ✅ 修复方案
+1. **移除重复的位置标记**:删除独立的`locationMarker`
+2. **合并功能到朝向标记**:朝向标记同时承担位置和朝向显示
+3. **更新清除逻辑**:移除对`locationMarker`的引用
+4. **添加数据缓存**:为点击事件提供设备数据支持
+
+### 🎯 修复后的效果
+- **固定摄像头(电脑端)**:💻电脑图标 + 蓝色视野扇形
+- **移动设备(移动端)**:🚁无人机图标 + 朝向箭头 + 橙色视野扇形
+
+## 总结
+
+通过这次修复,彻底解决了摄像头图标重叠的问题,确保地图上的标记状态与实际配置始终保持一致,提升了用户体验。同时解决了重复无人机图标的问题,让图标显示更加清晰和直观。
\ No newline at end of file
diff --git a/distance-judgement/CAMERA_ORIENTATION_GUIDE.md b/distance-judgement/CAMERA_ORIENTATION_GUIDE.md
new file mode 100644
index 00000000..4c628189
--- /dev/null
+++ b/distance-judgement/CAMERA_ORIENTATION_GUIDE.md
@@ -0,0 +1,236 @@
+# 摄像头朝向自动配置功能指南 🧭
+
+## 功能概述
+
+本系统现在支持自动获取设备位置和朝向,将本地摄像头设置为面朝使用者,实现智能的摄像头配置。
+
+## 🎯 主要功能
+
+### 1. 自动GPS定位
+- **Windows系统**: 使用Windows Location API获取精确GPS位置
+- **其他系统**: 使用IP地理定位作为备选方案
+- **精度**: GPS可达10米内,IP定位约10公里
+
+### 2. 设备朝向检测
+- **桌面设备**: 使用默认朝向算法(假设用户面向屏幕)
+- **移动设备**: 支持陀螺仪和磁力计朝向检测
+- **智能计算**: 自动计算摄像头应该面向用户的角度
+
+### 3. 自动配置应用
+- **实时更新**: 自动更新配置文件和运行时参数
+- **地图同步**: 自动更新地图上的摄像头位置标记
+- **即时生效**: 配置立即应用到距离计算和人员定位
+
+## 🚀 使用方法
+
+### 方法一:启动时自动配置
+
+```bash
+python main_web.py
+```
+
+系统会检测到默认配置并询问是否自动配置:
+```
+🤖 检测到摄像头使用默认配置
+ 是否要自动配置摄像头位置和朝向?
+ • 输入 'y' - 立即自动配置
+ • 输入 'n' - 跳过,使用Web界面配置
+ • 直接回车 - 跳过自动配置
+
+🔧 请选择 (y/n/回车): y
+```
+
+### 方法二:独立配置工具
+
+```bash
+# 完整自动配置
+python tools/auto_configure_camera.py
+
+# 仅测试GPS功能
+python tools/auto_configure_camera.py --test-gps
+
+# 仅测试朝向功能
+python tools/auto_configure_camera.py --test-heading
+```
+
+### 方法三:Web界面配置
+
+1. 启动Web服务器:`python main_web.py`
+2. 打开浏览器访问 `https://127.0.0.1:5000`
+3. 在"🧭 自动位置配置"面板中:
+ - 点击"📍 获取位置"按钮
+ - 点击"🧭 获取朝向"按钮
+ - 点击"🤖 自动配置摄像头"按钮
+
+## 📱 Web界面功能详解
+
+### GPS位置获取
+```javascript
+// 使用浏览器Geolocation API
+navigator.geolocation.getCurrentPosition()
+```
+
+**支持的浏览器**:
+- ✅ Chrome/Edge (推荐)
+- ✅ Firefox
+- ✅ Safari
+- ❌ IE (不支持)
+
+**权限要求**:
+- 首次使用需要授权位置权限
+- HTTPS环境下精度更高
+- 室外环境GPS信号更好
+
+### 设备朝向检测
+```javascript
+// 使用设备朝向API
+window.addEventListener('deviceorientation', handleOrientation)
+```
+
+**支持情况**:
+- 📱 **移动设备**: 完全支持(手机、平板)
+- 💻 **桌面设备**: 有限支持(使用算法估算)
+- 🍎 **iOS 13+**: 需要明确请求权限
+
+## ⚙️ 技术实现
+
+### 后端模块
+
+#### 1. OrientationDetector (`src/orientation_detector.py`)
+- GPS位置获取(多平台支持)
+- 设备朝向检测
+- 摄像头朝向计算
+- 配置文件更新
+
+#### 2. WebOrientationDetector (`src/web_orientation_detector.py`)
+- Web API接口
+- 前后端数据同步
+- 实时状态管理
+
+### 前端功能
+
+#### JavaScript函数
+- `requestGPSPermission()` - GPS权限请求
+- `requestOrientationPermission()` - 朝向权限请求
+- `autoConfigureCamera()` - 自动配置执行
+- `manualConfiguration()` - 手动配置入口
+
+#### API接口
+- `POST /api/orientation/auto_configure` - 自动配置
+- `POST /api/orientation/update_location` - 更新GPS
+- `POST /api/orientation/update_heading` - 更新朝向
+- `GET /api/orientation/get_status` - 获取状态
+
+## 🔧 配置原理
+
+### 朝向计算逻辑
+
+```python
+def calculate_camera_heading_facing_user(self, user_heading: float) -> float:
+ """
+ 计算摄像头朝向用户的角度
+ 摄像头朝向 = (用户朝向 + 180°) % 360°
+ """
+ camera_heading = (user_heading + 180) % 360
+ return camera_heading
+```
+
+### 坐标转换
+
+```python
+def calculate_person_position(self, pixel_x, pixel_y, distance, frame_width, frame_height):
+ """
+ 基于摄像头位置、朝向和距离计算人员GPS坐标
+ 使用球面几何学进行精确计算
+ """
+ # 像素到角度转换
+ horizontal_angle_per_pixel = self.camera_fov / frame_width
+ horizontal_offset_degrees = (pixel_x - center_x) * horizontal_angle_per_pixel
+
+ # 计算实际方位角
+ person_bearing = (self.camera_heading + horizontal_offset_degrees) % 360
+
+ # 球面坐标计算
+ person_lat, person_lng = self._calculate_destination_point(
+ self.camera_lat, self.camera_lng, distance, person_bearing
+ )
+```
+
+## 📋 系统要求
+
+### 环境要求
+- Python 3.7+
+- 现代Web浏览器
+- 网络连接(GPS定位需要)
+
+### Windows特别要求
+```bash
+# 安装Windows位置服务支持
+pip install winrt-runtime winrt-Windows.Devices.Geolocation
+```
+
+### 移动设备要求
+- HTTPS访问(GPS权限要求)
+- 现代移动浏览器
+- 设备朝向传感器支持
+
+## 🔍 故障排除
+
+### GPS获取失败
+**常见原因**:
+- 位置权限被拒绝
+- 网络连接问题
+- GPS信号不佳
+
+**解决方案**:
+1. 检查浏览器位置权限设置
+2. 移动到室外或窗边
+3. 使用IP定位作为备选
+4. 手动输入坐标
+
+### 朝向检测失败
+**常见原因**:
+- 设备不支持朝向传感器
+- 浏览器兼容性问题
+- 权限被拒绝
+
+**解决方案**:
+1. 使用支持的移动设备
+2. 更新到现代浏览器
+3. 允许设备朝向权限
+4. 使用手动配置
+
+### 配置不生效
+**可能原因**:
+- 配置文件写入失败
+- 权限不足
+- 模块导入错误
+
+**解决方案**:
+1. 检查文件写入权限
+2. 重启应用程序
+3. 查看控制台错误信息
+
+## 💡 使用建议
+
+### 最佳实践
+1. **首次配置**: 使用Web界面进行配置,可视化效果更好
+2. **定期更新**: 位置变化时重新配置
+3. **精度要求**: GPS环境下精度更高,室内可用IP定位
+4. **设备选择**: 移动设备朝向检测更准确
+
+### 注意事项
+1. **隐私保护**: GPS数据仅用于本地配置,不会上传
+2. **网络要求**: 初次配置需要网络连接
+3. **兼容性**: 老旧浏览器可能不支持某些功能
+4. **精度限制**: 桌面设备朝向检测精度有限
+
+## 📚 相关文档
+
+- [MAP_USAGE_GUIDE.md](MAP_USAGE_GUIDE.md) - 地图功能使用指南
+- [MOBILE_GUIDE.md](MOBILE_GUIDE.md) - 移动端使用指南
+- [HTTPS_SETUP.md](HTTPS_SETUP.md) - HTTPS配置指南
+
+---
+
+🎯 **快速开始**: 运行 `python main_web.py`,选择自动配置,享受智能的摄像头定位体验!
\ No newline at end of file
diff --git a/distance-judgement/HTTPS_SETUP.md b/distance-judgement/HTTPS_SETUP.md
new file mode 100644
index 00000000..9eca3614
--- /dev/null
+++ b/distance-judgement/HTTPS_SETUP.md
@@ -0,0 +1,99 @@
+# 🔒 HTTPS设置指南
+
+## 概述
+本系统已升级支持HTTPS,解决摄像头权限问题。现代浏览器要求HTTPS才能访问摄像头等敏感设备。
+
+## 🚀 快速启动
+
+### 方法一:自动设置(推荐)
+1. 在PyCharm中打开项目
+2. 直接运行 `main_web.py`
+3. 系统会自动生成SSL证书并启动HTTPS服务器
+
+### 方法二:手动安装依赖
+如果遇到cryptography库缺失:
+```bash
+pip install cryptography
+```
+
+## 📱 访问地址
+
+启动后访问地址已升级为HTTPS:
+- **本地访问**: https://127.0.0.1:5000
+- **手机访问**: https://你的IP:5000/mobile/mobile_client.html
+
+## 🔑 浏览器安全警告处理
+
+### 桌面浏览器
+1. 访问 https://127.0.0.1:5000
+2. 看到"您的连接不是私密连接"警告
+3. 点击 **"高级"**
+4. 点击 **"继续访问localhost(不安全)"**
+5. 正常使用
+
+### 手机浏览器
+1. 访问 https://你的IP:5000/mobile/mobile_client.html
+2. 出现安全警告时,点击 **"高级"** 或 **"详细信息"**
+3. 选择 **"继续访问"** 或 **"继续前往此网站"**
+4. 正常使用摄像头功能
+
+## 📂 文件结构
+
+新增文件:
+```
+ssl/
+├── cert.pem # SSL证书文件
+└── key.pem # 私钥文件
+```
+
+## 🔧 技术说明
+
+### SSL证书特性
+- **类型**: 自签名证书
+- **有效期**: 365天
+- **支持域名**: localhost, 127.0.0.1
+- **算法**: RSA-2048, SHA-256
+
+### 摄像头权限要求
+- ✅ HTTPS环境 - 支持摄像头访问
+- ❌ HTTP环境 - 浏览器阻止摄像头访问
+- ⚠️ localhost - HTTP也可以,但IP访问必须HTTPS
+
+## 🐛 故障排除
+
+### 问题1: cryptography库安装失败
+```bash
+# Windows
+pip install --upgrade pip
+pip install cryptography
+
+# 如果还是失败,尝试:
+pip install --only-binary=cryptography cryptography
+```
+
+### 问题2: 证书生成失败
+1. 检查ssl目录权限
+2. 重新运行程序,会自动重新生成
+
+### 问题3: 手机无法访问
+1. 确保手机和电脑在同一网络
+2. 检查防火墙设置
+3. 在手机浏览器中接受安全证书
+
+### 问题4: 摄像头仍然无法访问
+1. 确认使用HTTPS访问
+2. 检查浏览器摄像头权限设置
+3. 尝试不同浏览器(Chrome、Firefox等)
+
+## 📋 更新日志
+
+### v2.0 - HTTPS升级
+- ✅ 自动SSL证书生成
+- ✅ 完整HTTPS支持
+- ✅ 摄像头权限兼容
+- ✅ 手机端HTTPS支持
+- ✅ 浏览器安全警告处理指南
+
+## 🎯 下一步
+
+完成HTTPS升级后,您的移动端摄像头功能将完全正常工作,不再受到浏览器安全限制的影响。
\ No newline at end of file
diff --git a/distance-judgement/MAP_USAGE_GUIDE.md b/distance-judgement/MAP_USAGE_GUIDE.md
new file mode 100644
index 00000000..bce1849f
--- /dev/null
+++ b/distance-judgement/MAP_USAGE_GUIDE.md
@@ -0,0 +1,165 @@
+# 地图功能使用指南 🗺️
+
+## 功能概述
+
+本系统集成了高德地图API,可以实时在地图上显示:
+- 📷 摄像头位置(蓝色标记)
+- 👥 检测到的人员位置(红色标记)
+- 📏 每个人员距离摄像头的距离
+
+## 快速开始
+
+### 1. 配置摄像头位置 📍
+
+首先需要设置摄像头的地理位置:
+
+```bash
+python setup_camera_location.py
+```
+
+按提示输入:
+- 摄像头纬度(例:39.9042)
+- 摄像头经度(例:116.4074)
+- 摄像头朝向角度(0-360°,0为正北)
+- 高德API Key(可选,用于更好的地图体验)
+
+### 2. 启动系统 🚀
+
+```bash
+python main.py
+```
+
+### 3. 查看地图 🗺️
+
+在检测界面按 `m` 键打开地图,系统会自动在浏览器中显示实时地图。
+
+## 操作说明
+
+### 键盘快捷键
+
+- `q` - 退出程序
+- `c` - 距离校准模式
+- `r` - 重置为默认参数
+- `s` - 保存当前帧截图
+- `m` - 打开地图显示 🗺️
+- `h` - 设置摄像头朝向 🧭
+
+### 地图界面说明
+
+- 🔵 **蓝色标记** - 摄像头位置
+- 🔴 **红色标记** - 检测到的人员位置
+- 📊 **信息面板** - 显示系统状态和统计信息
+- ⚡ **实时更新** - 地图每3秒自动刷新一次
+
+## 坐标计算原理
+
+系统通过以下步骤计算人员的地理坐标:
+
+1. **像素坐标获取** - 从YOLO检测结果获取人体在画面中的位置
+2. **角度计算** - 根据摄像头视场角计算人相对于摄像头中心的角度偏移
+3. **方位角计算** - 结合摄像头朝向,计算人相对于正北的绝对角度
+4. **地理坐标转换** - 使用球面几何学公式,根据距离和角度计算地理坐标
+
+### 关键参数
+
+- `CAMERA_FOV` - 摄像头视场角(默认60°)
+- `CAMERA_HEADING` - 摄像头朝向角度(0°为正北)
+- 距离计算基于已校准的距离测量算法
+
+## 高德地图API配置
+
+### 获取API Key
+
+1. 访问 [高德开放平台](https://lbs.amap.com/)
+2. 注册并创建应用
+3. 获取Web服务API Key
+4. 在配置中替换 `your_gaode_api_key_here`
+
+### API使用限制
+
+- 免费配额:每日10万次调用
+- 超出配额后可能影响地图加载
+- 建议使用自己的API Key以确保稳定服务
+
+## 精度优化建议
+
+### 距离校准 📏
+
+使用 `c` 键进入校准模式:
+1. 让一个人站在已知距离处
+2. 输入实际距离
+3. 系统自动调整计算参数
+
+### 朝向校准 🧭
+
+使用 `h` 键设置准确朝向:
+1. 确定摄像头实际朝向(使用指南针)
+2. 输入角度(0°为正北,90°为正东)
+
+### 位置校准 📍
+
+确保摄像头GPS坐标准确:
+1. 使用手机GPS应用获取精确坐标
+2. 运行 `setup_camera_location.py` 更新配置
+
+## 故障排除
+
+### 地图无法打开
+
+1. 检查网络连接
+2. 确认高德API Key配置正确
+3. 尝试手动访问生成的HTML文件
+
+### 人员位置不准确
+
+1. 重新校准距离参数
+2. 检查摄像头朝向设置
+3. 确认摄像头GPS坐标准确
+
+### 地图显示异常
+
+1. 刷新浏览器页面
+2. 清除浏览器缓存
+3. 检查JavaScript控制台错误信息
+
+## 技术细节
+
+### 坐标转换公式
+
+系统使用WGS84坐标系和球面几何学公式:
+
+```python
+# 球面距离计算
+lat2 = asin(sin(lat1) * cos(d/R) + cos(lat1) * sin(d/R) * cos(bearing))
+lng2 = lng1 + atan2(sin(bearing) * sin(d/R) * cos(lat1), cos(d/R) - sin(lat1) * sin(lat2))
+```
+
+### 视场角映射
+
+```python
+# 像素到角度的转换
+horizontal_angle_per_pixel = camera_fov / frame_width
+horizontal_offset = (pixel_x - center_x) * horizontal_angle_per_pixel
+```
+
+## 系统要求
+
+- Python 3.7+
+- OpenCV 4.0+
+- 网络连接(地图加载)
+- 现代浏览器(Chrome/Firefox/Edge)
+
+## 注意事项
+
+⚠️ **重要提醒**:
+- 本系统仅供技术研究使用
+- 实际部署需要考虑隐私保护
+- GPS坐标精度影响最终定位准确性
+- 距离计算基于单目视觉,存在一定误差
+
+## 更新日志
+
+- v1.0.0 - 基础地图显示功能
+- v1.1.0 - 添加实时人员位置标记
+- v1.2.0 - 优化坐标计算精度
+- v1.3.0 - 增加配置工具和用户指南
\ No newline at end of file
diff --git a/distance-judgement/MOBILE_GUIDE.md b/distance-judgement/MOBILE_GUIDE.md
new file mode 100644
index 00000000..ec650d7c
--- /dev/null
+++ b/distance-judgement/MOBILE_GUIDE.md
@@ -0,0 +1,246 @@
+# 📱 手机连接功能使用指南
+
+## 🚁 无人机战场态势感知系统 - 手机扩展功能
+
+这个功能允许你使用手机作为移动侦察设备,将手机摄像头图像、GPS位置和设备信息实时传输到指挥中心,扩展战场态势感知能力。
+
+## 🌟 功能特性
+
+### 📡 数据传输
+- **实时视频流**: 传输手机摄像头画面到指挥中心
+- **GPS定位**: 自动获取和传输手机的精确位置
+- **设备状态**: 监控电池电量、信号强度等
+- **人体检测**: 在手机端进行AI人体检测
+- **地图集成**: 检测结果自动显示在指挥中心地图上
+
+### 🛡️ 技术特点
+- **低延迟传输**: 优化的数据压缩和传输协议
+- **自动重连**: 网络中断后自动重新连接
+- **多设备支持**: 支持多台手机同时连接
+- **跨平台兼容**: 支持Android、iOS等主流移动设备
+
+## 🚀 快速开始
+
+### 1. 启动服务端
+
+#### 方法一:使用Web模式(推荐)
+```bash
+python run.py
+# 选择 "1. Web模式"
+# 在Web界面中点击"启用手机模式"
+```
+
+#### 方法二:直接启动Web服务器
+```bash
+python main_web.py
+```
+
+### 2. 配置网络连接
+
+确保手机和电脑在同一网络环境下:
+- **局域网连接**: 连接同一WiFi网络
+- **热点模式**: 电脑开启热点,手机连接
+- **有线网络**: 电脑有线连接,手机连WiFi
+
+### 3. 获取服务器IP地址
+
+在电脑上查看IP地址:
+
+**Windows:**
+```cmd
+ipconfig
+```
+
+**Linux/Mac:**
+```bash
+ifconfig
+# 或
+ip addr show
+```
+
+记下显示的IP地址(如 192.168.1.100)
+
+### 4. 手机端连接
+
+#### 方法一:使用浏览器(推荐)
+1. 打开手机浏览器
+2. 访问 `http://[服务器IP]:5000/mobile/mobile_client.html`
+3. 例如:`http://192.168.1.100:5000/mobile/mobile_client.html`
+
+#### 方法二:直接访问HTML文件
+1. 将 `mobile/mobile_client.html` 复制到手机
+2. 在文件中修改服务器IP地址
+3. 用浏览器打开HTML文件
+
+### 5. 开始传输
+1. 在手机页面中点击"开始传输"
+2. 允许摄像头和位置权限
+3. 查看连接状态指示灯变绿
+4. 在指挥中心Web界面查看实时数据
+
+## 📱 手机端界面说明
+
+### 状态面板
+- **📍 GPS坐标**: 显示当前精确位置
+- **🔋 电池电量**: 实时电池状态
+- **🌐 连接状态**: 与服务器的连接状态
+
+### 控制按钮
+- **📹 开始传输**: 启动数据传输
+- **⏹️ 停止传输**: 停止传输
+- **🔄 重连**: 重新连接服务器
+
+### 统计信息
+- **📊 已发送帧数**: 传输的图像帧数量
+- **📈 数据量**: 累计传输的数据量
+
+### 日志面板
+- 显示详细的操作日志和错误信息
+- 帮助诊断连接问题
+
+## 🖥️ 服务端管理
+
+### Web界面控制
+访问 `http://localhost:5000` 查看:
+- **地图显示**: 实时显示手机位置和检测结果
+- **设备管理**: 查看连接的手机列表
+- **数据统计**: 查看传输统计信息
+
+### API接口
+- `GET /api/mobile/devices` - 获取连接设备列表
+- `POST /api/mobile/toggle` - 切换手机模式开关
+- `POST /mobile/ping` - 手机连接测试
+- `POST /mobile/upload` - 接收手机数据
+
+### 命令行监控
+服务器控制台会显示详细日志:
+```
+📱 新设备连接: iPhone (mobile_12)
+📍 设备 mobile_12 位置更新: (39.904200, 116.407400)
+🎯 检测到 2 个人
+📍 手机检测人员 1: 距离5.2m, 坐标(39.904250, 116.407450)
+```
+
+## ⚙️ 高级配置
+
+### 修改传输参数
+
+在手机端HTML文件中可以调整:
+
+```javascript
+// 修改服务器地址
+this.serverHost = '192.168.1.100';
+this.serverPort = 5000;
+
+// 修改传输频率(毫秒)
+const interval = 1000; // 1秒传输一次
+
+// 修改图像质量(0.1-1.0)
+const frameData = this.canvas.toDataURL('image/jpeg', 0.5);
+```
+
+### 网络优化
+
+**低带宽环境:**
+- 降低图像质量 (0.3-0.5)
+- 增加传输间隔 (2-5秒)
+- 减小图像分辨率
+
+**高质量需求:**
+- 提高图像质量 (0.7-0.9)
+- 减少传输间隔 (0.5-1秒)
+- 使用更高分辨率
+
+## 🔧 故障排除
+
+### 常见问题
+
+#### 1. 手机无法连接服务器
+- **检查网络**: 确保在同一网络
+- **检查IP地址**: 确认服务器IP正确
+- **检查防火墙**: 关闭防火墙或开放端口
+- **检查端口**: 确认5000端口未被占用
+
+#### 2. 摄像头无法访问
+- **权限设置**: 在浏览器中允许摄像头权限
+- **HTTPS需求**: 某些浏览器需要HTTPS才能访问摄像头
+- **设备占用**: 关闭其他使用摄像头的应用
+
+#### 3. GPS定位失败
+- **位置权限**: 允许浏览器访问位置信息
+- **网络连接**: 确保网络连接正常
+- **室内环境**: 移动到有GPS信号的位置
+
+#### 4. 传输断开
+- **网络稳定性**: 检查WiFi信号强度
+- **服务器状态**: 确认服务器正常运行
+- **自动重连**: 等待自动重连或手动重连
+
+### 调试方法
+
+#### 手机端调试
+1. 打开浏览器开发者工具 (F12)
+2. 查看Console面板的错误信息
+3. 检查Network面板的网络请求
+
+#### 服务端调试
+1. 查看控制台输出的日志信息
+2. 使用 `python tests/test_system.py` 测试系统
+3. 检查网络连接和端口状态
+
+## 🌐 网络配置示例
+
+### 局域网配置
+```
+电脑 (192.168.1.100) ←→ 路由器 ←→ 手机 (192.168.1.101)
+```
+
+### 热点配置
+```
+电脑热点 (192.168.137.1) ←→ 手机 (192.168.137.2)
+```
+
+### 有线+WiFi配置
+```
+电脑 (有线: 192.168.1.100) ←→ 路由器 ←→ 手机 (WiFi: 192.168.1.101)
+```
+
+## 📊 性能建议
+
+### 推荐配置
+- **网络**: WiFi 5GHz频段,带宽 ≥ 10Mbps
+- **手机**: RAM ≥ 4GB,Android 8+ / iOS 12+
+- **服务器**: 双核CPU,RAM ≥ 4GB
+
+### 优化设置
+- **高质量模式**: 0.7质量,1秒间隔
+- **平衡模式**: 0.5质量,1秒间隔(推荐)
+- **省流量模式**: 0.3质量,2秒间隔
+
+## 🚁 实战应用场景
+
+### 军用场景
+- **前线侦察**: 士兵携带手机进行前方侦察
+- **多点监控**: 多个观察点同时传输情报
+- **指挥决策**: 指挥部实时获取战场态势
+
+### 民用场景
+- **安保监控**: 保安巡逻时实时传输画面
+- **应急救援**: 救援人员现场情况汇报
+- **活动监管**: 大型活动现场监控
+
+### 技术演示
+- **远程教学**: 实地教学直播
+- **技术展示**: 产品演示和技术验证
+
+---
+
+## 📞 技术支持
+
+如有问题,请:
+1. 查看控制台日志信息
+2. 运行系统测试脚本
+3. 检查网络配置
+4. 参考故障排除指南
+
+这个手机连接功能大大扩展了战场态势感知系统的应用场景,让移动侦察成为可能!
\ No newline at end of file
diff --git a/distance-judgement/__pycache__/config.cpython-311.pyc b/distance-judgement/__pycache__/config.cpython-311.pyc
new file mode 100644
index 00000000..24bd06e8
Binary files /dev/null and b/distance-judgement/__pycache__/config.cpython-311.pyc differ
diff --git a/distance-judgement/__pycache__/demo_map.cpython-311.pyc b/distance-judgement/__pycache__/demo_map.cpython-311.pyc
new file mode 100644
index 00000000..fab59c43
Binary files /dev/null and b/distance-judgement/__pycache__/demo_map.cpython-311.pyc differ
diff --git a/distance-judgement/__pycache__/distance_calculator.cpython-311.pyc b/distance-judgement/__pycache__/distance_calculator.cpython-311.pyc
new file mode 100644
index 00000000..cc94bd0e
Binary files /dev/null and b/distance-judgement/__pycache__/distance_calculator.cpython-311.pyc differ
diff --git a/distance-judgement/__pycache__/main.cpython-311.pyc b/distance-judgement/__pycache__/main.cpython-311.pyc
new file mode 100644
index 00000000..ee040a15
Binary files /dev/null and b/distance-judgement/__pycache__/main.cpython-311.pyc differ
diff --git a/distance-judgement/__pycache__/main_web.cpython-311.pyc b/distance-judgement/__pycache__/main_web.cpython-311.pyc
new file mode 100644
index 00000000..44b68aa7
Binary files /dev/null and b/distance-judgement/__pycache__/main_web.cpython-311.pyc differ
diff --git a/distance-judgement/__pycache__/main_web.cpython-39.pyc b/distance-judgement/__pycache__/main_web.cpython-39.pyc
new file mode 100644
index 00000000..71f0194d
Binary files /dev/null and b/distance-judgement/__pycache__/main_web.cpython-39.pyc differ
diff --git a/distance-judgement/__pycache__/person_detector.cpython-311.pyc b/distance-judgement/__pycache__/person_detector.cpython-311.pyc
new file mode 100644
index 00000000..71709859
Binary files /dev/null and b/distance-judgement/__pycache__/person_detector.cpython-311.pyc differ
diff --git a/distance-judgement/create_cert.bat b/distance-judgement/create_cert.bat
new file mode 100644
index 00000000..0519ecba
--- /dev/null
+++ b/distance-judgement/create_cert.bat
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/distance-judgement/create_simple_cert.py b/distance-judgement/create_simple_cert.py
new file mode 100644
index 00000000..d5dbd7a5
--- /dev/null
+++ b/distance-judgement/create_simple_cert.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+使用OpenSSL命令行工具创建简单的自签名证书
+不依赖Python的cryptography库
+"""
+
+import os
+import subprocess
+import sys
+
+def create_ssl_dir():
+ """创建ssl目录"""
+ if not os.path.exists("ssl"):
+ os.makedirs("ssl")
+ print("✅ 创建ssl目录")
+
+def create_certificate_with_openssl():
+ """使用OpenSSL命令创建证书"""
+ print("🔑 使用OpenSSL创建自签名证书...")
+
+ # 检查OpenSSL是否可用
+ try:
+ subprocess.run(["openssl", "version"], check=True, capture_output=True)
+ except (subprocess.CalledProcessError, FileNotFoundError):
+ print("❌ OpenSSL未安装或不在PATH中")
+ print("📝 请安装OpenSSL或使用其他方法")
+ return False
+
+ # 创建私钥
+ key_cmd = [
+ "openssl", "genrsa",
+ "-out", "ssl/key.pem",
+ "2048"
+ ]
+
+ # 创建证书
+ cert_cmd = [
+ "openssl", "req", "-new", "-x509",
+ "-key", "ssl/key.pem",
+ "-out", "ssl/cert.pem",
+ "-days", "365",
+ "-subj", "/C=CN/ST=Beijing/L=Beijing/O=Distance System/CN=localhost"
+ ]
+
+ try:
+ print(" 生成私钥...")
+ subprocess.run(key_cmd, check=True, capture_output=True)
+
+ print(" 生成证书...")
+ subprocess.run(cert_cmd, check=True, capture_output=True)
+
+ print("✅ SSL证书创建成功!")
+ print(" 🔑 私钥: ssl/key.pem")
+ print(" 📜 证书: ssl/cert.pem")
+ return True
+
+ except subprocess.CalledProcessError as e:
+ print(f"❌ OpenSSL命令执行失败: {e}")
+ return False
+
+def create_certificate_manual():
+ """提供手动创建证书的说明"""
+ print("📝 手动创建SSL证书说明:")
+ print()
+ print("方法1 - 使用在线工具:")
+ print(" 访问: https://www.selfsignedcertificate.com/")
+ print(" 下载证书文件并重命名为 cert.pem 和 key.pem")
+ print()
+ print("方法2 - 使用Git Bash (Windows):")
+ print(" 打开Git Bash,进入项目目录,执行:")
+ print(" openssl genrsa -out ssl/key.pem 2048")
+ print(" openssl req -new -x509 -key ssl/key.pem -out ssl/cert.pem -days 365")
+ print()
+ print("方法3 - 暂时使用HTTP:")
+ print(" 运行: python main_web.py")
+ print(" 注意: HTTP模式下手机摄像头可能无法使用")
+
+def main():
+ """主函数"""
+ create_ssl_dir()
+
+ # 检查证书是否已存在
+ if os.path.exists("ssl/cert.pem") and os.path.exists("ssl/key.pem"):
+ print("✅ SSL证书已存在")
+ return
+
+ print("🔍 尝试创建SSL证书...")
+
+ # 尝试使用OpenSSL
+ if create_certificate_with_openssl():
+ return
+
+ # 提供手动创建说明
+ create_certificate_manual()
+
+
\ No newline at end of file
diff --git a/distance-judgement/demo_mobile.py b/distance-judgement/demo_mobile.py
new file mode 100644
index 00000000..1e36a4ee
--- /dev/null
+++ b/distance-judgement/demo_mobile.py
@@ -0,0 +1,206 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+手机连接功能演示脚本
+展示如何使用手机作为移动侦察设备
+"""
+
+import time
+import json
+import base64
+import requests
+from src import MobileConnector, config
+
+def demo_mobile_functionality():
+ """演示手机连接功能"""
+ print("📱 手机连接功能演示")
+ print("=" * 60)
+
+ print("🎯 演示内容:")
+ print("1. 启动手机连接服务器")
+ print("2. 模拟手机客户端连接")
+ print("3. 发送模拟数据")
+ print("4. 展示数据处理流程")
+ print()
+
+ # 创建手机连接器
+ mobile_connector = MobileConnector(port=8080)
+
+ print("📱 正在启动手机连接服务器...")
+ if mobile_connector.start_server():
+ print("✅ 手机连接服务器启动成功")
+ print(f"🌐 等待手机客户端连接到端口 8080")
+ print()
+
+ print("📖 使用说明:")
+ print("1. 确保手机和电脑在同一网络")
+ print("2. 在手机浏览器中访问:")
+ print(" http://[电脑IP]:5000/mobile/mobile_client.html")
+ print("3. 或者直接打开 mobile/mobile_client.html 文件")
+ print("4. 点击'开始传输'按钮")
+ print()
+
+ print("🔧 获取电脑IP地址的方法:")
+ print("Windows: ipconfig")
+ print("Linux/Mac: ifconfig 或 ip addr show")
+ print()
+
+ # 设置回调函数来显示接收的数据
+ def on_frame_received(device_id, frame, device):
+ print(f"📷 收到设备 {device_id[:8]} 的图像帧")
+ print(f" 分辨率: {frame.shape[1]}x{frame.shape[0]}")
+ print(f" 设备: {device.device_name}")
+
+ def on_location_received(device_id, location, device):
+ lat, lng, accuracy = location
+ print(f"📍 收到设备 {device_id[:8]} 的位置信息")
+ print(f" 坐标: ({lat:.6f}, {lng:.6f})")
+ print(f" 精度: {accuracy}m")
+
+ def on_device_event(event_type, device):
+ if event_type == 'device_connected':
+ print(f"📱 设备连接: {device.device_name} ({device.device_id[:8]})")
+ print(f" 电池: {device.battery_level}%")
+ elif event_type == 'device_disconnected':
+ print(f"📱 设备断开: {device.device_name} ({device.device_id[:8]})")
+
+ # 注册回调函数
+ mobile_connector.add_frame_callback(on_frame_received)
+ mobile_connector.add_location_callback(on_location_received)
+ mobile_connector.add_device_callback(on_device_event)
+
+ print("⏳ 等待手机连接... (按 Ctrl+C 退出)")
+
+ try:
+ # 监控连接状态
+ while True:
+ time.sleep(5)
+
+ # 显示统计信息
+ stats = mobile_connector.get_statistics()
+ online_devices = mobile_connector.get_online_devices()
+
+ if stats['online_devices'] > 0:
+ print(f"\n📊 连接统计:")
+ print(f" 在线设备: {stats['online_devices']}")
+ print(f" 接收帧数: {stats['frames_received']}")
+ print(f" 数据量: {stats['data_received_mb']:.2f} MB")
+ print(f" 平均帧率: {stats['avg_frames_per_second']:.1f} FPS")
+
+ print(f"\n📱 在线设备:")
+ for device in online_devices:
+ print(f" • {device.device_name} ({device.device_id[:8]})")
+ print(f" 电池: {device.battery_level}%")
+ if device.current_location:
+ lat, lng, acc = device.current_location
+ print(f" 位置: ({lat:.6f}, {lng:.6f})")
+ else:
+ print("⏳ 等待设备连接...")
+
+ except KeyboardInterrupt:
+ print("\n🔴 用户中断")
+
+ finally:
+ mobile_connector.stop_server()
+ print("📱 手机连接服务器已停止")
+
+ else:
+ print("❌ 手机连接服务器启动失败")
+ print("💡 可能的原因:")
+ print(" - 端口 8080 已被占用")
+ print(" - 网络权限问题")
+ print(" - 防火墙阻止连接")
+
+def test_mobile_api():
+ """测试手机相关API"""
+ print("\n🧪 测试手机API接口")
+ print("=" * 40)
+
+ base_url = "http://127.0.0.1:5000"
+
+ try:
+ # 测试ping接口
+ test_data = {"device_id": "test_device_123"}
+ response = requests.post(f"{base_url}/mobile/ping",
+ json=test_data, timeout=5)
+
+ if response.status_code == 200:
+ data = response.json()
+ print("✅ Ping API测试成功")
+ print(f" 服务器时间: {data.get('server_time')}")
+ else:
+ print(f"❌ Ping API测试失败: HTTP {response.status_code}")
+
+ except requests.exceptions.ConnectionError:
+ print("⚠️ 无法连接到Web服务器")
+ print("💡 请先启动Web服务器: python main_web.py")
+
+ except Exception as e:
+ print(f"❌ API测试出错: {e}")
+
+def show_mobile_guide():
+ """显示手机连接指南"""
+ print("\n📖 手机连接步骤指南")
+ print("=" * 40)
+
+ print("1️⃣ 启动服务端:")
+ print(" python main_web.py")
+ print(" 或 python run.py (选择Web模式)")
+ print()
+
+ print("2️⃣ 获取电脑IP地址:")
+ print(" Windows: 打开CMD,输入 ipconfig")
+ print(" Mac/Linux: 打开终端,输入 ifconfig")
+ print(" 记下IP地址,如: 192.168.1.100")
+ print()
+
+ print("3️⃣ 手机端连接:")
+ print(" 方法1: 浏览器访问 http://[IP]:5000/mobile/mobile_client.html")
+ print(" 方法2: 直接打开 mobile/mobile_client.html 文件")
+ print()
+
+ print("4️⃣ 开始传输:")
+ print(" • 允许摄像头和位置权限")
+ print(" • 点击'开始传输'按钮")
+ print(" • 查看连接状态指示灯")
+ print()
+
+ print("5️⃣ 查看结果:")
+ print(" • 在电脑Web界面查看地图")
+ print(" • 观察实时检测结果")
+ print(" • 监控设备状态")
+
+if __name__ == "__main__":
+ print("🚁 无人机战场态势感知系统 - 手机连接演示")
+ print("=" * 60)
+
+ while True:
+ print("\n选择演示内容:")
+ print("1. 📱 启动手机连接服务器")
+ print("2. 🧪 测试手机API接口")
+ print("3. 📖 查看连接指南")
+ print("0. ❌ 退出")
+
+ try:
+ choice = input("\n请输入选择 (0-3): ").strip()
+
+ if choice == "1":
+ demo_mobile_functionality()
+ elif choice == "2":
+ test_mobile_api()
+ elif choice == "3":
+ show_mobile_guide()
+ elif choice == "0":
+ print("👋 再见!")
+ break
+ else:
+ print("❌ 无效选择,请重新输入")
+
+ except KeyboardInterrupt:
+ print("\n👋 再见!")
+ break
+ except Exception as e:
+ print(f"❌ 出错: {e}")
+
+ input("\n按回车键继续...")
\ No newline at end of file
diff --git a/distance-judgement/get_ip.py b/distance-judgement/get_ip.py
new file mode 100644
index 00000000..cfc745f9
--- /dev/null
+++ b/distance-judgement/get_ip.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import socket
+
+def get_local_ip():
+ """获取本机IP地址"""
+ try:
+ # 创建一个socket连接来获取本机IP
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ s.connect(("8.8.8.8", 80))
+ ip = s.getsockname()[0]
+ s.close()
+ return ip
+ except:
+ try:
+ # 备用方法
+ import subprocess
+ result = subprocess.run(['ipconfig'], capture_output=True, text=True, shell=True)
+ lines = result.stdout.split('\n')
+ for line in lines:
+ if 'IPv4' in line and '192.168' in line:
+ return line.split(':')[-1].strip()
+ except:
+ return '127.0.0.1'
+
+if __name__ == "__main__":
+ ip = get_local_ip()
+ print(f"🌐 服务器地址信息")
+ print(f"="*50)
+ print(f"本机IP地址: {ip}")
+ print(f"主页面地址: http://{ip}:5000/")
+ print(f"移动客户端: http://{ip}:5000/mobile/mobile_client.html")
+ print(f"GPS测试页面: http://{ip}:5000/mobile/gps_test.html")
+ print(f"设备选择测试: http://{ip}:5000/test_device_selector.html")
+ print(f"="*50)
+ print(f"�� 手机/平板请访问移动客户端地址!")
\ No newline at end of file
diff --git a/distance-judgement/main.py b/distance-judgement/main.py
new file mode 100644
index 00000000..c1429b85
--- /dev/null
+++ b/distance-judgement/main.py
@@ -0,0 +1,261 @@
+import cv2
+import time
+import numpy as np
+from src import PersonDetector, DistanceCalculator, MapManager, config
+
+class RealTimePersonDistanceDetector:
+ def __init__(self):
+ self.detector = PersonDetector()
+ self.distance_calculator = DistanceCalculator()
+ self.cap = None
+ self.fps_counter = 0
+ self.fps_time = time.time()
+ self.current_fps = 0
+
+ # 初始化地图管理器
+ if config.ENABLE_MAP_DISPLAY:
+ self.map_manager = MapManager(
+ api_key=config.GAODE_API_KEY,
+ camera_lat=config.CAMERA_LATITUDE,
+ camera_lng=config.CAMERA_LONGITUDE
+ )
+ self.map_manager.set_camera_position(
+ config.CAMERA_LATITUDE,
+ config.CAMERA_LONGITUDE,
+ config.CAMERA_HEADING
+ )
+ print("🗺️ 地图管理器已初始化")
+ else:
+ self.map_manager = None
+
+ def initialize_camera(self):
+ """初始化摄像头"""
+ self.cap = cv2.VideoCapture(config.CAMERA_INDEX)
+ if not self.cap.isOpened():
+ raise Exception(f"无法开启摄像头 {config.CAMERA_INDEX}")
+
+ # 设置摄像头参数
+ self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, config.FRAME_WIDTH)
+ self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, config.FRAME_HEIGHT)
+ self.cap.set(cv2.CAP_PROP_FPS, config.FPS)
+
+ # 获取实际设置的参数
+ actual_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
+ actual_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
+ actual_fps = int(self.cap.get(cv2.CAP_PROP_FPS))
+
+ print(f"摄像头初始化成功:")
+ print(f" 分辨率: {actual_width}x{actual_height}")
+ print(f" 帧率: {actual_fps} FPS")
+
+ def calculate_fps(self):
+ """计算实际帧率"""
+ self.fps_counter += 1
+ current_time = time.time()
+ if current_time - self.fps_time >= 1.0:
+ self.current_fps = self.fps_counter
+ self.fps_counter = 0
+ self.fps_time = current_time
+
+ def draw_info_panel(self, frame, person_count=0):
+ """绘制信息面板"""
+ height, width = frame.shape[:2]
+
+ # 绘制顶部信息栏
+ info_height = 60
+ cv2.rectangle(frame, (0, 0), (width, info_height), (0, 0, 0), -1)
+
+ # 显示FPS
+ fps_text = f"FPS: {self.current_fps}"
+ cv2.putText(frame, fps_text, (10, 25), config.FONT, 0.6, (0, 255, 0), 2)
+
+ # 显示人员计数
+ person_text = f"Persons: {person_count}"
+ cv2.putText(frame, person_text, (150, 25), config.FONT, 0.6, (0, 255, 255), 2)
+
+ # 显示模型信息
+ model_text = self.detector.get_model_info()
+ cv2.putText(frame, model_text, (10, 45), config.FONT, 0.5, (255, 255, 255), 1)
+
+ # 显示操作提示
+ help_text = "Press 'q' to quit | 'c' to calibrate | 'r' to reset | 'm' to open map"
+ text_size = cv2.getTextSize(help_text, config.FONT, 0.5, 1)[0]
+ cv2.putText(frame, help_text, (width - text_size[0] - 10, 25),
+ config.FONT, 0.5, (255, 255, 0), 1)
+
+ # 显示地图状态
+ if self.map_manager:
+ map_status = "Map: ON"
+ cv2.putText(frame, map_status, (10, height - 10),
+ config.FONT, 0.5, (0, 255, 255), 1)
+
+ return frame
+
+ def calibrate_distance(self, detections):
+ """距离校准模式"""
+ if len(detections) == 0:
+ print("未检测到人体,无法校准")
+ return
+
+ print("\n=== 距离校准模式 ===")
+ print("请确保画面中有一个人,并输入该人距离摄像头的真实距离")
+
+ try:
+ real_distance = float(input("请输入真实距离(厘米): "))
+
+ # 使用第一个检测到的人进行校准
+ detection = detections[0]
+ x1, y1, x2, y2, conf = detection
+ bbox_height = y2 - y1
+
+ # 更新参考参数
+ config.REFERENCE_DISTANCE = real_distance
+ config.REFERENCE_HEIGHT_PIXELS = bbox_height
+
+ # 重新初始化距离计算器
+ self.distance_calculator = DistanceCalculator()
+
+ print(f"校准完成!")
+ print(f"参考距离: {real_distance}cm")
+ print(f"参考像素高度: {bbox_height}px")
+
+ except ValueError:
+ print("输入无效,校准取消")
+ except Exception as e:
+ print(f"校准失败: {e}")
+
+ def process_frame(self, frame):
+ """处理单帧图像"""
+ # 检测人体
+ detections = self.detector.detect_persons(frame)
+
+ # 计算距离并更新地图位置
+ distances = []
+ if self.map_manager:
+ self.map_manager.clear_persons()
+
+ for i, detection in enumerate(detections):
+ bbox = detection[:4] # [x1, y1, x2, y2]
+ x1, y1, x2, y2 = bbox
+ distance = self.distance_calculator.get_distance(bbox)
+ distance_str = self.distance_calculator.format_distance(distance)
+ distances.append(distance_str)
+
+ # 更新地图上的人员位置
+ if self.map_manager:
+ # 计算人体中心点
+ center_x = (x1 + x2) / 2
+ center_y = (y1 + y2) / 2
+
+ # 将距离从厘米转换为米
+ distance_meters = distance / 100.0
+
+ # 添加到地图
+ self.map_manager.add_person_position(
+ center_x, center_y, distance_meters,
+ frame.shape[1], frame.shape[0], # width, height
+ f"P{i+1}"
+ )
+
+ # 绘制检测结果
+ frame = self.detector.draw_detections(frame, detections, distances)
+
+ # 绘制信息面板
+ frame = self.draw_info_panel(frame, len(detections))
+
+ # 计算FPS
+ self.calculate_fps()
+
+ return frame, detections
+
+ def run(self):
+ """运行主程序"""
+ try:
+ print("正在初始化...")
+ self.initialize_camera()
+
+ print("系统启动成功!")
+ print("操作说明:")
+ print(" - 按 'q' 键退出程序")
+ print(" - 按 'c' 键进入距离校准模式")
+ print(" - 按 'r' 键重置为默认参数")
+ print(" - 按 's' 键保存当前帧")
+ if self.map_manager:
+ print(" - 按 'm' 键打开地图显示")
+ print(" - 按 'h' 键设置摄像头朝向")
+ print("\n开始实时检测...")
+
+ frame_count = 0
+
+ while True:
+ ret, frame = self.cap.read()
+ if not ret:
+ print("无法读取摄像头画面")
+ break
+
+ # 处理帧
+ processed_frame, detections = self.process_frame(frame)
+
+ # 显示结果
+ cv2.imshow('Real-time Person Distance Detection', processed_frame)
+
+ # 处理按键
+ key = cv2.waitKey(1) & 0xFF
+
+ if key == ord('q'):
+ print("用户退出程序")
+ break
+ elif key == ord('c'):
+ # 校准模式
+ self.calibrate_distance(detections)
+ elif key == ord('r'):
+ # 重置参数
+ print("重置为默认参数")
+ self.distance_calculator = DistanceCalculator()
+ elif key == ord('s'):
+ # 保存当前帧
+ filename = f"capture_{int(time.time())}.jpg"
+ cv2.imwrite(filename, processed_frame)
+ print(f"已保存截图: {filename}")
+ elif key == ord('m') and self.map_manager:
+ # 打开地图显示
+ print("正在打开地图...")
+ self.map_manager.open_map()
+ elif key == ord('h') and self.map_manager:
+ # 设置摄像头朝向
+ try:
+ heading = float(input("请输入摄像头朝向角度 (0-360°, 0为正北): "))
+ if 0 <= heading <= 360:
+ self.map_manager.update_camera_heading(heading)
+ else:
+ print("角度必须在0-360度之间")
+ except ValueError:
+ print("输入无效")
+
+ frame_count += 1
+
+ except KeyboardInterrupt:
+ print("\n程序被用户中断")
+ except Exception as e:
+ print(f"程序运行出错: {e}")
+ finally:
+ self.cleanup()
+
+ def cleanup(self):
+ """清理资源"""
+ if self.cap:
+ self.cap.release()
+ cv2.destroyAllWindows()
+ print("资源已清理,程序结束")
+
+def main():
+ """主函数"""
+ print("=" * 50)
+ print("实时人体距离检测系统")
+ print("=" * 50)
+
+ detector = RealTimePersonDistanceDetector()
+ detector.run()
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/distance-judgement/main_web.py b/distance-judgement/main_web.py
new file mode 100644
index 00000000..e2b698c0
--- /dev/null
+++ b/distance-judgement/main_web.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+无人机战场态势感知系统 - Web版本
+先显示地图界面,通过按钮控制摄像头启动和显示
+"""
+
+import sys
+import os
+from src import WebServer, config
+
+def main():
+ """主函数"""
+ global config # 声明 config 为全局变量
+
+ print("=" * 60)
+ print("🚁 无人机战场态势感知系统 - Web版本")
+ print("=" * 60)
+ print()
+
+ # 检查配置
+ print("📋 系统配置检查...")
+ print(f"📍 摄像头位置: ({config.CAMERA_LATITUDE:.6f}, {config.CAMERA_LONGITUDE:.6f})")
+ print(f"🧭 摄像头朝向: {config.CAMERA_HEADING}°")
+ print(f"🔑 API Key: {'已配置' if config.GAODE_API_KEY != 'your_gaode_api_key_here' else '未配置'}")
+ print()
+
+ if config.GAODE_API_KEY == "your_gaode_api_key_here":
+ print("⚠️ 警告: 未配置高德地图API Key")
+ print(" 地图功能可能受限,建议运行 setup_camera_location.py 进行配置")
+ print()
+
+ # 检查是否为默认配置,提供自动配置选项
+ if (config.CAMERA_LATITUDE == 39.9042 and
+ config.CAMERA_LONGITUDE == 116.4074 and
+ config.CAMERA_HEADING == 0):
+ print("🤖 检测到摄像头使用默认配置")
+ print(" 是否要自动配置摄像头位置和朝向?")
+ print(" • 输入 'y' - 立即自动配置")
+ print(" • 输入 'n' - 跳过,使用Web界面配置")
+ print(" • 直接回车 - 跳过自动配置")
+ print()
+
+ try:
+ choice = input("🔧 请选择 (y/n/回车): ").strip().lower()
+
+ if choice == 'y':
+ print("\n🚀 启动自动配置...")
+ from src.orientation_detector import OrientationDetector
+
+ detector = OrientationDetector()
+ result = detector.auto_configure_camera_location()
+
+ if result['success']:
+ print(f"✅ 自动配置成功!")
+ print(f"📍 新位置: ({result['gps_location'][0]:.6f}, {result['gps_location'][1]:.6f})")
+ print(f"🧭 新朝向: {result['camera_heading']:.1f}°")
+
+ apply_choice = input("\n🔧 是否应用此配置? (y/n): ").strip().lower()
+ if apply_choice == 'y':
+ detector.update_camera_config(
+ result['gps_location'],
+ result['camera_heading']
+ )
+ print("✅ 配置已应用!")
+
+ # 重新加载配置模块
+ import importlib
+ import src.config
+ importlib.reload(src.config)
+
+ # 更新全局 config 变量
+ config = src.config
+ else:
+ print("⏭️ 配置未应用,将使用原配置")
+ else:
+ print("❌ 自动配置失败,将使用默认配置")
+ print("💡 可以在Web界面启动后使用自动配置功能")
+
+ print()
+ elif choice == 'n':
+ print("⏭️ 已跳过自动配置")
+ print("💡 提示: 系统启动后可在Web界面使用自动配置功能")
+ print()
+ else:
+ print("⏭️ 已跳过自动配置")
+ print()
+
+ except KeyboardInterrupt:
+ print("\n⏭️ 已跳过自动配置")
+ print()
+ except Exception as e:
+ print(f"⚠️ 自动配置过程出错: {e}")
+ print("💡 将使用默认配置,可在Web界面手动配置")
+ print()
+
+ # 系统介绍
+ print("🎯 系统功能:")
+ print(" • 🗺️ 实时地图显示")
+ print(" • 📷 摄像头控制(Web界面)")
+ print(" • 👥 人员检测和定位")
+ print(" • 📏 距离测量")
+ print(" • 🌐 Web界面操作")
+ print()
+
+ print("💡 使用说明:")
+ print(" 1. 系统启动后会自动打开浏览器")
+ print(" 2. 在地图界面点击 '启动视频侦察' 按钮")
+ print(" 3. 右上角会显示摄像头小窗口")
+ print(" 4. 检测到的人员会在地图上用红点标记")
+ print(" 5. 点击 '停止侦察' 按钮停止检测")
+ print()
+
+ try:
+ # 创建并启动Web服务器
+ print("🌐 正在启动Web服务器...")
+ web_server = WebServer()
+
+ # 获取本机IP地址用于移动设备连接
+ import socket
+ try:
+ # 连接到一个远程地址来获取本机IP
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ s.connect(("8.8.8.8", 80))
+ local_ip = s.getsockname()[0]
+ s.close()
+ except:
+ local_ip = "127.0.0.1"
+
+ # 启动服务器
+ print("✅ 系统已启动!")
+ print(f"🔒 本地访问: https://127.0.0.1:5000")
+ print(f"🔒 手机/平板访问: https://{local_ip}:5000")
+ print(f"📱 手机客户端: https://{local_ip}:5000/mobile/mobile_client.html")
+ print("🔴 按 Ctrl+C 停止服务器")
+ print()
+ print("🔑 HTTPS注意事项:")
+ print(" • 首次访问会显示'您的连接不是私密连接'警告")
+ print(" • 点击'高级'->'继续访问localhost(不安全)'即可")
+ print(" • 手机访问时也需要点击'继续访问'")
+ print()
+
+ # 尝试自动打开浏览器
+ try:
+ import webbrowser
+ webbrowser.open('https://127.0.0.1:5000')
+ print("🌐 浏览器已自动打开")
+ except:
+ print("⚠️ 无法自动打开浏览器,请手动访问地址")
+
+ print("-" * 60)
+
+ # 运行服务器,绑定到所有网络接口,启用HTTPS
+ web_server.run(host='0.0.0.0', port=5000, debug=False, ssl_enabled=True)
+
+ except KeyboardInterrupt:
+ print("\n🔴 用户中断程序")
+ except Exception as e:
+ print(f"❌ 程序运行出错: {e}")
+ print("💡 建议检查:")
+ print(" 1. 是否正确安装了所有依赖包")
+ print(" 2. 摄像头是否正常工作")
+ print(" 3. 网络连接是否正常")
+ sys.exit(1)
+ finally:
+ print("👋 程序已结束")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/distance-judgement/main_web_simple_https.py b/distance-judgement/main_web_simple_https.py
new file mode 100644
index 00000000..77b91833
--- /dev/null
+++ b/distance-judgement/main_web_simple_https.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+简化版HTTPS Web服务器
+使用Python内置ssl模块,无需额外依赖
+"""
+
+import ssl
+import socket
+from src.web_server import create_app
+from get_ip import get_local_ip
+
+def create_simple_ssl_context():
+ """创建简单的SSL上下文,使用自签名证书"""
+ context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+
+ # 检查是否存在SSL证书文件
+ import os
+ cert_file = "ssl/cert.pem"
+ key_file = "ssl/key.pem"
+
+ if not os.path.exists(cert_file) or not os.path.exists(key_file):
+ print("❌ SSL证书文件不存在")
+ print("📝 为了使用HTTPS,请选择以下选项之一:")
+ print(" 1. 安装cryptography库: pip install cryptography")
+ print(" 2. 使用HTTP版本: python main_web.py")
+ print(" 3. 手动创建SSL证书")
+ return None
+
+ try:
+ context.load_cert_chain(cert_file, key_file)
+ return context
+ except Exception as e:
+ print(f"❌ 加载SSL证书失败: {e}")
+ return None
+
+def main():
+ """启动简化版HTTPS服务器"""
+ print("🚀 启动简化版HTTPS服务器...")
+
+ # 创建Flask应用
+ app = create_app()
+
+ # 获取本地IP
+ local_ip = get_local_ip()
+
+ print(f"🌐 本地IP地址: {local_ip}")
+ print()
+ print("📱 访问地址:")
+ print(f" 桌面端: https://127.0.0.1:5000")
+ print(f" 手机端: https://{local_ip}:5000/mobile/mobile_client.html")
+ print()
+ print("⚠️ 如果看到安全警告,请点击 '高级' -> '继续访问'")
+ print()
+
+ # 创建SSL上下文
+ ssl_context = create_simple_ssl_context()
+
+ if ssl_context is None:
+ print("🔄 回退到HTTP模式...")
+ print(f" 桌面端: http://127.0.0.1:5000")
+ print(f" 手机端: http://{local_ip}:5000/mobile/mobile_client.html")
+ app.run(host='0.0.0.0', port=5000, debug=True)
+ else:
+ print("🔒 HTTPS模式启动成功!")
+ app.run(host='0.0.0.0', port=5000, debug=True, ssl_context=ssl_context)
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/distance-judgement/mobile/baidu_browser_test.html b/distance-judgement/mobile/baidu_browser_test.html
new file mode 100644
index 00000000..4dca59f5
--- /dev/null
+++ b/distance-judgement/mobile/baidu_browser_test.html
@@ -0,0 +1,1442 @@
+
+
+
+
+
+
+ 📱 百度浏览器摄像头测试
+
+
+
+
+ 📱 百度浏览器摄像头测试
+ 专门针对百度浏览器的摄像头API兼容性测试
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/distance-judgement/mobile/browser_compatibility_guide.html b/distance-judgement/mobile/browser_compatibility_guide.html
new file mode 100644
index 00000000..671aa68f
--- /dev/null
+++ b/distance-judgement/mobile/browser_compatibility_guide.html
@@ -0,0 +1,410 @@
+
+
+
+
+
+
+ 🌐 浏览器兼容性指南
+
+
+
+
+
+
+
+
+
+
+
🔍 "设备扫描失败: 浏览器不支持设备枚举功能" 问题说明
+
+
+
⚠️ 问题原因
+
这个错误表示您的浏览器不支持 navigator.mediaDevices.enumerateDevices()
API,这个API用于列出可用的摄像头设备。
+
+
+
+
✅ 系统自动解决方案
+
我们的系统已经自动启用了兼容模式,为您提供以下设备选项:
+
+ - 📱 默认摄像头 - 使用系统默认摄像头
+ - 📹 后置摄像头 - 尝试使用后置摄像头
+ - 🤳 前置摄像头 - 尝试使用前置摄像头
+
+
您可以通过设备选择器逐个测试这些选项,找到适合的摄像头配置。
+
+
+
+
+
📱 浏览器兼容性列表
+
+
+
+ 浏览器 |
+ getUserMedia |
+ enumerateDevices |
+ Permissions API |
+ 总体支持 |
+
+
+
+
+ Chrome 53+ |
+ ✅ 完全支持 |
+ ✅ 完全支持 |
+ ✅ 完全支持 |
+ 推荐 |
+
+
+ Firefox 36+ |
+ ✅ 完全支持 |
+ ✅ 完全支持 |
+ ⚠️ 部分支持 |
+ 推荐 |
+
+
+ Safari 11+ |
+ ✅ 完全支持 |
+ ✅ 完全支持 |
+ ❌ 不支持 |
+ ⚠️ 基本可用 |
+
+
+ Edge 17+ |
+ ✅ 完全支持 |
+ ✅ 完全支持 |
+ ✅ 完全支持 |
+ 推荐 |
+
+
+ 旧版浏览器 |
+ ⚠️ 需要前缀 |
+ ❌ 不支持 |
+ ❌ 不支持 |
+ ⚠️ 兼容模式 |
+
+
+
+
+
+
+
🔧 解决方案与建议
+
+
1. 最佳解决方案 - 升级浏览器
+
+
推荐使用以下现代浏览器:
+
+ - 🌐 Chrome 版本 53 或更高
+ - 🦊 Firefox 版本 36 或更高
+ - 🧭 Safari 版本 11 或更高(iOS/macOS)
+ - ⭐ Edge 版本 17 或更高
+
+
+
+
2. 兼容模式使用方法
+
+
如果无法升级浏览器,请按以下步骤操作:
+
+ - 忽略"设备扫描失败"的提示
+ - 点击"📷 选择设备"按钮
+ - 在设备列表中选择"默认摄像头"、"后置摄像头"或"前置摄像头"
+ - 点击"使用选中设备"测试摄像头功能
+ - 如果某个选项不工作,尝试其他选项
+
+
+
+
3. 移动设备特别说明
+
+
移动设备用户请注意:
+
+ - 📱 Android:建议使用 Chrome 浏览器
+ - 🍎 iOS:建议使用 Safari 浏览器
+ - 🔒 确保在 HTTPS 环境下访问(已自动配置)
+ - 🎥 允许摄像头权限访问
+
+
+
+
+
+
🚨 常见问题排除
+
+
+
+
+ 完全无法访问摄像头
+
检查浏览器是否支持getUserMedia,尝试升级浏览器或使用HTTPS访问
+
+
+
+ 无法枚举设备但能使用摄像头
+
正常现象,使用兼容模式的默认设备选项即可
+
+
+
+ 权限被拒绝
+
检查浏览器权限设置,清除网站数据后重新允许权限
+
+
+
+ 摄像头被占用
+
关闭其他使用摄像头的应用程序或浏览器标签页
+
+
+
+
+
+
🧪 测试工具
+
使用以下工具测试您的浏览器兼容性和摄像头功能:
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/distance-judgement/mobile/camera_permission_test.html b/distance-judgement/mobile/camera_permission_test.html
new file mode 100644
index 00000000..b2995a9e
--- /dev/null
+++ b/distance-judgement/mobile/camera_permission_test.html
@@ -0,0 +1,504 @@
+
+
+
+
+
+
+ 📷 摄像头权限测试
+
+
+
+
+
+
+
+
+
🔍 1. 浏览器兼容性检查
+
等待测试...
+
+
+
+
+
🔐 2. 权限状态查询
+
等待测试...
+
+
+
+
+
📱 3. 设备枚举测试
+
等待测试...
+
+
+
+
+
🎥 4. 摄像头访问测试
+
等待测试...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/distance-judgement/mobile/gps_test.html b/distance-judgement/mobile/gps_test.html
new file mode 100644
index 00000000..b5ebcf08
--- /dev/null
+++ b/distance-judgement/mobile/gps_test.html
@@ -0,0 +1,312 @@
+
+
+
+
+
+
+ GPS连接测试
+
+
+
+
+
+
📍 GPS连接测试工具
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ⚠️ 重要提示:
+ • 现代浏览器在HTTP模式下可能限制GPS访问
+ • 请确保允许浏览器访问位置信息
+ • 在室外或窗边可获得更好的GPS信号
+ • 首次访问需要用户授权位置权限
+
+
+
📋 操作日志
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/distance-judgement/mobile/legacy_browser_help.html b/distance-judgement/mobile/legacy_browser_help.html
new file mode 100644
index 00000000..59da0502
--- /dev/null
+++ b/distance-judgement/mobile/legacy_browser_help.html
@@ -0,0 +1,247 @@
+
+
+
+
+
+
+ 📱 旧版浏览器使用指南
+
+
+
+
+
+
+
+
+
⚠️ 检测结果
+
您的浏览器兼容性较低,但系统已自动启用兼容模式。请按照以下步骤操作:
+
+
+
+
🔧 使用步骤
+
+
+ 1
+ 返回主页面
+
关闭此页面,返回移动侦察终端主界面
+
+
+
+ 2
+ 查看系统状态
+
确认页面显示"兼容模式:已为您的浏览器启用兼容支持"
+
+
+
+ 3
+ 选择摄像头设备
+
点击页面中的"📷 选择设备"按钮
+
+
+
+
4
+
测试设备选项
+
在弹窗中选择以下任一设备进行测试:
+
+
📱 默认摄像头 - 系统自动选择
+
📹 后置摄像头 - 优先使用后置
+
🤳 前置摄像头 - 优先使用前置
+
+
+
+
+ 5
+ 启动摄像头
+
选择设备后点击"✅ 使用选择的设备"
+
+
+
+ 6
+ 允许权限
+
当浏览器弹出权限请求时,点击"允许"
+
+
+
+ 7
+ 开始使用
+
摄像头启动成功后,点击"📹 开始传输"
+
+
+
+
+
🚨 常见问题
+
+
Q: 权限被拒绝怎么办?
+
A: 清除浏览器数据,重新访问页面并允许权限
+
+
Q: 某个设备选项不工作?
+
A: 尝试其他设备选项,通常至少有一个会工作
+
+
Q: 完全无法使用摄像头?
+
A: 考虑升级浏览器或换用现代浏览器
+
+
+
+
🌐 推荐浏览器
+
为获得最佳体验,建议升级到以下浏览器:
+
+ - 🌐 Chrome 53+ - 完全支持所有功能
+ - 🦊 Firefox 36+ - 良好的兼容性
+ - 🧭 Safari 11+ - iOS/macOS用户推荐
+ - ⭐ Edge 17+ - Windows用户推荐
+
+
+
+
+
✅ 重要提醒
+
兼容模式虽然功能有限,但基本的摄像头录制和GPS定位功能仍然可用。请耐心按步骤操作。
+
+
+
+
+
+
技术说明:您的浏览器缺少现代Web API支持,但我们通过以下方式提供兼容:
+
+ - 使用传统getUserMedia API (webkit/moz前缀)
+ - 提供预定义设备配置代替设备枚举
+ - 简化权限检查流程
+ - 降级使用基础功能
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/distance-judgement/mobile/mobile_client.html b/distance-judgement/mobile/mobile_client.html
new file mode 100644
index 00000000..b323ffbf
--- /dev/null
+++ b/distance-judgement/mobile/mobile_client.html
@@ -0,0 +1,2874 @@
+
+
+
+
+
+
+ 🚁 移动侦察终端
+
+
+
+
+
+
+
+
+
+
+
+ 📍 GPS坐标
+ 获取中...
+
+
+ 🧭 设备朝向
+ 获取中...
+
+
+ 🔋 电池电量
+ --
+
+
+ 📶 信号强度
+ --
+
+
+ 🌐 连接状态
+ 离线
+
+
+
+
+
+
+
+ 点击"开始传输"启动视频监控
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
⚙️ 连接设置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
📍 GPS权限说明
+
如果GPS获取失败,请确保:
+
+ - 在浏览器弹出权限请求时点击"允许"
+ - 在浏览器地址栏点击🔒图标,设置位置权限为"允许"
+ - 确保设备GPS已开启
+ - 在室外或窗边以获得更好的GPS信号
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/distance-judgement/mobile/permission_guide.html b/distance-judgement/mobile/permission_guide.html
new file mode 100644
index 00000000..7078d29a
--- /dev/null
+++ b/distance-judgement/mobile/permission_guide.html
@@ -0,0 +1,430 @@
+
+
+
+
+
+
+ 📱 权限设置指南
+
+
+
+
+
+
📱 权限设置指南
+
+
+
📊 当前权限状态
+
+
+
正在检查权限状态...
+
+
+
+
🎯 第1步:GPS定位权限
+
为了在地图上显示您的位置,需要获取GPS定位权限:
+
+ - 当浏览器弹出权限请求时,点击"允许"
+ - 如果已经拒绝,点击地址栏的🔒图标重新设置
+ - 确保设备的定位服务已开启
+
+
+
+
+
+
+
+
📷 第2步:摄像头权限
+
为了拍摄和传输视频,需要获取摄像头访问权限:
+
+ - 当浏览器询问摄像头权限时,点击"允许"
+ - 如果失败,检查其他应用是否占用摄像头
+ - 建议使用后置摄像头以获得更好效果
+
+
+
+
+
+
+
+
🔧 不同浏览器的权限设置方法:
+
+
+
📱 Safari (iOS):
+
+ - 设置 → Safari → 摄像头/麦克风 → 允许
+ - 设置 → 隐私与安全性 → 定位服务 → Safari → 使用App期间
+
+
+
+
+
🤖 Chrome (Android):
+
+ - 点击地址栏左侧的🔒或ℹ️图标
+ - 设置权限为"允许"
+ - 或在设置 → 网站设置中调整
+
+
+
+
+
🖥️ 桌面浏览器:
+
+ - 点击地址栏的🔒图标
+ - 将摄像头和位置权限设为"允许"
+ - 刷新页面使设置生效
+
+
+
+
+
+
⚠️ 常见问题解决:
+
GPS获取失败:
+
+ - 移动到窗边或室外获得更好信号
+ - 检查设备的定位服务是否开启
+ - 在浏览器设置中清除网站数据后重试
+
+
摄像头无法访问:
+
+ - 关闭其他正在使用摄像头的应用
+ - 重启浏览器或设备
+ - 使用Chrome或Safari等现代浏览器
+
+
+
+
+
+
+
🎉 权限设置成功!
+
所有权限已获取,您现在可以正常使用移动侦察系统了。
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/distance-judgement/requirements.txt b/distance-judgement/requirements.txt
new file mode 100644
index 00000000..ce615fd4
--- /dev/null
+++ b/distance-judgement/requirements.txt
@@ -0,0 +1,14 @@
+opencv-python==4.8.1.78
+ultralytics==8.0.196
+numpy==1.24.3
+torch==2.0.1
+torchvision==0.15.2
+matplotlib==3.7.2
+pillow==10.0.0
+requests==2.31.0
+flask==2.3.3
+cryptography>=3.4.8
+
+# Windows系统位置服务支持(仅Windows)
+winrt-runtime>=1.0.0; sys_platform == "win32"
+winrt-Windows.Devices.Geolocation>=1.0.0; sys_platform == "win32"
\ No newline at end of file
diff --git a/distance-judgement/run.py b/distance-judgement/run.py
new file mode 100644
index 00000000..ae118b81
--- /dev/null
+++ b/distance-judgement/run.py
@@ -0,0 +1,97 @@
+1#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+无人机战场态势感知系统 - 启动脚本
+让用户选择运行模式
+"""
+
+import sys
+import os
+
+def show_menu():
+ """显示菜单"""
+ print("=" * 60)
+ print("🚁 无人机战场态势感知系统")
+ print("=" * 60)
+ print()
+ print("请选择运行模式:")
+ print()
+ print("1. 🌐 Web模式 (推荐)")
+ print(" • 地图作为主界面")
+ print(" • 通过浏览器操作")
+ print(" • 可视化程度更高")
+ print(" • 支持远程访问")
+ print()
+ print("2. 🖥️ 传统模式")
+ print(" • 直接显示摄像头画面")
+ print(" • 键盘快捷键操作")
+ print(" • 性能更好")
+ print(" • 适合本地使用")
+ print()
+ print("3. ⚙️ 配置摄像头位置")
+ print(" • 设置GPS坐标")
+ print(" • 配置朝向角度")
+ print(" • 设置API Key")
+ print()
+ print("4. 🧪 运行系统测试")
+ print(" • 检查各模块状态")
+ print(" • 验证系统功能")
+ print()
+ print("0. ❌ 退出")
+ print()
+
+def main():
+ """主函数"""
+ while True:
+ show_menu()
+ try:
+ choice = input("请输入选择 (0-4): ").strip()
+
+ if choice == "1":
+ print("\n🌐 启动Web模式...")
+ import main_web
+ main_web.main()
+ break
+
+ elif choice == "2":
+ print("\n🖥️ 启动传统模式...")
+ import main
+ main.main()
+ break
+
+ elif choice == "3":
+ print("\n⚙️ 配置摄像头位置...")
+ import sys
+ sys.path.append('tools')
+ import setup_camera_location
+ setup_camera_location.main()
+ print("\n配置完成,请重新选择运行模式")
+ input("按回车键继续...")
+
+ elif choice == "4":
+ print("\n🧪 运行系统测试...")
+ import sys
+ sys.path.append('tests')
+ import test_system
+ test_system.main()
+ print("\n测试完成")
+ input("按回车键继续...")
+
+ elif choice == "0":
+ print("\n👋 再见!")
+ sys.exit(0)
+
+ else:
+ print("\n❌ 无效选择,请重新输入")
+ input("按回车键继续...")
+
+ except KeyboardInterrupt:
+ print("\n\n👋 再见!")
+ sys.exit(0)
+ except Exception as e:
+ print(f"\n❌ 运行出错: {e}")
+ input("按回车键继续...")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/distance-judgement/src/__init__.py b/distance-judgement/src/__init__.py
new file mode 100644
index 00000000..02675b6d
--- /dev/null
+++ b/distance-judgement/src/__init__.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+实时人体距离检测系统 - 核心模块包
+
+包含以下模块:
+- config: 配置文件
+- person_detector: 人体检测模块
+- distance_calculator: 距离计算模块
+"""
+
+__version__ = "1.0.0"
+__author__ = "Distance Detection System"
+
+# 导入核心模块
+from .config import *
+from .person_detector import PersonDetector
+from .distance_calculator import DistanceCalculator
+from .map_manager import MapManager
+from .web_server import WebServer
+from .mobile_connector import MobileConnector, MobileDevice
+from .orientation_detector import OrientationDetector
+from .web_orientation_detector import WebOrientationDetector
+
+__all__ = [
+ 'PersonDetector',
+ 'DistanceCalculator',
+ 'MapManager',
+ 'WebServer',
+ 'MobileConnector',
+ 'MobileDevice',
+ 'CAMERA_INDEX',
+ 'FRAME_WIDTH',
+ 'FRAME_HEIGHT',
+ 'FPS',
+ 'MODEL_PATH',
+ 'CONFIDENCE_THRESHOLD',
+ 'IOU_THRESHOLD',
+ 'KNOWN_PERSON_HEIGHT',
+ 'FOCAL_LENGTH',
+ 'REFERENCE_DISTANCE',
+ 'REFERENCE_HEIGHT_PIXELS',
+ 'FONT',
+ 'FONT_SCALE',
+ 'FONT_THICKNESS',
+ 'BOX_COLOR',
+ 'TEXT_COLOR',
+ 'TEXT_BG_COLOR',
+ 'PERSON_CLASS_ID'
+]
\ No newline at end of file
diff --git a/distance-judgement/src/__pycache__/__init__.cpython-311.pyc b/distance-judgement/src/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 00000000..8c6971c7
Binary files /dev/null and b/distance-judgement/src/__pycache__/__init__.cpython-311.pyc differ
diff --git a/distance-judgement/src/__pycache__/__init__.cpython-39.pyc b/distance-judgement/src/__pycache__/__init__.cpython-39.pyc
new file mode 100644
index 00000000..8a3f9798
Binary files /dev/null and b/distance-judgement/src/__pycache__/__init__.cpython-39.pyc differ
diff --git a/distance-judgement/src/__pycache__/config.cpython-311.pyc b/distance-judgement/src/__pycache__/config.cpython-311.pyc
new file mode 100644
index 00000000..ec3f976d
Binary files /dev/null and b/distance-judgement/src/__pycache__/config.cpython-311.pyc differ
diff --git a/distance-judgement/src/__pycache__/config.cpython-39.pyc b/distance-judgement/src/__pycache__/config.cpython-39.pyc
new file mode 100644
index 00000000..7804005d
Binary files /dev/null and b/distance-judgement/src/__pycache__/config.cpython-39.pyc differ
diff --git a/distance-judgement/src/__pycache__/distance_calculator.cpython-311.pyc b/distance-judgement/src/__pycache__/distance_calculator.cpython-311.pyc
new file mode 100644
index 00000000..1624b4b5
Binary files /dev/null and b/distance-judgement/src/__pycache__/distance_calculator.cpython-311.pyc differ
diff --git a/distance-judgement/src/__pycache__/map_manager.cpython-311.pyc b/distance-judgement/src/__pycache__/map_manager.cpython-311.pyc
new file mode 100644
index 00000000..fcc437f7
Binary files /dev/null and b/distance-judgement/src/__pycache__/map_manager.cpython-311.pyc differ
diff --git a/distance-judgement/src/__pycache__/mobile_connector.cpython-311.pyc b/distance-judgement/src/__pycache__/mobile_connector.cpython-311.pyc
new file mode 100644
index 00000000..b1cbff01
Binary files /dev/null and b/distance-judgement/src/__pycache__/mobile_connector.cpython-311.pyc differ
diff --git a/distance-judgement/src/__pycache__/orientation_detector.cpython-311.pyc b/distance-judgement/src/__pycache__/orientation_detector.cpython-311.pyc
new file mode 100644
index 00000000..33a8bec8
Binary files /dev/null and b/distance-judgement/src/__pycache__/orientation_detector.cpython-311.pyc differ
diff --git a/distance-judgement/src/__pycache__/person_detector.cpython-311.pyc b/distance-judgement/src/__pycache__/person_detector.cpython-311.pyc
new file mode 100644
index 00000000..b41347d6
Binary files /dev/null and b/distance-judgement/src/__pycache__/person_detector.cpython-311.pyc differ
diff --git a/distance-judgement/src/__pycache__/web_orientation_detector.cpython-311.pyc b/distance-judgement/src/__pycache__/web_orientation_detector.cpython-311.pyc
new file mode 100644
index 00000000..9ad2f9a6
Binary files /dev/null and b/distance-judgement/src/__pycache__/web_orientation_detector.cpython-311.pyc differ
diff --git a/distance-judgement/src/__pycache__/web_server.cpython-311.pyc b/distance-judgement/src/__pycache__/web_server.cpython-311.pyc
new file mode 100644
index 00000000..072b8632
Binary files /dev/null and b/distance-judgement/src/__pycache__/web_server.cpython-311.pyc differ
diff --git a/distance-judgement/src/config.py b/distance-judgement/src/config.py
new file mode 100644
index 00000000..309eda0f
--- /dev/null
+++ b/distance-judgement/src/config.py
@@ -0,0 +1,40 @@
+# 配置文件
+import cv2
+
+# 摄像头设置
+CAMERA_INDEX = 0 # 默认摄像头索引
+FRAME_WIDTH = 640
+FRAME_HEIGHT = 480
+FPS = 30
+
+# YOLO模型设置
+MODEL_PATH = 'yolov8n.pt' # YOLOv8 nano模型
+CONFIDENCE_THRESHOLD = 0.5
+IOU_THRESHOLD = 0.45
+
+# 距离计算参数
+# 这些参数需要根据实际摄像头和场景进行标定
+KNOWN_PERSON_HEIGHT = 170 # 假设平均人身高170cm
+FOCAL_LENGTH = 500 # 焦距参数,需要校准
+REFERENCE_DISTANCE = 200 # 参考距离(cm)
+REFERENCE_HEIGHT_PIXELS = 300 # 在参考距离下人体框的像素高度
+
+# 显示设置
+FONT = cv2.FONT_HERSHEY_SIMPLEX
+FONT_SCALE = 0.7
+FONT_THICKNESS = 2
+BOX_COLOR = (0, 255, 0) # 绿色框
+TEXT_COLOR = (255, 255, 255) # 白色文字
+TEXT_BG_COLOR = (0, 0, 0) # 黑色背景
+
+# 人体类别ID(COCO数据集中person的类别ID是0)
+PERSON_CLASS_ID = 0
+
+# 地图配置
+GAODE_API_KEY = "3dcf7fa331c70e62d4683cf40fffc443" # 需要替换为真实的高德API key
+CAMERA_LATITUDE = 28.262339630314234 # 摄像头纬度
+CAMERA_LONGITUDE = 113.04752581515713 # 摄像头经度
+CAMERA_HEADING = 180 # 摄像头朝向角度
+CAMERA_FOV = 60 # 摄像头视场角度
+ENABLE_MAP_DISPLAY = True # 是否启用地图显示
+MAP_AUTO_REFRESH = True # 地图是否自动刷新
\ No newline at end of file
diff --git a/distance-judgement/src/distance_calculator.py b/distance-judgement/src/distance_calculator.py
new file mode 100644
index 00000000..3f1702fc
--- /dev/null
+++ b/distance-judgement/src/distance_calculator.py
@@ -0,0 +1,206 @@
+import numpy as np
+import math
+from . import config
+
+class DistanceCalculator:
+ def __init__(self):
+ self.focal_length = config.FOCAL_LENGTH
+ self.known_height = config.KNOWN_PERSON_HEIGHT
+ self.reference_distance = config.REFERENCE_DISTANCE
+ self.reference_height_pixels = config.REFERENCE_HEIGHT_PIXELS
+
+ def calculate_distance_by_height(self, bbox_height):
+ """
+ 根据人体框高度计算距离
+ 使用相似三角形原理:距离 = (已知高度 × 焦距) / 像素高度
+ """
+ if bbox_height <= 0:
+ return 0
+
+ # 使用参考距离和参考像素高度来校准
+ distance = (self.reference_distance * self.reference_height_pixels) / bbox_height
+ return max(distance, 30) # 最小距离限制为30cm
+
+ def calculate_distance_by_focal_length(self, bbox_height):
+ """
+ 使用焦距公式计算距离
+ 距离 = (真实高度 × 焦距) / 像素高度
+ """
+ if bbox_height <= 0:
+ return 0
+
+ distance = (self.known_height * self.focal_length) / bbox_height
+ return max(distance, 30) # 最小距离限制为30cm
+
+ def calibrate_focal_length(self, known_distance, measured_height_pixels):
+ """
+ 标定焦距
+ 焦距 = (像素高度 × 真实距离) / 真实高度
+ """
+ self.focal_length = (measured_height_pixels * known_distance) / self.known_height
+ print(f"焦距已标定为: {self.focal_length:.2f}")
+
+ def get_distance(self, bbox):
+ """
+ 根据边界框计算距离
+ bbox: [x1, y1, x2, y2]
+ """
+ x1, y1, x2, y2 = bbox
+ bbox_height = y2 - y1
+ bbox_width = x2 - x1
+
+ # 使用高度计算距离(更准确)
+ distance = self.calculate_distance_by_height(bbox_height)
+
+ return distance
+
+ def format_distance(self, distance):
+ """
+ 格式化距离显示
+ """
+ if distance < 100:
+ return f"{distance:.1f}cm"
+ else:
+ return f"{distance/100:.1f}m"
+
+ def calculate_person_gps_position(self, camera_lat, camera_lng, camera_heading,
+ bbox, distance_meters, frame_width, frame_height,
+ camera_fov=60):
+ """
+ 🎯 核心算法:根据摄像头GPS位置、朝向、人体检测框计算人员真实GPS坐标
+
+ Args:
+ camera_lat: 摄像头纬度
+ camera_lng: 摄像头经度
+ camera_heading: 摄像头朝向角度 (0=正北, 90=正东)
+ bbox: 人体检测框 [x1, y1, x2, y2]
+ distance_meters: 人员距离摄像头的距离(米)
+ frame_width: 画面宽度(像素)
+ frame_height: 画面高度(像素)
+ camera_fov: 摄像头水平视场角(度)
+
+ Returns:
+ (person_lat, person_lng): 人员GPS坐标
+ """
+ x1, y1, x2, y2 = bbox
+
+ # 计算人体检测框中心点
+ person_center_x = (x1 + x2) / 2
+ person_center_y = (y1 + y2) / 2
+
+ # 计算人员相对于画面中心的偏移角度
+ frame_center_x = frame_width / 2
+ horizontal_offset_pixels = person_center_x - frame_center_x
+
+ # 将像素偏移转换为角度偏移
+ horizontal_angle_per_pixel = camera_fov / frame_width
+ horizontal_offset_degrees = horizontal_offset_pixels * horizontal_angle_per_pixel
+
+ # 计算人员相对于正北的实际方位角
+ person_bearing = (camera_heading + horizontal_offset_degrees) % 360
+
+ # 使用球面几何计算人员GPS坐标
+ person_lat, person_lng = self._calculate_destination_point(
+ camera_lat, camera_lng, distance_meters, person_bearing
+ )
+
+ return person_lat, person_lng
+
+ def _calculate_destination_point(self, lat, lng, distance, bearing):
+ """
+ 🌍 球面几何计算:根据起点坐标、距离和方位角计算目标点坐标
+
+ Args:
+ lat: 起点纬度
+ lng: 起点经度
+ distance: 距离(米)
+ bearing: 方位角(度,0=正北)
+
+ Returns:
+ (target_lat, target_lng): 目标点坐标
+ """
+ # 地球半径(米)
+ R = 6371000
+
+ # 转换为弧度
+ lat1 = math.radians(lat)
+ lng1 = math.radians(lng)
+ bearing_rad = math.radians(bearing)
+
+ # 球面几何计算目标点坐标
+ lat2 = math.asin(
+ math.sin(lat1) * math.cos(distance / R) +
+ math.cos(lat1) * math.sin(distance / R) * math.cos(bearing_rad)
+ )
+
+ lng2 = lng1 + math.atan2(
+ math.sin(bearing_rad) * math.sin(distance / R) * math.cos(lat1),
+ math.cos(distance / R) - math.sin(lat1) * math.sin(lat2)
+ )
+
+ return math.degrees(lat2), math.degrees(lng2)
+
+ def is_person_in_camera_fov(self, camera_lat, camera_lng, camera_heading,
+ person_lat, person_lng, camera_fov=60, max_distance=100):
+ """
+ 🔍 检查人员是否在摄像头视野范围内
+
+ Args:
+ camera_lat: 摄像头纬度
+ camera_lng: 摄像头经度
+ camera_heading: 摄像头朝向角度
+ person_lat: 人员纬度
+ person_lng: 人员经度
+ camera_fov: 摄像头视场角(度)
+ max_distance: 最大检测距离(米)
+
+ Returns:
+ bool: 是否在视野内
+ """
+ # 计算人员相对于摄像头的距离和方位角
+ distance, bearing = self._calculate_distance_and_bearing(
+ camera_lat, camera_lng, person_lat, person_lng
+ )
+
+ # 检查距离是否在范围内
+ if distance > max_distance:
+ return False
+
+ # 计算人员方位角与摄像头朝向的角度差
+ angle_diff = abs(bearing - camera_heading)
+ if angle_diff > 180:
+ angle_diff = 360 - angle_diff
+
+ # 检查是否在视场角范围内
+ return angle_diff <= camera_fov / 2
+
+ def _calculate_distance_and_bearing(self, lat1, lng1, lat2, lng2):
+ """
+ 🧭 计算两点间距离和方位角
+
+ Returns:
+ (distance_meters, bearing_degrees): 距离(米)和方位角(度)
+ """
+ # 转换为弧度
+ lat1_rad = math.radians(lat1)
+ lng1_rad = math.radians(lng1)
+ lat2_rad = math.radians(lat2)
+ lng2_rad = math.radians(lng2)
+
+ # 计算距离 (Haversine公式)
+ dlat = lat2_rad - lat1_rad
+ dlng = lng2_rad - lng1_rad
+
+ a = (math.sin(dlat/2)**2 +
+ math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlng/2)**2)
+ c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
+ distance = 6371000 * c # 地球半径6371km
+
+ # 计算方位角
+ y = math.sin(dlng) * math.cos(lat2_rad)
+ x = (math.cos(lat1_rad) * math.sin(lat2_rad) -
+ math.sin(lat1_rad) * math.cos(lat2_rad) * math.cos(dlng))
+ bearing = math.atan2(y, x)
+ bearing_degrees = (math.degrees(bearing) + 360) % 360
+
+ return distance, bearing_degrees
\ No newline at end of file
diff --git a/distance-judgement/src/map_manager.py b/distance-judgement/src/map_manager.py
new file mode 100644
index 00000000..f6570be5
--- /dev/null
+++ b/distance-judgement/src/map_manager.py
@@ -0,0 +1,242 @@
+import requests
+import json
+import math
+import webbrowser
+import os
+from typing import List, Tuple, Dict
+import time
+
+class MapManager:
+ """高德地图管理器 - 处理地图显示和坐标标记"""
+
+ def __init__(self, api_key: str = None, camera_lat: float = None, camera_lng: float = None):
+ self.api_key = api_key or "your_gaode_api_key_here" # 需要替换为真实的API key
+ self.camera_lat = camera_lat or 39.9042 # 默认北京天安门坐标
+ self.camera_lng = camera_lng or 116.4074
+ self.camera_heading = 0 # 摄像头朝向角度(正北为0度)
+ self.camera_fov = 60 # 摄像头视场角度
+ self.persons_positions = [] # 人员位置列表
+ self.map_html_path = "person_tracking_map.html"
+
+ def set_camera_position(self, lat: float, lng: float, heading: float = 0):
+ """设置摄像头位置和朝向"""
+ self.camera_lat = lat
+ self.camera_lng = lng
+ self.camera_heading = heading
+ print(f"📍 摄像头位置已设置: ({lat:.6f}, {lng:.6f}), 朝向: {heading}°")
+
+ def calculate_person_position(self, pixel_x: float, pixel_y: float, distance: float,
+ frame_width: int, frame_height: int) -> Tuple[float, float]:
+ """根据人在画面中的像素位置和距离,计算真实地理坐标"""
+ # 将像素坐标转换为相对角度
+ horizontal_angle_per_pixel = self.camera_fov / frame_width
+
+ # 计算人相对于摄像头中心的角度偏移
+ center_x = frame_width / 2
+ horizontal_offset_degrees = (pixel_x - center_x) * horizontal_angle_per_pixel
+
+ # 计算人相对于摄像头的实际角度
+ person_bearing = (self.camera_heading + horizontal_offset_degrees) % 360
+
+ # 将距离和角度转换为地理坐标偏移
+ person_lat, person_lng = self._calculate_destination_point(
+ self.camera_lat, self.camera_lng, distance, person_bearing
+ )
+
+ return person_lat, person_lng
+
+ def _calculate_destination_point(self, lat: float, lng: float, distance: float, bearing: float) -> Tuple[float, float]:
+ """根据起点坐标、距离和方位角计算目标点坐标,使用球面几何学计算"""
+ # 地球半径(米)
+ R = 6371000
+
+ # 转换为弧度
+ lat1 = math.radians(lat)
+ lng1 = math.radians(lng)
+ bearing_rad = math.radians(bearing)
+
+ # 计算目标点坐标
+ lat2 = math.asin(
+ math.sin(lat1) * math.cos(distance / R) +
+ math.cos(lat1) * math.sin(distance / R) * math.cos(bearing_rad)
+ )
+
+ lng2 = lng1 + math.atan2(
+ math.sin(bearing_rad) * math.sin(distance / R) * math.cos(lat1),
+ math.cos(distance / R) - math.sin(lat1) * math.sin(lat2)
+ )
+
+ return math.degrees(lat2), math.degrees(lng2)
+
+ def add_person_position(self, pixel_x: float, pixel_y: float, distance: float,
+ frame_width: int, frame_height: int, person_id: str = None):
+ """添加人员位置"""
+ lat, lng = self.calculate_person_position(pixel_x, pixel_y, distance, frame_width, frame_height)
+
+ person_info = {
+ 'id': person_id or f"person_{len(self.persons_positions) + 1}",
+ 'lat': lat,
+ 'lng': lng,
+ 'distance': distance,
+ 'timestamp': time.time(),
+ 'pixel_x': pixel_x,
+ 'pixel_y': pixel_y
+ }
+
+ self.persons_positions.append(person_info)
+
+ # 只保留最近10秒的数据
+ current_time = time.time()
+ self.persons_positions = [
+ p for p in self.persons_positions
+ if current_time - p['timestamp'] < 10
+ ]
+
+ return lat, lng
+
+ def clear_persons(self):
+ """清空人员位置"""
+ self.persons_positions = []
+
+ def add_person_at_coordinates(self, lat: float, lng: float, person_id: str,
+ distance: float = 0, source: str = "manual"):
+ """直接在指定GPS坐标添加人员标记"""
+ person_data = {
+ 'id': person_id,
+ 'lat': lat,
+ 'lng': lng,
+ 'distance': distance,
+ 'timestamp': time.time(),
+ 'source': source # 标记数据来源(如设备ID)
+ }
+
+ # 添加到人员数据列表
+ self.persons_positions.append(person_data)
+
+ # 只保留最近10秒的数据
+ current_time = time.time()
+ self.persons_positions = [
+ p for p in self.persons_positions
+ if current_time - p['timestamp'] < 10
+ ]
+
+ return lat, lng
+
+ def get_persons_data(self) -> List[Dict]:
+ """获取当前人员数据"""
+ return self.persons_positions
+
+ def generate_map_html(self) -> str:
+ """生成高德地图HTML页面"""
+ persons_data_json = json.dumps(self.persons_positions)
+
+ html_content = f"""
+
+
+
+ 实时人员位置追踪系统 🚁
+
+
+
+
+
+
+
+
🚁 无人机战场态势感知
+
● 摄像头在线
+
📍 坐标: {self.camera_lat:.6f}, {self.camera_lng:.6f}
+
🧭 朝向: {self.camera_heading}°
+
👥 检测到: {len(self.persons_positions)} 人
+
+ 🔴 红点 = 人员位置
+ 📷 蓝点 = 摄像头位置
+ ⚡ 实时更新
+
+
+
+
+
+"""
+
+ # 保存HTML文件
+ with open(self.map_html_path, 'w', encoding='utf-8') as f:
+ f.write(html_content)
+
+ return self.map_html_path
+
+ def open_map(self):
+ """在浏览器中打开地图"""
+ html_path = self.generate_map_html()
+ file_url = f"file://{os.path.abspath(html_path)}"
+ webbrowser.open(file_url)
+ print(f"🗺️ 地图已在浏览器中打开: {html_path}")
+
+ def update_camera_heading(self, new_heading: float):
+ """更新摄像头朝向"""
+ self.camera_heading = new_heading
+ print(f"🧭 摄像头朝向已更新: {new_heading}°")
\ No newline at end of file
diff --git a/distance-judgement/src/mobile_connector.py b/distance-judgement/src/mobile_connector.py
new file mode 100644
index 00000000..cd4d6354
--- /dev/null
+++ b/distance-judgement/src/mobile_connector.py
@@ -0,0 +1,303 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+手机连接器模块
+用于接收手机传送的摄像头图像、GPS位置和设备信息
+"""
+
+import cv2
+import numpy as np
+import json
+import time
+import threading
+from datetime import datetime
+import base64
+import socket
+import struct
+from typing import Dict, List, Optional, Tuple, Callable
+from . import config
+
+class MobileDevice:
+ """移动设备信息类"""
+ def __init__(self, device_id: str, device_name: str):
+ self.device_id = device_id
+ self.device_name = device_name
+ self.last_seen = time.time()
+ self.is_online = True
+ self.current_location = None # (lat, lng, accuracy)
+ self.battery_level = 100
+ self.signal_strength = 100
+ self.camera_info = {}
+ self.connection_info = {}
+
+ def update_status(self, data: dict):
+ """更新设备状态"""
+ self.last_seen = time.time()
+ self.is_online = True
+
+ if 'gps' in data:
+ self.current_location = (
+ data['gps'].get('latitude'),
+ data['gps'].get('longitude'),
+ data['gps'].get('accuracy', 0)
+ )
+
+ if 'battery' in data:
+ self.battery_level = data['battery']
+
+ if 'signal' in data:
+ self.signal_strength = data['signal']
+
+ if 'camera_info' in data:
+ self.camera_info = data['camera_info']
+
+ def is_location_valid(self) -> bool:
+ """检查GPS位置是否有效"""
+ if not self.current_location:
+ return False
+ lat, lng, _ = self.current_location
+ return lat is not None and lng is not None and -90 <= lat <= 90 and -180 <= lng <= 180
+
+class MobileConnector:
+ """手机连接器主类"""
+
+ def __init__(self, port: int = 8080):
+ self.port = port
+ self.server_socket = None
+ self.is_running = False
+ self.devices = {} # device_id -> MobileDevice
+ self.frame_callbacks = [] # 帧数据回调函数列表
+ self.location_callbacks = [] # 位置数据回调函数列表
+ self.device_callbacks = [] # 设备状态回调函数列表
+ self.client_threads = []
+
+ # 统计信息
+ self.total_frames_received = 0
+ self.total_data_received = 0
+ self.start_time = time.time()
+
+ def add_frame_callback(self, callback: Callable):
+ """添加帧数据回调函数"""
+ self.frame_callbacks.append(callback)
+
+ def add_location_callback(self, callback: Callable):
+ """添加位置数据回调函数"""
+ self.location_callbacks.append(callback)
+
+ def add_device_callback(self, callback: Callable):
+ """添加设备状态回调函数"""
+ self.device_callbacks.append(callback)
+
+ def start_server(self):
+ """启动服务器"""
+ try:
+ self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.server_socket.bind(('0.0.0.0', self.port))
+ self.server_socket.listen(5)
+ self.is_running = True
+
+ print(f"📱 手机连接服务器启动成功,端口: {self.port}")
+ print(f"🌐 等待手机客户端连接...")
+
+ # 启动服务器监听线程
+ server_thread = threading.Thread(target=self._server_loop, daemon=True)
+ server_thread.start()
+
+ # 启动设备状态监控线程
+ monitor_thread = threading.Thread(target=self._device_monitor, daemon=True)
+ monitor_thread.start()
+
+ return True
+
+ except Exception as e:
+ print(f"❌ 启动服务器失败: {e}")
+ return False
+
+ def stop_server(self):
+ """停止服务器"""
+ self.is_running = False
+ if self.server_socket:
+ self.server_socket.close()
+
+ # 清理客户端连接
+ for thread in self.client_threads:
+ if thread.is_alive():
+ thread.join(timeout=1.0)
+
+ print("📱 手机连接服务器已停止")
+
+ def _server_loop(self):
+ """服务器主循环"""
+ while self.is_running:
+ try:
+ client_socket, address = self.server_socket.accept()
+ print(f"📱 新的手机客户端连接: {address}")
+
+ # 为每个客户端创建处理线程
+ client_thread = threading.Thread(
+ target=self._handle_client,
+ args=(client_socket, address),
+ daemon=True
+ )
+ client_thread.start()
+ self.client_threads.append(client_thread)
+
+ except Exception as e:
+ if self.is_running:
+ print(f"⚠️ 服务器接受连接时出错: {e}")
+ break
+
+ def _handle_client(self, client_socket, address):
+ """处理客户端连接"""
+ device_id = None
+ try:
+ while self.is_running:
+ # 接收数据长度
+ length_data = self._recv_all(client_socket, 4)
+ if not length_data:
+ break
+
+ data_length = struct.unpack('!I', length_data)[0]
+
+ # 接收JSON数据
+ json_data = self._recv_all(client_socket, data_length)
+ if not json_data:
+ break
+
+ try:
+ data = json.loads(json_data.decode('utf-8'))
+ device_id = data.get('device_id')
+
+ if device_id:
+ self._process_mobile_data(device_id, data, address)
+ self.total_data_received += len(json_data)
+
+ except json.JSONDecodeError as e:
+ print(f"⚠️ JSON解析错误: {e}")
+ continue
+
+ except Exception as e:
+ print(f"⚠️ 处理客户端 {address} 时出错: {e}")
+ finally:
+ client_socket.close()
+ if device_id and device_id in self.devices:
+ self.devices[device_id].is_online = False
+ print(f"📱 设备 {device_id} 已断开连接")
+
+ def _recv_all(self, socket, length):
+ """接收指定长度的数据"""
+ data = b''
+ while len(data) < length:
+ packet = socket.recv(length - len(data))
+ if not packet:
+ return None
+ data += packet
+ return data
+
+ def _process_mobile_data(self, device_id: str, data: dict, address):
+ """处理手机发送的数据"""
+ # 更新或创建设备信息
+ if device_id not in self.devices:
+ device_name = data.get('device_name', f'Mobile-{device_id[:8]}')
+ self.devices[device_id] = MobileDevice(device_id, device_name)
+ print(f"📱 新设备注册: {device_name} ({device_id[:8]})")
+
+ # 触发设备状态回调
+ for callback in self.device_callbacks:
+ try:
+ callback('device_connected', self.devices[device_id])
+ except Exception as e:
+ print(f"⚠️ 设备回调错误: {e}")
+
+ device = self.devices[device_id]
+ device.update_status(data)
+ device.connection_info = {'address': address}
+
+ # 处理图像数据
+ if 'frame' in data:
+ try:
+ frame_data = base64.b64decode(data['frame'])
+ frame = cv2.imdecode(
+ np.frombuffer(frame_data, np.uint8),
+ cv2.IMREAD_COLOR
+ )
+
+ if frame is not None:
+ self.total_frames_received += 1
+
+ # 触发帧数据回调
+ for callback in self.frame_callbacks:
+ try:
+ callback(device_id, frame, device)
+ except Exception as e:
+ print(f"⚠️ 帧回调错误: {e}")
+
+ except Exception as e:
+ print(f"⚠️ 图像数据处理错误: {e}")
+
+ # 处理GPS位置数据
+ if 'gps' in data and device.is_location_valid():
+ for callback in self.location_callbacks:
+ try:
+ callback(device_id, device.current_location, device)
+ except Exception as e:
+ print(f"⚠️ 位置回调错误: {e}")
+
+ def _device_monitor(self):
+ """设备状态监控"""
+ while self.is_running:
+ try:
+ current_time = time.time()
+ offline_devices = []
+
+ for device_id, device in self.devices.items():
+ # 超过30秒没有数据认为离线
+ if current_time - device.last_seen > 30:
+ if device.is_online:
+ device.is_online = False
+ offline_devices.append(device_id)
+
+ # 通知离线设备
+ for device_id in offline_devices:
+ print(f"📱 设备 {device_id[:8]} 已离线")
+ for callback in self.device_callbacks:
+ try:
+ callback('device_disconnected', self.devices[device_id])
+ except Exception as e:
+ print(f"⚠️ 设备回调错误: {e}")
+
+ time.sleep(5) # 每5秒检查一次
+
+ except Exception as e:
+ print(f"⚠️ 设备监控错误: {e}")
+ time.sleep(5)
+
+ def get_online_devices(self) -> List[MobileDevice]:
+ """获取在线设备列表"""
+ return [device for device in self.devices.values() if device.is_online]
+
+ def get_device_by_id(self, device_id: str) -> Optional[MobileDevice]:
+ """根据ID获取设备"""
+ return self.devices.get(device_id)
+
+ def get_statistics(self) -> dict:
+ """获取连接统计信息"""
+ online_count = len(self.get_online_devices())
+ total_count = len(self.devices)
+ uptime = time.time() - self.start_time
+
+ return {
+ 'online_devices': online_count,
+ 'total_devices': total_count,
+ 'frames_received': self.total_frames_received,
+ 'data_received_mb': self.total_data_received / (1024 * 1024),
+ 'uptime_seconds': uptime,
+ 'avg_frames_per_second': self.total_frames_received / uptime if uptime > 0 else 0
+ }
+
+ def send_command_to_device(self, device_id: str, command: dict):
+ """向指定设备发送命令(预留接口)"""
+ # TODO: 实现向手机发送控制命令的功能
+ pass
\ No newline at end of file
diff --git a/distance-judgement/src/orientation_detector.py b/distance-judgement/src/orientation_detector.py
new file mode 100644
index 00000000..d220e20c
--- /dev/null
+++ b/distance-judgement/src/orientation_detector.py
@@ -0,0 +1,295 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+设备朝向检测模块
+用于自动获取设备的GPS位置和朝向信息
+"""
+
+import requests
+import time
+import json
+import math
+from typing import Tuple, Optional, Dict
+from . import config
+
+
+class OrientationDetector:
+ """设备朝向检测器"""
+
+ def __init__(self):
+ self.current_location = None # (lat, lng, accuracy)
+ self.current_heading = None # 设备朝向角度
+ self.last_update = 0
+ self.gps_cache_duration = 300 # GPS缓存5分钟
+
+ def get_current_gps_location(self) -> Optional[Tuple[float, float, float]]:
+ """
+ 获取当前设备的GPS位置
+ 返回: (纬度, 经度, 精度) 或 None
+ """
+ try:
+ # 首先尝试使用系统API (需要安装相关库)
+ location = self._get_system_gps()
+ if location:
+ return location
+
+ # 如果系统API不可用,使用IP地理定位作为备选
+ location = self._get_ip_geolocation()
+ if location:
+ print("🌐 使用IP地理定位获取位置(精度较低)")
+ return location
+
+ return None
+
+ except Exception as e:
+ print(f"❌ GPS位置获取失败: {e}")
+ return None
+
+ def _get_system_gps(self) -> Optional[Tuple[float, float, float]]:
+ """尝试使用系统GPS API获取位置"""
+ try:
+ # 在Windows上可以使用Windows Location API
+ # 这里提供一个框架,实际实现需要根据操作系统选择合适的API
+ import platform
+ system = platform.system()
+
+ if system == "Windows":
+ return self._get_windows_location()
+ elif system == "Darwin": # macOS
+ return self._get_macos_location()
+ elif system == "Linux":
+ return self._get_linux_location()
+
+ except ImportError:
+ print("💡 系统定位API不可用,将使用IP定位")
+
+ return None
+
+ def _get_windows_location(self) -> Optional[Tuple[float, float, float]]:
+ """Windows系统GPS定位"""
+ try:
+ # 使用Windows Location API
+ import winrt.windows.devices.geolocation as geo
+
+ locator = geo.Geolocator()
+ # 设置期望精度
+ locator.desired_accuracy = geo.PositionAccuracy.HIGH
+
+ print("🔍 正在获取Windows系统GPS位置...")
+
+ # 获取位置信息(同步方式)
+ position = locator.get_geoposition_async().get()
+
+ lat = position.coordinate.point.position.latitude
+ lng = position.coordinate.point.position.longitude
+ accuracy = position.coordinate.accuracy
+
+ print(f"✅ Windows GPS获取成功: ({lat:.6f}, {lng:.6f}), 精度: ±{accuracy:.0f}m")
+ return (lat, lng, accuracy)
+
+ except Exception as e:
+ print(f"⚠️ Windows GPS API失败: {e}")
+ return None
+
+ def _get_macos_location(self) -> Optional[Tuple[float, float, float]]:
+ """macOS系统GPS定位"""
+ try:
+ # macOS可以使用Core Location框架
+ # 这里提供一个基本框架
+ print("💡 macOS GPS定位需要额外配置,建议使用IP定位")
+ return None
+
+ except Exception as e:
+ print(f"⚠️ macOS GPS API失败: {e}")
+ return None
+
+ def _get_linux_location(self) -> Optional[Tuple[float, float, float]]:
+ """Linux系统GPS定位"""
+ try:
+ # Linux可以使用gpsd或NetworkManager
+ print("💡 Linux GPS定位需要额外配置,建议使用IP定位")
+ return None
+
+ except Exception as e:
+ print(f"⚠️ Linux GPS API失败: {e}")
+ return None
+
+ def _get_ip_geolocation(self) -> Optional[Tuple[float, float, float]]:
+ """使用IP地址进行地理定位"""
+ try:
+ print("🌐 正在使用IP地理定位...")
+
+ # 使用免费的IP地理定位服务
+ response = requests.get("http://ip-api.com/json/", timeout=10)
+
+ if response.status_code == 200:
+ data = response.json()
+
+ if data.get('status') == 'success':
+ lat = float(data.get('lat', 0))
+ lng = float(data.get('lon', 0))
+ accuracy = 10000 # IP定位精度通常在10km左右
+
+ city = data.get('city', '未知')
+ region = data.get('regionName', '未知')
+ country = data.get('country', '未知')
+
+ print(f"✅ IP定位成功: {city}, {region}, {country}")
+ print(f"📍 位置: ({lat:.6f}, {lng:.6f}), 精度: ±{accuracy:.0f}m")
+
+ return (lat, lng, accuracy)
+
+ except Exception as e:
+ print(f"❌ IP地理定位失败: {e}")
+
+ return None
+
+ def get_device_heading(self) -> Optional[float]:
+ """
+ 获取设备朝向(磁力计方向)
+ 返回: 角度 (0-360度,0为正北) 或 None
+ """
+ try:
+ # 桌面设备通常没有磁力计,返回默认朝向
+ # 可以根据摄像头位置或用户设置来确定朝向
+ print("💡 桌面设备朝向检测有限,使用默认朝向")
+
+ # 假设用户面向屏幕,摄像头朝向用户
+ # 如果摄像头在屏幕上方,那么朝向就是用户的相反方向
+ default_heading = 180.0 # 假设用户面向南方,摄像头朝向北方
+
+ return default_heading
+
+ except Exception as e:
+ print(f"❌ 设备朝向检测失败: {e}")
+ return None
+
+ def calculate_camera_heading_facing_user(self, user_heading: float) -> float:
+ """
+ 计算摄像头朝向用户的角度
+
+ Args:
+ user_heading: 用户朝向角度 (0-360度)
+
+ Returns:
+ 摄像头应该设置的朝向角度
+ """
+ # 摄像头朝向用户,即朝向用户相反的方向
+ camera_heading = (user_heading + 180) % 360
+ return camera_heading
+
+ def auto_configure_camera_location(self) -> Dict:
+ """
+ 自动配置摄像头位置和朝向
+
+ Returns:
+ 配置信息字典
+ """
+ result = {
+ 'success': False,
+ 'gps_location': None,
+ 'device_heading': None,
+ 'camera_heading': None,
+ 'method': None,
+ 'accuracy': None
+ }
+
+ print("🚀 开始自动配置摄像头位置和朝向...")
+
+ # 1. 获取GPS位置
+ gps_location = self.get_current_gps_location()
+ if not gps_location:
+ print("❌ 无法获取GPS位置,自动配置失败")
+ return result
+
+ lat, lng, accuracy = gps_location
+ result['gps_location'] = (lat, lng)
+ result['accuracy'] = accuracy
+
+ # 2. 获取设备朝向
+ device_heading = self.get_device_heading()
+ if device_heading is None:
+ print("⚠️ 无法获取设备朝向,使用默认朝向")
+ device_heading = 0.0 # 默认朝北
+
+ result['device_heading'] = device_heading
+
+ # 3. 计算摄像头朝向(朝向用户)
+ camera_heading = self.calculate_camera_heading_facing_user(device_heading)
+ result['camera_heading'] = camera_heading
+
+ # 4. 确定配置方法
+ if accuracy < 100:
+ result['method'] = 'GPS'
+ else:
+ result['method'] = 'IP定位'
+
+ result['success'] = True
+
+ print(f"✅ 自动配置完成:")
+ print(f"📍 GPS位置: ({lat:.6f}, {lng:.6f})")
+ print(f"🧭 设备朝向: {device_heading:.1f}°")
+ print(f"📷 摄像头朝向: {camera_heading:.1f}°")
+ print(f"🎯 定位方法: {result['method']}")
+ print(f"📏 定位精度: ±{accuracy:.0f}m")
+
+ return result
+
+ def update_camera_config(self, gps_location: Tuple[float, float], camera_heading: float):
+ """
+ 更新摄像头配置文件
+
+ Args:
+ gps_location: (纬度, 经度)
+ camera_heading: 摄像头朝向角度
+ """
+ try:
+ from tools.setup_camera_location import update_config_file
+
+ lat, lng = gps_location
+
+ # 更新配置文件
+ update_config_file(lat, lng, camera_heading)
+
+ # 同时更新运行时配置
+ config.CAMERA_LATITUDE = lat
+ config.CAMERA_LONGITUDE = lng
+ config.CAMERA_HEADING = camera_heading
+
+ print(f"✅ 摄像头配置已更新")
+ print(f"📍 新位置: ({lat:.6f}, {lng:.6f})")
+ print(f"🧭 新朝向: {camera_heading:.1f}°")
+
+ except Exception as e:
+ print(f"❌ 配置更新失败: {e}")
+
+
+def main():
+ """测试函数"""
+ print("🧭 设备朝向检测器测试")
+ print("=" * 50)
+
+ detector = OrientationDetector()
+
+ # 测试自动配置
+ result = detector.auto_configure_camera_location()
+
+ if result['success']:
+ print("\n🎯 是否应用此配置? (y/n): ", end="")
+ choice = input().strip().lower()
+
+ if choice == 'y':
+ detector.update_camera_config(
+ result['gps_location'],
+ result['camera_heading']
+ )
+ print("✅ 配置已应用")
+ else:
+ print("⏭️ 配置未应用")
+ else:
+ print("❌ 自动配置失败")
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/distance-judgement/src/person_detector.py b/distance-judgement/src/person_detector.py
new file mode 100644
index 00000000..cb84178b
--- /dev/null
+++ b/distance-judgement/src/person_detector.py
@@ -0,0 +1,100 @@
+import cv2
+import numpy as np
+from ultralytics import YOLO
+from . import config
+
+class PersonDetector:
+ def __init__(self):
+ self.model = None
+ self.load_model()
+
+ def load_model(self):
+ """加载YOLO模型"""
+ try:
+ self.model = YOLO(config.MODEL_PATH)
+ print(f"YOLO模型加载成功: {config.MODEL_PATH}")
+ except Exception as e:
+ print(f"模型加载失败: {e}")
+ print("正在下载YOLOv8n模型...")
+ self.model = YOLO('yolov8n.pt') # 会自动下载
+
+ def detect_persons(self, frame):
+ """
+ 检测图像中的人体
+ 返回: 检测结果列表,每个结果包含 [x1, y1, x2, y2, confidence]
+ """
+ if self.model is None:
+ return []
+
+ try:
+ # 使用YOLO进行检测
+ results = self.model(frame, verbose=False)
+
+ persons = []
+ for result in results:
+ boxes = result.boxes
+ if boxes is not None:
+ for box in boxes:
+ # 获取类别、置信度和坐标
+ cls = int(box.cls[0])
+ conf = float(box.conf[0])
+
+ # 只保留人体检测结果
+ if cls == config.PERSON_CLASS_ID and conf >= config.CONFIDENCE_THRESHOLD:
+ # 获取边界框坐标
+ x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
+ persons.append([int(x1), int(y1), int(x2), int(y2), conf])
+
+ return persons
+
+ except Exception as e:
+ print(f"检测过程中出错: {e}")
+ return []
+
+ def draw_detections(self, frame, detections, distances):
+ """
+ 在图像上绘制检测结果和距离信息
+ """
+ for i, detection in enumerate(detections):
+ x1, y1, x2, y2, conf = detection
+
+ # 绘制边界框
+ cv2.rectangle(frame, (x1, y1), (x2, y2), config.BOX_COLOR, 2)
+
+ # 准备显示文本
+ person_id = f"Person #{i+1}"
+ distance_text = f"Distance: {distances[i]}" if i < len(distances) else "Distance: N/A"
+ conf_text = f"Conf: {conf:.2f}"
+
+ # 计算文本位置
+ text_y = y1 - 35 if y1 - 35 > 20 else y1 + 20
+
+ # 绘制人员ID文本背景和文字
+ id_text_size = cv2.getTextSize(person_id, config.FONT, config.FONT_SCALE, config.FONT_THICKNESS)[0]
+ cv2.rectangle(frame, (x1, text_y - id_text_size[1] - 5),
+ (x1 + id_text_size[0] + 10, text_y + 5), (255, 0, 0), -1)
+ cv2.putText(frame, person_id, (x1 + 5, text_y),
+ config.FONT, config.FONT_SCALE, config.TEXT_COLOR, config.FONT_THICKNESS)
+
+ # 绘制距离文本背景和文字
+ distance_text_y = text_y + 25
+ distance_text_size = cv2.getTextSize(distance_text, config.FONT, config.FONT_SCALE, config.FONT_THICKNESS)[0]
+ cv2.rectangle(frame, (x1, distance_text_y - distance_text_size[1] - 5),
+ (x1 + distance_text_size[0] + 10, distance_text_y + 5), config.TEXT_BG_COLOR, -1)
+ cv2.putText(frame, distance_text, (x1 + 5, distance_text_y),
+ config.FONT, config.FONT_SCALE, config.TEXT_COLOR, config.FONT_THICKNESS)
+
+ # 绘制置信度文本(在框的右上角)
+ conf_text_size = cv2.getTextSize(conf_text, config.FONT, config.FONT_SCALE - 0.2, config.FONT_THICKNESS)[0]
+ cv2.rectangle(frame, (x2 - conf_text_size[0] - 10, y1),
+ (x2, y1 + conf_text_size[1] + 10), config.TEXT_BG_COLOR, -1)
+ cv2.putText(frame, conf_text, (x2 - conf_text_size[0] - 5, y1 + conf_text_size[1] + 5),
+ config.FONT, config.FONT_SCALE - 0.2, config.TEXT_COLOR, config.FONT_THICKNESS)
+
+ return frame
+
+ def get_model_info(self):
+ """获取模型信息"""
+ if self.model:
+ return f"YOLO Model: {config.MODEL_PATH}"
+ return "Model not loaded"
\ No newline at end of file
diff --git a/distance-judgement/src/web_orientation_detector.py b/distance-judgement/src/web_orientation_detector.py
new file mode 100644
index 00000000..7d004305
--- /dev/null
+++ b/distance-judgement/src/web_orientation_detector.py
@@ -0,0 +1,335 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Web端朝向检测器
+提供Web API接口用于获取GPS位置和设备朝向信息
+"""
+
+from flask import Blueprint, jsonify, request
+import json
+import time
+from typing import Dict, Optional, Tuple
+from . import config
+from .orientation_detector import OrientationDetector
+
+
+class WebOrientationDetector:
+ """Web端朝向检测器"""
+
+ def __init__(self):
+ self.orientation_detector = OrientationDetector()
+ self.current_web_location = None
+ self.current_web_heading = None
+ self.last_web_update = 0
+
+ # 创建Blueprint
+ self.blueprint = Blueprint('orientation', __name__)
+ self.setup_routes()
+
+ def setup_routes(self):
+ """设置Web API路由"""
+
+ @self.blueprint.route('/api/orientation/auto_configure', methods=['POST'])
+ def auto_configure_from_web():
+ """从Web端自动配置摄像头位置和朝向"""
+ try:
+ data = request.get_json() or {}
+ print(f"🔍 收到自动配置请求: {data}")
+
+ # 支持两种数据格式
+ # 新格式: {gps_location: [lat, lng], user_heading: heading, apply_config: true}
+ # 旧格式: {gps: {...}, orientation: {...}}
+
+ if 'gps_location' in data:
+ # 新格式处理
+ gps_location = data.get('gps_location')
+ user_heading = data.get('user_heading', 0)
+ apply_config = data.get('apply_config', True)
+
+ if not gps_location or len(gps_location) < 2:
+ return jsonify({
+ "success": False,
+ "error": "GPS位置数据格式错误"
+ })
+
+ lat, lng = float(gps_location[0]), float(gps_location[1])
+
+ # 验证坐标范围
+ if not (-90 <= lat <= 90) or not (-180 <= lng <= 180):
+ return jsonify({
+ "success": False,
+ "error": "GPS坐标范围不正确"
+ })
+
+ # 计算摄像头朝向
+ if user_heading is not None:
+ # 计算摄像头朝向(朝向用户方向)
+ camera_heading = (user_heading + 180) % 360
+ else:
+ camera_heading = 0.0
+
+ print(f"📍 处理GPS位置: ({lat:.6f}, {lng:.6f})")
+ print(f"🧭 用户朝向: {user_heading}°, 摄像头朝向: {camera_heading}°")
+
+ if apply_config:
+ # 应用配置
+ self.orientation_detector.update_camera_config((lat, lng), camera_heading)
+ print(f"✅ 配置已应用到系统")
+
+ return jsonify({
+ "success": True,
+ "message": "摄像头位置和朝向已自动配置",
+ "gps_location": [lat, lng],
+ "user_heading": user_heading,
+ "camera_heading": camera_heading,
+ "applied": apply_config
+ })
+
+ else:
+ # 旧格式处理
+ gps_data = data.get('gps')
+ orientation_data = data.get('orientation')
+
+ if not gps_data:
+ # 如果前端没有提供GPS,尝试后端获取
+ result = self.orientation_detector.auto_configure_camera_location()
+ else:
+ # 使用前端提供的数据
+ result = self.process_web_data(gps_data, orientation_data)
+
+ if result['success']:
+ # 应用配置
+ self.orientation_detector.update_camera_config(
+ result['gps_location'],
+ result['camera_heading']
+ )
+
+ return jsonify({
+ "success": True,
+ "message": "摄像头位置和朝向已自动配置",
+ **result
+ })
+ else:
+ return jsonify({
+ "success": False,
+ "error": result.get('error', '自动配置失败')
+ })
+
+ except Exception as e:
+ print(f"❌ 自动配置异常: {e}")
+ import traceback
+ traceback.print_exc()
+ return jsonify({
+ "success": False,
+ "error": f"配置失败: {str(e)}"
+ })
+
+ @self.blueprint.route('/api/orientation/update_location', methods=['POST'])
+ def update_location():
+ """更新GPS位置信息"""
+ try:
+ data = request.get_json()
+
+ if not data or 'latitude' not in data or 'longitude' not in data:
+ return jsonify({
+ "status": "error",
+ "message": "缺少位置信息"
+ })
+
+ lat = float(data['latitude'])
+ lng = float(data['longitude'])
+ accuracy = float(data.get('accuracy', 1000))
+
+ # 验证坐标范围
+ if not (-90 <= lat <= 90) or not (-180 <= lng <= 180):
+ return jsonify({
+ "status": "error",
+ "message": "坐标范围不正确"
+ })
+
+ # 更新位置信息
+ self.current_web_location = (lat, lng, accuracy)
+ self.last_web_update = time.time()
+
+ print(f"📍 Web GPS更新: ({lat:.6f}, {lng:.6f}), 精度: ±{accuracy:.0f}m")
+
+ return jsonify({
+ "status": "success",
+ "message": "位置信息已更新"
+ })
+
+ except Exception as e:
+ return jsonify({
+ "status": "error",
+ "message": f"位置更新失败: {str(e)}"
+ })
+
+ @self.blueprint.route('/api/orientation/update_heading', methods=['POST'])
+ def update_heading():
+ """更新设备朝向信息"""
+ try:
+ data = request.get_json()
+
+ if not data or 'heading' not in data:
+ return jsonify({
+ "status": "error",
+ "message": "缺少朝向信息"
+ })
+
+ heading = float(data['heading'])
+
+ # 标准化角度到0-360范围
+ heading = heading % 360
+
+ # 更新朝向信息
+ self.current_web_heading = heading
+ self.last_web_update = time.time()
+
+ print(f"🧭 Web朝向更新: {heading:.1f}°")
+
+ return jsonify({
+ "status": "success",
+ "message": "朝向信息已更新"
+ })
+
+ except Exception as e:
+ return jsonify({
+ "status": "error",
+ "message": f"朝向更新失败: {str(e)}"
+ })
+
+ @self.blueprint.route('/api/orientation/get_status')
+ def get_orientation_status():
+ """获取当前朝向状态"""
+ try:
+ current_time = time.time()
+
+ # 检查数据是否过期(30秒)
+ web_data_fresh = (current_time - self.last_web_update) < 30
+
+ status = {
+ "web_location": self.current_web_location,
+ "web_heading": self.current_web_heading,
+ "web_data_fresh": web_data_fresh,
+ "last_update": self.last_web_update,
+ "current_config": {
+ "latitude": config.CAMERA_LATITUDE,
+ "longitude": config.CAMERA_LONGITUDE,
+ "heading": config.CAMERA_HEADING
+ }
+ }
+
+ return jsonify({
+ "status": "success",
+ "data": status
+ })
+
+ except Exception as e:
+ return jsonify({
+ "status": "error",
+ "message": f"状态获取失败: {str(e)}"
+ })
+
+ @self.blueprint.route('/api/orientation/apply_config', methods=['POST'])
+ def apply_config():
+ """应用当前的位置和朝向配置"""
+ try:
+ if not self.current_web_location:
+ return jsonify({
+ "status": "error",
+ "message": "没有可用的位置信息"
+ })
+
+ lat, lng, accuracy = self.current_web_location
+
+ # 使用Web朝向或默认朝向
+ if self.current_web_heading is not None:
+ # 计算摄像头朝向(朝向用户)
+ camera_heading = self.orientation_detector.calculate_camera_heading_facing_user(
+ self.current_web_heading
+ )
+ else:
+ # 使用默认朝向
+ camera_heading = 0.0
+
+ # 应用配置
+ self.orientation_detector.update_camera_config((lat, lng), camera_heading)
+
+ return jsonify({
+ "status": "success",
+ "message": "配置已应用",
+ "data": {
+ "latitude": lat,
+ "longitude": lng,
+ "camera_heading": camera_heading,
+ "accuracy": accuracy
+ }
+ })
+
+ except Exception as e:
+ return jsonify({
+ "status": "error",
+ "message": f"配置应用失败: {str(e)}"
+ })
+
+ def process_web_data(self, gps_data: Dict, orientation_data: Optional[Dict] = None) -> Dict:
+ """
+ 处理来自Web端的GPS和朝向数据
+
+ Args:
+ gps_data: GPS数据 {'latitude': float, 'longitude': float, 'accuracy': float}
+ orientation_data: 朝向数据 {'heading': float} (可选)
+
+ Returns:
+ 配置结果字典
+ """
+ result = {
+ 'success': False,
+ 'gps_location': None,
+ 'device_heading': None,
+ 'camera_heading': None,
+ 'method': 'Web',
+ 'accuracy': None
+ }
+
+ try:
+ # 处理GPS数据
+ lat = float(gps_data['latitude'])
+ lng = float(gps_data['longitude'])
+ accuracy = float(gps_data.get('accuracy', 1000))
+
+ # 验证坐标
+ if not (-90 <= lat <= 90) or not (-180 <= lng <= 180):
+ raise ValueError("坐标范围不正确")
+
+ result['gps_location'] = (lat, lng)
+ result['accuracy'] = accuracy
+
+ # 处理朝向数据
+ device_heading = 0.0 # 默认朝向
+ if orientation_data and 'heading' in orientation_data:
+ device_heading = float(orientation_data['heading']) % 360
+
+ result['device_heading'] = device_heading
+
+ # 计算摄像头朝向(面向用户)
+ camera_heading = self.orientation_detector.calculate_camera_heading_facing_user(device_heading)
+ result['camera_heading'] = camera_heading
+
+ result['success'] = True
+
+ print(f"✅ Web数据处理完成:")
+ print(f"📍 GPS位置: ({lat:.6f}, {lng:.6f})")
+ print(f"🧭 设备朝向: {device_heading:.1f}°")
+ print(f"📷 摄像头朝向: {camera_heading:.1f}°")
+ print(f"📏 定位精度: ±{accuracy:.0f}m")
+
+ except Exception as e:
+ print(f"❌ Web数据处理失败: {e}")
+
+ return result
+
+ def get_blueprint(self):
+ """获取Flask Blueprint"""
+ return self.blueprint
\ No newline at end of file
diff --git a/distance-judgement/src/web_server.py b/distance-judgement/src/web_server.py
new file mode 100644
index 00000000..6be0db32
--- /dev/null
+++ b/distance-judgement/src/web_server.py
@@ -0,0 +1,3961 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from flask import Flask, jsonify, request
+import threading
+import time
+import cv2
+import base64
+import numpy as np
+from . import config
+from .person_detector import PersonDetector
+from .distance_calculator import DistanceCalculator
+from .map_manager import MapManager
+from .mobile_connector import MobileConnector
+from .web_orientation_detector import WebOrientationDetector
+
+class WebServer:
+ """Web服务器类,管理地图界面和摄像头控制"""
+
+ def __init__(self):
+ self.app = Flask(__name__)
+ self.map_manager = MapManager(
+ api_key=config.GAODE_API_KEY,
+ camera_lat=config.CAMERA_LATITUDE,
+ camera_lng=config.CAMERA_LONGITUDE
+ )
+
+ # 摄像头相关
+ self.camera_active = False
+ self.cap = None
+ self.detector = None
+ self.distance_calculator = None
+ self.camera_thread = None
+ self.current_frame = None
+ self.detection_data = []
+
+ # 手机连接相关
+ self.mobile_connector = MobileConnector(port=8080)
+ self.mobile_frames = {} # device_id -> latest frame
+ self.mobile_mode = False # 是否启用手机模式
+
+ # 🌟 移动端实时数据存储
+ self.mobile_locations = {} # device_id -> latest location
+ self.mobile_orientations = {} # device_id -> latest orientation
+
+ # 朝向检测相关
+ self.web_orientation_detector = WebOrientationDetector()
+
+ # 设置路由
+ self.setup_routes()
+
+ # 设置手机连接器回调
+ self.setup_mobile_callbacks()
+
+ # 注册朝向检测API
+ self.app.register_blueprint(self.web_orientation_detector.get_blueprint())
+
+ # 设置安全头部
+ self.setup_security_headers()
+
+ def setup_security_headers(self):
+ """设置HTTP安全头部"""
+ @self.app.after_request
+ def add_security_headers(response):
+ """为所有响应添加安全头部"""
+ response.headers['X-Content-Type-Options'] = 'nosniff'
+ response.headers['X-Frame-Options'] = 'SAMEORIGIN'
+ response.headers['X-XSS-Protection'] = '1; mode=block'
+ response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
+ return response
+
+ def setup_routes(self):
+ """设置Flask路由"""
+
+ @self.app.route('/')
+ def index():
+ """主页面 - 显示地图和控制界面"""
+ return self.generate_main_page()
+
+ @self.app.route('/mobile/mobile_client.html')
+ def mobile_client():
+ """手机端客户端页面"""
+ return self.serve_mobile_client()
+
+ @self.app.route('/mobile/gps_test.html')
+ def gps_test():
+ """GPS测试页面"""
+ return self.serve_gps_test()
+
+ @self.app.route('/mobile/permission_guide.html')
+ def permission_guide():
+ """权限设置指南页面"""
+ return self.serve_permission_guide()
+
+ @self.app.route('/mobile/baidu_browser_test.html')
+ def baidu_browser_test():
+ """百度浏览器测试页面"""
+ return self.serve_baidu_browser_test()
+
+ @self.app.route('/mobile/camera_permission_test.html')
+ def camera_permission_test():
+ """摄像头权限测试页面"""
+ return self.serve_camera_permission_test()
+
+ @self.app.route('/mobile/browser_compatibility_guide.html')
+ def browser_compatibility_guide():
+ """浏览器兼容性指南页面"""
+ return self.serve_browser_compatibility_guide()
+
+ @self.app.route('/mobile/legacy_browser_help.html')
+ def legacy_browser_help():
+ """旧版浏览器帮助页面"""
+ return self.serve_legacy_browser_help()
+
+ @self.app.route('/mobile/harmonyos_camera_fix.html')
+ def harmonyos_camera_fix():
+ """鸿蒙系统摄像头修复页面"""
+ return self.serve_harmonyos_camera_fix()
+
+ @self.app.route('/test_device_selector.html')
+ def test_device_selector():
+ """设备选择器测试页面"""
+ try:
+ with open('test_device_selector.html', 'r', encoding='utf-8') as f:
+ return f.read()
+ except FileNotFoundError:
+ return "测试页面未找到", 404
+
+ @self.app.route('/api/start_camera', methods=['POST'])
+ def start_camera():
+ """启动摄像头"""
+ try:
+ print("🔍 API调用: /api/start_camera")
+ if not self.camera_active:
+ print("📷 摄像头当前状态: 离线,正在启动...")
+ self.start_camera_detection()
+ # 等待一下检查启动是否成功
+ time.sleep(0.5)
+ if self.camera_active:
+ print("✅ 摄像头启动成功")
+ return jsonify({"status": "success", "message": "摄像头已启动", "camera_active": True})
+ else:
+ print("❌ 摄像头启动失败")
+ return jsonify({"status": "error", "message": "摄像头启动失败,请检查设备连接"})
+ else:
+ print("⚠️ 摄像头已在运行中")
+ return jsonify({"status": "warning", "message": "摄像头已在运行中", "camera_active": True})
+ except Exception as e:
+ print(f"❌ 摄像头启动异常: {e}")
+ import traceback
+ traceback.print_exc()
+ return jsonify({"status": "error", "message": f"启动失败: {str(e)}"})
+
+ @self.app.route('/api/stop_camera', methods=['POST'])
+ def stop_camera():
+ """停止摄像头"""
+ try:
+ self.stop_camera_detection()
+ return jsonify({"status": "success", "message": "摄像头已停止"})
+ except Exception as e:
+ return jsonify({"status": "error", "message": f"停止失败: {str(e)}"})
+
+ @self.app.route('/api/get_persons_data')
+ def get_persons_data():
+ """获取人员检测数据"""
+ data = self.map_manager.get_persons_data()
+ print(f"🌐 API调用 /api/get_persons_data 返回 {len(data)} 个人员数据")
+ return jsonify(data)
+
+ @self.app.route('/api/get_mobile_devices_data')
+ def get_mobile_devices_data():
+ """获取移动端设备的实时位置和朝向数据"""
+ devices_data = []
+ current_time = time.time()
+
+ # 获取所有活跃的移动设备数据
+ for device_id in set(list(self.mobile_locations.keys()) + list(self.mobile_orientations.keys())):
+ device_info = {}
+
+ # 获取位置数据
+ location_data = self.mobile_locations.get(device_id)
+ if location_data and (current_time - location_data['timestamp']) < 30: # 30秒内的数据有效
+ device_info['location'] = {
+ 'latitude': location_data['latitude'],
+ 'longitude': location_data['longitude'],
+ 'accuracy': location_data['accuracy'],
+ 'timestamp': location_data['timestamp']
+ }
+
+ # 获取朝向数据
+ orientation_data = self.mobile_orientations.get(device_id)
+ if orientation_data and (current_time - orientation_data['timestamp']) < 30: # 30秒内的数据有效
+ device_info['orientation'] = {
+ 'heading': orientation_data['heading'],
+ 'tilt': orientation_data['tilt'],
+ 'roll': orientation_data['roll'],
+ 'timestamp': orientation_data['timestamp']
+ }
+
+ # 只有有数据的设备才加入返回列表
+ if device_info:
+ device_info['device_id'] = device_id
+ devices_data.append(device_info)
+
+ return jsonify(devices_data)
+
+ @self.app.route('/api/debug_info')
+ def debug_info():
+ """获取调试信息"""
+ debug_data = {
+ "camera_active": self.camera_active,
+ "persons_count": len(self.map_manager.get_persons_data()),
+ "persons_data": self.map_manager.get_persons_data(),
+ "detection_count": len(self.detection_data) if self.detection_data else 0,
+ "camera_position": {
+ "lat": config.CAMERA_LATITUDE,
+ "lng": config.CAMERA_LONGITUDE,
+ "heading": config.CAMERA_HEADING
+ }
+ }
+ return jsonify(debug_data)
+
+ @self.app.route('/api/get_camera_frame')
+ def get_camera_frame():
+ """获取当前摄像头帧(base64编码)"""
+ if self.current_frame is not None:
+ _, buffer = cv2.imencode('.jpg', self.current_frame)
+ frame_base64 = base64.b64encode(buffer).decode('utf-8')
+ return jsonify({
+ "status": "success",
+ "frame": f"data:image/jpeg;base64,{frame_base64}",
+ "active": self.camera_active
+ })
+ else:
+ return jsonify({
+ "status": "no_frame",
+ "frame": None,
+ "active": self.camera_active
+ })
+
+ # 手机端API路由
+ @self.app.route('/mobile/ping', methods=['POST'])
+ def mobile_ping():
+ """手机端连接测试"""
+ try:
+ data = request.get_json()
+ device_id = data.get('device_id')
+ return jsonify({"status": "success", "server_time": time.time(), "device_id": device_id})
+ except Exception as e:
+ return jsonify({"status": "error", "message": str(e)}), 400
+
+ @self.app.route('/mobile/upload', methods=['POST'])
+ def mobile_upload():
+ """接收手机端数据"""
+ try:
+ data = request.get_json()
+ self.process_mobile_data(data)
+ return jsonify({"status": "success", "timestamp": time.time()})
+ except Exception as e:
+ print(f"⚠️ 处理手机数据错误: {e}")
+ return jsonify({"status": "error", "message": str(e)}), 400
+
+ @self.app.route('/api/mobile/realtime_data', methods=['POST'])
+ def mobile_realtime_data():
+ """接收移动端实时GPS和朝向数据"""
+ try:
+ data = request.get_json() or {}
+ device_id = data.get('device_id')
+ data_type = data.get('type')
+ payload = data.get('data', {})
+
+ if not device_id or not data_type:
+ return jsonify({
+ "status": "error",
+ "message": "设备ID和数据类型不能为空"
+ }), 400
+
+ # 处理不同类型的数据
+ if data_type == 'location':
+ return self.handle_mobile_location_data(device_id, payload)
+ elif data_type == 'orientation':
+ return self.handle_mobile_orientation_data(device_id, payload)
+ else:
+ return jsonify({
+ "status": "error",
+ "message": f"不支持的数据类型: {data_type}"
+ }), 400
+
+ except Exception as e:
+ print(f"❌ 移动端实时数据处理错误: {e}")
+ return jsonify({
+ "status": "error",
+ "message": f"数据处理失败: {str(e)}"
+ }), 500
+
+ @self.app.route('/api/mobile/devices')
+ def get_mobile_devices():
+ """获取连接的手机设备列表"""
+ devices = []
+
+ # 首先添加Socket连接的设备
+ for device in self.mobile_connector.get_online_devices():
+ devices.append({
+ "device_id": device.device_id,
+ "device_name": device.device_name,
+ "battery_level": device.battery_level,
+ "is_online": device.is_online,
+ "last_seen": device.last_seen,
+ "location": device.current_location
+ })
+
+ # 然后添加HTTP API连接的设备
+ current_time = time.time()
+ for device_id, frame_data in self.mobile_frames.items():
+ # 检查是否已经在Socket设备列表中
+ device_exists = any(d["device_id"] == device_id for d in devices)
+ if not device_exists:
+ device_info = frame_data.get('device_info', {})
+ last_seen = frame_data.get('timestamp', current_time)
+
+ # 设备被认为在线如果最后一次见到的时间在30秒内
+ is_online = (current_time - last_seen) < 30
+
+ location = None
+ gps_data = device_info.get('gps')
+ if gps_data and gps_data.get('latitude') and gps_data.get('longitude'):
+ location = {
+ "lat": gps_data['latitude'],
+ "lng": gps_data['longitude'],
+ "accuracy": gps_data.get('accuracy', 0)
+ }
+
+ devices.append({
+ "device_id": device_id,
+ "device_name": device_info.get('device_name', f'HTTP-Device-{device_id[:8]}'),
+ "battery_level": device_info.get('battery', 100),
+ "is_online": is_online,
+ "last_seen": last_seen,
+ "location": location,
+ "connection_type": "HTTP"
+ })
+
+ return jsonify(devices)
+
+ @self.app.route('/api/mobile/toggle', methods=['POST'])
+ def toggle_mobile_mode():
+ """切换手机模式"""
+ try:
+ if not self.mobile_mode:
+ # 启动手机模式
+ if self.mobile_connector.start_server():
+ self.mobile_mode = True
+ # 停止本地摄像头
+ if self.camera_active:
+ self.stop_camera_detection()
+ return jsonify({"status": "success", "message": "手机模式已启动", "mobile_mode": True})
+ else:
+ return jsonify({"status": "error", "message": "手机服务器启动失败"})
+ else:
+ # 停止手机模式
+ self.mobile_connector.stop_server()
+ self.mobile_mode = False
+ self.mobile_frames.clear()
+ self.map_manager.clear_persons()
+ return jsonify({"status": "success", "message": "手机模式已停止", "mobile_mode": False})
+ except Exception as e:
+ return jsonify({"status": "error", "message": str(e)})
+
+ @self.app.route('/api/connect_remote_device', methods=['POST'])
+ def connect_remote_device():
+ """连接到远程设备"""
+ try:
+ data = request.get_json()
+ device_id = data.get('device_id')
+ client_id = data.get('client_id')
+
+ # 检查设备是否存在 - 首先检查Socket连接的设备
+ target_device = None
+ for device in self.mobile_connector.get_online_devices():
+ if device.device_id == device_id:
+ target_device = device
+ break
+
+ # 如果在Socket设备中没找到,检查HTTP连接的设备
+ if not target_device and device_id in self.mobile_frames:
+ frame_data = self.mobile_frames[device_id]
+ device_info = frame_data.get('device_info', {})
+ current_time = time.time()
+ last_seen = frame_data.get('timestamp', current_time)
+
+ # 检查设备是否在线(30秒内有数据)
+ if (current_time - last_seen) < 30:
+ # 创建一个临时设备对象用于响应
+ target_device = {
+ 'device_id': device_id,
+ 'device_name': device_info.get('device_name', f'HTTP-Device-{device_id[:8]}'),
+ 'battery_level': device_info.get('battery', 100),
+ 'current_location': None
+ }
+
+ # 如果有GPS数据,添加位置信息
+ gps_data = device_info.get('gps')
+ if gps_data and gps_data.get('latitude') and gps_data.get('longitude'):
+ target_device['current_location'] = {
+ "lat": gps_data['latitude'],
+ "lng": gps_data['longitude'],
+ "accuracy": gps_data.get('accuracy', 0)
+ }
+
+ if not target_device:
+ return jsonify({"status": "error", "message": "目标设备未找到或已离线"}), 404
+
+ # 建立连接关系
+ connection_info = {
+ "client_id": client_id,
+ "device_id": device_id,
+ "connected_at": time.time(),
+ "status": "connected"
+ }
+
+ # 存储连接信息(这里可以扩展为更复杂的连接管理)
+ if not hasattr(self, 'remote_connections'):
+ self.remote_connections = {}
+ self.remote_connections[f"{client_id}->{device_id}"] = connection_info
+
+ # 处理不同格式的设备数据
+ if isinstance(target_device, dict):
+ # HTTP连接的设备(字典格式)
+ device_name = target_device['device_name']
+ device_info = {
+ "device_id": target_device['device_id'],
+ "device_name": target_device['device_name'],
+ "battery_level": target_device['battery_level'],
+ "location": target_device['current_location']
+ }
+ else:
+ # Socket连接的设备(对象格式)
+ device_name = target_device.device_name
+ device_info = {
+ "device_id": target_device.device_id,
+ "device_name": target_device.device_name,
+ "battery_level": target_device.battery_level,
+ "location": target_device.current_location
+ }
+
+ return jsonify({
+ "status": "success",
+ "message": f"已连接到设备 {device_name}",
+ "device_info": device_info
+ })
+
+ except Exception as e:
+ return jsonify({"status": "error", "message": str(e)}), 500
+
+ @self.app.route('/api/remote_device/stream/')
+ def get_remote_device_stream(device_id):
+ """获取远程设备的视频流"""
+ try:
+ # 获取指定设备的最新帧
+ if device_id in self.mobile_frames:
+ frame_data = self.mobile_frames[device_id]
+ device_info = frame_data.get('device_info', {})
+
+ # 准备返回数据,包含GPS信息
+ response_data = {
+ "status": "success",
+ "frame": f"data:image/jpeg;base64,{frame_data.get('frame')}",
+ "timestamp": frame_data.get('timestamp'),
+ "device_info": device_info
+ }
+
+ # 如果有GPS数据,加入响应中
+ gps_data = device_info.get('gps')
+ if gps_data and gps_data.get('latitude') and gps_data.get('longitude'):
+ response_data['gps'] = {
+ "lat": gps_data['latitude'],
+ "lng": gps_data['longitude'],
+ "accuracy": gps_data.get('accuracy', 0)
+ }
+
+ return jsonify(response_data)
+ else:
+ return jsonify({
+ "status": "no_frame",
+ "message": "设备无视频数据"
+ }), 404
+ except Exception as e:
+ return jsonify({"status": "error", "message": str(e)}), 500
+
+ @self.app.route('/favicon.ico')
+ def favicon():
+ return '', 204 # 返回空内容,状态码204表示无内容
+
+ def draw_info_panel(self, frame, person_count=0):
+ """绘制信息面板 - 完全复制main.py的逻辑"""
+ height, width = frame.shape[:2]
+
+ # 绘制顶部信息栏
+ info_height = 60
+ cv2.rectangle(frame, (0, 0), (width, info_height), (0, 0, 0), -1)
+
+ # 显示FPS(Web版本不需要实时FPS,显示固定文本)
+ fps_text = "Web Mode"
+ cv2.putText(frame, fps_text, (10, 25), config.FONT, 0.6, (0, 255, 0), 2)
+
+ # 显示人员计数
+ person_text = f"Persons: {person_count}"
+ cv2.putText(frame, person_text, (150, 25), config.FONT, 0.6, (0, 255, 255), 2)
+
+ # 显示模型信息
+ if hasattr(self, 'person_detector') and self.person_detector:
+ model_text = self.person_detector.get_model_info()
+ else:
+ model_text = "YOLO Model: Loading..."
+ cv2.putText(frame, model_text, (10, 45), config.FONT, 0.5, (255, 255, 255), 1)
+
+ # 显示时间信息
+ time_text = f"Time: {time.strftime('%H:%M:%S')}"
+ cv2.putText(frame, time_text, (width - 150, 25), config.FONT, 0.5, (255, 255, 0), 1)
+
+ # 显示地图状态
+ map_status = "Map: ON"
+ cv2.putText(frame, map_status, (10, height - 10),
+ config.FONT, 0.5, (0, 255, 255), 1)
+
+ # 显示摄像头信息
+ camera_info = f"Camera: Local ({config.CAMERA_LATITUDE:.4f}, {config.CAMERA_LONGITUDE:.4f})"
+ cv2.putText(frame, camera_info, (width - 400, height - 10),
+ config.FONT, 0.4, (255, 255, 255), 1)
+
+ return frame
+
+ def draw_backend_info_panel(self, frame, person_count=0, fps=0):
+ """绘制后端摄像头的信息面板 - 显示实时FPS"""
+ height, width = frame.shape[:2]
+
+ # 绘制顶部信息栏
+ info_height = 60
+ cv2.rectangle(frame, (0, 0), (width, info_height), (0, 0, 0), -1)
+
+ # 显示实时FPS
+ fps_text = f"FPS: {fps}"
+ cv2.putText(frame, fps_text, (10, 25), config.FONT, 0.6, (0, 255, 0), 2)
+
+ # 显示人员计数
+ person_text = f"Persons: {person_count}"
+ cv2.putText(frame, person_text, (150, 25), config.FONT, 0.6, (0, 255, 255), 2)
+
+ # 显示模型信息
+ if hasattr(self, 'detector') and self.detector:
+ model_text = self.detector.get_model_info()
+ else:
+ model_text = "YOLO Model: Loading..."
+ cv2.putText(frame, model_text, (10, 45), config.FONT, 0.5, (255, 255, 255), 1)
+
+ # 显示时间信息
+ time_text = f"Time: {time.strftime('%H:%M:%S')}"
+ cv2.putText(frame, time_text, (width - 150, 25), config.FONT, 0.5, (255, 255, 0), 1)
+
+ # 显示地图状态
+ map_status = "Map: ON"
+ cv2.putText(frame, map_status, (10, height - 10),
+ config.FONT, 0.5, (0, 255, 255), 1)
+
+ # 显示摄像头信息
+ camera_info = f"Camera: Backend ({config.CAMERA_LATITUDE:.4f}, {config.CAMERA_LONGITUDE:.4f})"
+ cv2.putText(frame, camera_info, (width - 450, height - 10),
+ config.FONT, 0.4, (255, 255, 255), 1)
+
+ # 显示操作提示
+ help_text = "Real-time Detection Mode"
+ cv2.putText(frame, help_text, (width - 250, 45),
+ config.FONT, 0.5, (255, 255, 0), 1)
+
+ return frame
+
+ def setup_mobile_callbacks(self):
+ """设置手机连接器回调函数"""
+ self.mobile_connector.add_frame_callback(self.on_mobile_frame)
+ self.mobile_connector.add_location_callback(self.on_mobile_location)
+ self.mobile_connector.add_device_callback(self.on_mobile_device_event)
+
+ def process_mobile_data(self, data):
+ """处理手机端发送的数据"""
+ device_id = data.get('device_id')
+ if not device_id:
+ raise ValueError("缺少device_id")
+
+ # 处理图像数据
+ if 'frame' in data:
+ try:
+ # 存储base64图像数据(用于远程设备流)
+ self.mobile_frames[device_id] = {
+ 'frame': data['frame'],
+ 'timestamp': data.get('timestamp', time.time()),
+ 'device_info': {
+ 'device_name': data.get('device_name', 'Unknown'),
+ 'battery': data.get('battery', 100),
+ 'gps': data.get('gps')
+ }
+ }
+
+ # 解码base64图像用于检测
+ frame_data = base64.b64decode(data['frame'])
+ frame = cv2.imdecode(np.frombuffer(frame_data, np.uint8), cv2.IMREAD_COLOR)
+
+ if frame is not None:
+
+ # 如果启用了检测器,进行人体检测
+ if not self.detector:
+ self.detector = PersonDetector()
+ self.distance_calculator = DistanceCalculator()
+
+ # 处理GPS和设备信息
+ gps_data = data.get('gps')
+ if gps_data and gps_data.get('latitude') and gps_data.get('longitude'):
+ self.process_mobile_detection(device_id, frame, gps_data)
+
+ print(f"📱 收到设备 {device_id[:8]} 的图像数据")
+
+ except Exception as e:
+ print(f"⚠️ 处理手机图像数据错误: {e}")
+ raise
+
+ def process_mobile_detection(self, device_id, frame, gps_data):
+ """处理手机图像的人体检测"""
+ try:
+ # 存储移动设备的最新GPS位置
+ if gps_data:
+ print(f"📍 设备 {device_id[:8]} GPS位置: ({gps_data['latitude']:.6f}, {gps_data['longitude']:.6f})")
+
+ # 更新设备在移动连接器中的位置信息
+ if hasattr(self.mobile_connector, 'update_device_location'):
+ self.mobile_connector.update_device_location(device_id, gps_data['latitude'], gps_data['longitude'])
+
+ # 创建帧的副本用于绘制检测框
+ processed_frame = frame.copy()
+
+ # 检测人体
+ detections = self.detector.detect_persons(frame)
+
+ if len(detections) > 0:
+ print(f"📱 设备 {device_id[:8]} 检测到 {len(detections)} 个人")
+
+ # 清除地图上该设备的旧标记
+ self.map_manager.clear_persons()
+
+ for i, detection in enumerate(detections):
+ bbox = detection[:4] # [x1, y1, x2, y2]
+ x1, y1, x2, y2 = bbox
+
+ # 绘制检测框
+ cv2.rectangle(processed_frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
+
+ # 计算距离(基于手机摄像头)
+ distance = self.distance_calculator.get_distance(bbox)
+ distance_meters = distance / 100.0
+
+ # 绘制距离标签
+ label = f"Person {i+1}: {distance_meters:.1f}m"
+ label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)[0]
+ cv2.rectangle(processed_frame, (int(x1), int(y1-label_size[1]-10)),
+ (int(x1+label_size[0]), int(y1)), (0, 255, 0), -1)
+ cv2.putText(processed_frame, label, (int(x1), int(y1-5)),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 2)
+
+ # 🎯 使用精确的GPS坐标计算
+ # 获取设备朝向信息
+ device_heading = 0 # 默认朝向正北
+ if device_id in self.mobile_orientations:
+ orientation_data = self.mobile_orientations[device_id]
+ if time.time() - orientation_data['timestamp'] < 30: # 30秒内的朝向数据有效
+ device_heading = orientation_data['heading']
+
+ # 使用距离计算器的精确算法计算人员GPS坐标
+ frame_height, frame_width = frame.shape[:2]
+ person_lat, person_lng = self.distance_calculator.calculate_person_gps_position(
+ camera_lat=gps_data['latitude'],
+ camera_lng=gps_data['longitude'],
+ camera_heading=device_heading,
+ bbox=bbox,
+ distance_meters=distance_meters,
+ frame_width=frame_width,
+ frame_height=frame_height,
+ camera_fov=60 # 假设移动设备摄像头视场角为60度
+ )
+
+ # 🔍 检查人员是否在摄像头视野范围内
+ in_fov = self.distance_calculator.is_person_in_camera_fov(
+ camera_lat=gps_data['latitude'],
+ camera_lng=gps_data['longitude'],
+ camera_heading=device_heading,
+ person_lat=person_lat,
+ person_lng=person_lng,
+ camera_fov=60,
+ max_distance=50 # 最大检测距离50米
+ )
+
+ if in_fov:
+ # 添加到地图
+ self.map_manager.add_person_at_coordinates(
+ person_lat, person_lng,
+ f"Mobile-P{i+1}",
+ distance_meters,
+ device_id
+ )
+ print(f"📍 手机检测人员 {i+1}: 距离{distance_meters:.1f}m, 坐标({person_lat:.6f}, {person_lng:.6f}) ✅视野内")
+ else:
+ print(f"⚠️ 手机检测人员 {i+1}: 距离{distance_meters:.1f}m, 坐标({person_lat:.6f}, {person_lng:.6f}) ❌超出视野")
+ else:
+ # 即使没有检测到人员,也要清除旧标记,避免误导
+ print(f"📱 设备 {device_id[:8]} 未检测到人员")
+
+ # 绘制信息面板
+ processed_frame = self.draw_backend_info_panel(processed_frame, len(detections), fps=60)
+
+ # 在顶部添加移动设备标识
+ device_label = f"Mobile Device: {device_id[:8]}"
+ cv2.putText(processed_frame, device_label, (10, 80), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
+
+ # 更新当前帧供前端显示
+ self.current_frame = processed_frame
+
+ # 标记摄像头为活跃状态
+ self.camera_active = True
+
+ except Exception as e:
+ print(f"⚠️ 手机检测处理错误: {e}")
+
+ def on_mobile_frame(self, device_id, frame, device):
+ """手机帧数据回调"""
+ # 这个方法会被MobileConnector调用,但我们已经在process_mobile_data中处理了
+ pass
+
+ def on_mobile_location(self, device_id, location, device):
+ """手机位置数据回调"""
+ lat, lng, accuracy = location
+ print(f"📍 设备 {device_id[:8]} 位置更新: ({lat:.6f}, {lng:.6f})")
+
+ def on_mobile_device_event(self, event_type, device):
+ """手机设备事件回调"""
+ if event_type == 'device_connected':
+ print(f"📱 新设备连接: {device.device_name} ({device.device_id[:8]})")
+ elif event_type == 'device_disconnected':
+ print(f"📱 设备断开: {device.device_name} ({device.device_id[:8]})")
+ # 清理该设备的数据
+ if device.device_id in self.mobile_frames:
+ del self.mobile_frames[device.device_id]
+ # 🌟 清理实时数据
+ if device.device_id in self.mobile_locations:
+ del self.mobile_locations[device.device_id]
+ if device.device_id in self.mobile_orientations:
+ del self.mobile_orientations[device.device_id]
+
+ def handle_mobile_location_data(self, device_id, location_data):
+ """处理移动端GPS位置数据"""
+ try:
+ latitude = location_data.get('latitude')
+ longitude = location_data.get('longitude')
+ accuracy = location_data.get('accuracy', 100)
+ timestamp = location_data.get('timestamp', time.time())
+
+ if latitude is None or longitude is None:
+ return jsonify({
+ "status": "error",
+ "message": "GPS坐标数据不完整"
+ }), 400
+
+ # 存储位置数据
+ self.mobile_locations[device_id] = {
+ 'latitude': latitude,
+ 'longitude': longitude,
+ 'accuracy': accuracy,
+ 'timestamp': timestamp
+ }
+
+ print(f"📍 设备 {device_id[:8]} GPS更新: ({latitude:.6f}, {longitude:.6f}) ±{accuracy}m")
+
+ return jsonify({
+ "status": "success",
+ "message": "GPS位置数据已接收"
+ })
+
+ except Exception as e:
+ print(f"❌ GPS数据处理错误: {e}")
+ return jsonify({
+ "status": "error",
+ "message": f"GPS数据处理失败: {str(e)}"
+ }), 500
+
+ def handle_mobile_orientation_data(self, device_id, orientation_data):
+ """处理移动端设备朝向数据"""
+ try:
+ heading = orientation_data.get('heading')
+ tilt = orientation_data.get('tilt')
+ roll = orientation_data.get('roll')
+ timestamp = orientation_data.get('timestamp', time.time())
+
+ if heading is None:
+ return jsonify({
+ "status": "error",
+ "message": "朝向数据不完整"
+ }), 400
+
+ # 存储朝向数据
+ self.mobile_orientations[device_id] = {
+ 'heading': heading,
+ 'tilt': tilt,
+ 'roll': roll,
+ 'timestamp': timestamp
+ }
+
+ print(f"🧭 设备 {device_id[:8]} 朝向更新: {heading:.1f}° (倾斜:{tilt:.1f}°)")
+
+ return jsonify({
+ "status": "success",
+ "message": "朝向数据已接收"
+ })
+
+ except Exception as e:
+ print(f"❌ 朝向数据处理错误: {e}")
+ return jsonify({
+ "status": "error",
+ "message": f"朝向数据处理失败: {str(e)}"
+ }), 500
+
+ def serve_mobile_client(self):
+ """提供手机端客户端页面"""
+ try:
+ with open('mobile/mobile_client.html', 'r', encoding='utf-8') as f:
+ return f.read()
+ except FileNotFoundError:
+ return """
+
+ 文件未找到
+
+ ❌ 手机端客户端文件未找到
+ 请确保 mobile/mobile_client.html 文件存在
+ 或者直接访问该文件的完整路径
+
+
+ """, 404
+
+ def serve_gps_test(self):
+ """提供GPS测试页面"""
+ try:
+ with open('mobile/gps_test.html', 'r', encoding='utf-8') as f:
+ return f.read()
+ except FileNotFoundError:
+ return """
+
+ 文件未找到
+
+ ❌ GPS测试页面未找到
+ 请确保 mobile/gps_test.html 文件存在
+
+
+ """, 404
+
+ def serve_permission_guide(self):
+ """提供权限设置指南页面"""
+ try:
+ with open('mobile/permission_guide.html', 'r', encoding='utf-8') as f:
+ return f.read()
+ except FileNotFoundError:
+ return """
+
+ 文件未找到
+
+ ❌ 权限指南页面未找到
+ 请确保 mobile/permission_guide.html 文件存在
+
+
+ """, 404
+
+ def serve_baidu_browser_test(self):
+ """提供百度浏览器测试页面"""
+ try:
+ with open('mobile/baidu_browser_test.html', 'r', encoding='utf-8') as f:
+ return f.read()
+ except FileNotFoundError:
+ return """
+
+ 文件未找到
+
+ ❌ 百度浏览器测试页面未找到
+ 请确保 mobile/baidu_browser_test.html 文件存在
+ 返回移动客户端
+
+
+ """, 404
+
+ def serve_camera_permission_test(self):
+ """提供摄像头权限测试页面"""
+ try:
+ with open('mobile/camera_permission_test.html', 'r', encoding='utf-8') as f:
+ return f.read()
+ except FileNotFoundError:
+ return """
+
+ 文件未找到
+
+ ❌ 摄像头权限测试页面未找到
+ 请确保 mobile/camera_permission_test.html 文件存在
+ 返回移动客户端
+
+
+ """, 404
+
+ def serve_browser_compatibility_guide(self):
+ """提供浏览器兼容性指南页面"""
+ try:
+ with open('mobile/browser_compatibility_guide.html', 'r', encoding='utf-8') as f:
+ return f.read()
+ except FileNotFoundError:
+ return """
+
+ 文件未找到
+
+ ❌ 浏览器兼容性指南页面未找到
+ 请确保 mobile/browser_compatibility_guide.html 文件存在
+ 返回移动客户端
+
+
+ """, 404
+
+ def serve_legacy_browser_help(self):
+ """提供旧版浏览器帮助页面"""
+ try:
+ with open('mobile/legacy_browser_help.html', 'r', encoding='utf-8') as f:
+ return f.read()
+ except FileNotFoundError:
+ return """
+
+ 文件未找到
+
+ ❌ 旧版浏览器帮助页面未找到
+ 请确保 mobile/legacy_browser_help.html 文件存在
+ 返回移动客户端
+
+
+ """, 404
+
+ def serve_harmonyos_camera_fix(self):
+ """提供鸿蒙系统摄像头修复页面"""
+ try:
+ with open('mobile/harmonyos_camera_fix.html', 'r', encoding='utf-8') as f:
+ return f.read()
+ except FileNotFoundError:
+ return """
+
+ 文件未找到
+
+ ❌ 鸿蒙系统摄像头修复页面未找到
+ 请确保 mobile/harmonyos_camera_fix.html 文件存在
+ 返回移动客户端
+
+
+ """, 404
+
+ def start_camera_detection(self):
+ """启动摄像头检测"""
+ if self.camera_active:
+ print("⚠️ 摄像头已在运行中")
+ return
+
+ try:
+ print("=" * 60)
+ print(f"🔍 开始摄像头检测初始化...")
+ print(f"🔍 当前时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
+ print(f"🔍 配置的摄像头索引: {config.CAMERA_INDEX}")
+ print(f"🔍 当前camera_active状态: {self.camera_active}")
+
+ # 检查OpenCV是否正常
+ print(f"🔍 OpenCV版本: {cv2.__version__}")
+
+ # 检查系统摄像头
+ print("🔍 检查系统可用摄像头...")
+ available_cameras = []
+ for i in range(10):
+ test_cap = cv2.VideoCapture(i)
+ if test_cap.isOpened():
+ available_cameras.append(i)
+ test_cap.release()
+ print(f" ✅ 找到摄像头索引: {i}")
+ else:
+ test_cap.release()
+
+ if not available_cameras:
+ raise Exception("系统中没有找到任何可用的摄像头设备")
+
+ print(f"🔍 可用摄像头索引: {available_cameras}")
+
+ # 初始化摄像头
+ print(f"🔍 正在尝试打开摄像头索引: {config.CAMERA_INDEX}")
+ self.cap = cv2.VideoCapture(config.CAMERA_INDEX)
+ print(f"🔍 VideoCapture对象创建完成: {self.cap}")
+ print(f"🔍 cap.isOpened(): {self.cap.isOpened()}")
+
+ # 等待摄像头初始化
+ time.sleep(1)
+
+ if not self.cap.isOpened():
+ print(f"❌ 初始摄像头索引 {config.CAMERA_INDEX} 打开失败")
+ # 尝试其他摄像头索引
+ success = False
+ for i in available_cameras:
+ print(f"🔍 尝试备用摄像头索引: {i}")
+ if self.cap:
+ self.cap.release()
+ self.cap = cv2.VideoCapture(i)
+ time.sleep(0.8) # 增加等待时间
+ print(f"🔍 索引 {i} isOpened(): {self.cap.isOpened()}")
+ if self.cap.isOpened():
+ print(f"✅ 成功打开备用摄像头索引: {i}")
+ success = True
+ break
+ else:
+ print(f"❌ 索引 {i} 打开失败")
+ self.cap.release()
+
+ if not success:
+ raise Exception(f"无法打开任何摄像头,尝试了索引: {available_cameras}")
+ else:
+ print(f"✅ 初始摄像头索引 {config.CAMERA_INDEX} 打开成功")
+
+ # 设置摄像头参数
+ print(f"📐 设置摄像头参数: {config.FRAME_WIDTH}x{config.FRAME_HEIGHT}@{config.FPS}fps")
+ self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, config.FRAME_WIDTH)
+ self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, config.FRAME_HEIGHT)
+ self.cap.set(cv2.CAP_PROP_FPS, config.FPS)
+
+ # 测试读取一帧
+ print("🔍 测试摄像头帧读取...")
+ for attempt in range(3):
+ ret, test_frame = self.cap.read()
+ print(f"🔍 读取尝试 {attempt + 1}: ret={ret}, frame={'有效' if test_frame is not None else '无效'}")
+ if ret and test_frame is not None:
+ print(f"✅ 摄像头测试成功,实际分辨率: {test_frame.shape[1]}x{test_frame.shape[0]}")
+ break
+ time.sleep(0.5)
+ else:
+ raise Exception("摄像头无法读取图像帧,连续3次尝试都失败")
+
+ # 初始化检测器
+ print("🤖 初始化YOLO检测器...")
+ try:
+ self.detector = PersonDetector()
+ print("✅ PersonDetector初始化完成")
+ self.distance_calculator = DistanceCalculator()
+ print("✅ DistanceCalculator初始化完成")
+ except Exception as e:
+ print(f"❌ 检测器初始化失败: {e}")
+ raise e
+
+ # 启动检测线程
+ print("🔍 设置camera_active = True")
+ self.camera_active = True
+ print(f"🔍 当前camera_active状态: {self.camera_active}")
+
+ print("🔍 创建摄像头检测线程...")
+ self.camera_thread = threading.Thread(target=self.camera_detection_loop, name="CameraDetectionThread")
+ self.camera_thread.daemon = True
+
+ print("🔍 启动摄像头检测线程...")
+ self.camera_thread.start()
+ print(f"🔍 线程是否存活: {self.camera_thread.is_alive()}")
+
+ # 等待线程启动
+ time.sleep(1)
+ print(f"🔍 启动后camera_active状态: {self.camera_active}")
+ print(f"🔍 线程启动后状态: {self.camera_thread.is_alive()}")
+
+ print("🎯 摄像头检测线程启动完成")
+ print("=" * 60)
+
+ except Exception as e:
+ self.camera_active = False
+ if hasattr(self, 'cap') and self.cap:
+ self.cap.release()
+ print(f"❌ 摄像头启动失败: {e}")
+ raise e
+
+ def stop_camera_detection(self):
+ """停止摄像头检测"""
+ self.camera_active = False
+ if self.camera_thread:
+ self.camera_thread.join(timeout=2)
+ if self.cap:
+ self.cap.release()
+ self.current_frame = None
+ self.detection_data = []
+ self.map_manager.clear_persons()
+ print("📷 摄像头检测已停止")
+
+ def camera_detection_loop(self):
+ """摄像头检测循环"""
+ fps_counter = 0
+ fps_time = time.time()
+ current_fps = 0
+ frame_count = 0
+
+ print("=" * 50)
+ print("🔍 摄像头检测循环已启动")
+ print(f"🔍 线程名称: {threading.current_thread().name}")
+ print(f"🔍 启动时camera_active: {self.camera_active}")
+ print(f"🔍 启动时cap对象: {self.cap}")
+ print(f"🔍 启动时cap.isOpened(): {self.cap.isOpened() if self.cap else 'cap为None'}")
+ print("=" * 50)
+
+ while self.camera_active and self.cap:
+ try:
+ # 详细的读帧调试
+ if frame_count % 100 == 0: # 每100帧输出一次详细状态
+ print(f"🔍 循环状态检查 - 帧{frame_count}: camera_active={self.camera_active}, cap={self.cap is not None}")
+
+ ret, frame = self.cap.read()
+ if not ret or frame is None:
+ print(f"❌ 无法读取摄像头画面 - 帧{frame_count}: ret={ret}, frame={'有效' if frame is not None else '无效'}")
+ print(f"🔍 当前cap状态: {self.cap}")
+ print(f"🔍 cap.isOpened(): {self.cap.isOpened() if self.cap else 'cap为None'}")
+ break
+
+ frame_count += 1
+
+ # 检测人体
+ try:
+ detections = self.detector.detect_persons(frame)
+ except Exception as detect_error:
+ print(f"❌ 人体检测错误 - 帧{frame_count}: {detect_error}")
+ detections = []
+
+ # 添加调试输出 (每30帧输出一次状态,避免刷屏)
+ if frame_count % 30 == 0 or len(detections) > 0:
+ print(f"📹 帧 {frame_count}: 检测到 {len(detections)} 个人 (FPS: {current_fps})")
+ if len(detections) > 0:
+ print(f"🎯 检测详情: {[f'person_{i}_{det[4]:.2f}' for i, det in enumerate(detections)]}")
+
+ # 计算距离并更新地图位置
+ self.map_manager.clear_persons()
+ distances = []
+
+ for i, detection in enumerate(detections):
+ bbox = detection[:4] # [x1, y1, x2, y2]
+ x1, y1, x2, y2 = bbox
+ distance = self.distance_calculator.get_distance(bbox)
+ distance_str = self.distance_calculator.format_distance(distance)
+ distances.append(distance_str)
+
+ # 计算人体中心点
+ center_x = (x1 + x2) / 2
+ center_y = (y1 + y2) / 2
+
+ # 将距离从厘米转换为米
+ distance_meters = distance / 100.0
+
+ # 🎯 使用精确的GPS坐标计算 - 固定摄像头
+ frame_height, frame_width = frame.shape[:2]
+ lat, lng = self.distance_calculator.calculate_person_gps_position(
+ camera_lat=config.CAMERA_LATITUDE,
+ camera_lng=config.CAMERA_LONGITUDE,
+ camera_heading=config.CAMERA_HEADING,
+ bbox=bbox,
+ distance_meters=distance_meters,
+ frame_width=frame_width,
+ frame_height=frame_height,
+ camera_fov=config.CAMERA_FOV
+ )
+
+ # 🔍 检查人员是否在摄像头视野范围内
+ in_fov = self.distance_calculator.is_person_in_camera_fov(
+ camera_lat=config.CAMERA_LATITUDE,
+ camera_lng=config.CAMERA_LONGITUDE,
+ camera_heading=config.CAMERA_HEADING,
+ person_lat=lat,
+ person_lng=lng,
+ camera_fov=config.CAMERA_FOV,
+ max_distance=100 # 最大检测距离100米
+ )
+
+ if in_fov:
+ # 添加到地图
+ self.map_manager.add_person_at_coordinates(
+ lat, lng, f"P{i+1}", distance_meters, "fixed_camera"
+ )
+
+ if len(detections) > 0: # 只在有检测时输出详细信息
+ if in_fov:
+ print(f"📍 人员 {i+1}: 像素位置({center_x:.1f}, {center_y:.1f}), 距离{distance_meters:.1f}m, 坐标({lat:.6f}, {lng:.6f}) ✅视野内")
+ else:
+ print(f"⚠️ 人员 {i+1}: 像素位置({center_x:.1f}, {center_y:.1f}), 距离{distance_meters:.1f}m, 坐标({lat:.6f}, {lng:.6f}) ❌超出视野")
+
+ # 绘制检测结果(完整的检测框、距离、置信度)
+ frame = self.detector.draw_detections(frame, detections, distances)
+
+ # 计算FPS
+ fps_counter += 1
+ current_time = time.time()
+ if current_time - fps_time >= 1.0:
+ current_fps = fps_counter
+ fps_counter = 0
+ fps_time = current_time
+
+ # 添加完整的信息面板 - 带FPS信息
+ frame = self.draw_backend_info_panel(frame, len(detections), current_fps)
+
+ # 保存当前帧
+ try:
+ self.current_frame = frame.copy()
+ self.detection_data = detections
+
+ # 每100帧验证一次帧保存
+ if frame_count % 100 == 0:
+ print(f"🔍 帧保存状态 - 帧{frame_count}: current_frame={'已保存' if self.current_frame is not None else '保存失败'}")
+
+ except Exception as save_error:
+ print(f"❌ 帧保存错误 - 帧{frame_count}: {save_error}")
+
+ # 控制帧率
+ time.sleep(1/30) # 30 FPS
+
+ except Exception as e:
+ print(f"❌ 检测循环错误 - 帧{frame_count}: {e}")
+ import traceback
+ traceback.print_exc()
+ break
+
+ print(f"🛑 摄像头检测循环已结束 - 总处理帧数: {frame_count}")
+ print(f"🔍 结束时camera_active: {self.camera_active}")
+ print(f"🔍 结束时cap状态: {self.cap}")
+ print("=" * 50)
+
+ def generate_main_page(self):
+ """生成主页面HTML"""
+ html_template = f"""
+
+
+
+ 🚁 无人机战场态势感知系统
+
+
+
+
+
+
+
+
+
🚁 系统控制台
+
+
+ 📍 摄像头位置:
+ {config.CAMERA_LATITUDE:.4f}, {config.CAMERA_LONGITUDE:.4f}
+
+
+ 🧭 朝向角度:
+ {config.CAMERA_HEADING}°
+
+
+ 📷 摄像头状态:
+ 离线
+
+
+ 👥 检测人数:
+ 0
+
+
+
+
+
+
+
+
+
🧭 自动配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![实时摄像头画面]()
+
等待视频流...
+
+
+
+
+
+ 💻固定指挥中心
+
+
+ 🚁移动无人机
+
+
+ 🔶无人机视野
+
+
+ 🧑🌾检测目标
+
+
+
+
+
+
+
+
+
📷 选择视频设备
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
🔶 无人机视野调整
+
+
+
+
+ 📊 当前参数
+
+
+ 📏 视野大小:
+ 50px
+
+
+ 📐 视野角度:
+ 90°
+
+
+
+
+
+
📏 调整视野大小
+
+
+
+
+
+
+
+
+
📐 调整视野角度
+
+
+
+
+
+
+
+
+
⚡ 快速预设
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 💡 修改会实时应用到所有无人机视野
+
+
+
+
+
+
+"""
+ return html_template
+
+ def run(self, host='127.0.0.1', port=5000, debug=False, ssl_enabled=True):
+ """启动Web服务器"""
+ print("🌐 启动Web服务器...")
+
+ ssl_context = None
+ protocol = "http"
+
+ if ssl_enabled:
+ import os
+ cert_file = "ssl/cert.pem"
+ key_file = "ssl/key.pem"
+
+ if os.path.exists(cert_file) and os.path.exists(key_file):
+ ssl_context = (cert_file, key_file)
+ protocol = "https"
+ print("🔒 HTTPS模式已启用")
+ else:
+ print("⚠️ SSL证书文件不存在,正在生成...")
+ self.generate_ssl_certificate()
+ if os.path.exists(cert_file) and os.path.exists(key_file):
+ ssl_context = (cert_file, key_file)
+ protocol = "https"
+ print("🔒 HTTPS模式已启用")
+ else:
+ print("❌ SSL证书生成失败,使用HTTP模式")
+ ssl_enabled = False
+
+ print(f"📍 访问地址: {protocol}://{host}:{port}")
+ if ssl_enabled:
+ print("🔑 注意: 自签名证书会显示安全警告,点击'高级'->'继续访问'即可")
+ print("🚁 无人机战场态势感知系统已就绪")
+
+ try:
+ self.app.run(host=host, port=port, debug=debug, threaded=True, ssl_context=ssl_context)
+ except KeyboardInterrupt:
+ print("\n🔴 服务器已停止")
+ self.stop_camera_detection()
+ self.stop_camera_detection()
+
+ def generate_ssl_certificate(self):
+ """生成自签名SSL证书"""
+ try:
+ import os
+ import datetime
+ import ipaddress
+ from cryptography import x509
+ from cryptography.x509.oid import NameOID
+ from cryptography.hazmat.primitives import hashes, serialization
+ from cryptography.hazmat.primitives.asymmetric import rsa
+
+ # 创建ssl目录
+ ssl_dir = "ssl"
+ if not os.path.exists(ssl_dir):
+ os.makedirs(ssl_dir)
+
+ print("🔑 正在生成SSL证书...")
+
+ # 生成私钥
+ private_key = rsa.generate_private_key(
+ public_exponent=65537,
+ key_size=2048,
+ )
+
+ # 创建证书主体
+ subject = issuer = x509.Name([
+ x509.NameAttribute(NameOID.COUNTRY_NAME, "CN"),
+ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Beijing"),
+ x509.NameAttribute(NameOID.LOCALITY_NAME, "Beijing"),
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Distance Judgement System"),
+ x509.NameAttribute(NameOID.COMMON_NAME, "localhost"),
+ ])
+
+ # 生成证书
+ cert = x509.CertificateBuilder().subject_name(
+ subject
+ ).issuer_name(
+ issuer
+ ).public_key(
+ private_key.public_key()
+ ).serial_number(
+ x509.random_serial_number()
+ ).not_valid_before(
+ datetime.datetime.utcnow()
+ ).not_valid_after(
+ datetime.datetime.utcnow() + datetime.timedelta(days=365)
+ ).add_extension(
+ x509.SubjectAlternativeName([
+ x509.DNSName("localhost"),
+ x509.DNSName("127.0.0.1"),
+ x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")),
+ ]),
+ critical=False,
+ ).sign(private_key, hashes.SHA256())
+
+ # 保存私钥
+ key_path = os.path.join(ssl_dir, "key.pem")
+ with open(key_path, "wb") as f:
+ f.write(private_key.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.PKCS8,
+ encryption_algorithm=serialization.NoEncryption()
+ ))
+
+ # 保存证书
+ cert_path = os.path.join(ssl_dir, "cert.pem")
+ with open(cert_path, "wb") as f:
+ f.write(cert.public_bytes(serialization.Encoding.PEM))
+
+ print(f"✅ SSL证书已生成:")
+ print(f" 🔑 私钥: {key_path}")
+ print(f" 📜 证书: {cert_path}")
+ print(f" 📅 有效期: 365天")
+
+ except ImportError:
+ print("❌ 缺少cryptography库,请先安装: pip install cryptography")
+ except Exception as e:
+ print(f"❌ 生成SSL证书失败: {e}")
diff --git a/distance-judgement/ssl/cert.pem b/distance-judgement/ssl/cert.pem
new file mode 100644
index 00000000..903d0bf8
--- /dev/null
+++ b/distance-judgement/ssl/cert.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDiTCCAnGgAwIBAgIUD45qB5JkkfGfRqN8cZTJ1Q2TE14wDQYJKoZIhvcNAQEL
+BQAwaTELMAkGA1UEBhMCQ04xEDAOBgNVBAgMB0JlaWppbmcxEDAOBgNVBAcMB0Jl
+aWppbmcxIjAgBgNVBAoMGURpc3RhbmNlIEp1ZGdlbWVudCBTeXN0ZW0xEjAQBgNV
+BAMMCWxvY2FsaG9zdDAeFw0yNTA2MjkwODQ2MTRaFw0yNjA2MjkwODQ2MTRaMGkx
+CzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdCZWlqaW5nMRAwDgYDVQQHDAdCZWlqaW5n
+MSIwIAYDVQQKDBlEaXN0YW5jZSBKdWRnZW1lbnQgU3lzdGVtMRIwEAYDVQQDDAls
+b2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3u/JfTd1P
+/62wGwE0vAEOOPh0Zxn+lCssp0K9axWTfrvp0oWErcyGCVp+E+QjFOPyf0ocw7BX
+31O5UoJtOCYHACutXvp+Vd2YFxptXYU+CN/qj4MF+n28U7AwUiWPqSOy9/IMcdOl
+IfDKkSHCLWmUtNC8ot5eG/mYxqDVLZfI3Carclw/hwIYBa18YnaYG0xYM+G13Xpp
+yP5itRXLGS8I4GpTCoYFlPq0n+rW81sWNQjw3RmK4t1dF2AWhuDc5nYvRZdf4Qhk
+ovwW9n48fRaTfsUDylTVZ9RgmSo3KRWmw8DDCo4rlTtOS4x7fd1l6m1JPgPWg9bX
+9Qbz17wGGoUdAgMBAAGjKTAnMCUGA1UdEQQeMByCCWxvY2FsaG9zdIIJMTI3LjAu
+MC4xhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQBEneYvDdzdvv65rHUA9UKJzBGs
+4+j5ZYhCTl0E1HCVxWVHtheUmpUUTlXd0q40NayD0fqt+Cak+0gxKoh8vj1jceKU
+EO2OSMx7GIEETF1DU2mvaEHvlgLC5YC72DzirGrM+e4VXIIf7suvmcvAw42IGMtw
+xzEZANYeVY87LYVtJQ0Uw11j2C3dKdQJpEFhldWYwlaLYU6jhtkkiybAa7ZAI1AQ
+mL+02Y+IQ2sNOuVL7ltqoo0b5BmD4MXjn0wjcy/ARNlq7LxQcvm9UKQCFWtgPGNh
+qP8BBUq2pbJJFoxgjQYqAAL7tbdimWElBXwiOEESAjjIC8l/YG4s8QKWhGcq
+-----END CERTIFICATE-----
diff --git a/distance-judgement/ssl/key.pem b/distance-judgement/ssl/key.pem
new file mode 100644
index 00000000..930da7c3
--- /dev/null
+++ b/distance-judgement/ssl/key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3u/JfTd1P/62w
+GwE0vAEOOPh0Zxn+lCssp0K9axWTfrvp0oWErcyGCVp+E+QjFOPyf0ocw7BX31O5
+UoJtOCYHACutXvp+Vd2YFxptXYU+CN/qj4MF+n28U7AwUiWPqSOy9/IMcdOlIfDK
+kSHCLWmUtNC8ot5eG/mYxqDVLZfI3Carclw/hwIYBa18YnaYG0xYM+G13XppyP5i
+tRXLGS8I4GpTCoYFlPq0n+rW81sWNQjw3RmK4t1dF2AWhuDc5nYvRZdf4QhkovwW
+9n48fRaTfsUDylTVZ9RgmSo3KRWmw8DDCo4rlTtOS4x7fd1l6m1JPgPWg9bX9Qbz
+17wGGoUdAgMBAAECggEAAJVp+AexNkHRez5xCFrg2XQp+yW7ifWRiM4RbN0xPs0Y
+ZJ1BgcwnOTIX7+Q5LdrS2CBitB7zixzCG1qgj2K7nhYg0MJo+pynepOmvNBAyrUa
+dP1fCF0eXevqc37zGM5w+lpg6aTxw5ByOJtaNOqfikN4QLNBU6GSwA/Hkm8NP56J
+ZtVBfGE/inq4pyoFxLBwfGgYn9sRoo4AgPaUYiCFL7s4CXpkrFAg86sxkt0ak6pa
+9Hj9nVIcYdhNlEfvO53pnmU3KeXEGUVaE5CtxATEuYfTqNfb2+CBAUAkd1JTzC6P
+YLZC1WnrajC9LbblDgWvKQ2ItuNxPcCQOEgQl0IVRwKBgQDf74VeEaCAzQwY48q8
+/RiuJfCc/C7zAHNk4LuYalWSRFaMfciJSfWHNi2UhTuTYiYdg7rSfdrwLOJg/gz0
+c/H9k5SPwObFP0iXSY7FRsfviA5BJIe5xHyMNs0upiO9bmPA0X948esk4dCaUwWz
+TleMHlFSf7gk5sOsL7utYPqF0wKBgQDSCtHnXEaVCzoSrpuw9sEZnNIAqqfPOmfg
+OYwjz2yp89X4i/N1Lp15oe2vyfGNF4TzRl5kcGwv534om3PjMF9j4ANgy7BCdAx2
+5YXtoCull8lFd5ansBKM6BYtN/YWABTywxkFxMrR+f7gg7L8ywopGomyyyGc/hX6
+4UWaRQdDTwKBgAzt/31W9zV4oWIuhN40nuAvQJ1P0kYlmIQSlcJPIXG4kGa8PH/w
+zURpVGhm6PGxkRHTMU5GBgYoEUoYYRccOrSxeLp0IN7ysHZLwPqTA6hI6snIGi4X
+sjlGUMKIxTeC0C+p6PpKvZD7mNfQQ1v/Af8NIRTqWu+Gg3XFq8hu+QgRAoGBAMYh
++MFTFS2xKnXHCgyTp7G+cYa5dJSRlr0368838lwbLGNJuT133IqJSkpBp78dSYem
+gJIkTpmduC8b/OR5k/IFtYoQelMlX0Ck4II4ThPlq7IAzjeeatFKeOjs2hEEwL4D
+dc4wRdZvCZPGCAhYi1wcsXncDfgm4psG934/0UsXAoGAf1mWndfCOtj3/JqjcAKz
+cCpfdwgFnTt0U3SNZ5FMXZ4oCRXcDiKN7VMJg6ZtxCxLgAXN92eF/GdMotIFd0ou
+6xXLJzIp0XPc1uh5+VPOEjpqtl/ByURge0sshzce53mrhx6ixgAb2qWBJH/cNmIK
+VKGQWzXu+zbojPTSWzJltA0=
+-----END PRIVATE KEY-----
diff --git a/distance-judgement/test_device_selector.html b/distance-judgement/test_device_selector.html
new file mode 100644
index 00000000..0e7e4a6f
--- /dev/null
+++ b/distance-judgement/test_device_selector.html
@@ -0,0 +1,392 @@
+
+
+
+
+
+
+ 设备选择器测试
+
+
+
+
+
+
🧪 设备选择器测试
+
+
+
+
+ 点击"选择设备"开始使用摄像头
+
+
+
+
+
+
+
📷 选择视频设备
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/distance-judgement/test_network.py b/distance-judgement/test_network.py
new file mode 100644
index 00000000..755a9466
--- /dev/null
+++ b/distance-judgement/test_network.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+网络连接测试脚本
+帮助诊断手机/平板连接问题
+"""
+
+import socket
+import subprocess
+import sys
+import threading
+import time
+from http.server import HTTPServer, SimpleHTTPRequestHandler
+
+def get_local_ip():
+ """获取本机IP地址"""
+ try:
+ # 方法1: 连接到远程地址获取本地IP
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ s.connect(("8.8.8.8", 80))
+ ip = s.getsockname()[0]
+ s.close()
+ return ip
+ except:
+ try:
+ # 方法2: 获取主机名对应的IP
+ hostname = socket.gethostname()
+ ip = socket.gethostbyname(hostname)
+ if ip.startswith("127."):
+ return None
+ return ip
+ except:
+ return None
+
+def test_port(host, port):
+ """测试端口是否可访问"""
+ try:
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.settimeout(5)
+ result = sock.connect_ex((host, port))
+ sock.close()
+ return result == 0
+ except:
+ return False
+
+def start_test_server(port=8888):
+ """启动测试HTTP服务器"""
+ class TestHandler(SimpleHTTPRequestHandler):
+ def do_GET(self):
+ self.send_response(200)
+ self.send_header('Content-type', 'text/html; charset=utf-8')
+ self.end_headers()
+ html = """
+ 网络测试
+
+ ✅ 网络连接测试成功!
+ 如果您能看到这个页面,说明网络连接正常。
+ 测试时间: %s
+ 您的IP: %s
+ 刷新测试
+
+ """ % (time.strftime('%Y-%m-%d %H:%M:%S'), self.client_address[0])
+ self.wfile.write(html.encode('utf-8'))
+
+ def log_message(self, format, *args):
+ print(f"📱 测试访问: {self.client_address[0]} - {format % args}")
+
+ try:
+ server = HTTPServer(('0.0.0.0', port), TestHandler)
+ print(f"🧪 测试服务器已启动,端口: {port}")
+ server.serve_forever()
+ except Exception as e:
+ print(f"❌ 测试服务器启动失败: {e}")
+
+def main():
+ print("=" * 60)
+ print("🔍 网络连接诊断工具")
+ print("=" * 60)
+ print()
+
+ # 1. 获取IP地址
+ print("📍 1. 获取网络IP地址...")
+ local_ip = get_local_ip()
+ if local_ip:
+ print(f"✅ 本机IP地址: {local_ip}")
+ else:
+ print("❌ 无法获取IP地址,请检查网络连接")
+ return
+
+ # 2. 检查常用端口
+ print("\n🔌 2. 检查端口状态...")
+ ports_to_test = [5000, 8080, 8888]
+ for port in ports_to_test:
+ if test_port('127.0.0.1', port):
+ print(f"⚠️ 端口 {port} 已被占用")
+ else:
+ print(f"✅ 端口 {port} 可用")
+
+ # 3. 显示连接信息
+ print(f"\n📱 3. 移动设备连接信息:")
+ print(f" 主服务器地址: http://{local_ip}:5000")
+ print(f" 手机客户端: http://{local_ip}:5000/mobile/mobile_client.html")
+ print(f" 测试地址: http://{local_ip}:8888")
+
+ # 4. 防火墙检查提示
+ print(f"\n🛡️ 4. 防火墙设置提示:")
+ print(" 如果平板无法连接,请检查Windows防火墙设置:")
+ print(" 1. 打开 Windows 安全中心")
+ print(" 2. 点击 防火墙和网络保护")
+ print(" 3. 点击 允许应用通过防火墙")
+ print(" 4. 确保 Python 程序被允许通过防火墙")
+ print(" 或者临时关闭防火墙进行测试")
+
+ # 5. 网络检查命令
+ print(f"\n🔧 5. 网络检查命令:")
+ print(f" 在平板上ping测试: ping {local_ip}")
+ print(f" 在电脑上查看网络: ipconfig")
+ print(f" 检查防火墙状态: netsh advfirewall show allprofiles")
+
+ # 6. 启动测试服务器
+ print(f"\n🧪 6. 启动网络测试服务器...")
+ print(f" 请在平板浏览器访问: http://{local_ip}:8888")
+ print(" 如果能看到测试页面,说明网络连接正常")
+ print(" 按 Ctrl+C 停止测试")
+ print()
+
+ try:
+ start_test_server(8888)
+ except KeyboardInterrupt:
+ print("\n👋 测试结束")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/distance-judgement/tests/test_system.py b/distance-judgement/tests/test_system.py
new file mode 100644
index 00000000..b56d5fbd
--- /dev/null
+++ b/distance-judgement/tests/test_system.py
@@ -0,0 +1,224 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+系统综合测试脚本
+用于验证各个模块是否正常工作
+"""
+
+import cv2
+import numpy as np
+import sys
+import traceback
+import requests
+import json
+import time
+
+def test_opencv():
+ """测试OpenCV是否正常工作"""
+ print("=" * 50)
+ print("测试 OpenCV...")
+ try:
+ print(f"OpenCV 版本: {cv2.__version__}")
+
+ # 测试摄像头
+ cap = cv2.VideoCapture(0)
+ if cap.isOpened():
+ print("✓ 摄像头可以正常打开")
+ ret, frame = cap.read()
+ if ret:
+ print(f"✓ 摄像头可以正常读取画面,分辨率: {frame.shape[1]}x{frame.shape[0]}")
+ else:
+ print("✗ 无法从摄像头读取画面")
+ cap.release()
+ else:
+ print("✗ 无法打开摄像头")
+
+ return True
+ except Exception as e:
+ print(f"✗ OpenCV 测试失败: {e}")
+ return False
+
+def test_yolo_model():
+ """测试YOLO模型是否正常工作"""
+ print("=" * 50)
+ print("测试 YOLO 模型...")
+ try:
+ from ultralytics import YOLO
+ print("✓ ultralytics 库导入成功")
+
+ # 尝试加载模型
+ model = YOLO('yolov8n.pt')
+ print("✓ YOLOv8n 模型加载成功")
+
+ # 创建一个测试图像
+ test_image = np.zeros((640, 480, 3), dtype=np.uint8)
+ results = model(test_image, verbose=False)
+ print("✓ YOLO 推理测试成功")
+
+ return True
+ except Exception as e:
+ print(f"✗ YOLO 模型测试失败: {e}")
+ traceback.print_exc()
+ return False
+
+def test_modules():
+ """测试自定义模块"""
+ print("=" * 50)
+ print("测试自定义模块...")
+ try:
+ # 测试配置模块
+ from src import config
+ print("✓ config 模块导入成功")
+
+ # 测试距离计算模块
+ from src import DistanceCalculator
+ calculator = DistanceCalculator()
+ test_bbox = [100, 100, 200, 400] # 测试边界框
+ distance = calculator.get_distance(test_bbox)
+ print(f"✓ distance_calculator 模块测试成功,测试距离: {calculator.format_distance(distance)}")
+
+ # 测试人体检测模块
+ from src import PersonDetector
+ detector = PersonDetector()
+ print("✓ person_detector 模块导入成功")
+
+ # 测试地图管理器
+ from src import MapManager
+ map_manager = MapManager(
+ api_key=config.GAODE_API_KEY,
+ camera_lat=config.CAMERA_LATITUDE,
+ camera_lng=config.CAMERA_LONGITUDE
+ )
+ print("✓ map_manager 模块导入成功")
+
+ # 测试手机连接器
+ from src import MobileConnector
+ mobile_connector = MobileConnector(port=8081) # 使用不同端口避免冲突
+ print("✓ mobile_connector 模块导入成功")
+
+ return True
+ except Exception as e:
+ print(f"✗ 自定义模块测试失败: {e}")
+ traceback.print_exc()
+ return False
+
+def test_flask():
+ """测试Flask是否安装"""
+ print("=" * 50)
+ print("测试 Flask 环境...")
+ try:
+ import flask
+ print(f"✓ Flask 导入成功,版本: {flask.__version__}")
+
+ # 测试Web服务器模块
+ from src import WebServer
+ print("✓ WebServer 模块导入成功")
+
+ return True
+ except ImportError:
+ print("✗ Flask 未安装,Web功能不可用")
+ return False
+ except Exception as e:
+ print(f"✗ Flask 测试失败: {e}")
+ return False
+
+def test_web_apis(base_url="http://127.0.0.1:5000"):
+ """测试Web API接口(仅在服务器运行时测试)"""
+ print("=" * 50)
+ print("测试Web API接口...")
+
+ try:
+ # 测试主页面
+ response = requests.get(f"{base_url}/", timeout=5)
+ if response.status_code == 200:
+ print("✓ 主页面访问正常")
+
+ # 测试人员数据API
+ response = requests.get(f"{base_url}/api/get_persons_data", timeout=5)
+ if response.status_code == 200:
+ data = response.json()
+ print(f"✓ 人员数据API正常: {len(data)} 个人员")
+
+ # 测试调试信息API
+ response = requests.get(f"{base_url}/api/debug_info", timeout=5)
+ if response.status_code == 200:
+ data = response.json()
+ print(f"✓ 调试信息API正常")
+ print(f" 摄像头状态: {'在线' if data.get('camera_active') else '离线'}")
+
+ # 测试手机相关API
+ response = requests.get(f"{base_url}/api/mobile/devices", timeout=5)
+ if response.status_code == 200:
+ devices = response.json()
+ print(f"✓ 手机设备API正常: {len(devices)} 个设备")
+
+ # 测试手机端页面
+ response = requests.get(f"{base_url}/mobile/mobile_client.html", timeout=5)
+ if response.status_code == 200:
+ print(f"✓ 手机端页面可访问")
+
+ return True
+ else:
+ print("✗ Web服务器未响应")
+ return False
+
+ except requests.exceptions.ConnectionError:
+ print("⚠️ Web服务器未运行,跳过API测试")
+ return True # 不算失败,因为服务器可能没启动
+ except Exception as e:
+ print(f"✗ Web API测试失败: {e}")
+ return False
+
+def main():
+ """主测试函数"""
+ print("🔧 开始系统综合测试...")
+ print("测试将验证所有必要的组件是否正常工作")
+
+ tests = [
+ ("OpenCV", test_opencv),
+ ("YOLO模型", test_yolo_model),
+ ("自定义模块", test_modules),
+ ("Flask环境", test_flask),
+ ("Web API", test_web_apis),
+ ]
+
+ results = {}
+
+ for test_name, test_func in tests:
+ print(f"\n🧪 开始测试: {test_name}")
+ results[test_name] = test_func()
+
+ # 显示测试结果摘要
+ print("\n" + "=" * 50)
+ print("📊 测试结果摘要:")
+ print("=" * 50)
+
+ passed = 0
+ total = len(tests)
+
+ for test_name, result in results.items():
+ status = "✓ 通过" if result else "✗ 失败"
+ print(f"{test_name:<15}: {status}")
+ if result:
+ passed += 1
+
+ print(f"\n总体结果: {passed}/{total} 测试通过")
+
+ if passed >= total - 1: # 允许Web API测试失败(因为可能没启动服务器)
+ print("🎉 系统基本功能正常!")
+ print("\n📖 使用建议:")
+ print(" 1. 运行 'python run.py' 选择运行模式")
+ print(" 2. 使用Web模式获得最佳体验")
+ print(" 3. 配置摄像头位置获得准确的地图显示")
+ else:
+ print("⚠️ 系统存在问题,请检查相关组件")
+ print("\n🔧 建议操作:")
+ print(" 1. 重新运行 'python tools/install.py' 安装依赖")
+ print(" 2. 检查requirements.txt中的依赖版本")
+ print(" 3. 确认摄像头设备连接正常")
+
+ print("\n测试完成!")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/distance-judgement/tools/__pycache__/setup_camera_location.cpython-311.pyc b/distance-judgement/tools/__pycache__/setup_camera_location.cpython-311.pyc
new file mode 100644
index 00000000..815e9cf5
Binary files /dev/null and b/distance-judgement/tools/__pycache__/setup_camera_location.cpython-311.pyc differ
diff --git a/distance-judgement/tools/auto_configure_camera.py b/distance-judgement/tools/auto_configure_camera.py
new file mode 100644
index 00000000..5657c1db
--- /dev/null
+++ b/distance-judgement/tools/auto_configure_camera.py
@@ -0,0 +1,158 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+摄像头自动配置工具
+自动获取设备位置和朝向,设置摄像头参数
+"""
+
+import sys
+import os
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from src.orientation_detector import OrientationDetector
+
+
+def main():
+ """主函数"""
+ print("=" * 60)
+ print("🤖 摄像头自动配置工具")
+ print("=" * 60)
+ print()
+
+ print("🎯 功能说明:")
+ print(" • 自动获取当前设备的GPS位置")
+ print(" • 自动检测设备朝向")
+ print(" • 计算摄像头应该面向用户的角度")
+ print(" • 自动更新系统配置文件")
+ print()
+
+ print("⚠️ 注意事项:")
+ print(" • 请确保设备连接到互联网")
+ print(" • Windows系统可能需要开启位置服务")
+ print(" • 桌面设备朝向检测精度有限")
+ print()
+
+ try:
+ # 创建朝向检测器
+ detector = OrientationDetector()
+
+ # 执行自动配置
+ result = detector.auto_configure_camera_location()
+
+ if result['success']:
+ print()
+ print("✅ 自动配置成功!")
+ print("📊 配置详情:")
+ print(f" 📍 GPS位置: {result['gps_location'][0]:.6f}, {result['gps_location'][1]:.6f}")
+ print(f" 🧭 设备朝向: {result['device_heading']:.1f}°")
+ print(f" 📷 摄像头朝向: {result['camera_heading']:.1f}°")
+ print(f" 🎯 定位方法: {result['method']}")
+ print(f" 📏 定位精度: ±{result['accuracy']:.0f}m")
+ print()
+
+ # 询问是否应用配置
+ while True:
+ choice = input("🔧 是否应用此配置? (y/n/r): ").strip().lower()
+
+ if choice == 'y':
+ # 应用配置
+ detector.update_camera_config(
+ result['gps_location'],
+ result['camera_heading']
+ )
+ print("✅ 配置已应用到系统!")
+ break
+
+ elif choice == 'n':
+ print("⏭️ 配置未应用")
+ break
+
+ elif choice == 'r':
+ # 重新检测
+ print("\n🔄 重新检测...")
+ result = detector.auto_configure_camera_location()
+ if not result['success']:
+ print("❌ 重新检测失败")
+ break
+
+ print("📊 新的配置详情:")
+ print(f" 📍 GPS位置: {result['gps_location'][0]:.6f}, {result['gps_location'][1]:.6f}")
+ print(f" 🧭 设备朝向: {result['device_heading']:.1f}°")
+ print(f" 📷 摄像头朝向: {result['camera_heading']:.1f}°")
+ print(f" 🎯 定位方法: {result['method']}")
+ print(f" 📏 定位精度: ±{result['accuracy']:.0f}m")
+ print()
+
+ else:
+ print("❌ 请输入 y(应用)/n(取消)/r(重新检测)")
+
+ else:
+ print("❌ 自动配置失败")
+ print("💡 建议:")
+ print(" 1. 检查网络连接")
+ print(" 2. 使用手动配置: python tools/setup_camera_location.py")
+ print(" 3. 或在Web界面中手动设置")
+
+ except KeyboardInterrupt:
+ print("\n🔴 用户取消操作")
+
+ except Exception as e:
+ print(f"❌ 配置过程出错: {e}")
+ print("💡 建议使用手动配置工具")
+
+ finally:
+ print("\n👋 配置工具结束")
+
+
+def test_gps_only():
+ """仅测试GPS定位功能"""
+ print("🧪 GPS定位测试")
+ print("-" * 30)
+
+ detector = OrientationDetector()
+ location = detector.get_current_gps_location()
+
+ if location:
+ lat, lng, accuracy = location
+ print(f"✅ GPS测试成功:")
+ print(f" 📍 位置: {lat:.6f}, {lng:.6f}")
+ print(f" 📏 精度: ±{accuracy:.0f}m")
+ else:
+ print("❌ GPS测试失败")
+
+
+def test_heading_only():
+ """仅测试朝向检测功能"""
+ print("🧪 朝向检测测试")
+ print("-" * 30)
+
+ detector = OrientationDetector()
+ heading = detector.get_device_heading()
+
+ if heading is not None:
+ print(f"✅ 朝向测试成功:")
+ print(f" 🧭 设备朝向: {heading:.1f}°")
+
+ # 计算摄像头朝向
+ camera_heading = detector.calculate_camera_heading_facing_user(heading)
+ print(f" 📷 摄像头朝向: {camera_heading:.1f}°")
+ else:
+ print("❌ 朝向测试失败")
+
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(description='摄像头自动配置工具')
+ parser.add_argument('--test-gps', action='store_true', help='仅测试GPS功能')
+ parser.add_argument('--test-heading', action='store_true', help='仅测试朝向功能')
+
+ args = parser.parse_args()
+
+ if args.test_gps:
+ test_gps_only()
+ elif args.test_heading:
+ test_heading_only()
+ else:
+ main()
\ No newline at end of file
diff --git a/distance-judgement/tools/generate_ssl_cert.py b/distance-judgement/tools/generate_ssl_cert.py
new file mode 100644
index 00000000..0180b0af
--- /dev/null
+++ b/distance-judgement/tools/generate_ssl_cert.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+生成自签名SSL证书用于HTTPS服务
+"""
+
+import os
+import datetime
+import ipaddress
+from cryptography import x509
+from cryptography.x509.oid import NameOID
+from cryptography.hazmat.primitives import hashes, serialization
+from cryptography.hazmat.primitives.asymmetric import rsa
+
+def generate_ssl_certificate():
+ """生成自签名SSL证书"""
+
+ # 创建ssl目录
+ ssl_dir = "ssl"
+ if not os.path.exists(ssl_dir):
+ os.makedirs(ssl_dir)
+
+ print("🔑 正在生成SSL证书...")
+
+ # 生成私钥
+ private_key = rsa.generate_private_key(
+ public_exponent=65537,
+ key_size=2048,
+ )
+
+ # 创建证书主体
+ subject = issuer = x509.Name([
+ x509.NameAttribute(NameOID.COUNTRY_NAME, "CN"),
+ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Beijing"),
+ x509.NameAttribute(NameOID.LOCALITY_NAME, "Beijing"),
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Distance Judgement System"),
+ x509.NameAttribute(NameOID.COMMON_NAME, "localhost"),
+ ])
+
+ # 生成证书
+ cert = x509.CertificateBuilder().subject_name(
+ subject
+ ).issuer_name(
+ issuer
+ ).public_key(
+ private_key.public_key()
+ ).serial_number(
+ x509.random_serial_number()
+ ).not_valid_before(
+ datetime.datetime.utcnow()
+ ).not_valid_after(
+ datetime.datetime.utcnow() + datetime.timedelta(days=365)
+ ).add_extension(
+ x509.SubjectAlternativeName([
+ x509.DNSName("localhost"),
+ x509.DNSName("127.0.0.1"),
+ x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")),
+ x509.IPAddress(ipaddress.IPv4Address("0.0.0.0")),
+ ]),
+ critical=False,
+ ).sign(private_key, hashes.SHA256())
+
+ # 保存私钥
+ key_path = os.path.join(ssl_dir, "key.pem")
+ with open(key_path, "wb") as f:
+ f.write(private_key.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.PKCS8,
+ encryption_algorithm=serialization.NoEncryption()
+ ))
+
+ # 保存证书
+ cert_path = os.path.join(ssl_dir, "cert.pem")
+ with open(cert_path, "wb") as f:
+ f.write(cert.public_bytes(serialization.Encoding.PEM))
+
+ print(f"✅ SSL证书已生成:")
+ print(f" 🔑 私钥: {key_path}")
+ print(f" 📜 证书: {cert_path}")
+ print(f" 📅 有效期: 365天")
+ print()
+ print("⚠️ 注意: 这是自签名证书,浏览器会显示安全警告")
+ print(" 点击 '高级' -> '继续访问localhost(不安全)' 即可")
+
+if __name__ == "__main__":
+ try:
+ generate_ssl_certificate()
+ except ImportError:
+ print("❌ 缺少cryptography库,正在尝试安装...")
+ import subprocess
+ import sys
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "cryptography"])
+ print("✅ cryptography库安装完成,重新生成证书...")
+ generate_ssl_certificate()
+ except Exception as e:
+ print(f"❌ 生成SSL证书失败: {e}")
\ No newline at end of file
diff --git a/distance-judgement/tools/install.py b/distance-judgement/tools/install.py
new file mode 100644
index 00000000..2f83a295
--- /dev/null
+++ b/distance-judgement/tools/install.py
@@ -0,0 +1,264 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+自动安装脚本
+自动安装所需依赖并验证环境
+"""
+
+import subprocess
+import sys
+import time
+import os
+
+def print_header(title):
+ """打印标题"""
+ print("\n" + "=" * 60)
+ print(f" {title}")
+ print("=" * 60)
+
+def run_command(command, description):
+ """运行命令并显示结果"""
+ print(f"\n🔄 {description}...")
+ try:
+ result = subprocess.run(command, shell=True, capture_output=True, text=True)
+ if result.returncode == 0:
+ print(f"✅ {description} 成功完成")
+ return True
+ else:
+ print(f"❌ {description} 失败")
+ print(f"错误信息: {result.stderr}")
+ return False
+ except Exception as e:
+ print(f"❌ {description} 执行异常: {e}")
+ return False
+
+def check_python_version():
+ """检查Python版本"""
+ print_header("检查Python版本")
+
+ version = sys.version_info
+ print(f"当前Python版本: {version.major}.{version.minor}.{version.micro}")
+
+ if version.major < 3 or (version.major == 3 and version.minor < 8):
+ print("❌ Python版本过低,需要Python 3.8或更高版本")
+ return False
+ else:
+ print("✅ Python版本满足要求")
+ return True
+
+def install_requirements():
+ """安装依赖包"""
+ print_header("安装依赖包")
+
+ if not os.path.exists("requirements.txt"):
+ print("❌ requirements.txt 文件不存在")
+ return False
+
+ # 升级pip
+ if not run_command(f"{sys.executable} -m pip install --upgrade pip", "升级pip"):
+ print("⚠️ pip升级失败,继续安装依赖")
+
+ # 安装基础依赖
+ success1 = run_command(f"{sys.executable} -m pip install -r requirements.txt", "安装基础依赖包")
+
+ # 安装Flask(Web功能所需)
+ success2 = install_flask()
+
+ return success1 and success2
+
+def install_flask():
+ """安装Flask及其依赖"""
+ print("\n🔧 安装Flask(Web功能支持)...")
+
+ try:
+ # 检查是否已安装
+ import flask
+ print(f"✅ Flask已安装,版本: {flask.__version__}")
+ return True
+ except ImportError:
+ pass
+
+ # 尝试使用清华镜像源
+ result = subprocess.run([
+ sys.executable, "-m", "pip", "install",
+ "flask==2.3.3",
+ "-i", "https://pypi.tuna.tsinghua.edu.cn/simple/",
+ "--trusted-host", "pypi.tuna.tsinghua.edu.cn"
+ ], capture_output=True, text=True, timeout=300)
+
+ if result.returncode == 0:
+ print("✅ Flask安装成功!")
+ return True
+ else:
+ # 尝试官方源
+ result = subprocess.run([
+ sys.executable, "-m", "pip", "install", "flask==2.3.3"
+ ], capture_output=True, text=True, timeout=300)
+
+ if result.returncode == 0:
+ print("✅ Flask安装成功!")
+ return True
+ else:
+ print(f"❌ Flask安装失败: {result.stderr}")
+ return False
+
+def verify_installation():
+ """验证安装"""
+ print_header("验证安装")
+
+ modules_to_check = [
+ ("cv2", "OpenCV"),
+ ("numpy", "NumPy"),
+ ("ultralytics", "Ultralytics"),
+ ("torch", "PyTorch"),
+ ("flask", "Flask"),
+ ]
+
+ all_success = True
+
+ for module, name in modules_to_check:
+ try:
+ __import__(module)
+ print(f"✅ {name} 安装成功")
+ except ImportError:
+ print(f"❌ {name} 安装失败")
+ all_success = False
+
+ return all_success
+
+def download_yolo_model():
+ """预下载YOLO模型"""
+ print_header("下载YOLO模型")
+
+ try:
+ from ultralytics import YOLO
+ print("🔄 正在下载YOLOv8n模型,请稍候...")
+ model = YOLO('yolov8n.pt')
+ print("✅ YOLOv8n模型下载成功")
+
+ # 测试模块结构
+ try:
+ from src import PersonDetector, DistanceCalculator
+ print("✅ 重构后的模块结构测试成功")
+ except ImportError as e:
+ print(f"⚠️ 模块结构测试失败: {e}")
+
+ return True
+ except Exception as e:
+ print(f"❌ YOLO模型下载失败: {e}")
+ return False
+
+def run_test():
+ """运行测试"""
+ print_header("运行系统测试")
+
+ if os.path.exists("test_modules.py"):
+ return run_command(f"{sys.executable} test_modules.py", "运行系统测试")
+ else:
+ print("⚠️ 测试脚本不存在,跳过测试")
+ return True
+
+def create_desktop_shortcut():
+ """创建桌面快捷方式(Windows)"""
+ try:
+ import platform
+ if platform.system() == "Windows":
+ print_header("创建桌面快捷方式")
+
+ desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
+ if os.path.exists(desktop_path):
+ shortcut_content = f"""
+@echo off
+cd /d "{os.getcwd()}"
+python run.py
+pause
+"""
+ shortcut_path = os.path.join(desktop_path, "人体距离检测.bat")
+ with open(shortcut_path, "w", encoding="gbk") as f:
+ f.write(shortcut_content)
+ print(f"✅ 桌面快捷方式已创建: {shortcut_path}")
+ return True
+ else:
+ print("⚠️ 未找到桌面路径")
+ return False
+ except Exception as e:
+ print(f"⚠️ 创建快捷方式失败: {e}")
+ return False
+
+def main():
+ """主安装函数"""
+ print("🚀 人体距离检测系统 - 自动安装程序")
+ print("此程序将自动安装所需依赖并配置环境")
+
+ steps = [
+ ("检查Python版本", check_python_version),
+ ("安装依赖包", install_requirements),
+ ("验证安装", verify_installation),
+ ("下载YOLO模型", download_yolo_model),
+ ("运行系统测试", run_test),
+ ]
+
+ # 可选步骤
+ optional_steps = [
+ ("创建桌面快捷方式", create_desktop_shortcut),
+ ]
+
+ print(f"\n📋 安装计划:")
+ for i, (name, _) in enumerate(steps, 1):
+ print(f" {i}. {name}")
+
+ print(f"\n可选步骤:")
+ for i, (name, _) in enumerate(optional_steps, 1):
+ print(f" {i}. {name}")
+
+ input("\n按Enter键开始安装...")
+
+ start_time = time.time()
+ success_count = 0
+ total_steps = len(steps)
+
+ # 执行主要安装步骤
+ for i, (name, func) in enumerate(steps, 1):
+ print(f"\n📦 步骤 {i}/{total_steps}: {name}")
+
+ if func():
+ success_count += 1
+ print(f"✅ 步骤 {i} 完成")
+ else:
+ print(f"❌ 步骤 {i} 失败")
+
+ # 询问是否继续
+ choice = input("是否继续安装?(y/n): ").lower()
+ if choice != 'y':
+ print("安装已取消")
+ return
+
+ # 执行可选步骤
+ for name, func in optional_steps:
+ choice = input(f"\n是否执行: {name}?(y/n): ").lower()
+ if choice == 'y':
+ func()
+
+ # 显示安装结果
+ elapsed_time = time.time() - start_time
+ print_header("安装完成")
+
+ print(f"✅ 安装步骤: {success_count}/{total_steps} 完成")
+ print(f"⏱️ 总耗时: {elapsed_time:.1f}秒")
+
+ if success_count == total_steps:
+ print("🎉 所有步骤都已成功完成!")
+ print("\n📖 使用指南:")
+ print(" 1. 运行 'python run.py' 启动系统")
+ print(" 2. 选择运行模式(Web或传统模式)")
+ print(" 3. 如需配置摄像头位置,运行 setup_camera_location.py")
+ print(" 4. 如遇问题,运行 test_modules.py 进行诊断")
+ else:
+ print("⚠️ 部分步骤未成功,请检查错误信息")
+ print("💡 如需帮助,请查看README.md文档")
+
+ print("\n安装程序结束!")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/distance-judgement/tools/setup_camera_location.py b/distance-judgement/tools/setup_camera_location.py
new file mode 100644
index 00000000..31a7f0e0
--- /dev/null
+++ b/distance-judgement/tools/setup_camera_location.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+摄像头地理位置配置工具
+用于设置摄像头的经纬度坐标和朝向角度
+"""
+
+import os
+import sys
+
+def update_config_file(lat, lng, heading, api_key=None):
+ """更新配置文件中的摄像头位置信息"""
+ config_path = "src/config.py"
+
+ # 读取配置文件
+ with open(config_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ # 更新纬度
+ if "CAMERA_LATITUDE = " in content:
+ lines = content.split('\n')
+ for i, line in enumerate(lines):
+ if line.startswith('CAMERA_LATITUDE = '):
+ lines[i] = f'CAMERA_LATITUDE = {lat} # 摄像头纬度'
+ break
+ content = '\n'.join(lines)
+
+ # 更新经度
+ if "CAMERA_LONGITUDE = " in content:
+ lines = content.split('\n')
+ for i, line in enumerate(lines):
+ if line.startswith('CAMERA_LONGITUDE = '):
+ lines[i] = f'CAMERA_LONGITUDE = {lng} # 摄像头经度'
+ break
+ content = '\n'.join(lines)
+
+ # 更新朝向
+ if "CAMERA_HEADING = " in content:
+ lines = content.split('\n')
+ for i, line in enumerate(lines):
+ if line.startswith('CAMERA_HEADING = '):
+ lines[i] = f'CAMERA_HEADING = {heading} # 摄像头朝向角度'
+ break
+ content = '\n'.join(lines)
+
+ # 更新API key(如果提供)
+ if api_key and 'GAODE_API_KEY = ' in content:
+ lines = content.split('\n')
+ for i, line in enumerate(lines):
+ if line.startswith('GAODE_API_KEY = '):
+ lines[i] = f'GAODE_API_KEY = "{api_key}" # 高德地图API密钥'
+ break
+ content = '\n'.join(lines)
+
+ # 写回配置文件
+ with open(config_path, 'w', encoding='utf-8') as f:
+ f.write(content)
+
+def main():
+ print("=" * 60)
+ print("🚁 无人机摄像头地理位置配置工具")
+ print("=" * 60)
+ print()
+
+ print("📍 请设置摄像头的地理位置信息")
+ print("提示: 可以通过高德地图等应用获取准确的经纬度坐标")
+ print()
+
+ try:
+ # 获取纬度
+ lat = float(input("请输入摄像头纬度 (例: 39.9042): "))
+ if not (-90 <= lat <= 90):
+ raise ValueError("纬度必须在-90到90之间")
+
+ # 获取经度
+ lng = float(input("请输入摄像头经度 (例: 116.4074): "))
+ if not (-180 <= lng <= 180):
+ raise ValueError("经度必须在-180到180之间")
+
+ # 获取朝向
+ heading = float(input("请输入摄像头朝向角度 (0-360°, 0为正北): "))
+ if not (0 <= heading <= 360):
+ raise ValueError("朝向角度必须在0到360之间")
+
+ # 可选:设置高德API Key
+ print("\n🔑 高德地图API Key设置 (可选)")
+ print("如果您有高德开放平台的API Key,请输入以获得更好的地图体验")
+ api_key = input("请输入高德API Key (留空跳过): ").strip()
+
+ # 更新配置
+ update_config_file(lat, lng, heading, api_key if api_key else None)
+
+ print("\n✅ 配置更新成功!")
+ print(f"📍 摄像头位置: ({lat:.6f}, {lng:.6f})")
+ print(f"🧭 朝向角度: {heading}°")
+ if api_key:
+ print("🔑 API Key 已设置")
+
+ print("\n🎯 建议使用步骤:")
+ print("1. 运行 python run.py 启动系统")
+ print("2. 选择Web模式获得最佳体验")
+ print("3. 按 'm' 键打开地图查看效果")
+ print("4. 按 'c' 键校准距离参数以提高精度")
+
+ except ValueError as e:
+ print(f"❌ 输入错误: {e}")
+ print("请重新运行程序并输入正确的数值")
+ sys.exit(1)
+ except Exception as e:
+ print(f"❌ 配置失败: {e}")
+ sys.exit(1)
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/distance-judgement/tools/ssl/cert.pem b/distance-judgement/tools/ssl/cert.pem
new file mode 100644
index 00000000..786e3616
--- /dev/null
+++ b/distance-judgement/tools/ssl/cert.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDjzCCAnegAwIBAgIUJK9wxusX1FTV1FbBSRVlZUwUdmcwDQYJKoZIhvcNAQEL
+BQAwaTELMAkGA1UEBhMCQ04xEDAOBgNVBAgMB0JlaWppbmcxEDAOBgNVBAcMB0Jl
+aWppbmcxIjAgBgNVBAoMGURpc3RhbmNlIEp1ZGdlbWVudCBTeXN0ZW0xEjAQBgNV
+BAMMCWxvY2FsaG9zdDAeFw0yNTA2MjkwODQ2MDNaFw0yNjA2MjkwODQ2MDNaMGkx
+CzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdCZWlqaW5nMRAwDgYDVQQHDAdCZWlqaW5n
+MSIwIAYDVQQKDBlEaXN0YW5jZSBKdWRnZW1lbnQgU3lzdGVtMRIwEAYDVQQDDAls
+b2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCEb/6AFLiJ
+18UpEH5DKJdiXBAzc/c9ECzU/4k+ZJpp9Hs2SfhZhRvDpTw90dZReQfh2squyLcy
+DhyWTF+7nedSX4AaU1zQSwdQR/J+0T4fCH23gwxcSWRXA2yUrIgjgCtfc3wSzU+X
+eeA9ntjBAv+Axf9PPwMiqtxmWjNCc8YmDr24L0oBWwBSX0lsIB6cmTBOhwkAI5J5
+DWcHaIi/jPjojwJu6z8mGIPmaOBqf12ZJfMB1u3YUHysZYidVuA3TaR/Dx07tZJk
+yd/RT8sJYJ4VdiA7ZxOynrKh/z84fqpKl2xzuCHGIMJFsyTyKmsYNux0h0lCKGNL
+dLotTjYC8ravAgMBAAGjLzAtMCsGA1UdEQQkMCKCCWxvY2FsaG9zdIIJMTI3LjAu
+MC4xhwR/AAABhwQAAAAAMA0GCSqGSIb3DQEBCwUAA4IBAQAaU/fpR8g5mUmFjdco
+/0vkcoUxH4mO3w8cbVcRO513KKmV3mLQim+sNkL+mdEXSsHDdoiz/rjhTD6i9LW4
+qrQCIvYPJHtFr2SEdOJWLHsPMaOv86DnF0ufEIB22SmnFAFa75PN35p08JZoWiUk
+19RmC5gXn2G32eGRfwir9a+sB9lS4Q0MfmSdK8myb32JmuXkFWJgB5jtzEsVDX3q
+RpLVBlM7CIisX9+EfrjJVeaj5EnlLeFayHEnyuRBFy2k4mqdhdMOFxdmaqmTtmS+
+TFrmCiGGKU74HLmGr4m10ZBkL5hhw/7XtGqTDMzKLmPXf62j1HoJhhdzVH2QbbRy
+QnR2
+-----END CERTIFICATE-----
diff --git a/distance-judgement/tools/ssl/key.pem b/distance-judgement/tools/ssl/key.pem
new file mode 100644
index 00000000..60d75a6a
--- /dev/null
+++ b/distance-judgement/tools/ssl/key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCEb/6AFLiJ18Up
+EH5DKJdiXBAzc/c9ECzU/4k+ZJpp9Hs2SfhZhRvDpTw90dZReQfh2squyLcyDhyW
+TF+7nedSX4AaU1zQSwdQR/J+0T4fCH23gwxcSWRXA2yUrIgjgCtfc3wSzU+XeeA9
+ntjBAv+Axf9PPwMiqtxmWjNCc8YmDr24L0oBWwBSX0lsIB6cmTBOhwkAI5J5DWcH
+aIi/jPjojwJu6z8mGIPmaOBqf12ZJfMB1u3YUHysZYidVuA3TaR/Dx07tZJkyd/R
+T8sJYJ4VdiA7ZxOynrKh/z84fqpKl2xzuCHGIMJFsyTyKmsYNux0h0lCKGNLdLot
+TjYC8ravAgMBAAECggEAC9+MhggViUoeY3GWmEfF1qwhSbOeWUufcVMdh0n2rAQe
+nb3g9Ymg9RfVwEcVO0WqBr4aSLQ29FZeirz7IjNkXzavoeySWBw54iEpJOR2eMrG
+lpK5o3Zy9/gXHncfV2twuAR+/aKJfa+QAoZAsYEmzfEyU/T2v39o9gYlLVJ608OC
+iyb3xRsiidnonRR7pCIX2ghI/GJcKFZmYbc2g4hehBz6zBN7Xu7t26sUrdJd9tT2
+wWIEz0pH2Iutwiy4mlfkqJ+dezSZPCRXxLHbq2RRKn/17YNiCvTjBpsX83FxcwKR
+6XlIabWMNJ6EOvNGtwufXAUwrieHq6uPFx5mKHfIoQKBgQC6WWk0IyZOFyuqTNBq
+2axuXkxmU+xjfbu4NyFXxoNmyiR6VkK4tpcj7nV2ZN6gdA8pCOjZiQM9NA3GYPPv
+eOcTvgIL16cE4EdrkE+Jv+UF7SAhToPbrnBF9y2GN5FBk9L2tvDDeF0qcXzyIleK
+9dJYqoAxssCUIhASb5AsCoo6oQKBgQC18BqB1Ucp9tz7juRP+rT81zESJCEGpfu1
+TPOiZgjkr6jR5xsvFHOBtbnXkCi7hir1Wo9kRTujsL58Pu+vA7M6ZWjpGIBtdfmw
+fSUZmt+hW+V6O1K8WQRFQgErM3PJNBN6l/mLh9Lj39tyeFrrA1WBhtx4mVot4DTC
+ds9CVb0/TwKBgCXWX8kpVe7HP6N9o1f+yMdEOGkSo030Srh14TxMX4PwiYWZnESb
+NociNRGMG7QivK1NVNJOwqybtCxSpVU7jFfy3cF/0TbpPzc0/yFuKFeStVJt+dIS
+UlOyg7jb8Y+KL2zO6oYWG3yxvHgBxxq9HS/Jtuvgar/pRrAnnPOEVFrhAoGAHVwx
+6uHQKiV8Y9wbXAzJSEQx1wudiMUgaZGRf5OXu8/dHoJ9EIvsV/JLm03YROrR4+ZJ
+XZUOmsva8ZH2e/fM5I+Y7oTVtNRlBuYrJoansBJ0ZdVM9LgoyERui9oxxTZyLkZ4
+LtwsXDmz4DUr9uEC23Q3//4/X0ffO8KQj9PmRmECgYBR3YU15qledPzD2n5CtqKD
+EiVkB1TRZPq46bJFTZ/WhwvIOOrSDb4u7aYP4DzW7kzZt+uRiAHNfrY5AE29681c
+llt1kr+MrAbX0CdqYUWJoT0Z8Svuw083m9O0EPAZMiYT73izgcvlvvG5MT9uHkQB
+q6LmyYRBH1NLxYz0aFvY+w==
+-----END PRIVATE KEY-----
diff --git a/distance-judgement/yolov8n.pt b/distance-judgement/yolov8n.pt
new file mode 100644
index 00000000..d61ef50d
Binary files /dev/null and b/distance-judgement/yolov8n.pt differ