masato-ka's diary

日々思ったこととか、やったことの備忘録。

ディープラーニングとJetson nanoでEnd-to-Endな自動走行を実現した話〜Jetpilotを作ってみた〜

1. この記事について

 Jetson nanoを搭載した移動ロボットJetbotを作成し、前回はJetbotを使った単眼vSLAMを実行させた。

masato-ka.hatenablog.com

 しかしvSLAMはPC側で処理される。Jetson nanoはPCへ映像を送信するだけだ。これでは搭載されているGPUが活躍していない。せっかくなのでJetson nanoに搭載されているGPUの威力を体感したかった。

 そこで今回はディープラーニング を利用して画像から直接判断して走行する、End-to-Endな自動走行にチャレンジしてみた。コースを追従するだけであれば、単純な画像認識によるライントレースと簡単なルールベースの仕組みで十分だろう。しかし、今回は前提となるルールを作るのではなくデータだけ与えて、ディープラーニングで解くのがポイントとなる。以下の画像のようにJetbotがコース上を追従して走行していくイメージだ。

f:id:masato-ka:20190823163621p:plain
End-to-Endの自動走行イメージ

2. End-to-Endのディープラーニングによるラジコン自動走行の方式

 End-to-Endの自動走行について説明する。ここでいうEnd-to-Endの自動走行とは以下のNVIDIAの論文のように車両で撮影した画像から直接その車両を制御する信号をディープラーニングで推論することを目指している。以下、論文の例では走行中の画像を撮影して、道路の状況に即して最適なハンドル操作角、つまりステアリング制御を行なっている。

 実車を公道など複雑な環境で自動走行する場合、SLAMなど自己位置推定の技術に加えて、ディープラーニングを利用して障害物や標識を探し、走行ルールに則り車を制御するのが一般的かもしれない。そのためEnd-to-Endな使い方は知る限り実車では例が少ない。しかし、近年草の根の盛り上がりがあるDIY Robot CarやAI RC Carと呼ばれる分野でいくつもの先例がみられる。

2.1 Jetbotのroad_followingサンプル

 Jetbotのサンプルリポジトリにはroad_followingというサンプルがある。これは自動走行したいコースの各ポイントで画像を撮影する。撮影した画像のそれぞれで、進行方向となる点をマーキングしその画像上でのX,Y座標をラベルとする。この学習データを元に転移学習されたResNetはコース走行時に、画像場で推定された進行方向の座標(X,Y)を返す。この座標を目標にPID制御でJetbotを動かすと言うものだ。上位ランクのJetRacerも基本的には同じ仕組みになっている。

https://github.com/NVIDIA-AI-IOT/jetbot/tree/master/notebooks/road_following

画像上での進行方向を推論し、その進行方向を目標値としてPID制御によって車両を制御している。正確にはEnd-to-Endにはなっていないが、実現方式としては近い。

2.2 DonkeyCar

   ラジコンカーにRaspberryPiまたはJetson nanoをマウントして作成するDonkeyCarでは人間の操作を直接学習さる。手順としては以下の通りだ。

  1. ラジコン操作で人間が実際に教示走行させる
  2. 教示走行で走行画像と合わせてそのタイミングで人間が操作した速度とステアリングの値をラベルとして記録する。
  3. 学習データを元に入力画像に対して最適な速度とステアリングを学習する。

 DonkeyCarではいくつかのDNNのネットワークモデルが提供されている。上記のNVIDIAの論文に記載している素朴なCNNを使い、分類問題として解く方法や、回帰問題として学習させる方法がある。また、3D-CNNやRNN, LSTMを利用し、前後の画像列から時系列での動作を意識したモデルを学習をさせることもできる。DonkeyCarにどんなモデルが実装されているかは以下のリポジトリからKerasでの実装を確認できる。

donkeycar/keras.py at dev · autorope/donkeycar · GitHub

 学習は1万枚程度の画像を使い素のネットワークから学習する。1万枚と聞くとかなりの量に感じるが、コースを10周もすれば集まるようだ。 DonkeyCarでは画像から車両の制御情報を直接推論するため、End-to-Endな方式と呼んで問題ないだろう。

2.3 今回実装した方法

 road_followingのデモでは画像一枚ごとに自分で画像上でマーキングを行いラベルをつける必要がある。また、End-to-EndではなくPID制御がシステムに入っている。DonkeyCarは教示走行により学習データを集めるため、比較的に簡単にデータを収集できる。また、End-to-Endの学習を実現している。しかし、DonkeyCarではネットワークをゼロから学習するため、Jetson nano上で学習するのに時間がかかる。  そこで今回はroad_followingで使用されている転移学習のコードをベースにDonkeyCar同様、画像とコントローラからの指令値をペアにした学習データを与えて学習させる方法をとった。

3. 実装

 ソフトウェアの実装ではROSを使ったコードで試作し実現性を確認した。その後、公開、利用がしやすいようにJupyter notebookとして再実装した。このnotebookは3つのノートブックで構成されている。学習データの収集にはUSB接続のゲームコントローラを必要とする。詳細な利用方法や実装については以下リポジトリを掲載する。プロジェクト名はJetpilot名付けた。

github.com

以下、実装時のポイントについて解説していく。

3.1 学習データの取得 (data_collection.ipynb)

 学習データの収集はコースをJetbotをゲームコントローラで走行させる。コースはテープなど2本のラインで構成された1週可能なものを想定しいてる。走行時に画像と一緒にコントローラの入力値を画像のファイル名として[speed]_[steering]_[uuid].jpgという形で保存していく。画像の保存はカメラの画像取得タイミングと同期しているため、カメラのFPSを10Hzに設定するとサンプリングの間隔は10Hzになる。ただ正確に計測していないので10Hzは厳密ではない。以下映像のようなコースの場合、3〜5周ほど周回ささせ700〜900枚程度になる。

※ 音が出るので再生には注意


Jetbotとディープラーニングで自動走行を学習させる

 Jetbotのサンプルはtraitletsというライブラリとipywidgetを使ってJupyter notebook上でインタラクティブなアプリを実装している。今回もこれらを利用してゲームコントローラの入力処理や画像の取得処理、Jetbotの制御を行なっている。    Jetbotの運動制御はmobile.pyのMobileControllerクラスで実装している。このクラスにSpeedとSteeringの値を入力することでJetbotを制御できる。Jetbotは単純な対向二輪型のロボットのため、制御に使う逆運動学は既知の式を利用している。

車輪ロボット(対向2輪型)の運動を計算してみよう | Tajima Robotics

Speedについては入力値をある範囲で区切り、離散化している。Steeringの値は60cmを最大値とする旋回半径として与えられ、与えられた値が大きくなるにつれ、旋回半径が短くなるようにしている。つまり、コントローラを大きく倒すほどJetbotが急激に曲がることになる。

