MAVSDK-Python 프로그래밍 완벽 가이드 [제9편]: OFFBOARD 속도 제어 (BODY 및 NED 기준)
안녕하세요! 자율비행 로봇과 드론 제어 알고리즘 연구에 매진하고 계신 대학생 및 연구원 여러분. MAVSDK-Python 연재 블로그의 아홉 번째 시간에 오신 것을 환영합니다.
지난 **[제8편]**에서는 특정 좌표(NED)로 기체를 이동시키는 오프보드 위치 제어(Position Control)에 대해 알아보았습니다. 하지만 위치 제어만으로는 카메라 비전을 이용해 움직이는 물체를 실시간으로 추적하거나, 장애물을 부드럽게 회피하는 다이내믹한 기동을 구현하기에 한계가 있습니다.
이를 해결하기 위해 연구원들이 가장 많이 사용하는 제어 방식이 바로 **속도 제어(Velocity Control)**입니다. 드론에게 “목표 좌표”를 주는 대신, “현재 바라보는 방향에서 앞으로 5m/s 속도로 날아가!”라고 명령하는 방식이죠.
이번 **[제9편: OFFBOARD 속도 제어 (BODY 및 NED 기준)]**에서는 드론의 움직임을 훨씬 더 부드럽고 직관적으로 만들어주는 두 가지 속도 제어 방식(BODY 좌표계와 NED 좌표계)의 원리와 전체 코드를 아주 상세하고 친절하게 분석해 보겠습니다.
1. 속도 제어의 두 가지 기준계 (BODY vs NED)
코드를 작성하기 전, 속도를 지시할 때 기준이 되는 두 가지 좌표계를 명확히 구분해야 합니다. 연구 목적에 따라 사용할 프레임이 완전히 달라지기 때문입니다.
- BODY 기준 (FRD 좌표계): 드론 기체 자체를 기준으로 합니다. Forward(앞), Right(오른쪽), Down(아래)의 방향을 가집니다. 카메라가 드론 정면에 장착되어 있을 때, 화면 오른쪽에 목표물이 나타나면 단순히 “오른쪽으로 속도를 내라”고 명령할 수 있어 비전 기반 객체 추적 연구에 필수적입니다.
- NED 기준 (North-East-Down): 지구의 절대 방위(북/동/하)를 기준으로 합니다. 기체가 어느 방향을 보고 있든 무관하게 북쪽이나 동쪽으로 일정한 속도를 내어 이동합니다. 넓은 농경지를 그리드(Grid) 형태로 스캔하거나, 절대적인 방위 기준의 순찰 임무를 수행할 때 매우 유용합니다.
2. BODY 기준 속도 제어 완벽 이해 (offboard_velocity_body.py)
기체 기준(BODY)으로 속도를 제어할 때는 VelocityBodyYawspeed 클래스를 사용합니다. 이 클래스는 다음 4가지 매개변수를 받습니다.
forward_m_s: 앞으로 전진하는 속도 (m/s)right_m_s: 오른쪽으로 이동하는 속도 (m/s)down_m_s: 아래로 하강하는 속도 (m/s) (⚠️ 이륙하려면 마이너스 값 입력)yawspeed_deg_s: 기체의 기수(Nose)를 1초에 몇 도씩 회전시킬 것인가? (시계방향이 양수)
① 초기 설정점(Setpoint) 주입
오프보드 모드를 시작하기 전, 반드시 제어하려는 방식과 동일한 메서드(set_velocity_body)를 사용하여 설정점을 0으로 초기화해야 합니다.
print("-- Setting initial setpoint")
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, 0.0, 0.0, 0.0))
② 이륙 및 원주 비행(Circle Flight) 로직 분석
BODY 제어의 가장 큰 장점은 원주 비행을 매우 쉽게 구현할 수 있다는 점입니다.
# 1. 상승하며 시계방향 회전
print("-- Turn clock-wise and climb")
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, 0.0, -1.0, 60.0))
await asyncio.sleep(5)
위 코드는 전후좌우 이동 없이, 위로 1m/s의 속도로 상승(-1.0)하면서 초당 60도씩 시계방향으로 빠르게 회전하도록 명령합니다. 5초간 대기하므로 약 5m 상공까지 스크류를 그리며 올라갑니다.
# 2. 전진하며 회전 (원주 비행)
print("-- Fly a circle")
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(5.0, 0.0, 0.0, 30.0))
await asyncio.sleep(15)
이 코드는 기체 기준 앞으로 5m/s의 빠른 속도로 전진하면서 동시에 초당 30도씩 기수를 꺾습니다. 이 두 가지 힘이 결합되어 드론은 아름답고 커다란 원을 그리며 비행하게 됩니다.
# 3. 옆으로 이동하며 회전 (측면 원주 비행)
print("-- Fly a circle sideways")
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, -5.0, 0.0, 30.0))
await asyncio.sleep(15)
FRD 좌표계에서 right_m_s에 -5.0을 주었으므로 왼쪽 측면으로 5m/s로 미끄러지듯 이동하면서 회전합니다.
🚀 BODY 속도 제어 전체 코드
여러분의 PC에서 아래의 코드를 실행해 보십시오. Local Position NED 제어보다 기체의 움직임이 훨씬 부드러워진 것을 눈으로 확인할 수 있습니다.
#!/usr/bin/env python3
import asyncio
from mavsdk import System
from mavsdk.offboard import (OffboardError, VelocityBodyYawspeed)
async def run():
""" Does Offboard control using velocity body coordinates. """
drone = System()
await drone.connect(system_address="udp://:14540")
print("Waiting for drone to connect...")
async for state in drone.core.connection_state():
if state.is_connected:
print(f"-- Connected to drone!")
break
print("Waiting for drone to have a global position estimate...")
async for health in drone.telemetry.health():
if health.is_global_position_ok and health.is_home_position_ok:
print("-- Global position estimate OK")
break
print("-- Arming")
await drone.action.arm()
print("-- Setting initial setpoint")
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, 0.0, 0.0, 0.0))
print("-- Starting offboard")
try:
await drone.offboard.start()
except OffboardError as error:
print(f"Starting offboard mode failed with error code: {error._result.result}")
print("-- Disarming")
await drone.action.disarm()
return
print("-- Turn clock-wise and climb")
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, 0.0, -1.0, 60.0))
await asyncio.sleep(5)
print("-- Turn back anti-clockwise")
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, 0.0, 0.0, -60.0))
await asyncio.sleep(5)
print("-- Wait for a bit (Hold)")
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, 0.0, 0.0, 0.0))
await asyncio.sleep(2)
print("-- Fly a circle")
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(5.0, 0.0, 0.0, 30.0))
await asyncio.sleep(15)
print("-- Wait for a bit (Hold)")
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, 0.0, 0.0, 0.0))
await asyncio.sleep(5)
print("-- Fly a circle sideways")
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, -5.0, 0.0, 30.0))
await asyncio.sleep(15)
print("-- Wait for a bit (Hold)")
await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, 0.0, 0.0, 0.0))
await asyncio.sleep(8)
print("-- Stopping offboard")
try:
await drone.offboard.stop()
except OffboardError as error:
print(f"Stopping offboard mode failed with error code: {error._result.result}")
if __name__ == "__main__":
asyncio.run(run())

