[ROS2 GPS, IMU 항법 코드] ROS2 기반 GPS + IMU 항법 시스템 구현

2025. 6. 4. 23:50·ROS2

개요

이 글에서는 ROS 2 기반 시스템에서 GPS(Global Positioning System)와 IMU(Inertial Measurement Unit) 센서 데이터를 활용하여 선박 또는 로봇의 위치 및 자세 정보를 계산하고 항법 메시지로 변환하는 과정을 다룹니다.

 

사용 목적

  • GPS와 IMU 센서를 기반으로 자율운항 선박이나 로봇의 실시간 위치 및 자세 정보를 추정
  • 추정된 정보를 바탕으로 Guidance & Control 알고리즘에 필요한 데이터를 생성

이론 설명

1. GPS 좌표계를 UTM 좌표계로 변환하는 이유

GPS 좌표계의 한계

  • GPS는 WGS84 기반의 위도(latitude), 경도(longitude) 정보를 제공합니다.
  • 이는 구면 좌표계 기반이므로 거리 계산, 상대 위치 추정에 어려움이 있습니다.

UTM 좌표계의 장점

  • UTM (Universal Transverse Mercator) 좌표계는 지구를 60개의 존(Zone)으로 나누고, 각 존에서 2차원 직교 좌표계 (X, Y) 로 변환합니다.
  • 이를 통해 미터 단위의 거리, 상대 위치 계산이 용이해집니다.
  • 예: "현재 위치가 기준 위치에서 동쪽으로 24.5m, 북쪽으로 17.2m 떨어져 있음"과 같은 계산이 가능

 따라서 GPS로 받은 위도/경도 값을 UTM으로 변환하면, 로컬 좌표계를 기준으로 상대 위치를 쉽게 계산할 수 있습니다.

 

2. IMU 센서의 역할

IMU는 3축 가속도계와 3축 자이로스코프를 포함한 센서입니다.

주요 데이터

  • orientation: 자세 (쿼터니언 형태)
  • angular velocity: 회전 속도 (rad/s)
  • linear acceleration: 가속도 (optional)

사용 이유

  • GPS는 위치는 정확하지만 자세나 회전 정보를 제공하지 않습니다.
  • IMU는 실시간 회전 각도(yaw)와 회전 속도(r)를 추정할 수 있어, GPS를 보완합니다.

항법 시스템에서 IMU는 선박의 방향(ψ), 회전 속도(r), 가속도 등 자세 정보를 제공합니다.

 


코드 기반 설명

 

 이제 위의 이론을 바탕으로 실제 ROS 2 C++ 코드 구현 내용을 살펴보겠습니다. 아래에 소개할 코드는 GitHub 저장소 macro_gnc에서 확인할 수 있으며, 항법 데이터를 생성하는 핵심 모듈 중 하나인 gps_navigation.cpp 파일에 해당합니다. 이 코드는 ROS 2 노드로 동작하며, GPS 및 IMU 센서로부터 받은 데이터를 바탕으로 선박 혹은 무인이동체의 위치 및 자세 정보를 실시간으로 계산하여 퍼블리시합니다.

 

GitHub - lidarmansiwon/macro_gnc

Contribute to lidarmansiwon/macro_gnc development by creating an account on GitHub.

github.com

 

 특히 속도 계산 부분에는 navigation/tool/velocity_calculator.hpp 파일이 사용됩니다. 이 모듈은 이동체의 위치 및 자세 변화율로부터 선속(u), 횡속(v), 그리고 요레이크(r)를 계산하며, 저역통과 필터(Low-Pass Filter, LPF)를 적용하여 노이즈가 많은 센서 데이터로부터 안정적인 속도 추정치를 제공합니다. 이 필터 계수는 파라미터로 조정할 수 있어 상황에 맞는 튜닝이 가능합니다.

이제 gps_navigation.cpp 코드의 주요 구성 요소와 각 기능에 대해 자세히 살펴보겠습니다.

초기화 및 파라미터 설정

this->declare_parameter<std::string>("imu_topic", "/imu_topic");
this->declare_parameter<std::string>("gps_topic", "/gps_topic");
this->declare_parameter<std::string>("navigation_topic", "/navigation_data");
  • 사용자 정의 토픽 이름을 파라미터로 설정합니다.
  • reference_gps_latitude, reference_gps_longitude는 상대 좌표의 기준점입니다.

 