3.2 学習(train_data.ipynb)

 学習スクリプトはroad_followingのサンプルをベースにResNet18をベースに転移学習を行った。出力はtorch.nn.Linear(512,2)としてSpeed とSteeringの値を出すようにする。MSE-Lossを使用して回帰問題として学習させる。 road_followingの学習スクリプトから大きな変更はない。road_followingでは画像上でのXY座標を回帰問題として求めるのに対して今回はその時のコントローラの出力値を出力するだけだ。違うのは与えるデータだけ。そのため、Datasetクラスの実装で画像名からのラベルデータの取り出し部分を変更している。

 ResNet18のLayer1からLayer3までは学習しないようにフリーズすると少ないエポック数でそれっぽく学習できた。ResNetの畳み込み層はLayer4までなので最後の特徴抽出層とその後の全結合層のみ学習させる。次のコードで層をフリーズさせることができる。

# ResNet18のLayer1からLayer3までを学習しないようフリーズさせる場合は、学習前に追加
for param in model.layer1.parameters():
    param.requires_grad = False
for param in model.layer2.parameters():
    param.requires_grad = False
for param in model.layer3.parameters():
    param.requires_grad = False

 それ以外にもvalidation lossが下がらない場合に過学習しないよう、学習を止めるEarlyStoppingを設定した。10〜30エポック程度で停止する。 Jetson nano上でそのまま学習させることもできるが、Google Coraboratoryなどで学習させると短時間に学習できる。

3.3 走行の様子(live_demo.ipynb)

 学習したモデルを使い実際にJetbotを走行させる。各フレームで入力された画像からスピードとステアリングを直接推論する。推論された値はそのままSpeedとSteeringになるので前述のMobileControllerへ直接与えることで走行することができる。  走行中のフレームレートを稼ぎたいため、推論では半精度の浮動小数点で計算をしている。以下が、走行時の動画となる。

※ 音が出るので再生には注意


Jetbotとディープラーニングで自動走行を学習させる

見事コース一周に成功。しかし、数回走らせたうちの1回成功なので精度的にはまだまだ改良の余地がある。

3.4 Grad-CAMでの評価

 実際に走行できたが、このモデルはなにを見てスピードとステアリングを決めているのか?Grad-CAMと呼ばれる手法を用いて、各画像の推論時に画像のどの部分が結果に一番寄与したのかを可視化した。今回はSteeringの推論にて画像中のどの部分が特徴として一番寄与したかを可視化している。動画中の色が赤に近づくほど結果への寄与が高いことになる。


Grad-CAMによるテストデータの可視化

基本的には白線部分をみているようだが、大きくカーブする場面では画像全体を均一に特徴として捉えている。過学習っぽいのかな。

4. やってみて学んだこと

以下、やってみて自分で感じたことなどを記載しておく。ごくごく当たり前の内容も含まれているが、自分でやってみると以外に見落としがちだった。

4.1 データ取得のための調整

 そもそもディープラーニングで学習する前にデータ取得での地道な作業が重要だった。データ取得での調整ポイントは次の通りだ。

  1. コースのレーンの色がはっきりとわかるようにする。(コースとそれ以外のコントラスト)
  2. 速度が0のデータや誤操作時の画像の除去といった学習データのクレンジング
  3. 学習走行時に周回ごとに操作がなるべくブレないようにする。これもデータを綺麗にする意味で重要
  4. カメラの設置角を調整して画像内でのコースの割合を大きくするといったハードウェア周りの調整。

 一般的にデータの前処理は地味だが重要な作業という。今回の場合も例外にもれず、身をもって体感した。また、ロボットなどハードウェアが絡む場合ではハードウェアの調整も重要なポイントとなる。

4.2 エッジ学習のやり方

 Jetson nanoで試行錯誤しながら学習するには処理が遅い。そこで、同じコードでGoogle Coraboratoryなど他の環境で学習し、エポック数やデータセット、バッチサイズなどのハイパーパラメータを調整しておく。「あたり」をつけたパラメータを使いEdgeで学習させると効率的。そのため、エッジ側とクラウド側で同じ環境、同じコードで学習できることが重要。今回はJupyter notebookが役に立った。

 こうなると全部クラウドでと考えてしまうが、データの収集、学習から推論まで エッジでできるのは手軽さがあり、データの転送など煩雑な操作がない分、手軽さがある。クラウド側とエッジ側の分担や使いわけはまだまだ課題だろう。

 また、エッジのように利用するデータを少なくしなければならない場合は転移学習は強力な手法になる。ゼロから学習させると画像の枚数が多くなり、Jetson nanoでの学習は現実的に厳しい。Jetson nano上で学習させるなら転移学習させるのが現実解なのだろう。

4.4 過学習に気をつけろ

 最初は何も考えず、 70エポックをフルで回していた。Early Stopping入れることで短い時間で割と良い結果が出ることに気がついた。エポックの回数をむやみに増やせば良いというものでもないのだな。

4.5 GPUメモリリーク

 当初Jetson nano上で400枚程度の画像でGPUのメモリが足りなくなり、学習が止まってしまう問題があった。これはGPU上のメモリに保存されているLossの値をトータルLossの計算でそのまま加算していたため、トータルLossの値がGPUのメモリを確保し続け、メモリが足りなくなる事象が発生していた。    GPUGPUに得意なことしかさせない。GPUを使わない処理や必要のないデータはGPUのメモリから外すようにするというのが特にエッジGPUでは重要だろう。

ちなみにこの修正は本家のリポジトリへプルリクエストを送ってみた。マージされるといいな。

Fix GPU memory leaks. by masato-ka · Pull Request #117 · NVIDIA-AI-IOT/jetbot · GitHub

5 まとめ

ディープラーニングを使ったEnd-to-Endな自動走行を実現した。ちゃんと教示走行を学習してそれっぽく動く姿は非常に興味深かった。今後はモデルを変えたり、センサ情報や、画像上に移った物体の情報で動きを変える(標識の画像で一時停止や反転など)ようなものに発展させたい。

お礼

この記事を執筆するにあたり、茅場町 コワーキングスペースCo-Edoの会議室を実験、撮影にて利用させていただきました。

www.coworking.tokyo.jp

JetbotでOpenVSLAMを試してみた。

この記事にてついて

この記事ではJetbotを使ってOpenVSLAMを動かした内容をまとめている。SLAMの実行はJetson nanoではなく、別のホストマシンで実行した。ROSの設定方法や、動かし方を中心に記載している。

ROSで使える単眼SLAM

