ロボットロヌダヌプロゞェクト自分の堎所を芋぀ける

私の長幎のむギリスのパヌトナヌ2幎前に圌に宛おた「郵送先䜏所の認識」 は、ビゞネスプロセスを最適化するための新しいアむデアを持っおいたした。 もちろん、ポむントは、ロヌダヌがすべおのロボットの埌ろに続き、ロボットが停止するずすぐにロヌドずアンロヌドを開始するこずではありたせんが、ロヌダヌよりもはるかに倚くのロボットがあり、ほずんどの堎合ロボットぱンドポむントにありたすルヌト、ロヌド埅ち。 ロヌダヌは、あるロボットから次のロボットに移動し、それぞれをロヌドするだけで、商品の茞送に時間を浪費したせん。



背景



昚幎、ルンバの自走匏掃陀機プラットフォヌムを実隓したした。 新品の掃陀機の費甚は玄300ポンド䜿甚枈みのものは100ポンドたたはそれ以䞋であり、車茪に2台の電気駆動装眮、前面に2台のタッチセンサヌ、䞋郚に手順を怜出するためおよび䞊郚に爆匟ステヌションを芋぀けるために赀倖線センサヌが含たれおいたす。 センサヌの正確なリストはモデルによっお異なりたす。プロトコルは、䞋から最倧4぀の赀倖線センサヌを提䟛し、それぞれが1ビットを返したす「床が芋える/芋えない」。 いずれにせよ、距離蚈はありたせん。利甚可胜なセンサヌはすべおシングルビットです。 さらに、Roombaには「プログラム可胜なarduins」はありたせん。それを制埡するには、ラップトップたたはarduinoを䞊郚にむンストヌルし、RS-232経由でロボットず通信する必芁がありたす。 掃陀機で遊んだ埌、倉庫の棚の1぀にほこりを぀けたたたにしたした。



今幎、 Microsoft Robotics Development StudioMRDSを詊しおみるこずにしたした。これを促進するために、Microsoftは「暙準」ロボットを制埡する機噚ずプロトコルのセットである「MRDSリファレンスプラットフォヌム」の仕様を策定したした。 この仕様により、ロボット愛奜家は互換性のあるロボットを䜜成し、ロボット間でプログラムを転送できたす。 掃陀機のハヌドりェアず比范しお、リファレンスプラットフォヌムはより耇雑で匷力です。仕様には、Kinect、3぀の赀倖線距離蚈、2぀の超音波距離蚈、ホむヌル回転センサヌ゚ンコヌダヌが含たれたす。 これたでのMRDS RPの実装は、 Eddie Kinectを含たない玄1000ポンドず呌ばれるParallaxによっおのみ提䟛されおいたす。 EddieずMRDS RP仕様のプロトタむプロボットの写真ずの異垞な類䌌性は、仕様がParallaxずの密接なコラボレヌションで䜜成されたこずを瀺しおいたす。蚀い換えるず、ParallaxはMicrosoftにプラットフォヌムを参考にさせるこずに成功したした。



さたざたなセンサヌに加えお、Eddieには機械的に印象的なプラットフォヌム20 kgの負荷容量が宣蚀されおおり、それ自䜓の前に倉庫ロヌダヌを抌すのに十分なモヌタヌ電力がありたすず、プログラマブルコントロヌラヌParallax Propeller、぀たり 重芁なコヌドは、コンピュヌタヌから盎接呜什するだけでなく、ロボットに盎接瞫い付けるこずができたす。



British Channel 4 は 、Gadget Manプログラムのリリヌスの1぀に、ショッピングカヌトに統合されたEddieのデモを含む2分間の断片を含めたした。 残念ながら、英囜のIPアドレスの所有者のみがチャンネル4のWebサむトでプログラムを芋るこずができたすが、私はそれを぀かんでリロヌドするこずができたせんでしたおそらく読者の䞀郚は成功するでしょうか。







挑戊する



ロボットを「前埌巊右」だけで制埡するのは難しいこずではありたせん。そのような「リモヌト制埡」のプログラムぱディの配信に盎接含たれおいたす。 しかし、ロボットロヌダヌのパックを操瞊する人はいたせん。各自が倉庫のどのポむントにいるかを認識し、次のポむントに進む必芁がありたす。 この䜍眮の定矩で、䞻な問題が生じたした。 GPSのような技術は䞀掃されおいたす。衛星からの信号は倉庫の屋根を通過したせん。 Kinectはゞェスチャヌ認識に適しおいたすが、ストレヌゞシェルフをどのように認識したすか 棚自䜓には、メヌタヌごずに䞀意のバヌコヌドが貌り付けられおいたすが、たず、倖出先で棚から20 cm離れた堎所ではあたり信頌性の高いバヌコヌドが読み取れたせん近づきたせん-サポヌトにぶ぀かったり、棚から突き出おいる箱にぶ぀かる危険がありたす。 第二に、20cmの距離で棚に移動しおバヌコヌドを読み取るには、珟圚の䜍眮を倧たかに理解する必芁がありたす。さもなければ、棚から暪に移動するロボットは完党に取り返しの぀かない方向を倱いたす。



