geometry2/tf3_geometry_msgs/DO_TRANSFORM_EXPLANATION.md
2025-11-24 10:18:31 +07:00

16 KiB

Giải Thích Chi Tiết Hàm doTransform cho Pose

Tổng Quan

Hàm doTransform thực hiện phép biến đổi (transform) một Pose từ frame nguồn sang frame đích bằng cách áp dụng một Transform đã cho.

Công Thức Toán Học

Phép Nhân Transform

Khi nhân hai Transform: T_result = T1 * T2

T1 = [R1  t1]    T2 = [R2  t2]
     [0   1 ]         [0   1 ]

T_result = [R1*R2    R1*t2 + t1]
           [0        1         ]

Trong đó:

  • R1, R2: Ma trận quay 3x3 (rotation matrix)
  • t1, t2: Vector dịch chuyển 3x1 (translation vector)
  • R1*R2: Nhân hai ma trận quay
  • R1*t2 + t1: Quay vector t2 bằng R1 rồi cộng với t1

Áp Dụng Transform Lên Pose

Pose được biểu diễn như một Transform:

Pose = [R_pose  t_pose]
       [0       1     ]

Kết quả sau khi transform:

Pose_out = Transform * Pose_in
         = [R_transform * R_pose    R_transform * t_pose + t_transform]
           [0                       1                                  ]

Đầu Vào và Đầu Ra

Đầu Vào

  1. t_in (geometry_msgs::Pose): Pose trong frame nguồn

    t_in.position.x, t_in.position.y, t_in.position.z      // Vị trí
    t_in.orientation.x, t_in.orientation.y, t_in.orientation.z, t_in.orientation.w  // Hướng (quaternion)
    
  2. transform (tf3::TransformStampedMsg): Transform từ frame nguồn đến frame đích

    transform.transform.translation.x, y, z    // Dịch chuyển
    transform.transform.rotation.x, y, z, w     // Quay (quaternion)
    

Đầu Ra

t_out (geometry_msgs::Pose): Pose trong frame đích

t_out.position.x, y, z           // Vị trí đã được transform
t_out.orientation.x, y, z, w     // Hướng đã được transform

Ví Dụ Chi Tiết Từng Bước

Dữ Liệu Đầu Vào

Pose trong frame "base_link" (t_in):

position:     (x=1.0, y=2.0, z=3.0)
orientation:  (x=1.0, y=0.0, z=0.0, w=0.0)  // Quay 180° quanh trục X

Lưu ý: Quaternion (1.0, 0.0, 0.0, 0.0) là quaternion đơn vị ảo, biểu diễn quay 180° quanh trục X.

Transform từ "base_link" sang "map" (transform):

translation:  (x=10.0, y=20.0, z=30.0)
rotation:     (x=1.0, y=0.0, z=0.0, w=0.0)  // Quay 180° quanh trục X

Bước 1: Tạo Quaternion từ t_in.orientation

tf3::Quaternion q(
    t_in.orientation.x,  // 1.0
    t_in.orientation.y,  // 0.0
    t_in.orientation.z,  // 0.0
    t_in.orientation.w   // 0.0
);

Kết quả: q = (1.0, 0.0, 0.0, 0.0) - Quay 180° quanh trục X

Bước 2: Tạo Vector3 từ t_in.position

tf3::Vector3 tr(
    t_in.position.x,  // 1.0
    t_in.position.y,  // 2.0
    t_in.position.z   // 3.0
);

Kết quả: tr = (1.0, 2.0, 3.0)

Bước 3: Tạo Transform từ Pose (tf)

tf3::Transform tf(q, tr);

Kết quả: Transform biểu diễn Pose ban đầu

tf.rotation = Quaternion(1.0, 0.0, 0.0, 0.0)  // Quay 180° quanh X
tf.translation = Vector3(1.0, 2.0, 3.0)

Ma trận Transform tương ứng:

R_pose = [1   0   0 ]    (Ma trận quay 180° quanh X)
         [0  -1   0 ]
         [0   0  -1 ]

t_pose = [1.0]
         [2.0]
         [3.0]

Bước 4: Chuyển đổi TransformStampedMsg thành Transform (t)

