การวางแผนเส้นทางและหลบหลีก
UAT 308 ระบบอัตโนมัติและหุ่นยนต์
บทเรียน
เมื่อเรียนจบโมดูลนี้ ผู้เรียนจะสามารถ
- อธิบายการวางแผนแบบสุ่มตัวอย่างและเขียน RRT บนแผนที่ที่มีสิ่งกีดขวาง
- ปรับเส้นทางจาก RRT ให้สั้นลงด้วยการตัดทางลัด
- ใช้ pure pursuit ติดตามเส้นทางและเลือกระยะมองหน้าให้เหมาะกับเวลาหน่วง
- แยกหน้าที่ของตัววางแผนเส้นทางกับตัวติดตามเส้นทาง
ทำไมต้องรู้
UAT 314 หาเส้นทางบนตารางด้วย A* และ UAT 366 หลบสิ่งกีดขวางด้วย potential field และ VFH หน่วยความรู้เรื่องการวางแผนเส้นทางและหลบหลีกสิ่งกีดขวางอัตโนมัติของคลังความรู้โดรนกล่าวถึง A*, RRT, occupancy map และ obstacle avoidance โมดูลนี้เพิ่มสองเรื่อง คือ RRT ที่หาเส้นทางได้ในพื้นที่ต่อเนื่องโดยไม่ต้องแบ่งเป็นตาราง และ pure pursuit ที่เปลี่ยนเส้นทางเป็นคำสั่งเลี้ยวของรถ ตำราของ Siegwart และคณะ และ Lynch และ Park แยกงานสองอย่างนี้ออกจากกัน ตัววางแผนตอบว่า “ไปทางไหน” ตัวติดตามตอบว่า “เลี้ยวเท่าไรตอนนี้”
RRT
RRT (Rapidly-exploring Random Tree, LaValle 1998) สร้างต้นไม้จากจุดเริ่ม ทุกรอบสุ่มจุดในพื้นที่ หาโหนดในต้นไม้ที่ใกล้ที่สุด แล้วยื่นกิ่งไปทางจุดนั้นหนึ่งช่วง ถ้ากิ่งไม่ชนสิ่งกีดขวางก็เพิ่มเข้าต้นไม้ การสุ่มทำให้ต้นไม้ขยายเข้าหาพื้นที่ว่างที่ยังไม่สำรวจได้เร็ว และบางรอบสุ่มเป้าหมายโดยตรง (goal bias) เพื่อให้ต้นไม้เข้าหาเป้า เส้นทางแรกที่ได้มักคดเคี้ยว จึงนิยมตัดทางลัดโดยลากเส้นตรงข้ามจุดที่ไม่ชนอะไร
ตัวอย่างที่ 1 ข้ามแถวแผงไปยังตู้อินเวอร์เตอร์
พื้นที่ 20 × 20 m มีแถวแผงสามแถวสลับด้านกัน รถเริ่มที่ (1, 1) และต้องไปตู้อินเวอร์เตอร์ที่ (19, 19) ช่วงยื่นกิ่ง 1 m สุ่มเป้าหมาย 10% ของรอบ กำหนดเมล็ดสุ่มให้ผลซ้ำได้ (ข้อมูลจำลอง)
import numpy as np
rng = np.random.default_rng(7)
start, goal = np.array([1.0, 1.0]), np.array([19.0, 19.0])
# แถวแผงโซลาร์ (xmin, ymin, xmax, ymax) m
rows = [(0, 5, 15, 6.5), (5, 10, 20, 11.5), (0, 15, 15, 16.5)]
def free(p):
return all(not (x0 <= p[0] <= x1 and y0 <= p[1] <= y1) for x0, y0, x1, y1 in rows)
def segment_free(a, b, step=0.1):
n = max(2, int(np.linalg.norm(b - a) / step))
return all(free(a + (b - a) * s) for s in np.linspace(0, 1, n))
def rrt(max_nodes=3000, step=1.0, goal_bias=0.1):
nodes, parent = [start], [-1]
while len(nodes) < max_nodes:
sample = goal if rng.random() < goal_bias else rng.uniform(0, 20, 2)
i = int(np.argmin([np.linalg.norm(n - sample) for n in nodes]))
d = sample - nodes[i]
new = nodes[i] + d / max(np.linalg.norm(d), 1e-9) * min(step, np.linalg.norm(d))
if free(new) and segment_free(nodes[i], new):
nodes.append(new); parent.append(i)
if np.linalg.norm(new - goal) < step and segment_free(new, goal):
nodes.append(goal); parent.append(len(nodes) - 2)
path, k = [], len(nodes) - 1
while k != -1:
path.append(nodes[k]); k = parent[k]
return path[::-1], len(nodes)
return None, len(nodes)
def length(path):
return sum(np.linalg.norm(path[i+1] - path[i]) for i in range(len(path) - 1))
def shortcut(path):
out, i = [path[0]], 0
while i < len(path) - 1:
j = len(path) - 1
while j > i + 1 and not segment_free(path[i], path[j]):
j -= 1
out.append(path[j]); i = j
return out
path, n = rrt()
short = shortcut(path)
print(f"nodes in tree {n}")
print(f"RRT path {length(path):.1f} m ({len(path)} points)")
print(f"after shortcutting {length(short):.1f} m ({len(short)} points)")
print(f"straight line {np.linalg.norm(goal - start):.1f} m (blocked by panels)")
nodes in tree 389
RRT path 70.7 m (74 points)
after shortcutting 52.6 m (7 points)
straight line 25.5 m (blocked by panels)
RRT ได้เส้นทางที่ผ่านได้ภายในไม่กี่ร้อยโหนด แต่คดเคี้ยวยาว 70.7 m การตัดทางลัดลดเหลือ 52.6 m ด้วยจุดเลี้ยวเพียง 7 จุด เส้นทางยังชิดขอบแผง งานจริงจึงต้องขยายสิ่งกีดขวางออกเท่าครึ่งความกว้างรถบวกระยะเผื่อก่อนวางแผน ผลของ RRT ขึ้นกับการสุ่ม ถ้าเปลี่ยนเมล็ดสุ่มจะได้เส้นทางต่างกัน จึงควรทดสอบหลายครั้ง
pure pursuit
Coulter (1992) อธิบาย pure pursuit ว่าเลือกจุดเป้าบนเส้นทางที่อยู่ห่างจากรถเป็นระยะมองหน้า แล้วคำนวณความโค้งของวงกลมที่ผ่านทั้งรถและจุดเป้า โดย คือระยะด้านข้างของจุดเป้าในกรอบรถ เขียนอีกแบบได้ เมื่อ คือมุมจากหัวรถไปยังจุดเป้า อัตราหมุนที่สั่งคือ ระยะมองหน้าสั้นทำให้เข้าเส้นทางเร็วแต่ไวต่อเวลาหน่วงและสัญญาณรบกวน ระยะยาวทำให้นิ่งแต่ตัดโค้งและเข้าเส้นทางช้า
ตัวอย่างที่ 2 เข้าแนวแถวแผงเมื่อคำสั่งเลี้ยวหน่วง 0.3 s
รถวิ่ง 1 m/s เริ่มห่างแนวแถวแผง 1.0 m หันขนานกับแนว คำสั่งเลี้ยวไปถึงล้อช้า 0.3 s จากการสื่อสารและตัวขับมอเตอร์ เปรียบเทียบระยะมองหน้า 0.5, 1.0, 1.5 และ 3.0 m (แบบจำลองจลนศาสตร์)
import numpy as np
V, DT, T = 1.0, 0.05, 15.0 # ความเร็ว m/s ช่วงเวลา s เวลาจำลอง s
DELAY = 0.3 # คำสั่งเลี้ยวถึงล้อช้า 0.3 s
def track(Ld):
x, y, th = 0.0, 1.0, 0.0 # เริ่มห่างเส้นทาง y = 0 อยู่ 1.0 m
buf = [0.0] * int(round(DELAY / DT))
ys = []
for _ in range(int(T / DT)):
dx, dy = Ld, -y # จุดเป้าบนเส้นทาง ห่างไปข้างหน้า Ld ตามแนว x
alpha = np.arctan2(dy, dx) - th
buf.append(2 * np.sin(alpha) / np.hypot(dx, dy))
kappa = buf.pop(0)
th += V * kappa * DT
x += V * np.cos(th) * DT
y += V * np.sin(th) * DT
ys.append(y)
return np.array(ys)
for Ld in [0.5, 1.0, 1.5, 3.0]:
ys = track(Ld)
out = np.where(np.abs(ys) >= 0.05)[0]
settle = (out[-1] + 1) * DT
s = f"settles within ±5 cm by {settle:.1f} s" if settle < T else "does not settle in 15 s"
print(f"Ld={Ld:.1f} m {s} max overshoot {max(0, -ys.min())*100:.1f} cm")
Ld=0.5 m does not settle in 15 s max overshoot 24.9 cm
Ld=1.0 m settles within ±5 cm by 2.3 s max overshoot 1.8 cm
Ld=1.5 m settles within ±5 cm by 3.2 s max overshoot 2.7 cm
Ld=3.0 m settles within ±5 cm by 6.1 s max overshoot 3.8 cm
ถ้าไม่มีเวลาหน่วง ระยะมองหน้าสั้นจะเข้าเส้นทางเร็วที่สุด แต่เมื่อคำสั่งหน่วง 0.3 s ระยะ 0.5 m แกว่งไปมาไม่หยุด ส่วน 1.0 m เข้าแถบ ±5 cm เร็วที่สุด ระยะ 3.0 m นิ่งแต่ช้า การเลือกระยะมองหน้าจึงต้องคิดรวมกับความเร็วและเวลาหน่วงของระบบจริง ระบบหลายตัวปรับระยะมองหน้าตามความเร็ว
ปฏิบัติการประจำโมดูล
ปฏิบัติการ: วางแผนและติดตามเส้นทางในสนามจำลอง
- สร้างแผนที่สนามจำลองแถวแผงด้วยกรวยหรือแผ่นไม้ ขยายสิ่งกีดขวางตามความกว้างรถ
- หาเส้นทางด้วย RRT ตามตัวอย่างที่ 1 สิบครั้งด้วยเมล็ดสุ่มต่างกัน บันทึกความยาวและจำนวนโหนด เทียบกับ A* จาก UAT 314
- วัดเวลาหน่วงจริงของคำสั่งเลี้ยวตั้งแต่ส่งจนล้อเริ่มหมุน
- ให้รถติดตามเส้นทางด้วย pure pursuit ที่ระยะมองหน้า 3 ค่า วัดความคลาดด้านข้างจาก LiDAR หรือ RTK GNSS
- เลือกระยะมองหน้าที่ใช้งานและเขียนเหตุผลโดยอ้างผลวัด
ข้อผิดพลาดที่พบบ่อย
ระวัง
- ไม่ขยายสิ่งกีดขวางตามขนาดรถ จนเส้นทางชิดขอบเกินไป
- เชื่อผล RRT ครั้งเดียว ทั้งที่ผลขึ้นกับการสุ่ม
- ตั้งระยะมองหน้าสั้นโดยไม่คิดถึงเวลาหน่วง
- ให้ตัวติดตามแก้ปัญหาที่ตัววางแผนต้องแก้ เช่นหลบสิ่งกีดขวางใหม่
- ไม่จำกัดความเร็วในโค้งแคบ
สรุป
- RRT หาเส้นทางในพื้นที่ต่อเนื่องด้วยการสุ่มขยายต้นไม้ และควรตัดทางลัดภายหลัง
- pure pursuit คำนวณความโค้งจากจุดเป้าที่ห่างเท่าระยะมองหน้า
- ระยะมองหน้าต้องเลือกร่วมกับความเร็วและเวลาหน่วงของระบบ
- ตัววางแผนกับตัวติดตามทำงานต่างระดับและต่างความถี่
แบบฝึกตรวจความเข้าใจ
- RRT ขยายต้นไม้อย่างไรในแต่ละรอบ
- goal bias มีไว้เพื่ออะไร
- จุดเป้าอยู่ด้านข้าง 0.5 m ระยะมองหน้า 2 m ความโค้งเท่าใด
- ความโค้ง 0.25 1/m ที่ความเร็ว 2 m/s อัตราหมุนเท่าใด
- ทำไมระยะมองหน้าสั้นจึงแกว่งเมื่อมีเวลาหน่วง
เฉลย
- สุ่มจุด หาโหนดที่ใกล้ที่สุด ยื่นกิ่งไปทางจุดนั้นหนึ่งช่วง แล้วเพิ่มถ้าไม่ชน
- ให้ต้นไม้เข้าหาเป้าหมายเร็วขึ้น
- 1/m
- rad/s
- รถแก้ทิศแรงและเร็ว แต่คำสั่งถึงล้อช้า จึงเลยเส้นแล้วแก้กลับไปมา
สรุปสูตรสำคัญ
| ความโค้งของ pure pursuit | |
| อัตราหมุน |
แหล่งอ้างอิงหลัก
- LaValle, S. M. (1998). Rapidly-exploring random trees: A new tool for path planning (TR 98-11). Iowa State University. link
- Coulter, R. C. (1992). Implementation of the pure pursuit path tracking algorithm (Tech. Rep. CMU-RI-TR-92-01). Robotics Institute, Carnegie Mellon University. link
- Siegwart, R., Nourbakhsh, I. R., & Scaramuzza, D. (2011). Introduction to autonomous mobile robots (2nd ed.). MIT Press. link
- Lynch, K. M., & Park, F. C. (2017). Modern robotics: Mechanics, planning, and control. Cambridge University Press. link
อ่านเพิ่มเติม
ศึกษาหน่วยความรู้ที่กำหนดล่วงหน้า ดูสื่อประกอบ และทำ quiz ประจำโมดูล
ในชั้นเรียน / ภาคสนาม
ปฏิบัติการในห้องแล็บหรือภาคสนามตามใบงาน พร้อม checklist ความปลอดภัย
หลักฐานการเรียนรู้: ใบงานที่ผ่านการตรวจและผล quiz