8. tf2 — กรอบพิกัด
หัวข้อนี้คือหัวใจของหุ่นเคลื่อนที่ ข้ามไม่ได้
ปัญหาที่ tf2 แก้
LiDAR วัดว่า "มีของอยู่ห่างไปข้างหน้า 2 เมตร" — แต่ข้างหน้าของ LiDAR ซึ่งติดอยู่หน้าหุ่น สูงจากพื้น 20 ซม. และอาจหันกลับหลัง
คำถามที่ต้องตอบคือ "ของชิ้นนั้นอยู่ตรงไหนบนแผนที่"
ต้องแปลงพิกัดผ่านหลายทอด — จาก LiDAR ไปจุดกลางหุ่น จากจุดกลางหุ่นไปแผนที่
tf2 ทำหน้าที่นี้ และทำแบบย้อนเวลาได้ เพราะข้อมูลแต่ละชิ้นมาถึงคนละเวลา
กรอบพิกัดมาตรฐานของหุ่นเคลื่อนที่
ลำดับนี้เป็นมาตรฐานสากล (REP-105) เครื่องมือทุกตัวคาดหวังแบบนี้
map
└── odom
└── base_link
├── laser_front
├── laser_rear
└── wheel_left / wheel_right
กรอบ |
คืออะไร |
ใครเป็นคนประกาศ |
|---|---|---|
|
จุดอ้างอิงคงที่ของพื้นที่ ไม่ขยับ |
AMCL หรือ SLAM |
|
จุดที่หุ่นเริ่มเดิน · นิ่มแต่ไหลสะสม |
ตัวอ่าน encoder |
|
จุดกลางหุ่น มักอยู่กลางเพลาล้อ |
— |
เซ็นเซอร์ |
ตำแหน่งเทียบกับ |
URDF |
Important
ทำไมต้องมีทั้ง map และ odom — คนสับสนตรงนี้มากที่สุด
odom → base_link คำนวณจาก encoder จึงลื่นไหลต่อเนื่องไม่มีกระโดด
แต่ผิดสะสมเรื่อย ๆ วิ่งไปนาน ๆ จะเพี้ยนไปหลายเมตร
map → odom คือตัวแก้ค่าที่สะสมไว้ AMCL คำนวณจากการเทียบ LiDAR กับแผนที่
แล้วปรับให้ตำแหน่งจริงถูกต้อง ค่านี้กระโดดได้
รวมกันแล้วได้ทั้ง 2 อย่าง — ตำแหน่งที่ลื่นไหลพอให้ควบคุมล้อ และตำแหน่งที่ถูกต้องพอให้ไปถึงเป้าหมาย
ประกาศกรอบคงที่
ตำแหน่ง LiDAR เทียบกับตัวหุ่นไม่เปลี่ยน ประกาศครั้งเดียวพอ
ทดลองเร็ว ๆ ด้วยคำสั่ง (LiDAR อยู่หน้าหุ่น 40 ซม. สูง 20 ซม.)
ros2 run tf2_ros static_transform_publisher \
--x 0.4 --z 0.2 --frame-id base_link --child-frame-id laser_front
ระยะเป็นเมตร มุมเป็นเรเดียน ค่าที่ไม่ใส่ถือเป็นศูนย์
มุมใส่ได้ด้วย --roll --pitch --yaw
Note
ตัวอย่างเก่าตามอินเทอร์เน็ตมักเขียนแบบเรียงตำแหน่งติดกัน
ros2 run tf2_ros static_transform_publisher 0.4 0 0.2 0 0 0 base_link laser_front
แบบนั้นยังใช้ได้ใน Humble แต่จะขึ้นคำเตือน
Old-style arguments are deprecated และลำดับคือ x y z yaw pitch roll
ซึ่ง yaw มาก่อน roll สลับกับชื่อ flag แบบใหม่ — จำสลับแล้วหุ่นหันผิดทางโดยไม่รู้ตัว
ใช้แบบใส่ชื่อ flag ปลอดภัยกว่า
Note
ในระบบจริงไม่ประกาศแบบนี้ แต่เขียนไว้ใน URDF แล้วให้ robot_state_publisher
ประกาศให้ทั้งหมดทีเดียว (อยู่ในส่วนที่ 2 หัวข้อ 13)
คำสั่งนี้เหมาะกับตอนทดลองหรือตอนหาสาเหตุ
ตรวจว่าต่อกันครบไหม
ดูเป็นรูป
ros2 run tf2_tools view_frames
ได้ไฟล์ frames.pdf เป็นแผนภาพต้นไม้ทั้งหมด พร้อมบอกความถี่ของแต่ละเส้น
วัดระยะระหว่าง 2 กรอบ
ros2 run tf2_ros tf2_echo base_link laser_front
- Translation: [0.400, 0.000, 0.200]
- Rotation: in RPY (radian) [0.000, 0.000, 0.000]
Tip
เอาค่าที่ได้ไปเทียบกับตลับเมตรจริง ถ้าไม่ตรง แปลว่าค่าใน URDF ผิด
ผลของการผิดคือแผนที่จะเพี้ยน โดยไม่มีอะไรฟ้องเลย — เพราะระบบเชื่อว่าค่าที่บอกไปถูก
กับดักที่เจอบ่อย
① ต้นไม้ขาดตอน
Could not find a connection between 'map' and 'laser_front'
แปลว่ามีเส้นใดเส้นหนึ่งหายไป ดู frames.pdf จะเห็นว่าขาดตรงไหน
สาเหตุมักเป็น node ที่ควรประกาศเส้นนั้นยังไม่ได้เปิด
② มีคนประกาศเส้นเดียวกัน 2 คน
อาการคือหุ่นใน RViz สั่นหรือกระตุกไปมา เพราะได้ค่าจาก 2 แหล่งสลับกัน
เจอบ่อยตอนเปิด robot_state_publisher พร้อมกับ static_transform_publisher
ที่ประกาศเส้นเดียวกัน
ดู frames.pdf — ถ้าเส้นเดียวมี publisher มากกว่า 1 จะเห็นในนั้น
③ เวลาไม่ตรงกัน
Lookup would require extrapolation into the future
เกิดตอนใช้หลายเครื่อง แล้วนาฬิกาไม่ตรงกัน แก้ด้วยการติดตั้ง chrony
ให้ทุกเครื่องเทียบเวลากัน
Warning
🔴 บนหุ่นจริงที่ ROS 2 วิ่งข้ามเครื่อง เรื่องนาฬิกาสำคัญกว่าที่คิด ต่างกัน 0.1 วินาทีก็ทำให้ tf ใช้ไม่ได้แล้ว และข้อความ error ไม่ได้บอกว่าเป็นเพราะนาฬิกา
④ ใช้ sim time ผิด
ถ้ารันกับ Gazebo ต้องตั้ง use_sim_time: true ให้ ทุก node
ถ้าตั้งไม่ครบ node ที่ตกหล่นจะใช้เวลาจริงในขณะที่ตัวอื่นใช้เวลาจำลอง
ผลคือ tf พังทั้งระบบ
กลับกัน — พอย้ายมาหุ่นจริงต้องเปลี่ยนเป็น false ให้ครบทุกตัวเช่นกัน
ก่อนหน้า: 7. launch file กับ parameter · ต่อไป: 9. RViz