در فصل هشتم، diff_drive_controller از روی چرخش Encoderهای دو چرخ، مسیر ARCHO را تخمین
زد و روی /odom منتشر کرد. این خیلی خوب بهنظر میرسد — تا وقتی که یک روز متوجه شوی بعد از
نیم ساعت حرکت در انبار، موقعیت گزارششده ربات با موقعیت واقعیاش چند متر فاصله دارد. این پدیده
Drift نام دارد و دقیقاً همان مشکلی است که این فصل حلش میکند.
Wheel Odometry مثل شمردن قدمهایت با چشم بسته است — دقیق و سریع در کوتاهمدت، اما هر لغزش کوچک چرخ روی زمین به خطای تجمعی تبدیل میشود. IMU مثل حس تعادل گوش داخلی توست — میفهمد چقدر چرخیدهای، اما نمیداند چقدر جلو رفتهای. هیچکدام بهتنهایی کامل نیستند؛ اما وقتی با هم ترکیب شوند، نقاط ضعف یکدیگر را جبران میکنند. این ترکیب را Sensor Fusion میگویند.
ببینیم دقیقاً چه چیزی روی /odom منتشر میشود:
پیام Odometry سه بخش اصلی دارد:
| بخش | معنی |
|---|---|
pose | موقعیت و جهت تخمینی فعلی ربات |
twist | سرعت خطی و زاویهای لحظهای |
covariance | میزان عدم قطعیت این تخمین (بخش بعد بهطور کامل توضیح میدهد) |
Wheel Odometry فرض میکند چرخها دقیقاً به اندازه چرخششان روی زمین میلغزند — نه بیشتر، نه کمتر.
اما در واقعیت، سطح انبار ممکن است لیز باشد، چرخها کمی روی جا بچرخند، یا شعاع واقعی چرخ بهخاطر
سایش کمی از مقدار نوشتهشده در controllers.yaml فصل قبل فرق داشته باشد. هر کدام از این
خطاهای کوچک، لحظه به لحظه جمع میشوند — دقیقاً مثل خطای گردکردن در یک محاسبه طولانی.
IMU (که در فصل چهارم Frame فیزیکیاش را ساختیم) شتاب خطی و سرعت زاویهای را با نرخ بالا اندازه میگیرد. برخلاف Wheel Odometry، IMU هیچ فرضی درباره لغزش چرخ ندارد — مستقیم حرکت واقعی بدنه را حس میکند. اما IMU هم ضعف خودش را دارد: نویز بالا در بلندمدت باعث میشود تخمین موقعیتش بهتنهایی هم Drift کند، فقط از نوع دیگری.
| حسگر | نقطه قوت | نقطه ضعف |
|---|---|---|
| Wheel Odometry | دقیق در کوتاهمدت، نرخ پایدار | لغزش چرخ باعث Drift تجمعی میشود |
| IMU | مستقیماً چرخش و شتاب واقعی بدنه را حس میکند | نویزی و مستعد Drift در بلندمدت، مخصوصاً برای موقعیت |
برای ترکیب این دو منبع، از یک بسته استاندارد ROS 2 بهنام robot_localization استفاده میکنیم که یک فیلتر کالمن توسعهیافته (EKF — Extended Kalman Filter) پیادهسازی میکند.
# archo_bringup/config/ekf.yaml
ekf_filter_node:
ros__parameters:
frequency: 50.0
sensor_timeout: 0.1
two_d_mode: true
publish_tf: true
map_frame: map
odom_frame: odom
base_link_frame: base_link
world_frame: odom
odom0: /diff_drive_controller/odom
odom0_config: [false, false, false,
false, false, false,
true, true, false,
false, false, true,
false, false, false]
imu0: /imu/data
imu0_config: [false, false, false,
false, false, true,
false, false, false,
false, false, true,
true, false, false]
imu0_remove_gravitational_acceleration: true
هر _config پانزده مقدار true/false دارد که به ترتیب به X، Y، Z، Roll، Pitch، Yaw،
Vx، Vy، Vz، Vroll، Vpitch، Vyaw، Ax، Ay، Az اشاره میکنند. true یعنی «از این مقدار این
حسگر استفاده کن». مثلاً برای odom0، فقط Vx و Vy و Vyaw (سرعت خطی و زاویهای) روشن است
— چون به سرعت چرخها اعتماد داریم، نه به موقعیت مطلق آنها. برای imu0، Yaw و Vyaw و
شتاب طولی (Ax) روشناند — چون IMU در تشخیص چرخش دقیقتر از حرکت خطی است.
شتابسنج IMU همیشه گرانش زمین را هم اندازه میگیرد، حتی وقتی ربات کاملاً ساکن است. اگر این جزء حذف نشود، فیلتر فکر میکند ربات دائماً در حال شتابگرفتن است — یکی از رایجترین دلایل «ربات ساکنی که در RViz آرامآرام سر میخورد».
Covariance عددی است که میگوید یک اندازهگیری چقدر قابلاعتماد است. عدد بسیار کوچک یعنی «تقریباً مطمئنم»؛ عدد بزرگ یعنی «این اندازهگیری را با احتیاط در نظر بگیر». EKF از این اعداد استفاده میکند تا تصمیم بگیرد به کدام حسگر، در کدام لحظه، بیشتر وزن بدهد.
فرض کن ARCHO روی یک سطح خیلی صاف حرکت میکند — Wheel Odometry تقریباً بدون خطاست، پس Covariance پایینی دارد و EKF بیشتر به آن اعتماد میکند. اما وقتی از روی یک ناهمواری کوچک رد میشود، لغزش چرخ زیاد میشود؛ اینجا IMU (که این نوع اختلال را حس نمیکند) بهطور نسبی وزن بیشتری میگیرد. تمام این توازن، خودکار و لحظهبهلحظه توسط EKF انجام میشود.
سؤال منطقی: چرا داده /scan را هم مستقیم به EKF نمیدهیم؟ چون LiDAR خام یک
LaserScan است — فهرستی از فاصلهها در جهتهای مختلف — نه یک Pose یا Odometry. EKF به
دادهای با ساختار حرکتی (موقعیت، سرعت، شتاب) نیاز دارد. اگر بخواهیم از LiDAR برای تصحیح موقعیت
استفاده کنیم، باید اول آن را از طریق SLAM یا مقایسه با نقشه (که در فصل دهم و یازدهم میبینیم) به یک
Pose تبدیل کنیم؛ آن Pose، نه Scan خام، وارد Fusion میشود.
ros2 run tf2_ros tf2_echo odom base_link
ros2 run tf2_tools view_frames
ros2 topic echo /odometry/filtered
همانطور که در فصل پنجم دیدیم، این خطا معمولاً یعنی Timestampها هماهنگ نیستند یا
use_sim_time روی همه Nodeهای درگیر (از جمله ekf_filter_node) یکسان تنظیم
نشده است. در شبیهسازی، این تنظیم را هرگز فراموش نکن.
/odometry/filtered با نرخ پایدار (مثلاً ۵۰ هرتز) منتشر میشود.odom → base_link بدون خطای extrapolation در دسترس است.
ARCHO حالا به یک منبع تکحسگری برای تخمین موقعیت متکی نیست. Wheel Odometry و IMU، هرکدام با نقاط
قوت و ضعف خودشان، توسط robot_localization ترکیب میشوند تا یک تخمین پایدارتر روی
/odometry/filtered تولید شود — پایهای که SLAM و Nav2 در فصلهای بعد بدون آن اصلاً
نمیتوانند درست کار کنند.
پروژه ARCHO اکنون یک ekf.yaml کامل دارد و زنجیره odom → base_link که در فصل پنجم فقط تعریفش را دیدیم، حالا واقعاً با داده ترکیبی Wheel Odometry و IMU پر میشود.
در فصل دهم، ARCHO برای اولین بار بدون هیچ نقشه از پیشآمادهای وارد یک محیط ناشناخته میشود و با SLAM Toolbox، همزمان محیط را کشف میکند و نقشه میسازد — درست همانجایی که تخمین دقیق موقعیت این فصل، پیشنیاز مطلق آن است.