提交
This commit is contained in:
parent
41cf62006a
commit
d7a816d993
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
||||
/venv
|
||||
/venv
|
||||
|
@ -1,244 +0,0 @@
|
||||
# 📹 本地测试指南
|
||||
|
||||
## 🎯 测试视频文件放置位置
|
||||
|
||||
### 推荐目录结构:
|
||||
```
|
||||
checkHand/
|
||||
├── data/
|
||||
│ └── videos/ # 测试视频目录
|
||||
│ ├── test_basic.mp4 # 基础移动测试视频
|
||||
│ ├── test_gesture.mp4 # 手势测试视频
|
||||
│ ├── your_video.mp4 # 你的自定义视频
|
||||
│ └── real_hand.mp4 # 真实手部动作视频
|
||||
```
|
||||
|
||||
## 🚀 快速开始本地测试
|
||||
|
||||
### 1. 使用自动生成的测试视频
|
||||
|
||||
```bash
|
||||
# 生成测试视频
|
||||
python create_test_video.py
|
||||
|
||||
# 使用基础移动测试视频
|
||||
./start_service.sh --test-video data/videos/test_basic.mp4
|
||||
|
||||
# 使用手势测试视频
|
||||
./start_service.sh --test-video data/videos/test_gesture.mp4
|
||||
```
|
||||
|
||||
### 2. 使用你自己的视频文件
|
||||
|
||||
#### 支持的视频格式:
|
||||
- MP4 (推荐)
|
||||
- AVI
|
||||
- MOV
|
||||
- MKV
|
||||
|
||||
#### 推荐视频参数:
|
||||
- 分辨率:640x480 或 1280x720
|
||||
- 帧率:30 FPS
|
||||
- 编码:H.264
|
||||
- 时长:10-60秒(测试用)
|
||||
|
||||
#### 放置你的视频:
|
||||
```bash
|
||||
# 1. 将视频文件复制到测试目录
|
||||
cp /path/to/your/video.mp4 data/videos/
|
||||
|
||||
# 2. 启动服务器并使用你的视频
|
||||
./start_service.sh --test-video data/videos/your_video.mp4
|
||||
```
|
||||
|
||||
## 🎬 创建自定义测试视频
|
||||
|
||||
### 使用脚本生成:
|
||||
```bash
|
||||
# 生成基础移动测试视频(10秒)
|
||||
python create_test_video.py --type basic --duration 10
|
||||
|
||||
# 生成手势测试视频(15秒)
|
||||
python create_test_video.py --type gesture --duration 15
|
||||
|
||||
# 生成两种类型的视频
|
||||
python create_test_video.py --type both --duration 12
|
||||
|
||||
# 自定义帧率和输出目录
|
||||
python create_test_video.py --fps 60 --output-dir my_videos/
|
||||
```
|
||||
|
||||
### 使用手机录制真实手部视频:
|
||||
|
||||
#### 录制建议:
|
||||
1. **光线充足** - 避免阴影和反光
|
||||
2. **背景简单** - 纯色背景最佳
|
||||
3. **手部清晰** - 确保手部在画面中央
|
||||
4. **动作缓慢** - 便于算法跟踪
|
||||
5. **时长适中** - 10-30秒即可
|
||||
|
||||
#### 录制内容示例:
|
||||
- 手掌张开和握拳
|
||||
- 手指抓取动作
|
||||
- 手部左右移动
|
||||
- 手部上下移动
|
||||
- 手部前后移动(改变大小)
|
||||
|
||||
#### 文件转换:
|
||||
```bash
|
||||
# 如果需要转换格式,可以使用 ffmpeg
|
||||
ffmpeg -i input.mov -c:v libx264 -c:a aac data/videos/output.mp4
|
||||
```
|
||||
|
||||
## 🔧 测试步骤
|
||||
|
||||
### 1. 准备测试环境
|
||||
|
||||
```bash
|
||||
# 1. 确保虚拟环境已激活
|
||||
source venv/bin/activate
|
||||
|
||||
# 2. 检查测试视频是否存在
|
||||
ls -la data/videos/
|
||||
|
||||
# 3. 运行系统测试
|
||||
python test_system.py
|
||||
```
|
||||
|
||||
### 2. 启动测试服务
|
||||
|
||||
```bash
|
||||
# 方式1:使用启动脚本
|
||||
./start_service.sh --test-video data/videos/test_basic.mp4
|
||||
|
||||
# 方式2:手动启动
|
||||
python run_web_service.py --test-video data/videos/test_basic.mp4
|
||||
```
|
||||
|
||||
### 3. 访问测试界面
|
||||
|
||||
1. 打开浏览器访问:`http://localhost:5000`
|
||||
2. 观察Web界面显示:
|
||||
- 实时视频预览
|
||||
- 手部检测结果
|
||||
- 3D坐标值
|
||||
- 控制信号
|
||||
|
||||
### 4. 测试机械臂客户端
|
||||
|
||||
```bash
|
||||
# 新开一个终端窗口
|
||||
./start_robot_client.sh
|
||||
|
||||
# 或者手动启动
|
||||
cd src
|
||||
python robot_client.py --mock
|
||||
```
|
||||
|
||||
## 📊 测试检查项
|
||||
|
||||
### ✅ 视频显示检查:
|
||||
- [ ] 视频正常播放
|
||||
- [ ] 手部检测框显示正确
|
||||
- [ ] 关键点连接线正常
|
||||
- [ ] 坐标轴指示器显示
|
||||
|
||||
### ✅ 数据输出检查:
|
||||
- [ ] X、Y、Z角度值变化
|
||||
- [ ] 抓取状态检测
|
||||
- [ ] 动作识别正确
|
||||
- [ ] FPS显示正常
|
||||
|
||||
### ✅ 通信检查:
|
||||
- [ ] WebSocket连接正常
|
||||
- [ ] 机械臂客户端接收信号
|
||||
- [ ] 控制信号格式正确
|
||||
- [ ] 实时性满足要求
|
||||
|
||||
## 🛠️ 常见问题排查
|
||||
|
||||
### 问题1:视频无法播放
|
||||
```bash
|
||||
# 检查视频文件
|
||||
file data/videos/your_video.mp4
|
||||
|
||||
# 检查视频信息
|
||||
ffmpeg -i data/videos/your_video.mp4 2>&1 | head -20
|
||||
```
|
||||
|
||||
### 问题2:检测效果不佳
|
||||
- 检查视频光线条件
|
||||
- 确认手部在画面中央
|
||||
- 尝试不同的视频文件
|
||||
- 调整MediaPipe参数
|
||||
|
||||
### 问题3:性能问题
|
||||
```bash
|
||||
# 降低视频分辨率
|
||||
ffmpeg -i input.mp4 -vf scale=640:480 output.mp4
|
||||
|
||||
# 减少帧率
|
||||
ffmpeg -i input.mp4 -r 15 output.mp4
|
||||
```
|
||||
|
||||
## 📈 性能测试
|
||||
|
||||
### 测试延迟:
|
||||
```bash
|
||||
# 启动服务器
|
||||
./start_service.sh --test-video data/videos/test_basic.mp4
|
||||
|
||||
# 在另一个终端查看延迟
|
||||
curl -s http://localhost:5000/api/status | jq .fps
|
||||
```
|
||||
|
||||
### 测试负载:
|
||||
```bash
|
||||
# 启动多个机械臂客户端
|
||||
for i in {1..5}; do
|
||||
python src/robot_client.py --mock &
|
||||
done
|
||||
```
|
||||
|
||||
## 🎯 高级测试
|
||||
|
||||
### 使用真实摄像头:
|
||||
```python
|
||||
# 创建摄像头测试脚本
|
||||
import cv2
|
||||
import socketio
|
||||
import base64
|
||||
|
||||
sio = socketio.Client()
|
||||
sio.connect('http://localhost:5000')
|
||||
sio.emit('register_client', {'type': 'video_source'})
|
||||
|
||||
cap = cv2.VideoCapture(0)
|
||||
while True:
|
||||
ret, frame = cap.read()
|
||||
if ret:
|
||||
_, buffer = cv2.imencode('.jpg', frame)
|
||||
frame_data = base64.b64encode(buffer).decode('utf-8')
|
||||
sio.emit('video_frame', {'frame': frame_data})
|
||||
time.sleep(1/30)
|
||||
```
|
||||
|
||||
### 性能监控:
|
||||
```bash
|
||||
# 监控资源使用
|
||||
top -p $(pgrep -f "python run_web_service.py")
|
||||
|
||||
# 监控网络流量
|
||||
netstat -i
|
||||
```
|
||||
|
||||
## 🎉 测试完成
|
||||
|
||||
测试完成后,你应该能看到:
|
||||
- 实时视频流处理
|
||||
- 准确的手部检测
|
||||
- 正确的3D坐标计算
|
||||
- 稳定的WebSocket通信
|
||||
- 机械臂控制信号输出
|
||||
|
||||
现在你可以将真实的视频流连接到系统,或者集成到你的机械臂控制系统中!
|
27
README.md
27
README.md
@ -1,28 +1,26 @@
|
||||
# 实时手部检测Web服务系统
|
||||
# 实时手部检测Web服务系统----下位机实现
|
||||
|
||||
|
||||
|
||||
一个基于MediaPipe的实时手部检测系统,支持WebSocket通信、实时视频流处理和机械臂控制。
|
||||
|
||||
## 快速开始
|
||||
|
||||
|
||||
### 目录介绍
|
||||
+ src/:下位机源码
|
||||
+ start_robot_client:启动机械臂客户端脚本
|
||||
|
||||
|
||||
### 方式一:使用启动脚本(推荐)
|
||||
|
||||
#### Linux/Mac:
|
||||
#### 树莓派
|
||||
```bash
|
||||
# 启动Web服务器(自动创建虚拟环境并安装依赖)
|
||||
./start_service.sh
|
||||
|
||||
# 启动机械臂客户端(新终端窗口)
|
||||
./start_robot_client.sh
|
||||
```
|
||||
|
||||
#### Windows:
|
||||
```batch
|
||||
# 启动Web服务器(自动创建虚拟环境并安装依赖)
|
||||
start_service.bat
|
||||
|
||||
# 启动机械臂客户端(新命令行窗口)
|
||||
start_robot_client.bat
|
||||
```
|
||||
|
||||
### 方式二:手动启动
|
||||
|
||||
@ -31,9 +29,6 @@ start_robot_client.bat
|
||||
```bash
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate # Linux/Mac
|
||||
# 或
|
||||
venv\Scripts\activate # Windows
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
#### 2. 启动Web服务器
|
||||
@ -42,7 +37,7 @@ pip install -r requirements.txt
|
||||
python run_web_service.py
|
||||
```
|
||||
|
||||
#### 3. 启动机械臂客户端(可选)
|
||||
#### 3. 启动机械臂客户端(
|
||||
|
||||
```bash
|
||||
cd src
|
||||
|
@ -1,193 +0,0 @@
|
||||
# 🎬 视频选择器使用指南
|
||||
|
||||
## 🎯 新功能介绍
|
||||
|
||||
现在Web界面支持**选择本地视频**进行测试!不再需要在命令行指定视频文件,直接在Web界面选择即可。
|
||||
|
||||
## 🖥️ 界面功能
|
||||
|
||||
### 📋 视频选择区域
|
||||
- **视频下拉列表** - 显示所有可用的本地视频文件
|
||||
- **开始视频测试按钮** - 使用选中的视频开始测试
|
||||
- **刷新视频列表按钮** - 重新扫描视频文件
|
||||
|
||||
### 🔍 支持的视频文件
|
||||
|
||||
系统会自动扫描以下目录中的视频文件:
|
||||
- `data/videos/` - 主要测试视频目录
|
||||
- `videos/` - 备用视频目录
|
||||
- `.` - 当前目录
|
||||
|
||||
支持的视频格式:
|
||||
- `.mp4` (推荐)
|
||||
- `.avi`
|
||||
- `.mov`
|
||||
- `.mkv`
|
||||
- `.wmv`
|
||||
- `.flv`
|
||||
|
||||
## 🚀 使用步骤
|
||||
|
||||
### 1. 启动服务器
|
||||
```bash
|
||||
./start_service.sh
|
||||
```
|
||||
|
||||
### 2. 访问Web界面
|
||||
打开浏览器访问 `http://localhost:5000`
|
||||
|
||||
### 3. 选择视频
|
||||
- 在"选择测试视频"下拉列表中选择一个视频文件
|
||||
- 视频显示格式:`文件名 (文件大小)`
|
||||
|
||||
### 4. 开始测试
|
||||
点击"开始视频测试"按钮
|
||||
|
||||
### 5. 观察结果
|
||||
- 查看系统日志中的反馈信息
|
||||
- 观察视频预览和检测结果
|
||||
- 查看实时的控制数据
|
||||
|
||||
## 📁 添加自定义视频
|
||||
|
||||
### 方法1:复制到测试目录
|
||||
```bash
|
||||
# 将你的视频文件复制到测试目录
|
||||
cp /path/to/your/video.mp4 data/videos/
|
||||
|
||||
# 在Web界面点击"刷新视频列表"
|
||||
```
|
||||
|
||||
### 方法2:使用脚本生成
|
||||
```bash
|
||||
# 生成多种测试视频
|
||||
python create_test_video.py --type both --duration 20
|
||||
|
||||
# 生成自定义视频
|
||||
python create_test_video.py --type gesture --duration 30 --fps 60
|
||||
```
|
||||
|
||||
## 🎨 下拉列表显示
|
||||
|
||||
视频列表会显示以下信息:
|
||||
- **文件名** - 视频文件的名称
|
||||
- **文件大小** - 文件大小(MB)
|
||||
- **状态提示** - 如果没有视频文件,会显示提示信息
|
||||
|
||||
示例:
|
||||
```
|
||||
请选择视频文件
|
||||
test_basic.mp4 (1.4MB)
|
||||
test_gesture.mp4 (1.3MB)
|
||||
my_hand_video.mp4 (2.1MB)
|
||||
```
|
||||
|
||||
## 🔧 功能特点
|
||||
|
||||
### ✅ 自动扫描
|
||||
- 页面加载时自动扫描视频文件
|
||||
- 支持多个目录扫描
|
||||
- 按文件名自动排序
|
||||
|
||||
### ✅ 实时反馈
|
||||
- 显示视频加载状态
|
||||
- 实时错误提示
|
||||
- 操作成功确认
|
||||
|
||||
### ✅ 文件验证
|
||||
- 检查视频文件是否存在
|
||||
- 显示文件大小信息
|
||||
- 支持多种视频格式
|
||||
|
||||
### ✅ 用户友好
|
||||
- 清晰的操作指引
|
||||
- 详细的错误提示
|
||||
- 一键刷新功能
|
||||
|
||||
## 🛠️ 错误处理
|
||||
|
||||
### 常见错误和解决方案
|
||||
|
||||
#### 1. "未找到视频文件"
|
||||
**原因**:没有视频文件在指定目录
|
||||
**解决**:
|
||||
```bash
|
||||
# 生成测试视频
|
||||
python create_test_video.py
|
||||
|
||||
# 或复制视频文件
|
||||
cp your_video.mp4 data/videos/
|
||||
```
|
||||
|
||||
#### 2. "请先选择一个视频文件"
|
||||
**原因**:没有在下拉列表中选择视频
|
||||
**解决**:在下拉列表中选择一个视频文件
|
||||
|
||||
#### 3. "视频文件不存在"
|
||||
**原因**:选择的视频文件已被删除或移动
|
||||
**解决**:点击"刷新视频列表"重新扫描
|
||||
|
||||
## 📊 系统日志
|
||||
|
||||
测试过程中会显示以下日志信息:
|
||||
|
||||
### 成功信息
|
||||
- `✅ 找到 3 个视频文件`
|
||||
- `✅ 本地测试已开始,使用视频: test_basic.mp4`
|
||||
- `正在启动视频测试: data/videos/test_gesture.mp4`
|
||||
|
||||
### 错误信息
|
||||
- `❌ 未找到测试视频文件`
|
||||
- `❌ 请先选择一个视频文件`
|
||||
- `❌ 视频文件不存在: xxx.mp4`
|
||||
|
||||
### 帮助信息
|
||||
- `💡 运行 python create_test_video.py 生成测试视频`
|
||||
|
||||
## 🎯 高级使用
|
||||
|
||||
### 批量添加视频
|
||||
```bash
|
||||
# 创建多个测试视频
|
||||
for i in {1..5}; do
|
||||
python create_test_video.py --type basic --duration $((i*5)) --output-dir data/videos
|
||||
mv data/videos/test_basic.mp4 data/videos/test_basic_${i}.mp4
|
||||
done
|
||||
```
|
||||
|
||||
### 视频格式转换
|
||||
```bash
|
||||
# 如果你的视频格式不支持,可以转换
|
||||
ffmpeg -i input.mov -c:v libx264 -c:a aac data/videos/output.mp4
|
||||
```
|
||||
|
||||
### 视频质量优化
|
||||
```bash
|
||||
# 压缩视频文件
|
||||
ffmpeg -i input.mp4 -crf 23 -preset medium data/videos/compressed.mp4
|
||||
|
||||
# 调整分辨率
|
||||
ffmpeg -i input.mp4 -vf scale=640:480 data/videos/resized.mp4
|
||||
```
|
||||
|
||||
## 🔄 与命令行模式的区别
|
||||
|
||||
### Web界面模式(新)
|
||||
- ✅ 图形化选择视频
|
||||
- ✅ 实时预览文件列表
|
||||
- ✅ 一键刷新功能
|
||||
- ✅ 错误提示更友好
|
||||
|
||||
### 命令行模式(旧)
|
||||
- 需要手动指定视频路径
|
||||
- 需要重启服务器切换视频
|
||||
- 错误信息在终端显示
|
||||
|
||||
## 🎉 现在就试试吧!
|
||||
|
||||
1. 确保有测试视频:`python create_test_video.py`
|
||||
2. 启动服务器:`./start_service.sh`
|
||||
3. 访问:`http://localhost:5000`
|
||||
4. 选择视频并开始测试!
|
||||
|
||||
享受更便捷的视频测试体验! 🚀
|
@ -1,135 +0,0 @@
|
||||
# 🖥️ Web界面使用指南
|
||||
|
||||
## 📋 界面功能说明
|
||||
|
||||
### 🎯 "开始本地视频测试" 按钮功能
|
||||
|
||||
这个按钮的作用是:
|
||||
- **启动预置测试视频** - 使用系统自带的测试视频进行手部检测
|
||||
- **无需外部视频流** - 不需要摄像头或外部视频文件
|
||||
- **一键测试** - 点击按钮即可开始测试系统功能
|
||||
|
||||
### 🔧 按钮工作原理
|
||||
|
||||
1. **点击按钮** → 发送 `start_local_test` 事件到服务器
|
||||
2. **服务器检查** → 查找以下测试视频文件:
|
||||
- `data/videos/test_basic.mp4` (基础手部移动测试)
|
||||
- `data/videos/test_gesture.mp4` (手势测试)
|
||||
3. **自动播放** → 找到测试视频后自动开始播放和检测
|
||||
4. **实时显示** → 在Web界面显示检测结果
|
||||
|
||||
### 📁 测试视频文件要求
|
||||
|
||||
按钮会按顺序查找以下文件:
|
||||
```
|
||||
data/videos/test_basic.mp4 # 第一优先级
|
||||
data/videos/test_gesture.mp4 # 第二优先级
|
||||
```
|
||||
|
||||
### 🎬 如何生成测试视频
|
||||
|
||||
如果测试视频不存在,运行以下命令生成:
|
||||
|
||||
```bash
|
||||
# 生成测试视频
|
||||
python create_test_video.py
|
||||
|
||||
# 或者指定类型
|
||||
python create_test_video.py --type both --duration 10
|
||||
```
|
||||
|
||||
### 🔍 按钮状态说明
|
||||
|
||||
| 按钮状态 | 说明 |
|
||||
|---------|------|
|
||||
| "开始本地视频测试" | 按钮就绪,可以点击 |
|
||||
| "启动中..." | 正在启动测试,按钮禁用 |
|
||||
| 恢复原状态 | 5秒后自动恢复 |
|
||||
|
||||
### 📊 测试结果显示
|
||||
|
||||
点击按钮后,你会看到:
|
||||
|
||||
1. **系统日志区域**:
|
||||
- ✅ 成功信息:`本地测试已开始,使用视频: data/videos/test_basic.mp4`
|
||||
- ❌ 错误信息:`未找到测试视频文件`
|
||||
- 💡 帮助信息:`请先生成测试视频:python create_test_video.py`
|
||||
|
||||
2. **视频预览区域**:
|
||||
- 显示测试视频内容
|
||||
- 显示手部检测结果
|
||||
- 显示关键点和连接线
|
||||
|
||||
3. **控制面板**:
|
||||
- 实时更新的X、Y、Z角度值
|
||||
- 抓取状态指示器
|
||||
- 当前动作显示
|
||||
- FPS计数
|
||||
|
||||
### 🚨 常见问题
|
||||
|
||||
#### 问题1:按钮点击没有反应
|
||||
**原因**:测试视频文件不存在
|
||||
**解决**:
|
||||
```bash
|
||||
# 检查文件是否存在
|
||||
ls -la data/videos/
|
||||
|
||||
# 如果没有文件,生成测试视频
|
||||
python create_test_video.py
|
||||
```
|
||||
|
||||
#### 问题2:出现错误信息
|
||||
**查看日志**:在Web界面的"系统日志"区域查看具体错误信息
|
||||
|
||||
#### 问题3:视频不显示
|
||||
**检查**:
|
||||
- 确保测试视频文件存在
|
||||
- 查看浏览器控制台是否有错误
|
||||
- 检查服务器日志
|
||||
|
||||
### 🎯 使用步骤
|
||||
|
||||
1. **确保测试视频存在**:
|
||||
```bash
|
||||
# 检查文件
|
||||
ls data/videos/
|
||||
|
||||
# 如果没有,生成测试视频
|
||||
python create_test_video.py
|
||||
```
|
||||
|
||||
2. **启动服务器**:
|
||||
```bash
|
||||
./start_service.sh
|
||||
```
|
||||
|
||||
3. **打开Web界面**:
|
||||
访问 `http://localhost:5000`
|
||||
|
||||
4. **点击测试按钮**:
|
||||
点击"开始本地视频测试"按钮
|
||||
|
||||
5. **观察结果**:
|
||||
查看视频预览和检测数据
|
||||
|
||||
### 💡 提示
|
||||
|
||||
- 这个功能主要用于**系统测试**和**功能演示**
|
||||
- 如果要使用**真实视频流**,需要:
|
||||
- 启动时指定视频文件:`./start_service.sh --test-video your_video.mp4`
|
||||
- 或者通过外部客户端发送视频流
|
||||
|
||||
### 🔗 相关文档
|
||||
|
||||
- [本地测试指南](LOCAL_TEST_GUIDE.md) - 详细的本地测试说明
|
||||
- [README.md](README.md) - 项目总体说明
|
||||
- [系统测试脚本](test_system.py) - 自动化测试
|
||||
|
||||
## 🎉 现在试试吧!
|
||||
|
||||
1. 运行 `python create_test_video.py` 生成测试视频
|
||||
2. 启动服务器 `./start_service.sh`
|
||||
3. 打开 `http://localhost:5000`
|
||||
4. 点击"开始本地视频测试"按钮
|
||||
5. 观察手部检测的神奇效果!
|
@ -1 +0,0 @@
|
||||
Subproject commit 7903ed308ff4bf115e5502dd1efb235e61e62dc3
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
启动手部检测Web服务
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加src目录到Python路径
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
||||
|
||||
from web_server import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Binary file not shown.
@ -1,181 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 > nul
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
:: 手部检测Web服务启动脚本 (Windows版本)
|
||||
:: 包含虚拟环境激活和服务启动
|
||||
|
||||
:: 设置脚本目录
|
||||
cd /d "%~dp0"
|
||||
|
||||
:: 颜色定义 (Windows CMD不支持颜色,使用echo代替)
|
||||
echo ================================================
|
||||
echo 🤖 手部检测Web服务启动器 (Windows)
|
||||
echo ================================================
|
||||
echo.
|
||||
|
||||
:: 检查Python是否存在
|
||||
python --version >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo [ERROR] Python 未找到,请先安装Python
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "tokens=*" %%i in ('python --version 2^>^&1') do set PYTHON_VERSION=%%i
|
||||
echo [INFO] Python版本: %PYTHON_VERSION%
|
||||
|
||||
:: 检查虚拟环境是否存在
|
||||
if not exist "venv" (
|
||||
echo [WARNING] 虚拟环境不存在,正在创建...
|
||||
python -m venv venv
|
||||
if %errorlevel% neq 0 (
|
||||
echo [ERROR] 虚拟环境创建失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [SUCCESS] 虚拟环境创建成功
|
||||
)
|
||||
|
||||
:: 激活虚拟环境
|
||||
echo [INFO] 正在激活虚拟环境...
|
||||
call venv\Scripts\activate.bat
|
||||
if %errorlevel% neq 0 (
|
||||
echo [ERROR] 虚拟环境激活失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [SUCCESS] 虚拟环境已激活
|
||||
|
||||
:: 检查依赖是否已安装
|
||||
echo [INFO] 检查Python依赖...
|
||||
if exist "requirements.txt" (
|
||||
python -c "import flask, flask_socketio, cv2, mediapipe" >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo [WARNING] 依赖未完全安装,正在安装...
|
||||
pip install -r requirements.txt
|
||||
if %errorlevel% neq 0 (
|
||||
echo [ERROR] 依赖安装失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [SUCCESS] 依赖安装成功
|
||||
) else (
|
||||
echo [SUCCESS] 依赖已安装
|
||||
)
|
||||
) else (
|
||||
echo [ERROR] requirements.txt 文件不存在
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: 检查必要文件是否存在
|
||||
echo [INFO] 检查必要文件...
|
||||
set "files=run_web_service.py src\web_server.py src\hand_detection_3d.py templates\index.html"
|
||||
for %%f in (%files%) do (
|
||||
if not exist "%%f" (
|
||||
echo [ERROR] 缺少必要文件: %%f
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
echo [SUCCESS] 所有必要文件存在
|
||||
|
||||
:: 解析命令行参数
|
||||
set HOST=0.0.0.0
|
||||
set PORT=5000
|
||||
set DEBUG=
|
||||
set TEST_VIDEO=
|
||||
|
||||
:parse_args
|
||||
if "%~1"=="" goto :end_parse
|
||||
if "%~1"=="--host" (
|
||||
set HOST=%~2
|
||||
shift
|
||||
shift
|
||||
goto :parse_args
|
||||
)
|
||||
if "%~1"=="--port" (
|
||||
set PORT=%~2
|
||||
shift
|
||||
shift
|
||||
goto :parse_args
|
||||
)
|
||||
if "%~1"=="--debug" (
|
||||
set DEBUG=--debug
|
||||
shift
|
||||
goto :parse_args
|
||||
)
|
||||
if "%~1"=="--test-video" (
|
||||
set TEST_VIDEO=--test-video %~2
|
||||
shift
|
||||
shift
|
||||
goto :parse_args
|
||||
)
|
||||
if "%~1"=="-h" goto :show_help
|
||||
if "%~1"=="--help" goto :show_help
|
||||
if "%~1"=="/?" goto :show_help
|
||||
|
||||
echo [ERROR] 未知参数: %~1
|
||||
echo 使用 %~nx0 --help 查看帮助
|
||||
pause
|
||||
exit /b 1
|
||||
|
||||
:show_help
|
||||
echo 用法: %~nx0 [选项]
|
||||
echo.
|
||||
echo 选项:
|
||||
echo --host HOST 服务器地址 (默认: 0.0.0.0)
|
||||
echo --port PORT 端口号 (默认: 5000)
|
||||
echo --debug 启用调试模式
|
||||
echo --test-video VIDEO 使用本地测试视频
|
||||
echo -h, --help, /? 显示帮助信息
|
||||
echo.
|
||||
echo 示例:
|
||||
echo %~nx0 # 基本启动
|
||||
echo %~nx0 --host localhost --port 8080 # 自定义地址和端口
|
||||
echo %~nx0 --debug # 调试模式
|
||||
echo %~nx0 --test-video data\videos\test.mp4 # 本地视频测试
|
||||
pause
|
||||
exit /b 0
|
||||
|
||||
:end_parse
|
||||
|
||||
:: 显示启动信息
|
||||
echo.
|
||||
echo [INFO] 启动配置:
|
||||
echo - 服务器地址: %HOST%
|
||||
echo - 端口号: %PORT%
|
||||
if not "%DEBUG%"=="" (
|
||||
echo - 调试模式: 启用
|
||||
)
|
||||
if not "%TEST_VIDEO%"=="" (
|
||||
echo - 测试视频: %TEST_VIDEO:~13%
|
||||
)
|
||||
echo.
|
||||
|
||||
:: 显示访问地址
|
||||
if "%HOST%"=="0.0.0.0" (
|
||||
echo [INFO] 服务启动后可通过以下地址访问:
|
||||
echo - 本地访问: http://localhost:%PORT%
|
||||
for /f "tokens=2 delims=:" %%i in ('ipconfig ^| findstr "IPv4"') do (
|
||||
set IP=%%i
|
||||
set IP=!IP: =!
|
||||
echo - 局域网访问: http://!IP!:%PORT%
|
||||
goto :continue
|
||||
)
|
||||
:continue
|
||||
) else (
|
||||
echo [INFO] 服务启动后访问地址: http://%HOST%:%PORT%
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [INFO] 按 Ctrl+C 停止服务
|
||||
echo ================================================
|
||||
echo.
|
||||
|
||||
:: 启动服务
|
||||
echo [INFO] 正在启动手部检测Web服务...
|
||||
python run_web_service.py --host %HOST% --port %PORT% %DEBUG% %TEST_VIDEO%
|
||||
|
||||
pause
|
183
start_service.sh
183
start_service.sh
@ -1,183 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 手部检测Web服务启动脚本
|
||||
# 包含虚拟环境激活和服务启动
|
||||
|
||||
# 设置脚本目录
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 打印彩色消息
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 显示标题
|
||||
echo "================================================"
|
||||
echo " 🤖 手部检测Web服务启动器"
|
||||
echo "================================================"
|
||||
echo ""
|
||||
|
||||
# 检查Python3是否存在
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
print_error "Python3 未找到,请先安装Python3"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_info "Python3 版本: $(python3 --version)"
|
||||
|
||||
# 检查虚拟环境是否存在
|
||||
if [ ! -d "venv" ]; then
|
||||
print_warning "虚拟环境不存在,正在创建..."
|
||||
python3 -m venv venv
|
||||
if [ $? -eq 0 ]; then
|
||||
print_success "虚拟环境创建成功"
|
||||
else
|
||||
print_error "虚拟环境创建失败"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 激活虚拟环境
|
||||
print_info "正在激活虚拟环境..."
|
||||
source venv/bin/activate
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
print_success "虚拟环境已激活"
|
||||
else
|
||||
print_error "虚拟环境激活失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查依赖是否已安装
|
||||
print_info "检查Python依赖..."
|
||||
if [ -f "requirements.txt" ]; then
|
||||
# 检查是否需要安装依赖
|
||||
if ! python -c "import flask, flask_socketio, cv2, mediapipe" &> /dev/null; then
|
||||
print_warning "依赖未完全安装,正在安装..."
|
||||
# 先强制降级 numpy,避免 numpy 2.x 兼容性问题
|
||||
pip install --break-system-packages --force-reinstall 'numpy<2'
|
||||
pip install --break-system-packages -r requirements.txt
|
||||
if [ $? -eq 0 ]; then
|
||||
print_success "依赖安装成功"
|
||||
else
|
||||
print_error "依赖安装失败"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
print_success "依赖已安装"
|
||||
fi
|
||||
else
|
||||
print_error "requirements.txt 文件不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查必要文件是否存在
|
||||
print_info "检查必要文件..."
|
||||
required_files=("run_web_service.py" "src/web_server.py" "src/hand_detection_3d.py" "templates/index.html")
|
||||
for file in "${required_files[@]}"; do
|
||||
if [ ! -f "$file" ]; then
|
||||
print_error "缺少必要文件: $file"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
print_success "所有必要文件存在"
|
||||
|
||||
# 解析命令行参数
|
||||
HOST="0.0.0.0"
|
||||
PORT="5000"
|
||||
DEBUG=""
|
||||
TEST_VIDEO=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--host)
|
||||
HOST="$2"
|
||||
shift 2
|
||||
;;
|
||||
--port)
|
||||
PORT="$2"
|
||||
shift 2
|
||||
;;
|
||||
--debug)
|
||||
DEBUG="--debug"
|
||||
shift
|
||||
;;
|
||||
--test-video)
|
||||
TEST_VIDEO="--test-video $2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
echo "用法: $0 [选项]"
|
||||
echo ""
|
||||
echo "选项:"
|
||||
echo " --host HOST 服务器地址 (默认: 0.0.0.0)"
|
||||
echo " --port PORT 端口号 (默认: 5000)"
|
||||
echo " --debug 启用调试模式"
|
||||
echo " --test-video VIDEO 使用本地测试视频"
|
||||
echo " -h, --help 显示帮助信息"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 # 基本启动"
|
||||
echo " $0 --host localhost --port 8080 # 自定义地址和端口"
|
||||
echo " $0 --debug # 调试模式"
|
||||
echo " $0 --test-video data/videos/test.mp4 # 本地视频测试"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
print_error "未知参数: $1"
|
||||
echo "使用 $0 --help 查看帮助"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 显示启动信息
|
||||
echo ""
|
||||
print_info "启动配置:"
|
||||
echo " - 服务器地址: $HOST"
|
||||
echo " - 端口号: $PORT"
|
||||
if [ ! -z "$DEBUG" ]; then
|
||||
echo " - 调试模式: 启用"
|
||||
fi
|
||||
if [ ! -z "$TEST_VIDEO" ]; then
|
||||
echo " - 测试视频: ${TEST_VIDEO#--test-video }"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 显示访问地址
|
||||
if [ "$HOST" = "0.0.0.0" ]; then
|
||||
print_info "服务启动后可通过以下地址访问:"
|
||||
echo " - 本地访问: http://localhost:$PORT"
|
||||
echo " - 局域网访问: http://$(hostname -I | awk '{print $1}'):$PORT"
|
||||
else
|
||||
print_info "服务启动后访问地址: http://$HOST:$PORT"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_info "按 Ctrl+C 停止服务"
|
||||
echo "================================================"
|
||||
echo ""
|
||||
|
||||
# 启动服务
|
||||
print_info "正在启动手部检测Web服务..."
|
||||
python3 run_web_service.py --host "$HOST" --port "$PORT" $DEBUG $TEST_VIDEO
|
@ -1,548 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>实时手部检测控制系统</title>
|
||||
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.main-content {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.video-section {
|
||||
background: #000;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
min-height: 480px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.video-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 640px;
|
||||
max-height: 480px;
|
||||
}
|
||||
#videoPreview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.no-video {
|
||||
color: #999;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
.control-panel {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
.status-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.status-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding: 8px 12px;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
.status-label {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.status-value {
|
||||
color: #007bff;
|
||||
font-family: monospace;
|
||||
}
|
||||
.angle-display {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.angle-card {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #ddd;
|
||||
text-align: center;
|
||||
}
|
||||
.angle-card.x-axis {
|
||||
border-color: #ff4757;
|
||||
}
|
||||
.angle-card.y-axis {
|
||||
border-color: #2ed573;
|
||||
}
|
||||
.angle-card.z-axis {
|
||||
border-color: #3742fa;
|
||||
}
|
||||
.angle-label {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.angle-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
}
|
||||
.action-display {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ddd;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.action-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
.action-value {
|
||||
font-size: 18px;
|
||||
padding: 8px 12px;
|
||||
background: #e3f2fd;
|
||||
border-radius: 5px;
|
||||
color: #1976d2;
|
||||
font-family: monospace;
|
||||
}
|
||||
.grip-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
.grip-indicator {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #ccc;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
.grip-indicator.active {
|
||||
background: #ff6b6b;
|
||||
}
|
||||
.connection-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ddd;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.connection-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: #ccc;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
.connection-dot.connected {
|
||||
background: #2ed573;
|
||||
}
|
||||
.test-controls {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.video-selector {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.video-select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
background: white;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.video-select:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
}
|
||||
.test-button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.test-button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
.test-button:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.log-section {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
.log-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
.log-container {
|
||||
background: #000;
|
||||
color: #0f0;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.log-entry {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.log-timestamp {
|
||||
color: #888;
|
||||
}
|
||||
.fps-display {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🤖 实时手部检测控制系统</h1>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="video-section">
|
||||
<div class="video-container">
|
||||
<img id="videoPreview" style="display:none;" alt="视频预览">
|
||||
<div id="noVideo" class="no-video">
|
||||
等待视频流...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-panel">
|
||||
<div class="connection-status">
|
||||
<div id="connectionDot" class="connection-dot"></div>
|
||||
<span id="connectionStatus">连接中...</span>
|
||||
</div>
|
||||
|
||||
<div class="status-section">
|
||||
<div class="status-item">
|
||||
<span class="status-label">FPS:</span>
|
||||
<span id="fpsValue" class="status-value">0</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">速度:</span>
|
||||
<span id="speedValue" class="status-value">5</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="angle-display">
|
||||
<div class="angle-card x-axis">
|
||||
<div class="angle-label" style="color: #ff4757;">X轴 (左右)</div>
|
||||
<div id="xAngle" class="angle-value" style="color: #ff4757;">90°</div>
|
||||
</div>
|
||||
<div class="angle-card y-axis">
|
||||
<div class="angle-label" style="color: #2ed573;">Y轴 (上下)</div>
|
||||
<div id="yAngle" class="angle-value" style="color: #2ed573;">90°</div>
|
||||
</div>
|
||||
<div class="angle-card z-axis">
|
||||
<div class="angle-label" style="color: #3742fa;">Z轴 (前后)</div>
|
||||
<div id="zAngle" class="angle-value" style="color: #3742fa;">90°</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-display">
|
||||
<div class="action-title">当前动作:</div>
|
||||
<div id="actionValue" class="action-value">none</div>
|
||||
</div>
|
||||
|
||||
<div class="grip-status">
|
||||
<span>抓取状态:</span>
|
||||
<div class="grip-indicator" id="gripIndicator"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-controls">
|
||||
<div class="video-selector">
|
||||
<label for="videoSelect" style="font-size: 14px; font-weight: bold; margin-bottom: 8px; display: block;">选择测试视频:</label>
|
||||
<select id="videoSelect" class="video-select">
|
||||
<option value="">加载中...</option>
|
||||
</select>
|
||||
</div>
|
||||
<button id="testButton" class="test-button">开始视频测试</button>
|
||||
<button id="refreshButton" class="test-button" style="background: #28a745; margin-top: 5px;">刷新视频列表</button>
|
||||
<div style="font-size: 12px; color: #666; margin-top: 5px; text-align: center;">
|
||||
选择本地视频文件进行检测
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="log-section">
|
||||
<div class="log-title">系统日志</div>
|
||||
<div class="log-container" id="logContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
class HandDetectionClient {
|
||||
constructor() {
|
||||
this.socket = io();
|
||||
this.isConnected = false;
|
||||
this.setupSocketEvents();
|
||||
this.setupUI();
|
||||
}
|
||||
|
||||
setupSocketEvents() {
|
||||
this.socket.on('connect', () => {
|
||||
this.isConnected = true;
|
||||
this.updateConnectionStatus('已连接', true);
|
||||
this.log('WebSocket连接已建立');
|
||||
|
||||
// 注册为web预览客户端
|
||||
this.socket.emit('register_client', {
|
||||
type: 'web_preview'
|
||||
});
|
||||
});
|
||||
|
||||
this.socket.on('disconnect', () => {
|
||||
this.isConnected = false;
|
||||
this.updateConnectionStatus('连接断开', false);
|
||||
this.log('WebSocket连接已断开');
|
||||
});
|
||||
|
||||
this.socket.on('status', (data) => {
|
||||
this.log(`服务器状态: ${data.message}`);
|
||||
});
|
||||
|
||||
this.socket.on('registration_success', (data) => {
|
||||
this.log(`客户端注册成功: ${data.type}`);
|
||||
});
|
||||
|
||||
this.socket.on('detection_results', (data) => {
|
||||
this.updateDetectionResults(data);
|
||||
});
|
||||
|
||||
this.socket.on('error', (data) => {
|
||||
this.log(`错误: ${data.message}`, 'error');
|
||||
});
|
||||
|
||||
this.socket.on('pong', (data) => {
|
||||
// 处理ping响应
|
||||
});
|
||||
|
||||
this.socket.on('test_started', (data) => {
|
||||
this.log(`✅ ${data.message}`, 'info');
|
||||
});
|
||||
|
||||
this.socket.on('test_error', (data) => {
|
||||
this.log(`❌ ${data.message}`, 'error');
|
||||
if (data.help) {
|
||||
this.log(`💡 ${data.help}`, 'info');
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('video_list', (data) => {
|
||||
this.updateVideoList(data.videos);
|
||||
});
|
||||
}
|
||||
|
||||
setupUI() {
|
||||
document.getElementById('testButton').addEventListener('click', () => {
|
||||
this.startLocalTest();
|
||||
});
|
||||
|
||||
document.getElementById('refreshButton').addEventListener('click', () => {
|
||||
this.refreshVideoList();
|
||||
});
|
||||
|
||||
// 请求视频列表
|
||||
this.refreshVideoList();
|
||||
|
||||
// 定期发送ping
|
||||
setInterval(() => {
|
||||
if (this.isConnected) {
|
||||
this.socket.emit('ping');
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
updateConnectionStatus(status, connected) {
|
||||
const statusElement = document.getElementById('connectionStatus');
|
||||
const dotElement = document.getElementById('connectionDot');
|
||||
|
||||
statusElement.textContent = status;
|
||||
if (connected) {
|
||||
dotElement.classList.add('connected');
|
||||
} else {
|
||||
dotElement.classList.remove('connected');
|
||||
}
|
||||
}
|
||||
|
||||
updateDetectionResults(data) {
|
||||
const { control_signal, processed_frame, fps } = data;
|
||||
|
||||
// 更新角度显示
|
||||
document.getElementById('xAngle').textContent = `${control_signal.x_angle.toFixed(1)}°`;
|
||||
document.getElementById('yAngle').textContent = `${control_signal.y_angle.toFixed(1)}°`;
|
||||
document.getElementById('zAngle').textContent = `${control_signal.z_angle.toFixed(1)}°`;
|
||||
|
||||
// 更新动作显示
|
||||
document.getElementById('actionValue').textContent = control_signal.action;
|
||||
|
||||
// 更新速度显示
|
||||
document.getElementById('speedValue').textContent = control_signal.speed;
|
||||
|
||||
// 更新抓取状态
|
||||
const gripIndicator = document.getElementById('gripIndicator');
|
||||
if (control_signal.grip === 1) {
|
||||
gripIndicator.classList.add('active');
|
||||
} else {
|
||||
gripIndicator.classList.remove('active');
|
||||
}
|
||||
|
||||
// 更新FPS显示
|
||||
document.getElementById('fpsValue').textContent = fps || 0;
|
||||
|
||||
// 更新视频预览
|
||||
if (processed_frame) {
|
||||
const videoPreview = document.getElementById('videoPreview');
|
||||
const noVideo = document.getElementById('noVideo');
|
||||
|
||||
videoPreview.src = processed_frame;
|
||||
videoPreview.style.display = 'block';
|
||||
noVideo.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
startLocalTest() {
|
||||
const button = document.getElementById('testButton');
|
||||
const videoSelect = document.getElementById('videoSelect');
|
||||
const selectedVideo = videoSelect.value;
|
||||
|
||||
if (!selectedVideo) {
|
||||
this.log('❌ 请先选择一个视频文件', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
button.textContent = '启动中...';
|
||||
|
||||
// 请求开始本地测试,带上选择的视频
|
||||
this.socket.emit('start_local_test', {
|
||||
video_path: selectedVideo
|
||||
});
|
||||
|
||||
this.log(`正在启动视频测试: ${selectedVideo}`);
|
||||
|
||||
setTimeout(() => {
|
||||
button.disabled = false;
|
||||
button.textContent = '开始视频测试';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
refreshVideoList() {
|
||||
this.log('正在刷新视频列表...');
|
||||
this.socket.emit('get_video_list');
|
||||
}
|
||||
|
||||
updateVideoList(videos) {
|
||||
const videoSelect = document.getElementById('videoSelect');
|
||||
videoSelect.innerHTML = '';
|
||||
|
||||
if (videos.length === 0) {
|
||||
const option = document.createElement('option');
|
||||
option.value = '';
|
||||
option.textContent = '未找到视频文件';
|
||||
videoSelect.appendChild(option);
|
||||
|
||||
this.log('❌ 未找到测试视频文件', 'error');
|
||||
this.log('💡 运行 python create_test_video.py 生成测试视频', 'info');
|
||||
} else {
|
||||
// 添加默认选项
|
||||
const defaultOption = document.createElement('option');
|
||||
defaultOption.value = '';
|
||||
defaultOption.textContent = '请选择视频文件';
|
||||
videoSelect.appendChild(defaultOption);
|
||||
|
||||
// 添加视频文件选项
|
||||
videos.forEach(video => {
|
||||
const option = document.createElement('option');
|
||||
option.value = video.path;
|
||||
option.textContent = `${video.name} (${video.size})`;
|
||||
videoSelect.appendChild(option);
|
||||
});
|
||||
|
||||
this.log(`✅ 找到 ${videos.length} 个视频文件`);
|
||||
}
|
||||
}
|
||||
|
||||
log(message, type = 'info') {
|
||||
const logContainer = document.getElementById('logContainer');
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const logEntry = document.createElement('div');
|
||||
logEntry.className = 'log-entry';
|
||||
|
||||
const color = type === 'error' ? '#f00' : '#0f0';
|
||||
logEntry.innerHTML = `<span class="log-timestamp">[${timestamp}]</span> <span style="color: ${color}">${message}</span>`;
|
||||
|
||||
logContainer.appendChild(logEntry);
|
||||
logContainer.scrollTop = logContainer.scrollHeight;
|
||||
|
||||
// 限制日志数量
|
||||
const entries = logContainer.querySelectorAll('.log-entry');
|
||||
if (entries.length > 100) {
|
||||
entries[0].remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化客户端
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new HandDetectionClient();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
206
test_system.py
206
test_system.py
@ -1,206 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
系统测试脚本
|
||||
测试手部检测Web服务的各个组件
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
|
||||
# 添加src目录到Python路径
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
||||
|
||||
def test_import_modules():
|
||||
"""测试模块导入"""
|
||||
print("🔍 测试模块导入...")
|
||||
try:
|
||||
import cv2
|
||||
print("✅ OpenCV 导入成功")
|
||||
|
||||
import mediapipe as mp
|
||||
print("✅ MediaPipe 导入成功")
|
||||
|
||||
import flask
|
||||
import flask_socketio
|
||||
print("✅ Flask 和 SocketIO 导入成功")
|
||||
|
||||
from hand_detection_3d import load_mediapipe_model, process_frame_3d
|
||||
print("✅ 手部检测模块导入成功")
|
||||
|
||||
from web_server import HandDetectionWebServer
|
||||
print("✅ Web服务器模块导入成功")
|
||||
|
||||
from robot_client import RobotArmClient
|
||||
print("✅ 机械臂客户端模块导入成功")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 模块导入失败: {e}")
|
||||
return False
|
||||
|
||||
def test_hand_detection():
|
||||
"""测试手部检测功能"""
|
||||
print("\n🤖 测试手部检测功能...")
|
||||
try:
|
||||
from hand_detection_3d import load_mediapipe_model, process_frame_3d
|
||||
import numpy as np
|
||||
|
||||
# 加载模型
|
||||
model = load_mediapipe_model()
|
||||
print("✅ MediaPipe模型加载成功")
|
||||
|
||||
# 创建测试图像
|
||||
test_frame = np.zeros((480, 640, 3), dtype=np.uint8)
|
||||
test_frame[:] = (50, 50, 50) # 灰色背景
|
||||
|
||||
# 处理帧
|
||||
control_signal, hand_data = process_frame_3d(test_frame, model)
|
||||
|
||||
# 检查输出格式
|
||||
required_keys = ['x_angle', 'y_angle', 'z_angle', 'grip', 'action', 'speed']
|
||||
if all(key in control_signal for key in required_keys):
|
||||
print("✅ 手部检测输出格式正确")
|
||||
print(f" 控制信号: {control_signal}")
|
||||
return True
|
||||
else:
|
||||
print("❌ 手部检测输出格式不正确")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 手部检测测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_web_server():
|
||||
"""测试Web服务器"""
|
||||
print("\n🌐 测试Web服务器...")
|
||||
try:
|
||||
from web_server import HandDetectionWebServer
|
||||
|
||||
# 创建服务器实例
|
||||
server = HandDetectionWebServer(port=5003)
|
||||
print("✅ Web服务器创建成功")
|
||||
|
||||
# 测试路由配置
|
||||
with server.app.test_client() as client:
|
||||
# 测试主页
|
||||
response = client.get('/')
|
||||
if response.status_code == 200:
|
||||
print("✅ 主页路由正常")
|
||||
else:
|
||||
print(f"❌ 主页路由失败: {response.status_code}")
|
||||
return False
|
||||
|
||||
# 测试API状态
|
||||
response = client.get('/api/status')
|
||||
if response.status_code == 200:
|
||||
print("✅ API状态路由正常")
|
||||
status_data = json.loads(response.data)
|
||||
print(f" 状态信息: {status_data}")
|
||||
else:
|
||||
print(f"❌ API状态路由失败: {response.status_code}")
|
||||
return False
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Web服务器测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_robot_client():
|
||||
"""测试机械臂客户端"""
|
||||
print("\n🦾 测试机械臂客户端...")
|
||||
try:
|
||||
from robot_client import RobotArmClient, MockRobotArmController
|
||||
|
||||
# 创建模拟控制器
|
||||
mock_controller = MockRobotArmController()
|
||||
print("✅ 模拟机械臂控制器创建成功")
|
||||
|
||||
# 测试控制器方法
|
||||
mock_controller.move_to_angles(45, 90, 135, 5)
|
||||
print("✅ 机械臂移动测试成功")
|
||||
|
||||
mock_controller.set_gripper_state(1)
|
||||
print("✅ 抓取器控制测试成功")
|
||||
|
||||
# 创建客户端(不连接服务器)
|
||||
client = RobotArmClient('http://localhost:5003')
|
||||
print("✅ 机械臂客户端创建成功")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 机械臂客户端测试失败: {e}")
|
||||
return False
|
||||
|
||||
def test_file_structure():
|
||||
"""测试文件结构"""
|
||||
print("\n📁 测试文件结构...")
|
||||
required_files = [
|
||||
'README.md',
|
||||
'requirements.txt',
|
||||
'run_web_service.py',
|
||||
'start_service.sh',
|
||||
'start_service.bat',
|
||||
'start_robot_client.sh',
|
||||
'start_robot_client.bat',
|
||||
'src/__init__.py',
|
||||
'src/hand_detection_3d.py',
|
||||
'src/web_server.py',
|
||||
'src/robot_client.py',
|
||||
'templates/index.html'
|
||||
]
|
||||
|
||||
missing_files = []
|
||||
for file_path in required_files:
|
||||
if not os.path.exists(file_path):
|
||||
missing_files.append(file_path)
|
||||
|
||||
if missing_files:
|
||||
print(f"❌ 缺少文件: {missing_files}")
|
||||
return False
|
||||
else:
|
||||
print("✅ 所有必需文件存在")
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("=" * 60)
|
||||
print("🧪 手部检测Web服务系统测试")
|
||||
print("=" * 60)
|
||||
|
||||
tests = [
|
||||
test_file_structure,
|
||||
test_import_modules,
|
||||
test_hand_detection,
|
||||
test_web_server,
|
||||
test_robot_client
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test in tests:
|
||||
if test():
|
||||
passed += 1
|
||||
else:
|
||||
print(f"\n⚠️ 测试失败,请检查上述错误")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(f"📊 测试结果: {passed}/{total} 个测试通过")
|
||||
|
||||
if passed == total:
|
||||
print("🎉 所有测试通过!系统准备就绪")
|
||||
print("\n🚀 启动命令:")
|
||||
print(" Linux/Mac: ./start_service.sh")
|
||||
print(" Windows: start_service.bat")
|
||||
print(" 访问地址: http://localhost:5000")
|
||||
else:
|
||||
print("❌ 部分测试失败,请修复问题后重试")
|
||||
|
||||
print("=" * 60)
|
||||
return passed == total
|
||||
|
||||
if __name__ == '__main__':
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
Loading…
x
Reference in New Issue
Block a user