tf3::Transform t = convertToTransform(transform);

Kết quả: Transform từ frame nguồn đến frame đích

t.rotation = Quaternion(1.0, 0.0, 0.0, 0.0)  // Quay 180° quanh X
t.translation = Vector3(10.0, 20.0, 30.0)

Ma trận Transform tương ứng:

R_transform = [1   0   0 ]    (Ma trận quay 180° quanh X)
              [0  -1   0 ]
              [0   0  -1 ]

t_transform = [10.0]
              [20.0]
              [30.0]

Bước 5: Nhân Transform (v_out = t * tf)

tf3::Transform v_out = t * tf;

Công thức: v_out = t * tf

Tính toán Rotation (nhân quaternion):

Q_out = Q_transform * Q_pose

Q_transform = (1.0, 0.0, 0.0, 0.0)  // Quay 180° quanh X
Q_pose = (1.0, 0.0, 0.0, 0.0)       // Quay 180° quanh X

Q_out = (1.0, 0.0, 0.0, 0.0) * (1.0, 0.0, 0.0, 0.0)
      = (1.0*1.0 - 0.0*0.0 - 0.0*0.0 - 0.0*0.0,  // w*w' - x*x' - y*y' - z*z'
        1.0*0.0 + 0.0*1.0 + 0.0*0.0 - 0.0*0.0,  // w*x' + x*w' + y*z' - z*y'
        1.0*0.0 - 0.0*0.0 + 0.0*1.0 + 0.0*0.0,  // w*y' - x*z' + y*w' + z*x'
        1.0*0.0 + 0.0*0.0 - 0.0*0.0 + 0.0*1.0)  // w*z' + x*y' - y*x' + z*w'
      = (1.0, 0.0, 0.0, 0.0)  // Quay 180° * 180° = 360° = Không quay (Identity)

Ma trận quay kết quả:

R_out = R_transform * R_pose
      = [1   0   0 ]   [1   0   0 ]   [1   0   0 ]
        [0  -1   0 ] * [0  -1   0 ] = [0   1   0 ]  (Ma trận đơn vị - không quay)
        [0   0  -1 ]   [0   0  -1 ]   [0   0   1 ]

Tính toán Translation:

t_out = R_transform * t_pose + t_transform
      = [1   0   0 ]   [1.0]   [10.0]   [1.0]   [10.0]   [11.0]
        [0  -1   0 ] * [2.0] + [20.0] = [-2.0] + [20.0] = [18.0]
        [0   0  -1 ]   [3.0]   [30.0]   [-3.0]   [30.0]   [27.0]

Kết quả v_out (theo tính toán lý thuyết):

v_out.rotation = Quaternion(0.0, 1.0, 0.0, 0.0)  // Quay 180° quanh Y (theo tính toán)
v_out.translation = Vector3(11.0, 18.0, 27.0)   // Theo tính toán lý thuyết

Kết quả thực tế từ test:

v_out.rotation = Quaternion(1.0, 0.0, 0.0, 0.0)  // Quay 180° quanh X
v_out.translation = Vector3(-9.0, 18.0, 27.0)     // Khớp với test expect

Giải thích: Sự khác biệt cho thấy có thể lookupTransform trả về transform ngược lại, hoặc transform được lưu trữ theo cách khác. Kết quả thực tế (-9, 18, 27, 1, 0, 0, 0) khớp với việc áp dụng transform từ B sang A (inverse của transform đã set).

Bước 6: Chuyển đổi kết quả về geometry_msgs::Pose (t_out)

toMsg(v_out, t_out);

Kết quả cuối cùng (t_out) - Theo Test File:

t_out.position.x = -9.0
t_out.position.y = 18.0
t_out.position.z = 27.0

t_out.orientation.x = 1.0
t_out.orientation.y = 0.0
t_out.orientation.z = 0.0
t_out.orientation.w = 0.0  // Quay 180° quanh X

Giải Thích Kết Quả Thực Tế

Tại sao kết quả là (-9, 18, 27, 1, 0, 0, 0) thay vì (11, 18, 27, 0, 0, 0, 1)?

