自由課題

学んだり、考えたり、試したりしたこと。

Jetson Nanoで自律走行ラジコンを作る(3)

「Jetson Nanoで自律走行ラジコンを作る」シリーズのまとめ記事は以下。

kimitok.hateblo.jp

モデルの訓練に必要なデータをGoogle Driveにアップロードできたので、ブラウザ上からPyhonが使える実行環境であるGoogle Colabを利用してモデルを作成しようと思う。

colab.research.google.com

今回は前回機体から収集したデータの内容を確認しつつTensorFlow上でデータセットを作成する。
やり方はTensorFlowのチュートリアルを参考にした。

なお、今回紹介するコードはgithubに公開している。

github.com

Google Colabでは以下のようなコードを書くだけでGoogle Driveをマウントできる。便利。

from google.colab import drive
drive.mount('/content/drive')

Google Drive上に格納したtarball(複数可)はKerasのAPIで展開できる。

import pathlib
import os
import glob

for tarball_path in glob.glob("./*.tar.gz"):
  data_root_org = tf.keras.utils.get_file(fname='dataset', origin='file://' + os.path.abspath(tarball_path) , extract=True)
data_root = pathlib.Path(pathlib.Path(data_root_org).parents[0]/'control')
print(data_root)

上記を実行すると以下のように出力される。

Downloading data from file:///content/drive/MyDrive/lunchjet/train_2021_06_03_04_30_41.tar.gz
164814848/164812768 [==============================] - 4s 0us/step
/root/.keras/datasets/control

ここまででtarballが所定の場所に展開されるので、あとはファイルを読めばよい。

データがきちんと展開されているか軽く確認してみる。

annot_paths = sorted(list(pathlib.Path(data_root/'annotations').glob("*")))
print(len(annot_paths))
print(annot_paths[-10:])
6469
[PosixPath('/root/.keras/datasets/control/annotations/2021_05_22_12_03_47_209.log'), PosixPath('/root/.keras/datasets/control/annotations/2021_05_22_12_03_47_273.log'), PosixPath('/root/.keras/datasets/control/annotations/2021_05_22_12_03_47_341.log'), ...]

データ点数は約6500個だった。

次に、これらのログファイルをパースする。
前々回に記載したように、ログファイルには、画像ファイル名・ステアリングモーターの制御値(-1が左旋回、0が直進、1が右旋回)、駆動モーターの制御値(0が停止、1が全速前進)がスペース区切りで記録されている。例えば以下。

2021_05_22_11_52_41_148.jpg 0.499992 0.108504

上記を以下のようなコードでパースする。

image_paths = []
labels = []
for annot_file in annot_paths:
  with open(annot_file) as annot:
    for line in annot:
      image, steer, speed = line.split(' ')
      image_paths.append(str(image_root/image))
      labels.append([float(steer), float(speed)])

モーターの制御値がきちんと読めているかを確認するために、一旦可視化してみる。
いくつか方法があるが、今回は2Dのヒストグラム*1にした。

import numpy as np
import matplotlib.cm as cm
import matplotlib.colors

steers = np.array(labels)[:, 0]
throtles = np.array(labels)[:, 1]

fig = plt.figure()
hist = plt.hist2d(steers, throtles, bins=40, cmap=cm.jet, norm=matplotlib.colors.LogNorm())
fig.colorbar(hist[3])
plt.xlabel('steering')
plt.ylabel('throtle')
plt.show()

結果は以下。

f:id:kimito_k:20210610050228p:plain

各軸の値域に問題はなさそうで、値の分布としても想定と一致する。ちなみにこのデータは同じコースをぐるぐる右旋回して収集したものである。

どうでもよいが、あるドライバーがあるコースを走行した時のヒストグラム(ライン取りや加減速のクセ)はおおよそ決まっているような気がするので、このヒストグラムによりドライバーを特定したり操縦時の改善点を見つけられそうな気がする*2

ここまで確認すれば、あとは tf.datasetAPIを少し使うだけでデータセットの準備ができる。

image_paths_ds = tf.data.Dataset.from_tensor_slices(image_paths)
image_ds = image_paths_ds.map(preprocess_image, num_parallel_calls=tf.data.AUTOTUNE)

label_ds = tf.data.Dataset.from_tensor_slices(labels)

ds = tf.data.Dataset.zip((image_ds, label_ds))
print(ds)
<ZipDataset shapes: ((224, 224, 3), (2,)), types: (tf.float32, tf.float32)>

あとはTFのチュートリアルと同じように shufle()batch() prefetch() などを設定すれば、モデルに流し込む準備ができたはずである。

最後に確認のためにデータセット内のデータを可視化してみる。自宅のリビングの画像なのでモザイクをかけているが、実際には画像のサイズは224x224である。

for batch in ds.take(1):
  images, labels = batch
  fig = plt.figure(figsize=(12.0, 12.0))
  for i in range(9):
    image = images[i]
    label = labels[i]
    label = 'steer={}, throtle={}'.format(tf.strings.as_string(label[0], precision=2).numpy().decode('utf-8'), tf.strings.as_string(label[1], precision=2).numpy().decode('utf-8'))

    subplot = fig.add_subplot(3, 3, i+1)
    subplot.imshow(image)
    subplot.grid(False)
    subplot.set_title(label)

f:id:kimito_k:20210610053023p:plain

数枚サンプリングしてみる限りデータに問題はなさそうだ。

ということで、次回はモデルについて考える。

*1:度数のレンジが広いのでログスケール

*2:モータースポーツではそういうことをやっているのではと想像する