在Carla中構建自動駕駛:使用PID控制和ROS2進行路徑跟蹤

  1. 機器人軟件開發
  2. 什么是 P、PI 和 PID 控制器?
  3. 比例 (P) 控制器
  4. 比例積分 (PI) 控制器
  5. 比例-積分-微分 (PID) 控制器
  6. 橫向控制簡介
  7. CARLA ROS2 集成
  8. 縱向控制
  9. 橫向控制
  10. 關鍵要點
  11. 結論
  12. 引用

機器人軟件開發

機器人技術是一個多學科領域,涉及機械設計和計算機科學的各個領域,例如計算機體系結構、網絡和數據庫。由于它集成了不同的領域,它通常被視為系統工程。機器人技術的核心軟件組件是數學密集型的,必須經過高度優化才能保持低延遲。機器人技術的主要軟件組件是:

  • 知覺
  • 規劃
  • 控制

無論其環境如何,通用的 autonomous stack 將如下所示(見下圖),至少在其初始迭代中是這樣。該堆棧由 4 個子堆棧組成,即傳感器堆棧、感知堆棧、規劃和控制堆棧。傳感器堆棧由前后兩個攝像頭以及車輛頂部的 LiDAR 組成。感知堆棧包括定位和地圖構建(我們在整個系列中一直在討論這一點),高清地圖對于導航也非常重要,但已經提出了一些方法,可以即時創建高清地圖,最后是用于檢測地標和了解可駕駛區域的對象檢測和分割。

規劃模塊分為三個部分。眾所周知,對于許多用例來說,擁有全局和本地規劃器可能就足夠了,但是在處理不同的場景時,機器人需要有一個行為樹。讓我們以自動駕駛汽車為例。可能存在諸如遵循紅綠燈、停車或越野駕駛等場景,在這些場景中,不同的任務需要不同的規劃者。最后是控件 — 通常,經過良好調整的 PID 控制器用于執行器信號。它通常分為兩個步驟:用于航點跟蹤的橫向控制和用于速度管理的縱向控制。在本文中,我們將更詳細地探討控制部分。

如果我們要在 CARLA 中設計一輛基本的自動駕駛汽車,我們將如何做到這一點?強調 “in CARLA”?因為它將位于模擬器內部,這使得在結構化環境中工作變得更加容易。此外,我們可以利用 CARLA 提供的所有?API 優勢來獲得大量預先計算的估計值。下面的系統架構顯示了如何使用?CARLA 和 ROS 2?創建自主路徑跟蹤車輛。

AV系統

我們首先啟動 CARLA 客戶端和 CARLA ROS 橋。這兩個模塊進行通信以將數據從 CARLA 發送到 ROS 2。最初,這將提供有關?CARLA?世界、天氣和狀態的信息。接下來,需要啟動?CARLA ros-bridge 生成車輛節點,以便在 CARLA 世界中生成車輛,這將通過 ROS 2 主題提供有關車輛、傳感器數據和交通的大量信息。之后,啟動Lanelet?和?Point Cloud 地圖,讓我們可以看到可駕駛區域。

Lanelet 地圖和點云地圖可以了解周圍環境和可駕駛區域。在我們了解之后,我們可動?waypoint generator。它采用車輛的起點和目標位置來生成最短路徑,該路徑由航點組成。路徑點本質上是定義全局路徑的一系列 3D 點。車輛的任務是沿著這些航路點到達其目標。為此,使用了?PID 控制器,該控制器采用航路點坐標和車輛的里程計(每個時間戳的當前位置和方向)來生成執行器信號。里程計數據也來自 CARLA。

從 ROS 2 的角度來看,系統是這樣工作的:節點是工作流的核心。它訂閱了開始和結束位置主題,以及為車輛生成的航路點,并使用車輛的里程計來計算控制命令。主題接收這些控制命令并移動車輛。整個系統在?RViz2?中可視化。carla vehicle ctrl/carla/ego_vehicle/vehicle_control_cmd

由于 CARLA 已經處理了里程計和規劃,我們將主要專注于車輛控制。為此,我們決定使用 PID 控制器,但可以探索更高級的控制器。以下是我們將涵蓋的主題:

  • 什么是 P、PI 和 PID 控制器?
  • 實現 PID 控制 python。
  • 什么是縱向控制和橫向控制?

什么是 P、PI 和 PID 控制器?

如前所述,我們將使用?PID 控制器通過生成必要的執行器信號,使車輛沿所需軌跡移動。但在此之前,“執行器信號”實際上是什么意思?執行器是將電能轉化為物理運動的機器人部件,通常是機器人關節中的電機或液壓系統。控制器的工作是產生適量的電能以實現所需的運動。例如,如果你有一輛車,希望它從一個地方行駛到另一個地方,或者如果你有一個機械臂,希望它從桌子上抓一個蘋果,規劃器會給你所需的物理運動——它可以表示為速度曲線、軌跡,或兩者兼而有之。控制者的工作是遵循建議的路徑。

現在我們了解了目的,讓我們來探索一下控制器是如何實現的。有不同類型的控制器可用,例如 PID 控制器、模糊邏輯控制器、模型預測控制 (MPC)、非線性模型預測控制?(NLMPC)、神經網絡控制器、基于 RL 的控制器等。但在本文中,我們將重點介紹 PID 控制器,它是最簡單但最高效的控制器之一。傳說中,經過適當調整的 PID 控制器仍然可以讓所有現代控制器物有所值。盡管它是最古老的控制器方法之一(已有 100 多年的歷史),但它仍在工業和學術界使用。