ORB_SLAM2とOpenVSLAMがROSパッケージのサンプルを同梱している。ORB_SLAM2のROSサンプルはSLAMのローカライゼーション結果(カメラの姿勢)もトピックに投げられるようになっている。OpenVSLAMはローカライゼーション結果はビュアーに表示されるのみだ。またORB_SLAMとOpenVSLAMは実行結果の表示にPangolinと呼ばれるOpenGLを使った3DViewerライブラリを利用する。そのためそれなりのビデオチップを搭載したマシンが必要になる。OpenVSLAMではPangolinを使わずにWEBブラウザのみで描画可能な表示モードが用意されている。今回はこれが決め手となり、OpenVSLAMを利用することにした。ちなみにORB_SLAM2はGPLv3でOpenVSLAMはBSDでそれぞれ公開されている。

環境構築

構成

SLAMの実行はVirtualBox上のUbuntu 18.04で実行した。物理マシンはMacBook Air 2018 メモリ16GBを利用。ROSのマスターはJetbot側にある。Jetbotで取得した画像はimage_procで圧縮し、/jetbot/image_rawへパブリッシュされる。ホスト側のUbuntuではimage_transferで画像をrawに戻してOpenVSLMAの入力に送っている。JetbotのコントロールゲームコントローラをホストUbuntuに接続し、ROSのJoyパッケージで操作を行なっている。

f:id:masato-ka:20190719083653j:plain

Jetson nano上でもOpenVSLAMが動くことは確認したが、GPUを使うわけではないのでJetson nanoで動かす旨味はあまりない。

ROSのインストール

Jetson nano とホスト側のUbuntu環境へROSをインストールする。また、Jetson nano側のROSはPython3でROS Nodeを開発できるように設定を済ませておく必要がある。

masato-ka.hatenablog.com

Jetbot用ROSパッケージの準備

JetbotへJetbot用のROSパッケージを準備する。以下のリポジトリをJetson nano側のROSワークスペースにクローンしcatkin_makeしておく。

github.com

cd ~/your_ros_workspace/src
git clone https://github.com/masato-ka/ros_jetbot.git
cd ../
catkin_make

image_procパッケージをインストールしていない場合はインストールしておく。

sudo apt-get install -y ros-melodic-image-proc

OpenVSLAMのインストール

ホスト側のUbuntu 18.04へOpenVSLAMをインストールする。以下のページの手順に進めればインストールできる。16.04 testedと書いているが、18.04でも問題なくインストールできる。

openvslam.readthedocs.io

ポイントは以下の2つ。

  1. OpenCVは上記手順通りソースからビルドする
  2. SocketViewerでインストールする
  3. SocketViewer用のNode.js環境も構築する。

もちろんPangolinを選択しても問題はないが、それなりのビデオカードが必要になる。

OpenVSLAMのビルドまで完了したらROSパッケージのインストールを行う。こちらも公式の手順通りで問題ない。また、ビルドオプションはSocketViewerを選択する。image_transportパッケージのインストールも忘れずに実施する。

openvslam.readthedocs.io

ORB特徴のボキャブラリーファイルをダウンロードする。ちなみに何に使うのかは理解してないのでこの辺りはどっかで調べる。

# download an ORB vocabulary from Google Drive
cd /path/to/openvslam/build/
FILE_ID="1wUPb328th8bUqhOk-i8xllt5mgRW4n84"
curl -sc /tmp/cookie "https://drive.google.com/uc?export=download&id=${FILE_ID}" > /dev/null
CODE="$(awk '/_warning_/ {print $NF}' /tmp/cookie)"
curl -sLb /tmp/cookie "https://drive.google.com/uc?export=download&confirm=${CODE}&id=${FILE_ID}" -o orb_vocab.zip
unzip orb_vocab.zip

またカメラパラメータのファイルを作っておく。Jetbotに使う広角レンズ(SainSmart IMX219)の場合

cd /path/to/openvslam/build/
vi jetbot_mono.yaml

でjetbot_mono.yamlを新規作成する。記載内容は以下の内容を記載する。

#==============#
# Camera Model #
#==============#

Camera.name: "Jetbot"
Camera.setup: "monocular"
Camera.model: "perspective"

Camera.fx: 1138.042594 
Camera.fy: 1152.150891 
Camera.cx: 314.573090 
Camera.cy: 273.959896 

Camera.k1: -2.663941 
Camera.k2: 5.967979 
Camera.p1: -0.053529 
Camera.p2: 0.031916 
Camera.k3: 0.0

Camera.fps: 5.0
Camera.cols: 640
Camera.rows: 480 

Camera.color_order: "RGB"

#================#
# ORB Parameters #
#================#

Feature.max_num_keypoints: 2000
Feature.scale_factor: 1.2
Feature.num_levels: 8
Feature.ini_fast_threshold: 20
Feature.min_fast_threshold: 7

カメラが違う場合はcamera_calibration パッケージを利用してキャリブレーションをする。

wiki.ros.org

実行

Jetbot側の準備

JetbotのROSモジュールを実行する。

export ROS_MASTER_URI=http://[Jetbot IP]:11311
export ROS_IP=[Jetbot IP]
roslaunch ros_jetbot jetbot.launch

ホスト側の準備

Jetbotコントロール用のjoy_nodeの立ち上げ

export ROS_MASTER_URI=http://[Jetbot IP]:11311#JetbotのIPを指定する
export ROS_IP=[Ubuntu IP]#Ubuntu VMのIP
rosparam set joy_node/dev [ゲームパットのデバイスパス]
rosrun joy joy_node

image_transportモジュールの起動

export ROS_MASTER_URI=http://[Jetbot IP]:11311#JetbotのIPを指定する
export ROS_IP=[Ubuntu IP]#Ubuntu VMのIP
 rosrun image_transport republish compresin:=/jetbot/image_color raw out:=/camera/image_raw

Viewersサーバの起動

cd /path/to/openvslam/viewer
node app.js

OpenVSLAM ROSモジュールを起動

export ROS_MASTER_URI=http://[Jetbot IP]:11311
export ROS_IP=[Jetbot IP]
 rosrun openvslam run_slam -v /path/to/openvslam/build/orb_vocab/orb_vocab.dbow2 -c /path/to/openvslam/build/jetbot_mono.yaml

実行結果の確認

http://[Ubuntu IP]:3001にアクセスするとOpenVSLAMの実行結果を確認することができる。

f:id:masato-ka:20190718233102p:plain
OpenVSLAM-Jetbot

  • 動画

動画だと実験環境のネットワークコンディションのせいでカクカクしているがもう少しスムーズに動く。また、途中でロストしているが、再度ローカライゼーションに成功している。


Visual SLAM with Jetbot

まとめ

Jetson nanoのGPUを使っていないので、あまりうまみはないが、ローカライゼーションさせれば何か面白いことができるかもしれない。CNN SLAMなどDNNを使ったSLAMを使うと状況が違うのかもしれない。

Jetbot+ROS+Python3環境構築

この記事について

