ろむめも

気になったこととか、調べたことをゆるくまとめます。主にプログラミング関連の話題が多いです。

Robotics⑤ Controls

以下に学習時のメモをまとめます。

制御

導入

制御工学の主なゴールはある入力に対して狙いの出力を得る事です。

制御の方法として、大きく分けてオープンループコントロールとクローズループがあります。オープンループでは出力の値はみません。単に入力に対して制御対象のモデルがあって、そのモデルで演算を行い、結果を出力として返すだけです。制御対象のシステムをプラントと呼びます。一方でクローズドループはプラントの出力値を入力に返します。例えばプラントの出力先にセンサーを付けて、その値をフィードバックします。この時、プラントの入力にするのではなく、その手前にコントローラを置いて、そこの入力することがあります。このコントローラはまさにフィードバック制御器であり、プラントにどのような出力を入れればよいか演算するものです。フィードバック信号は例えば入力信号から減算された値をコントローラへの入力とする場合もあります。オープンループモデルではプラントへの入力はUで表されることが多く、入力→プラントモデル→出力を合わせてUFTと呼ばれます。クローズドループもでるでは入力はRで表されることが多くRFTと呼ばれRはReference信号です。オープンループモデルではパラメータ調整が厳密になりがちです。一方でクローズドループモデルではパラメータ調整は厳密ではありませんが、所定の出力が得られるようにコントローラを調整する必要があります。

PIDコントローラ

クローズドループのコントローラにはPIDコントローラが使われることが多いです。P=Proportional, I=Integral, D=Derivativeです。PIDコントローラは様々な場面で使われ、パラメータ調整が簡単であることがポイントです。Pは実時間での効果、Iは時間積分での効果、Dは時間微分になります。これらを線形的に加算して出力を決めます。もちろんPだけ、PDだけもあり得ます。

Pコントローラについて、Pは比例のPです。例えば、コントローラに入ってくる指令値と観測値の差が狙いの値に達していない場合、このPが働いて狙いの位置までの出力を生成します。この出力はゲインによって強さが変わります。Pだけを使った場合、入力が収束するまで目標値の近傍を行ったり来たりすることがあります。これを避けるためにオフセットとなる反対項を入れることもあります。Kpの値が大きく設定されると、目標値に収束する振幅が大きくなります。これは目標値から外れたら大きな値を入力してしまうからです。逆に小さな値を入れてしまうと、目標値まで届かない場合があります。これを定常状態誤差と呼びます。

この定常状態誤差をどのように排除するかと言えば積分項を入れます。積分は時間方向に行うのであたかもオフセットをのせたように見えます。また、ノイズに対して強いコントローラになります。ですが大きすぎると時間変化が大きい場合にシステムが不安定になることがあります。Kiを大きくすると積分項が効きすぎてしまって目的の値から大きく外れすぎてしまいます。逆に小さすぎると効きが悪いです。Kiを入れることで、立ち上がり時間は減り、安定性は良くなります。一方でオーバーシュート量は増加し、安定までの時間は増えます。

このようなオーバーシュートの増加に対して微分項を入れます。これは誤差が大きくなったら増加するのでモーメントのような働きをします。

それぞれのゲインを調整するのに気を付けるべきポイントは立ち上がり時間、オーバーシュート、収束までの時間、値の収束性、収束までの振幅があります。立ち上がり時間はKp,Kiでは減少しますが、Kdでは微増します。オーバーシュートはKp,Kiでは増加しますが、Kdでは減少します。収束までの時間はKpはわずかに変化し、Kiは増加します。Kdは減少します。値の収束性はKpは減少し、Kiはなくす方向に動きます。Kdは変化なしです。収束までの振幅はKp,Kiは悪くなり、Kdでは値が小さい限りは改善します。

コントローラにはその出力が与える影響にディレイがあるのでその分誤差が生じます。またPIDコントローラは必ずしも最適ではなく、それを証明するのは難しいです。

コントローラの遅延に対する対策は入力値にフィルタを入れる事です。

パラメータチューニング方法について。
パラメータはまずKpに小さい値を入れて、Ki,Kpを0にします。そしてkpを徐々に大きくしていき、立ち上がり時間を現象させます。次にオーバーシュートと安定までの時間が短縮するようにKdをゆっくり増加させます。
これらの調整を自動的に行う方法としてTwiddleがあります、これは勾配降下アルゴリズムです。

Quadrotor(=QR)

QRを制御するには、4つのローターがどのように作用するか学ぶ必要があります。ローターが4つなので、3次元空間で求められる6つの座標をすべて独立に決めることはできません。ですので、いくつかの操作において、連携して各軸が動くことが求められます。この問題を解くのにまずは平衡状態での各プロペラの動きを考えると良いです。そこから空中での停止に必要なプロペラの回転速度がわかります。また、一つの制御が他の状態に影響してしまう場合にはフィードバックループがネスト化されたカスケードPID制御器を使うことがあります。一般的に内部のフィードバックループは外側のフィードバックループよりも早い応答速度、10倍程度、が必要です。それは外側のループから見たときに内側のループが静的に見えるからです。今回の検討では外側のループはヨー角を維持するように働き、内側のループでは姿勢を制御します。ロール、ピッチ角が平衡ホバー状態ではとどまると仮定すると運動方程式を解くことができます。



続く。。。。

深層学習

以下に学習時のメモをまとめます。

導入

紹介

ディープラーニングは様々な分野で利用されています。ディープラーニングニューラルネットで構成されます。たくさんのノードとパラメータで構成されます。簡単に言えば、例えば2値クラス分類タスクでデータを適切に分類するのがニューラルネットがやっていることです。
分類問題に関して例えば入試の結果と学業成績があるとして、その組み合わせからその学生を入学させるべきか考えます。その2変数の散布図を描くとどのあたりに境界線があるかというのがわかります。そしてその境界線こそがモデルです。ではどうやってその線を見つけるのでしょうか。二変数の直線はw1x1+w2x2+b=0で表すことが出来ます。そして、適切なw1,w2,bを見つけることが出来ればそれが境界線になります。これはニューロンの中で行われている計算と同じです。入力に変数があって、その入力にそれぞれ重みがあり、バイアスを足します。これがニューロンの演算で、その出力値が正か負かによって分類結果を変えます。そしてその分類結果は2値分類の場合ステップ関数のようになります。このステップ関数は後で別の関数に置き換えたりします。
同じようにニューロンは論理演算子としても働くことが出来ます。特にややこしいのはXORですがXORはOR/NAND/ANDの組み合わせで実現できるので、ニューラルネットでも実現することが出来ます。
境界線はどのように決定しますかという所に戻ります。これは境界線を引いて、それに基づいて分類をします。その結果が正しいか間違っているかで判断し、間違っていたら境界線を引き直します。どのように引き直すかというと、間違っている点が境界線の中に入るように境界線を動かします。複数の点がある場合、それらの全てを考慮して値を調整します。また、一気に変えてしまうと値が収束しない場合があるので、学習率と呼ばれる値を係数の変化分に掛けて緩やかに値を変化させていきます。これを繰り返すことで最適な解を求めることが出来ます。
次に例えば特徴量のデータを増やしたとします。この場合は3次元の空間に境界線を引くようなものなのですが、線ではなく面になります。このように入力の次元が増えていくと特徴量の空間は多次元になりますが、やっていることは変わりません。