PID 控制器中的 P、I 和 D 分別代表比例、積分和導數。每個術語都有助于控制機器人。讓我們用一個例子來分解一下:假設您有一輛自動駕駛汽車,想在保持 50 m/s 速度的同時從家到辦公室。以恒定速度自動駕駛車輛是一項稱為巡航控制的高級駕駛員輔助系統?(ADAS)?功能。

從控制的角度來看,目標是通過根據當前車速和目標速度之間的差異不斷調整油門來實現這一目標。從圖示上講,它可以表示如下:

在高層次上,你可以把它想象成一個系統,你提供所需的速度,它返回系統響應——實際的速度(見上面的圖 a)。控制器的目標是使實際速度等于所需的速度,這意味著它們之間的差異最終應該達到零。

如果我們放大系統(如圖 a),您將看到(圖 b)控制器和車輛(作為工藝/工廠)。在這里,控制器的工作是將所需速度和實際速度作為輸入,并計算它們之間的差值以產生節流值。向 PID 控制器提供飛行器的響應稱為閉環反饋。

現在我們已經全面了解了 PID 控制器的作用,讓我們糾纏 PID 控制器并了解 P、I 和 D 項的貢獻。

比例 (P) 控制器

P 控制器解決當前速度和所需速度之間的差值。例如,如果當前速度為 0 且目標為 50 m/s,則需要增加節流閥以縮小該間隙。誤差越大,應用的調節就越多,這意味著調節與誤差成正比,表示為:

在數學上,這可以寫成

下面的框圖表示反饋控制系統中使用的比例 (P) 控制器,用于調節車輛的速度。將所需速度輸入到系統中,并與實際速度進行比較以確定誤差。該誤差被饋送到 P 控制器中,該控制器根據誤差的成比例增益調整車輛的油門。輸出是車輛的實際速度,它被反饋到系統中進行連續調整,保持所需的速度。循環通過反饋閉合,確保實時校正飛行器的速度。

下面是簡單 P 控制器的 python 代碼

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider# Define PID Controller class with resistance
class PIDControllerWithResistance:def __init__(self, Kp, set_point=0, resistance_factor=0.1):self.Kp = Kpself.set_point = set_pointself.resistance_factor = resistance_factor  # Resistance to throttle (e.g., air resistance, friction)def update(self, current_value, dt):# Apply the same PID control logic but factor in resistanceerror = self.set_point - current_valueoutput = self.Kp * errorreturn output - self.resistance_factor * current_value  # Reduce output by a resistance factor# Simulation parameters
dt = 0.1  # Time step
time = np.arange(0, 50, dt)  # Simulation time# Initialize the PID controller with disturbance (resistance)
pid_with_resistance = PIDControllerWithResistance(Kp=1.0, set_point=50, resistance_factor=0.2)# Initial conditions
speed = 0
throttle_with_resistance = []
speed_record_with_resistance = []# Simulate the system with resistance
for t in time:control = pid_with_resistance.update(speed, dt)speed += control * dt  # Speed is affected by throttle control and resistancethrottle_with_resistance.append(control)speed_record_with_resistance.append(speed)# Plot setup
fig, ax = plt.subplots()
plt.subplots_adjust(left=0.1, bottom=0.3)l, = plt.plot(time, speed_record_with_resistance, label="Speed Output (With Resistance)")
plt.axhline(pid_with_resistance.set_point, color='r', linestyle='--', label='Set Point')
plt.xlabel('Time [s]')
plt.ylabel('Speed [m/s]')
plt.title('PID Cruise Control with Resistance')
plt.legend()
plt.show()

結果:

此代碼做了一些假設:

  1. 我們假設油門和車速之間的關系是線性的,這意味著增加油門會增加速度。這就是為什么我們簡單地做 .通過將 乘以 ,我們確保速度在每個小的時間間隔內逐漸變化,模擬逐漸變化。speed += control * dtcontroldt
  2. 為簡單起見,我們假設在這個模型中,阻力與速度成線性比例。

我們首先定義 class,它接受?

?value、set-point 和 resistance 值。在 update 函數中,我們計算誤差并將其乘以

得到節流。我們還包括 .PIDControllerWithResistanceself.set_point - current_valueoutput - self.resistance_factor * current_value

  • 將阻力系數乘以當前速度 () 可創建一個更真實的模型,其中阻力隨速度的增加而增加。current_value
  • 通過從輸出節氣門值中減去這個阻力分量,我們模擬了需要更多的節氣門來保持更高的速度,從而使車輛更難以更高的速度加速。

我們從 0 到 50 循環遍歷時間戳,保持 0.1,并調用控制器的 update 函數來獲得結果速度。然而,正如你所看到的,僅使用比例控制是不夠的。盡管速度接近設定點,但它從未完全達到設定點。穩態和設定點之間的這種差值稱為 。此時,誤差變得如此之小,以至于即使將其乘以

也會導致最小的變化,而速度幾乎保持不變。dtSteady State Error

比例積分 (PI) 控制器

正如我們所看到的,響應速度接近設定點,但并沒有完全達到它。為了解決這個問題,我們可以在 Proportional 控制器旁邊添加一個 Integral 控制器。Integral 控制器查看過去的錯誤并隨著時間的推移累積它們。這種正誤差的累積有助于將響應推向更接近設定點。

在數學上,這可以寫成

