Sensor fusion
UAT 322 ปฏิบัติการปัญญาประดิษฐ์และการประกอบรวมระบบอากาศยานไร้คนขับอัตโนมัติ
บทเรียน
เมื่อเรียนจบโมดูลนี้ ผู้เรียนจะสามารถ
- อธิบายหลักทำนายและปรับของ Kalman filter และเหตุที่การหลอมรวมเซนเซอร์แม่นกว่าเซนเซอร์เดี่ยว
- เขียน Kalman filter หนึ่งมิติที่รวม IMU กับ GNSS และตัดค่าวัดผิดปกติด้วย innovation gate
- อ่านการตั้งค่าเซนเซอร์และ test ratio ของ EKF2 ใน PX4 และ EKF3 ใน ArduPilot
- คำนวณ reprojection RMSE ของการปรับเทียบกล้องกับ IMU และบอกว่าต้องรายงานอะไรคู่กัน
ทำไมต้องรู้
IMU วัดความเร่งได้หลายร้อยครั้งต่อวินาที แต่ถ้าเอาความเร่งมาอินทิเกรตเป็นตำแหน่ง ความคลาดเคลื่อนเล็ก ๆ จะสะสมจนตำแหน่งลอยไปไกล ส่วน GNSS บอกตำแหน่งที่ไม่ลอย แต่ได้เพียงไม่กี่ครั้งต่อวินาที มีสัญญาณรบกวนระดับเมตร และบางครั้งกระโดดผิด ๆ เมื่อสัญญาณสะท้อนอาคาร การหลอมรวมเซนเซอร์ (sensor fusion) เอาจุดแข็งของทั้งสองมารวมกัน คล้ายเราเดินในห้องมืดโดยนับก้าวไปเรื่อย ๆ แล้วแตะผนังเป็นระยะเพื่อแก้ตำแหน่งที่นับผิด
Kalman filter
Kalman filter ประมาณสถานะ (เช่น ตำแหน่งและความเร็ว) พร้อมความไม่แน่นอนของมัน ทำงานสองขั้นวนไปเรื่อย ๆ ตามตำราของ Welch และ Bishop
- ทำนาย (predict) ใช้แบบจำลองการเคลื่อนที่และค่า IMU เลื่อนสถานะไปข้างหน้า ความไม่แน่นอน เพิ่มขึ้นทุกครั้ง
- ปรับ (update) เมื่อมีค่าวัด เช่น GNSS คำนวณ innovation คือค่าวัดลบค่าที่ทำนาย แล้วขยับสถานะไปทางค่าวัดด้วยน้ำหนัก Kalman gain ถ้าเชื่อค่าวัดมาก (R เล็ก) จะใหญ่
ก่อนใช้ค่าวัด ระบบตรวจว่า innovation ใหญ่เกินกว่าที่ความไม่แน่นอนอธิบายได้หรือไม่ ด้วย test ratio เมื่อ คือ gate ในหน่วยส่วนเบี่ยงเบนมาตรฐาน ถ้าเกิน 1 ค่าวัดนั้นถูกปฏิเสธ EKF2 ของ PX4 ใช้หลักเดียวกัน และ gate ของตำแหน่ง GNSS (EKF2_GPS_P_GATE) ตั้งค่าเริ่มต้นไว้ 5 เท่าของส่วนเบี่ยงเบนมาตรฐาน
ตัวอย่างที่ 1 หลอมรวม IMU กับ GNSS ในหนึ่งมิติ
ข้อมูลสังเคราะห์ 20 วินาที IMU 100 Hz มีไบแอส 0.05 m/s² และสัญญาณรบกวน GNSS 5 Hz มีสัญญาณรบกวน 1.5 m และมีค่าผิดพลาด +25 m หนึ่งครั้งที่วินาทีที่ 6
import numpy as np
rng = np.random.default_rng(348)
dt = 0.01
t = np.arange(0, 20.0, dt)
true_acc = 0.6 * np.sin(0.5 * t)
true_pos = np.cumsum(np.cumsum(true_acc) * dt) * dt
imu_acc = true_acc + 0.05 + rng.normal(0, 0.2, t.size)
gnss = true_pos + rng.normal(0, 1.5, t.size)
gnss[600] += 25.0 # ค่า GNSS ผิดพลาดที่ t = 6 s
x, P = np.zeros(2), np.diag([4.0, 1.0]) # สถานะ [ตำแหน่ง, ความเร็ว]
F, B = np.array([[1, dt], [0, 1]]), np.array([0.5 * dt**2, dt])
Q, R, H, gate = np.diag([1e-4, 4e-4]), 1.5**2, np.array([1.0, 0.0]), 5.0
est, dead, vel, pos = np.zeros(t.size), np.zeros(t.size), 0.0, 0.0
for k in range(t.size):
x, P = F @ x + B * imu_acc[k], F @ P @ F.T + Q # ทำนาย
vel += imu_acc[k] * dt
pos += vel * dt
dead[k] = pos # ใช้ IMU อย่างเดียว
if k % 20 == 0: # มี GNSS ทุก 0.2 s
y = gnss[k] - H @ x
S = H @ P @ H + R
ratio = y**2 / (gate**2 * S)
if ratio > 1:
print(f"t = {t[k]:.1f} s: GNSS rejected, test ratio {ratio:.2f}")
else:
K = P @ H / S
x, P = x + K * y, (np.eye(2) - np.outer(K, H)) @ P
est[k] = x[0]
g = np.arange(0, t.size, 20)
rmse = lambda a, idx=slice(None): np.sqrt(np.mean((a[idx] - true_pos[idx]) ** 2))
print(f"RMSE IMU only {rmse(dead):.2f} m | GNSS only {rmse(gnss, g):.2f} m | fused {rmse(est):.2f} m")
t = 6.0 s: GNSS rejected, test ratio 8.06
RMSE IMU only 4.82 m | GNSS only 2.94 m | fused 0.69 m
ค่า GNSS ที่ผิดพลาดมี test ratio 8.06 จึงถูกปฏิเสธ ไม่ดึงค่าประมาณให้กระโดดตาม ใช้ IMU อย่างเดียวคลาดเคลื่อน 4.82 m เพราะไบแอสสะสม ใช้ GNSS อย่างเดียวคลาดเคลื่อน 2.94 m (รวมค่าผิด 25 m หนึ่งครั้ง) แต่ค่าหลอมรวมคลาดเคลื่อนเพียง 0.69 m ค่า Q และ R ในตัวอย่างนี้ตั้งตามข้อมูลสังเคราะห์ที่รู้คำตอบ ระบบจริงต้องปรับจาก log และข้อมูลจำเพาะของเซนเซอร์
EKF ในตัวควบคุมการบิน
ระบบจริงมีสถานะหลายสิบตัว (ตำแหน่ง ความเร็ว ท่าทาง ไบแอสของเซนเซอร์ สนามแม่เหล็ก ลม) และสมการไม่เป็นเชิงเส้น จึงใช้ Extended Kalman Filter (EKF) ที่ทำให้สมการเป็นเชิงเส้นรอบค่าประมาณปัจจุบัน ตำรา Probabilistic Robotics อธิบายหลักนี้ละเอียด
- PX4 EKF2 รวม IMU เข็มทิศ GNSS บารอมิเตอร์ ตัววัดระยะ optical flow external vision และ airspeed แต่ละแหล่งมี test ratio ของตัวเองที่ดูได้ใน log
- ArduPilot EKF3 เป็นตัวประมาณค่าเริ่มต้นของ ArduPilot
- การรับตำแหน่งจากระบบภาพ (เช่น VIO) เข้า EKF2 เปิดด้วย
EKF2_EV_CTRLซึ่งเป็น bitmask เลือกได้ว่ารวมตำแหน่งแนวนอน แนวดิ่ง ความเร็ว หรือมุมหัน ค่าเริ่มต้นคือปิด
VIO (visual-inertial odometry) เช่น VINS-Mono และ OpenVINS ประมาณการเคลื่อนที่จากกล้องกับ IMU ใช้แทน GNSS ในอาคาร แต่ต้องปรับเทียบกล้องกับ IMU ให้ดีก่อน
ปรับเทียบกล้องกับ IMU
การปรับเทียบมีสามส่วน: intrinsics (โมเดลกล้อง เช่น ความยาวโฟกัสและความบิดเบี้ยว) extrinsics (การหมุนและเลื่อนระหว่างกล้องกับ IMU) และ time offset (ความต่างเวลาของสองเซนเซอร์) เครื่องมืออย่าง Kalibr รายงานคุณภาพด้วย reprojection error คือระยะบนภาพระหว่างจุดที่ตรวจพบกับจุดที่ฉายจากแบบจำลอง
residuals = [(3, 4), (-1, 2), (0, -2), (2, 1)] # (dx, dy) หน่วยพิกเซล
n = len(residuals)
sq = sum(dx * dx + dy * dy for dx, dy in residuals)
print(f"RMSE per point {(sq / n) ** 0.5:.2f} px, per component {(sq / (2 * n)) ** 0.5:.2f} px")
RMSE per point 3.12 px, per component 2.21 px
ค่าเดียวกันให้ตัวเลขต่างกันตามนิยาม OpenCV calibrateCamera หารด้วยจำนวนจุด จึงต้องระบุนิยามทุกครั้ง ค่าเล็กบนชุดที่ใช้ปรับเทียบยังไม่ยืนยันว่าดีกับภาพใหม่ ต้องตรวจกับข้อมูลอีกชุด และบันทึกความละเอียดภาพ เลนส์ โมเดลความบิดเบี้ยว และขนาดเป้าคู่กัน
ปฏิบัติการประจำโมดูล
ปฏิบัติการ: อ่าน EKF จาก log และปรับเทียบกล้อง
- ปรับค่า
Rและgateในตัวอย่างที่ 1 ทีละค่า แล้วบันทึกผลต่อ RMSE และค่าที่ถูกปฏิเสธ - บินลอยตัวใน SITL เปิด log แล้วพล็อต test ratio ของ GNSS บารอมิเตอร์ และเข็มทิศ ระบุช่วงที่ค่าเข้าใกล้ 1
- จำลองให้ GNSS ขาดหายใน SITL สังเกตว่า EKF2 รายงานสถานะอย่างไร และโดรนเปลี่ยนโหมดอย่างไร
- ถ่ายภาพเป้าปรับเทียบกล้องบนลำ ปรับเทียบด้วย OpenCV หรือ Kalibr รายงาน reprojection RMSE พร้อมนิยาม และตรวจกับภาพชุดใหม่
- บันทึกรุ่นซอฟต์แวร์ การตั้งค่า และผลทั้งหมดลงสมุดปฏิบัติการ
ข้อผิดพลาดที่พบบ่อย
ระวัง
- เชื่อ GNSS ทุกค่า โดยไม่มี gate ตัดค่าผิดปกติ
- ตั้ง R เล็กเกินจริง ทำให้ filter ไล่ตามสัญญาณรบกวน
- ตั้ง gate กว้างมากเพื่อไม่ให้มีค่าถูกปฏิเสธ จนค่าผิดเข้ามาได้
- เปิดรับ external vision โดยไม่ได้ตรวจแกนพิกัดและเวลา
- รายงาน reprojection error โดยไม่บอกนิยาม หรือไม่ตรวจกับข้อมูลชุดใหม่
สรุป
- Kalman filter ทำนายด้วยแบบจำลองและ IMU แล้วปรับด้วยค่าวัด โดยถ่วงน้ำหนักตามความไม่แน่นอน
- Innovation gate ใช้ test ratio ตัดค่าวัดผิดปกติ ค่าเกิน 1 ถูกปฏิเสธ
- EKF2 ของ PX4 และ EKF3 ของ ArduPilot รวมเซนเซอร์หลายแหล่ง และบันทึก test ratio ไว้ใน log
- การปรับเทียบกล้องกับ IMU มี intrinsics extrinsics และ time offset และต้องรายงาน RMSE พร้อมนิยาม
แบบฝึกตรวจความเข้าใจ
- ทำไมการอินทิเกรตความเร่งจาก IMU อย่างเดียวจึงให้ตำแหน่งลอยออกไป
- Innovation m, m², gate test ratio เท่าใด ค่าวัดถูกใช้หรือไม่
- ถ้าเพิ่ม R (ความแปรปรวนของค่าวัด) Kalman gain จะเพิ่มหรือลด
- Residual สองจุดคือ (3, 4) และ (0, 0) px reprojection RMSE ต่อจุดเท่าใด
- พารามิเตอร์ใดของ PX4 เปิดการรวมข้อมูลตำแหน่งจากระบบภาพ
เฉลย
- ความคลาดเคลื่อนและไบแอสเล็ก ๆ ถูกอินทิเกรตสองครั้ง จึงสะสมเพิ่มขึ้นตามเวลา
- จึงถูกปฏิเสธ
- ลดลง เพราะเชื่อค่าวัดน้อยลง
- px
EKF2_EV_CTRL
สรุปสูตรสำคัญ
| ทำนาย | |
| ปรับด้วยค่าวัด | |
| Test ratio ของ innovation | |
| Reprojection RMSE ต่อจุด |
แหล่งอ้างอิงหลัก
- Welch, G., & Bishop, G. (2004). An introduction to the Kalman filter (Tech. Rep. TR 95-041). Department of Computer Science, University of North Carolina at Chapel Hill. link
- Thrun, S., Burgard, W., & Fox, D. (2005). Probabilistic robotics. MIT Press. link
- PX4 Autopilot. Using the ECL EKF. PX4 user guide (main). link
- ArduPilot Dev Team. Extended Kalman filter (EKF) overview. ArduPilot Copter documentation. link
- Qin, T., Li, P., & Shen, S. (2018). VINS-Mono: A robust and versatile monocular visual-inertial state estimator. IEEE Transactions on Robotics, 34(4), 1004–1020. link
- Geneva, P., Eckenhoff, K., Lee, W., Yang, Y., & Huang, G. (2020). OpenVINS: A research platform for visual-inertial estimation. In 2020 IEEE International Conference on Robotics and Automation (pp. 4666–4672). link
อ่านเพิ่มเติม
ศึกษาหน่วยความรู้ที่กำหนดล่วงหน้า ดูสื่อประกอบ และทำ quiz ประจำโมดูล
การประมาณสถานะด้วย Kalman filter/EKF
ปรับเทียบกล้องและ IMU
VIN, VIO และ SLAM ต่างกันอย่างไร
ในชั้นเรียน / ภาคสนาม
ปฏิบัติการเข้มข้นในแล็บและภาคสนาม บันทึกผลลงสมุดปฏิบัติการ
หลักฐานการเรียนรู้: สมุดปฏิบัติการที่อาจารย์ลงนาม