今月のやること.ROS Navigation Stack

 おはようございます.昨日宣言した通り,これから毎月/毎週の目標と結果を書いていきます.

 昨年のつくばチャレンジでは,画像処理の実装に時間がかかりすぎて制御周りが間に合わなかったので,今年はまずここから始めたいと思います.ロボットの足回りはとりあえずそれとなく動くものができているので,今月はロボットの足回りを ROS の Navigation Stack を用いて動かすようにします.各週の細かいTODOはやりながらおいおい更新します.


1. Week of 2017/2/6
Navigation StackのWiki読み.システム構成の理解.ロボット側のインターフェースの実装.
2. Week of 2017/2/13
 Navigation Stackを用いたロボットの前進・右折・左折
3. Week of 2017/2/20
 Odometryフレーム内でのロボットの制御
4. Week of 2017/2/27
 ??

 というとこで,乞うご期待!

Road To つくばチャレンジ2017 ~ 完走へ向けて!~

 ということで,「2016年を振り返って」という若干後ろ向きなエントリを書いた数分後に,「完走へ向けて」なんていう無謀なエントリを書いてしまいます....

 2017年が明けてもう一か月が経過しようとしているのですが,年始に会社の研修発表会があった関係でしばらくロボットはお預けになってました.先週末からようやく時間が取れ始めたので,またロボットいじりを始めたのですが,早速一つ壁にぶち当たりました....

「モチベーションが上がってこない...」

 我ながら困っているのですが,本番まで9ヶ月ある現在,ちょっと時間がありすぎでやる気がいまいちわいてきません...が,昨年いやというほど思い知ったのですが,この時期からコツコツ積み上げないと,とてもじゃないが11月の本番に間に合わないのもこれまた事実で...やる気がいまいちわいてこない理由を自分なりに考えてみたのですが,

 いまさらですが,「自作で自律走行するロボットを作成する.」というハードルが思った以上に高く,何から手を付けていいかわからない状態になっていることが原因なんだろうなと思います.知り合いには,「ちゃんと計画立てないからだよ.」と言われたのですが,全体像がすっきり見えてないものに対して,計画立てるのって結構しんどいんですよね...アルゴリズム実装するにしても,予想してたほど精度が出なくて,ボツになったりすると,速攻で計画くるってしまうし...

 ということで,今年の進めかたを考えました.

  • 月ごとに目標・プランニングをしてブログで公表する.
  • 週ごとに達成内容をブログで残す.(備忘録・整理目的)

 自分の知識不足もあり,年単位のプランニングがやっぱり難しいので,月ごとに取り組む内容を決めて,ブログで公表します.で,週ごとの成果をブログで公表します.

  • まず,できる限りありものを使って,走るところまで持っていく.

 フレームワークとかライブラリとかの使い方を勉強するのがめんどくさくて,昨年はもっとシンプルなものを自分で作れないかと思っていたのですが,まずはありものをできる限りパクる・再利用するという方針で作り上げていきたいと思います.

 ということで,3日坊主になる可能性もありそうですが,,温かく見守ってください.次のエントリで書きますが,今月はROSのNavigationスタックの勉強・実装をします.

2016年を振り返って...

 現在,朝7時...今年は早寝・早起きを習慣にしようととりあえず毎朝5時に起きてます.昨日から(笑)

 2016年は転職から始まり,つくばチャレンジ2016参加,SI2016でのつくばチャレンジ取り組み発表,会社の研修・発表会等,いろいろありました.ばたばたといろいろ動きましたが,反省点も多々あり,,,というか,振り返ると反省点ばかりだったような気がします.

 結局のところ,2016年もやっぱりつくばチャレンジが一番のメインイベントだったのですが,なかなか思うようにいかず...参加2年目ということで,「2016年は完走するぞ!」と,ガチで(年初は)意気込んでいたのですが,結果的には全然間に合わず2015年の内容とほぼ同じになってしまいました.
 
 ハード作成の時は,トライアンドエラーでとにかく手を動かして,無理くりいろんなものをくっつけていったらなんとなく動くところまでは行ったのですが,ソフト作成に関しては思ったようにいきませんでした.まず,ROSとかOpenCVなんかのライブラリ・フレームワークのキャッチアップをして,それからアルゴリズムの勉強をして,,,,という感じで,「作る」前に「理解」することが前提で,全然間に合いませんでした.

 2017年は取り組み方をもう少し工夫しないといけないなと思ってます.この辺は,次の投稿で考えをまとめられれば!今年は,一週間に最低一つは記事を書きます!(書くよう努力します(笑))下のスライドにも書いてありますが,「素人がGoogle先生だけを頼りに,0からロボットを作ったらどうなるか?」をテーマに今年も頑張りますので,一つよろしくお願いします.<(_ _)>

 