誤差関数

誤差関数とは境界からの距離を測るものです。特に離散的な誤差関数を使ってしまうと、係数を変えても値が変わらないので、どちらに動かせばいいのかわからなくなります。例えば二値分類問題でyes/noの正解した数だけを評価関数としてしまうと、境界線をどのように動かせばいいのかわかりません。境界からの距離であれば連続値で表すことができるので、誤差関数はそういった連続関数が求められ、微分可能性が重要になります。また、誤差関数は間違えたものに大きなペナルティを与え、正しく分類できたものにはペナルティを与えません。特に間違えた点の距離に応じて値を変えると良いでしょう。

先程の例ではステップ関数でクラス分類を行ってきましたが、活性化関数としてシグモイド関数をつかうと、出力値が連続でかつ値の持ち方がステップ関数のように0~1になります。入力の値が離散的であったとしてもその値をシグモイド関数に入れると連続値にすることができます。これが連続な活性化関数を使う理由の一つです。

ディープラーニングでは多クラス分類も実現することが出来ます。その場合softmax関数を使います。これは出力された確率にもとづいてスコアを算出し、それを多クラス分類のスコアに変換します。単純にアベレージを取っただけでは、スコアが負の場合にどのように取り扱うかが難しい場合があるので、別の方法で変換します。ここでは値を正の値に制限したいので、出てきたスコアのexpを取ります。そして、その和を取って、特定のスコアの値を和で割ります。これをsoftmax関数と呼びます。その値がある意味確率のように働くのです。

ここまでの入力データは数値データでしたが、それ以外の場合はどうしますか。特に複数のクラス分類の場合、1,2,3等の値を入力するのではなく、それぞれのTrue/Falseを一つの特徴量にしてしまいます。例えば3クラス分類であれば3次元のベクトルで表し、それぞれの要素が犬であるかどうか、猫であるかどうか、鳥であるかどうかを表すようなものです。これをOne-Hot Encodingと呼びます。

上記の例でsoftmax関数について説明しました。これはある意味確率のように働きます。これをどのように取り扱うべきか考えますが、確率であれば、私たちがやるのは最尤推定と呼ばれる手法です。最尤推定は分類したそれぞれの点のもっともらしさを求めます。これは例えば確率のような形で表されてもよいです。それをすべての点について掛け算し、その値をスコアとします。このスコアが最も高いところが最もよく分類できている境界線であると決めることができます。これが最尤推定です。最尤決定線では、ほとんどの点を正しく分類することが出来ます。

ではそのスコアはどのように最大化しますか?各点のスコアは確率でした。その確率と誤差関数を結びつけることが出来れば、誤差関数を最小化することが、確率を最大化することにつながるので、そういう観点でこれまでの評価方法を見直します。これまでやってきた確率のようなものはすべてのスコアを掛け算していました。このlogを取ります。すると掛け算のスコアが和になります。ただ、確率なのでlnを取ると1より小さくマイナスになってしまいます。そこで-lnの和を取ることにします。この-lnを取った確率的スコアの各データ点に対する和を取った値のことをクロスエントロピーと呼びます。良いモデルは小さい値になり、悪いモデルは大きな値になります。これは良いモデルでは尤度は大きい値になるので1に近くなります。そのlnを取ると小さい値になるので、その和は値は小さくなります。反対に悪いモデルでは各点の尤度が小さい値になるので0に近い値になります。そのlnを取ると大きい値になるので、その和は大きくなります。また、間違えて分類されたデータ点の値は尤度が低いので必然的に大きな値になります。反対に正しく分類された値は小さい値になります。これは私たちがやりたかった誤差関数の考え方と同じです。即ちこのような誤差関数を定義することで、各点の尤度を最大化するという問題から、クロスエントロピーを最小化する問題に転換することが出来ました。

実際の各試行の中でこのクロスエントロピーを計算するには、試行の目がどちらであったかを考慮する必要があるので、yln(p)+(1-y)ln(1-p)として、yは出たかどうかであり、pは出現確率です。演算には出た時の確率値を加算する必要があるのでこのようにしています。

この演算を多クラスではどのように実装しますか考えます。基本的には同じですが、各試行に対して確率を持っています。実際の試行に対して評価を行うには同じように-yij*ln(pij)をとります。ここでyijはone-hot化したベクトル、M(=j=0toM)はクラスの数を表します。

エラー関数の最小値を探すのに、勾配降下法を考えます。それは重みに対する偏微分による勾配効果計算を行います。ここで、シグモイド関数はとてもいい性質があります。それは微分した形式にもシグモイド関数が登場するので、その値を再度計算する必要がなく、過去のシグモイド関数の計算結果を利用することができます。また、重みに対するエラー関数の偏微分を考えます。エラー関数がこれまで考えてきたような-yln(y_pred)-(1-y)ln(1-y_pred)で、y_pred=sigmoid(Wx+b)で与えられる場合、その偏微分を取ると、-(y-y_pred)*xとなります。このことが意味するのはある場合勾配を降下する際にはその値はラベルから遠ければ大きい値、近ければ小さい値が設定されるという事です。この偏微分による結果に学習率であるαをかけたものを重みに加算します。バイアスも同じようにかさんして、勾配降下していきます。エラーの平均を取っている場合はデータの数で割って平均を取ります。

データの境界線が非線形の場合も基本的には同じですが、非線形を表現できるモデルを使う必要があります。例えばSVMや、ニューラルネットはそれを表現することが出来ます。特にニューラルネットの場合は複数レイヤーを設けることでそれを実現することが出来ます。これはXORを実現するために学んだことと同じです。最初の層は入力レイヤーと呼びます。間のレイヤーは隠れ層と呼び最後の層を出力層と呼びます。例えば、隠れ層で犬と猫と鳥をそれぞれ分類できるものが出来たとすると最終層では犬猫鳥とそれ以外のものを分類できるようになります。レイヤーを深くするとこのような集約のような働きが出来るようになることがあります。多クラス分類をする場合は、クラスの数だけ出力ノードを設けて、それはsoftmax関数を通した値を入て、その最も大きな値を入れるのが良い事が多いです。

