ロボットプログラミング入門チュートリアル

公開: 2022-03-11
編集者のメモ:2018年10月16日に、この記事は最新のテクノロジーで動作するようにオーバーホールされました。

それに直面しましょう、ロボットはかっこいいです。 彼らはまたいつか世界を動かし、うまくいけば、その時に彼らは彼らの貧弱な柔らかな肉質のクリエーター(別名ロボット開発者)を憐れみ、私たちがたくさんのスペースユートピアを構築するのを手伝ってくれるでしょう。 もちろん冗談ですが、ちょっとだけです。

この問題に少し影響を与えたいという野心で、昨年、自律型ロボット制御理論のコースを受講しました。その結果、Pythonベースのロボットシミュレーターを構築して、シンプルでモバイルなプログラム可能なロボットで制御理論を実践できるようになりました。 。

この記事では、Pythonロボットフレームワークを使用して制御ソフトウェアを開発する方法、シミュレートされたロボット用に開発した制御スキームについて説明し、環境と相互作用して目標を達成する方法を示し、いくつかの途中で遭遇したロボットプログラミングの基本的な課題。

初心者向けのロボットプログラミングに関するこのチュートリアルに従うには、次の2つの基本的な知識が必要です。

  • 数学—いくつかの三角関数とベクトルを使用します
  • Python — Pythonは最も人気のある基本的なロボットプログラミング言語の1つであるため—基本的なPythonライブラリと関数を利用します

ここに示されているコードのスニペットは、クラスとインターフェイスに依存するシミュレーター全体の一部にすぎないため、コードを直接読み取るには、Pythonとオブジェクト指向プログラミングの経験が必要になる場合があります。

最後に、このチュートリアルをよりよく理解するのに役立つオプションのトピックは、ステートマシンとは何か、および範囲センサーとエンコーダーがどのように機能するかを知ることです。

プログラム可能なロボットの挑戦:知覚対現実、および制御の脆弱性

すべてのロボット工学の基本的な課題はこれです:環境の本当の状態を知ることは不可能です。 ロボット制御ソフトウェアは、センサーから返された測定値に基づいて現実世界の状態を推測することしかできません。 制御信号の生成を通じてのみ、現実世界の状態を変更しようとすることができます。

この図は、Pythonロボットプログラミングを実践する際の物理ロボットとコンピューターコントロール間の相互作用を示しています。

ロボット制御ソフトウェアは、センサーから返された測定値に基づいて現実世界の状態を推測することしかできません。

したがって、制御設計の最初のステップの1つは、モデルと呼ばれる現実世界の抽象化を考え出すことです。これを使用して、センサーの読み取り値を解釈し、決定を下します。 現実の世界がモデルの仮定に従って動作する限り、私たちは適切な推測を行い、制御を行うことができます。 しかし、現実の世界がこれらの仮定から逸脱するとすぐに、私たちは適切な推測を行うことができなくなり、制御が失われます。 多くの場合、制御が失われると、それを取り戻すことはできません。 (慈悲深い外力がそれを回復しない限り。)

これが、ロボット工学プログラミングが非常に難しい主な理由の1つです。 研究室で最新の研究用ロボットのビデオをよく見かけ、器用さ、ナビゲーション、またはチームワークの素晴らしい偉業を実行しています。「なぜこれが現実の世界で使用されないのですか?」と尋ねたくなります。 さて、次にそのようなビデオを見るときは、ラボ環境がどれほど高度に制御されているかを見てください。 ほとんどの場合、これらのロボットは、環境条件が内部モデルの狭い範囲内にある限り、これらの印象的なタスクを実行することしかできません。 したがって、ロボット工学の進歩の1つの鍵は、より複雑で、柔軟性があり、堅牢なモデルの開発です。この進歩は、利用可能な計算リソースの制限の影響を受けます。

ロボット工学の進歩の鍵の1つは、より複雑で、柔軟性があり、堅牢なモデルの開発です。