微分 (D) 控制器現已與 PI 控制器并聯添加,形成比例-積分-微分 (PID) 控制器。該系統用于反饋回路中,以調節車輛的速度,通過考慮現在、過去和預測的未來誤差來提高精度。將所需速度與實際速度進行比較以計算誤差。該誤差被饋送到三個控制器中:比例 (P) 控制器,對當前誤差做出反應,積分 (I) 控制器,解釋隨時間累積的誤差,以及導數 (D) 控制器,通過考慮誤差的變化率來預測未來的誤差。這些控制器的輸出組合在一起以調整車輛的油門,并將實際速度反饋到系統中進行連續校正,確保以更高的精度和穩定性保持所需的速度。

該方程式在編寫時考慮了離散時間。?值通常是通過反復試驗找到的,盡管有一些算法可用于調整增益值。表示導數收益。

以下是 PID 控制器的 python 代碼

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider# Define PID Controller class with resistance
class PIDControllerWithResistance:def __init__(self, Kp, Ki, Kd, set_point=0, resistance_factor=0.1):self.Kp = Kpself.Ki = Kiself.Kd = Kdself.set_point = set_pointself.prev_error = 0self.integral = 0self.resistance_factor = resistance_factor  # Resistance to throttle (e.g., air resistance, friction)def update(self, current_value, dt):# Apply the same PID control logic but factor in resistanceerror = self.set_point - current_valueself.integral += error * dtderivative = (error - self.prev_error) / dtoutput = self.Kp * error + self.Ki * self.integral + self.Kd * derivativeself.prev_error = errorreturn output - self.resistance_factor * current_value  # Reduce output by a resistance factor# Simulation parameters
dt = 0.1  # Time step
time = np.arange(0, 50, dt)  # Simulation time# Initialize the PID controller with disturbance (resistance)
pid_with_resistance = PIDControllerWithResistance(Kp=1.0, Ki=0.05, Kd=0.01, set_point=50, resistance_factor=0.05)# Initial conditions
speed = 0
throttle_with_resistance = []
speed_record_with_resistance = []# Simulate the system with resistance
for t in time:control = pid_with_resistance.update(speed, dt)speed += control * dt  # Speed is affected by throttle control and resistancethrottle_with_resistance.append(control)speed_record_with_resistance.append(speed)# Plot setup
fig, ax = plt.subplots()
plt.subplots_adjust(left=0.1, bottom=0.3)l, = plt.plot(time, speed_record_with_resistance, label="Velocity Output (With Resistance)")
plt.axhline(pid_with_resistance.set_point, color='r', linestyle='--', label='Set Point')
plt.xlabel('Time [s]')
plt.ylabel('Velocity [m/s]')
plt.title('PID Cruise Control with Resistance')
plt.legend()
plt.show()

該代碼與 PI 控制器代碼非常相似,只是在 PI 控制器輸出中添加了導數項。它的計算公式為 。derivative = (error - self.prev_error) / dt

總結 PID 控制器,P 查看當前錯誤,I 查看過去的錯誤,D 預測未來的錯誤。它們共同調節輸出響應以達到所需的結果。

橫向控制簡介

當將機器人從一個位置移動到另一個位置時,僅使用速度控制(通常稱為縱向控制)不足以確保準確的路徑跟蹤。雖然縱向控制控制機器人的速度,但規劃器通常會生成一個由從機器人當前位置到目標的 3D 航路點組成的軌跡。為了有效地遵循這一軌跡,機器人還需要橫向控制,調整其轉向角度或方向以保持所需的路徑。橫向控制對于處理偏差、確保平穩導航和實現精確定位至關重要,尤其是在復雜環境中移動時。

有各種類型的側向控制器旨在處理此任務,每種類型都適用于不同的應用和條件。這些控制器可以大致分為幾何控制器和動態控制器。

  • 幾何控制器:
    • Pure Pursuit (胡蘿卜追隨)
    • 赤 柱
    • PID (PID)
  • 動態控制器:
    • MPC 控制器
    • 其他控制系統
      • 滑動模式、反饋線性化等

CARLA ROS2 集成

現在我們已經了解了 PID 控制器,讓我們使用它在 CARLA 中移動我們的車輛。只有兩個主文件用于執行此作,并且 ;我們將一一介紹它們。vehicle_ctrl.pylat_lon_ctrl.py

首先了解 中的縱向和橫向控制代碼。下面的代碼取自 CARLA 本身,略有修改。lat_lon_ctrl.py

