# 無人機模擬環境與 ORB-SLAM3、YOLO 偵測技術文件 & 實體飛行測試文件 P.S. 程式與工研院筆電版本相同 ## 目錄 - [1. Docker 使用](#1-Docker) - [2. 模擬無人機專案建置](#2-模擬無人機專案建置) - [3. ORB-SLAM3 指令與流程](#3-ORB-SLAM3指令與流程) - [4. YOLO 指令與流程](#4-YOLO指令與流程) - [5. EVO軌跡評估工具](#5-EVO軌跡評估工具) - [6. ROS 常用除錯工具與技巧](#6-ROS常用除錯工具與技巧) - [7. 實體飛行無人機參數設定](#7-實體無人機參數設定) --- ## 1. Docker Docker 是一種開源軟體平台,其核心理念是「Build and Ship any Application Anywhere」,旨在讓開發者能在一個輕量、可攜的虛擬化環境中,輕鬆地開發、部署及管理任何應用程式。對於像無人機這樣涉及複雜軟體(如 ROS、SLAM 演算法、AI 模型)的專案,Docker 提供了標準化且可重現的解決方案。 ### 本專案的雙容器架構 為了實現模組化,本專案將採用**雙容器**的架構。不同的核心功能將被拆分到兩個獨立的容器中,它們之間透過 ROS (Robot Operating System) 的網路通訊機制(Topics, Services)進行溝通。 p.s. 請使用ubuntu 2204 以下的版本 #### Container 1: `rtabmap_drone_example` (模擬與導航核心) - **功能與內容**: - **模擬環境**: 內建 **Gazebo** 模擬器,提供一個虛擬世界讓無人機飛行與互動。 - **飛行控制**: 包含 **PX4-SITL** (Software in the Loop),模擬無人機的飛行控制器。 - **通訊橋梁**: 透過 **MAVROS** 將 ROS 指令轉換為 PX4 能理解的 MAVLink 訊息。 - **基礎導航**: 整合 **RTAB-Map** 作為視覺 SLAM 引擎,並搭配 **`move_base`** 導航堆疊,實現基本的自主路徑規劃與避障。 #### Container 2: `ORB-SLAM3 & YOLO detection` (進階感知與定位) - **功能與內容**: - **高精度 SLAM**: 搭載 **ORB-SLAM3** 函式庫。ORB-SLAM3 是一個功能強大的視覺慣性 SLAM 系統,能提供比 RTAB-Map 更高精度的定位與建圖能力。將其獨立打包成容器是常見作法,因為其依賴複雜且版本穩定,便於重複使用。 - **物件偵測**: 整合 **YOLO** 模型。此模型會接收來自模擬環境的影像串流,進行即時物件偵測,並將偵測結果(如物件類別、深度)透過 ROS Topic 發佈出去。 ### 啟動前的準備 (X11 Forwarding) 為了讓容器內的圖形化介面應用(如 Gazebo, RViz)能顯示在你的主機螢幕上,需要在本機先執行以下指令授權 X server 連線: ``` xhost +local:root XAUTH=/tmp/.docker.xauth ## 可以試試看 touch $XAUTH xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge - ``` ### Docker 啟動指令 (以 NVIDIA GPU 版本為例) ``` #ubuntu 建議22.04以下 不然rviz需要使用到的OpenGL會有衝突 docker run -it \ --name="<給容器取個名字>" \ --privileged \ --net=host \ --gpus=all \ --env="DISPLAY=$DISPLAY" \ --env="QT_X11_NO_MITSHM=1" \ --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" \ --env="XAUTHORITY=$XAUTH" \ --volume="$XAUTH:$XAUTH" \ --volume="<你的本機路徑>:<容器內對應路徑>" \ <你的映像檔名稱> \ /bin/bash (進入bash) ``` ## 2. 模擬無人機專案建置 ### 專案概述 本專案 `rtabmap_drone_example` 提供一個完整的範例,展示如何使用 `move_base`、`mavros/px4` 以及 `RTAB-Map` 視覺 SLAM 技術,在模擬環境中實現無人機的自主導航。專案的 GitHub 儲存庫與詳細安裝教學請參考:[rtabmap_drone_example](https://github.com/matlabbe/rtabmap_drone_example)。 <figure style="text-align: center;"> <img src="https://hackmd.io/_uploads/rJCEAwVNge.png" alt="模擬環境展示圖"> <figcaption style="font-style: italic;">圖1:rtabmap_drone_example 專案於 Gazebo 中的視覺化介面</figcaption> </figure> ### 核心組件與執行流程 要完整運行此模擬專案,需在**四個獨立的終端機**中依序啟動以下核心節點: 1. **啟動 Gazebo 模擬環境** ``` roslaunch rtabmap_drone_example gazebo.launch ``` > **作用**:此指令會啟動 Gazebo 模擬器,載入預設的世界地圖,並在其中生成一台 IRIS 四旋翼無人機模型。此模型已搭載模擬的攝影機與 IMU 感測器。 2. **啟動 RTAB-Map SLAM** ``` roslaunch rtabmap_drone_example slam.launch ``` > **作用**:啟動 `rtabmap` 節點,進入 SLAM 模式。它會訂閱來自 Gazebo 無人機的影像與 IMU 數據,即時進行同步定位與建圖 (Simultaneous Localization and Mapping),並發布地圖 (`/map`)、無人機位置 (`tf`) 與里程計 (`/rtabmap/odom`) 等資訊。 3. **啟動 RVIZ 視覺化介面** ``` roslaunch rtabmap_drone_example rviz.launch ``` > **作用**:啟動 RVIZ,並載入預設的設定檔。在此介面中,你可以看到 RTAB-Map 所建立的 2D/3D 地圖、無人機的即時位置,並能透過 **"2D Nav Goal"** 工具下達自主導航的目標點。 4. **啟動飛行控制節點** ``` # 執行 C++ 版本 rosrun rtabmap_drone_example offboard # 改寫的 Python 版本 rosrun rtabmap_drone_example offboard_python.py ``` > **作用**:它負責透過 MAVROS 與 PX4 飛行控制器溝通,將無人機切換至 `OFFBOARD` 模式,並執行解鎖與自動起飛。原專案提供的是 C++ 版本,但我們也可以使用 Python 版本進行替換。 <details> <summary>可以用python改寫起飛程式碼</summary> ``` #!/usr/bin/env python3 # coding: utf-8 import rospy import tf2_ros import tf2_geometry_msgs from geometry_msgs.msg import PoseStamped, Twist from mavros_msgs.msg import State, PositionTarget from mavros_msgs.srv import CommandBool, CommandBoolRequest, CommandLong, CommandLongRequest, SetMode, SetModeRequest import math # 定義不同控制模式的 type_mask(MAVROS 規範) VELOCITY2D_CONTROL = 0b011111000011 # 僅控制 X, Y 速度 VELOCITY_CONTROL = 0b011111000111 # 控制 X, Y, Z 速度 POSITION_CONTROL = 0b101111111000 # 控制位置 velocity_mask = VELOCITY2D_CONTROL # 預設為 2D 速度控制 def state_cb(msg): # 訂閱 mavros/state,取得無人機目前狀態(連線、解鎖、模式等) global current_state current_state = msg def twist_cb(msg): # 訂閱 /cmd_vel,接收速度指令,切換為速度控制模式 global current_goal, lastTwistReceived, velocity_mask if current_goal.type_mask == POSITION_CONTROL: rospy.loginfo("Switch to velocity control") current_goal.coordinate_frame = PositionTarget.FRAME_BODY_NED current_goal.type_mask = velocity_mask # 設定速度指令 current_goal.velocity.x = msg.linear.x current_goal.velocity.y = msg.linear.y current_goal.velocity.z = 0 if velocity_mask == VELOCITY2D_CONTROL else msg.linear.z current_goal.position.z = 1.5 current_goal.yaw_rate = msg.angular.z lastTwistReceived = rospy.Time.now() def get_yaw_from_quaternion(q): # 從四元數計算出 yaw 角度 siny_cosp = 2 * (q.w * q.z + q.x * q.y) cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z) return math.atan2(siny_cosp, cosy_cosp) # === 新增:目標點指令相關 === target_received = False # 是否收到目標點指令 target_pose = None # 目標點 Pose def my_target_cb(msg): # 訂閱 /my_target_point,收到目標點指令時觸發 global target_received, target_pose target_pose = msg target_received = True rospy.loginfo("收到新的目標點指令: x=%.2f, y=%.2f, z=%.2f" % (msg.pose.position.x, msg.pose.position.y, msg.pose.position.z)) if __name__ == "__main__": # 初始化 ROS 節點 rospy.init_node("offboard_node") # 初始化控制目標與狀態 current_goal = PositionTarget() current_state = State() lastTwistReceived = rospy.Time.now() current_pose = PoseStamped() # 訂閱無人機狀態 state_sub = rospy.Subscriber("mavros/state", State, state_cb) # 訂閱速度指令 twist_sub = rospy.Subscriber("/cmd_vel", Twist, twist_cb) # 訂閱目標點指令 my_target_sub = rospy.Subscriber("/my_target_point", PoseStamped, my_target_cb) # 建立控制指令與定位資訊的 publisher local_pos_pub = rospy.Publisher("mavros/setpoint_raw/local", PositionTarget, queue_size=10) # 發布位置/速度目標給 MAVROS vision_pos_pub = rospy.Publisher("mavros/vision_pose/pose", PoseStamped, queue_size=10) # 發布視覺定位資訊給 MAVROS # 等待 MAVROS 服務(arming, command, set_mode)可用 rospy.wait_for_service("/mavros/cmd/arming") arming_client = rospy.ServiceProxy("/mavros/cmd/arming", CommandBool) rospy.wait_for_service("/mavros/cmd/command") command_client = rospy.ServiceProxy("/mavros/cmd/command", CommandLong) rospy.wait_for_service("/mavros/set_mode") set_mode_client = rospy.ServiceProxy("/mavros/set_mode", SetMode) # 初始化 tf2 buffer 與 listener,用於查詢 /map → /base_link 的座標轉換 tf_buffer = tf2_ros.Buffer() tf_listener = tf2_ros.TransformListener(tf_buffer) rate = rospy.Rate(50) # 等待與飛控連線 while not rospy.is_shutdown() and not current_state.connected: rospy.loginfo_throttle(2, "wait for FCU connection...") rate.sleep() rospy.loginfo("Setting offboard mode... (5 seconds)") found_tf = False # 查詢一次 /map → /base_link 的 tf,取得初始位置 for _ in range(50): try: trans = tf_buffer.lookup_transform("map", "base_link", rospy.Time(0), rospy.Duration(5)) found_tf = True break except (tf2_ros.LookupException, tf2_ros.ExtrapolationException): rate.sleep() if not found_tf: rospy.logerr("Cannot get current position between /map and /base_link") exit(1) # 設定起飛初始點(座標、姿態) current_goal.coordinate_frame = PositionTarget.FRAME_LOCAL_NED current_goal.type_mask = POSITION_CONTROL current_goal.position.x = trans.transform.translation.x current_goal.position.y = trans.transform.translation.y current_goal.position.z = 1.5 # 預設起飛高度(有改過) q = trans.transform.rotation current_goal.yaw = get_yaw_from_quaternion(q) current_goal.velocity.x = 0 current_goal.velocity.y = 0 current_goal.velocity.z = 0 current_goal.yaw_rate = 0 current_goal.acceleration_or_force.x = 0 current_goal.acceleration_or_force.y = 0 current_goal.acceleration_or_force.z = 0 rospy.loginfo("Initial position=(%.2f,%.2f,%.2f) yaw=%.2f", current_goal.position.x, current_goal.position.y, trans.transform.translation.z, current_goal.yaw) # 起飛前先連續發送一段時間的 setpoint,讓 PX4 進入 offboard 模式 for _ in range(100): current_goal.header.stamp = rospy.Time.now() local_pos_pub.publish(current_goal) rate.sleep() # 準備服務請求物件 offb_set_mode = SetModeRequest() offb_set_mode.custom_mode = "OFFBOARD" arm_cmd = CommandBoolRequest() arm_cmd.value = True disarm_cmd = CommandLongRequest() disarm_cmd.broadcast = False disarm_cmd.command = 400 last_request = rospy.Time.now() lastTwistReceived = rospy.Time.now() current_pose = PoseStamped() current_pose.header.frame_id = "map" # 進入主迴圈 while not rospy.is_shutdown(): try: # 持續查詢 /map → /base_link 的 tf,取得無人機目前位置 trans = tf_buffer.lookup_transform("map", "base_link", rospy.Time(0), rospy.Duration(0.05)) current_pose.pose.position.x = trans.transform.translation.x current_pose.pose.position.y = trans.transform.translation.y current_pose.pose.position.z = trans.transform.translation.z current_pose.pose.orientation = trans.transform.rotation except (tf2_ros.LookupException, tf2_ros.ExtrapolationException): pass # 若尚未進入 OFFBOARD 模式,且間隔超過 5 秒則切換模式 if current_state.mode != "OFFBOARD" and (rospy.Time.now() - last_request > rospy.Duration(5.0)): if set_mode_client.call(offb_set_mode).mode_sent: rospy.loginfo("Offboard enabled") rospy.loginfo("Vehicle arming... (5 seconds)") last_request = rospy.Time.now() # 若尚未解鎖,且間隔超過 5 秒則解鎖 elif (not current_state.armed and not (current_goal.velocity.z < -0.4 and current_goal.yaw_rate < -0.4) and (rospy.Time.now() - last_request > rospy.Duration(5.0))): if arming_client.call(arm_cmd).success: rospy.loginfo("Vehicle armed") rospy.loginfo("Take off at 1.5 meter... to position=(%.2f,%.2f,%.2f) yaw=%.2f", current_goal.position.x, current_goal.position.y, current_goal.position.z, current_goal.yaw) last_request = rospy.Time.now() # 若速度與 yaw_rate 為負值(如搖桿下拉),則執行解鎖 elif (current_goal.velocity.z < -0.4 and current_goal.yaw_rate < -0.4 and (rospy.Time.now() - last_request > rospy.Duration(5.0))): if command_client.call(disarm_cmd).success: rospy.loginfo("Vehicle disarmed") rospy.signal_shutdown("Disarmed by stick command") else: rospy.loginfo("Disarming failed! Still in flight?") last_request = rospy.Time.now() # 若超過 1 秒未收到速度指令,則切回位置控制模式 if (current_goal.header.stamp.to_sec() - lastTwistReceived.to_sec() > 1 and current_goal.type_mask != POSITION_CONTROL): current_goal.coordinate_frame = PositionTarget.FRAME_LOCAL_NED current_goal.type_mask = POSITION_CONTROL current_goal.position.x = current_pose.pose.position.x current_goal.position.y = current_pose.pose.position.y current_goal.position.z = 1.5 q = current_pose.pose.orientation current_goal.yaw = get_yaw_from_quaternion(q) rospy.loginfo("Switch to position control (x=%.2f, y=%.2f, z=%.2f, yaw=%.2f)", current_goal.position.x, current_goal.position.y, current_goal.position.z, current_goal.yaw) # === 新增:收到目標點指令才會飛行 === if target_received and target_pose is not None: # 目標點模式:切換為位置控制,並設定目標點 current_goal.coordinate_frame = PositionTarget.FRAME_LOCAL_NED current_goal.type_mask = POSITION_CONTROL current_goal.position.x = target_pose.pose.position.x current_goal.position.y = target_pose.pose.position.y current_goal.position.z = target_pose.pose.position.z q = target_pose.pose.orientation current_goal.yaw = get_yaw_from_quaternion(q) rospy.loginfo("執行目標點飛行: x=%.2f, y=%.2f, z=%.2f, yaw=%.2f", current_goal.position.x, current_goal.position.y, current_goal.position.z, current_goal.yaw) target_received = False # 發布 setpoint 與 vision pose,供 MAVROS 及 PX4 使用 current_goal.header.stamp = rospy.Time.now() local_pos_pub.publish(current_goal) current_pose.header.stamp = current_goal.header.stamp vision_pos_pub.publish(current_pose) rate.sleep() ``` </details> ## 3. ORB-SLAM3指令與流程 ### ORB-SLAM3 ORB-SLAM3 是目前最先進的開源視覺 SLAM 方案之一,其最大的優勢在於支援多種感測器組合(單目、雙目、RGB-D,以及各自的慣性融合版本)並具備高精度的定位與地圖重建能力。它基於特徵點法,透過稀疏地圖進行定位,並整合了完整的重定位、迴圈閉環與地圖合併功能。 專案的 GitHub 儲存庫與詳細安裝教學請參考:[ORB-SLAM3](https://github.com/UZ-SLAMLab/ORB_SLAM3) 雖然 ORB-SLAM3 官方提供了 ROS 範例,但我們選用了一個由社群開發、更專注於 ROS 整合的版本:[orb_slam3_ros](https://github.com/thien94/orb_slam3_ros) ### **1. 啟動 ORB-SLAM3 節點** 使用 `roslaunch` 指令啟動對應的 SLAM 模式。 ``` #舉例 roslaunch orb_slam3_ros_wrapper t265.launch ``` ### **2. 播放 Rosbag 資料集** ``` #舉例 rosbag play --pause MVS/Dataset/indoor_flying1_data.bag \ /davis/left/image_raw:=/camera/left/image_raw \ /davis/right/image_raw:=/camera/right/image_raw **指令參數詳解:** - `--pause`: **(關鍵參數)** 播放開始時立即暫停。 - `MVS/Dataset/... .bag`: 指定要播放的 `.bag` 檔案路徑。 - `/davis/left/image_raw:=/camera/left/image_raw`: 這是 **Topic 重對映 (Remapping)**,也可以直接寫在launch檔裡。 ``` ORB-SLAM3 畫面如下,會顯示bag檔影像的畫面,以及演算法的定位軌跡。 <figure style="text-align: center;"> <img src="https://hackmd.io/_uploads/Bkcf2mSElx.png" alt="ORB-SLAM3 Rviz 畫面"> <figcaption style="font-style: italic;">圖2:ORB-SLAM3 移動軌跡</figcaption> </figure> 使用dronetest launch檔可以接取模擬無人機環境中的影像,指令如下: ``` #同時運行rtabmap_drone_example roslaunch orb_slam3_ros_wrapper dronetest.launch ``` <figure style="text-align: center;"> <img src="https://hackmd.io/_uploads/Hkw5TQSNgx.png" alt="ORB-SLAM3 Rviz 畫面"> <figcaption style="font-style: italic;">圖3:模擬環境中無人機飛行軌跡</figcaption> </figure> ## 4. YOLO指令與流程 **同步訂閱數據**:節點會同時訂閱來自模擬器的兩個關鍵 Topic: - `/camera/color/image_raw`: 標準的彩色影像,用於 YOLO 進行物件辨識。 - `/camera/depth/image_raw`: **深度影像**。這是一張灰階圖,但每個像素點的值直接對應著該點在真實世界中的距離(單位:公尺)。 ### **1. 安裝 Ultralytics YOLO** 透過 `pip` 即可一鍵安裝所有必要的套件。 ``` pip install ultralytics ``` ### **2. 啟動YOLO節點** ``` roslaunch orb_slam3_ros_wrapper yolo_detect.launch ``` 運行後,會接收模擬環境中無人機的拍攝到的影像,做物件偵測以及顯示該物體與無人機的距離(深度) <figure style="text-align: center;"> <img src="https://hackmd.io/_uploads/H1Ici4BEel.png" alt="YOLO偵測結果"> <figcaption style="font-style: italic;">圖4:YOLO偵測結果</figcaption> </figure> ## 5. EVO軌跡評估工具 在完成 SLAM 任務後,我們需要一個客觀、量化的方法來評估演算法的表現。我們使用 EVO (Evaluation of Odometry and SLAM)評估工具。可參考[這篇](https://blog.csdn.net/qq_28087491/article/details/129043366) > 評估指標:APE vs. RPE > > **絕對軌跡誤差 (Absolute Pose Error - APE)** > **評估目的**:衡量軌跡的**全域一致性 (Global Consistency)**。它直接比較估計軌跡與真實軌跡(Ground Truth)在對齊後的位置差異,最終給出一個整體的誤差值(如 RMSE - 均方根誤差)。APE 值越小,代表整個軌跡的形狀和位置越接近真實情況。 > > **相對位姿誤差 (Relative Pose Error - RPE)** > **評估目的**:衡量系統的**局部精度或漂移 (Drift)**。它關注的是軌跡在短時間內的變化量,非常適合用來評估系統的漂移累積速度。例如,它可以告訴我們無人機每飛行 1 公尺,會產生多少公分的平移或角度誤差。 ### evo_traj:軌跡視覺化與初步檢查 ``` evo_traj tum \ Examples/ROS/ORB_SLAM3/FrameTrajectory_TUM_Format.txt \ --ref MVS_inflying1_gt_od.tum \ -as -p --plot_mode=xyz #參數說明 tum: 指定輸入檔案的格式為 TUM。 FrameTrajectory_TUM_Format.txt: SLAM 系統輸出的估計軌跡。 --ref MVS_inflying1_gt_od.tum: 指定真實軌跡 (Ground Truth)。 -as: 表示在繪圖前,先對兩條軌跡進行對齊與尺度校正 (Sim(3) alignment)。 -p 或 --plot: 指示 EVO 產生一個繪圖視窗。 --plot_mode=xyz: 指定繪製一個 3D 軌跡圖。 ``` <figure style="text-align: center;"> <img src="https://hackmd.io/_uploads/S1Aa8OBNxx.png" alt="seq6_trajetories"> <figcaption style="font-style: italic;">圖5:軌跡視覺化比較圖</figcaption> </figure> ### evo_ape:計算絕對軌跡誤差 ``` evo_ape tum \ MVS_inflying1_gt_od.tum \ Examples/ROS/ORB_SLAM3/FrameTrajectory_TUM_Format.txt \ --align --plot ``` ### evo_rpe:計算相對位姿誤差 ``` evo_rpe tum \ MVS_inflying1_gt_od.tum \ Examples/ROS/ORB_SLAM3/FrameTrajectory_TUM_Format_second.txt \ --pose_relation angle_deg --delta 1 --delta_unit m --plot ``` ## 6. ROS常用除錯工具與技巧 ### rostopic:檢查Topic狀況 `rostopic` 是一個多功能的指令列工具集,用於深入探查單一 Topic 的詳細資訊。 **常用指令**: 1. **`rostopic list`** - **用途**:列出當前所有正在被發布的 Topic。 2. **`rostopic info <topic_name>`** - **用途**:顯示特定 Topic 的詳細資訊,包括它的訊息類型、發布者和訂閱者。 3. **`rostopic echo <topic_name>`** - **用途**:**(最重要的除錯工具)** 直接在終端機中即時印出某個 Topic 正在傳輸的原始數據內容。 ### rqt_image_view:視覺化影像數據 **用途**:對於影像類的 Topic,`rostopic echo` 會輸出一大堆無意義的數字。`rqt_image_view` 則提供了一個圖形化介面,可以直接將影像 Topic 的內容以圖片形式顯示出來。 ``` #啟動 rqt_image_view ``` > 啟動後,在左上角的下拉式選單中,選擇您想查看的影像 Topic 即可。 ## 7. 實體無人機參數設定及啟動流程 這裡彙整了從模擬環境轉向實體無人機時,所需的參數設定與軟體啟動流程。 ### QGroundControl <figure style="text-align: center;"> <img src="https://hackmd.io/_uploads/Hy1wnA0kbl.png " alt="QGC"> <figcaption style="font-style: italic;">圖6:QGroundControl 軟體介面圖</figcaption> </figure> 開源的地面控制站 (GCS) 軟體,主要使用到的功能如下: - 參數配置:這是 QGC 最核心的功能之一。您可以存取飛控(如 PX4)中的所有參數。 - 硬體校準:包括校準指南針(羅盤)、加速度計、陀螺儀、遙控器 (RC) 和電變 (ESC) 等。 - 飛行模式:設定遙控器上的開關對應到哪些飛行模式(例如:Manual、Position、Offboard)。 ### 7.1 PX4 飛控參數設定 (QGroundControl) 這一步是讓飛控「知道」它需要聽取外部視覺定位數據(如 ORB-SLAM3),而不是依賴 GPS。所有這些參數都可以在 QGroundControl (QGC) 中進行設定。 > 進入參數設定: 在 QGC 中,點選上方工具列的**「參數」**(Parameters)選項。 * **`EKF2_HGT_MODE`** * **用途**:設定高度估計的來源。 * **設定**:在搜尋框中輸入 `EKF2_HGT_MODE`。將這個參數的值從 `GPS` 改為 **`Vision`** 。這會告訴飛控的高度估計應該來自外部視覺系統。(如果要室內飛行的話,要轉成依靠視覺定位) * **`EKF2_EV_DELAY`** * **用途**:設定視覺數據從感應器傳輸到飛控的延遲時間。 * **設定**:搜尋 `EKF2_EV_DELAY`。精確的數值非常關鍵。可以先從一個保守的值開始,例如 **100 毫秒 (100ms)**,然後根據實際情況進行微調。如果飛行時出現抖動或不穩定,這個值可能需要調整。(這是室內飛行才會用到的) * **`MPC_XY_VEL_MAX`** * **用途**:最大水平速度限制。這是飛控允許的最高水平速度(單位 m/s)。 * **設定**:在位置模式 (Position Mode) 或 Offboard 模式下,您透過搖桿或程式碼(例如 MAVROS 的 `setpoint_velocity`)發出的指令速度,最終會被限制在這個值以下。(慢速飛行比較能防止暴衝) * **`MPC_LAND_SPEED`** * **用途**:修改自動降落 (Auto Land) 時的下降速度。 * **設定**:可透過 `rosservice` 動態調整。例如,設定為 **0.3 m/s** (QGC只能調到0.6m/s 可以用指令調) ```bash # 修改 Auto Land 降落速度 rosservice call /mavros/param/set "{param_id: 'MPC_LAND_SPEED', value: {integer: 0, real: 0.3}}" # 查看目前參數 rosservice call /mavros/param/get "{param_id: 'MPC_LAND_SPEED'}" ``` <figure style="text-align: center;"> <img src="https://hackmd.io/_uploads/HJD5h0R1Zx.png " alt="QGC"> <figcaption style="font-style: italic;">圖7:Parameters 介面圖</figcaption> </figure> ### 7.2 實體飛行啟動流程 (ORB-SLAM3 + Mavros) 這是在實體無人機上運行視覺定位的啟動順序。(可以啟動但尚未實機飛行) 1. **啟動 MavROS (無人機)** * **用途**:建立機載電腦 (G700) 與 PX4 飛控之間的 MAVLink 通訊。 * **指令** (範例): ```bash # 假設飛控連接到 /dev/ttyS2,波特率為 57600 roslaunch mavros px4.launch fcu_url:=/dev/ttyS2:57600 ``` 2. **開啟相機 SDK** * **用途**:啟動 RealSense 相機驅動,發布影像 Topic。 * **指令** (範例:D415): ```bash # 啟動紅外線雙目 roslaunch realsense2_camera rs_camera.launch \ infra_width:=1280 infra_height:=720 infra_fps:=30 \ enable_infra1:=true enable_infra2:=true ``` 3. **開啟 ORB-SLAM3** * **用途**:運行 SLAM 演算法,訂閱相機 Topic,並發布估計的攝影機姿態。 * **指令** (範例): ```bash # 啟動適用於 RealSense 的 SLAM 節點 roslaunch orb_slam3_ros realsense_d415.launch ``` 4. **開啟 POSE 資訊轉換 (SLAM -> Mavros)** * **用途**:啟動橋接腳本(見 7.3 節),將 ORB-SLAM3 輸出的姿態 Topic (如 `/orb_slam3_stereo/camera_pose`) 轉換並發布到 MAVROS 監聽的 Topic (如 `/mavros/vision_pose/pose`)。 * **指令** (範例): ```bash # 假設你的橋接腳本名稱為 orb_mavros.py rosrun realsense2_camera orb_mavros.py ``` 5. **驗證資料流** * **用途**:確認 MAVROS 確實有接收到來自橋接腳本的視覺定位數據。 * **指令**: ```bash rostopic echo mavros/vision_pose/pose ``` * **檢查**:如果終端機持續輸出 `PoseStamped` 訊息,表示整個視覺定位管線已成功運作。 ### 7.3 範例:SLAM 定位至 MavLink 橋接腳本 這個 Python 腳本(節點)的任務是訂閱 ORB-SLAM3 的姿態輸出,並將其轉發給 MAVROS。 ```python= #!/usr/bin/env python3 import rospy from geometry_msgs.msg import PoseStamped import time class SlamToMavrosBridge: def __init__(self): """ 初始化 SLAM 到 Mavros 的橋接節點。 """ rospy.init_node('slam_to_mavros_bridge', anonymous=True) # 建立一個發布者,將 PoseStamped 訊息發布給 Mavros # Mavros 會監聽 /mavros/vision_pose/pose 這個 Topic 來獲取外部定位資料 self.vision_pose_pub = rospy.Publisher('/mavros/vision_pose/pose', PoseStamped, queue_size=10) # 建立一個訂閱者,訂閱 ORB-SLAM3 發布的定位資訊 # 根據你的 ORB-SLAM3 設置,這個 Topic 名稱可能需要調整 rospy.Subscriber('/orb_slam3_stereo/camera_pose', PoseStamped, self.vision_pose_callback) # 等待 Mavros 服務啟動 rospy.loginfo("Waiting for Mavros to be ready...") time.sleep(5) # 暫停 5 秒,確保 Mavros 已啟動 rospy.loginfo("Bridge node started. Publishing vision poses to Mavros.") def vision_pose_callback(self, data): """ 將 ORB-SLAM3 的 PoseStamped 訊息直接轉發給 Mavros。 此處假設 ORB-SLAM3 的輸出座標系與 Mavros 的期望座標系(例如 ENU)一致。 """ # 創建一個新的 PoseStamped 訊息,以確保時間戳是最新的 px4_pose = PoseStamped() px4_pose.header = data.header px4_pose.header.stamp = rospy.Time.now() # 直接複製位置和姿態 px4_pose.pose.position = data.pose.position px4_pose.pose.orientation = data.pose.orientation # 發布訊息 self.vision_pose_pub.publish(px4_pose) if __name__ == '__main__': try: # 創建並運行橋接器 bridge = SlamToMavrosBridge() rospy.spin() except rospy.ROSInterruptException: pass ```