この記事ではJetbotのOSイメージにROSをインストールしPython3でROSのスクリプトを記述する方法について説明する。Jetbotのイメージで提供されているPythonのライブラリはPython3で記載されている。しかし、公式ではROSはPython2系対応のみだ。そのためJetbotのライブラリを利用してJetbot向けのROSのパッケージを作ることができない。 そこでROSのパッケージ開発(rospyなどを使った開発)のみPython3で行えるようにした。今回はROSのシステムはPython2.7で動作する。実際は以下のように ROS全てをPython3で動くようソースからビルドするのが望ましいだろう。

ros.youtalk.jp

手順

1. ROSのインストール

ROSのインストールは公式ページに従って進める。今回ROSのバージョンはMelodicをインストールした。またros-melodic-desktopを利用した。Jetson nano及びJetbotの場合、OSはUbuntu 18.04 がインストールされている。

roscore, rosrunが実行できる状態まで持っていく。またpython2系でimport rospyができるところまで確認する。

2. Python3向け準備

ROSコマンドやPYTHONPATHはROSインストール時にインストールされるPython2.7をさしている。ROSのPythonライブラリであるrospyやsensor_msgsなどはPython3系でそのまま動くようになっている。そこで、Python3からPython2.7のdist-packageにインストールされているROSライブラリを参照できるようにする。

2. Python3からrospyを使えるようにする。

基本的にはPyhotn3のパスにPyhton2.7のdist-packageのパスが追加できればいい(と信じている)rospkgというパッケージをインストールするとこれが実現できる。何らかの副作用だと思うが深くは追っていない。

sudo pip3 install rospkg catkin_pkg

パッケージのインストール後、Python3でrospy, sensor_megsがインポートできるようになる。

3. vision_opencvのインストール

この状態でOpenCVとROSのブリッジライブラリcv_bridgeライブラリを利用するとエラーが発生する。cv_bridgeで利用しているネイティブライブラリがPython2系向けにコンパイルされているためだ。そこで、cv_bridge自体をPython3系向けにコンパイルし直す必要がある。手順は以下のページを参考にした。一部パスがx86向けなので、ARM向けに修正した。

How to setup ROS with Python 3 - Omri Ben-Bassat - Medium

  • python3系向けにビルドするための準備
mkdir ~/catkin_build_ws && cd ~/catkin_build_ws
catkin config -DPYTHON_EXECUTABLE=/usr/bin/python3 -DPYTHON_INCLUDE_DIR=/usr/include/python3.6m -DPYTHON_LIBRARY=/usr/lib/aarch64-linux-gnu/libpython3.6m.so
catkin config --install
mkdir src
cd src
git clone -b melodic https://github.com/ros-perception/vision_opencv.git
cd ~/catkin_build_ws
catkin build cv_bridge
source install/setup.bash --extend

これで、Python3でROSパッケージを書く準備が整った。

4. スクリプトの書き方と実行方法

rosのワークスペースで新しいパッケージを作成する。ワークスペースはパスさえ通っていれば先ほどのワークスペースと違うものでも大丈夫。 パッケージ内にscriptsフォルダを作成し、次のファイルを作成する。

  • camera.py
#!/usr/bin/env python3

import yaml
import rospy
from sensor_msgs.msg import Image
from cv_bridge import CvBridge, CvBridgeError
from jetbot import Camera
from sensor_msgs.msg import CameraInfo

NODE_NAME = 'jetbot_camera_node'
IMAGE_TOPIC = 'jetbot/image_raw'

class JetbotCamera:

    def __init__(self, width=640, height=480, calib_file=''):
        self.calib_file = calib_file
        self.camera = Camera(width=width,height=height)
        self.publisher = rospy.Publisher(IMAGE_TOPIC, Image, queue_size=1)
        self.bridge = CvBridge()

    def start(self):
        rospy.init_node(NODE_NAME)
        self.camera.observe(self.image_proc, names='value')
        self.camera.start()
        rospy.spin()

    def image_proc(self, change):
        image_value = change['new']
        cv2_image = self.bridge.cv2_to_imgmsg(image_value, 'bgr8')
        self.publisher.publish(cv2_image)

if __name__ == '__main__':
    jetbot_camera = JetbotCamera()
    jetbot_camera.start()
    pass

ファイル先頭に#!/usr/bin/env python3を記載する。またファイルのパーミッションを755に設定し、実行ファイルにする。その後以下のコマンドで実行する。

roscore
#別ターミナルに切り替え
rosrun ros_package-name camera.py

そうするとjetbot/image_rawというトピックが設定される。rosのimage_viewで画像を表示させてみる。

rosrun image_view image_veiw image:=jetbot/image_raw

まとめ

jetbot用に車輪のコントロールと画像を受信するrosパッケージを作ってみた。広角カメラのカメラパラメータも一緒に入ってる。 時間があるときにちょくちょくメンテする予定。

github.com

Jetbotを3G USBモデム(AK-020)でSORACOM対応する。

この記事について

この記事ではJetson nanoに3G USBモデムAK-020を接続する方法を紹介する。RaspbianやUbuntuへの接続方法が多数紹介されている。Jetson nanoのUbuntu 18.04に接続する場合基本的な手順は同じだが、いくつか細かい違いがあったのでまとめておく。Jetbotへ接続し、3G回線を使いたい場合の参考に。

f:id:masato-ka:20190715205916j:plain

接続手順

1. 3Gモデムのマウント

AK-020はJetson nanoへ接続するとCD-ROMとしてマウントされる。そのため、CD-ROMをアンマウントして、3Gモデムとして動作させる必要がある。Raspbianで実行する場合はいくつか方法がある。Jetson nanoの場合はusb-modeswitchを使う方法で唯一成功した。

usb_modeswitchがインストールされていない場合はインストールする。

sudo apt-get install usb-modeswitch

usb_modeswitchの設定ファイルの末尾にAK-020用の設定を記載する。

# ファイル名 /lib/udev/rules.d/40-usb_modeswitch.rules

#AK-020
ATTR{idVendor}=="15eb", ATTR{idProduct}=="a403", RUN+="usb_modeswitch '%b/%k'"

さらに、GSMモデムとして認識させるための設定ファイルを作成する。

# ファイル名 /etc/usb_modeswitch.d/15eb:a403
DefaultVendor = 0x15eb
DefaultProduct = 0xa403
TargetVendor = 0x15eb
TargetProduct = 0x7d0e
StandardEject = 1
WaitBefore=2

ここまで設定したら一度Jetson nanoを再起動させる。Jetson nanoが起動したらAK-020を刺した時のイベントを確認する。dmesgだとコマンド実行時のみ表示されるので、tail -f /var/log/kern.logで確認するとよい。 CD-ROMがアンマウントされGMSモデムとして認識される。またこの時/dev/ttyUSB0〜/dev/ttyUSB3まで4つのシリアルデバイスが作成されている。このうちどれかがUSBモデムとの通信ポートになる。少し長ったらしくなるが、認識した時のログを掲載する。

