介紹性機器人編程教程
已發表: 2022-03-11讓我們面對現實吧,機器人很酷。 他們也將有一天統治世界,希望到那時他們會同情他們可憐的軟肉創造者(又名機器人開發者),並幫助我們建立一個充滿豐富的太空烏托邦。 我當然是在開玩笑,但只是在開玩笑。
為了對此事產生一些小的影響,我去年參加了自主機器人控制理論課程,最終我構建了一個基於 Python 的機器人模擬器,讓我能夠在一個簡單、移動、可編程的機器人上練習控制理論.
在本文中,我將展示如何使用 Python 機器人框架開發控制軟件,描述我為模擬機器人開發的控制方案,說明它如何與環境交互並實現目標,並討論一些我在此過程中遇到的機器人編程的基本挑戰。
為了學習本針對初學者的機器人編程教程,您應該具備兩件事的基本知識:
- 數學——我們將使用一些三角函數和向量
- Python——因為 Python 是更流行的基本機器人編程語言之一——我們將使用基本的 Python 庫和函數
這裡顯示的代碼片段只是整個模擬器的一部分,它依賴於類和接口,因此為了直接閱讀代碼,您可能需要一些 Python 和麵向對象編程的經驗。
最後,幫助您更好地遵循本教程的可選主題是了解什麼是狀態機以及距離傳感器和編碼器如何工作。
可編程機器人的挑戰:感知與現實,以及控制的脆弱性
所有機器人技術的基本挑戰是:永遠不可能知道環境的真實狀態。 機器人控制軟件只能根據傳感器返回的測量值來猜測現實世界的狀態。 它只能嘗試通過產生控制信號來改變現實世界的狀態。
因此,控制設計的第一步是提出現實世界的抽象,稱為模型,用它來解釋我們的傳感器讀數並做出決策。 只要現實世界按照模型的假設行事,我們就可以做出很好的猜測並施加控制。 然而,一旦現實世界偏離了這些假設,我們將不再能夠做出正確的猜測,並且將失去控制。 通常,一旦失去控制,就永遠無法恢復。 (除非一些仁慈的外力恢復它。)
這是機器人編程如此困難的關鍵原因之一。 我們經常在實驗室中看到最新研究機器人的視頻,它們在靈巧、導航或團隊合作方面表現出色,我們很想問:“為什麼不將其用於現實世界?” 好吧,下次你看到這樣的視頻,看看實驗室環境的控製程度如何。 在大多數情況下,只要環境條件保持在其內部模型的狹窄範圍內,這些機器人才能執行這些令人印象深刻的任務。 因此,機器人技術進步的一個關鍵是開發更複雜、靈活和健壯的模型——並且所述進步受到可用計算資源的限制。
[旁注:哲學家和心理學家都會注意到,生物也依賴於他們自己對感官所告訴他們的內部感知的依賴。 機器人技術的許多進步來自於觀察生物並觀察它們對意外刺激的反應。 想想看。 你的世界內部模型是什麼? 它和螞蟻不同,魚不同? (希望如此。)然而,就像螞蟻和魚一樣,它可能會過度簡化世界的某些現實。 當您對世界的假設不正確時,您可能會失去對事物的控制。 有時我們稱之為“危險”。 就像我們的小機器人在未知的宇宙中掙扎求生一樣,我們所有人也是如此。 對於機器人專家來說,這是一個強有力的洞察力。]
可編程機器人模擬器
我構建的模擬器是用 Python 編寫的,並且非常巧妙地稱為Sobot Rimulator 。 你可以在 GitHub 上找到 v1.0.0。 它沒有太多花里胡哨的東西,但它可以很好地完成一件事:提供移動機器人的準確模擬,並為有抱負的機器人專家提供一個簡單的框架來練習機器人軟件編程。 雖然擁有一個真正的機器人總是更好,但一個好的 Python 機器人模擬器更容易獲得,並且是一個很好的起點。
在現實世界的機器人中,生成控制信號的軟件(“控制器”)需要以非常高的速度運行並進行複雜的計算。 這會影響選擇最好使用哪種機器人編程語言:通常,C++ 用於這些場景,但在更簡單的機器人應用程序中,Python 是執行速度和易於開發和測試之間的一個很好的折衷方案。
我編寫的軟件模擬了一個名為 Khepera 的現實研究機器人,但它可以適應一系列具有不同尺寸和傳感器的移動機器人。 由於我嘗試對模擬器進行編程,使其盡可能與真實機器人的功能相似,因此控制邏輯可以通過最少的重構加載到真實的 Khepera 機器人中,並且它的性能與模擬機器人相同。 實現的具體功能參考 Khepera III,但它們可以很容易地適應新的 Khepera IV。
換句話說,對模擬機器人進行編程類似於對真實機器人進行編程。 如果模擬器要用於開發和評估不同的控制軟件方法,這一點至關重要。
在本教程中,我將描述Sobot Rimulator v1.0.0 附帶的機器人控制軟件架構,並提供來自 Python 源代碼的片段(為清晰起見稍作修改)。 但是,我鼓勵您深入研究源代碼並搞砸。 該模擬器已被分叉並用於控制不同的移動機器人,包括 iRobot 的 Roomba2。 同樣,請隨時 fork 項目並改進它。
機器人的控制邏輯受限於這些 Python 類/文件:
-
models/supervisor.py
這個類負責機器人周圍的模擬世界和機器人本身之間的交互。 它進化了我們的機器人狀態機並觸發控制器來計算所需的行為。 -
models/supervisor_state_machine.py
這個類代表機器人可以處於的不同狀態,這取決於它對傳感器的解釋。 -
models/controllers
目錄中的文件——這些類在已知環境狀態下實現機器人的不同行為。 具體來說,根據狀態機選擇特定的控制器。
目標
機器人和人一樣,在生活中需要一個目標。 我們的軟件控制這個機器人的目標非常簡單:它將嘗試前往預定的目標點。 這通常是任何移動機器人都應具備的基本功能,從自動駕駛汽車到機器人吸塵器。 目標的坐標在機器人被激活之前被編程到控制軟件中,但可以從監督機器人運動的額外 Python 應用程序中生成。 例如,想像一下它通過多個航路點行駛。
然而,更複雜的是,機器人的環境可能佈滿了障礙物。 機器人在前往目標的途中不得與障礙物發生碰撞。 因此,如果機器人遇到障礙物,它必須找到自己的出路,以便它可以繼續向目標前進。
可編程機器人
每個機器人都有不同的能力和控制問題。 讓我們熟悉一下我們的模擬可編程機器人。
首先要注意的是,在本指南中,我們的機器人將是一個自主移動機器人。 這意味著它將在空間中自由移動,並且它將在自己的控制下這樣做。 這與遙控機器人(非自主)或工廠機器人手臂(不可移動)形成鮮明對比。 我們的機器人必須自己弄清楚如何實現其目標並在其環境中生存。 對於新手機器人程序員來說,這被證明是一個令人驚訝的困難挑戰。
控制輸入:傳感器
機器人可以通過多種不同的方式來監控其環境。 這些可以包括接近傳感器、光傳感器、保險槓、攝像頭等任何東西。 此外,機器人可能會與外部傳感器進行通信,這些傳感器會為它們提供它們自己無法直接觀察到的信息。
我們的參考機器人配備了九個紅外傳感器——較新的型號有八個紅外和五個超聲波接近傳感器——佈置在各個方向的“裙子”中。 面對機器人正面的傳感器比背面的傳感器多,因為對於機器人來說,知道它前面的東西通常比知道它後面的東西更重要。
除了接近傳感器外,機器人還有一對跟踪車輪運動的車輪記號器。 這些使您可以跟踪每個輪子的旋轉次數,一個輪子的一整圈向前轉為 2,765 個滴答聲。 朝相反方向倒數,減少而不是增加滴答計數。 您不必擔心本教程中的具體數字,因為我們將編寫的軟件使用以米表示的行駛距離。 稍後我將向您展示如何使用簡單的 Python 函數從刻度計算它。
控制輸出:移動性
一些機器人用腿四處走動。 有些像球一樣滾動。 有些甚至像蛇一樣滑行。
我們的機器人是差動驅動機器人,這意味著它可以在兩個輪子上滾動。 當兩個輪子以相同的速度轉動時,機器人沿直線運動。 當輪子以不同的速度移動時,機器人就會轉動。 因此,控制這個機器人的運動歸結為適當地控制這兩個輪子的轉動速度。
API
在 Sobot Rimulator 中,機器人“計算機”和(模擬的)物理世界之間的分離由文件robot_supervisor_interface.py
體現,該文件定義了與“真實機器人”傳感器和電機交互的整個 API:
-
read_proximity_sensors()
以傳感器的本機格式返回一個由九個值組成的數組 read_wheel_encoders()
返回一個包含兩個值的數組,指示自開始以來的總滴答數set_wheel_drive_rates( v_l, v_r )
採用兩個值(以弧度/秒為單位)並將車輪的左右速度設置為這兩個值
該接口在內部使用一個機器人對象,該對象提供來自傳感器的數據以及移動電機或車輪的可能性。 如果你想創建一個不同的機器人,你只需要提供一個不同的 Python 機器人類,它可以被相同的接口使用,其餘的代碼(控制器、主管和模擬器)將開箱即用!
模擬器
由於您將在現實世界中使用真正的機器人,而不會過多關注所涉及的物理定律,因此您可以忽略機器人的模擬方式,直接跳到控制器軟件的編程方式,因為它們幾乎相同在現實世界和模擬之間。 但如果你好奇,我會在這裡簡單介紹一下。
文件world.py
是一個代表模擬世界的 Python 類,裡面有機器人和障礙物。 這個類中的 step 函數通過以下方式處理我們的簡單世界:
- 將物理規則應用於機器人的運動
- 考慮與障礙物的碰撞
- 為機器人傳感器提供新價值
最後,它調用負責執行機器人大腦軟件的機器人主管。
step 函數在循環中執行,以便robot.step_motion()
使用監督者在上一個模擬步驟中計算的輪速移動機器人。
# step the simulation through one time interval def step( self ): dt = self.dt # step all the robots for robot in self.robots: # step robot motion robot.step_motion( dt ) # apply physics interactions self.physics.apply_physics() # NOTE: The supervisors must run last to ensure they are observing the "current" world # step all of the supervisors for supervisor in self.supervisors: supervisor.step( dt ) # increment world time self.world_time += dt
apply_physics()
函數在內部更新機器人接近傳感器的值,以便主管能夠估計當前模擬步驟的環境。 相同的概念適用於編碼器。
一個簡單的模型
首先,我們的機器人將有一個非常簡單的模型。 它將對世界做出許多假設。 一些重要的包括:
- 地勢總是平坦的
- 障礙永遠不是圓的
- 車輪從不打滑
- 沒有什麼可以推動機器人
- 傳感器永遠不會出現故障或給出錯誤讀數
- 當他們被告知時,車輪總是轉動
儘管這些假設中的大多數在類似房屋的環境中都是合理的,但可能存在圓形障礙物。 我們的避障軟件有一個簡單的實現,並沿著障礙物的邊界繞過它們。 我們將提示讀者如何通過額外的檢查來改進機器人的控制框架,以避免圓形障礙物。
控制迴路
我們現在將進入控制軟件的核心,並解釋我們想要在機器人內部編程的行為。 可以在這個框架中添加額外的行為,你應該在閱讀完之後嘗試自己的想法! 基於行為的機器人軟件是 20 多年前提出的,它仍然是移動機器人的強大工具。 例如,2007 年 DARPA 城市挑戰賽中使用了一組行為,這是首個自動駕駛汽車競賽!
機器人是一個動態系統。 機器人的狀態、傳感器的讀數以及控制信號的影響是不斷變化的。 控制事件發生的方式涉及以下三個步驟:
- 應用控制信號。
- 測量結果。
- 生成經過計算的新控制信號,使我們更接近目標。
這些步驟一遍又一遍地重複,直到我們實現目標。 我們每秒可以執行此操作的次數越多,我們對系統的控制就越精細。 Sobot Rimulator 機器人每秒重複這些步驟 20 次 (20 Hz),但許多機器人必須每秒執行數千或數百萬次才能獲得足夠的控制。 請記住我們之前關於針對不同機器人系統和速度要求的不同機器人編程語言的介紹。
一般來說,每次我們的機器人使用其傳感器進行測量時,它都會使用這些測量值來更新其對世界狀態的內部估計——例如,與目標的距離。 它將這個狀態與它想要的狀態的參考值進行比較(對於距離,它希望它為零),併計算期望狀態和實際狀態之間的誤差。 一旦知道了這些信息,生成新的控制信號就可以簡化為最小化誤差的問題,該誤差最終將使機器人朝著目標移動。
一個絕妙的技巧:簡化模型
為了控制我們想要編程的機器人,我們必須向左輪發送一個信號,告訴它轉多快,並向右輪發送一個單獨的信號,告訴它轉多快。 我們稱這些信號為 v L和v R 。 然而,不斷地根據v L和v R來思考是非常麻煩的。 而不是問,“我們希望左輪轉多快,我們希望右輪轉多快?” 更自然地問,“我們希望機器人前進多快,我們希望它轉動多快,或者改變它的航向?” 我們將這些參數稱為速度v和角(旋轉)速度ω (讀作“omega”)。 事實證明,我們可以將整個模型建立在v和ω而不是v L和v R上,並且只有在我們確定了我們希望我們的編程機器人如何移動之後,才能將這兩個值數學轉換為我們需要的v L和v R實際控制機器人輪子。 這被稱為控制的獨輪車模型。
這是在supervisor.py
中實現最終轉換的 Python 代碼。 請注意,如果ω為 0,則兩個車輪將以相同的速度轉動:
# generate and send the correct commands to the robot def _send_robot_commands( self ): # ... v_l, v_r = self._uni_to_diff( v, omega ) self.robot.set_wheel_drive_rates( v_l, v_r ) def _uni_to_diff( self, v, omega ): # v = translational velocity (m/s) # omega = angular velocity (rad/s) R = self.robot_wheel_radius L = self.robot_wheel_base_length v_l = ( (2.0 * v) - (omega*L) ) / (2.0 * R) v_r = ( (2.0 * v) + (omega*L) ) / (2.0 * R) return v_l, v_r
估計狀態:機器人,了解自己
使用它的傳感器,機器人必須嘗試估計環境的狀態以及它自己的狀態。 這些估計永遠不會是完美的,但它們必須相當好,因為機器人將根據這些估計做出所有決定。 僅使用其接近傳感器和車輪記號器,它必須嘗試猜測以下內容:
- 障礙物的方向
- 與障礙物的距離
- 機器人的位置
- 機器人的航向
前兩個屬性由接近傳感器讀數確定,並且相當簡單。 API 函數read_proximity_sensors()
返回一個包含九個值的數組,每個傳感器一個值。 例如,我們提前知道第七個讀數對應於指向機器人右側 75 度的傳感器。
因此,如果該值顯示對應於 0.1 米距離的讀數,我們就知道在 0.1 米外,向左 75 度處有障礙物。 如果沒有障礙物,傳感器將返回其最大範圍 0.2 米的讀數。 因此,如果我們在傳感器 7 上讀取 0.2 米,我們將假設該方向實際上沒有障礙物。
由於紅外傳感器的工作方式(測量紅外反射),它們返回的數字是檢測到的實際距離的非線性變換。 因此,用於確定指示距離的 Python 函數必須將這些讀數轉換為米。 這是在supervisor.py
中完成的,如下所示:
# update the distances indicated by the proximity sensors def _update_proximity_sensor_distances( self ): self.proximity_sensor_distances = [ 0.02-( log(readval/3960.0) )/30.0 for readval in self.robot.read_proximity_sensors() ]
同樣,在這個 Python 機器人框架中,我們有一個特定的傳感器模型,而在現實世界中,傳感器附帶的軟件應該提供類似的從非線性值到米的轉換功能。
確定機器人的位置和航向(在機器人編程中統稱為位姿)更具挑戰性。 我們的機器人使用里程計來估計其姿勢。 這就是車輪計時的用武之地。通過測量自控制循環的最後一次迭代以來每個車輪轉動了多少,可以很好地估計機器人的姿勢是如何變化的——但前提是變化很小。
這就是在現實世界的機器人中非常頻繁地迭代控制迴路很重要的原因之一,其中驅動車輪的電機可能並不完美。 如果我們等了太久才測量車輪的刻度,兩個車輪都可以做很多事情,而且我們無法估計我們最終到達了哪裡。
鑑於我們當前的軟件模擬器,我們可以負擔得起以 20 Hz 的頻率運行里程計計算——與控制器的頻率相同。 但是讓一個單獨的 Python 線程運行得更快以捕捉代碼的較小移動可能是一個好主意。
下面是supervisor.py
中更新機器人姿態估計的完整里程計函數。 請注意,機器人的姿態由坐標x
和y
以及航向theta
組成,航向 theta 以正 X 軸的弧度為單位測量。 正x
指向東,正y
指向北。 因此,航向為0
表示機器人直接面向東方。 機器人始終假定其初始姿態為(0, 0), 0
。
# update the estimated position of the robot using it's wheel encoder readings def _update_odometry( self ): R = self.robot_wheel_radius N = float( self.wheel_encoder_ticks_per_revolution ) # read the wheel encoder values ticks_left, ticks_right = self.robot.read_wheel_encoders() # get the difference in ticks since the last iteration d_ticks_left = ticks_left - self.prev_ticks_left d_ticks_right = ticks_right - self.prev_ticks_right # estimate the wheel movements d_left_wheel = 2*pi*R*( d_ticks_left / N ) d_right_wheel = 2*pi*R*( d_ticks_right / N ) d_center = 0.5 * ( d_left_wheel + d_right_wheel ) # calculate the new pose prev_x, prev_y, prev_theta = self.estimated_pose.scalar_unpack() new_x = prev_x + ( d_center * cos( prev_theta ) ) new_y = prev_y + ( d_center * sin( prev_theta ) ) new_theta = prev_theta + ( ( d_right_wheel - d_left_wheel ) / self.robot_wheel_base_length ) # update the pose estimate with the new values self.estimated_pose.scalar_update( new_x, new_y, new_theta ) # save the current tick count for the next iteration self.prev_ticks_left = ticks_left self.prev_ticks_right = ticks_right
現在我們的機器人能夠生成對現實世界的良好估計,讓我們使用這些信息來實現我們的目標。
Python 機器人編程方法:Go-to-Goal 行為
在這個編程教程中,我們的小機器人存在的最高目的是達到目標。 那麼我們如何讓輪子轉動才能到達那裡呢? 讓我們從稍微簡化我們的世界觀開始,並假設路上沒有障礙。
然後,這變成了一項簡單的任務,並且可以很容易地用 Python 進行編程。 如果我們在面對目標的同時前進,我們就會到達那裡。 由於我們的里程計,我們知道我們當前的坐標和航向是什麼。 我們也知道目標的坐標是什麼,因為它們是預先編程的。 因此,使用一點線性代數,我們可以確定從我們的位置到目標的向量,如go_to_goal_controller.py
:
# return a go-to-goal heading vector in the robot's reference frame def calculate_gtg_heading_vector( self ): # get the inverse of the robot's pose robot_inv_pos, robot_inv_theta = self.supervisor.estimated_pose().inverse().vector_unpack() # calculate the goal vector in the robot's reference frame goal = self.supervisor.goal() goal = linalg.rotate_and_translate_vector( goal, robot_inv_theta, robot_inv_pos ) return goal
請注意,我們在機器人的參考系中獲取到目標的向量,而不是在世界坐標中。 如果目標位於機器人參考系中的 X 軸上,則意味著它就在機器人的正前方。 因此,這個向量與 X 軸的角度是我們的航向和我們想要的航向之間的差異。 換句話說,這是我們當前狀態與我們希望當前狀態之間的誤差。 因此,我們想要調整我們的轉彎率ω ,以便我們的航向和目標之間的角度將變為 0。我們想要最小化誤差:

