8. tf2 — กรอบพิกัด

หัวข้อนี้คือหัวใจของหุ่นเคลื่อนที่ ข้ามไม่ได้

ปัญหาที่ tf2 แก้

LiDAR วัดว่า "มีของอยู่ห่างไปข้างหน้า 2 เมตร" — แต่ข้างหน้าของ LiDAR ซึ่งติดอยู่หน้าหุ่น สูงจากพื้น 20 ซม. และอาจหันกลับหลัง

คำถามที่ต้องตอบคือ "ของชิ้นนั้นอยู่ตรงไหนบนแผนที่"

ต้องแปลงพิกัดผ่านหลายทอด — จาก LiDAR ไปจุดกลางหุ่น จากจุดกลางหุ่นไปแผนที่ tf2 ทำหน้าที่นี้ และทำแบบย้อนเวลาได้ เพราะข้อมูลแต่ละชิ้นมาถึงคนละเวลา

กรอบพิกัดมาตรฐานของหุ่นเคลื่อนที่

ลำดับนี้เป็นมาตรฐานสากล (REP-105) เครื่องมือทุกตัวคาดหวังแบบนี้

map
 └── odom
      └── base_link
           ├── laser_front
           ├── laser_rear
           └── wheel_left / wheel_right

กรอบ

คืออะไร

ใครเป็นคนประกาศ

map

จุดอ้างอิงคงที่ของพื้นที่ ไม่ขยับ

AMCL หรือ SLAM

odom

จุดที่หุ่นเริ่มเดิน · นิ่มแต่ไหลสะสม

ตัวอ่าน encoder

base_link

จุดกลางหุ่น มักอยู่กลางเพลาล้อ

เซ็นเซอร์

ตำแหน่งเทียบกับ base_link · คงที่

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