Parameter 값으로 전달 받은 filter 강도 전달

  double velocity_alpha = this->get_parameter("velocity_alpha").as_double();
  vel_calc_ = VelocityCalculator(velocity_alpha);

 

IMU 데이터를 활용한 자세 계산

geometry_msgs::msg::Quaternion quat = imu_data_->orientation;
std::array<double, 3> rpy = euler_from_quaternion({quat.x, quat.y, quat.z, quat.w});
double yaw = rpy[2];
  • 쿼터니언 → 롤/피치/요(roll, pitch, yaw) 변환
  • 항법에서 가장 중요한 건 yaw (ψ), 즉 선박의 방향입니다.

Pub, Sub 객체 정의 및 timer 정의

  // 생성자에서 시작 표시.
  RCLCPP_INFO(this->get_logger(), "Run GPS Navigation");

  imu_subscription_ = this->create_subscription<sensor_msgs::msg::Imu>(
    imu_topic, rclcpp::SensorDataQoS(), std::bind(&GPSNavigation::imu_callback, this, _1));

  gps_subscription_ = this->create_subscription<sensor_msgs::msg::NavSatFix>(
    gps_topic, rclcpp::SensorDataQoS(), std::bind(&GPSNavigation::gps_callback, this, _1));

  navigation_publisher_ = this->create_publisher<mk3_msgs::msg::NavigationType>(
    navigation_topic, 10);

  timer_ = this->create_wall_timer(50ms, std::bind(&GPSNavigation::process, this));
  •  생성자에서 노드를 처음 시작할 때, RCLCPP_INFO 로그 기능을 통하여 시작을 알립니다.
  •  전달 받을 센서 GPS, IMU에 대한 Subscribe 코드와 최종 계산된 항법 데이터를 Publish 할 코드를 작성합니다. 
  • timer를 통하여 50ms(20Hz) 간격으로 함수를 실행합니다. 

GPSNavigation::process() 함수 상세 설명

1. 센서 데이터 유효성 검사

if (!gps_data_ || !imu_data_)
{
  RCLCPP_WARN_THROTTLE(this->get_logger(), *this->get_clock(), 3000, "GPS or IMU data not received yet.");
  return;
}
  • 목적: GPS 또는 IMU 데이터가 아직 수신되지 않은 경우 경고 메시지를 출력하고 함수를 종료합니다.
  • 기능: ROS 2에서는 센서 콜백이 비동기로 도착하므로, 해당 데이터가 nullptr인지 확인하여 안정성을 확보합니다.
  • 기능 강조: RCLCPP_WARN_THROTTLE은 3초에 한 번씩만 경고를 출력하여 로그 과부하를 방지합니다.

2. GPS 좌표 → UTM 좌표 변환

double latitude = gps_data_->latitude;
double longitude = gps_data_->longitude;
...
GeographicLib::UTMUPS::Forward(latitude, longitude, zone, northp, utm_x, utm_y);
  • 목적: 위도(latitude)와 경도(longitude)를 UTM 평면좌표계로 변환합니다.
  • 사용 라이브러리: GeographicLib의 UTMUPS::Forward 함수
  • 입력값 : latitude, longitude
  • 출력값 :
    • utm_x, utm_y: 변환된 평면 좌표
    • zone: UTM 존 (6도 간격)
    • northp: 북반구 여부 (남반구일 경우 UTM 보정 방식이 다름)
RCLCPP_INFO(this->get_logger(), "Converted GPS (%.8f, %.8f) → UTM (%.2f, %.2f), Zone: %d, Northp: %d\n", latitude, longitude, utm_x, utm_y, zone, northp);

 

디버깅 정보 출력: 실제 GPS 좌표가 어떻게 변환되었는지 로그에 출력하여 추적 가능합니다.

 

3. 기준점 설정 및 상대 좌표 계산

if (!origin_set_) {
    ...
    GeographicLib::UTMUPS::Forward(reference_gps_latitude_, reference_gps_longitude_, reference_zone, reference_northp, reference_utm_x, reference_utm_y);
    ...
    origin_set_ = true;
}
  • 목적: 기준 GPS 좌표(reference)를 최초 한 번만 UTM으로 변환하여 고정된 원점으로 사용합니다.
  • 기능 설명:
    • origin_set_ 플래그는 기준점이 한 번만 설정되도록 합니다.
    • 일반적으로 로컬 좌표계를 만들기 위한 원점 설정에 해당합니다.
