masato-ka's diary

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

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

if-up 2019に参加した話。

この記事について

この記事ではソラコムさん主催のif-up 2019へ参加した感想について記載しています。if-upは2017年に開催されたソラコムさん主催のイベントです。その後同名のイベントはなかったのですが、今回パワーアップした形で帰ってきました。

if-up 2019の概要について

前述の通り、if-upは2017年に同名イベントが開催されましたが、今回は少し毛色が違ったイベントになったと感じました。詳細は以下のイベントページにを確認いただければと思います。

if-up2019.soracom.jp

毛色が違った大きな点としてはこれまでのイベントではIoTにおける課題解決をソラコムサービスでどうやって解決するかという内容が主体となっていたのですが、今回のイベントではその部分は少しトーンダウンしています。日本のものづくり、サービス開発は今どうなっているのか、みんな何を課題に取り組んでいるのか?そんなトーンのイベントとなっていました。 イベント冒頭のキーノートセッションでは「プロダクトの裏にはパッションをもった人とチームがいる!」、「プロダクトづくりの前提が変わった」、「日本からもっと良いプロダクトが出てもいいはず」という問いかけから始まり、大変興味深い内容のイベントとなりました。イベント全体を通して特に印象に残ったポイントについて紹介していきます。

if-upで印象に残ったポイント

世の中にないものを生み出すということ

 午前のキーノートでの主題的なテーマだと感じたことです。新しいプロダクトを作る際に、そのコンセプトが果たして正しいのか、それは便利なのかは誰にも分かり得ないことです。良いプロダクトを作るためにまた、作る側のパッションを保つためにもこういった不安を払拭していく必要があると思います。  そんな中でGroovXさんとソラコムさんのプロダクト開発で共通しているなと思ったことが、MVPを作り、ユーザへの提示を素早く行っていくことが一つの会になるのだなと思いました。Agile的な取り組みでは当たり前の解決策ではなるのですが、論より証拠というのはパワポ100ページよりも説得力がある非常にためになるお話でした。  GroovXさんのLOVOTの開発に関するお話の中でLOVOTはロボットではあるけども「便利ではなく心のケアを中心にプロダクトを作った」けれどもまだ形も無いもので投資家の理解も得られなければ、身内からも不安になる。そこで、MVPを作って仮説検証を繰り返し自分たちの目指す方向を正しく見定めたとのことでした。またソラコムさんのお話でもBeamからFunnelそしてHarvestとプロダクトが進化した経緯をベースに同様のことをおっしゃられていたと思います。

日本のプロダクトが売れないのはなぜか?

日本のプロダクトが売れないのは良いプロダクトが作れないのではなく、良いプロダクトの定義が変わったという話が興味深かったです。つまり日本のプロダクトは昔から同じもので過去の基準からすればいいもののはずだけど、そもそも世の中が考える良いプロダクトの基準が変わったため売れなくなってしまったという話です。じゃあ、そもそも「新しい良いプロダクトとはなんなのか?」について明確な定義は出ていなかったと思いますが、パネラーの方達から語られたのは

  • 良いプロダクトの定義が変わったよりはトレンドに振り回されず芯を通したプロダクト開発が必要である。
  • 自分たちが何をやっているのかしっかりをパッションを持つ。
  • 自分たちが何者なのかをはっきりさせる。

ということでした。プロダクトの機能を提供するのではなく、その機能が与える価値をはっきりとさせる、自分たちがユーザへ与える価値か解決する課題をしっかりと定義すること。これが良いプロダクトを作る秘訣なのかなと思ったり。少し前に流行ったコトづくりと似通ったところはありますが、プロダクトの価値としては本質的なんだろうなと思いました。

まとめ

 良いプロダクトをいかに作り出していくのかというのは非常に難しいテーマだと常日頃思います。今日のif-up2019参加してまだまだ振り返りきれていないところもあるのですが、やはり自分たちがテクノロジーを使い、いったい何に取り組むのかを明確化し定義する、それを素早く形にしていき検証を行っていくのが必要なのかなとも思います。それは大変難しいことでもあるのですが。。。

grpc-spring-boot-starterでSpringBootへgRPCを導入する。

# この記事について この記事ではgrpc-spring-boot-starterを使ってSpringBootでgRPCを利用する方法について記載します。主に自分向けの備忘録的記事となります。

プロジェクトの設定