誤差の伝搬にはバックプロパゲーションが有効であり、それには合成関数の微分にかんする知識が必要です。





続く。。。。

Robotics④ Perception

以下に学習時のメモを記載します。

Perception

導入

ロボットは知覚、意思決定、実行の動作を行うことを考えてきました。ここでは知覚について理解を深めます。特に、デプスセンサーを備えたカメラやLidarのデータをどのように扱うかという事を学習します。

現代の知覚アルゴリズムはとても強力ですが、強力なコンピュータでリアルタイムに実行する必要があります。そこにはGPUやTPUといった高速演算用のハードウェアが必須です。そして、消費電力の観点からもどのようなハードを使うかという事を考慮する必要があります。この先ではRANSACモデルのような古典的な手法を使って知覚技術に触れた後に、クラスタリングという機械学習を使った最新の手法にトライします。

ロボットが使うセンサーにはアクティブセンサーとパッシブセンサーの二つの種類があります。アクティブセンサーはエネルギー源からのパワー供給を使って環境の情報を取得します。特に光や音波などを発して、その反響を取得することで周囲の情報を取ってきます。逆にパッシブセンサーは環境からのエネルギーを受け取るだけです。例えばカメラは太陽光の光をセンサーに受け取り、それにより周囲の情報を取得することができます。

例えば、アクティブセンサーであるLidarは出力パルスの送信から反射パルスの受信までの時間を図ることで物体までの距離を計算します。水平方向の解像度や精度は良く、検知範囲も広い(100m)ですが、サイズが大きく、コストが高く、天気の影響を受けてしまいます。
他にはToFカメラがあります。これは赤外線を照射してそれが返ってくるまでの時間を観察することで深度を測定します。Lidarと違ってToFカメラに可動部分はありません。またパルスランタイムセンサーと位相シフト連続波の2種類の動作原理の異なるタイプが存在します。パルスランタイムセンサは放射光が戻ってくるまでの時間を測定します。そのためには正確なタイマーが必要になってきます。位相シフト連続波センサは放射光が反射するときの位相の変化を検出し、そこから距離を計算します。一般に赤外線センサはサイズが小さく、データが早く取得でき、処理も重くなく10mぐらいの範囲まで検知できますが、二次反射や環境光によるノイズ、複数のToFカメラによる干渉、透明なものは検知できない等の問題もあります。
超音波センサーは高周波音パルスを使って環境の情報を取得します。同じようにエコーの時間を測定します。一般的に精度は良く、コストもそこまで高くなく、透明なものも検知できますが、空間分解能は低く、範囲も狭く(5m)、他の超音波センサーと干渉し、二次反射が存在し、対象物の表面によっては正しい情報が得られない場合があり、気候によって値が変わってしまうという問題もあります。

単眼カメラはパッシブセンサーです。例えば、Structure from Motionという技術を使って複数の2D画像から3Dモデルを再構成することができます。これは一台のカメラで撮影をして、次の位置に動かすというのを同じ対象物に対して繰り返します。それらの情報から三角測量技術によって計算することが出来ます。更に深さの推論を組み合わせることでよりよい結果を得ることもできます。一般的に空間的な解像度やコスト、可搬性は良いですが、計算時間が重く、外乱光の影響を受け、範囲も限定的です。
別の方法としてはステレオカメラがあります。これは2台のカメラを使ってその視差を図り物体の深度データを計算する方法です。一般的に水平方向の空間分解能は良く、コストも安く可搬性がありますが、計算量が多く、外乱光の影響を受け、範囲も限定的で、対象物によっては精度が出ない場合もあります。

RGB-DカメラはパッシブRGBカメラとアクティブデプスセンサーが組み合わされて構成されています。デプスセンサーの放射は縦方向には同じタイミングで照射され、それを横方向に照射していくようになっています。デプスセンサーを使う事で重たい処理をしなくても良くなります。これは比較的安価ですが、解像度はそこまで高くなく、範囲も限定的です(5m)。デプスセンサーは赤外光を放射し、その反射を受信します。この放射される赤外光は特定のパターンを持ったものになっています。そのパターンが対象物にあたるとパターンの形が変化します。この変化を検知することで深度情報を取得することができます。

一般的なデプスセンサーの出力はポイントクラウドデータとして出力されることがあります。これは検出した空間データ上の点をx,y,zの位置情報と、RGBの画像情報によって表されています。ほかにも反射光の強度や、曲率に関する情報を含む場合があります。これらのデータは各点にアクセスしたり、フィルタをかけたり、統計的に分析したりすることでほしいデータを抽出するようになります。曲率を取得するのはある点での対象物の表面の法線を求めることができて、その情報から曲率が計算されます。

Calibration, Filtering and Segmentation

Camera

センサーにはノイズがつきものです、それは電気的な要因もありますが、個体差によって値が異なります。そういったばらつきがシステムに与える影響を抑えるためにキャリブレーションを行うのが一般的です。

カメラの調整にはCameraMatrixを使うのが一般的です。これは三次元上のx,y,zをカメラ画像上のp(x,y)に変換する際にディストーションの影響を補正するためのものです。ディストーションを細かく分解すると数種類存在し、代表的なものとしてはRadialとTangentialです。Radialディストーションは樽型糸巻き型などが存在し、最も一般的なものです。画面中心から等方的な影響をカメラ画像に与えます。Tangentialディストーションはカメラレンズがセンサーと平行に配置されていなかった場合に生じ、画像全体が奥行方向にチルトします。これらの影響を測定し、それを補正する方法を紹介します。まず、チェスボードのような格子上の白黒画像を用意します。それをいろいろな角度から写真に撮って、それらの情報をもとにディストーションの補正をします。ディストーションの補正にはopencvの'findChessboardCorners'で格子の点を見つけます。補正行列を演算するには`calibrateCamera()`を使い、テスト画像の補正を実行するには`undistort(img, mtx, dist, none, mtx)`を使います。
補正に使うパラメータは焦点距離fx,fy、光学中心cx,cy、そしてゆがみの係数(k for radial, p for tangential)です。

github.com

ROSを使う場合、camera_calibrationパッケージに補正用の関数が用意されています。

Point Cloud Libraly