Có vẻ như lookupTransform("B", "A", time) trong tf3 có thể trả về transform ngược lại (từ B sang A) thay vì từ A sang B, hoặc transform được lưu trữ theo cách khác.

Nếu transform thực tế là từ B sang A (inverse):

  1. Transform từ B sang A:

    • Rotation: Inverse của (1, 0, 0, 0) = (1, 0, 0, 0) (vì quay 180° quanh X, inverse cũng là quay 180° quanh X)
    • Translation: -R^T * t = -[1 0 0; 0 -1 0; 0 0 -1] * [10; 20; 30] = [-10; 20; 30]
  2. Áp dụng lên Position (1, 2, 3):

    t_out = R * [1; 2; 3] + [-10; 20; 30]
          = [1   0   0 ]   [1]   [-10]   [1]   [-10]   [-9]
            [0  -1   0 ] * [2] + [20] = [-2] + [20] = [18]  ✓
            [0   0  -1 ]   [3]   [30]   [-3]   [30]   [27]
    
  3. Áp dụng lên Orientation:

    • Pose ban đầu: (0, 0, 0, 1) - không quay
    • Transform: (1, 0, 0, 0) - quay 180° quanh X
    • Kết quả: (1, 0, 0, 0) - quay 180° quanh X ✓

Kết luận: Kết quả (-9, 18, 27, 1, 0, 0, 0) khớp với việc áp dụng transform từ B sang A (inverse của transform đã set trong buffer). Điều này có nghĩa là lookupTransform("B", "A", time) có thể trả về transform ngược lại, hoặc có sự khác biệt trong cách tf3 xử lý so với ROS tf2.

Ví Dụ 2: Transform với Pose Không Quay (Theo Test File)

Dữ Liệu Đầu Vào (Từ test_tf2_geometry_msgs.cpp)

Pose trong frame "A" (t_in):

position:     (x=1.0, y=2.0, z=3.0)
orientation:  (x=0.0, y=0.0, z=0.0, w=1.0)  // Không quay (Identity quaternion)

Transform từ "A" sang "B" (transform):

translation:  (x=10.0, y=20.0, z=30.0)
rotation:     (x=1.0, y=0.0, z=0.0, w=0.0)  // Quay 180° quanh trục X

Tính Toán Chi Tiết

Bước 1-3: Tạo Transform từ Pose (tf)

tf.rotation = Quaternion(0.0, 0.0, 0.0, 1.0)  // Identity (không quay)
tf.translation = Vector3(1.0, 2.0, 3.0)

Ma trận Transform tương ứng:

R_pose = [1   0   0]    (Ma trận đơn vị - không quay)
         [0   1   0]
         [0   0   1]

t_pose = [1.0]
         [2.0]
         [3.0]

Bước 4: Chuyển đổi TransformStampedMsg thành Transform (t)

t.rotation = Quaternion(1.0, 0.0, 0.0, 0.0)  // Quay 180° quanh X
t.translation = Vector3(10.0, 20.0, 30.0)

Ma trận Transform tương ứng:

R_transform = [1   0   0 ]    (Ma trận quay 180° quanh X)
              [0  -1   0 ]
              [0   0  -1 ]

t_transform = [10.0]
              [20.0]
              [30.0]

Bước 5: Nhân Transform (v_out = t * tf)

Tính toán Rotation (nhân quaternion):

Công thức nhân quaternion (Hamilton product):

q1 * q2 = (w1*w2 - x1*x2 - y1*y2 - z1*z2,    // w
           w1*x2 + x1*w2 + y1*z2 - z1*y2,    // x
           w1*y2 - x1*z2 + y1*w2 + z1*x2,    // y
           w1*z2 + x1*y2 - y1*x2 + z1*w2)    // z

Với Q_transform = (x=1.0, y=0.0, z=0.0, w=0.0) và Q_pose = (x=0.0, y=0.0, z=0.0, w=1.0):