[Jul 14 05:39:04 jetbot kernel: [19427.007551] usb 1-2.4: new high-speed USB device number 4 using tegra-xusb
Jul 14 05:39:04 jetbot kernel: [19427.031933] usb 1-2.4: New USB device found, idVendor=15eb, idProduct=a403
Jul 14 05:39:04 jetbot kernel: [19427.031999] usb 1-2.4: New USB device strings: Mfr=2, Product=3, SerialNumber=4
Jul 14 05:39:04 jetbot kernel: [19427.032041] usb 1-2.4: Product: AK-020
Jul 14 05:39:04 jetbot kernel: [19427.032080] usb 1-2.4: Manufacturer: AK-020
Jul 14 05:39:04 jetbot kernel: [19427.032116] usb 1-2.4: SerialNumber: 
Jul 14 05:39:04 jetbot kernel: [19427.044589] usb-storage 1-2.4:1.0: USB Mass Storage device detected
Jul 14 05:39:04 jetbot kernel: [19427.045364] scsi host0: usb-storage 1-2.4:1.0
Jul 14 05:39:05 jetbot kernel: [19428.069400] scsi 0:0:0:0: CD-ROM            HSPA_USB SCSI CD-ROM       622 PQ: 0 ANSI: 0 CCS
Jul 14 05:39:07 jetbot kernel: [19430.368167] usb 1-2.4: USB disconnect, device number 4
Jul 14 05:39:08 jetbot kernel: [19431.363561] usb 1-2.4: new high-speed USB device number 5 using tegra-xusb
Jul 14 05:39:08 jetbot kernel: [19431.394481] usb 1-2.4: New USB device found, idVendor=15eb, idProduct=7d0e
Jul 14 05:39:08 jetbot kernel: [19431.394525] usb 1-2.4: New USB device strings: Mfr=9, Product=10, SerialNumber=0
Jul 14 05:39:08 jetbot kernel: [19431.394552] usb 1-2.4: Product: AK-020
Jul 14 05:39:08 jetbot kernel: [19431.394580] usb 1-2.4: Manufacturer: AK-020
Jul 14 05:39:08 jetbot kernel: [19431.405649] usb-storage 1-2.4:1.6: USB Mass Storage device detected
Jul 14 05:39:08 jetbot kernel: [19431.410039] scsi host0: usb-storage 1-2.4:1.6
Jul 14 05:39:09 jetbot kernel: [19432.420346] scsi 0:0:0:0: Direct-Access     HSPA_USB SCSI CD-ROM       622 PQ: 0 ANSI: 0 CCS
Jul 14 05:39:09 jetbot kernel: [19432.451840] sd 0:0:0:0: [sda] Test WP failed, assume Write Enabled
Jul 14 05:39:09 jetbot kernel: [19432.458823] sd 0:0:0:0: [sda] Asking for cache data failed
Jul 14 05:39:09 jetbot kernel: [19432.464333] sd 0:0:0:0: [sda] Assuming drive cache: write through
Jul 14 05:39:09 jetbot kernel: [19432.474501] sd 0:0:0:0: [sda] Attached SCSI removable disk
Jul 14 05:39:10 jetbot kernel: [19433.406926] usbcore: registered new interface driver option
Jul 14 05:39:10 jetbot kernel: [19433.406994] usbserial: USB Serial support registered for GSM modem (1-port)
Jul 14 05:39:17 jetbot kernel: [19440.421602] option 1-2.4:1.2: GSM modem (1-port) converter detected
Jul 14 05:39:17 jetbot kernel: [19440.430267] usb 1-2.4: GSM modem (1-port) converter now attached to ttyUSB0
Jul 14 05:39:17 jetbot kernel: [19440.430449] option 1-2.4:1.3: GSM modem (1-port) converter detected
Jul 14 05:39:17 jetbot kernel: [19440.431161] usb 1-2.4: GSM modem (1-port) converter now attached to ttyUSB1
Jul 14 05:39:17 jetbot kernel: [19440.431358] option 1-2.4:1.4: GSM modem (1-port) converter detected
Jul 14 05:39:17 jetbot kernel: [19440.431718] usb 1-2.4: GSM modem (1-port) converter now attached to ttyUSB2
Jul 14 05:39:17 jetbot kernel: [19440.431906] option 1-2.4:1.5: GSM modem (1-port) converter detected
Jul 14 05:39:17 jetbot kernel: [19440.435208] usb 1-2.4: GSM modem (1-port) converter now attached to ttyUSB3

[参考]

2. ModemManagerの停止

接続は後述するpppconfigを使用する。しかしUbuntuの場合、別途ModemManagerがモデムを管理し始めるので競合しないようにあらかじめ停止させておく。

sudo systemctl stop ModemManager
sudo systemctl disable ModemManager

3. 通信接続設定

pppconfigを使ってキャリアへの接続を行う。pppconfigのインストールは以下の通り。

sudo apt-get install pppconfig

インストール後、ターミナルからpppconfigを立ち上けてcreate connectionを選択する。以下の表に合わせて設定進める。ユーザ名やパスワードについてはSORACOM Air SIMのデフォルトの場合。それ以外のキャリアの場合は都度修正を行う。

設定
Provider soracom_ak-020
Configure Nameservers (DNS) Dynamic
Authentication Method PAP
User Name sora
Password sora
Speed 460800
Puls or Tone Tone
Phone Number *99#
Choose Modem Config Method No
Manually Select Modem Port /dev/ttyUSB0

ppp接続時に3G回線をデフォルトルートとして設定できるように、pppconfigの設定ファイルを直接編集する。ファイル名はProviderで設定した値になっている。末尾から3行を追加する。

# This optionfile was generated by pppconfig 2.3.18.
#
#
hide-password
noauth
connect "/usr/sbin/chat -v -f /etc/chatscripts/soracom_ak-020"
debug
/dev/ttyUSB0
460800
defaultroute
noipdefault
user "sora"
remotename soracom_ak-020
ipparam soracom_ak-020

usepeerdns #追加
persist #追加
replacedefaultroute #追加

[参考]

続いて、ATコマンドの修正を行う。このATコマンドファイルに従い、pppconfigがシリアルポートを介してモデムを制御する。

 This chatfile was generated by pppconfig 2.3.18.
# Please do not delete any of the comments.  Pppconfig needs them.
#
# ispauth PAP
# abortstring
ABORT BUSY ABORT 'NO CARRIER' ABORT VOICE ABORT 'NO DIALTONE' ABORT 'NO DIAL TONE' ABORT 'NO ANSWER' ABORT DELAYED
# modeminit
'' ATH
# ispnumber
OK AT+CFUN=1
OK ATZ
OK AT+CGDCONT=1,"IP","soracom.io"
OK-AT-OK "ATD*99#"
# ispconnect
CONNECT \d\c
# prelogin

# ispname
# isppassword
# postlogin

# end of pppconfig stuff

3.起動

以下のコマンドで接続を行う。AK-020の場合はLEDが緑色に点滅したら接続成功となる。

pon soracom_ak-020

ifconfigを打つと以下のようにppp0というインタフェースができている。IPが当たっていたら接続成功。

ppp0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1500
        inet 10.xxx.xxx.xxx  netmask 255.255.255.255  destination 10.64.64.64
        ppp  txqueuelen 3  (Point-to-Point Protocol)
        RX packets 62  bytes 649 (649.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 63  bytes 1474 (1.4 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

明示的に切断したい場合は以下のコマンドで接続する。

poff soracom_ak-020

あとはこのコマンドを起動スクリプトに仕掛けておけば良い。起動スクリプトに仕掛ける場合は、usb_modeswitchの実行後になるように順番に留意する。

まとめ

Jetson nanoを3G回線にダイレクトに接続できる。WiFi モバイルルータに経由で接続する方法もあるが、それだと3G回線のみになる。とりあえずソラコムまで繋がったら、Jetbotの走行データをHarvestにあげるもよし、Napterで遠隔操作するも良し。この続きはまたいずれ。

3DプリンタなしでオリジナルJetbotを作った記録

この記事について

JetbotはNVIDIAのJetson nanoを搭載したDIY Robotcarだ。その作成方法をNVIDIAオープンソースで公開し誰もが作成できる。しかし、車体といった筐体パーツは3Dプリントが必要になる。STLファイルが公開されているとはいえ、3Dプリンタサービスで筐体を出力するのにはそれなりの金額がかかる。そこで、入手性の良い部品を使ってオリジナルのJetbotを作成したので紹介する。

近々キットも発売されるようなのでそれでも面倒な方はそちらから。

robotstart.info

完成したオリジナルJetbot

  完成したJetbotはアルミ製のロボット台車をベースにしている。この台車は秋月電子で購入できる市販品だ。この上にアルミ板を一枚載せてバッテリーとJetson nanoをマウントしている。モータドライバーもWaveshareのMotor Driver HATを利用している。こちらは千石通商で購入できる。

f:id:masato-ka:20190623125330j:plain
完成したJetbot

これらの部品選定については以下野良ハックさんのサイトを参考にさせていただいた。

note.mu

部品リスト

使用した部品は次の表のとおり。Jetson nanoを除けば16000円程度で部品か揃う。Jetson nanoも12000円程度なのでトータルで3万円に達しないくらいで構築できる。

  部品名 数量 参考価格 (円) 購入店舗 備考
FT-DC-002(台車) 1 1,480 秋月電子 -
Motor Driver HAT 1 1,560 千石通商 -
1ミリ厚アルミ板100mmx200mm 1 400 ホームセンタなど -
オスオスジャンパー線 1 200 秋月電子など モータとモータドライバの結線用
2mmネジ 14 200 西川電子部品 -
2mmナット 14 200 西川電子部品 -
2mmx15mmスペーサ 4 100 西川電子部品 -
2mmx5mmスペーサ 12 250 西川電子部品 -
INIU モバイルバッテリー 1 1999 Amazon -
広角カメラ 1 2499 Amazon -
Raspberry Pi Camera Model V2 1 3,900 Amazon -
micro SD Card U3 A2 1 3480 Amazon -

 

作成のノウハウ

基本的な作り方や構成はNVIDIA公開のWikiと同じになる。特別なノウハウは特にないのだがNVIDIA公開手順と違うポイントを中心に、紹介する。  

筐体の作成

筐体の構成は3層構造になている。まずはモータ含む台車部分だ。これは台車をそのまま組み立てる。その上にモバイルバッテリーを配置する。さらにスペーサを支柱がわりにアルミ板を載せる。アルミ板の上にJetson nanoをマウントする。

f:id:masato-ka:20190626095056j:plain

台車への穴あけ

台車に空いているネジ穴はモバイルバッテリーで塞がれてしまう。そこで、モバイルバッテリーに干渉しないよう、新しくネジ穴を開ける必要がある。台車の天板四隅に2mmネジの穴を開ける。穴にはスペーサを取り付ける。今回は15mmのスペーサと5mmのスペーサを組み合わせて20mmの長さにした。

f:id:masato-ka:20190623123223j:plain

アルミ板の加工方法は後述する。

アルミ天板の作成

次にアルミの天板を作成する。まずはアルミ板の切り出しサイズと台車と連結するネジ穴を開ける。寸法は実物を使って適当に印をつける。この際に、台車のネジ穴位置を間違えないようにする。

f:id:masato-ka:20190621222638j:plain
アルミ板の切り出しサイズの決定と穴あけ位置の決定

切り出しと穴あけが終わったアルミの天板にJetson nanoをそっとのせる。Jetson nanoがショートしないように電源を全て取り外し、十分に放電しておくと良いだろう。静電気にも注意したい。Jetson nanoのネジ穴の位置を天板城へ直接採寸する。Jetson nanoのネジ穴から細めの油性マジックで天板へ印をつけていく。Jetson nanoの設置むきに注意する。I/O用ピンヘッダがでている側が車体の後方である。また、前方にはカメラを固定する必要があるため、若干天板の後ろ気味に設置し、前方にスペースを出す方が良いだろう。

f:id:masato-ka:20190621224040j:plain
Jetson nanoの採寸、アルミ板に載せるのでショートに注意

 アルミの天板は本体と固定するスペーサ用の穴とJetson nanoを取り付ける穴を開ける。こちらも2mmのネジ穴となる。カメラを固定するためのネジ穴も開けておく。

カメラ天板の作成

 カメラマウントもアルミで作る。Raspberry Pi Camera V2とスペーサを介してアルミ板に固定する。カメラが少し地面を向くように「く」の字に曲げておき、カメラが付いていない辺を本体との固定に使う。

f:id:masato-ka:20190628135625j:plain
カメラマウント

作成したカメラマウントはアルミ天板に設置する。カメラ中心がなるべく車体中央になるように穴あけ位置を決める。先にカメラマウント側に穴を開け、位置合わせをするといいだろう。

f:id:masato-ka:20190623123921j:plain
カメラマウントを天板に設置した様子

加工について

 アルミのカットや穴あけは一見難しそうだが、カッターナイフやピンバイスといった工具で作業ができる。今回は写真のような工具を利用している。

f:id:masato-ka:20190623135318j:plain
利用した工具

左に写っているドリルはタミヤから発売されている組み立て式のドリルでヨドバシカメラでも購入できる。付属に2mmのピンバイスが付属しているのでネジ穴はこれで開けられる。

https://www.amazon.co.jp/%E3%82%BF%E3%83%9F%E3%83%A4-%E3%82%AF%E3%83%A9%E3%83%95%E3%83%88%E3%83%84%E3%83%BC%E3%83%AB%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E9%9B%BB%E5%8B%95%E3%83%8F%E3%83%B3%E3%83%87%E3%82%A3%E3%83%89%E3%83%AA%E3%83%AB-%E3%83%97%E3%83%A9%E3%83%A2%E3%83%87%E3%83%AB%E7%94%A8%E5%B7%A5%E5%85%B7-74041/dp/B01LX208SY/ref=sr_1_3?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&keywords=%E3%82%BF%E3%83%9F%E3%83%A4+%E3%83%89%E3%83%AA%E3%83%AB&qid=1561690301&s=gateway&sr=8-3www.amazon.co.jp

穴を開ける場合はけがき代わりにピンバイスで少し浅めの穴を削ってからドリルで穴あけする。こうすることでずれずに穴を開けることができるドリルを使う際はゴーグルなどをして、アルミの削りカスや折れたドリル歯の破片から目を保護することを強くお勧めする。

アルミのカットについては以下の動画が参考になる。こちらの動画では万力などを利用している。今回のサイズ感のものであれば手で抑えれば十分だ。

www.youtube.com

いずれにしても加工の際の怪我には十分注意する。自身がない場合は、ホームセンターや有料工作スペースで相談してみるのも良いだろう。 カインズホームでは有料の工作スペースがある店舗がある。3Dプリンタも置いていたりする。

www.cainz.co.jp

モータドライバ

モータドライバ接続の注意点

モータドライバは前述の通り、WaveshareのMotor Driver HATを利用している。このボードに外部電源を接続してはいけない。Jetson nanoの接続については何も考えずにピンヘッダに差し込むだけでOKだ。注意点は、モータ用の外部電源を接続してはいけないという点だ。このボードはモータ用に外部から6V以上の電源を供給できるようになっている。しかし、このボードに6Vを供給するとモータドライバ側からJetson nano側の5V端子に電力を供給してしまう。電気的に危険に思うので外部電源は接続しないのが無難だろう。Motor Driver HATのスイッチもオフにしておこう。

ソフトウェアの修正

オリジナルのJetbotで使われているモータドライバとWaveshareのモータドライバは基本構成は同じだ。それぞれPCA9685というI2CインタフェースのPWMコントローラからTB6612というDCモータドライバを制御している。しかし、PCA9685とTB6612の接続ピンアサインに違いがあるため、Jetbotのサンプルを使うためにはAdafruitのドライバを修正する必要がある。以下のコマンドで修正する。

sudo vi /usr/local/lib/python3.6/dist-packages/Adafruit_MotorHAT-1.4.0-py3.6.egg/Adafruit_MotorHAT/Adafruit_MotorHAT_Motors.py

class Adafruit_DCMotor:
    def __init__(self, controller, num):
        self.MC = controller
        self.motornum = num
        pwm = in1 = in2 = 0

        if (num == 0):
                 pwm = 5 # 8 -> 5
                 in2 = 4 # 9 -> 4
                 in1 = 3 # 10 -> 3
        elif (num == 1):
                 pwm = 0 # 13 -> 0
                 in2 = 1 # 12 -> 1
                 in1 = 2 # 11 -> 2
        elif (num == 2):
                 pwm = 2
                 in2 = 3
                 in1 = 4
        elif (num == 3):
                 pwm = 7
                 in2 = 6
                 in1 = 5

また、JetbotのリポジトリをコピーしてJetbotnライブラリをインストールする。自分の環境ではモータドライバのI2Cアドレスを0x60として呼び出しを行っていた。デフォルトは0x40なので修正した。

github.com

jetbot/robot.pyの22行目を以下のように修正する。 (追記 2020/04/26 Lunranさんからのご指摘で、引数名が違っていたようです。address->addrに変更しました。)

       self.motor_driver = Adafruit_MotorHAT(addr=0x40, i2c_bus=self.i2c_bus)

まとめ

Jetbotは手軽に移動ロボットが作れるプラットフォームとして興味があった。しかし3Dプリンタのボディー作成にお金がかかりそうで躊躇していた。今回のように既製品とちょっとの工作の組み合わせでオリジナルJetbotを構築できれば同じようなポイントで躊躇してる人もはじめやすくなるのではないだろうか。Jetbotを使った面白い事例がもっと増えてほしい。

SpresenseとNeural Network Consoleのハンズオンに参加して気づいたこと。

この記事について

この記事では以下のハンズオンに参加して、SonyのNeural Network ConsoleとSpresenseを使ってみた感想を記載している。Neural Network Consoleを使ってSpresenseで動かすCNNを組む際に気をつける点について記載してみた。

algyan.connpass.com

ハンズオンでは以下の資料に沿って進めた。Neural Network Console、およびSpresenseの基本的な使用法についてはひとまず使えるようになる。

  • Neural Compute Consoleハンズオン

www.slideshare.net

  • Spresense ハンズオン

https://github.com/takayoshi-k/algyan_spresense_ai_hands_on/releases/download/tmp_version/20190615_ALGYAN_SPRESENSSE_AI_HAnds_ON_at_.pdf

当日貸し出しされた機材はSpresenseにTFT液晶とカメラがついていた。ALGYANさんのイベントはこういった機材を使いやすい形で用意してくれる。気になっていた機材の検証がショートカットができて嬉しい。感謝!

f:id:masato-ka:20190616145226j:plain
当日貸し出し機材

当日ハンズオンの成果について

当日のハンズオンではclassification-hand-signと言う学習済みのサンプルモデル利用した。Neural Compute Consoleからダウンロードし、Spresens上で動作させると言う内容だ。手順通りに進めると、初見でも準備を含めて1時間程度で完了できる。モデル自体はよくある2層のCNNと2層の全結合層で構成されている。28x28 pixelの画像を学習データとして利用する。 Spresenseで動作させるとカメラで撮影された画像がTFT液晶に表示される。さらに、認識したハンドサインに対応したアイコンが、画面の左上に表示される。

フレームレートは計測していないが、少しもっさりといった感じだった。機敏に反応してるとは言い難いが、これなら何かに使えそうと言う速度感だった。 グーチョキパーの判定は精度が少し厳しめだった。自分が試した時はチョキがパーとして判定されていた。学習データが単純な白地にグーチョキパーが写っている画像しかなく、もっと汎化できるようなデータを使うと改善さるかもしれない。

Neural Network ConsoleでSpresense向けモデルを作る

用意されたモデルを使うだけではハンズオン時間を持て余してしまった。そこでハンズオン前半で用意した自作のモデルをSpresenseへ入れ動かしてみることにした。学習データはMNISTのデータから4と9のみを抽出し、2クラス分類を行なっている。  モデルの入力と出力に変更はないので、Spresenseのコードは変更せずにモデルだけ差し替えを行なった。しかし、こちらのモデルを動かすことができなかった。

Spresenseで動かすにはモデルのサイズが重要

 チュータの方に質問してみたところ、モデルのサイズが大きく、メモリに乗り切らなかったのではないかとのこと。モデル自体はSDカードに保存し、実行時にプログラムメモリ上にロードする。プログラム上はモデルロードに成功しているが、実際にはメモリ上に乗り切れていない可能性があると言う。この部分、検証をしっかりしてないが、モデルサイズが大きいと言うのは本当らしい。  モデルのメモリサイズはパラメータの数に比例する。独自のモデルもhand-signのモデルと同様、4層のCNNのモデルだった。しかし、畳み込み層のフィルタ数や、全結合層のニューロンの数に違いがある。hand-signのモデルはこの部分を調整し、ネットワーク全体のパラメータ数を調整していた。

モデルサイズを小さくするには

Neural Network Consoleでは作成したネットワーク全体のパラメータを画面右下Staticsに表示されるCostParameterで確認できる。また、CostParameterをクリックすると、ネットワークの各層の横にそれぞれの層ごとのCostParameterが表示される。CostParameterが小さくなるようにボトルネックとなる層のパラメータの調整とその結果を確認しながらインタラクティブに操作できる。この点はFWを使って実装するよりも優れているかもしれない。

f:id:masato-ka:20190616145231j:plain
Neural Network Consoleの画面

おそらく、このパラメータの数はDNNの表現力に直結するため、ときたい問題とその目標精度の設定も重要だろう。

感想

Neural Network ConsoleとSpresenseの組み合わは組み込みAIを少ないコストで実現できると感じた。もちろん、Spresense向けにネットワーク構成を工夫する必要がある。しかし、Neural Network Consoleが試行錯誤しやすい仕組みを提供している。組み込みAIを動かすためのプラットフォームは多く提供しているが、敷居が低いと言う点で、Neural Network CosnoleとSpresenseの組み合わせはベストな組み合わせだろう。  今回のつまづきポイントだった、モデルのサイズについては、明らかにモデルサイズが大きい場合はどこかのタイミングで警告が出るようにできるとわかりやすいかもしれない。

Javaライブラリ、KuromojiとKumoで日本語 Word cloudを作成する。

この記事について

この記事ではJavaでWordCloudを作るためのライブラリKumoの使い方を紹介しています。Word cloudは文章中の単語を並べ、各単語の出現頻度にしたがって文字の大きさを変化させる画像です。これにより、直感的に文章の内容や特徴を捉えることができます。

Kumoの概要と日本語形態素解析の検討

JavaのWord cloudライブラリを探したところKumoというライブラリを発見しました。このライブラリはJavaのみで完結しています。

github.com

また、ラテン系文字だけでなく、中国語の形態素解析もできるようです。しかし、日本語については形態素解析器が含まれていません。そこで、日本語解析はJava形態素解析器としてKuromojiを利用します。

www.atilika.com

使い方

それでは早速使い方を見ていきましょう。

Javaプロジェクトの依存関係へ追加

前述の通り、日本語の形態素解析にはKuromojiを追加します。まずはMavenまたはGradleの依存関係へKumoとKuromojiを追加しましょう。Kumoはkumo-coreを依存関係に設定すると形態素解析部分を除いたWord cloud作成ライブラリのみをインストールすることができます。Kuromojiについて今回はipadic版を利用します。たの辞書を使う場合もこの後の利用方法に変更はありません。

<dependency>
    <groupId>com.kennycason</groupId>
    <artifactId>kumo-core</artifactId>
    <version>1.17</version>
</dependency>
<dependency>
    <groupId>com.atilika.kuromoji</groupId>
    <artifactId>kuromoji-ipadic</artifactId>
    <version>0.9.0</version>
</dependency>

Kuromojiの形態素解析の実行

Kuromojiの形態素解析後Map<String,Long>という型でデータをまとめます。Keyは抽出した単語、Valueは文章中での出現回数です。 inputTextは文章などが入っている想定です。また、以下の例では固有名詞のみを抜き出しています。

public Map<String,Long> tokenize(String inputText){
    List<Token> tokens = tokenizer.tokenize(inputText);
    Map<String,Long> dataSet = tokens.stream()
          .filter(t -> t.getPartOfSpeechLevel2().equals("固有名詞"))
          .map(t -> t.getSurface())
          .collect(Collectors.groupingBy(e->e,Collectors.counting()));
}

実際にWord cloudを作成するためにはkumo-coreライブラリで提供されているWordFrequencyオブジェクトのListを後述するWordCloudオブジェクトへ渡す必要があります。そのために以下のように変換を行います。

List<WordFrequency> wordFrequencies = dataSet.entrySet()
                .stream()
                .map(data -> new WordFrequency(data.getKey(), data.getValue().intValue()))
                .collect(Collectors.toList());

KumoでWord cloudの作成

実際にWord cloud を作成するにはkumo-coreで提供されるWordCloudクラスを使います。

final Dimension dimension = new Dimension(300,300);//  (1)
final WordCloud wordCloud = new WordCloud(dimension, CollisionMode.PIXEL_PERFECT);// (2)
wordCloud.setPadding(2);
wordCloud.setBackground(new CircleBackground(200)); // (3)
wordCloud.setBackgroundColor(Color.white); // (4)
wordCloud.setColorPalette(new LinearGradientColorPalette(Color.RED, Color.BLUE, Color.GREEN,
         30, 30)); // (5)
wordCloud.setFontScalar(new LinearFontScalar(10, 40)); // (6)
wordCloud.build(this.wordFrequencies); // (7)
BufferedImage resultImage = wordCloud.getBufferedImage(); // (8)
  • (1) Dimensionオブジェクトで作成する画像のサイズを決めるここでは300 X 300
  • (2) コンストラクタへ(1)のDimensionオブジェクトを渡してコンストラクタを作成する。
  • (3) 半径200の円状にWord cloudを作成する。四角や指定したテンプレート画像に沿った形状にすることもできる。
  • (4) 作成画像の背景を白に変更
  • (5) 文字のグラデーションを設定
  • (6) フォントサイズの設定
  • (7) 作成元のデータが入っているListオブジェクトを渡しビルドする。
  • (8) 作成された画像をBufferedImageとして 取得する。

最終的に出力されたBufferedImageオブジェクトをファイルに保存します。画像の拡張子はデフォルトでpngとなっていますが、設定で変更できるようです。

作成してみたWord cloudの画像

試しに、このブログに対して処理をかけてみたのが以下の結果です。見事に横文字単語だけで構成されています。あんまり日本語の形態素解析をしたありがたみがないです。

f:id:masato-ka:20190312230457p:plain
WordCloudをこのブログにかけてみた

ニュース記事なんかで試してみるといいかもしれませんね。

まとめ

自分のブログにWord Cloudを実行すると今までにどんなブログを書いていたのかわかりやすくなります。今後のブログ内容や自分の興味関心などをアバウトに把握できていい感じです。