最初のナビゲヌションの詊みは、距離蚈に基づいおいたした。ロボットは棚を「暡玢」し、それに沿っお移動し、階段から壁に寄りかかっお驚異的な酔っぱらいが歩くように、所定の距離を維持したす。 類䌌性は非垞に倧きかった距離蚈からの信号の受信、MRDSでの信号の凊理、モヌタヌのチヌムの圢成、慣性の克服の遅延は10分の1秒でした。 この間、ロボットは冷静に暪に「匕き離された」ので、そのたびに「コヌス修正」は数十床になり、軌道は広いゞグザグになりたした。 これに加えお、゚ディの距離蚈は非垞に正確ではありたせんでした-゚ラヌは最倧±5cmです-ず非垞に狭い焊点、すなわち 棚は、床から所定の高さの1぀だけで認識されたした。



より有望なのは、ホむヌルセンサヌのナビゲヌションで、各ホむヌルの走行距離を玄±7mmの粟床で䞎えたした。 Eddieファヌムりェアには「ロボットの走行距離」䞡車茪の走行の半分ず「ロボットの方向」車茪の走行距離の差、正芏化係数の蚈算が既に含たれおいたすが、珟圚の座暙x、yは2぀のカりンタヌの倀から蚈算できたせん ロボットの軌道を小さな単玔なセクションに分割し、各セクションの動きを芁玄する必芁がありたす。 䞀芋、砎線ロボットの経路を近づけたいず思いたす。 しかし実際には、「静止」-モヌタヌの䞀定速床で-ロボットは円の匧に沿っお移動したす。半埄が倧きいほど、モヌタヌの速床の差は小さくなりたす。 それどころか、ブレヌクポむントは、ロボットがスポットから移動せずに方向を倉曎する堎合、実際には存圚し、存圚するこずはできたせん。 したがっお、ロボットの軌道を䞀連の円匧で近䌌したす。



蚈算



それはすべお、倜遅くにナプキンに絵を描くこずから始たりたした。







アヌクの開始時、ロボットは点A 既知にあり、最埌に点B 未知になり、巊車茪は既知の経路S 1 = φr1を、右車茪は既知の経路S 2 = φr2を移動したした 。 トラック間の距離wは䞀定であり、ロボットの蚭蚈によっお決たりたす。 取埗するもの

r 2 / r 1 = S 2 / S 1およびr 2 = r 1 + w

 r 1 + w / r 1 = S 2 / S 1

r 1 S 1 + wS 1 = r 1 S 2

r 1 = wS 1 / S 2 - S 1 



したがっお、ロボットが回転した角床はφ = S 1 / r 1 = S 2 - S 1 / wであり、距離| AB | = 2 | AM | = 2 r 1 + w / 2sin φ / 2=2 wS 1 / S 2 - S 1 + w sin φ / 2= w sin φ / 2 S 2 + S 1 / S 2 - S 1 



ここで、抂しお、䜍眮を決定するためのすべおの数孊蚈算されたφをロボットの方向の角床に远加し、珟圚の座暙を距離だけシフトするたびに| AB |。 実甚的な問題は残っおいたすそのような䜍眮の曎新を実行する頻床は 䜙りにも頻繁に、私達は離散的な「刻み目」で走行距離を枬定する車茪センサヌの䞍正確さに出くわしたす。 あたりにたれな堎合、円の匧による経路セグメントの近䌌は十分に正確ではありたせん。



ホむヌルセンサヌの読み取り倀は「ティック」の時点で最も正確であるず刀断したため、ホむヌルのティックごずにロボットの䜍眮を再蚈算したす。ただし、玄80ミリ秒前埌に蚘録されたロボットの調査時間を超えたせん。 少なくずも1秒間に1回、停止したホむヌルたずえば、回転䞭も蚘録されるようにしたす。 登録されたすべおの「ティック」はリストに保存されたす。これは、䜍眮を再蚈算するために、䞡方のホむヌルを同じ時間実行する必芁があるためです。 䞡方のホむヌルで以前の「ティック」 の早い方から間隔を取り、「ティックのリスト」に埓っお同じ瞬間に2番目のホむヌルの走行距離を線圢補間したす。 ギャップの終わりに぀いおは、同様に、目盛り付きホむヌルのセンサヌが読み取った走行距離を取埗し、2番目の走行距離を盎線的に倖挿したす。 したがっお、粟床は可胜な限り達成可胜でなければなりたせん。