下記,SI2016での発表資料です.今回は何か技術的に中身のある発表にしたかったのですが,結局取り組み紹介で終わってしまいましたが...

www.slideshare.net


写真は,SI2016で訪れた札幌の写真.
f:id:rkoichi2001:20170207071747j:plain

以上,旧正月の挨拶でした!

パソコン環境設定 2017

今年もつくばチャレンジ2017に向けて、まずは環境のセットアップということで早速準備。
備忘録として、インストール一覧をメモメモ。

Windows
Chrome
Visual Studio 2015, Community
ITunes
Office
Anaconda
さくらエディタ

Linux
Chrome

  • sudo apt-get update
  • sudo apt-get install libgconf2-4 libnss3-1d libxss1

で、インストールした Chrome を実行。

ibus-mozc

  • sudo apt-get install ibus-mozc

で、再起動後に Text Entry Setting であらたに追加された Mozc を選択。

日本語キーボード対応

  • /usr/share/ibus/component/mozc.xml

のlayoutの部分を変更する。
default -> jp


ROS Kinetic
下記のチュートリアル通りに。
kinetic/Installation/Ubuntu - ROS Wiki

JDK
Eclipse を動かすための JAVA 環境のインストール。

  • sudo apt-get install default-jre

Eclipse
下記から Eclipse IDE for C/C++ Developers のインストール。Neonってのが一番新しいらしい。
http://www.eclipse.org/downloads/eclipse-packages/
Eclipse を /opt/ 以下に移動して、実行ファイルのパスを通す。
PATH="$PATH:/opt/eclipse/"

turtle-bot のシミュレーション関係一式
turtlebot/Tutorials/indigo/Turtlebot Installation - ROS Wiki

sudo apt-get install ros-kinetic-turtlebot ros-kinetic-turtlebot-apps ros-kinetic-turtlebot-interactions ros-kinetic-turtlebot-simulator ros-kinetic-kobuki-ftdi ros-kinetic-ar-track-alvar-msgs ros-kinetic-turtlebot-gazebo

Python リストのソートの仕方

機械学習の勉強にPythonを使ってるんですが,ちょっとしたデータ処理したりするの,めっちゃ使いやすいですね.
結構大きいデータ使ってるんですが,そこそこの速度で動いてくれるし.

ちょっと,Pythonのリストをソートする方法を調べてたんですが,備忘録としてメモしておきます.

例えば,名簿のリストがあったとします.
デフォルトでは,アルファベット順で並んでますが,同時に各人の身長のデータも持っていて,身長順にリストをソートしたいとします.
この時には,sorted関数を使えます.

['crues', 'hanks', 'mike', 'samp', 'tifanny', 'tom']

下記,名前が与えられたらその人の身長を返すメソッドだとします.

def orderFunc(k):
    
    if (k == "sam"):
        return 175
    elif (k == "mike"):
        return 180
    elif (k == "tom"):
        return 150
    elif (k == "hanks"):
        return 175
    elif (k == "crues"):
        return 190
    elif (k == "tifanny"):
        return 200
        
    return 0

で,上記のメソッドの名前を sorted 関数に渡してあげます.

In [180]: sorted(namelist, key=orderFunc, reverse=True)
Out[180]: ['tifanny', 'crues', 'mike', 'hanks', 'sam', 'tom']

めっちゃ便利ですね...

Pandas の使いかた

およそ半年ぶりの更新ですが,何事もなかったかのように平然とエントリをし始めましたが...
(つくばチャレンジにも参加しました.結果は残念でしたが...また取り組みをアップします.)

最近会社で機械学習の勉強をしていて,その際に Pandas というライブラリを使う必要があったので,概要と使い方を備忘録としてまとめておきます.
勉強に使ったのは下記の本です.一年半くらい前に買って,積読状態になっていたので,活用できる機会ができてよかった...

Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理

Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理


  • pandas のデータ構造

 大まかにいうと,Series と DataFrame という二種類のデータ構造がある.シリーズというのは,一次元の配列らしいが,Numpyの配列と異なるところは,インデックスというデータラベルの配列がつくところ.まず,IPython を使って Series で遊んでみます.


ライブラリのインポート.

