ローリングコンバットピッチなう!

AIとか仮想化とかペーパークラフトとか

conda-forgeのOpenCVが何時の間にか動画対応(ffmpeg対応)になっていた

[technology][python]conda-forgeのOpenCVが何時の間にか動画対応(ffmpeg対応)になっていた

いつもchainer等の実験に使っているanacondaベースのpython3.6環境でOpenCVを使って動画ファイルの操作をしようとしたのですが、上手く行かない。
具体的には

import cv2

input_video = cv2.VideoCapture('test.mp4')

の様にして、

input_video0.isOpened()

をチェックするとFalseになっていて、動画ファイルをオープン出来ない。
色々ググってみると、「anaconda環境にOpenCVをインストールする際のメジャーなインストール元リポジトリであるcondaやmenpoに登録されたOpenCVのパッケージはffmpegを使う様にビルドされていないので、自分でOpenCVをビルドして組み込むのが吉」というもの。

いくつか同様の事をやっているブログ等を参考にビルド環境を作ろうとしたのですが、ふと思いついて、

$ conda update opencv

を実行すると、動画ファイルの読み込みが出来る様になりました。
インストールされているopencvはconda-forge由来のもので、

$ conda install -c conda-forge opencv

でインストールしたものです。
update後のパッケージ情報を見ると(py36は今使っているpython3.6環境の名前です)

$  conda list -n py36
〜中略〜
ffmpeg                    4.0                  hcdf2ecd_0  
〜中略〜
libopencv                 3.4.2                hb342d67_1  
〜中略〜
opencv                    3.4.2            py36h6fd60c2_1  
openh264                  1.7.0                         0    conda-forge

の様にOpenCVが3.4.2になっており、ffmpegもconda環境に入っています。
この環境でcv2経由での動画ファイルの読み込み、書き出しが両方とも動作しています。
以前は動画扱えなかったんですが、最新のconda-forgeのパッケージではffmpeg対応している様です。

ちなみにOSはUbuntu 16.04 LTSです。

夏休みの自由研究: AIロボットCOZMOを画像認識で一時停止、再発進させる(DONKEY COZMO Part.5)

[technology][Deep Learning][chainer]AIロボットCOZMOを画像認識で一時停止、再発進させる(DONKEY COZMO Part.5)

夏休みに入ったのでCOZMOの自律走行スクリプトを少し改造しました。

これまで「前進」「左転回(10度)」「右転回(10度)」という3種類の判断をしていたものに、「何もしない(停止)」という4つ目の判断を追加。
例えばCOZMOの前に障害物を置いて、その直前に停止させる等を学習可能に。

  • 停止、再発進時にCOZMOが音声で通知する様にした

あまり真面目に学習用データを取っていない(画像600枚弱)のですが、ラフに試した結果は下記の動画の通りです。

youtu.be

最新ソースコードを纏めてgithubに置きました。
github.com

Deep Learningで画像認識してロボットを制御するという実験をやってきたのですが、やってみて感じた事をちょっと書いてみます。
どれも文章にすると当たり前というか、まあそうだよね..という話なんですが、他人からの見聞ではなく、自分で試してみると実感として判る感じがします。