from collections import deque
import math
import numpy as np
import carla
from agents.tools.misc import get_speed...class PIDLongitudinalController():"""PIDLongitudinalController implements longitudinal control using a PID."""def __init__(self, vehicle, K_P=1.0, K_I=0.0, K_D=0.0, dt=0.03):"""Constructor method.:param vehicle: actor to apply to local planner logic onto:param K_P: Proportional term:param K_D: Differential term:param K_I: Integral term:param dt: time differential in seconds"""self._vehicle = vehicleself._k_p = K_Pself._k_i = K_Iself._k_d = K_Dself._dt = dtself._error_buffer = deque(maxlen=10)def run_step(self, target_speed, debug=False):"""Execute one step of longitudinal control to reach a given target speed.:param target_speed: target speed in Km/h:param debug: boolean for debugging:return: throttle control"""current_speed = get_speed(self._vehicle)if debug:print('Current speed = {}'.format(current_speed))return self._pid_control(target_speed, current_speed)def _pid_control(self, target_speed, current_speed):"""Estimate the throttle/brake of the vehicle based on the PID equations:param target_speed:  target speed in Km/h:param current_speed: current speed of the vehicle in Km/h:return: throttle/brake control"""error = target_speed - current_speedself._error_buffer.append(error)if len(self._error_buffer) >= 2:_de = (self._error_buffer[-1] - self._error_buffer[-2]) / self._dt_ie = sum(self._error_buffer) * self._dtelse:_de = 0.0_ie = 0.0return np.clip((self._k_p * error) + (self._k_d * _de) + (self._k_i * _ie), -1.0, 1.0)def change_parameters(self, K_P, K_I, K_D, dt):"""Changes the PID parameters"""self._k_p = K_Pself._k_i = K_Iself._k_d = K_Dself._dt = dtclass PIDLateralController():"""PIDLateralController implements lateral control using a PID."""def __init__(self, vehicle, offset=0, K_P=1.0, K_I=0.0, K_D=0.0, dt=0.03):"""Constructor method.:param vehicle: actor to apply to local planner logic onto:param offset: distance to the center line. If might cause issues if the valueis large enough to make the vehicle invade other lanes.:param K_P: Proportional term:param K_D: Differential term:param K_I: Integral term:param dt: time differential in seconds"""self._vehicle = vehicleself._k_p = K_Pself._k_i = K_Iself._k_d = K_Dself._dt = dtself._offset = offsetself._e_buffer = deque(maxlen=10)def run_step(self, waypoint):"""Execute one step of lateral control to steerthe vehicle towards a certain waypoin.:param waypoint: target waypoint:return: steering control in the range [-1, 1] where:-1 maximum steering to left+1 maximum steering to right"""return self._pid_control(waypoint, self._vehicle.get_transform())def _pid_control(self, waypoint, vehicle_transform):"""Estimate the steering angle of the vehicle based on the PID equations:param waypoint: target waypoint:param vehicle_transform: current transform of the vehicle:return: steering control in the range [-1, 1]"""# Get the ego's location and forward vectorego_loc = vehicle_transform.locationv_vec = vehicle_transform.get_forward_vector()v_vec = np.array([v_vec.x, v_vec.y, 0.0])# Get the vector vehicle-target_wpif self._offset != 0:# Displace the wp to the sidew_tran = waypointr_vec = w_tran.get_right_vector()w_loc = w_tran.location + carla.Location(x=self._offset*r_vec.x,y=self._offset*r_vec.y)else:w_loc = waypoint.locationw_vec = np.array([w_loc.x - ego_loc.x,w_loc.y - ego_loc.y,0.0])wv_linalg = np.linalg.norm(w_vec) * np.linalg.norm(v_vec)if wv_linalg == 0:_dot = 1else:_dot = math.acos(np.clip(np.dot(w_vec, v_vec) / (wv_linalg), -1.0, 1.0))_cross = np.cross(v_vec, w_vec)if _cross[2] < 0:_dot *= -1.0self._e_buffer.append(_dot)if len(self._e_buffer) >= 2:_de = (self._e_buffer[-1] - self._e_buffer[-2]) / self._dt_ie = sum(self._e_buffer) * self._dtelse:_de = 0.0_ie = 0.0return np.clip((self._k_p * _dot) + (self._k_d * _de) + (self._k_i * _ie), -1.0, 1.0)def change_parameters(self, K_P, K_I, K_D, dt):"""Changes the PID parameters"""self._k_p = K_Pself._k_i = K_Iself._k_d = K_Dself._dt = dt
縱向控制

PIDLongitudinalControllerclass 包含縱向控制的代碼,我們首先初始化 PID 增益、Carla Vehicle 實例和誤差緩沖區(用于 D-Control)。 是從外部函數迭代調用 PID 更新的函數的函數。真正的魔力在于函數內部。實現非常簡單,類似于上面的 PID 實現。我們首先計算誤差,填充列表。如果有 2 個以上的元素,那么我們計算導數誤差 () 和積分誤差 (),最后通過將其從 -1 裁剪到 1 來返回總控制輸出為 。run_step_pid_control_pid_controlerror = target_speed - current_speedself._error_bufferself._error_buffer_de_ienp.clip((self._k_p * error) + (self._k_d * _de) + (self._k_i * _ie), -1.0, 1.0)

橫向控制

橫向控制有點棘手。在跳入代碼之前,我們先看看橫向誤差是如何計算的。誤差表示為無人機前向矢量和航路點矢量之間的有符號角。角度的標志是根據車輛是在所需路徑的左側還是右側來決定。角度應為正 (向右) 或負 (向左)。

在自動駕駛汽車的橫向控制中,主要目標是使車輛保持在正確的軌跡或路徑上。車輛的橫向控制通常與最小化與該所需路徑的偏差有關,這是通過控制轉向角來實現的。您提供的代碼使用 PID (Proportional, Integral, Derivative) 控制器根據此偏差調整車輛的轉向角。

讓我們來看看代碼。 是定義橫向控制的位置。以下是誤差角的計算方法,PIDLateralController

1.?車輛的前向矢量?(v_vec):

正向向量表示無人機當前移動的方向,該方向源自無人機的變換 ()。它是一個 3D 向量,但由于車輛在 2D 平面上移動,因此僅使用 x 和 y 分量。vehicle_transform.get_forward_vector()

2.?航點矢量?():w_vec

航路點向量表示從機體的當前位置 () 到目標航路點 () 的方向:ego_locw_loc

3.?點積和角度