from pandas import Series, DataFrame
import pandas as pd
  • シリーズオブジェクトの作成
In [131]: series = Series([1, 10, 100, 1000])

In [132]: series
Out[132]: 
0       1
1      10
2     100
3    1000
dtype: int64

上の出力を見てみると,Numpyの配列とは違って,勝手にインデックスが付与されていることがわかります.
また,このインデックスに関しては,自分で好きなものを付与することもできます.

  • インデックスラベルを指定したシリーズオブジェクトの作成
In [134]: series_w_index_specified = Series([1, 10, 100, 1000], index=['JPN', 'USA', 'GER', 'GBR'])

In [135]: series_w_index_specified
Out[135]: 
JPN       1
USA      10
GER     100
GBR    1000
dtype: int64
  • データフレームオブジェクトの作成

 シリーズが一次元の配列であるのに対し,こちらは二次元の配列というかテーブル風のデータ構造.

In [151]: data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'], 
     ...: 'year' : ['2000', '2001', '2002', '2001', '2002'], 
     ...: 'pop' :['1.5', '1.7', '3.6', '2.4', '2.9']}

In [152]: frame = DataFrame(data)

In [153]: frame

In [154]: Out[153]: 
   pop   state  year
0  1.5    Ohio  2000
1  1.7    Ohio  2001
2  3.6    Ohio  2002
3  2.4  Nevada  2001
4  2.9  Nevada  2002

 これが使いたかったんです.以前 SQL を使ってデータベースの処理をしていたことがあったんですが,テーブルの結合とかと等価の処理を自分で作るのは時間もかかるしめんどくさいし..ということで,pandas を使ってデータセットをマージする方法をメモ.

pandas.merge : データフレームに含まれる行を一つ以上のキーでマージできる.
pandas.concat : 複数の列のオブジェクトを貼り合わせたり,積み上げたりすることが可能.

    • pandas.merge を使ったデータフレームのマージ
In [9]: df1 = DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
   ...: 'data1': range(7)})

In [10]: df2 = DataFrame({'key': ['a', 'b', 'd'],
    ...: 'data2': range(3)})

In [11]: df1
Out[11]: 
   data1 key
0      0   b
1      1   b
2      2   a
3      3   c
4      4   a
5      5   a
6      6   b

In [12]: df2
Out[12]: 
   data2 key
0      0   a
1      1   b
2      2   d

上で作成したデータを,pandas.merge を使ってマージします.
超簡単ですね.ありがたやありがたや.

In [15]: pd.merge(df1, df2, on = 'key')
Out[15]: 
   data1 key  data2
0      0   b      1
1      1   b      1
2      6   b      1
3      2   a      0
4      4   a      0
5      5   a      0


結合する列の名前が左右のテーブルで異なるときもあると思いますが,こちらに関しても下記のようにすれば結合できるみたいです.
両方にキーが存在する行のみがマージされた結果に残されている様子が見て取れます.この結合を内部結合といい,結果に表れるキーは両者の共通集合になります.

In [22]: df3 = DataFrame({'lkey':['b', 'b', 'a', 'c', 'a', 'a', 'b'],
    ...: 'data1': range(7)})

In [23]: df4 = DataFrame({'rkey':['a', 'b', 'd'],
    ...: 'data2': range(3)})

In [24]: pd.merge(df3, df4, left_on='lkey', right_on='rkey')
Out[24]: 
   data1 lkey  data2 rkey
0      0    b      1    b
1      1    b      1    b
2      6    b      1    b
3      2    a      0    a
4      4    a      0    a
5      5    a      0    a

一方で,どちらか片方に存在するキーも消したくない場合もあると思います.この場合には,外部結合を実施すれば結合できます.
ただし,ペアとなるキーが相手方に存在しない場合はそのデータはNaNとなります.下記の実行結果を見てもわかる通り,結合ペアのキーのない箇所はデータが NaN になっています.

In [25]: pd.merge(df3, df4, left_on='lkey', right_on='rkey', how='outer')
Out[25]: 
   data1 lkey  data2 rkey
0    0.0    b    1.0    b
1    1.0    b    1.0    b
2    6.0    b    1.0    b
3    2.0    a    0.0    a
4    4.0    a    0.0    a
5    5.0    a    0.0    a
6    3.0    c    NaN  NaN
7    NaN  NaN    2.0    d

OpenCV Stereo Matching の性能比較 1