ポイントクラウドデータセットを使う上でもノイズが発生します。それらを効率的に扱う方法を紹介します。例えばVoxelGridDownSamplingFilterやExtractindicesFilter、PathThroughFilter, RANSAC等があります。python上でpclを利用するにはpython-pclを使います。
ポイントクラウドデータを扱う場合に、RGB-DカメラはLidarセンサよりも密度の高いデータ点を返します。これは演算速度は必要ですが情報量は多いです。この場合ダウンサンプリングを行うことで演算量上の問題を回避することができます。一般に画像では1pixelを画素と呼びますが、PCLではvoxelと呼びます。これは3次元空間上の小さな単位グリッドのようなものです。ボクセルグリッドフィルタはこのボクセル内の点を平均化しダウンサンプリングします。これは`make_voxel_grid_filter()`を使って実現できます。この時にボクセルのサイズ(リーフサイズ)の単位はメートルです。1にしたら1メートルになり、`set_leaf_size()`で設定することができます。初めは小さい値にして大きくしていくのが良いでしょう。

次にパススルーフィルタを設定することが出来ます。これは例えばX/Y/Z軸のどこからどこまでの範囲を表示するか決定することができます。今回は机の上がROIなので、そこを設定します。`make_passthrough_filter()`でインスタンスを作り、`set_filter_field_name(filter_axis)`で座標軸を、`set_filter_limits(min, max)`で表示する範囲を決定します。フィルタの適応には`filter()`を実行します。

これまでのように使わないデータを処理したり、ノイズを除去したりデータをうまく扱ってきましたが、ものが机の上に載っていたりするとそれら全てが一つの塊としてあつくぁれてしまいます。そこで、それらを別のサブセットに分けるのにセグメンテーションの技術を使います。この時、色、形、近傍度合いによって分離します。ここではRANSACアルゴリズムを使います。RANSACではデータ点を3次の多項式で近似して、近似曲線にあっているものを当たり値、それ以外を外れ値として評価します。この時、多項式にはポイントクラウドデータにどのような形状のものが含まれているかを事前知識として与える事ができます。欠点としては演算時間が膨大にかかる可能性があることです。アルゴリズムの詳細は、次のようなものです。まず事前知識として与えられた形状をもとにそれを構成するための最小の点の数を表示します。これは線なら2点、平面なら3点となります。これら3点を通る連立方程式を考えて、それを係数に関して解きます。そして、それらの係数には各点の座標を代入して、それぞれの座標から算出された係数が概ね一致したら平面上にあると予測できます。これを複数の点に対して実行し、必要なデータと必要でないデータを分けていきます。コードでは`make_segmenter()`を使ってインスタンスを生成します。前提知識となる形状は`set_model_type(pcl.SACMODEL_PLANE)`で、アルゴリズムの選択は`set_method_type(pcl.SAC_RANSAC)`で設定することができます。RANSACの場合は距離の閾値を設定することが出来て、`set_distance_threshold(distance)`で設定できます。実行は`segment()`です。これを実行するとポイントクラウド内のどの点が平面になっているかを識別することができます。この平面の集合を表示するには`extract(inliers, negative=False)`を使います。逆に平面の集合を除外するにはnegative=Trueとします。また、出力inliersはmax_distanceの範囲内で最もそれらしいものを返すようになっています。

上記のようなやり方で概ねノイズは除外できますが、外乱要因となる埃、湿度の変化、外部光源の存在などによるノイズも除去しなければいけません。この除去には各点の近傍で統計分析を行って、特定の基準を満たさない点は除去するようにすることがつかえます。例えば、`StatisticalOutlierRemoval`フィルタ等が使われます。これはガウス分布を仮定したときにすべての点の距離は全体距離の平均に標準偏差を足したもので定義される量の外にあるものは外れ値であるという考えに基づいています。実装は`make_statistical_outlier_filter()`でインスタンスを受けて、`set_mean_k(n)`で周辺いくつの点を評価するかを決め、`set_std_dev_mul_thresh()`で標準偏差の何倍の値まで許容するかを設定し、`filter()`で実行します。

Clustering for Segmentation

ここまでで不要なデータを削除してきました。ここからはそのデータを使ってクラスタリングを行います。クラスタリングは各点が持つ色や形、強度、表面情報などを使って行います。RANSACアルゴリズムには同じように円筒形のモデルも用意されており、それを使う事で缶を取得することが出来ますが、似たような形のものがたくさんあった場合どうしますか。また、用意されていないものがあった場合にどうするべきでしょうか。

セグメンテーションに使うデータは色や形等様々あります。今回クラスタリングを行うために2つの方法を使います。K-means ClusteringとEuclideanClusteringです。

K-means Clusteringはベーシックな方法です。もともとのクラスタの数が決まっている場合に特に有効で、クラスタ同士が重なり合っているとうまく分けることが出来ません。方法としてはまず適当な位置にクラスタの数分だけ点を置きます。データ点を各点の内最も近いもののクラスに分類します。分類したクラスの重心位置を算出し、そこに最初に置いた点を移動させます。これを繰り返して、データ点のクラス間の移動がなくなるまで実施します。この方法において、演算終了は反復回数に比例するので、パフォーマンスはその回数に応じて異なる場合があります。

DBSCAN=Density-Based Spatial Clustering of Applications with Noiseはクラスタの数がわからないときに有効です。密度を使ってクラスタリングしてきます。それはある閾値の距離の範囲内にあるデータをグループ化していきます。それはデータ点同士のユークリッド距離によって決まるので、ユークリッドクラスタリングと呼ばれることもあります。方法ですが、あるデータ点を選択し、その付近にあるデータが所定の距離の範囲内かどうか確認します。範囲内であればそのデータを同じグループにします。そして、そのデータ点をエッジとして、そのエッジのデータ点に対して同じことを繰り返します。内包されたデータ点は省略の為探索する必要がなくなります。最後にランダムにデータ点にアクセスして、全ての点がクラスかどうか判断されているか確認する場合もあります。欠点としては同じデータ点が複数のクラスに入ってしまう場合や、順番によって結果が変わってしまう場合があります。DBSCANは`from sklearn.cluster import DBSCAN`から利用することが出来ます。DBSCANは外れ値が入っている場合にも閾値の設定でその値を除外できるので有効です。また、このアルゴリズムは一度すべての点を探索してしまえば、それ以降探索する必要がないので、収束基準などはそんざいしません。

ユークリッドクラスタリングアルゴリズムを適用する場合、ポイントクラウドデータを直接操作するのではなく、kdツリーと呼ばれるデータ構造を採用する必要があります。これは計算負荷の軽減のためですが、PCLのユークリッドクラスタリングアルゴリズムはこのデータ構造しかサポートしていません。このデータタイプは簡単に言えばXYZ形式のポイントクラウドデータであり、XYZRGBのデータタイプからRGB要素を削除する必要があります。

Object Detection