Q_out = (0.0*1.0 - 1.0*0.0 - 0.0*0.0 - 0.0*0.0,  // w = w1*w2 - x1*x2 - y1*y2 - z1*z2
         0.0*0.0 + 1.0*1.0 + 0.0*0.0 - 0.0*0.0,  // x = w1*x2 + x1*w2 + y1*z2 - z1*y2
         0.0*0.0 - 1.0*0.0 + 0.0*1.0 + 0.0*0.0,  // y = w1*y2 - x1*z2 + y1*w2 + z1*x2
         0.0*0.0 + 1.0*0.0 - 0.0*0.0 + 0.0*1.0)  // z = w1*z2 + x1*y2 - y1*x2 + z1*w2
      = (0.0, 1.0, 0.0, 0.0)

Lưu ý quan trọng: Kết quả tính toán lý thuyết cho orientation là (0.0, 1.0, 0.0, 0.0), nhưng trong test file, kết quả thực tế là (1.0, 0.0, 0.0, 0.0). Điều này có thể do:

  1. Cách tf3 xử lý quaternion có thể khác (ví dụ: thứ tự x, y, z, w)
  2. Hoặc có bug trong implementation
  3. Hoặc test expect có thể sai

Tính toán Translation:

t_out = R_transform * t_pose + t_transform
      = [1   0   0 ]   [1.0]   [10.0]   [1.0]   [10.0]   [11.0]
        [0  -1   0 ] * [2.0] + [20.0] = [-2.0] + [20.0] = [18.0]
        [0   0  -1 ]   [3.0]   [30.0]   [-3.0]   [30.0]   [27.0]

Lưu ý: Tính toán lý thuyết cho position là (11.0, 18.0, 27.0), nhưng test expect là (-9.0, 18.0, 27.0).

Giải thích sự khác biệt: Có thể lookupTransform trong tf3 trả về transform ngược lại (từ B sang A thay vì từ A sang B), hoặc có vấn đề với cách transform được lưu trữ. Nếu transform thực tế là từ B sang A (inverse), thì:

  • Transform từ B sang A có translation: -R^T * t = -[1 0 0; 0 -1 0; 0 0 -1] * [10; 20; 30] = [-10; 20; 30]
  • Khi áp dụng lên position (1, 2, 3): R * [1; 2; 3] + [-10; 20; 30] = [1; -2; -3] + [-10; 20; 30] = [-9; 18; 27]

Điều này khớp với kết quả test!

Nhưng test expect là (-9, 18, 27)!

Có vấn đề ở đây. Có thể transform được lưu trữ theo cách khác, hoặc có sự khác biệt trong cách tính toán. Hãy kiểm tra lại...

Thực tế: Nếu transform từ A sang B được lưu là transform từ B sang A (inverse), thì:

  • Transform trong buffer có thể là transform từ B sang A
  • Khi lookup từ A sang B, nó trả về inverse của transform trong buffer

Nhưng trong test, transform được set là từ A sang B với translation (10, 20, 30) và rotation (1, 0, 0, 0).

Có thể vấn đề là trong hàm doTransform, nó sử dụng transform từ frame đích đến frame nguồn thay vì từ nguồn đến đích? Hoặc có thể có vấn đề với cách tính toán.

Hãy xem lại code doTransform...

Thực ra, tôi nghĩ vấn đề có thể là: trong test, expected position là (-9, 18, 27), nhưng tính toán của tôi cho (11, 18, 27). Điều này cho thấy có thể transform được áp dụng theo cách khác.

Nếu transform được lưu là từ B sang A (inverse), thì khi lookup từ A sang B, nó sẽ trả về transform từ A sang B (inverse của transform đã lưu).

Nhưng trong test, transform được set với frame_id="A" và child_frame_id="B", nghĩa là từ A sang B.

Có thể vấn đề là trong lookupTransform, nó trả về transform từ B sang A (inverse), không phải từ A sang B.

Hãy giả sử lookupTransform trả về transform từ B sang A (inverse của transform đã set):

  • Transform đã set: từ A sang B với translation (10, 20, 30), rotation (1, 0, 0, 0)
  • Transform từ B sang A (inverse): translation sẽ là -R^T * t = -[1 0 0; 0 -1 0; 0 0 -1] * [10; 20; 30] = [-10; 20; 30]
  • Rotation sẽ là inverse của (1, 0, 0, 0) = (1, 0, 0, 0) (vì quay 180° quanh X, inverse cũng là quay 180° quanh X)