前回の投稿から少し時間が空いてしまいました...が,ブログ更新が飽きたというわけではなく,ちょっと更新するほどのまとまったネタができなかったわけです.というわけで,今日はつくばチャレンジで使うステレオマッチングメソッドの比較・選定を行いました.とりあえず,OpenCVでロジックが提供されている下記の3つのアルゴリズムを比較検討しました.

  1. Block Matching Method
  2. Stereo Block Matching Method
  3. Stereo Graph Cut Method

0. 実験に用いたベースデータ

 実験には,手軽に手に入る Tsukuba の視差画像を用いました.

左画像 サイズ 384 x 288
f:id:rkoichi2001:20160625230555p:plain
右画像 サイズ 384 x 288
f:id:rkoichi2001:20160625230603p:plain

1. Block Matching Method

 一番オーソドックスなアルゴリズムです.実験結果は下記の通り.
実行時間 3.3464ms (334.646 / 100)
f:id:rkoichi2001:20160625232749p:plain

2. Semi Global Matching Method

 一番良く使われているアルゴリズム?ではないでしょうか.
実行時間 16.6549ms (1665.49 / 100)
f:id:rkoichi2001:20160625231149p:plain

3. Stereo Graph Cut Method

 実行時間が結構長いです..が,結果も一番きれいです.解説にあるとおり,オフラインでの使用前提ですね.
実行時間 6664.27ms (66642.7 / 10)
f:id:rkoichi2001:20160625233306p:plain

実際には,パラメータを適切に調整して比較したわけではないので純粋に比較できているわけでは無いです.次のエントリではこの3つの手法を自分が撮影した画像に適用して見たいと思います.下記,実験に使ったソースコードをおいておきます.

1. Block Matching Method

#include <iostream>
using namespace std;

#include "opencv2/opencv.hpp"

int main() {
  const char *rightImg = "/home/koichi/data/tsukuba/scene1.row3.col4.ppm";
  const char *leftImg = "/home/koichi/data/tsukuba/scene1.row3.col2.ppm";

  // const char *rightImg = "/home/koichi/Desktop/Stereo Pair/pentR.bmp";
  // const char *leftImg = "/home/koichi/Desktop/Stereo Pair/pentL.bmp";

  IplImage *imgR, *imgL, *dst;
  IplImage *dispLeft, *dispRight;
  int disp = 32;
  //  CvStereoGCState *state = cvCreateStereoGCState(disp, 4);
  CvStereoBMState *state = cvCreateStereoBMState(CV_STEREO_BM_BASIC, disp);
  state->SADWindowSize = 9;

  // . Load Image.
  imgR = cvLoadImage(rightImg, CV_LOAD_IMAGE_GRAYSCALE);
  imgL = cvLoadImage(leftImg, CV_LOAD_IMAGE_GRAYSCALE);

  CvSize size;
  size.width = imgR->width;
  size.height = imgR->height;

  // . Allocate Memory
  dispLeft = cvCreateImage(size, IPL_DEPTH_16S, 1);
  dispRight = cvCreateImage(size, IPL_DEPTH_16S, 1);
  dst = cvCreateImage(size, IPL_DEPTH_8U, 1);

  {
    cv::TickMeter meter;
    meter.start();

    for (int i = 0; i < 100; i++) {
      // Calcuate Disparity
      cvFindStereoCorrespondenceBM(imgL, imgR, dispLeft, state);
    }

    meter.stop();
    std::cout << "Average Time for Calculation : "
              << meter.getTimeMilli() / 100.0 << std::endl;
  }
  double min, max;
  cvMinMaxLoc(dispLeft, &min, &max);

  cvConvertScale(dispLeft, dst, 255 / (max - min), 255 / min);
  // cvNormalize(dispLeft, dst, 1, 0, CV_MINMAX);

  const char *winName = "Stereo Correspondence";

  cvNamedWindow("Right Image", CV_WINDOW_AUTOSIZE);
  cvNamedWindow("Left Image", CV_WINDOW_AUTOSIZE);
  cvNamedWindow("Disparity", CV_WINDOW_AUTOSIZE);

  cvShowImage("Right Image", imgR);
  cvShowImage("Left Image", imgL);
  cvShowImage("Disparity", dst);

  cvWaitKey(0);

  // cvReleaseStereoGCState(&state);
  cvDestroyAllWindows();

  cvReleaseImage(&imgR);
  cvReleaseImage(&imgL);

  return 0;
}

2. Semi Global Matching Method

#include <iostream>
#include <string>
using namespace std;

#include "opencv2/opencv.hpp"