# calculate the error terms theta_d = atan2( self.gtg_heading_vector[1], self.gtg_heading_vector[0] ) # calculate angular velocity omega = self.kP * theta_d
上述控制器 Python 實現片段中的self.kP
是一種控制增益。 它是一個係數,它決定了我們轉彎的速度與我們離目標的距離成正比。 如果我們的航向錯誤是0
,那麼轉彎率也是0
。 在go_to_goal_controller.py
文件中的真正 Python 函數中,您會看到更多類似的增益,因為我們使用了 PID 控制器而不是簡單的比例係數。
現在我們有了角速度ω ,我們如何確定前進速度v ? 一個好的一般經驗法則是您可能本能地知道的:如果我們不轉彎,我們可以全速前進,然後我們轉彎越快,我們應該越慢。 這通常有助於我們保持系統穩定並在模型範圍內運行。 因此, v是ω的函數。 在go_to_goal_controller.py
中,等式是:
# calculate translational velocity # velocity is v_max when omega is 0, # drops rapidly to zero as |omega| rises v = self.supervisor.v_max() / ( abs( omega ) + 1 )**0.5
詳細說明這個公式的一個建議是考慮到我們通常在接近目標時放慢速度,以便以零速度到達它。 這個公式將如何變化? 它必須以某種方式將v_max()
替換為與距離成比例的東西。 好的,我們幾乎完成了一個控制循環。 剩下要做的就是將這兩個獨輪車模型參數轉換為不同的車輪速度,並將信號發送到車輪。 這是一個機器人在 go-to-goal 控制器下的軌跡示例,沒有障礙物:
正如我們所看到的,目標向量是我們進行控制計算的有效參考。 它是“我們想去的地方”的內部表示。 正如我們將看到的,去到目標和其他行為之間的唯一主要區別是,有時朝著目標去是一個壞主意,所以我們必須計算一個不同的參考向量。
Python 機器人編程方法:避免障礙行為
當那個方向有障礙物時朝著目標前進就是一個很好的例子。 讓我們嘗試編寫一個控制法則,讓機器人避開它們,而不是一頭扎進我們的方式。
為了簡化場景,讓我們現在完全忘記目標點,將以下目標作為我們的目標:當我們面前沒有障礙物時,繼續前進。 當遇到障礙物時,轉身遠離它,直到它不再在我們面前。
因此,當我們面前沒有障礙物時,我們希望我們的參考向量簡單地指向前方。 然後ω將為零, v將是最大速度。 然而,一旦我們用接近傳感器檢測到障礙物,我們希望參考矢量指向遠離障礙物的任何方向。 這將導致ω向上射擊以使我們遠離障礙物,並導致v下降以確保我們不會在此過程中意外撞到障礙物。
生成所需參考向量的一種巧妙方法是將我們的九個鄰近讀數轉換為向量,並進行加權求和。 當沒有檢測到障礙物時,向量將對稱求和,從而產生一個根據需要指向正前方的參考向量。 但是,如果右側的傳感器檢測到障礙物,它將對總和貢獻一個較小的矢量,結果將是一個向左移動的參考矢量。
對於具有不同傳感器位置的通用機器人,可以應用相同的想法,但當傳感器在機器人前後對稱時,可能需要改變重量和/或額外注意,因為加權和可能會變為零.
這是在avoid_obstacles_controller.py
中執行此操作的代碼:
# sensor gains (weights) self.sensor_gains = [ 1.0+( (0.4*abs(p.theta)) / pi ) for p in supervisor.proximity_sensor_placements() ] # ... # return an obstacle avoidance vector in the robot's reference frame # also returns vectors to detected obstacles in the robot's reference frame def calculate_ao_heading_vector( self ): # initialize vector obstacle_vectors = [ [ 0.0, 0.0 ] ] * len( self.proximity_sensor_placements ) ao_heading_vector = [ 0.0, 0.0 ] # get the distances indicated by the robot's sensor readings sensor_distances = self.supervisor.proximity_sensor_distances() # calculate the position of detected obstacles and find an avoidance vector robot_pos, robot_theta = self.supervisor.estimated_pose().vector_unpack() for i in range( len( sensor_distances ) ): # calculate the position of the obstacle sensor_pos, sensor_theta = self.proximity_sensor_placements[i].vector_unpack() vector = [ sensor_distances[i], 0.0 ] vector = linalg.rotate_and_translate_vector( vector, sensor_theta, sensor_pos ) obstacle_vectors[i] = vector # store the obstacle vectors in the robot's reference frame # accumulate the heading vector within the robot's reference frame ao_heading_vector = linalg.add( ao_heading_vector, linalg.scale( vector, self.sensor_gains[i] ) ) return ao_heading_vector, obstacle_vectors
使用生成的ao_heading_vector
作為機器人嘗試匹配的參考,以下是僅使用避免障礙控制器在模擬中運行機器人軟件的結果,完全忽略目標點。 機器人漫無目的地四處彈跳,但它從不與障礙物相撞,甚至設法在一些非常狹窄的空間內航行:
Python機器人編程方法:混合自動機(行為狀態機)
到目前為止,我們已經單獨描述了兩種行為——達到目標和避免障礙。 兩者都發揮了令人欽佩的作用,但為了在充滿障礙的環境中成功實現目標,我們需要將它們結合起來。
我們將開發的解決方案在於一類具有非常酷的混合自動機名稱的機器。 混合自動機編程有幾種不同的行為或模式,以及監督狀態機。 監督狀態機在離散時間(當目標實現或環境突然變化太大時)從一種模式切換到另一種模式,而每個行為都使用傳感器和輪子對環境變化做出持續反應。 該解決方案被稱為混合解決方案,因為它以離散和連續的方式發展。
我們的 Python 機器人框架在文件supervisor_state_machine.py
中實現了狀態機。
配備我們兩個方便的行為,一個簡單的邏輯不言自明:當沒有檢測到障礙物時,使用 go-to-goal 行為。 當檢測到障礙物時,切換到避免障礙物行為,直到不再檢測到障礙物。
然而事實證明,這種邏輯會產生很多問題。 當這個系統遇到障礙物時,它傾向於做的是轉身離開它,然後一旦它離開它,就立即轉身再次撞上它。 結果是一個無限循環的快速切換,使機器人無用。 In the worst case, the robot may switch between behaviors with every iteration of the control loop—a state known as a Zeno condition .
There are multiple solutions to this problem, and readers that are looking for deeper knowledge should check, for example, the DAMN software architecture.
What we need for our simple simulated robot is an easier solution: One more behavior specialized with the task of getting around an obstacle and reaching the other side.
Python Robot Programming Methods: Follow-Wall Behavior
Here's the idea: When we encounter an obstacle, take the two sensor readings that are closest to the obstacle and use them to estimate the surface of the obstacle. Then, simply set our reference vector to be parallel to this surface. Keep following this wall until A) the obstacle is no longer between us and the goal, and B) we are closer to the goal than we were when we started. Then we can be certain we have navigated the obstacle properly.
With our limited information, we can't say for certain whether it will be faster to go around the obstacle to the left or to the right. To make up our minds, we select the direction that will move us closer to the goal immediately. To figure out which way that is, we need to know the reference vectors of the go-to-goal behavior and the avoid-obstacle behavior, as well as both of the possible follow-wall reference vectors. Here is an illustration of how the final decision is made (in this case, the robot will choose to go left):
Determining the follow-wall reference vectors turns out to be a bit more involved than either the avoid-obstacle or go-to-goal reference vectors. Take a look at the Python code in follow_wall_controller.py
to see how it's done.
Final Control Design
The final control design uses the follow-wall behavior for almost all encounters with obstacles. However, if the robot finds itself in a tight spot, dangerously close to a collision, it will switch to pure avoid-obstacles mode until it is a safer distance away, and then return to follow-wall. Once obstacles have been successfully negotiated, the robot switches to go-to-goal. Here is the final state diagram, which is programmed inside the supervisor_state_machine.py
:
Here is the robot successfully navigating a crowded environment using this control scheme:
An additional feature of the state machine that you can try to implement is a way to avoid circular obstacles by switching to go-to-goal as soon as possible instead of following the obstacle border until the end (which does not exist for circular objects!)
Tweak, Tweak, Tweak: Trial and Error
The control scheme that comes with Sobot Rimulator is very finely tuned. It took many hours of tweaking one little variable here, and another equation there, to get it to work in a way I was satisfied with. Robotics programming often involves a great deal of plain old trial-and-error. Robots are very complex and there are few shortcuts to getting them to behave optimally in a robot simulator environment…at least, not much short of outright machine learning, but that's a whole other can of worms.
I encourage you to play with the control variables in Sobot Rimulator and observe and attempt to interpret the results. Changes to the following all have profound effects on the simulated robot's behavior:
- The error gain
kP
in each controller - The sensor gains used by the avoid-obstacles controller
- The calculation of v as a function of ω in each controller
- The obstacle standoff distance used by the follow-wall controller
- The switching conditions used by
supervisor_state_machine.py
- Pretty much anything else
When Programmable Robots Fail
We've done a lot of work to get to this point, and this robot seems pretty clever. Yet, if you run Sobot Rimulator through several randomized maps, it won't be long before you find one that this robot can't deal with. Sometimes it drives itself directly into tight corners and collides. Sometimes it just oscillates back and forth endlessly on the wrong side of an obstacle. Occasionally it is legitimately imprisoned with no possible path to the goal. After all of our testing and tweaking, sometimes we must come to the conclusion that the model we are working with just isn't up to the job, and we have to change the design or add functionality.
In the mobile robot universe, our little robot's “brain” is on the simpler end of the spectrum. Many of the failure cases it encounters could be overcome by adding some more advanced software to the mix. More advanced robots make use of techniques such as mapping , to remember where it's been and avoid trying the same things over and over; heuristics , to generate acceptable decisions when there is no perfect decision to be found; and machine learning , to more perfectly tune the various control parameters governing the robot's behavior.
A Sample of What's to Come
Robots are already doing so much for us, and they are only going to be doing more in the future. While even basic robotics programming is a tough field of study requiring great patience, it is also a fascinating and immensely rewarding one.
In this tutorial, we learned how to develop reactive control software for a robot using the high-level programming language Python. But there are many more advanced concepts that can be learned and tested quickly with a Python robot framework similar to the one we prototyped here. I hope you will consider getting involved in the shaping of things to come!
Acknowledgement: I would like to thank Dr. Magnus Egerstedt and Jean-Pierre de la Croix of the Georgia Institute of Technology for teaching me all this stuff, and for their enthusiasm for my work on Sobot Rimulator.