[補足:哲学者と心理学者は同様に、生き物もまた、感覚が彼らに伝えていることに対する彼ら自身の内部知覚への依存に苦しんでいることに気付くでしょう。 ロボット工学の多くの進歩は、生き物を観察し、予期しない刺激にどのように反応するかを見ることから生まれます。 考えてみてください。 あなたの世界の内部モデルは何ですか? アリや魚とは違うの? (うまくいけば。)しかし、アリや魚のように、それは世界のいくつかの現実を過度に単純化する可能性があります。 世界についてのあなたの仮定が正しくないとき、それはあなたに物事のコントロールを失う危険を冒す可能性があります。 これを「危険」と呼ぶこともあります。 私たちの小さなロボットが未知の宇宙に対して生き残るのに苦労しているのと同じように、私たち全員もそうします。 これはロボット工学者にとって強力な洞察です。]

プログラム可能なロボットシミュレーター

私が作成したシミュレーターはPythonで書かれており、非常に巧妙にSobotRimulatorと呼ばれています。 v1.0.0はGitHubにあります。 ベルやホイッスルはあまりありませんが、1つのことを非常にうまく行うように構築されています。それは、移動ロボットの正確なシミュレーションを提供し、意欲的なロボット工学者にロボットソフトウェアプログラミングを実践するためのシンプルなフレームワークを提供することです。 実際のロボットで遊ぶ方が常に良いのですが、優れたPythonロボットシミュレーターははるかにアクセスしやすく、始めるのに最適な場所です。

実世界のロボットでは、制御信号を生成するソフトウェア(「コントローラー」)が非常に高速で実行され、複雑な計算を行う必要があります。 これは、使用するのに最適なロボットプログラミング言語の選択に影響します。通常、これらの種類のシナリオにはC ++が使用されますが、より単純なロボットアプリケーションでは、Pythonは実行速度と開発およびテストの容易さの間の非常に優れた妥協点です。

私が書いたソフトウェアは、Kheperaと呼ばれる実際の研究用ロボットをシミュレートしていますが、さまざまな寸法とセンサーを備えたさまざまな移動ロボットに適合させることができます。 実際のロボットの機能と可能な限り類似したシミュレーターをプログラムしようとしたので、制御ロジックを最小限のリファクタリングで実際のKheperaロボットにロードでき、シミュレートされたロボットと同じように動作します。 実装された特定の機能はKheperaIIIを参照していますが、新しいKheperaIVに簡単に適合させることができます。

言い換えれば、シミュレートされたロボットのプログラミングは、実際のロボットのプログラミングに似ています。 これは、シミュレータがさまざまな制御ソフトウェアのアプローチを開発および評価するために役立つ場合に重要です。

このチュートリアルでは、 Sobot Rimulatorのv1.0.0に付属するロボット制御ソフトウェアアーキテクチャについて説明し、Pythonソースからのスニペットを提供します(わかりやすくするために少し変更を加えています)。 ただし、ソースに飛び込んで混乱させることをお勧めします。 シミュレーターはフォークされ、iRobotのRoomba2を含むさまざまな移動ロボットを制御するために使用されています。 同様に、プロジェクトをフォークして改善してください。

ロボットの制御ロジックは、次のPythonクラス/ファイルに制限されています。

  • models/supervisor.py —このクラスは、ロボットの周りのシミュレートされた世界とロボット自体の間の相互作用を担当します。 ロボットステートマシンを進化させ、コントローラーをトリガーして目的の動作を計算します。
  • models/supervisor_state_machine.py /supervisor_state_machine.py —このクラスは、センサーの解釈に応じて、ロボットが存在できるさまざまな状態を表します。
  • models/controllersディレクトリ内のファイル-これらのクラスは、環境の既知の状態が与えられた場合に、ロボットのさまざまな動作を実装します。 特に、ステートマシンに応じて特定のコントローラーが選択されます。

目標

ロボットは、人と同じように、人生の目的を必要としています。 このロボットを制御するソフトウェアの目標は非常に単純です。それは、所定の目標点に到達しようとします。 これは通常、自動運転車からロボット掃除機まで、あらゆる移動ロボットが持つべき基本的な機能です。 目標の座標は、ロボットがアクティブ化される前に制御ソフトウェアにプログラムされますが、ロボットの動きを監視する追加のPythonアプリケーションから生成することもできます。 たとえば、複数のウェイポイントを通過することを考えてみてください。