機體航向與目標航點之間的誤差由機體的前向矢量與航點矢量之間的角度來測量。這是使用點積和這些向量的大小計算的:

此角度 () 表示以弧度為單位的橫向誤差。_dot

4.?Sign 的叉積

為了確定無人機是在所需路徑的左側還是右側(即誤差的符號),計算前向矢量和航點矢量之間的叉積

叉積的第三個分量(z 分量)的符號確定角度應該是正 (向右) 還是負 (向左)。如果 z 分量為負,則角度反轉:

因此,橫向誤差 () 是一個有符號角度,表示無人機需要轉向多少才能與航點對齊。_dot

PID 控制應用

錯誤 () 被傳遞到 PID 控制器中,PID 控制器使用以下公式:_dot

現在我們已經完成了 PID 控制,讓我們看看如何使用 ROS 2 運行它。

現在讓我們來看看 .vehicle_ctrl.py

class CarlaVehicleControl(Node):def __init__(self):super().__init__('carla_route_planner')# Subscribersself.initialpose_sub = self.create_subscription(PoseWithCovarianceStamped,'/initialpose',self.initialpose_callback,10)self.goal_pose_sub = self.create_subscription(PoseStamped,'/goal_pose',self.goal_pose_callback,10)self.waypt_sub = self.create_subscription(Path, '/carla/ego_vehicle/waypoints', self.waypoints_callback, 10)# Subscriber to the /carla/ego_vehicle/odometry topicself.odom_sub = self.create_subscription(Odometry,'/carla/ego_vehicle/odometry',self.odometry_callback,10)self.vehicle_control_publisher = self.create_publisher(CarlaEgoVehicleControl, '/carla/ego_vehicle/vehicle_control_cmd', 10)# Initialize Carla client and mapself.client = Client('localhost', 2000)self.client.set_timeout(10.0)# Get the current worldself.world = self.client.get_world()# Check if Town01 is already loadedif 'Town01' not in self.world.get_map().name:print("Town01 is not loaded. Loading Town01...")self.world = self.client.load_world('Town01')print("Done!")else:print("Town01 is already loaded.")self.map = self.world.get_map()# Initialize GlobalRoutePlannerself.route_planner = GlobalRoutePlanner(self.map, 2.0)# Get all actors (vehicles, pedestrians, etc.) in the worldself.actors = self.world.get_actors()# Filter to get only the vehicles get the 0-th veh as there is only one vehself.vehicle = self.actors.filter('vehicle.*')[0]# Placeholders for start and end posesself.start_pose = Noneself.end_pose = Noneself.waypoints_list = []self.odom = None# TF2 listener and buffer# self.tf_buffer = Buffer()# self.tf_listener = TransformListener(self.tf_buffer, self)# self.vehicle_loc = Nonedef odometry_callback(self, msg):self.get_logger().info(f"Received odometry data: {msg.pose.pose.position.x}, {msg.pose.pose.position.y}, {msg.pose.pose.position.z}")# Extract position and orientation from Odometry messagex = msg.pose.pose.position.xy = -msg.pose.pose.position.yz = msg.pose.pose.position.zprint(" ^^^^ ODOM XYZ: ", x,y,z )orientation_q = msg.pose.pose.orientationroll, pitch, yaw = euler_from_quaternion([orientation_q.x, orientation_q.y, orientation_q.z, orientation_q.w])# Create a carla.Location objectlocation = carla.Location(x=x, y=y, z=z)# Create a carla.Rotation objectrotation = carla.Rotation(roll=roll, pitch=pitch, yaw=yaw)# Create a carla.Transform objecttransform = carla.Transform(location, rotation)self.odom = transformdef waypoints_callback(self, msg):# self.waypoints_list.clear()  # Clear the list before storing new waypoints# Iterate through all the waypoints in the Path messagefor pose in msg.poses:# Extract the position from the posex = pose.pose.position.xy = -pose.pose.position.yz = pose.pose.position.z# Extract the orientation (quaternion) from the poseorientation_q = pose.pose.orientationroll, pitch, yaw = euler_from_quaternion([orientation_q.x, orientation_q.y, orientation_q.z, orientation_q.w])# Create a carla.Location objectlocation = carla.Location(x=x, y=y, z=z)# Create a carla.Rotation objectrotation = carla.Rotation(roll=roll, pitch=pitch, yaw=yaw)# Create a carla.Transform objecttransform = carla.Transform(location, rotation)# Store the Waypoint in the global listself.waypoints_list.append(transform)self.get_logger().info(f"Stored {len(self.waypoints_list)} waypoints as carla.libcarla.Waypoint objects.")def create_ctrl_msg(self, throttle, steer, brake):control_msg = CarlaEgoVehicleControl()control_msg.throttle = throttlecontrol_msg.steer = steercontrol_msg.brake = brakereturn control_msgdef initialpose_callback(self, msg):self.get_logger().info("Received initialpose")self.start_pose = msg.pose.posedef goal_pose_callback(self, msg):self.get_logger().info("Received goal_pose")self.end_pose = msg.pose# Clear the waypoints list for the new goalself.waypoints_list.clear()def get_transform(self, vehicle_location, angle, d=6.4):a = math.radians(angle)location = carla.Location(d * math.cos(a), d * math.sin(a), 2.0) + vehicle_locationreturn carla.Transform(location, carla.Rotation(yaw=180 + angle, pitch=-15))def setup_PID(self, vehicle):"""This function creates a PID controller for the vehicle passed to it """args_lateral_dict = {'K_P': 0.5,  # Reduced proportional gain for smoother steering'K_D': 0.1,  # Small derivative gain to dampen oscillations'K_I': 0.01, # Small integral gain to correct for long-term drift'dt': 0.05}args_long_dict = {'K_P': 0.2,  # Slightly lower gain for acceleration control'K_D': 0.3,  # Moderate derivative gain'K_I': 0.01, # Small integral gain'dt': 0.05}PID= VehiclePIDController(vehicle,args_lateral=args_lateral_dict,args_longitudinal=args_long_dict)return PIDdef find_dist_veh(self, vehicle_loc,target):dist = math.sqrt( (target.location.x - vehicle_loc.x)**2 + \(target.location.y - vehicle_loc.y)**2 )return distdef drive_through_plan(self, planned_route, vehicle, speed, PID):"""This function drives throught the planned_route with the speed passed in the argument"""i=0waypt_cnt = len(planned_route)-1target=planned_route[0]cnt = 0while True:self.world.get_spectator().set_transform(self.get_transform(vehicle.get_location() +carla.Location(z=1, x=0.5), vehicle.get_transform().rotation.yaw-180))# vehicle_loc =  vehicle.get_location()vehicle_loc =  self.odom.locationdistance_v = self.find_dist_veh(vehicle_loc,target)control = PID.run_step(speed,target)# vehicle.apply_control(control)ctrl_msg = self.create_ctrl_msg(control.throttle,control.steer,control.brake)self.vehicle_control_publisher.publish(ctrl_msg)if i==(len(planned_route)-1):print("last waypoint reached")breakif (distance_v<3.5):control = PID.run_step(speed,target)# vehicle.apply_control(control)ctrl_msg = self.create_ctrl_msg(control.throttle,control.steer,control.brake)self.vehicle_control_publisher.publish(ctrl_msg)i=i+1target=planned_route[i]if cnt%5==0:print("=----------------------------------------------------------")print(f"\n{GREEN} ***** from current loc to {i}/{waypt_cnt} waypoint distance: {distance_v}{RESET}\n")print("ROS2 vehilce location: ", self.odom.location)print("CARLA vehilce location: ", vehicle.get_location())print("target location: ", target.location)rclpy.spin_once(self)# time.sleep(0.1)  # Add a slight delay to reduce control frequency# time.sleep(1)  # Add a 1-second delay# print("throttle: ", control.throttle)cnt+=1control = PID.run_step(0,planned_route[len(planned_route)-1])# vehicle.apply_control(control)ctrl_msg = self.create_ctrl_msg(control.throttle,control.steer,control.brake)self.vehicle_control_publisher.publish(ctrl_msg)def run(self):desired_velocity=10 #Km/hwhile rclpy.ok():rclpy.spin_once(self)if self.start_pose is None or self.end_pose is None:self.get_logger().info(f'Start pose: {self.start_pose}, End pose: {self.end_pose}')elif not self.waypoints_list:self.get_logger().info('Waiting for waypoints to be generated...')else:# Delay to ensure waypoints are populatedself.get_logger().info('Waiting a bit for waypoints to be fully populated...')time.sleep(1)  # Add a 1-second delayself.get_logger().info(f'Generated {len(self.waypoints_list)} waypoints from start to end pose')# calculating life time of the markertotal_dist = self.find_dist_veh(self.waypoints_list[0].location, self.waypoints_list[len(self.waypoints_list)-1])marker_life_time = (total_dist/desired_velocity) * 3.6# Draw waypoints on the Carla mapfor w in self.waypoints_list:# print("self.waypoints_list: ",w.location)self.world.debug.draw_string(w.location, 'O', draw_shadow=False,color=Color(r=255, g=0, b=0), life_time=5000000.0,persistent_lines=True)# drive the vehiclePID=self.setup_PID(self.vehicle)self.drive_through_plan(self.waypoints_list,self.vehicle,desired_velocity,PID)# After processing, break the loop if neededbreakdef main(args=None):rclpy.init(args=args)route_planner = CarlaVehicleControl()route_planner.run()rclpy.shutdown()