ビルドツールはMavenを利用します。SpringBootのライブラリ作成後、pomのdependenciesに以下を追加して依存関係にgrpc-spring-boot-starterを追加します。 grpc-spring-boot-starterはgRPCをSpringBootで利用できるようにするAutoConfigurationを実行します。

<dependencies>
        <dependency>
            <groupId>io.github.lognet</groupId>
            <artifactId>grpc-spring-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>
 </dependencies>

ビルドを実行するプラットフォームを特定するためにプラグイン拡張を追加します。プロトコルバッファをビルドするためのprotocコマンドダウンロードに利用します。

<build>
...
<extensions>
      <extension>
           <groupId>kr.motd.maven</groupId>
           <artifactId>os-maven-plugin</artifactId>
           <version>1.5.0.Final</version>
      </extension>
</extensions>
...
</build>

さらにpurotobuf-maven-pluginを追加することで、Mavenのビルドプロセスの中でプロトコルバッファをコンパイルできるようにします。

<build>
...
<plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.6.0:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.18.0:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
...
</plugins>
</build>

上記でgRPC導入の設定は完了です。

gRPCのインタフェース定義

gRPCではprotoファイルと呼ばれるファイルでRPCの定義を記述します。記述されたprotoファイルからprotobuf-manve-plugin(実際にはprotocコマンド)がgRPCのサーバ側の抽象クラスとクライアントコードを自動生成します。

protoファイルの作成

以下のようなprotoファイルで定義します。 先頭の「option java_package」で生成後のコードのパッケージを定義します。プロジェクトのパッケージに合わせて適宜変更します。

syntax = "proto3";

option java_multiple_files = true;
option java_package = "ka.masato.grpc.demo.grpcmavendemo";
option java_outer_classname = "GreetClass";

package greet;

service Greet{
    rpc greeting (GreetRequest) returns (GreetResponse);
}


message GreetRequest{
    string value = 1;
}

message GreetResponse{
    string value = 1;
}

protoファイルの文法については以下を参照ください。

developers.google.com

protoファイルのビルド

protoファイルのビルドは前述のようにMavenのビルドプロセスの中で実行れます。以下のようにビルドを実行します。

$ mvnw clean package install

実行後、target/generated-souces/protobuf配下に生成されたコードが格納されています。IntelliJ IDEAで実行した際にはclassパスにも追加されているはずのなので、そのまま、srcフォルダ配下のパッケージから参照することができます。

gRPCの実装

生成されたコードをベースにサーバとクライアントを実装します。

サーバ側の実装

生成されたコードに含まれるGreetGrpc.GreetImplBaseを継承したクラスを作成します。この抽象クラスにはprotoファイルで定義したサービスがメソッドとして定義されており、これをオーバライドすることでAPIを実装します。また、継承クラスにはgrpc-spring-boot-starterで定義されている@GRpcServiceアノテーションをつけることで、gRPCのサーバとして登録されます。

@GRpcService
public class endpoint extends GreetGrpc.GreetImplBase{

    @Override
    public void greeting(GreetRequest request, StreamObserver<GreetResponse> responseObserver) {

        String value = request.getValue();
        GreetResponse greetResponse = GreetResponse.newBuilder().setValue("Greeting!"+ value).build();
         responseObserver.onNext(greetResponse);
         responseObserver.onCompleted();

    }
}

クライアントの実装

クライアント側ではスタブを作成し、スタブ経由でサービス側のメソッドを呼び出します。正確かはわかりませんがManagedChannelはgrpc-spring-boot-starterでbean定義されていると思います。

public class GreetClient {

    private final GreetGrpc.GreetBlockingStub stub;


    public GreetClient(ManagedChannel managedChannel) {
        this.stub = GreetGrpc.newBlockingStub(managedChannel);
    }

    public String getHello(){
        GreetRequest greetRequest = GreetRequest.newBuilder().setValue("Hello").build();
        GreetResponse greetResponse = this.stub.greeting(greetRequest);
        return greetResponse.getValue();

    }

}

まとめ

grpc-spring-boot-starterを使うとSpringBootで簡単にgRPCを実装することができました。また、proto-buf-mavenプラグインを利用することで、Mavenのビルドプロセスの中でプロトコルバッファのファイルからgRPCのコードを自動生成することができました。gRPCはprotoファイルで定義した内容からサービスの実装インタフェースと対応するクライアントコードを自動生成でき、サービス間通信のインターフェースを実装と定義を紐付ける良い方法だと思います。実際の開発にはprotoファイルの管理やなど実装以外に工夫が必要そうです。