ただし、問題を複雑にするために、ロボットの環境には障害物が散らばっている可能性があります。 ロボットは、ゴールに向かう途中で障害物と衝突してはなりません。 したがって、ロボットが障害物に遭遇した場合、ロボットが目標に向かって進むことができるように、ロボットはその道を見つける必要があります。

プログラム可能なロボット

すべてのロボットには、さまざまな機能と制御の問題があります。 シミュレートされたプログラム可能なロボットに慣れましょう。

最初に注意することは、このガイドでは、私たちのロボットは自律移動ロボットになるということです。 これは、それが宇宙を自由に動き回ることを意味し、それはそれ自身の制御の下でそうするでしょう。 これは、たとえば、リモートコントロールロボット(自律型ではない)または工場のロボットアーム(モバイルではない)とは対照的です。 私たちのロボットは、その目標を達成し、その環境で生き残る方法を自分で理解する必要があります。 これは、初心者のロボット工学プログラマーにとって驚くほど難しい課題であることがわかります。

制御入力:センサー

ロボットがその環境を監視するために装備することができる多くの異なる方法があります。 これらには、近接センサー、光センサー、バンパー、カメラなど、あらゆるものが含まれます。 さらに、ロボットは、ロボット自身が直接観察できない情報をロボットに与える外部センサーと通信する場合があります。

私たちの参照ロボットには、 9つの赤外線センサーが装備されています。新しいモデルには、8つの赤外線センサーと5つの超音波近接センサーがあり、あらゆる方向に「スカート」に配置されています。 ロボットは通常、ロボットの後ろよりも前にあるものを知ることが重要であるため、ロボットの後ろよりも前に面しているセンサーの方が多くなります。

近接センサーに加えて、ロボットには、ホイールの動きを追跡する1対のホイールティッカーがあります。 これらを使用すると、各ホイールの回転数を追跡できます。ホイールを1回転させると2,765ティックになります。 逆方向にカウントを逆方向に回し、ティックカウントを増やすのではなく減らします。 このチュートリアルで作成するソフトウェアはメートルで表された移動距離を使用するため、このチュートリアルで特定の数値について心配する必要はありません。 後で、簡単なPython関数を使用してダニから計算する方法を説明します。

制御出力:モビリティ

一部のロボットは脚を動かします。 ボールのように転がる人もいます。 ヘビのようにずるずる人もいます。

私たちのロボットは差動駆動ロボットです。つまり、2つの車輪で回転します。 両方の車輪が同じ速度で回転すると、ロボットは直線的に移動します。 車輪が異なる速度で動くと、ロボットは回転します。 したがって、このロボットの動きを制御することは、これら2つの車輪のそれぞれが回転する速度を適切に制御することになります。

API

Sobot Rimulatorでは、ロボットの「コンピューター」と(シミュレートされた)物理的な世界の分離は、ファイルrobot_supervisor_interface.pyによって具体化されます。このファイルは、「実際のロボット」のセンサーやモーターと対話するためのAPI全体を定義します。

  • read_proximity_sensors()は、センサーのネイティブ形式で9つの値の配列を返します
  • read_wheel_encoders()は、開始以降の合計ティックを示す2つの値の配列を返します
  • set_wheel_drive_rates( v_l, v_r )は2つの値(ラジアン/秒)を取り、ホイールの左右の速度をこれら2つの値に設定します

このインターフェースは、センサーからのデータとモーターまたはホイールを動かす可能性を提供するロボットオブジェクトを内部的に使用します。 別のロボットを作成する場合は、同じインターフェイスで使用できる別の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アーバンチャレンジで一連の行動が使用されました。

ロボットは動的システムです。 ロボットの状態、センサーの読み取り値、および制御信号の影響は常に変化しています。 イベントの再生方法を制御するには、次の3つの手順が必要です。

  1. 制御信号を適用します。
  2. 結果を測定します。
  3. 私たちを目標に近づけるために計算された新しい制御信号を生成します。

