Desmond-Dong commited on
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 SDK's compose_world_offset for proper pose composition (same as conversation_app).
704
- Primary pose comes from actions, secondary offsets come from speech sway and face tracking.
705
  """
706
- # Build primary head pose matrix (from actions)
707
- if SDK_UTILS_AVAILABLE:
708
- primary_head = create_head_pose(
709
- x=self.state.target_x,
710
- y=self.state.target_y,
711
- z=self.state.target_z,
712
- roll=self.state.target_roll,
713
- pitch=self.state.target_pitch,
714
- yaw=self.state.target_yaw,
715
- degrees=False, # Our state is in radians
716
- mm=False, # Our state is in meters
717
- )
718
- else:
719
- # Fallback: build matrix manually
720
- rotation = R.from_euler('xyz', [
721
- self.state.target_roll,
722
- self.state.target_pitch,
723
- self.state.target_yaw,
724
- ])
725
- primary_head = np.eye(4)
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
- # Combine all secondary offsets
737
- secondary_x = self.state.speech_x + face_offsets[0]
738
- secondary_y = self.state.speech_y + face_offsets[1]
739
- secondary_z = self.state.speech_z + self.state.breathing_z + face_offsets[2]
740
- secondary_roll = self.state.speech_roll + face_offsets[3]
741
- secondary_pitch = self.state.speech_pitch + face_offsets[4]
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": final_pitch,
796
- "yaw": final_yaw,
797
- "roll": final_roll,
798
- "x": final_x,
799
- "y": final_y,
800
- "z": final_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 [roll, pitch, yaw]
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