int main() {
  string rightImg = "/home/koichi/data/tsukuba/scene1.row3.col4.ppm";
  string leftImg = "/home/koichi/data/tsukuba/scene1.row3.col2.ppm";

  cv::Mat imgR = cv::imread(rightImg, CV_8UC1);
  cv::Mat imgL = cv::imread(leftImg, CV_8UC1);
  cv::Mat dispMat(imgR.rows, imgR.cols, CV_8UC1);

  int disp = 32;
  int minDisparity = 0;
  int numDisparities = 32;
  int SADWindowSize = 3;
  int P1 = 0;
  int P2 = 0;
  int disp12MaxDiff = 0;
  int preFilterCap = 0;
  int uniquenessRatio = 0;
  int speckleWindowSize = 0;
  int speckleRange = 0;
  bool fullDp = false;

  cv::StereoSGBM sgbm(minDisparity, numDisparities, SADWindowSize, P1, P2,
                      disp12MaxDiff, preFilterCap, uniquenessRatio,
                      speckleWindowSize, speckleRange, fullDp);

  {
    cv::TickMeter meter;
    meter.start();

    for (int i = 0; i < 100; i++) {
      sgbm(imgL, imgR, dispMat);
    }

    meter.stop();
    std::cout << "Average Time for Calculation : "
              << meter.getTimeMilli() / 100.0 << std::endl;
  }

  double min, max;
  cv::minMaxLoc(dispMat, &min, &max);
  cv::convertScaleAbs(dispMat, dispMat, 255 / (max - min), 255 / min);

  cv::namedWindow("Right Image", CV_WINDOW_AUTOSIZE);
  cv::namedWindow("Left Image", CV_WINDOW_AUTOSIZE);
  cv::namedWindow("Disparity", CV_WINDOW_AUTOSIZE);

  cv::imshow("Right Image", imgR);
  cv::imshow("Left Image", imgL);
  cv::imshow("Disparity", dispMat);

  cvWaitKey(0);
  cvDestroyAllWindows();

  return 0;
}

3. Stereo Graph Cut Method

#include <iostream>
using namespace std;

#include "opencv2/opencv.hpp"

int main() {
  const char *rightImg = "/home/koichi/data/tsukuba/scene1.row3.col4.ppm";
  const char *leftImg = "/home/koichi/data/tsukuba/scene1.row3.col2.ppm";

  // const char *rightImg = "/home/koichi/Desktop/Stereo Pair/pentR.bmp";
  // const char *leftImg = "/home/koichi/Desktop/Stereo Pair/pentL.bmp";

  IplImage *imgR, *imgL, *dst;
  IplImage *dispLeft, *dispRight;
  int disp = 32;
  CvStereoGCState *state = cvCreateStereoGCState(disp, 4);

  // . Load Image.
  imgR = cvLoadImage(rightImg, CV_LOAD_IMAGE_GRAYSCALE);
  imgL = cvLoadImage(leftImg, CV_LOAD_IMAGE_GRAYSCALE);

  CvSize size;
  size.width = imgR->width;
  size.height = imgR->height;

  // . Allocate Memory
  dispLeft = cvCreateImage(size, IPL_DEPTH_16S, 1);
  dispRight = cvCreateImage(size, IPL_DEPTH_16S, 1);
  dst = cvCreateImage(size, IPL_DEPTH_8U, 1);

  // Calcuate Disparity
  {
    cv::TickMeter meter;
    meter.start();
    int repeat = 10;

    for (int i = 0; i < repeat; i++) {
      cvFindStereoCorrespondenceGC(imgL, imgR, dispLeft, dispRight, state, 0);
    }

    meter.stop();
    std::cout << "Average Time for Calculation : "
              << meter.getTimeMilli() / repeat << std::endl;
  }

  cvConvertScale(dispLeft, dst, -256 / disp);

  const char *winName = "Stereo Correspondence";

  cvNamedWindow("Right Image", CV_WINDOW_AUTOSIZE);
  cvNamedWindow("Left Image", CV_WINDOW_AUTOSIZE);
  cvNamedWindow("Disparity", CV_WINDOW_AUTOSIZE);

  cvShowImage("Right Image", imgR);
  cvShowImage("Left Image", imgL);
  cvShowImage("Disparity", dst);

  cvWaitKey(0);

  // cvReleaseStereoGCState(&state);
  cvDestroyAllWindows();

  cvReleaseImage(&imgR);
  cvReleaseImage(&imgL);

  return 0;
}