これらのステップは、目標を達成するまで何度も繰り返されます。 1秒間にこれを実行できる回数が増えるほど、システムをより細かく制御できるようになります。 Sobot Rimulatorロボットは、これらの手順を1秒間に20回(20 Hz)繰り返しますが、多くのロボットは、適切な制御を行うために、これを1秒間に数千回または数百万回実行する必要があります。 さまざまなロボットシステムと速度要件に対応するさまざまなロボットプログラミング言語についての以前の紹介を思い出してください。

一般に、ロボットがセンサーで測定を行うたびに、ロボットはこれらの測定値を使用して、世界の状態の内部推定値(たとえば、目標からの距離)を更新します。 この状態を、状態が必要な状態参照値と比較し(距離については、ゼロにする必要があります)、目的の状態と実際の状態の間の誤差を計算します。 この情報がわかれば、新しい制御信号の生成を、最終的にロボットを目標に向けて動かすエラーを最小限に抑えるという問題に減らすことができます。

気の利いたトリック:モデルの簡素化

プログラムするロボットを制御するには、左輪に信号を送信して回転速度を通知し、右輪に別の信号を送信て回転速度を通知する必要があります。 これらの信号をvLおよびvR呼びましょう。 ただし、 vLvRの観点から常に考えることは、非常に面倒です。 「左の車輪をどれだけ速く回転させたいのか、右の車輪をどれだけ速く回転させたいのか」と尋ねる代わりに。 「ロボットをどれだけ速く前進させ、どれだけ速く回転させたり、方向を変えたりしたいのか」と尋ねるのがより自然です。 これらのパラメータを速度vおよび角(回転)速度ωと呼びましょう(「オメガ」と読みます)。 モデル全体をvLvRではなくvωに基づいて作成できることがわかりました。プログラムされたロボットの移動方法を決定したら、これら2つ値を必要なvLvRに数学的に変換します。実際にロボットの車輪を制御します。 これは、制御の一輪車モデルとして知られています。

ロボットプログラミングでは、一輪車モデルと差動駆動モデルの違いを理解することが重要です。

これは、 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

推定状態:ロボット、汝自身を知れ

ロボットはセンサーを使用して、環境の状態と自身の状態を推定する必要があります。 これらの見積もりは決して完璧ではありませんが、ロボットはこれらの見積もりに基づいてすべての決定を行うため、かなり適切である必要があります。 近接センサーとホイールティッカーだけを使用して、次のことを推測する必要があります。

  • 障害物への方向
  • 障害物からの距離
  • ロボットの位置
  • ロボットの見出し

最初の2つのプロパティは、近接センサーの読み取り値によって決定され、かなり簡単です。 API関数read_proximity_sensors()は、センサーごとに1つずつ、9つの値の配列を返します。 たとえば、7番目の読み取り値は、ロボットの右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ロボットフレームワークには特定のセンサーモデルがありますが、現実の世界では、センサーには、非線形値からメートルへの同様の変換関数を提供するソフトウェアが付属しています。

ロボットの位置と方向(ロボット工学プログラミングではポーズとも呼ばれます)を決定することは、やや困難です。 私たちのロボットは、オドメトリを使用してその姿勢を推定します。 これがホイールティッカーの出番です。制御ループの最後の反復以降に各ホイールがどれだけ回転したかを測定することにより、ロボットのポーズがどのように変化したかを正確に推定できますが、変化が小さい場合に限ります。

これが、車輪を動かすモーターが完全ではない可能性がある実際のロボットで制御ループを頻繁に繰り返すことが重要である理由の1つです。 ホイールティッカーを測定するのに時間がかかりすぎると、両方のホイールがかなり多くのことを行った可能性があり、最終的にどこに到達したかを推定することは不可能になります。

現在のソフトウェアシミュレーターを考えると、コントローラーと同じ周波数である20Hzでオドメトリ計算を実行する余裕があります。 ただし、ティッカーの小さな動きをキャッチするために、別のPythonスレッドをより高速に実行することをお勧めします。

以下は、ロボットの姿勢推定を更新するsupervisor.pyの完全なオドメトリ機能です。 ロボットのポーズは、座標xy 、および正のX軸からラジアンで測定される方位thetaで構成されていることに注意してください。 正の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ロボットプログラミングメソッド:目標達成行動