3. NED 기준 속도 제어 완벽 이해 (offboard_velocity_ned.py)
지구 절대 방위를 기준으로 하는 NED 속도 제어에는 VelocityNedYaw 클래스를 사용합니다. 이 클래스의 매개변수는 방향의 기준이 다릅니다.
north_m_s: 북쪽으로 향하는 속도 (m/s)east_m_s: 동쪽으로 향하는 속도 (m/s)down_m_s: 아래로 하강하는 속도 (m/s)yaw_deg: ⚠️ 주의! 여기서는 회전 ‘속도’가 아니라, 기수가 바라볼 **’절대 각도(도)’**입니다. (북:0, 동:90, 남:180, 서:270)
① 로직 분석 및 방향 독립성
print("-- Setting initial setpoint")
await drone.offboard.set_velocity_ned(VelocityNedYaw(0.0, 0.0, 0.0, 0.0))
마찬가지로 set_velocity_ned를 사용하여 진입 전 0으로 초기화를 해줍니다.
print("-- Go North 2 m/s, turn to face East")
await drone.offboard.set_velocity_ned(VelocityNedYaw(2.0, 0.0, 0.0, 90.0))
await asyncio.sleep(14)
이 코드가 보여주는 움직임은 매우 흥미롭습니다. 기체는 북쪽(north_m_s=2.0) 방향을 향해 2m/s의 속도로 이동하지만, 드론의 정면 기수(카메라)는 동쪽(90.0도)을 바라보게 됩니다. 즉, 옆으로 게걸음을 치며 북쪽으로 날아가는 모습을 연출합니다. 이는 방위와 상관없이 특정 건물이나 피사체를 계속 바라보며 이동해야 하는 촬영 임무나 감시 임무에서 압도적인 위력을 발휘합니다.
🚀 NED 속도 제어 전체 코드
아래 코드를 실행하면 기체가 고도 상승 후 북, 남, 서, 동 방향으로 일정하게 속도를 내며 이동하는 정밀한 방위 기반 제어를 확인할 수 있습니다.
#!/usr/bin/env python3
import asyncio
from mavsdk import System
from mavsdk.offboard import (OffboardError, VelocityNedYaw)
async def run():
""" Does Offboard control using velocity NED coordinates. """
drone = System()
await drone.connect(system_address="udp://:14540")
print("Waiting for drone to connect...")
async for state in drone.core.connection_state():
if state.is_connected:
print(f"-- Connected to drone!")
break
print("Waiting for drone to have a global position estimate...")
async for health in drone.telemetry.health():
if health.is_global_position_ok and health.is_home_position_ok:
print("-- Global position estimate OK")
break
print("-- Arming")
await drone.action.arm()
print("-- Setting initial setpoint")
await drone.offboard.set_velocity_ned(VelocityNedYaw(0.0, 0.0, 0.0, 0.0))
print("-- Starting offboard")
try:
await drone.offboard.start()
except OffboardError as error:
print(f"Starting offboard mode failed with error code: {error._result.result}")
print("-- Disarming")
await drone.action.disarm()
return
print("-- Go up 2 m/s")
await drone.offboard.set_velocity_ned(VelocityNedYaw(0.0, 0.0, -2.0, 0.0))
await asyncio.sleep(14)
print("-- Go North 2 m/s, turn to face East")
await drone.offboard.set_velocity_ned(VelocityNedYaw(2.0, 0.0, 0.0, 90.0))
await asyncio.sleep(14)
print("-- Go South 2 m/s, turn to face West")
await drone.offboard.set_velocity_ned(VelocityNedYaw(-2.0, 0.0, 0.0, 270.0))
await asyncio.sleep(14)
print("-- Go West 2 m/s, turn to face East")
await drone.offboard.set_velocity_ned(VelocityNedYaw(0.0, -2.0, 0.0, 90.0))
await asyncio.sleep(14)
print("-- Go East 2 m/s")
await drone.offboard.set_velocity_ned(VelocityNedYaw(0.0, 2.0, 0.0, 90.0))
await asyncio.sleep(14)
print("-- Turn to face South")
await drone.offboard.set_velocity_ned(VelocityNedYaw(0.0, 0.0, 0.0, 180.0))
await asyncio.sleep(12)
print("-- Go down 1 m/s, turn to face North")
await drone.offboard.set_velocity_ned(VelocityNedYaw(0.0, 0.0, 1.0, 0.0))
await asyncio.sleep(14)
print("-- Stopping offboard")
try:
await drone.offboard.stop()
except OffboardError as error:
print(f"Stopping offboard mode failed with error code: {error._result.result}")
if __name__ == "__main__":
asyncio.run(run())
<그림> 우분투 터미널의 파이썬 스크립트 실행 화면과, NED 속도 제어 명령을 받아 기수를 돌린 채 정해진 방위(북/남/서/동)로 십자(Cross) 형태의 이동 궤적을 그리는 SITL 3D 시뮬레이터 화면
마치며 및 연구를 위한 핵심 요약
이번 제9편에서는 연구 논문과 실전 자율비행 알고리즘 개발에서 가장 사랑받는 기능인 OFFBOARD 속도 제어에 대해 심층적으로 다루어 보았습니다.
연구원 여러분이 코드를 작성하실 때 꼭 기억해야 할 핵심 포인트는 다음과 같습니다.
- Z축 방향의 주의: 고도는 NED 기준에서 항상 마이너스(-) 값이 위쪽(상공)을 향한다는 점을 명심하십시오. 고도 상승을 위해
down_m_s에 양수를 넣으면 기체는 바닥으로 곤두박질칩니다! - 좌표계의 올바른 선택: 기체의 정면을 기준으로 하는 유연한 비행(회피, 비전 추적 등)이 필요할 땐
VelocityBodyYawspeed를, 지구의 방위 공간을 기준으로 삼아야 할 땐VelocityNedYaw를 선택하십시오. - 초기 설정점 주의:
start()명령을 내리기 전, 여러분이 선택한 방식에 맞는 메서드(set_velocity_body또는set_velocity_ned)로 반드시 0점 초기화를 해주어야 Offboard 진입 에러를 방지할 수 있습니다.
속도 제어 코드를 통해 부드러운 비행을 경험하셨다면, 다음 단계는 이 위치 제어와 속도 제어를 융합하여 더욱 빠르고 정밀하게 목표에 도달하는 기법입니다. 다음 **[제10편]**에서는 고급 제어 공학의 꽃이라 불리는 **’OFFBOARD 위치, 속도 제어 (피드포워드, Feed-Forward)’**와 이를 응용한 **’키보드 수동 기체 제어’**에 대해 알아보겠습니다.
실행 중 궁금한 점이 생기거나 버그를 마주하셨다면 망설이지 말고 댓글을 남겨주세요. 여러분의 눈부신 연구 성과와 안전한 비행을 응원합니다! 감사합니다.

Author: maponarooo, CEO of QUAD Drone Lab
Date: March 5, 2026
