合并修改
This commit is contained in:
commit
75950d5acf
@ -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,
|
||||
# 计算前后足压力分布
|
||||
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,24 +1651,60 @@ 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,16 +1715,94 @@ 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:
|
||||
"""视频推流管理器"""
|
||||
|
@ -82,10 +82,26 @@
|
||||
</div>
|
||||
<div style="margin-left: 10px;font-size: 14px;">{{ imuStatus }}</div>
|
||||
</div>
|
||||
<el-button type="primary" class="start-btn" style="background-image: linear-gradient(to right, rgb(236, 50, 166), rgb(160, 5, 216));
|
||||
--el-button-border-color: transparent !important;border-radius: 20px;border:none;width: 150px;">
|
||||
清零
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<el-button
|
||||
type="primary"
|
||||
class="start-btn"
|
||||
@click="clearAndStartTracking"
|
||||
:disabled="isRecording"
|
||||
style="background-image: linear-gradient(to right, rgb(236, 50, 166), rgb(160, 5, 216));
|
||||
--el-button-border-color: transparent !important;border-radius: 20px;border:none;width: 80px;">
|
||||
{{ isTrackingMaxValues ? '跟踪中' : '清零' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
class="start-btn"
|
||||
@click="saveMaxValuesToHistory"
|
||||
:disabled="!isTrackingMaxValues || isRecording"
|
||||
style="background-image: linear-gradient(to right, rgb(64, 158, 255), rgb(64, 158, 255));
|
||||
--el-button-border-color: transparent !important;border-radius: 20px;border:none;width: 80px;">
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -95,25 +111,28 @@
|
||||
<div id="headChart1" style="width: 100%;height: 140px;"></div>
|
||||
<!-- <img src="@/assets/test1.png" alt="" style="width: 100%;height: 140px;"> -->
|
||||
<div class="gauge-group-box">
|
||||
<div class="gauge-group-box-text1">左:<span class="gauge-group-box-text2">-55.2°</span></div>
|
||||
<div class="gauge-group-box-text1">旋转</div>
|
||||
<div class="gauge-group-box-text1">左:<span class="gauge-group-box-text2">{{ headPoseMaxValues.rotationLeftMax.toFixed(1) }}°</span></div>
|
||||
<div class="gauge-group-box-text1" style="margin-left: 20px;">右:<span
|
||||
class="gauge-group-box-text2">-55.2°</span></div>
|
||||
class="gauge-group-box-text2">{{ headPoseMaxValues.rotationRightMax.toFixed(1) }}°</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 33%;">
|
||||
<img src="@/assets/test1.png" alt="" style="width: 100%;height: 140px;">
|
||||
<div class="gauge-group-box">
|
||||
<div class="gauge-group-box-text1">左:<span class="gauge-group-box-text2">-7.7°</span></div>
|
||||
<div class="gauge-group-box-text1">倾斜</div>
|
||||
<div class="gauge-group-box-text1">左:<span class="gauge-group-box-text2">{{ headPoseMaxValues.tiltLeftMax.toFixed(1) }}°</span></div>
|
||||
<div class="gauge-group-box-text1" style="margin-left: 20px;">右:<span
|
||||
class="gauge-group-box-text2">8.7°</span></div>
|
||||
class="gauge-group-box-text2">{{ headPoseMaxValues.tiltRightMax.toFixed(1) }}°</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 33%;">
|
||||
<img src="@/assets/test1.png" alt="" style="width: 100%;height: 140px;">
|
||||
<div class="gauge-group-box">
|
||||
<div class="gauge-group-box-text1">左:<span class="gauge-group-box-text2">-10.5°</span></div>
|
||||
<div class="gauge-group-box-text1" style="margin-left: 20px;">右:<span
|
||||
class="gauge-group-box-text2">11.5°</span></div>
|
||||
<div class="gauge-group-box-text1">俯仰</div>
|
||||
<div class="gauge-group-box-text1">下:<span class="gauge-group-box-text2">{{ headPoseMaxValues.pitchDownMax.toFixed(1) }}°</span></div>
|
||||
<div class="gauge-group-box-text1" style="margin-left: 20px;">上:<span
|
||||
class="gauge-group-box-text2">{{ headPoseMaxValues.pitchUpMax.toFixed(1) }}°</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -672,36 +691,255 @@ function displayDepthCameraFrame(base64Image) {
|
||||
}
|
||||
|
||||
|
||||
// 头部姿态最值跟踪数据
|
||||
const headPoseMaxValues = ref({
|
||||
rotationLeftMax: 0, // 旋转-左旋最大值
|
||||
rotationRightMax: 0, // 旋转-右旋最大值
|
||||
tiltLeftMax: 0, // 倾斜-左倾最大值
|
||||
tiltRightMax: 0, // 倾斜-右倾最大值
|
||||
pitchUpMax: 0, // 俯仰-上仰最大值
|
||||
pitchDownMax: 0 // 俯仰-下俯最大值
|
||||
})
|
||||
|
||||
// 头部姿态历史最值记录数组
|
||||
const headPoseHistory = ref([])
|
||||
|
||||
// 最值跟踪状态
|
||||
const isTrackingMaxValues = ref(false)
|
||||
|
||||
// 处理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)}°`)
|
||||
|
||||
// 如果正在跟踪最值,则更新最值数据
|
||||
if (isTrackingMaxValues.value) {
|
||||
updateHeadPoseMaxValues(headPose)
|
||||
}
|
||||
|
||||
// 这里可以添加数据可视化逻辑
|
||||
// 例如更新图表或显示数值
|
||||
|
||||
// 如果有图表组件,可以在这里更新数据
|
||||
// updateHeadPoseChart(data.euler_angles)
|
||||
// updateHeadPoseChart({
|
||||
// rotation: headPose.rotation,
|
||||
// tilt: headPose.tilt,
|
||||
// pitch: headPose.pitch
|
||||
// })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 处理IMU数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新头部姿态最值
|
||||
function updateHeadPoseMaxValues(headPose) {
|
||||
try {
|
||||
// 更新旋转角最值
|
||||
if (headPose.rotation < 0) {
|
||||
// 左旋(负值),取绝对值的最大值
|
||||
headPoseMaxValues.value.rotationLeftMax = Math.min(
|
||||
headPoseMaxValues.value.rotationLeftMax,
|
||||
Math.abs(headPose.rotation)
|
||||
)
|
||||
} else if (headPose.rotation > 0) {
|
||||
// 右旋(正值)
|
||||
headPoseMaxValues.value.rotationRightMax = Math.max(
|
||||
headPoseMaxValues.value.rotationRightMax,
|
||||
headPose.rotation
|
||||
)
|
||||
}
|
||||
|
||||
// 更新倾斜角最值
|
||||
if (headPose.tilt < 0) {
|
||||
// 左倾(负值),取绝对值的最大值
|
||||
headPoseMaxValues.value.tiltLeftMax = Math.min(
|
||||
headPoseMaxValues.value.tiltLeftMax,
|
||||
Math.abs(headPose.tilt)
|
||||
)
|
||||
} else if (headPose.tilt > 0) {
|
||||
// 右倾(正值)
|
||||
headPoseMaxValues.value.tiltRightMax = Math.max(
|
||||
headPoseMaxValues.value.tiltRightMax,
|
||||
headPose.tilt
|
||||
)
|
||||
}
|
||||
|
||||
// 更新俯仰角最值
|
||||
if (headPose.pitch < 0) {
|
||||
// 下俯(负值),取绝对值的最大值
|
||||
headPoseMaxValues.value.pitchDownMax = Math.min(
|
||||
headPoseMaxValues.value.pitchDownMax,
|
||||
Math.abs(headPose.pitch)
|
||||
)
|
||||
} else if (headPose.pitch > 0) {
|
||||
// 上仰(正值)
|
||||
headPoseMaxValues.value.pitchUpMax = Math.max(
|
||||
headPoseMaxValues.value.pitchUpMax,
|
||||
headPose.pitch
|
||||
)
|
||||
}
|
||||
|
||||
// 输出当前最值(用于调试)
|
||||
console.log('📊 当前头部姿态最值:', {
|
||||
rotationLeft: headPoseMaxValues.value.rotationLeftMax.toFixed(1),
|
||||
rotationRight: headPoseMaxValues.value.rotationRightMax.toFixed(1),
|
||||
tiltLeft: headPoseMaxValues.value.tiltLeftMax.toFixed(1),
|
||||
tiltRight: headPoseMaxValues.value.tiltRightMax.toFixed(1),
|
||||
pitchUp: headPoseMaxValues.value.pitchUpMax.toFixed(1),
|
||||
pitchDown: headPoseMaxValues.value.pitchDownMax.toFixed(1)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('❌ 更新头部姿态最值失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 清零最值并开始跟踪
|
||||
function clearAndStartTracking() {
|
||||
try {
|
||||
saveMaxValuesToHistory()
|
||||
|
||||
// 重置所有最值为0
|
||||
headPoseMaxValues.value = {
|
||||
rotationLeftMax: 0,
|
||||
rotationRightMax: 0,
|
||||
tiltLeftMax: 0,
|
||||
tiltRightMax: 0,
|
||||
pitchUpMax: 0,
|
||||
pitchDownMax: 0
|
||||
}
|
||||
|
||||
// 开始跟踪
|
||||
isTrackingMaxValues.value = true
|
||||
|
||||
console.log('🔄 头部姿态最值已清零,开始跟踪')
|
||||
ElMessage.success('头部姿态最值已清零,开始跟踪')
|
||||
} catch (error) {
|
||||
console.error('❌ 清零最值失败:', error)
|
||||
ElMessage.error('清零最值失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前最值到历史记录
|
||||
function saveMaxValuesToHistory() {
|
||||
try {
|
||||
if (!isTrackingMaxValues.value) {
|
||||
ElMessage.warning('请先点击清零开始跟踪')
|
||||
return
|
||||
}
|
||||
|
||||
// 创建当前最值的副本
|
||||
const currentMaxValues = {
|
||||
id: headPoseHistory.value.length + 1,
|
||||
rotationLeftMax: Number(headPoseMaxValues.value.rotationLeftMax.toFixed(1)),
|
||||
rotationRightMax: Number(headPoseMaxValues.value.rotationRightMax.toFixed(1)),
|
||||
tiltLeftMax: Number(headPoseMaxValues.value.tiltLeftMax.toFixed(1)),
|
||||
tiltRightMax: Number(headPoseMaxValues.value.tiltRightMax.toFixed(1)),
|
||||
pitchUpMax: Number(headPoseMaxValues.value.pitchUpMax.toFixed(1)),
|
||||
pitchDownMax: Number(headPoseMaxValues.value.pitchDownMax.toFixed(1)),
|
||||
timestamp: new Date().toLocaleString()
|
||||
}
|
||||
|
||||
// 添加到历史记录
|
||||
headPoseHistory.value.push(currentMaxValues)
|
||||
|
||||
// 停止跟踪
|
||||
isTrackingMaxValues.value = false
|
||||
|
||||
console.log('💾 头部姿态最值已保存:', currentMaxValues)
|
||||
ElMessage.success(`头部姿态最值已保存(第${currentMaxValues.id}组)`)
|
||||
|
||||
// 更新历史数据表格(如果存在的话)
|
||||
updateHistoryTable()
|
||||
} catch (error) {
|
||||
console.error('❌ 保存最值失败:', error)
|
||||
ElMessage.error('保存最值失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 更新历史数据表格
|
||||
function updateHistoryTable() {
|
||||
try {
|
||||
// 将头部姿态最值数据合并到现有的历史数据中
|
||||
if (headPoseHistory.value.length > 0) {
|
||||
const latestData = headPoseHistory.value[headPoseHistory.value.length - 1]
|
||||
|
||||
// 更新historyData数组,添加头部姿态最值数据
|
||||
const newHistoryItem = {
|
||||
id: latestData.id,
|
||||
rotLeft: latestData.rotationLeftMax,
|
||||
rotRight: latestData.rotationRightMax,
|
||||
tiltLeft: latestData.tiltLeftMax,
|
||||
tiltRight: latestData.tiltRightMax,
|
||||
pitchDown: latestData.pitchDownMax,
|
||||
pitchUp: latestData.pitchUpMax,
|
||||
timestamp: latestData.timestamp
|
||||
}
|
||||
|
||||
// 如果historyData数组存在,则添加新数据
|
||||
if (historyData.value) {
|
||||
historyData.value.push(newHistoryItem)
|
||||
}
|
||||
|
||||
console.log('📋 历史数据表格已更新')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 更新历史数据表格失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理压力传感器足部压力数据
|
||||
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`)
|
||||
}
|
||||
|
||||
// 显示平衡分析
|
||||
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)
|
||||
}
|
||||
|
||||
// 这里可以添加数据可视化逻辑
|
||||
// 例如更新图表或显示压力分布
|
||||
|
||||
// 如果有图表组件,可以在这里更新数据
|
||||
// updatePressureChart(data.pressure_data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 处理压力传感器数据失败:', error)
|
||||
|
Loading…
Reference in New Issue
Block a user