このプログラミングチュートリアルでの私たちの小さなロボットの存在の最大の目的は、ゴールポイントに到達することです。 では、どうやって車輪を回転させてそこに到達させるのでしょうか? まず、世界観を少し単純化して、障害物がないと仮定しましょう。

これは単純なタスクになり、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()を距離に比例するものに置き換える必要があります。 OK、ほぼ1つの制御ループが完了しました。 あとは、これら2つの一輪車モデルのパラメーターを差動ホイール速度に変換し、信号をホイールに送信するだけです。 これは、障害物のない、go-to-goalコントローラーの下でのロボットの軌道の例です。

これは、プログラムされたロボットの軌道の例です。

ご覧のとおり、目標へのベクトルは、制御計算の基礎となる効果的なリファレンスです。 これは、「行きたい場所」の内部表現です。 後で説明するように、go-to-goalと他の動作の唯一の大きな違いは、目標に向かって進むことは悪い考えである場合があるため、別の参照ベクトルを計算する必要があることです。

Pythonロボットプログラミングメソッド:回避-障害物の動作

その方向に障害物があるときにゴールに向かって行くことはその好例です。 私たちのやり方で物事に真っ向からぶつかる代わりに、ロボットにそれらを回避させる制御法則をプログラムしてみましょう。

シナリオを単純化するために、ゴールポイントを完全に忘れて、次の目標を立てましょう。目の前に障害物がない場合は、前進します。 障害物に遭遇したときは、それが私たちの前になくなるまで、障害物から離れてください。

したがって、目の前に障害物がない場合は、参照ベクトルを単純に前方に向けます。 その場合、 ωはゼロになり、 vは最大速度になります。 ただし、近接センサーで障害物を検出したらすぐに、参照ベクトルが障害物から離れた方向を指すようにします。 これにより、 ωが跳ね上がり、障害物から遠ざかり、 vが低下して、プロセス中に誤って障害物にぶつからないようにします。

目的の参照ベクトルを生成するための適切な方法は、9つの近接読み取り値をベクトルに変換し、加重和をとることです。 障害物が検出されない場合、ベクトルは対称的に合計され、必要に応じて真っ直ぐ前を指す参照ベクトルになります。 しかし、たとえば右側のセンサーが障害物を拾うと、合計に小さなベクトルが寄与し、結果は左にシフトした参照ベクトルになります。

センサーの配置が異なる一般的なロボットの場合、同じアイデアを適用できますが、センサーがロボットの前後で対称である場合、重みの合計がゼロになる可能性があるため、重みの変更や追加の注意が必要になる場合があります。

正しくプログラムされると、ロボットはこれらの複雑な障害物を回避できます。

これを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ロボットプログラミングメソッド:ハイブリッドオートマトン(動作状態マシン)

これまで、2つの動作(目標達成と障害物回避)を個別に説明してきました。 どちらも見事に機能しますが、障害物に満ちた環境で目標を達成するには、それらを組み合わせる必要があります。

私たちが開発するソリューションは、ハイブリッドオートマトンの非常にクールなサウンドの指定を持つマシンのクラスにあります。 ハイブリッドオートマトンは、いくつかの異なる動作またはモード、および監視ステートマシンでプログラムされます。 監視ステートマシンは、個別の時間(目標が達成されたとき、または環境が突然変化しすぎたとき)にあるモードから別のモードに切り替わりますが、各動作はセンサーとホイールを使用して環境の変化に継続的に反応します。 このソリューションは、離散的かつ連続的に進化するため、ハイブリッドと呼ばれていました。

Pythonロボットフレームワークは、ファイルsupervisor_state_machine.pyにステートマシンを実装します。

2つの便利な動作を備えているため、単純なロジックがそれ自体を示唆しています。障害物が検出されない場合は、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):

Utilizing a few types of behaviors, the programmed robot avoids obstacles and continues onward.

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 :

This diagram illustrates the switching between robotics programming behaviors to achieve a goal and avoid obstacles.

Here is the robot successfully navigating a crowded environment using this control scheme:

The robot simulator has successfully allowed the robot software to avoid obstacles and achieve its original purpose.

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.

Robotics often involves a great deal of plain old trial-and-error.

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.

Related: OpenCV Tutorial: Real-time Object Detection Using MSER in iOS