This commit is contained in:
51hhh 2025-08-11 13:24:28 +08:00
parent 41cf62006a
commit d7a816d993
16 changed files with 12 additions and 1723 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
/venv
/venv

View File

@ -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通信
- 机械臂控制信号输出
现在你可以将真实的视频流连接到系统,或者集成到你的机械臂控制系统中!

View File

@ -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

View File

@ -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. 选择视频并开始测试!
享受更便捷的视频测试体验! 🚀

View File

@ -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.

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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)