物体認識にはカメラ画像をどのようの処理するかというのがとても大事です。これは特徴量エンジニアリングと似ています。例えば、色、形、その両方を使って、物体を検知します。

色空間を使う場合RGB空間からHSV空間に変換することがあります。HSV空間では光量が落ちた場合でもSatuationの値が落ちません。また、Vの値は全体の明るさを表しています。

これらをどのように使うのが良いかというと、例としてヒストグラムがあります。これは例えば一つの画像の中のR画素を抜き出して、それを0から255の範囲でヒストグラムにします。一般的には複数の色データのヒストグラムをconcatしてそれを特徴量にするのが良いです。また、この時に複数の色空間を組み合わせたものを用意してもいいです。その入力ベクトルを学習器に入力し、学習させます。今回はSVMを使います。

実行する時に他のパッケージがcatkin_ws/srcの下にあるとだめなので、sensor_stickだけにする必要があります。



続く。。。

gitとROSでよく使うコマンドをメモする

git

  • リモートレポジトリから最新のコードを取り込む

git pull

  • ローカルブランチを作成する

git branch [ブランチ名]

  • ブランチを確認する

git branch

  • ローカルブランチを切り替える

git checkout [ブランチ名]

  • ローカルブランチを作成して、切り替える

git checkout -b [ブランチ名]

  • ファイルを追加する

git add [ファイル名]

  • ファイルを削除する

git rm [ファイル名]

git rm -r [ディレクトリ名]

  • ローカルレポジトリにコミットする

git commit -m "コメント"

  • ブランチ同士をマージする

git checkout master
git merge[ブランチ名]

  • マージしたブランチを削除する

git branch -d [ブランチ名]

  • ブランチをリモートレポジトリにコミットする

git push

ROS

roscore

  • rosモードに入る

roscd

Robotics③ Kinematics

以下に学習時のメモを記載します。

運動学

導入

重要な4つの概念。reference frame, generalized coordinates, degrees of freedom, homogeneous transform
Joint Space(theta) -> Forward Kinematics -> Cartesian Space(x,y,z)
Cartesian Space(x,y,z) -> Inverse Kinematics -> Joint Space(theta)
一般的に逆運動学を解くのは運動学を解くのよりも難しい。

Dofは独立でない一般化座標の数で表されます。独立でない一般化座標の数はその系を表すのに最低限必要な変数の数です。
robot工学において、configuration spaceやjoint spaceという言葉は重要です。これはアームがとりうる全てのconfigurationsを表します。path planningやobstacle avoidanceの中で使われます。

例えば3次元の世界座標系があってそこに一つの棒を置いたとします。この系を表すのに必要なのは6つの変数です。これは例えば棒の両端のx,y,zの座標やr,theta,phiで表されても良いです。二つの棒を置いた場合は12個の変数が必要になります。二つの棒を関節でつないだ場合は7Dofです。

回転およびプリズムジョイントだけを持っているアームの自由度はエンドエフェクタも含めた可動な関節の数と同じになります。例外として閉ループのアームの自由度は1です。もしアームが与えられたタスクに必要な自由度より多くの自由度を有している場合、運動学的冗長と言います。この冗長性はタスクを解くのに有利で、アームの動かし方に自由度があるという事になります。一方でコストと制御の難しさを考慮する必要があります。

一般的に産業界で使われるのは4か6自由度のアームです。特に有名なのはspherical wristです。先端にroll,yawを持っていて、次の関節にroll,yawがあり、最後にpitchを持ちます。最初の3つで先端の位置の制御をして、残りの3つで姿勢の制御をします。

関節構成のタイプを評価するのにworkspaceという概念を使うことがあります。workspaceは先端が到達可能な点の集合です。一般的にworkspaceには2つの意味に分解できて、到達可能なworkspaceと器用なworkspaceになります。器用なworkspaceとは任意の姿勢で先端部が到達できるかという追加の自由度の事です。

一般的な関節構成のタイプとしては、デカルト型(PPP)、円筒型(RPP)、擬人型(RRR)、スカラ型(RRP)、球状型(RRP)があります。また、平行リンク型も重要なタイプです。

運動学、逆運動学

空間上のパーティクルの位置は参照座標の単位ベクトルにパーティクルの座標のスカラをそれぞれ掛けた形で求められます。ここで一つの座標系を別の座標系に変換する方法を考えます。例えば原点を中心として回転させた場合、回転行列を使う事でその系の返還を表すことが出来ます。回転行列の特性としては以下のものがあります。
逆行列は転置で表すことができる
行列式は1
③行および列は直行する単位ベクトルを意味し、任意の行または列の大きさは1にひとしく、2つの行または列の内積は0になる
④列は元の座標系に対する回転した座標系の基底ベクトルを表す。
これらは3次元にしても同じです。

この計算を行うのに、今回はsympyを利用します。これはシンボルを定義して、式を抽象的に定義することができます。これは簡単なシステムにおいて方程式を簡潔に表すことができるし、浮動小数点演算においても誤差が少なくなる傾向にあります。
サンプルコード

from sympy import symbols, cos, sin, pi, simplify
from sympy.matrices import Matrix
import numpy as np
q1, q2, q3, q4 = symbols('q1:5')
A, R, O, C = symbols('A R O C')
rtd = 180./np.pi
dtr = np.pi/180.
R_x = Matrix([,,[]])
R_x.evalf(subs={q1: 45*dtr})

基本的には回転は順番によらないが、演算上の回転の順番は主に以下のものがある
Intransic Rotation = RzRyRx
Extransic Rotation = RxRyRz

回転行列を使う場合に注意しなければならないポイントは
①パフォーマンスとして3つ座標系なのに9つのパラメータが必要になる
②回転行列の反復乗算によって丸め誤差が発生し、時間と共に有効な回転行列でなくなってしまう。
③回転後に同じ平面に回転軸が来てしまった場合自由度が一つ減り、表現の自由度がうそなわれてしまう。これをgimbal lockと呼ぶ。これを防ぐにはそのような領域に近づかないようにすることです。

オイラー角を求める場合にはatan2を使う。

各関節の角度から先端の位置を求める場合に同次変換を利用する。これは位置(平行移動)のベクトルと姿勢(回転)の行列を一つの行列にまとめたものである。
同次変換を複数回繰り返した場合、結果は同じになるはずです。それは例えばA->Dに変換したいとしてA->B->Dと変換するのとA->C->Dと変換するのでは結果が変わらないことを意味します。

DHパラメータ

