增加了头部姿态和足部压力数据
This commit is contained in:
parent
2a3d168e50
commit
ba8f13b3ea
@ -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'])
|
||||
|
@ -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:
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user