Commit ·
10a4ce7
1
Parent(s): 237ed8a
fix: 修复人脸追踪偏上问题
Browse files- 修复 _compose_final_pose 返回值引用错误 (final_pitch -> pitch)
- 修复 _issue_control_command 中 euler 顺序 ([roll,pitch,yaw] -> [pitch,roll,yaw])
- 移除调试日志
reachy_mini_ha_voice/camera_server.py
CHANGED
|
@@ -252,11 +252,6 @@ class MJPEGCameraServer:
|
|
| 252 |
float(rotation[1]),
|
| 253 |
float(rotation[2]),
|
| 254 |
]
|
| 255 |
-
|
| 256 |
-
# Debug: log the actual values
|
| 257 |
-
_LOGGER.info("Face tracking: trans=(%.4f, %.4f, %.4f), rot=(%.2f, %.2f, %.2f) deg",
|
| 258 |
-
translation[0], translation[1], translation[2],
|
| 259 |
-
np.degrees(rotation[0]), np.degrees(rotation[1]), np.degrees(rotation[2]))
|
| 260 |
|
| 261 |
except Exception as e:
|
| 262 |
_LOGGER.debug("Face tracking error: %s", e)
|
|
|
|
| 252 |
float(rotation[1]),
|
| 253 |
float(rotation[2]),
|
| 254 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
except Exception as e:
|
| 257 |
_LOGGER.debug("Face tracking error: %s", e)
|
reachy_mini_ha_voice/movement_manager.py
CHANGED
|
@@ -700,80 +700,37 @@ class MovementManager:
|
|
| 700 |
def _compose_final_pose(self) -> Dict[str, float]:
|
| 701 |
"""Compose final pose from all sources.
|
| 702 |
|
| 703 |
-
Uses
|
| 704 |
-
Primary pose comes from actions, secondary offsets come from speech sway and face tracking.
|
| 705 |
"""
|
| 706 |
-
#
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
primary_head[:3, :3] = rotation.as_matrix()
|
| 727 |
-
primary_head[0, 3] = self.state.target_x
|
| 728 |
-
primary_head[1, 3] = self.state.target_y
|
| 729 |
-
primary_head[2, 3] = self.state.target_z
|
| 730 |
-
|
| 731 |
-
# Build secondary offset pose (speech sway + face tracking + breathing)
|
| 732 |
-
# Get face tracking offsets
|
| 733 |
with self._face_tracking_lock:
|
| 734 |
face_offsets = self._face_tracking_offsets
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
secondary_yaw = self.state.speech_yaw + face_offsets[5]
|
| 743 |
-
|
| 744 |
-
if SDK_UTILS_AVAILABLE:
|
| 745 |
-
secondary_head = create_head_pose(
|
| 746 |
-
x=secondary_x,
|
| 747 |
-
y=secondary_y,
|
| 748 |
-
z=secondary_z,
|
| 749 |
-
roll=secondary_roll,
|
| 750 |
-
pitch=secondary_pitch,
|
| 751 |
-
yaw=secondary_yaw,
|
| 752 |
-
degrees=False,
|
| 753 |
-
mm=False,
|
| 754 |
-
)
|
| 755 |
-
# Compose using SDK utility (same as conversation_app)
|
| 756 |
-
combined_head = compose_world_offset(primary_head, secondary_head, reorthonormalize=True)
|
| 757 |
-
else:
|
| 758 |
-
# Fallback: simple addition (less accurate but works)
|
| 759 |
-
secondary_rotation = R.from_euler('xyz', [secondary_roll, secondary_pitch, secondary_yaw])
|
| 760 |
-
secondary_head = np.eye(4)
|
| 761 |
-
secondary_head[:3, :3] = secondary_rotation.as_matrix()
|
| 762 |
-
secondary_head[0, 3] = secondary_x
|
| 763 |
-
secondary_head[1, 3] = secondary_y
|
| 764 |
-
secondary_head[2, 3] = secondary_z
|
| 765 |
-
|
| 766 |
-
# Simple composition: R_final = R_secondary @ R_primary, t_final = t_primary + t_secondary
|
| 767 |
-
combined_head = np.eye(4)
|
| 768 |
-
combined_head[:3, :3] = secondary_head[:3, :3] @ primary_head[:3, :3]
|
| 769 |
-
combined_head[:3, 3] = primary_head[:3, 3] + secondary_head[:3, 3]
|
| 770 |
-
|
| 771 |
-
# Extract final pose values from combined matrix
|
| 772 |
-
final_rotation = R.from_matrix(combined_head[:3, :3])
|
| 773 |
-
final_roll, final_pitch, final_yaw = final_rotation.as_euler('xyz')
|
| 774 |
-
final_x = combined_head[0, 3]
|
| 775 |
-
final_y = combined_head[1, 3]
|
| 776 |
-
final_z = combined_head[2, 3]
|
| 777 |
|
| 778 |
# Antenna pose with freeze blending
|
| 779 |
target_antenna_left = self.state.target_antenna_left + self.state.breathing_antenna_left
|
|
@@ -792,12 +749,12 @@ class MovementManager:
|
|
| 792 |
antenna_right = target_antenna_right
|
| 793 |
|
| 794 |
return {
|
| 795 |
-
"pitch":
|
| 796 |
-
"yaw":
|
| 797 |
-
"roll":
|
| 798 |
-
"x":
|
| 799 |
-
"y":
|
| 800 |
-
"z":
|
| 801 |
"antenna_left": antenna_left,
|
| 802 |
"antenna_right": antenna_right,
|
| 803 |
"body_yaw": self.state.target_body_yaw,
|
|
@@ -812,10 +769,6 @@ class MovementManager:
|
|
| 812 |
if self.robot is None:
|
| 813 |
return
|
| 814 |
|
| 815 |
-
# Debug: log the final pose being sent
|
| 816 |
-
logger.info("Final pose: pitch=%.2f°, yaw=%.2f°, roll=%.2f°",
|
| 817 |
-
np.degrees(pose["pitch"]), np.degrees(pose["yaw"]), np.degrees(pose["roll"]))
|
| 818 |
-
|
| 819 |
# Check if pose changed significantly (prevent unnecessary commands)
|
| 820 |
if self._last_sent_pose is not None:
|
| 821 |
max_diff = max(
|
|
@@ -839,10 +792,10 @@ class MovementManager:
|
|
| 839 |
|
| 840 |
try:
|
| 841 |
# Build head pose matrix
|
| 842 |
-
# SDK uses 'xyz' euler order with [
|
| 843 |
rotation = R.from_euler('xyz', [
|
| 844 |
-
pose["roll"],
|
| 845 |
pose["pitch"],
|
|
|
|
| 846 |
pose["yaw"],
|
| 847 |
])
|
| 848 |
|
|
|
|
| 700 |
def _compose_final_pose(self) -> Dict[str, float]:
|
| 701 |
"""Compose final pose from all sources.
|
| 702 |
|
| 703 |
+
Uses simple addition for pose composition (same as original working version).
|
|
|
|
| 704 |
"""
|
| 705 |
+
# Primary pose (from actions)
|
| 706 |
+
pitch = self.state.target_pitch
|
| 707 |
+
yaw = self.state.target_yaw
|
| 708 |
+
roll = self.state.target_roll
|
| 709 |
+
x = self.state.target_x
|
| 710 |
+
y = self.state.target_y
|
| 711 |
+
z = self.state.target_z
|
| 712 |
+
|
| 713 |
+
# Add speech sway (secondary) - rotation and translation
|
| 714 |
+
pitch += self.state.speech_pitch
|
| 715 |
+
yaw += self.state.speech_yaw
|
| 716 |
+
roll += self.state.speech_roll
|
| 717 |
+
x += self.state.speech_x
|
| 718 |
+
y += self.state.speech_y
|
| 719 |
+
z += self.state.speech_z
|
| 720 |
+
|
| 721 |
+
# Add breathing
|
| 722 |
+
z += self.state.breathing_z
|
| 723 |
+
|
| 724 |
+
# Add face tracking offsets (from camera worker)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 725 |
with self._face_tracking_lock:
|
| 726 |
face_offsets = self._face_tracking_offsets
|
| 727 |
+
|
| 728 |
+
x += face_offsets[0]
|
| 729 |
+
y += face_offsets[1]
|
| 730 |
+
z += face_offsets[2]
|
| 731 |
+
roll += face_offsets[3]
|
| 732 |
+
pitch += face_offsets[4]
|
| 733 |
+
yaw += face_offsets[5]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
|
| 735 |
# Antenna pose with freeze blending
|
| 736 |
target_antenna_left = self.state.target_antenna_left + self.state.breathing_antenna_left
|
|
|
|
| 749 |
antenna_right = target_antenna_right
|
| 750 |
|
| 751 |
return {
|
| 752 |
+
"pitch": pitch,
|
| 753 |
+
"yaw": yaw,
|
| 754 |
+
"roll": roll,
|
| 755 |
+
"x": x,
|
| 756 |
+
"y": y,
|
| 757 |
+
"z": z,
|
| 758 |
"antenna_left": antenna_left,
|
| 759 |
"antenna_right": antenna_right,
|
| 760 |
"body_yaw": self.state.target_body_yaw,
|
|
|
|
| 769 |
if self.robot is None:
|
| 770 |
return
|
| 771 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 772 |
# Check if pose changed significantly (prevent unnecessary commands)
|
| 773 |
if self._last_sent_pose is not None:
|
| 774 |
max_diff = max(
|
|
|
|
| 792 |
|
| 793 |
try:
|
| 794 |
# Build head pose matrix
|
| 795 |
+
# SDK uses 'xyz' euler order with [pitch, roll, yaw] (same as old working version)
|
| 796 |
rotation = R.from_euler('xyz', [
|
|
|
|
| 797 |
pose["pitch"],
|
| 798 |
+
pose["roll"],
|
| 799 |
pose["yaw"],
|
| 800 |
])
|
| 801 |
|