如果您一直在關注 robotics 系列,上面的代碼應該很容易理解。代碼的核心在于 and 函數。run()drive_through_plan()

  • 調用 main 函數時,回調將轉到該函數。您會注意到我們沒有使用 ,而是使用 .這是因為 Continuous 旋轉以收集所有已發布的數據。然而,我們不能僅僅為了這個目的而旋轉——我們還需要根據輸入來移動車輛。如果我們只關注這一點,我們可能會錯過已發布的數據,導致車輛失控并可能撞墻。

    這可以通過以下方式處理:run()rclpy.spin()rclpy.spin_once(self)rclpy.spin()
    • 在單獨的線程中運行這兩個任務。
    • 運行 with 來收集已發布的數據,在循環中時,定期使用 來更新 subscriber 變量。rclpy.spin_once()while rclpy.ok()drive_through_planrclpy.spin_once()
  • 工作原理的解釋如下:它首先設置一個目標軌跡點并進入一個 while 循環。此循環一直持續到飛機到達其目標位置。在循環中,它首先計算車輛位置與目標航點之間的距離,然后使用 PID 控制器根據所需的速度和目標航點計算轉向和油門值。這些值將應用于車輛。此過程將重復,直到機體與目標航點之間的距離小于 3.5。此時,它會重新計算油門和轉向值,應用它們,并更新目標航點。如果迭代到達最后一個航點,則車輛會根據所需的速度 0 制動并應用控制。此外,在該條件中,它會執行以更新里程計值。drive_through_planif cnt%5==0:rclpy.spin_once(self)