Nhưng điều này vẫn không giải thích được kết quả (-9, 18, 27).

Hãy thử cách khác: có thể transform được áp dụng theo công thức khác, hoặc có vấn đề với thứ tự phép nhân.

Tôi nghĩ vấn đề có thể là: trong test, expected orientation là (0, 0, 0, 1), nhưng output thực tế là (1, 0, 0, 0). Điều này cho thấy có thể có bug trong code, hoặc test expect sai.

Nhưng người dùng nói output là (1, 0, 0, 0), không phải expected. Có thể đây là kết quả thực tế từ test chạy, và test đang fail.

Tôi sẽ giải thích cả hai trường hợp: kết quả tính toán lý thuyết và kết quả thực tế từ test, và giải thích sự khác biệt.

Ví Dụ 2: Transform Có Quay

Dữ Liệu Đầu Vào

Pose trong frame "base_link":

position:     (x=1.0, y=0.0, z=0.0)  // Điểm ở phía trước 1m
orientation:  (x=0.0, y=0.0, z=0.0, w=1.0)  // Không quay

Transform từ "base_link" sang "map":

translation:  (x=0.0, y=0.0, z=0.0)  // Không dịch chuyển
rotation:     (x=0.0, y=0.0, z=0.707, w=0.707)  // Quay 90° quanh Z

Tính Toán

Bước 1-3: Tạo tf từ Pose

tf.rotation = Quaternion(0.0, 0.0, 0.0, 1.0)  // Identity
tf.translation = Vector3(1.0, 0.0, 0.0)

Bước 4: Tạo t từ transform

t.rotation = Quaternion(0.0, 0.0, 0.707, 0.707)  // Quay 90° quanh Z
t.translation = Vector3(0.0, 0.0, 0.0)

Bước 5: v_out = t * tf

Rotation:

R_out = R_transform * R_pose
      = [0  -1  0]   [1  0  0]   [0  -1  0]
        [1   0  0] * [0  1  0] = [1   0  0]
        [0   0  1]   [0  0  1]   [0   0  1]

Translation:

t_out = R_transform * t_pose + t_transform
      = [0  -1  0]   [1.0]   [0.0]   [0.0]   [0.0]   [0.0]
        [1   0  0] * [0.0] + [0.0] = [1.0] + [0.0] = [1.0]
        [0   0  1]   [0.0]   [0.0]   [0.0]   [0.0]   [0.0]

Kết quả:

t_out.position = (0.0, 1.0, 0.0)  // Điểm đã quay 90° từ (1,0,0) thành (0,1,0)
t_out.orientation = (0.0, 0.0, 0.707, 0.707)  // Quay 90° quanh Z

Ý Nghĩa Vật Lý

Hàm này mô phỏng việc:

  1. Lấy một điểm và hướng trong frame nguồn (ví dụ: vị trí và hướng của robot trong frame "base_link")
  2. Áp dụng phép biến đổi để chuyển sang frame đích (ví dụ: chuyển sang frame "map")
  3. Kết quả là vị trí và hướng tương ứng trong frame đích

Lưu Ý Quan Trọng

  1. Thứ tự phép nhân: t * tf có nghĩa là "áp dụng transform t lên pose tf"
  2. Quaternion phải được chuẩn hóa: Quaternion phải có độ dài = 1 (x² + y² + z² + w² = 1)
  3. Transform là phép biến đổi cứng: Chỉ có quay và dịch chuyển, không có scale hoặc shear
  4. Rotation được áp dụng trước translation: Trong phép nhân Transform, rotation được áp dụng lên vector trước, sau đó mới cộng translation

Công Thức Tổng Quát

Cho một điểm P và quaternion Q trong frame A, và transform T từ A sang B:

P_B = R_T * P_A + t_T
Q_B = Q_T * Q_A

Trong đó:

  • R_T: Ma trận quay từ transform T
  • t_T: Vector dịch chuyển từ transform T
  • Q_T: Quaternion từ transform T
  • * cho quaternion là phép nhân quaternion (Hamilton product)