增加了头部姿态和足部压力数据

This commit is contained in:
root 2025-08-06 14:01:04 +08:00
parent 2a3d168e50
commit ba8f13b3ea
4 changed files with 279 additions and 60 deletions

View File

@ -561,6 +561,37 @@ def calibrate_devices():
logger.error(f'设备校准失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/devices/calibrate/imu', methods=['POST'])
def calibrate_imu():
"""校准IMU头部姿态传感器"""
try:
if not device_manager:
return jsonify({'success': False, 'error': '设备管理器未初始化'}), 500
if not device_manager.device_status.get('imu', False):
return jsonify({'success': False, 'error': 'IMU设备未连接'}), 400
# 执行IMU校准
result = device_manager._calibrate_imu()
if result.get('status') == 'success':
logger.info('IMU头部姿态校准成功')
return jsonify({
'success': True,
'message': 'IMU头部姿态校准成功正立状态已设为零位基准',
'data': result
})
else:
logger.error(f'IMU校准失败: {result.get("error", "未知错误")}')
return jsonify({
'success': False,
'error': result.get('error', 'IMU校准失败')
}), 500
except Exception as e:
logger.error(f'IMU校准异常: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
# ==================== 视频推流API ====================
@app.route('/api/streaming/start', methods=['POST'])

View File

@ -429,13 +429,25 @@ class DeviceManager:
'z': np.mean([s['gyro']['z'] for s in samples])
}
# 计算头部姿态零点偏移(正立状态为标准零位)
head_pose_offset = {
'rotation': np.mean([s['head_pose']['rotation'] for s in samples if 'head_pose' in s]),
'tilt': np.mean([s['head_pose']['tilt'] for s in samples if 'head_pose' in s]),
'pitch': np.mean([s['head_pose']['pitch'] for s in samples if 'head_pose' in s])
}
calibration = {
'status': 'success',
'accel_offset': accel_offset,
'gyro_offset': gyro_offset,
'head_pose_offset': head_pose_offset, # 头部姿态零点偏移
'timestamp': datetime.now().isoformat()
}
# 保存校准数据到设备实例
if hasattr(self.imu_device, 'set_calibration'):
self.imu_device.set_calibration(calibration)
return calibration
except Exception as e:
@ -1123,27 +1135,16 @@ class DeviceManager:
# 从IMU设备读取数据
imu_data = self.imu_device.read_data()
if imu_data:
# 计算头部姿态角度(欧拉角)
# 这里使用简化的计算方法,实际应用中可能需要更复杂的姿态解算
accel = imu_data['accel']
gyro = imu_data['gyro']
if imu_data and 'head_pose' in imu_data:
# 直接使用设备提供的头部姿态数据
head_pose = imu_data['head_pose']
# 计算俯仰角和横滚角(基于加速度计)
import math
pitch = math.atan2(accel['x'], math.sqrt(accel['y']**2 + accel['z']**2)) * 180 / math.pi
roll = math.atan2(accel['y'], math.sqrt(accel['x']**2 + accel['z']**2)) * 180 / math.pi
# 偏航角需要磁力计数据,这里使用陀螺仪积分(简化处理)
yaw = gyro['z'] * 0.1 # 简化的偏航角计算
# 构建头部姿态数据
# 构建完整的头部姿态数据
head_pose_data = {
'roll': roll,
'pitch': pitch,
'yaw': yaw,
'acceleration': accel,
'gyroscope': gyro,
'rotation': head_pose['rotation'], # 旋转角:左旋(-), 右旋(+)
'tilt': head_pose['tilt'], # 倾斜角:左倾(-), 右倾(+)
'pitch': head_pose['pitch'], # 俯仰角:俯角(-), 仰角(+)
'temperature': imu_data.get('temperature', 25),
'timestamp': imu_data['timestamp']
}
@ -1176,32 +1177,58 @@ class DeviceManager:
# 从压力传感器设备读取数据
pressure_data = self.pressure_device.read_data()
if pressure_data:
# 计算平衡相关指标
left_pressure = pressure_data['left_foot']
right_pressure = pressure_data['right_foot']
total_pressure = left_pressure + right_pressure
if pressure_data and 'foot_pressure' in pressure_data:
foot_pressure = pressure_data['foot_pressure']
# 获取各区域压力值
left_front = foot_pressure['left_front']
left_rear = foot_pressure['left_rear']
right_front = foot_pressure['right_front']
right_rear = foot_pressure['right_rear']
left_total = foot_pressure['left_total']
right_total = foot_pressure['right_total']
# 计算总压力
total_pressure = left_total + right_total
# 计算平衡比例(左脚压力占总压力的比例)
balance_ratio = left_pressure / total_pressure if total_pressure > 0 else 0.5
balance_ratio = left_total / total_pressure if total_pressure > 0 else 0.5
# 计算压力中心偏移
pressure_center_offset = (balance_ratio - 0.5) * 100 # 转换为百分比
# 构建足部压力数据
foot_pressure_data = {
'left_foot_pressure': left_pressure,
'right_foot_pressure': right_pressure,
'total_pressure': total_pressure,
'balance_ratio': balance_ratio,
'pressure_center_offset': pressure_center_offset,
'balance_status': 'balanced' if abs(pressure_center_offset) < 10 else 'unbalanced',
# 计算前后足压力分布
left_front_ratio = left_front / left_total if left_total > 0 else 0.5
right_front_ratio = right_front / right_total if right_total > 0 else 0.5
# 构建完整的足部压力数据
complete_pressure_data = {
# 分区压力值
'pressure_zones': {
'left_front': left_front,
'left_rear': left_rear,
'right_front': right_front,
'right_rear': right_rear,
'left_total': left_total,
'right_total': right_total,
'total_pressure': total_pressure
},
# 平衡分析
'balance_analysis': {
'balance_ratio': round(balance_ratio, 3),
'pressure_center_offset': round(pressure_center_offset, 2),
'balance_status': 'balanced' if abs(pressure_center_offset) < 10 else 'unbalanced',
'left_front_ratio': round(left_front_ratio, 3),
'right_front_ratio': round(right_front_ratio, 3)
},
# 压力图片
'pressure_image': pressure_data.get('pressure_image', ''),
'timestamp': pressure_data['timestamp']
}
# 通过WebSocket发送足部压力数据
self.socketio.emit('pressure_data', {
'foot_pressure': foot_pressure_data,
'foot_pressure': complete_pressure_data,
'timestamp': datetime.now().isoformat()
})
@ -1624,23 +1651,59 @@ class MockIMUDevice:
def __init__(self):
self.noise_level = 0.1
self.calibration_data = None # 校准数据
self.head_pose_offset = {'rotation': 0, 'tilt': 0, 'pitch': 0} # 头部姿态零点偏移
def set_calibration(self, calibration: Dict[str, Any]):
"""设置校准数据"""
self.calibration_data = calibration
if 'head_pose_offset' in calibration:
self.head_pose_offset = calibration['head_pose_offset']
def apply_calibration(self, raw_data: Dict[str, Any]) -> Dict[str, Any]:
"""应用校准数据"""
if not self.calibration_data:
return raw_data
# 应用头部姿态零点校准
if 'head_pose' in raw_data:
raw_data['head_pose']['rotation'] -= self.head_pose_offset['rotation']
raw_data['head_pose']['tilt'] -= self.head_pose_offset['tilt']
raw_data['head_pose']['pitch'] -= self.head_pose_offset['pitch']
return raw_data
def read_data(self) -> Dict[str, Any]:
"""读取IMU数据"""
return {
'accel': {
'x': np.random.normal(0, self.noise_level),
'y': np.random.normal(0, self.noise_level),
'z': np.random.normal(9.8, self.noise_level) # 重力加速度
# 生成头部姿态角度数据,角度范围(-90°, +90°)
# 使用正弦波模拟自然的头部运动,添加随机噪声
import time
current_time = time.time()
# 旋转角(左旋为负,右旋为正)
rotation_angle = 30 * np.sin(current_time * 0.5) + np.random.normal(0, self.noise_level * 5)
rotation_angle = np.clip(rotation_angle, -90, 90)
# 倾斜角(左倾为负,右倾为正)
tilt_angle = 20 * np.sin(current_time * 0.3 + np.pi/4) + np.random.normal(0, self.noise_level * 5)
tilt_angle = np.clip(tilt_angle, -90, 90)
# 俯仰角(俯角为负,仰角为正)
pitch_angle = 15 * np.sin(current_time * 0.7 + np.pi/2) + np.random.normal(0, self.noise_level * 5)
pitch_angle = np.clip(pitch_angle, -90, 90)
# 生成原始数据
raw_data = {
'head_pose': {
'rotation': rotation_angle, # 旋转角:左旋(-), 右旋(+)
'tilt': tilt_angle, # 倾斜角:左倾(-), 右倾(+)
'pitch': pitch_angle # 俯仰角:俯角(-), 仰角(+)
},
'gyro': {
'x': np.random.normal(0, self.noise_level),
'y': np.random.normal(0, self.noise_level),
'z': np.random.normal(0, self.noise_level)
},
'temperature': np.random.normal(25, 2),
'timestamp': datetime.now().isoformat()
}
# 应用校准并返回
return self.apply_calibration(raw_data)
class MockPressureDevice:
@ -1652,15 +1715,93 @@ class MockPressureDevice:
def read_data(self) -> Dict[str, Any]:
"""读取压力数据"""
# 模拟轻微的左右脚压力差异
left_pressure = self.base_pressure + np.random.normal(0, self.noise_level)
right_pressure = self.base_pressure + np.random.normal(0, self.noise_level)
# 模拟各个足部区域的压力值
left_front = max(0, self.base_pressure * 0.6 + np.random.normal(0, self.noise_level))
left_rear = max(0, self.base_pressure * 0.4 + np.random.normal(0, self.noise_level))
right_front = max(0, self.base_pressure * 0.6 + np.random.normal(0, self.noise_level))
right_rear = max(0, self.base_pressure * 0.4 + np.random.normal(0, self.noise_level))
# 计算总压力
left_total = left_front + left_rear
right_total = right_front + right_rear
# 生成模拟的足部压力图片base64格式
pressure_image_base64 = self._generate_pressure_image(left_front, left_rear, right_front, right_rear)
return {
'left_foot': max(0, left_pressure),
'right_foot': max(0, right_pressure),
'foot_pressure': {
'left_front': round(left_front, 2), # 左前足压力
'left_rear': round(left_rear, 2), # 左后足压力
'right_front': round(right_front, 2), # 右前足压力
'right_rear': round(right_rear, 2), # 右后足压力
'left_total': round(left_total, 2), # 左足总压力
'right_total': round(right_total, 2) # 右足总压力
},
'pressure_image': pressure_image_base64, # 足部压力图片base64格式
'timestamp': datetime.now().isoformat()
}
def _generate_pressure_image(self, left_front, left_rear, right_front, right_rear) -> str:
"""生成足部压力图片的base64数据"""
try:
import base64
from io import BytesIO
import matplotlib.pyplot as plt
import matplotlib.patches as patches
# 创建图形
fig, ax = plt.subplots(1, 1, figsize=(6, 8))
ax.set_xlim(0, 10)
ax.set_ylim(0, 12)
ax.set_aspect('equal')
ax.axis('off')
# 定义颜色映射(根据压力值)
max_pressure = max(left_front, left_rear, right_front, right_rear)
if max_pressure > 0:
left_front_color = plt.cm.Reds(left_front / max_pressure)
left_rear_color = plt.cm.Reds(left_rear / max_pressure)
right_front_color = plt.cm.Reds(right_front / max_pressure)
right_rear_color = plt.cm.Reds(right_rear / max_pressure)
else:
left_front_color = left_rear_color = right_front_color = right_rear_color = 'lightgray'
# 绘制左脚
left_front_rect = patches.Rectangle((1, 6), 2, 4, linewidth=1, edgecolor='black', facecolor=left_front_color)
left_rear_rect = patches.Rectangle((1, 2), 2, 4, linewidth=1, edgecolor='black', facecolor=left_rear_color)
# 绘制右脚
right_front_rect = patches.Rectangle((7, 6), 2, 4, linewidth=1, edgecolor='black', facecolor=right_front_color)
right_rear_rect = patches.Rectangle((7, 2), 2, 4, linewidth=1, edgecolor='black', facecolor=right_rear_color)
# 添加到图形
ax.add_patch(left_front_rect)
ax.add_patch(left_rear_rect)
ax.add_patch(right_front_rect)
ax.add_patch(right_rear_rect)
# 添加标签
ax.text(2, 8, f'{left_front:.1f}', ha='center', va='center', fontsize=10, weight='bold')
ax.text(2, 4, f'{left_rear:.1f}', ha='center', va='center', fontsize=10, weight='bold')
ax.text(8, 8, f'{right_front:.1f}', ha='center', va='center', fontsize=10, weight='bold')
ax.text(8, 4, f'{right_rear:.1f}', ha='center', va='center', fontsize=10, weight='bold')
ax.text(2, 0.5, '左足', ha='center', va='center', fontsize=12, weight='bold')
ax.text(8, 0.5, '右足', ha='center', va='center', fontsize=12, weight='bold')
# 保存为base64
buffer = BytesIO()
plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100, facecolor='white')
buffer.seek(0)
image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
plt.close(fig)
return f"data:image/png;base64,{image_base64}"
except Exception as e:
logger.warning(f"生成压力图片失败: {e}")
# 返回一个简单的占位符base64图片
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
class VideoStreamManager:

View File

@ -112,6 +112,11 @@ export const deviceAPI = {
return api.post('/api/devices/calibrate')
},
// 校准IMU头部姿态传感器
calibrateIMU() {
return api.post('/api/devices/calibrate/imu')
},
// 测试设备
testDevice() {
return api.post('/api/devices/test')

View File

@ -30,7 +30,7 @@
--el-button-border-color: #409EFF;
--el-button-border-color: transparent ">
保存数据
</el-button>
</el-button>
</div>
<div class="top-bar-right">
@ -645,15 +645,28 @@ function displayDepthCameraFrame(base64Image) {
// IMU姿
function handleIMUData(data) {
try {
if (data && data.euler_angles) {
if (data && data.head_pose) {
const headPose = data.head_pose
// 姿
console.log('🎯 更新IMU头部姿态数据:', data.euler_angles)
console.log('🎯 更新IMU头部姿态数据:', {
rotation: headPose.rotation, // (-), (+)
tilt: headPose.tilt, // (-), (+)
pitch: headPose.pitch // (-), (+)
})
//
console.log(`📐 头部姿态角度 - 旋转: ${headPose.rotation.toFixed(1)}°, 倾斜: ${headPose.tilt.toFixed(1)}°, 俯仰: ${headPose.pitch.toFixed(1)}°`)
//
//
//
// updateHeadPoseChart(data.euler_angles)
// updateHeadPoseChart({
// rotation: headPose.rotation,
// tilt: headPose.tilt,
// pitch: headPose.pitch
// })
}
} catch (error) {
console.error('❌ 处理IMU数据失败:', error)
@ -663,15 +676,44 @@ function handleIMUData(data) {
//
function handlePressureData(data) {
try {
if (data && data.pressure_data) {
if (data && data.foot_pressure) {
const pressureData = data.foot_pressure
//
console.log('👣 更新压力传感器数据:', data.pressure_data)
console.log('👣 接收到压力传感器数据:')
//
//
//
if (pressureData.pressure_zones) {
const zones = pressureData.pressure_zones
console.log(' 分区压力值:')
console.log(` 左前足: ${zones.left_front} N`)
console.log(` 左后足: ${zones.left_rear} N`)
console.log(` 右前足: ${zones.right_front} N`)
console.log(` 右后足: ${zones.right_rear} N`)
console.log(` 左足总压力: ${zones.left_total} N`)
console.log(` 右足总压力: ${zones.right_total} N`)
console.log(` 总压力: ${zones.total_pressure} N`)
}
//
// updatePressureChart(data.pressure_data)
//
if (pressureData.balance_analysis) {
const balance = pressureData.balance_analysis
console.log(' 平衡分析:')
console.log(` 平衡比例: ${(balance.balance_ratio * 100).toFixed(1)}%`)
console.log(` 压力中心偏移: ${balance.pressure_center_offset}%`)
console.log(` 平衡状态: ${balance.balance_status}`)
console.log(` 左足前后比: ${(balance.left_front_ratio * 100).toFixed(1)}%`)
console.log(` 右足前后比: ${(balance.right_front_ratio * 100).toFixed(1)}%`)
}
//
if (pressureData.pressure_image) {
console.log(' 📊 接收到压力分布图片 (base64格式)')
//
updatePressureImage(pressureData.pressure_image)
}
}
} catch (error) {
console.error('❌ 处理压力传感器数据失败:', error)