double relative_x = utm_x - reference_utm_x;
double relative_y = utm_y - reference_utm_y;
  • 목적: UTM 좌표계 상에서 기준점으로부터의 상대적인 위치를 계산합니다.
  • 결과: 로컬 UTM 좌표 (x, y) 생성

4. 자세 정보 추출 (IMU → Roll, Pitch, Yaw)

geometry_msgs::msg::Quaternion quat = imu_data_->orientation;
std::array<double, 3> rpy = euler_from_quaternion({quat.x, quat.y, quat.z, quat.w});
double roll = rpy[0], pitch = rpy[1], yaw = rpy[2];
  • 목적: IMU로부터 쿼터니언(quaternion) 형태의 자세 정보를 받아서 오일러각(Roll, Pitch, Yaw)으로 변환합니다.
  • 사용 함수: euler_from_quaternion() (사용자 정의 함수로 추정)
  • 중요한 값: yaw는 선박의 방향(heading)을 의미하는 각도로, 이후 속도 계산에 활용됩니다.

5. 속도 계산 (velocity_calculator 사용)

auto result = vel_calc_.update(x, y, psi, time_sec);
double LPFVel_x = result.LPFVel_x;
double LPFVel_y = result.LPFVel_y;
double angular_velocity = result.angular_velocity;
  • 목적: 위치 변화와 자세 변화량을 기반으로 선속(u), 횡속(v), 회전 속도(r)를 계산합니다.
  • 사용 클래스: velocity_calculator.hpp에 정의된 VelocityCalculator 클래스
  • 핵심 알고리즘:
    • 이전 상태와 현재 상태 간 시간차(dt)를 사용하여 속도를 계산
    • 저역통과필터(LPF)를 통해 노이즈 제거 및 평활화 처리
  • 출력 항목:
    • LPFVel_x: 선속 (u)
    • LPFVel_y: 횡속 (v)
    • angular_velocity: 회전 속도 (r)

6. 최종 메시지 생성 및 퍼블리시

NavigationType nav_msg;
nav_msg.x = x;
nav_msg.y = y;
nav_msg.psi = psi * 180.0 / M_PI;
nav_msg.u = LPFVel_x;
nav_msg.v = LPFVel_y;
nav_msg.r = angular_velocity;
nav_msg.w = imu_data_->angular_velocity.z;

navigation_publisher_->publish(nav_msg);
  • 목적: 계산된 위치, 자세, 속도를 NavigationType 메시지 형태로 퍼블리시
  • 메시지 필드 설명:
    • x, y: 로컬 UTM 좌표계 기준 상대 위치
    • psi: 선박의 방향각 (라디안 → 도 단위 변환)
    • u, v, r: 각각 선속, 횡속, 요 회전 속도
    • w: IMU에서 측정된 z축 각속도 (원시 데이터)

종합 요약

이 함수는 GPS와 IMU 데이터를 융합하여 이동체의 현재 위치, 자세, 속도 정보를 계산한 후, 이를 다른 노드에서 사용할 수 있도록 퍼블리시하는 핵심적인 항법 처리 로직입니다. 

 

velocity_calculator.hpp 설명

1. 목적

velocity_calculator.hpp는 위치 및 자세 정보의 시간 변화율을 통해 속도를 계산하기 위한 유틸리티 클래스입니다.
ROS 2 시스템에서 GPS와 IMU를 기반으로 수집된 위치(x, y) 및 방향(psi: 요각)을 기반으로 선속(u), 횡속(v), 각속도(r)를 추정합니다.

 

2. 핵심 원리: 수치 미분(Numerical Differentiation)

dx = x - prev_x_;
dy = y - prev_y_;
dpsi = normalize_angle(psi - prev_psi_);
dt = current_time - prev_time_;
  • 고전적인 1차 수치 미분법(forward difference)을 이용해 속도를 추정합니다.​
  • 이를 바탕으로 선속(u), 횡속(v), 회전속도(r)를 계산:
    • u = (dx * cos(psi) + dy * sin(psi)) / dt
    • v = (-dx * sin(psi) + dy * cos(psi)) / dt
    • r = dpsi / dt