ここまでは各リンクの位置を求めるのに回転行列と平行移動を使ってきました。これを簡単にやる方法がDHパラメータの導出です。これまでは位置と姿勢の6つの変数を決める必要がありましたが、DHパラメータでは4つの変数を決めることで求めることができます。但し、この変数の取り方は系の取り方によって5パターン程度存在するので、あくまでも1例です。
・α=Y軸を中心としたZ軸の回転角度
・a=X軸方向の移動距離
・d=Z軸に沿って測定されたXの符号付き距離
・Θ=Z軸を中心としたX軸の回転角度
X軸方向の単位ベクトルはZi-1とZiの外積で求めることができます。DHパラメータを使って座標系を変換するときには、変換はリンクi-1の参照フレームを、リンクiの参照フレームと正確に一致するように移動するかという事を考えると良いです。
RRPR型のアームの場合を考えます。
①全ての関節に1から番号をつけます
②すべてのリンクに0から番号をつけます。0は接地部分です
③駆動軸に線を引きます
④DHパラメータが少なくなるように取り扱うリンク間の距離変数を決定します
⑤各軸の方向を決めます。例えば回転軸であれば反時計回りを正にします。そして右ねじの法則でZ軸の方向を決めます。
⑥X軸の方向を決めます。Xi-1からXiの方向に正とするのが一般的です。ただ、P型ジョイントではDHパラメータを減らすためにPの駆動方向とは垂直な方向にとることがあります。
⑦joint1の変数を減らすためframe0とframe1を一致させます。そうすることでα=a=0になり、もしjoint1がR型の関節であればd=0、P型の関節であればd=0とすることができます。
⑧先端部のX軸の方向を決めます。一般的には一つ前のframeと同じように決めます。
次にDHパラメータのテーブルを決める方法です。
α0はz0とz1の回転角度です。a0はz0からz1のx0軸上の距離です。z軸が平行であればα、x軸の原点が同じであればaは0になります。d1はx0からx1のz1軸上のsin距離です。これもx0とx1が平行であれば0になります。Θ1はx0とx1の間のz1軸周りの回転角度です。x軸が平行ではない場合、Θは0になる場合もありますが、一定のオフセットが乗る場合もあります。
0->1:x,z軸が平行なのでα,d=0、xの原点が同じなのでa=0となり、α,a,d,Θ=0,0,0,Θ1
1->2:x,z軸が平行なのでα,d=0、α,a,d,Θ=0,a1,0,Θ2
2->3:x,z軸が平行なのでα=0、x軸は平行だが関節がP型なのでdは変数となり向きに注意して、α,a,d,Θ=0,a2,-d3,Θ2
3->4:x,z軸が平行なのでα,d=0、xの原点が同じなのでa=0となり、α,a,d,Θ=0,0,0,Θ2

逆運動学

先端の位置が決まったときに、それぞれの関節をどの角度に曲げればよいでしょうか。逆運動学を解くというのは本質的にはそいう問題になります。ところが、先端の位置が決まったとしてもすべての関節の向きが一意に決まるわけではなく、複数のとりえるパターンが出来てしまったり、解が無い場合もありえます。これが逆運動学の難しさです。一つの方法としてはNewton-Raphson法を使った数値的解法です。初期値が解に十分近ければ早く収束します。ほかのポーズを求めたい場合は別の初期値を与える必要があります。ただし、同じアルゴリズムですべてのアームに利用できます。別の方法としては、閉形式解を求める方法です。ただし適用できるものは①3つの隣接する関節軸が一点で交差する②3つの隣接する関節軸が平行のどちらかを満たしている必要があります。一般的に、現在実用されている6DoFアームはどちらかを満たしています。特に最後の3つの関節は回転関節であることが多く、これは手首を再現したようなものです。そして最初の3つの関節は先端の位置を決める為に利用されます。そうすることで位置決めと先端部の操作を切り分ける事ができます。一般的な逆運動学の解法を以下に示します。
①DHパラメータテーブルを埋めます。(手首中心とframe4,5,6を一致させてとると良い)
②ベースフレームに対する手首中心の位置関係を明らかにする。
③式を満たすように各関節の位置(特に位置決め関節であるJ1,J2,J3)を決める。
④各関節の位置が求まったら、逆に先端の位置を導出する。
⑤回転行列を使って各関節におけるオイラー角のセット算出する
⑥出てきた解から最適なものを選ぶ

簡単な例としてRRP型のアームの場合を考える。
先端の位置=zcはわかっているとして、Θ1, Θ2, d3の導出をする。
Θ1 = atan2(yc,xc)
Θ2 = atan2(s, r); r^2 = xc^2 + yc^2, s = zc - d1
d3 = sqrt(r^2 + s^2) = sqrt(xc^2 + yc^2 + (zc - d1)^2)

Project

github.com

Gazeboは物理演算を含むシミュレーション環境です。内部ではgzserverとgzclientが動いていて、起動するにはまずserverを起動し、clientを起動する必要があります。いっぺんに起動する場合はgazeboを使います。

URDFはxmlファイルでrobotモデルを表すのに使います。例えば、関節の構成やリンクの長さを定義します。同じようにxacroファイルを使ってurdfファイルを複数のxacroファイルに分けることもできます。

RVizはROSを可視化するツールです。ロボットの予測、意思決定、応答のすべてを可視化することが出来ます。RVizを使うとセンサー、カメラ、Lidarデータの可視化をリアルタイムに行うことができます。RVizはシミュレータではないので物理エンジンは入っていません。そのため、衝突や力学は考慮されません。rvizを起動するには、roscoreを行ってからrosrun rviz rvizを行います。

MoveIt!は操作、運動学、制御のためのモーションプランニングフレームワークです。MoveIt!はそれ自体でモーションプランニング用のアルゴリズムを持っていませんが、インターフェースを提供しています。このインターフェースはrequest/responce方式で実現されています。特に、ロボットを干渉がない経路を生成し、予め設定した速度や加速度の制限に従って軌道を演算してくれます。

ROSのRobotStateクラスは運動学機能にアクセスできますが、逆運動学は独自で実装する必要があります。

move_groupノードはMoveIt!のプライマリノードで、パッケージの提供とセントラルハブとしての機能を持ち、ほぼすべてのコンポーネントとの通信を提供します。

Rvizを使ったtransform matrixの導出にはrosrun tf tf_echo [reference frame] [target frame]を使うと良い


続く。。。

Robotics② ROSの基礎

ROSの基礎

導入

概要

ROSはロボット開発のフレームワークとして開発された。特にアイデアを簡単にシェアするために移植性が重要なコンセプトになっている。

ノードとトピック