ロボットの䜍眮が再蚈算されるたびに、ロボットを指定されたタヌゲットに向けるために、車茪の速床を新しい方法で蚭定する必芁がありたす。 ロボットを所定の「方䜍角」に正確に向けお定䜍眮で回転させるのは非珟実的です。前述の±7 mmのホむヌルマむレヌゞ゚ラヌは、ロボットが回転するず数床の゚ラヌになりたす。 ロボットは故意に「ランダムに」移動し、道路に沿っお再び旋回する必芁がありたす。 したがっお、タヌゲットぞの方向が珟圚の方向ずあたり違わない堎合は、事前に「方䜍角に向ける」こずなく、アヌクに沿っおロボットをタヌゲットに向けるこずにしたした。 珟圚の方向が垌望の方向ずπ / 2異なる堎合、そのような円匧は半円より倧きくなりたす。これは明らかに非珟実的です。この堎合、ロボットは正しい方向に回転する以倖に遞択肢がありたせん。



アヌクを蚈算するには、䞊の図に戻りたしょう。 これで、ポむントAずBがわかったため、角床φがわかりたした。 比率S 2 / S 1 = r 2 / r 1を芋぀ける必芁がありたす。これは、アヌクに必芁な車茪速床の比率を蚭定したす。 䞊蚘の蚈算を反察方向に繰り返したす。

| AB | = 2 | AM | = 2 r 1 + w / 2sin φ / 2

r 1 = | AB | /2sin φ / 2- w / 2

r 2 = | AB | /2sin φ / 2+ w / 2

r 2 / r 1 = 1 + w /| AB | /2sin φ / 2- w / 2= 1 + 2 /| AB | / w・sin φ / 2-1 



このようなアヌクは、ラむンABからどの皋床離れたすか ロボットが円匧を完成させるために䞡偎に2メヌトルの空きスペヌスを必芁ずする堎合、倉庫にはそのような機䌚はありたせん。棚間の通路の幅は1.5メヌトルしかありたせん。







距離を蚈算する| Mh | 円匧ず線の間| OH |-| OM | = r - r cos φ / 2= r 1 + w / 21-cos φ / 2



この「ハンプ」の倧きさが所定の制限を超える堎合、匧を描くように移動するこずを拒吊し、所定の䜍眮で回転しお、タヌゲットに向かっお近づこうずしたす。



刀明したように、実際には、モヌタヌのストレスの比率はただ車茪速床の比率を決定しおいたせん。 それらの間で正しい倉換を遞択するこずは可胜ですが、私は「ブルヌトフォヌス」で問題を解決したした私は比率を9床に䞊げたした。 片偎ぞのわずかな偏差は、すぐにロボットを反察方向に匷く抌したす。 実際、ロボットの動きが本圓に滑らかになったのはこの指数でした。角床が小さくなるず、ロボットは暪に倧きく倖れ、タヌゲット自䜓でのみ正しいルヌトに戻りたした。 倧きなものでは、補正が匷すぎるこずが刀明し、ロボットは狭いゞグザグで巊右に「揺れ」たした。



実装



ReferencePlatform2011配信からMarkRobotサヌビスに新しい機胜を远加するこずを蚈画しおいたした。PositionOperationsの粟神で新しいポヌトレットを実装したすPositionOperations : PortSet<GetPosition, SetPosition, SetDestination>



。 実際、この実装には至りたせんでした。サヌビス間で責任を分離せずに準備できたのは泥だらけのプロトタむプだけでした。 しかし、私がやったこずを芋せようずしたす。



最初に、座暙を操䜜するために䜕かが必芁です。それらを保存するためにSystem.Drawing.PointF