一切就緒后,剩下的工作就是運行代碼并觀察它的運行情況。

# in a new terminal, run carla first
# ./CarlaUE4.sh  # or ./CarlaUE4.sh -prefernvidia # $ ~/carla_simulator/PythonAPI/util/config.py --map Town01
$CARLA_ROOT/CarlaUE4.sh -quality-level=Low -prefernvidia -nosound# in a new terminal, get inside the `carla-ros-bridge/colcon_ws` folder and source the workspace; launch the `carla ros-bridge`
cd ~/carla-ros-bridge/colcon_ws && source install/setup.bash
ros2 launch carla_ros_bridge carla_ros_bridge.launch.py synchronous_mode:=True town:=Town01 # <town number, eg: 03># in a new terminal, launch the objects.json; launch ros-bridge
# cd ~/carla-ros-bridge/colcon_ws
cd ~/carla-ros-bridge/colcon_ws && source install/setup.bashros2 launch carla_spawn_objects carla_example_ego_vehicle.launch.py spawn_sensors_only:=False objects_definition_file:=<absolute path to>/src/vehicle_ctrl/vehicle_ctrl/config/objects.json# load the town1 lanelet map
python src/vehicle_ctrl/vehicle_ctrl/map.py# in new terminal, launch the rviz2 [set the global frame to map in rviz2]
rviz2 -d /src/vehicle_ctrl/rviz2/carla_map_spawn_anywherev2.rviz# in a new terminal, get inside the `carla-ros-bridge/colcon_ws` folder and source the workspace; waypoint publisher 
cd ~/carla-ros-bridge/colcon_ws && source install/setup.bash
ros2 launch carla_waypoint_publisher carla_waypoint_publisher.launch.py# goal remap
python src/vehicle_ctrl/vehicle_ctrl/remap_goal.py # waypoint following using carls ros-bridge
python src/vehicle_ctrl/vehicle_ctrl/simple_ctrl.py

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/77931.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/77931.shtml
英文地址,請注明出處:http://en.pswp.cn/web/77931.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【KWDB 創作者計劃】_深度解析KWDB存儲引擎

文章目錄 每日一句正能量引言一、存儲引擎核心模塊結構二、寫前日志 WAL&#xff08;Write-Ahead Log&#xff09;三、列式壓縮存儲&#xff08;Columnar Compression&#xff09;四、索引機制與混合查詢調度五、分布式核心功能&#xff1a;租約管理實戰六、時間序列數據處理&a…

Apache Tomcat 漏洞(CVE-2025-24813)導致服務器面臨 RCE 風險

CVE-2025-24813Apache Tomcat 中發現了一個嚴重安全漏洞,標識為,該漏洞可能導致服務器面臨遠程代碼執行 (RCE)、信息泄露和數據損壞的風險。 此缺陷影響以下版本: Apache Tomcat11.0.0-M1通過11.0.2Apache Tomcat10.1.0-M1通過10.1.34Apache Tomcat9.0.0-M1通過9.0.98了解 …

全面解析SimHash算法:原理、對比與Spring Boot實踐指南

一、SimHash算法概述 SimHash是一種局部敏感哈希算法&#xff0c;由Google工程師Moses Charikar提出&#xff0c;主要用于海量文本的快速去重與相似度檢測。其核心思想是將高維特征向量映射為固定長度的二進制指紋&#xff08;如64位&#xff09;&#xff0c;通過計算指紋間的…

臨床回歸分析及AI推理

在醫療保健決策越來越受數據驅動的時代&#xff0c;回歸分析已成為臨床醫生和研究人員最強大的工具之一。無論是預測結果、調整混雜因素、建模生存時間還是理解診斷性能&#xff0c;回歸模型都為將原始數據轉化為臨床洞察提供了統計學基礎。 AI推理 然而&#xff0c;隨著技術…

西門子PLC S7-1200 電動機的軟啟動控制

1 PWM 控制的基本概念 PWM 是 PulseWidth Modulation 的簡稱。 PWM 控制是一種脈沖寬度調制技術,通過對一系列脈沖的寬度進行調制來等效獲得所需要的波形(含形狀和幅值)。PWM 控制技術在逆變電路中應用比較廣泛,所應用的逆變電路絕大部分是PWM 型。除此之外, PWM 控制技術…

【學習 python day5】

學習目標&#xff1a; python基礎 掌握函數的定義及調用方法掌握模塊的用法掌握包的用法掌握如何捕獲異常 web自動化測試 能完成selenium自動化環境部署及結果驗證掌握selenium實現自動化測試的核心步驟 學習內容&#xff1a; 一、Python基礎 1、集合[了解] 1, 集合 set, …

day006-實戰練習題-參考答案

老男孩教育-99期-實戰練習題 1. 你作為"老男孩教育99期云計算"新晉運維工程師&#xff0c;在入職首日遭遇緊急事件&#xff1a; "生產環境3臺Web服務器突發性能告警&#xff0c;技術總監要求你立即完成&#xff1a; 快速建立故障診斷工作區收集關鍵系統指標分…

C# 實現列式存儲數據

C#實現列式存儲數據指南 一、列式存儲概述 列式存儲(Columnar Storage)是一種數據存儲方式&#xff0c;它將數據按列而非行組織。與傳統的行式存儲相比&#xff0c;列式存儲在以下場景具有優勢&#xff1a; ??分析型查詢??&#xff1a;聚合計算、分組統計等操作效率更高…