データ取得、意思決定、応答を行うためにそれぞれのプロセスはノードとして定義することが出来る。ノード間はROSmasterが定義するトピックと呼ばれるメッセージによってデータのやり取りを行う。メッセージには名前を付けることが出来る。ROSmasterの定義が終わったあとは、ノード間で独自にデータのやり取りが行われる。パラメータサーバーは中央集積所として働き、ノードがデータを必要に応じて見れるようにする。データを送信する側をpublisher、受信する側はsubscriberと呼ばれる。データはノード間で行われ、ROSmasterはそこに介在しない。一つのノードから複数のノードに送信することもできる。

メッセージ送信

メッセージはあらかじめ定義されているものがあり、センサーのデータや、画像のデータなどは既に用意されている。もちろん自分のデータタイプを定義することもできる。名前付きのメッセージであってもデータ型は何でも良い。

Service

リクエストレスポンス型のデータのやり取りもある。それはROSServiceと呼ばれる。例えば露光時間を送ったらその時間露光した画像を返すような仕組みもできる。

計算Graph

これらのノードはCompute Graphとして考えることが出来る。グラフはrqt_graphというツールを使って可視化することが出来る。

ROS環境構築

ROSの各ディストリビューションをインストールしたらsetup.bashというバッチファイルがあるのでそれを

source /**/setup.bash

で実行すると環境変数などが設定されます。この時、

./

を使って実行すると、新しいターミナルセッションが開き、そこで環境変数が設定されます。一方sourceを使うと、現在のターミナルセッション上で環境変数が設定されます。どちらも一時的な環境変数になってしまい、ターミナルを落としたら失われてしまいます。もしそれを避けたいならsetup.bashの内容をbashrcに書きましょう。それは

"source /**/setup.bash" >> ~/.bashrc

で実現できます。

ROSの実行

ROSを実行するにはマスタープロセスの実行が欠かせない。マスタープロセスは、実行中のノードに名前を与え、認証を行い、全てのノードを登録し、ログをまとめ、ノード間の接続を調整します。その起動にはターミナル上で

roscore

をタイプします。止めるにはctrl+cです。
次にノードを起動します。ノードの起動には

rosrun package_name node_name

を使います。packageは複数のnodeを含んだ概念です。上記のコマンドで起動したいノードを起動することができます。

どのノードが起動しているか見るには

rosnode list

をタイプします。所望のノードと、rosoutというものがあると思います。rosoutはログを管理しているノードでroscoreを実行すると自動的に起動します。

どういうTopicが飛び交っているか確認するには

rostopic list

をタイプします。/rosout_aggはrosoutが発行しているtopicです。それ以外はノードが発行しているTopicです。

どのTopicがどのノードからどのノードに発行されているか、どういう種類の情報か確認するには

rostopic info /nodename

を実行します。Topicに含まれるメッセージと、どのノードからどのノードに発行されているかが表示されます。

メッセージの中身の変数として何が含まれているか詳細な情報を知りたい場合は

rosmsg info message_type_name

を実行します。そのメッセージにどういう変数が含まれているかの情報が表示されます。同じようにそのメッセージの定義を確認したい場合は

rosed info message_type_name

を実行します。特にこのメッセージがどういう意図で使ってほしいかコメントで書かれていることが多いです。

実行中にTopicがどう変わっているか知りたい場合があります。その時は

rostopic echo node_name

を実行します。ROSの世界ではSI単位系が主に用いられています。

PackagesとCatkin workspace

導入

CatkinはPackageのマネージメントシステムです。そこではPackageを追加してコンパイルするようなことを行います。まずはworkspaceを作成しましょう

作り方

mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace
※この時CMakeLists.txtのシンボリックリンクが/opt/ros/kinetic/share/catkin/cmake/toplevel.cmakeに作られます。
cd ~/catkin_ws
catkin_make #ビルド。これはcatkin_wsで実行する。
lsを実行して、buildとdevelディレクトリがあればOK。buildはC++で書かれたパッケージ用のものであり、develにはsetup.bashが用意されるので、パッケージを使う前にsorceで実行する必要がある。
ビルドに失敗した理由がpackageが足りない場合もある。その時は
sudo apt-get install ros-**を実行して足りないパッケージをインストールする。

Tips

複数のnodeをrosrunで実行するよりも簡単な方法としてroslaunchが用意されている。roslaunchを使うにはcatkin_makeが実行されている必要がある。そしてその時にdevel/setup.bashを使うのでsource devel/setup.bashを行う。次に

roslaunch package_name *.launch

を実行する。そうするとlaunchファイルに書かれたnodeが実行される。

もしパッケージの依存関係を調べたり、ダウンロードしたりしたい場合はrosdepを使う。

rosdep check package_name

そうすると何が足りないか表示される。足りないパッケージをインストールするには

rosdep install -i package_name

を実行する。うまくいかない場合もあるのでもしだめならapt-getで所望のパッケージをインストールすると良い。

ROSのパッケージはsrcディレクトリに配置される必要がある。もし新しいパッケージを作る場合は

catkin_create_pkg package_name dependancy...

を実行する。また、もっと簡単に作る場合は

catkin_create_pkg first_package

を実行する。

package内のディレクトリには以下のフォルダが配置されます。

  • scriptsはpythonコード用
  • srcはC++ソースファイル
  • msgはカスタムメッセージの定義
  • srvはサービスメッセージの定義
  • includeは依存関係のあるもののheader/libraries
  • configはコンフィグファイル
  • launchはラウンチファイル
  • urdfはUniversal Robot Descriptionファイル
  • meshesはstl等のモデリングファイル
  • worldsはGazeboシミュレーションのXMLファイル

Write ROS Nodes

ROS Publishers

Publishersはtopicにmessagesを送ります。

pub1 = rospy.Publisher("/topic_name", message_type, queue_size=size)

引数は以下です。

  • /topic_name : publisherが持つtopicの内、送信するものの名前
  • message_type : topic_nameで定義された送信するメッセージのタイプ
  • queue_size:Noneか設定しなければ同期通信、値を入れていれば非同期送信となり保存するメッセージの数の意味

また、同期送信の場合は他のpublisherが既にtopicを送信済みであれば、自分のtopicはブロックされます。最初にtopicを送ったpublisherがメッセージをバッファにシリアル化し、バッファはそれぞれのtopicのsubscriberが書き込みます。非同期送信の場合はpublisherはメッセージを送れるようになるまでためておきます。queueの概念に基づいており、新しいメッセージが来たら古いものから消えていきます。publisherが作られ、データ型が定義されたら、以下で実際にメッセージを送ることが出来ます。

pub1.publish(message)

Create Nodes