이 방식은 구현이 간단하고 실시간으로 사용하기 적합하나, 센서 노이즈에 민감하다는 단점이 존재합니다.

 

3. 노이즈 보완: 저역통과 필터(Low-Pass Filter)

LPFVel_x = LPF_alpha * vel_x + (1 - LPF_alpha) * LPFVel_x;
LPFVel_y = LPF_alpha * vel_y + (1 - LPF_alpha) * LPFVel_y;

고주파 노이즈(잡음)를 줄이기 위해 저역통과 필터(LPF)를 사용

LPF_alpha는 필터 계수로, 0.0~1.0 사이의 값입니다.

  • 1에 가까울수록 새 데이터 반영률이 높고,
  • 0에 가까울수록 이전 데이터 반영률이 높아져 더 부드럽지만 반응이 느려집니다.

 

4. 한계점: 미분 기반 속도 추정의 문제점

  • GPS나 위치 센서는 노이즈가 포함된 데이터를 제공합니다.
  • 수치 미분은 노이즈를 증폭시킵니다.
    • 데이터가 순간적으로 튈 경우, 계산된 속도 값도 크게 튈 수 있습니다.
    • dt가 작을수록 더 민감해지므로 주의가 필요합니다.
  • LPF만으로는 동적 환경 변화에 적응하거나 예측이 불가능합니다.

 

5. 개선 방향: 칼만 필터(Kalman Filter)를 활용한 보정

많은 연구자들은 속도 추정을 더 정밀하게 하기 위해 Kalman Filter (KF) 를 적용합니다.

Kalman Filter 기본 개념:

  • 센서 노이즈를 모델링하고, 현재 상태(위치, 속도 등)를 추정하는 확률 기반 필터
  • 예측(Predict) 단계 + 관측 업데이트(Update) 단계를 반복
  • 시간에 따라 누적되는 오차를 수학적으로 최소화

항법 요소 데이터 출처 설명

위치 (x, y) GPS (UTM 변환 후) 기준 GPS 대비 상대 위치
자세 (ψ) IMU 방향 추정 (yaw)
선형 속도 (u, v) 위치 변화량 기반 LPF 적용
각속도 (r, w) 위치 변화량 + IMU yaw 변화량 기반 또는 자이로 직접 측정

 

참고 사항

  • UTM 변환에는 GeographicLib 라이브러리를 사용
  • IMU 오차와 GPS 신호 손실 등을 고려해 추가 필터 (예: 칼만 필터) 적용 가능
  • 본 코드는 ROS 2 Humble 기준으로 작성됨

 

전체 코드

코드 저장소: MACRO_GNC

 

GitHub - lidarmansiwon/macro_gnc

Contribute to lidarmansiwon/macro_gnc development by creating an account on GitHub.

github.com

 

[gps_navigation.cpp 전체 코드]

#include <iostream>
#include <memory>
// C++ Standard lib 
#include <chrono>
#include <functional>
#include <string>
#include "rclcpp/rclcpp.hpp"

#include <GeographicLib/UTMUPS.hpp>

#include "std_msgs/msg/string.hpp"
#include "mk3_msgs/msg/navigation_type.hpp"
#include "sensor_msgs/msg/imu.hpp"
#include "sensor_msgs/msg/nav_sat_fix.hpp"

#include "navigation/gps_navigation.hpp"
#include "navigation/tool/quaternion_utils.hpp"
#include "navigation/tool/velocity_calculator.hpp"


using std::placeholders::_1;
using namespace std::chrono_literals;