はありたせん。

 public struct Position { public readonly double x, y, heading; public Position(double x, double y, double heading) { this.x = x; this.y = y; this.heading = heading; } private static double Sqr(double d) { return d * d; } public double DistanceTo(double x, double y) { return Math.Sqrt(Sqr(this.y - y) + Sqr(this.x - x)); } public static double NormalizeHeading(double heading) { while (heading < -Math.PI) heading += 2 * Math.PI; while (heading > Math.PI) heading -= 2 * Math.PI; return heading; } // turn angle/2, go ahead distance, turn angle/2 again public Position advance(double distance, double angle) { double newHeading = heading + angle / 2, newX = x + distance * Math.Cos(newHeading), newY = y + distance * Math.Sin(newHeading); return new Position(newX, newY, NormalizeHeading(heading + angle)); } }
      
      





advance



メ゜ッドは、指定された回転角φず移動距離|の座暙の再蚈算を実装したす。 AB |蚘事の䞭倮の図のように。



今、ここにコヌドをコピヌしお、私は正確に「二重角床匏」を取埗し、角床φ / 2で| AB |だけシフトする代わりに、角床φで r 1 + w / 2だけ移動できるこずに気付きたした。たあ。



さらに、サヌビスにはPositionKeeping



むンスタンスが含たれおおり、その内郚にはEncoderLog



2぀のむンスタンスがありたす。「 EncoderLog



リスト」、各ホむヌルの個別のむンスタンスです。 理論的にはセンサヌデヌタは時系列で到着しない可胜性があるため、リストはSortedList



にSortedList



れたす。 EncoderLog



唯䞀の重芁な方法は、「ティック」の間たたは最埌の「ティック」の埌の任意の時点で倀を取埗するためのパスの線圢近䌌です。

 private class PositionKeeping { private static readonly DateTime initialized = DateTime.Now; public class EncoderLog { private SortedList<DateTime, double> log = new SortedList<DateTime, double> { { initialized, 0 } }; public void Register(DateTime at, double reading) { log[at] = reading; } public void Reset(DateTime at, double reading) { log.Clear(); log.Add(at, reading); } public DateTime LastTick { get { return log.Last().Key; } } public double LastReading { get { return log.Last().Value; }} public double ReadingAt(DateTime at) { int index = log.Count - 1; while(index>=0 && log.Keys[index] > at) index--; if(index<0) return double.NaN; // before first reading; impossible // now, log.Keys[index] <= at, and log.Keys[index+1] > at DateTime preceding = log.Keys[index], following = index<log.Count-1 ? log.Keys[index+1] : DateTime.MaxValue; if(following == DateTime.MaxValue) { // last reading precedes at; extrapolate if(index==0) // there's only one reading return log[preceding]; else { DateTime nextPreceding = log.Keys[index-1]; return log[nextPreceding] + (at-nextPreceding).TotalSeconds * (log[preceding]-log[nextPreceding]) / (preceding-nextPreceding).TotalSeconds; } } else // both readings are available; interpolate return log[preceding] + (at-preceding).TotalSeconds * (log[following]-log[preceding]) / (following - preceding).TotalSeconds; } } public EncoderLog leftEnc = new EncoderLog(), rightEnc = new EncoderLog();
      
      





「ティックリスト」に加えお、各「ティック」時の䜍眮のリストを PositionKeeping



に保存する必芁がありたす。この保存された䜍眮から、移動した距離を枬定しお新しい䜍眮を取埗したす。

  private SortedList<DateTime, Position> position = new SortedList<DateTime, Position> { { initialized, new Position(0, 0, Math.PI / 2) } }; public void Register(DateTime at, Position pos) { position.Add(at, pos); } public void Reset(DateTime at, Position pos) { // the position has changed => old ticks logs become obsolete leftEnc.Reset(at, leftEnc.ReadingAt(at)); rightEnc.Reset(at, rightEnc.ReadingAt(at)); position.Clear(); position.Add(at, pos); } public Position Current { get { return position.Last().Value; } }
      
      





導出された公匏に埓っお、䞡方のホむヌルセンサヌのデヌタに埓っお新しい䜍眮を蚈算するこずにより、 PositionKeeping



クラスを補完したす。 Update



はセンサヌチェックサむクル ServiceHandlerBehavior.Concurrent



から呌び出すこずができ、サヌビスポヌトを匕数ずしお受け取り、ティックが登録されるず新しい座暙を送信したす。 このメッセヌゞはサヌビスの状態を倉曎するため、 ServiceHandlerBehavior.Exclusive



ずしお扱う必芁がありたす。

  private static readonly TimeSpan RegisterDelay = TimeSpan.FromSeconds(1); // register null-tick if no actual ticks for this long public void Update(DateTime at, double left, double right, PositionOperations mainPort) { DateTime prevRef = Min(leftEnc.LastTick, rightEnc.LastTick); SetPosition set = new SetPosition { Timestamp = at, LeftEncUpdated = left != leftEnc.LastReading || at > leftEnc.LastTick + RegisterDelay, LeftEncReading = left, RightEncUpdated = right != rightEnc.LastReading || at > rightEnc.LastTick + RegisterDelay, RightEncReading = right }; if(set.LeftEncUpdated || set.RightEncUpdated) { set.Position = Recalculate(prevRef, left, right); mainPort.Post(set); } } private Position Recalculate(DateTime prevRef, double left, double right) { double sLeft = left - leftEnc.ReadingAt(prevRef), sRight = right - rightEnc.ReadingAt(prevRef); Position refPos = position[prevRef]; // has to exist if the encoder reference exists if (Math.Abs(sRight - sLeft) < .5) // less then half-tick difference: go straight return refPos.advance(Constants.CmPerTick * (sRight + sLeft) / 2, 0); else { double angle = Constants.CmPerTick * (sRight - sLeft) / Constants.WheelsDist, distance = Constants.WheelsDist * Math.Sin(angle / 2) * (sRight + sLeft) / (sRight - sLeft); return refPos.advance(distance, angle); } } }
      
      





目的のコヌドに残っおいるのはSetPosition



ハンドラヌだけです。これは、ロボットがタヌゲットに向かっお移動するようにモヌタヌの電圧を蚭定したす。

 [ServiceHandler(ServiceHandlerBehavior.Exclusive)] public void SetPositionHandler(SetPosition set) { if (!set.LeftEncUpdated && !set.RightEncUpdated) { // position updated by an absolute reference. positionKeeping.Reset(set.Timestamp, set.Position); } else { if (set.LeftEncUpdated) positionKeeping.leftEnc.Register(set.Timestamp, set.LeftEncReading); if (set.RightEncUpdated) positionKeeping.rightEnc.Register(set.Timestamp, set.RightEncReading); positionKeeping.Register(set.Timestamp, set.Position); } // the navigator Destination dest = state.dest; double distance = set.Position.DistanceTo(dest.x, dest.y); if (distance < 5) // reached { drivePort.SetDrivePower(0, 0); SendNotification(submgrPort, new DriveDistance()); return; } double heading = Position.NormalizeHeading(Math.Atan2(dest.y - set.Position.y, dest.x - set.Position.x)), power = (distance < 50) ? .2 : .4; // a few magic numbers if (Math.Abs(heading) < .05) { // straight ahead drivePort.SetDrivePower(power, power); return; } double r = distance / (2 * Math.Sin(heading / 2)), hump = r * (1 - Math.Cos(heading / 2)); if (Math.Abs(heading) > Math.PI / 2 || Math.Abs(hump) > Constants.MaxHump) { // not reachable by an arc; rotate if (heading > 0) // rotate left drivePort.SetDrivePower(-.3, .3); else // rotate right drivePort.SetDrivePower(.3, -.3); } else { // go in arc double rLeft = Math.Abs(r - Constants.WheelsDist / 2), rRight = Math.Abs(r + Constants.WheelsDist / 2), rMax = Math.Max(rLeft, rRight); // <Patrician|Away> what does your robot do, sam // <bovril> it collects data about the surrounding environment, then discards it and drives into walls drivePort.SetDrivePower(power * Math.Pow(rLeft / rMax, 9), power * Math.Pow(rRight / rMax, 9)); } }
      
      







次は



このロボットはかなりうたく運転したしたが、环積゚ラヌのために問題が発生したした。 座暙を修正するには、バヌコヌドの少なくずも20を正垞に読み取り、 LeftEncUpdated = RightEncUpdated = false



でLeftEncUpdated = RightEncUpdated = false



呌び出しおPositionKeeping



を「再起動」 PositionKeeping



SetPosition



でLeftEncUpdated = RightEncUpdated = false



です。 バヌコヌドの読み取り間隔が数メヌトルの堎合、座暙を決定する際の゚ラヌは20 cmを超えたせんでした-ロボットず棚の間にあたりにも倚く残したした。



方向を修正するものが䜕もないため、ヘディング゚ラヌの状況はさらに悪化したした。バヌコヌドはどの角床からでも読み取るこずができたすそしお神に感謝したす。 数床の环積誀差は、ロボットがランダムに乗っお党力で連隊に入るにはすでに十分です。 正面の解決策は、ゞャむロスコヌプを蚭眮するこずです。 しかし、Eddie甚の既補のゞャむロはありたせん。はんだごおずファヌムりェアの曞き蟌みで謎を回避したいず思いたす。



ゞャむロスコヌプや他の゚キゟチックなセンサヌなしで方向を修正するには、次の蚘事に専念したす。



All Articles