Mysql索引分類、索引失效場景

索引分類 按數據結構分類? B-Tree索引&#xff08;BTree&#xff09; 描述??&#xff1a;默認的索引類型&#xff0c;大多數存儲引擎&#xff08;如InnoDB、MyISAM&#xff09;支持。實際使用BTree結構&#xff0c;數據存儲在葉子節點&#xff0c;葉子節點通過指針連接&a…

SpringBoot+Redis全局唯一ID生成器

&#x1f4e6; 優雅版 Redis ID 生成器工具類 支持&#xff1a; 項目啟動時自動初始化起始值獲取自增 ID 方法yml 配置化起始值可靈活擴展多業務線 ID &#x1f4cc; application.yml 配置 id-generator:member-start-value: 1000000000&#x1f4cc; 配置類&#xff1a;IdG…

深入掌握CSS背景圖片:從基礎到實戰

背景圖片&#xff1a; 本文將通過系統化的講解實戰案例&#xff0c;幫助讀者徹底掌握CSS背景圖片的六大核心知識點。每個知識點都包含對比演示和記憶技巧&#xff0c;建議結合代碼實操學習。 一、背景圖片基礎設置 使用background-image&#xff08;路徑&#xff09;屬性設置…

WPF之XAML基礎

文章目錄 XAML基礎&#xff1a;深入理解WPF和UWP應用開發的核心語言1. XAML簡介XAML與XML的關系 2. XAML語法基礎元素語法屬性語法集合語法附加屬性 3. XAML命名空間命名空間映射關系 4. XAML標記擴展靜態資源引用數據綁定相對資源引用常見標記擴展對比 5. XAML與代碼的關系XAM…

驅動車輛診斷測試創新 | 支持診斷測試的模擬器及數據文件轉換生成

一 背景和挑戰 | 背景&#xff1a; 隨著汽車功能的日益豐富&#xff0c;ECU和域控制器的復雜性大大增加&#xff0c;導致測試需求大幅上升&#xff0c;尤其是在ECU的故障診斷和性能驗證方面。然而&#xff0c;傳統的實車測試方法難以滿足高頻率迭代和驗證需求&#xff0c;不僅…

免疫細胞靶點“破局戰”:從抗體到CAR-T,自免疾病治療的3大技術突破

引言 人體免疫系統組成了一個嚴密調控的“網絡”&#xff0c;時刻檢測著外來病原體&#xff0c;并將其與自身抗原區分開來。但免疫系統也可能會被“策反”&#xff0c;錯誤的攻擊我們自身&#xff0c;從而導致自身免疫性疾病的發生。 目前已知的自免疾病超過100種&#xff0c…

計算機網絡應用層(5)-- P2P文件分發視頻流和內容分發網

&#x1f493;個人主頁&#xff1a;mooridy &#x1f493;專欄地址&#xff1a;《計算機網絡&#xff1a;自頂向下方法》 大綱式閱讀筆記_mooridy的博客-CSDN博客 &#x1f493;本博客內容為《計算機網絡&#xff1a;自頂向下方法》第二章應用層第五、六節知識梳理 關注我&…

十二種存儲器綜合對比——《器件手冊--存儲器》

存儲器 名稱 特點 用途 EEPROM 可電擦除可編程只讀存儲器&#xff0c;支持按字節擦除和寫入操作&#xff0c;具有非易失性&#xff0c;斷電后數據不丟失。 常用于存儲少量需要頻繁更新的數據&#xff0c;如設備配置參數、用戶設置等。 NOR FLASH 支持按字節隨機訪問&…

第十六屆藍橋杯 2025 C/C++組 旗幟

目錄 題目&#xff1a; 題目描述&#xff1a; 題目鏈接&#xff1a; 思路&#xff1a; 思路詳解&#xff1a; 代碼&#xff1a; 代碼詳解&#xff1a; 題目&#xff1a; 題目描述&#xff1a; 題目鏈接&#xff1a; P12340 [藍橋杯 2025 省 AB/Python B 第二場] 旗幟 -…

比亞迪再獲國際雙獎 以“技術為王”書寫中國汽車出海新篇章

近日&#xff0c;全球汽車行業權威獎項“2025世界汽車大獎”&#xff08;World Car Awards&#xff09;在紐約國際車展舉行頒獎典禮&#xff0c;比亞迪海鷗&#xff08;BYD SEAGULL/BYD DOLPHIN MINI&#xff09;摘得“2025世界城市車&#xff08;World Urban Car&#xff09;”…

人工智能數學基礎(五):概率論

概率論是人工智能中處理不確定性的核心工具&#xff0c;它為機器學習、數據科學和統計分析提供了理論基礎。本文將深入淺出地介紹概率論的重要概念&#xff0c;并結合 Python 實例&#xff0c;幫助讀者更好地理解和應用這些知識。資源綁定附上完整資源供讀者參考學習&#xff0…

MCP協議:自然語言與結構化數據的雙向橋梁 ——基于JSON-RPC 2.0的標準化實踐

MCP協議&#xff1a;自然語言與結構化數據的雙向橋梁 ——基于JSON-RPC 2.0的標準化實踐 一、MCP的本質&#xff1a;標準化共識的協議框架 MCP&#xff08;Model Context Protocol&#xff09;是Anthropic于2024年提出的開放通信協議&#xff0c;其核心價值在于建立自然語言…