GPSNavigation::GPSNavigation(const rclcpp::NodeOptions & node_options)
: Node("gps_navigation", node_options), 
  reference_gps_latitude_(0.0),
  reference_gps_longitude_(0.0),
  reference_utm_x(0.0),
  reference_utm_y(0.0),
  reference_zone(0.0),
  reference_northp(false),
  vel_calc_(0.9)
{ 
  this->declare_parameter<std::string>("imu_topic", "/imu_topic");
  this->declare_parameter<std::string>("gps_topic", "/gps_topic");
  this->declare_parameter<std::string>("navigation_topic", "/navigation_data");

  this->declare_parameter<double>("reference_gps_latitude", 0.0);
  this->declare_parameter<double>("reference_gps_longitude", 0.0);
  this->declare_parameter<double>("velocity_alpha", 0.9);

  std::string imu_topic = this->get_parameter("imu_topic").as_string();
  std::string gps_topic = this->get_parameter("gps_topic").as_string();
  std::string navigation_topic = this->get_parameter("navigation_topic").as_string();

  reference_gps_latitude_  = this->get_parameter("reference_gps_latitude").as_double();
  reference_gps_longitude_ = this->get_parameter("reference_gps_longitude").as_double();
  double velocity_alpha = this->get_parameter("velocity_alpha").as_double();
  vel_calc_ = VelocityCalculator(velocity_alpha);

  // 생성자에서 시작 표시.
  RCLCPP_INFO(this->get_logger(), "Run GPS Navigation");

  imu_subscription_ = this->create_subscription<sensor_msgs::msg::Imu>(
    imu_topic, rclcpp::SensorDataQoS(), std::bind(&GPSNavigation::imu_callback, this, _1));

  gps_subscription_ = this->create_subscription<sensor_msgs::msg::NavSatFix>(
    gps_topic, rclcpp::SensorDataQoS(), std::bind(&GPSNavigation::gps_callback, this, _1));

  navigation_publisher_ = this->create_publisher<mk3_msgs::msg::NavigationType>(
    navigation_topic, 10);

  timer_ = this->create_wall_timer(50ms, std::bind(&GPSNavigation::process, this));
}

GPSNavigation::~GPSNavigation()
{

}

void GPSNavigation::imu_callback(const sensor_msgs::msg::Imu::SharedPtr msg){imu_data_ = msg;}
void GPSNavigation::gps_callback(const sensor_msgs::msg::NavSatFix::SharedPtr msg){gps_data_ = msg;}

void GPSNavigation::process()
{
  if (!gps_data_ || !imu_data_)
  {
    RCLCPP_WARN_THROTTLE(this->get_logger(), *this->get_clock(), 3000, "GPS or IMU data not received yet.");
    return;
  }

  double latitude = gps_data_->latitude;
  double longitude = gps_data_->longitude;

  double utm_x, utm_y;
  int zone;
  bool northp;

  try {
    // 입력: latitude, longitude, 출력: zone(UTM 존), northp(북반구, 남반구 여부), utm_x, utm_y
    GeographicLib::UTMUPS::Forward(latitude, longitude, zone, northp, utm_x, utm_y);
    RCLCPP_INFO(this->get_logger(), "Converted GPS (%.8f, %.8f) → UTM (%.2f, %.2f), Zone: %d, Northp: %d\n", latitude, longitude, utm_x, utm_y, zone, northp);
  } catch (const std::exception &e) {
    RCLCPP_ERROR(this->get_logger(), "UTM conversion failed: %s", e.what());
    return;
  }

  if (!origin_set_) {

    try {
        GeographicLib::UTMUPS::Forward(reference_gps_latitude_, reference_gps_longitude_, reference_zone, reference_northp, reference_utm_x, reference_utm_y);
        RCLCPP_INFO(this->get_logger(), "Converted Reference GPS (%.8f, %.8f) → Reference UTM (%.2f, %.2f), Zone: %d, Northp: %d\n", 
        reference_gps_latitude_, reference_gps_longitude_, reference_utm_x, reference_utm_y, reference_zone, reference_northp);
    } catch (const std::exception &e) {
        RCLCPP_ERROR(this->get_logger(), "Reference UTM conversion failed: %s", e.what());
        return;
    }
    origin_set_ = true;
  }

  double relative_x = utm_x - reference_utm_x;
  double relative_y = utm_y - reference_utm_y;


  RCLCPP_INFO(this->get_logger(), "Relative position: x=%.2f, y=%.2f\n\n", relative_x, relative_y);

  geometry_msgs::msg::Quaternion quat;

  quat = imu_data_->orientation;

  std::array<double, 3> rpy = euler_from_quaternion(
    {quat.x, quat.y, quat.z, quat.w}
  );

  double roll = rpy[0];
  double pitch = rpy[1];
  double yaw = rpy[2];

  double x = relative_x;
  double y = relative_y;
  double psi = yaw;

  rclcpp::Time current_time = this->now();
  double time_sec = current_time.seconds();

  auto result = vel_calc_.update(x, y, psi, time_sec);
  double LPFVel_x = result.LPFVel_x;
  double LPFVel_y = result.LPFVel_y;
  double angular_velocity = result.angular_velocity;

  NavigationType nav_msg;
  nav_msg.x = x;
  nav_msg.y = y;
  nav_msg.psi = psi * 180.0 / M_PI;
  nav_msg.u = LPFVel_x;
  nav_msg.v = LPFVel_y;
  nav_msg.r = angular_velocity;
  nav_msg.w = imu_data_->angular_velocity.z;

  navigation_publisher_->publish(nav_msg);
}

 