たかだが320x240のグレースケール画像を更に縦横1/2に縮小した画像から、ロボットが得る視点とその視点に対する操作(「前進」「左転回(10度)」「右転回(10度)」「何もしない(停止)」を学習(記憶?)させるわけですが、巷の初学者向け情報に載っている様な「畳み込み→Max Pooling→畳み込み→Max Pooling→全結合」という比較的シンプルなネットワーク設計でちゃんと自律走行している(様に見える)のは凄いなと。例えばCOZMOのカメラで得た画像からセンターラインをDeep Learning以外の画像認識手段で認識してトレースという仕組みも作れるのですが、自分が作ったコースだとコースが狭すぎるのと、COZMOのカメラの画角が狭いの二重苦でセンターラインをCOZMOの視界に捉え続けて移動させるのが結構難しい。そういったケースでも壁の模様等から左右の転回を記憶できてしまうがなかなかに凄い。

  • でもけして賢くは無い

といいつつ、やっているのは人間が走行させた際の動きを記憶させているだけなんですよね。人間が教えていない事はあたり前だけど出来ない。色々試していると判るし、当然なのですが、COZMO走行中にコース上に学習時には無かったものを適当に置いたりすると、どう動くか判りません。人間ならコースの脇に何かが置いてあったりしてもそれに惑わされてリしないのですが、Deep Learningでそこまでの汎化性能(と言って良いのか?)を獲得させるには相当幅広いシチュエーションでデータを取って学習させる必要があり、非常に効率が悪いです。

  • 学習データ(教師データ)には意識して一貫性を持たせないと上手く行かない

コースアウト状態や逆向き状態からの復帰を学習させた際に感じたのですが、いわゆる異常状態や準正常状態に陥った際にどのような動きでそこから脱出するか、明確なルール・基準を持って行動を学習させないと、COZMOが迷う様な動きを多々見せます。今回、右回りコースをメインで学習させましたが、例えばCOZMOを逆向きから正常な方向を戻す際に、常に右転回繰り返しで戻す様にしないと、結構COZMOが(一見すると)迷って嵌り込むパターンが散見されました。上手く文章で説明できていないのですが、Deep Learning用に教師データ(アノテーション)を人間が作る際、論理的に一貫した基準に基づいてアノテーションしないと推論精度が落ちるという当たり前といえば、当たり前の話なのですが、この一貫性をもったアノテーションが口で言うほど簡単ではないな...というのが感じるところです。


ちなみに機械学習ディープラーニングをやるのに、理論(数式)の理解は必須か?という議論が良くなされています。色々AI関連の記事を見ていると「ちゃんと理論的背景を理解して取り組まないとダメだよ」派と「細かい理論判らなくても、ニューラルネットワークはTensorFlowとか便利なライブラリがあり、使い方覚えれば誰も動かせるので、道具として使い方・外見えの特性をある程度理解したら、後は実践あるのみ」派にだいたい二分されます。

自分は統計論も線形代数も、ざっくり雰囲気レベルで知っているだけで、数式は全然理解できないレベルの人間です。ハイパーパラメーターの最適化とか、学習結果の品質測定とか、プロとしてやるならある程度理論的バックボーンは理解できた方が良いだろうなとは思います。一方で実際にニューラルネットワークや統計的機械学習を使ったアプリを組み、データを集めて学習させると、そのアプリケーションとかユースケース特有の特性に応じて設計する事(データをどうベクトル化するか?どういう粒度で採取するかetc)が、ニューラルネットワークそのものの設計以上に大事になるケースも多いだろうという感触。

数式理解できなくても、今回のCOZMO程度のデモは組めるので、ある程度アプリケーションの方向性が出たら、細かいチューニングは専門家の手を借りれば良い。大枠ではDeep Learningをどう使うか?どうユースケースにはめ込むのか?を考える方が大事かなと思います。


前回までの記事:
rc30-popo.hatenablog.com
rc30-popo.hatenablog.com
rc30-popo.hatenablog.com
rc30-popo.hatenablog.com

エレキット25周年(たまにはアナログ電子工作がしたい..)

[technology][news]エレキット25周年

akiba-pc.watch.impress.co.jp

ネットを徘徊していたらこんな記事が。
エレキットって25周年なんですね。なんかもっと昔からあった気がしていたんですが、おじさんからすると案外最近なんだなあと。

ここに紹介されているラジオの組み立てキット、少し前に欲しいなと思っていたんですよね。

実は家の中にラジオ受信機なるものが一台も無く、普段は別に要らないのですが、地震なんかでネットが使えない、停電とかになったら、乾電池で動くラジオがあると良いなと。
まあ、そのへんの電気屋で買ってくれば良いのですが、どうせならこーいう基盤むき出しの電子工作感あるやつが良いなと。

去年まで使っていたASUSのZenFone2 LaserにはFMラジオが内蔵されていたのですが、水没させてしまってそれ以降、家には一台もラジオが無い。

で、上記の記事見てまた欲しくなってきたのですが、といいつつ、なかなか買わない自分。いざとなったら電子ブロックミニを引っ張りだしてラジオ化すればいいやとかw

ちょっとググッてみたんですが、最近はラジオの組み立てキットもあんまり無いんですね。AITENDOにいかにも電子工作な感じのキットが色々あるみたいですが。

でもラジオより今欲しいのはこれ!!子供がもう少し大きくなったら子供の教材として買おう、うん。けして父ちゃんの趣味ではないぞ。

akiba-pc.watch.impress.co.jp



創立25周年記念パッケージ ELEKIT(エレキット) AM/FM DSP ラジオ TK-739D
株式会社イーケイジャパン
売り上げランキング: 2,686

ディープラーニングで自律走行するロボット(COZMO)にコースアウトからの復帰を学習させる(DONKEY COZMO Part.4)

[technology][AI][COZMO][Deep Learning][Chainer]DONKEY COZMOにコースアウトからの復帰や逆向きからの復帰を学習させてみた

rc30-popo.hatenablog.com
rc30-popo.hatenablog.com
弊ブログの上記記事で、COZMO SDKとChainerを使い、ディープラーニングで自律走行する様になったCOZMOですが、前回まではコース上を正常に周回する動作しか学習させていません。
例えばコースに対して逆向きに置いたり(テストに使ったコースは右回りで学習させた)、コースをはみ出したところに置いた時に自動でコースに復帰する様な動きのデータを取り、学習させてみました。

今回はpythonソースコード上のロジックは一切触らず、ひたすらCOZMOをダンボールで作成した箱庭コース上の様々な場所に、様々な向きで置き、そこから正常なコースに戻る操作を繰り返し学習させました。
まだデータ取り的に不十分な感じですが、それなりにコースに復帰する様になりました。

youtu.be

動画を見ると判るのですが、たまに左右どちらに動くべきか迷う様な挙動があります。
現状の実装では単純にカメラ画像を畳み込みニューラルネットワークに流し込んで、出てきた操作ラベルに従ってCOZMOを自動操縦しているのですが、左→右→左とか、右→左→右とかの迷う様な動きを検出したら、強制的に少し前進させるとか、左→右→左→左あるいは右→左→右→右等、視野を意図的に迷っている状態からずらす様に制御ロジックを入れた方が良いかもしれません。

別途試行してみたいと思っています。

普通にコースを周回する様子は下記の動画で。
youtu.be

AIロボットCOZMO自律走行用の畳み込みニューラルネットワークをちょっとチューニング

[technology][AI][COZMO][Deep Learning][Chainer]Donkey COZMOのCNNを少し小さくしてみる

rc30-popo.hatenablog.com
の続きです。

最初に作ったDONKEY COZMOの畳み込みニューラルネットワークの学習データをファイル保存すると180MBあったので、少しコンパクト化を試みました。
最初はチャンネル数16と32の畳み込みでしたが、これを8と16に変更。全結合層の中間層1024ノードを256ノードに減らしました。

class czCnn(Chain):
    def __init__(self):
        super(czCnn, self).__init__(
            conv1 = L.Convolution2D(in_channels=1, out_channels=8, ksize=4, stride=1, pad=1),
            conv2 = L.Convolution2D(in_channels=8, out_channels=16, ksize=4, stride=1, pad=1),
            l0 = L.Linear(None,256),
            l1 = L.Linear(None,3),
            bncv1 = L.BatchNormalization(8),
            bncv2 = L.BatchNormalization(16),
            bn0 = L.BatchNormalization(256),
        )
    def forward(self, x,ratio=0.5):
        h = F.reshape(x,(len(x),1,160,120))
        h = F.max_pooling_2d(F.relu(self.bncv1(self.conv1(h))),2)
        h = F.max_pooling_2d(F.relu(self.bncv2(self.conv2(h))),2)
        h = F.dropout(F.relu(self.bn0(self.l0(h))),ratio=ratio)
        h = self.l1(h)
        return h

これで前回の走行データを学習させましたが、20epochくらいで学習データに対するフィッティングは100%正解が出る様になりました。
10epochだとちょっとまだ足りない感じです。学習データのサイズは1/10くらいに。

実際に走らせてみると挙動不安定感が元のネットワークより増した様な気もします。

更に別のコースを作って、そちらもテスト。前回はコース10周分くらい学習させましたが、今回は6周くらい。
だいたい上手く行くのですが、うまく曲がらずに壁にぶつかるケースがありました。
このコースももう少し学習用データを増やして、更に最初のコース用データと合わせて学習してどちらのコースでも走行可能なネットワークを作ってみたいと思います。


youtu.be


7/6: 学習データのサイズに誤りがあったので訂正。

Donkey COZMOを作る Part.2/3(AIロボットCOZMOをChainerで自律走行させる)

[technology][deep learning][chainer]COZMOを内蔵カメラを利用してDeep Learning(Chainer+python)で自律走行させる。Part.2-学習編-

rc30-popo.hatenablog.com
の続きです。

前回、COZMOのカメラ画像と操作(前進、左旋回、右旋回)の紐付けデータが取れたので、これをDeep Learningで学習させます。
画像を畳み込みニューラルネットワークに読み込ませ、操作ラベル(前進、左旋回、右旋回)を教師データにします。

畳み込みニューラルネットワークの実装には国産Deep LearningフレームワークのChainerを利用しています。

ニューラルネットワークの設計

一応学習に使うマシンにはGeForceが載っているのですが、GPUに読ませるデータはGPU内蔵2GBとメインメモリから2GBの計4GBしか使えないので、なるべくメモリサイズが小さくなる様にあまり大規模なものは組まない様にしています。
ニューラルネットワークの入力は180x120とCOZMOのQVGA(320x240)画像を更に縦横1/2に縮小します。前回取得したカメラ画像を手動で何段階か縮小した感触として、このくらいのサイズなら画像から特徴抽出して操作ラベルとの紐付け学習ができるかな?と直感で決めたサイズです。

畳み込みとMaxプーリングを2段重ねて、全結合層へ。全結合層は中間層を1024ノードとしましたが、これもたいした根拠はなく適当です。
畳み込みのチャンネル数は一段目が16,二段目が32チャンネルを出力しています。中間層のノード数と合わせて、これも適当に決めましたが、結果としては上手く行っています。
もう少し各パラメータ小さい値でも良かったかもしれません。

学習

ミニバッチサイズ50,エポック数は10でやりました。
これでGPU内のメモリ使用量(nvidia-smiコマンドで確認)は1.6GBくらいだったので、バッチサイズは100でも良かったかもしれません。
通常は集めたデータの一部を検証用に残すのですが、今回は全データ学習させて、後から100個ほどランダムに抽出してフィッティングの具合だけ確認しました。
学習が終わったらモデルデータをファイルに保存します。


以下、pythonソースコード

畳み込みニューラルネットワークの定義: cozmo_dnn.py

# -*- Coding: utf-8 -*-
#  Donkey COZMO
#    Convolution network definition
#  Copyright (C) RC30-popo,2019

import chainer
import chainer.links as L
import chainer.functions as F
from chainer import Chain, optimizers, Variable

# chainer.config.train = False

class czCnn(Chain):
    def __init__(self):
        super(czCnn, self).__init__(
            conv1 = L.Convolution2D(in_channels=1, out_channels=16, ksize=4, stride=1, pad=1),
            conv2 = L.Convolution2D(in_channels=16, out_channels=32, ksize=4, stride=1, pad=1),
            l0 = L.Linear(None,1024),
            l1 = L.Linear(None,3),
            bncv1 = L.BatchNormalization(16),
            bncv2 = L.BatchNormalization(32),
            bn0 = L.BatchNormalization(1024),
        )
    def forward(self, x,ratio=0.5):
        h = F.reshape(x,(len(x),1,160,120))
        h = F.max_pooling_2d(F.relu(self.bncv1(self.conv1(h))),2)
        h = F.max_pooling_2d(F.relu(self.bncv2(self.conv2(h))),2)
        h = F.dropout(F.relu(self.bn0(self.l0(h))),ratio=ratio)
        h = self.l1(h)
        return h

学習走行Script: cozmo_dnn_train.py

# -*- Coding: utf-8 -*-
#  Donkey COZMO
#    Convolution network training
#  Copyright (C) RC30-popo,2019


# OS
import os
# OpenCV
import cv2
# Numpy
import numpy as np
# Chainer
import chainer
import chainer.links as L
import chainer.functions as F
from chainer import Chain,optimizers,Variable
from chainer import serializers
from chainer import cuda
# random
import random

xp = cuda.cupy

from cozmo_dnn import czCnn

# Files
DONKEY_COZMO_DATAFILE = 'data/donkey_cozmo.dat'
DONKEY_COZMO_MDLFILE = 'donkey_cozmo_mdl.npz'
DONKEY_COZMO_OPTFILE = 'donkey_cozmo_opt.npz'

img_x = 160
img_y = 120

x_train_data = []
t_train_data = []

# Read data and convert to vector
with open(DONKEY_COZMO_DATAFILE, mode='r', encoding='utf-8') as f:
    for line in f:
        imgfile,label = line[:-1].split(',')
        print('imgfile = '+imgfile+',label = '+label)
        img = cv2.imread(imgfile)
        img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
        img = cv2.resize(img,(img_x,img_y),interpolation=cv2.INTER_AREA)
#        print('img shape = ',img.shape)
        img_gs = img.flatten()

        x_train_data.append(img_gs)
        t_train_data.append(int(label))

x_train = xp.array(x_train_data,dtype=xp.float32)
x_train /= 255
t_train = xp.array(t_train_data,dtype=xp.int32)
total_datacount = len(x_train)
print('Total number of training data = ',total_datacount)

# Initialize Neural Network
model = czCnn()
chainer.config.train = True
optimizer = optimizers.Adam()
optimizer.setup(model)

# GPU setup
gpu_device = 0
cuda.get_device(gpu_device).use()
model.to_gpu(gpu_device)

# number of epochs
n_epoch = 10

# batch size
batch_size = 50

# Training main loop
for epoch in range(n_epoch):
    sum_loss = 0
    perm = np.random.permutation(total_datacount)

    for i in range(0,total_datacount,batch_size):
        if i + batch_size <= total_datacount:
            local_batch_size = batch_size
        else:
            local_batch_size = total_datacount - i
        x = Variable(x_train[perm[i:i+local_batch_size]])
        t = Variable(t_train[perm[i:i+local_batch_size]])

        y = model.forward(x,ratio=0.3)
        model.cleargrads()
        loss = F.softmax_cross_entropy(y, t)
        loss.backward()
        optimizer.update()
#        sum_loss += loss.data*local_batch_size
        sum_loss += float(cuda.to_cpu(loss.data)) * local_batch_size

    print("epoch: {0}, mean loss: {1}".format(epoch,sum_loss/total_datacount))

# Check accuracy
perm = np.random.permutation(total_datacount)
x = Variable(x_train[perm[0:100]])
t = t_train[perm[0:100]]
chainer.config.train = False
y = model.forward(x)
cnt = 0
for i in range(100):
    ti = t[i]
    yi = xp.argmax(y.data[i])
    print('[%d] t = %d, y = %d' % (i,ti,yi))
    if ti == yi:
        cnt += 1
# Display Result
# print(cnt)
print("accuracy: {}".format(cnt/(100)))



model.to_cpu()
serializers.save_npz(DONKEY_COZMO_MDLFILE, model)
# serializers.save_npz(DONKEY_COZMO_OPTFILE, optimizer)

[technology][deep learning][chainer]COZMOを内蔵カメラを利用してDeep Learningで自律走行させる。Part.3-自律走行編-

学習が終わったので、早速自律走行させてみます。
以下がその動画。スクリプトのバグ取りのみで、Deep Learningの学習部分は一発で上手くいきました。
カメラ画像だけで判断して自律走行ができています。

ちなみに原理上、新しいコースを走らせる場合は、新しいコースで再度、データ取りと学習をやり直す必要があります。

youtu.be


自律走行にはデータ取りに使ったスクリプトを改造します。
データ取りでは人間が操作をするために、キー入力を拾って前進、左旋回、右旋回のコマンドをCOZMOに送っていましたが、このキー入力を拾う部分をChainerで作成したCNNに置き換え、カメラ画像をCNNに渡して、出てきた操作ラベルに従ってCOZMOにコマンドを送るだけです。

以下、pythonソースコード

COZMO自律走行スクリプト: donky_cozmo_run.py

#!/usr/bin/env python3
# -*- Coding: utf-8 -*-
#  Donkey COZMO
#    autonomous run by deep learning result 
#  Copyright (C) RC30-popo,2019



import sys
import time
import termios
import os

import cv2
from PIL import Image
import numpy as np
import cozmo
from cozmo.util import degrees, distance_mm, speed_mmps

# Chainer
import chainer
import chainer.links as L
import chainer.functions as F
from chainer import Chain,optimizers,Variable
from chainer import serializers
from chainer import cuda
# random
import random

xp = cuda.cupy

from cozmo_dnn import czCnn

# Initial settings
DONKEY_COZMO_FACE_INIT_ANGLE = 0.0 # -25.0 degree to 44.5 degree
DONKEY_COZMO_TURN_ANGLE = 10.0
DONKEY_COZMO_FORWARD_DIST = 20.0
DONKEY_COZMO_SPEED = 50.0

# COZMO actions
DONKEY_COZMO_ACTION_FORWARD = 0
DONKEY_COZMO_ACTION_LEFT_TURN = 1
DONKEY_COZMO_ACTION_RIGHT_TURN = 2


# Files
DONKEY_COZMO_MDLFILE = 'donkey_cozmo_mdl.npz'

img_x = 160
img_y = 120

# Decide action by Deep Learning prediction from COZMO's camera image
def cozmo_decide_action(cur_img):
    global img_x
    global img_y
    global model

    img = cv2.resize(cur_img,(img_x,img_y),interpolation=cv2.INTER_AREA)
    img_gs = img.flatten()
    x_data = []
    x_data.append(img_gs)
    x = xp.array(x_data,dtype=np.float32)
    x /= 255
    x = Variable(x)
    y = model.forward(x)
#    print('y = ',y)
    res = xp.argmax(y.data[0])
#    print('res = ',res)
    return res


# Let COZMO go forward
def cozmo_go_forward(robot: cozmo.robot.Robot):
    global DONKEY_COZMO_FORWARD_DIST
    global DONKEY_COZMO_SPEED
    robot.drive_straight(distance_mm(DONKEY_COZMO_FORWARD_DIST), speed_mmps(DONKEY_COZMO_SPEED)).wait_for_completed()

# Let COZMO turn left
def cozmo_left_turn(robot: cozmo.robot.Robot):
    global DONKEY_COZMO_TURN_ANGLE
    robot.turn_in_place(degrees(DONKEY_COZMO_TURN_ANGLE)).wait_for_completed()

# Let COZMO turn right
def cozmo_rigth_turn(robot: cozmo.robot.Robot):
    global DONKEY_COZMO_TURN_ANGLE
    robot.turn_in_place(degrees(-1.0 * DONKEY_COZMO_TURN_ANGLE)).wait_for_completed()

# Record COZMO camera image with action(go straight, left turn, right turn)
def cozmo_donkey_run(robot: cozmo.robot.Robot):
    global DONKEY_COZMO_FACE_INIT_ANGLE

    robot.camera.image_stream_enabled = True # Enable COZMO camera capture
    lift_action = robot.set_lift_height(0.0, in_parallel=False)
    lift_action.wait_for_completed()
    head_action = robot.set_head_angle(cozmo.util.Angle(degrees=DONKEY_COZMO_FACE_INIT_ANGLE),in_parallel=False)
    head_action.wait_for_completed()
    firstTime = True
    try:
        while True:
            duration_s = 0.1
            latest_image = robot.world.latest_image
            if latest_image != None:
                gray_image = latest_image.raw_image.convert('L')

                cv_image = np.asarray(gray_image)
#                print('cv_image shape = ',cv_image.shape)
                if firstTime:
                    height, width = cv_image.shape[:2]
                    print('*** Start captureing COZMO camera')
                    print('image height   = ',height)
                    print('image width    = ',width)
                    firstTime = False

                cv2.imshow('frame',cv_image)
                cv2.waitKey(1)

                action = cozmo_decide_action(cv_image)
                if action == DONKEY_COZMO_ACTION_FORWARD:
                    print('*** Go FORWARD ***')
                    cozmo_go_forward(robot)
                elif action == DONKEY_COZMO_ACTION_LEFT_TURN:
                    print('*** Turn LEFT ***')
                    cozmo_left_turn(robot)
                elif action == DONKEY_COZMO_ACTION_RIGHT_TURN:
                    print('*** Turn RIGHT ***')
                    cozmo_rigth_turn(robot)
                
#            time.sleep(duration_s)
    except KeyboardInterrupt:
        print('Keyboard Interrupt!!')
        print('Exit Cozmo SDK')
        cv2.destroyAllWindows()
        pass


# Initialize Neural Network
model = czCnn()
optimizer = optimizers.Adam()
optimizer.setup(model)
chainer.config.train = False
serializers.load_npz(DONKEY_COZMO_MDLFILE, model)
# GPU setup
gpu_device = 0
cuda.get_device(gpu_device).use()
model.to_gpu(gpu_device)

# Run main code
cozmo.run_program(cozmo_donkey_run)


Chainer v2による実践深層学習
新納 浩幸
オーム社
売り上げランキング: 270,162

Donkey COZMOを作る Part.1 (AIロボットCOZMOをChainer+pythonで自律走行させる)

[technology][deep learning][chainer]COZMOを内蔵カメラを利用してDeep Learning(chainer)で自律走行させる。Part.1-データ取り編-

DONKEY CARと同じ原理でCOZMOを自動走行させる実験のPart.1
www.donkeycar.com

DONKEY CARは1/16のラジコンカーにraspberry piを搭載し、カメラ映像でコースを解析しながら走行するロボットカーです。
方式としては、一度人間がリモートでコースを走らせ、その際の操作(加減速、左右操舵角)をDONKEY CARに搭載したカメラの映像と紐付けてDeep Learningで学習させた後に自動走行させます。

COZMOはANKI社の手のひらサイズのAIロボットで、カメラと走行用キャタピラ、顔の表情を表現するためのOLEDディスプレイ等を搭載しています。日本ではタカラトミーから発売されています。
このCOZMO、スマフォをPCと接続することで、pythonからリモート制御できるSDKが公開されています。
このSDKを使って、DONKEY CARと同じ事が出来るんじゃないかと

まずはコースを走行させながら、COZMOのカメラ画像と走行状態(前進、左旋回、右旋回)のひも付けデータをディープラーニングの学習用に取得するスクリプトを書きました。
コースは家にあった通販のダンボールを加工して作成。

走行状態の取得ですが、走行しながら左右旋回等は難易度が高そうなので、

  • 前進2cm
  • (その場で)左旋回 10度
  • (その場で)右旋回 10度

の3種類の操作を定義。

1.カメラ画像を取得
2.上記のうちのどれかの操作を1つ選択
3.カメラ画像と操作ラベル(0,1,2)をファイルに保存

という単純なスクリプトにしました。
データ取りの段階では2.を人間がコマンドを入力して選択。
最終的にはデータを学習させたディープニューラルネットワークに2.を置き換えます。


作成したスクリプトやデータ取得中の写真等は下記。
背景画像のバリエーションを作るためにコースの向きを変えたりしながら10周程度COZMOを走行させ、1000枚程度の画像を集めました。
COZMOのカメラは320x240のいわゆるQVGAでグレースケールの画像が取得できます。

サンプル画像を見てもらうと判る通り、結構左右の視野角が狭いので、画像から操作のヒントが得られる様にコースにセンターラインを足したり、コースの壁面に方角毎に違う絵を描いたりしています。

なおCOZMOのSDKは母艦となるPC上で動作、COZMOアプリが走行するスマフォを介してCOZMOをリモート制御します。
[母艦PC(SDK)]---USB---[スマフォ(COZMOアプリ:SDKモード)]---WiFi---[COZMO]
こんな感じの接続になります。
androidでもiPhoneでも良いのですが、COZMOアプリが動作するスマフォが必要です。COZMOアプリはそれぞれのgoogle play及びapp storeから無料DL出来ます。

データ取り用リモート走行Pythonスクリプト

#!/usr/bin/env python3
#  Donkey COZMO
#    Data recorder for learning course
#  Copyright (C) RC30-popo,2019



import sys
import time
import termios
import os

import cv2
from PIL import Image
import numpy as np
import cozmo
from cozmo.util import degrees, distance_mm, speed_mmps

# Initial settings
DONKEY_COZMO_FACE_INIT_ANGLE = 0.0 # -25.0 degree to 44.5 degree
DONKEY_COZMO_TURN_ANGLE = 10.0
DONKEY_COZMO_FORWARD_DIST = 20.0
DONKEY_COZMO_SPEED = 50.0

# COZMO actions
DONKEY_COZMO_ACTION_FORWARD = 0
DONKEY_COZMO_ACTION_LEFT_TURN = 1
DONKEY_COZMO_ACTION_RIGHT_TURN = 2

# COZMO Key bindings
DONKEY_COZMO_KEY_FORWARD = 'l'
DONKEY_COZMO_KEY_LEFT_TURN = ','
DONKEY_COZMO_KEY_RIGTH_TURN = '.'

# Files
DONKEY_COZMO_DATADIR = 'data/'
DONKEY_COZMO_IMAGEPREFIX = 'img_'
DONKEY_COZMO_DATAFILE = 'donkey_cozmo.dat'
DONKEY_COZMO_SEQNOFILE = 'donkey_cozmo.seq'

# Data Sequense number management
def getSeqNo(filepath):
    if os.path.exists(filepath):
        with open(filepath, mode='r') as f:
            for line in f:
                seqno = int(line)
                break
    else:
        seqno = 0
    return seqno

def saveSeqNo(filepath,seqno):
    with open(filepath, mode='w', encoding='utf-8') as f:
        f.write(str(seqno))


# Get key input from stdin without Enter key
#   This code comes from
#   https://qiita.com/tortuepin/items/9ede6ca603ddc74f91ba
def get_keyinput():
    #標準入力のファイルディスクリプタを取得
    fd = sys.stdin.fileno()

    #fdの端末属性をゲットする
    #oldとnewには同じものが入る。
    #newに変更を加えて、適応する
    #oldは、後で元に戻すため
    old = termios.tcgetattr(fd)
    new = termios.tcgetattr(fd)

    #new[3]はlflags
    #ICANON(カノニカルモードのフラグ)を外す
    new[3] &= ~termios.ICANON
    #ECHO(入力された文字を表示するか否かのフラグ)を外す
    new[3] &= ~termios.ECHO

    try:
        # 書き換えたnewをfdに適応する
        termios.tcsetattr(fd, termios.TCSANOW, new)
        # キーボードから入力を受ける。
        # lfalgsが書き換えられているので、エンターを押さなくても次に進む。echoもしない
        ch = sys.stdin.read(1)
        termios.tcflush(fd,termios.TCIFLUSH)
        return ch

    finally:
        # fdの属性を元に戻す
        # 具体的にはICANONとECHOが元に戻る
        termios.tcsetattr(fd, termios.TCSANOW, old)

# Let COZMO go forward
def cozmo_go_forward(robot: cozmo.robot.Robot):
    global DONKEY_COZMO_FORWARD_DIST
    global DONKEY_COZMO_SPEED
    robot.drive_straight(distance_mm(DONKEY_COZMO_FORWARD_DIST), speed_mmps(DONKEY_COZMO_SPEED)).wait_for_completed()

# Let COZMO turn left
def cozmo_left_turn(robot: cozmo.robot.Robot):
    global DONKEY_COZMO_TURN_ANGLE
    robot.turn_in_place(degrees(DONKEY_COZMO_TURN_ANGLE)).wait_for_completed()

# Let COZMO turn right
def cozmo_rigth_turn(robot: cozmo.robot.Robot):
    global DONKEY_COZMO_TURN_ANGLE
    robot.turn_in_place(degrees(-1.0 * DONKEY_COZMO_TURN_ANGLE)).wait_for_completed()

# Record COZMO camera image with action(go straight, left turn, right turn)
def cozmo_donkey_recorder(robot: cozmo.robot.Robot):
    global DONKEY_COZMO_FACE_INIT_ANGLE
    global DONKEY_COZMO_KEY_FORWARD
    global DONKEY_COZMO_KEY_LEFT_TURN
    global DONKEY_COZMO_KEY_RIGTH_TURN
    global DONKEY_COZMO_ACTION_FORWARD
    global DONKEY_COZMO_ACTION_LEFT_TURN
    global DONKEY_COZMO_ACTION_RIGHT_TURN
    global DONKEY_COZMO_DATADIR
    global DONKEY_COZMO_IMAGEPREFIX
    global DONKEY_COZMO_DATAFILE
    global DONKEY_COZMO_SEQNOFILE

    robot.camera.image_stream_enabled = True # Enable COZMO camera capture
    lift_action = robot.set_lift_height(0.0, in_parallel=False)
    lift_action.wait_for_completed()
    head_action = robot.set_head_angle(cozmo.util.Angle(degrees=DONKEY_COZMO_FACE_INIT_ANGLE),in_parallel=False)
    head_action.wait_for_completed()
    firstTime = True
    try:
        seqno = getSeqNo(DONKEY_COZMO_DATADIR + DONKEY_COZMO_SEQNOFILE)
        datf = open(DONKEY_COZMO_DATADIR + DONKEY_COZMO_DATAFILE,mode='a',encoding='utf-8')
        while True:
            duration_s = 0.1
            latest_image = robot.world.latest_image
            if latest_image != None:
                gray_image = latest_image.raw_image.convert('L')

                cv_image = np.asarray(gray_image)
                if firstTime:
                    height, width = cv_image.shape[:2]
                    print('*** Start captureing COZMO camera')
                    print('image height   = ',height)
                    print('image width    = ',width)
                    firstTime = False

                cv2.imshow('frame',cv_image)
                cv2.waitKey(1)

                ch = get_keyinput()
                current_action = None
                if ch == DONKEY_COZMO_KEY_FORWARD:
                    current_action = DONKEY_COZMO_ACTION_FORWARD
                    print('*** Go FORWARD ***')
                    cozmo_go_forward(robot)
                elif ch == DONKEY_COZMO_KEY_LEFT_TURN:
                    current_action = DONKEY_COZMO_ACTION_LEFT_TURN
                    print('*** Turn LEFT ***')
                    cozmo_left_turn(robot)
                elif ch == DONKEY_COZMO_KEY_RIGTH_TURN:
                    current_action = DONKEY_COZMO_ACTION_RIGHT_TURN
                    print('*** Turn RIGHT ***')
                    cozmo_rigth_turn(robot)
                else:
                    current_action = None
                
                # save image and action data
                if current_action != None:
                    img_filename = DONKEY_COZMO_DATADIR + DONKEY_COZMO_IMAGEPREFIX + '_' + format(seqno,'05d') + '.png'
                    cv2.imwrite(img_filename,cv_image)
                    datf.write(img_filename + ',' + str(current_action) + '\n')
                    seqno = seqno + 1
#            time.sleep(duration_s)
    except KeyboardInterrupt:
        print('Keyboard Interrupt!!')
        print('Exit Cozmo SDK')
        saveSeqNo(DONKEY_COZMO_DATADIR + DONKEY_COZMO_SEQNOFILE,seqno)
        datf.close()
        cv2.destroyAllWindows()
        pass

cozmo.run_program(cozmo_donkey_recorder)


Donkey COZMO用のコース

COZMOを走行させているところ(コースにセンターラインを書き足した)

データ取りスクリプトで取得したCOZMOカメラ画像


カメラ画像のファイル名とタグの例。タグは0が前進,1が左10度旋回,2が右10度旋回

data/img__00037.png,2
data/img__00038.png,2
data/img__00039.png,2
data/img__00040.png,0
data/img__00041.png,2
data/img__00042.png,0
data/img__00043.png,0
data/img__00044.png,0
data/img__00045.png,1
data/img__00046.png,0

追記: 環境はUbuntu16.04LTS+Anaconda3(python3.6)にOpenCV及び次のエントリで紹介するDeep Learningの学習にはChainerが必要です。
Windowsの場合、キー入力の部分を変更必要と思います。