ノードを作るにはまずcatkin_wsディレクトリ配下のsrc内にpackage_nameのディレクトリを作ります。例えばその中に簡単なbash scriptを置いて、chmod u+x script_nameで権限を与えて実行します。実行にはcatkin_wsディレクトリでcatkin_makeを実行します。そして、source devel/setup.bashで新しい環境を作り、rosrun package_name script_nameを実行します。もしpythonスクリプトを作るのであれば、package_nameディレクトリ配下のscriptsに移動してスクリプトファイルを生成します。そのスクリプトファイル名がノードの名前です。
スクリプトには以下の宣言が必要です

import rospy
from std_msgs.msg import Float64

2行目のコードはmessage_typeを定義するのに重要です。
include宣言を終えたら以下のようにtopicを宣言します。

pub = rospy.Publisher('/topic_name', Float64, queue_size=10)

次にclientノードを初期化し、master nodeに登録するために以下の関数を呼びます

rospy.init_node('node_name')

また、ROSのメインループの周期を以下で決定することが出来ます。例えば10と設定されていたら、10Hzということです。

rate = rospy.Rate(10)
...
while True:
pub.publish(message) # メインループ内で送信したいmessageを送る
rate.sleep() #メインループでsleep関数を呼ぶことで10Hz周期となる

pythonコードを書いたら、ノードを実行できます。まずはcatkin_makeでビルドします。自作ノードの実行にはlaunchファイルを起動して、元ある環境のノードを起動します。それからrosrun package_name node_nameで自作ノードを実行します。

Create Service

ROS Serviceはrequest/response方式の通信です。あるノードがrequestを受信したらresponseとしてrequestの送信元のノードに返します。Serviceは以下のように宣言します。

service = rospy.Service('service_name', serviceClassName, handler)

引数は以下です。

  • service_name:サービスの名前で他のノードからどのサービスか特定するために用います
  • serviceClassName:サービスが実装されているファイル名で、.srv形式のテキストファイル。requestとresponseの両方のメッセージの型が記載されます
  • handler:適切なresponseメッセージを返す

また、別のノードからROS serviceを呼ぶ為のAPIのようなものを作るにはServiceProxyを使います。

service_proxy = rospy.ServiceProxy('service_name', serviceClassName)

これを使ってresponseを返すには、まず新しいメッセージをserviceClassNameResponse()で作成して、それからメッセージをservice_proxy(msg)で送ります。

新しいserviceを作るにはcatkin_ws/src/packagename/srvディレクトリを作成し、そこに.srvファイルを用意します。そこは"---"で分割された2つのセクションに分かれていて、最初のセクションはリクエストメッセージの定義(型と変数名)を書きます。二つ目のセクションにはレスポンスメッセージを定義します。もちろん独自定義の型を使う事もできますがmsgディレクトリに.msgファイルを作成してそこに定義を書く必要があります。

次にCMakeLists.txtを編集する必要があります。CMakeListsはGNU makeのmakefileと同じようなコンセプトです。find_package()関数は必要なパッケージを定義するものです。次にadd_service_files()関数でどのファイルがコードを生成するために必要か定義します。最後にgenerate_messages()関数で実際にコードを生成します。

これで大体ビルドができるのですが、package.xmlについて述べておきます。このファイルはpackageの名前やバージョン、著者、依存性について定義しておくものです。パッケージにはビルド時の依存関係と、実行時の依存関係があるのですが、rosdepを使うとどちらの依存関係も調査することが出来ます。それぞれの依存関係はpackage.xmlファイルの中でで定義されます。必要に応じて追加することが出来ます。

以上のようなことがわかったらビルドします。catkin_wsに戻ってcatkin_makeです。これらが正常に動けばdevel/lib/python2.7/dist-packagesにパッケージが作られて、その中のsrv以下に作成したserviceが配置されます。これで作ったパッケージがPYTHONPATHであるdevel/lib/python2.7/dist-packages配下に置かれたことになり、使うことが出来るようになりました。

他のノードを作成するには同じようにpackage_name/scriptsの配下にノード名のファイルを追加します。

次にパラメータの値が決まっている場合、それはlaunchファイルに書くことが出来ます。launchファイルのタグの中にそれを記載することが出来ます。

serviceが実行中にパラメータを渡したい場合はrosservice callを使います。何かおかしい場合はroscoreのコンソールを見ると何が起こっているかが出力されています。他にもserviceのパラメータを変更したい場合にはrosparam setを使うこともできます。

ターミナルでエラーが出る場合はsource ~/catkin_ws/devel/setup.bashがそれぞれのターミナルウインドで実行されているか確認してみてください。

Subscribers

Subscriberは他のノードからの情報を受信するためのものです。

sub = rospy.Subscriber("/topic_name", message_type, callback_function)

/topic_nameは受信するtopicの名前です。
message_typeはtopic_nameの型です。
callback_functionは関数の名前で、メッセージが来た時に呼ばれる関数の名前です。メッセージは関数の引数にそのまま渡されます。

Subscriberノードを作る場合はPublisherと同じようにrospy.init_nodeで初期化します。rospy.spin()を使うとシャットダウン要求をノードが受信するまでブロックします。

Robotics① 導入

以下に学習時のメモを載せます。

概要

Lesson 2 ロボットとは

ロボットの定義

センサーがあって、意思決定が出来て、それに対して応答できるもの。
センサーはカメラ、Lidar、超音波、マイク、温度圧力センサー、においセンサーGPS等がある。磁気センサーも。
意思決定は決定木で表されることもあるし、DNNで表されることもある。
応答は、物理的な駆動もあるし、光、音等もので実現できる。

Project Search and Sample Return

シミュレータを使って、ローバーを操作します。ローバーはデータ取得、予見、行動というステップを踏んで動かします。データ取得ではカメラの画像を取得し、その画像の画素に閾値を設定して道と障害物を分けます。次にその画像をバードアイビューに変換する為に、Perspective Transformを当てはめます。opencvではgetPerspectiveTransform()で変換行列を求めて、warpPerspective()で画像を変換します。変換行列を求めるには、もとの画像のグリッドの交点の位置と、欲しい画像上でのグリッドの位置をgetPerspectiveTransform()に与えます。次にそのバードアイビューから全体の地図を生成するためにローバーの現在位置を原点とした座標系から、世界座標系に射影します。これには得た鳥観図を回転したり、縮尺を変えて整えます。これをローバーの動きに合わせて合成していくと、ローバーの画像から全体の地図が出来上がります。
次にどこに向かうかですが、今回は道と障害物を画像の閾値で分けたので、それを利用します。道と判定された領域の角度の平均をとりました。これで行くことのできる方角がおおよそ算出できます。その角度に合わせてローバーが向きを変えるように出力します。
GitHub - romth777/RoboND-Rover-Project: Project repository for the Unity rover search and sample return project.