[velocity_calculator.hpp 전체 코드]

#ifndef VELOCITY_CALCULATOR_HPP
#define VELOCITY_CALCULATOR_HPP

#include <cmath>
#include <chrono>

class VelocityCalculator
{
public:
  VelocityCalculator(double alpha = 0.9)
    : alpha_(alpha), prev_initialized_(false), LPFVel_x_(0.0), LPFVel_y_(0.0) {}

  struct VelocityResult {
    double vx;
    double vy;
    double angular_velocity;
    double LPFVel_x;
    double LPFVel_y;
  };

  VelocityResult update(double x, double y, double psi, double current_time)
  {
    if (!prev_initialized_) {
      prev_x_ = x;
      prev_y_ = y;
      prev_psi_ = psi;
      prev_time_ = current_time;
      prev_initialized_ = true;
      return {0.0, 0.0, 0.0, 0.0, 0.0};
    }

    // double dt = current_time - prev_time_;
    // if (dt <= 0.0) {
    //   return {0.0, 0.0, 0.0, LPFVel_x_, LPFVel_y_};
    // }
    double dt = 0.05;

    double dx = x - prev_x_;
    double dy = y - prev_y_;
    double dpsi = psi - prev_psi_;

    double vx_fixed = dx / dt;
    double vy_fixed = dy / dt;

    double cos_psi = std::cos(psi);
    double sin_psi = std::sin(psi);

    double vx = vx_fixed * cos_psi + vy_fixed * sin_psi;
    double vy = -vx_fixed * sin_psi + vy_fixed * cos_psi;

    double angular_velocity = dpsi / dt;

    // Low-pass filter
    LPFVel_x_ = alpha_ * LPFVel_x_ + (1 - alpha_) * vx;
    LPFVel_y_ = alpha_ * LPFVel_y_ + (1 - alpha_) * vy;

    // Save current state
    prev_x_ = x;
    prev_y_ = y;
    prev_psi_ = psi;
    prev_time_ = current_time;

    return {vx, vy, angular_velocity, LPFVel_x_, LPFVel_y_};
  }

private:
  double alpha_;
  bool prev_initialized_;
  double prev_x_, prev_y_, prev_psi_, prev_time_;
  double LPFVel_x_, LPFVel_y_;
};

#endif  // VELOCITY_CALCULATOR_HPP

 

'ROS2' 카테고리의 다른 글

[ROS2 Multi Thread] Multi Thread의 필요성  (0) 2025.05.31
'ROS2' 카테고리의 다른 글
  • [ROS2 Multi Thread] Multi Thread의 필요성
lidarmansiwon
lidarmansiwon
lidarmansiwon 님의 블로그 입니다.
  • lidarmansiwon
    라이다맨 시원의 연구개발 라이프
    lidarmansiwon
  • 전체
    오늘
    어제
    • 분류 전체보기 (12)
      • 이론 정리 (2)
        • Thor I. Fossen 리뷰 (1)
      • Ubuntu 및 Linux (0)
        • Trouble shooting (0)
      • 개발 언어 (5)
        • C++ (5)
        • Python (0)
      • 논문 리뷰 (3)
      • ROS2 (2)
  • 블로그 메뉴

    • Github
    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    ROS2
    usv formation
    navigationcontrol
    cpp
    Sliding mode control
    maritimerobotics
    슬라이딩 모드 컨트롤
    이접안
    multithreadedexecutor
    C++
    marinecraft
    fossen
    c++ 기초부터 심화까지
    실행 흐름 제어
    singlethreadedexecutor
    motioncontrol
    do it! c++ 완전 정복
    usv formation path planning based on behavior trees and fast marching method
    자율선박
    해양공학
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
lidarmansiwon
[ROS2 GPS, IMU 항법 코드] ROS2 기반 GPS + IMU 항법 시스템 구현
상단으로

티스토리툴바