Raspberry Pi3でMovidius(NCSDK2)の動作環境をDockerで構築してみる

# はじめに
ラズパイを扱う上で悩ましい点の1つが環境の切り替えかなと思います。使用するライブラリの種類やバージョン違い等、様々な環境を使い分けるため、SDカードの入れ替え大会が行われ、最後にはどのSDカードがどの環境か分からなくなることも多々あります。

そんなわけでDockerを使って環境を切り分けられるようにしてみようと考えました。

今回は、Dockerコンテナ内で物体検出などディープラーニング系の処理を実行する基礎環境を構築してみます。
ここで作成するDockerイメージは必要最低限なものだけを導入し、ここから作成したコンテナ内で環境を育て、それを基にさらにDockerイメージを作る、というスパイラル的な使い方を想定しています。

今回のDockerコンテナでは以下を実行できるようにしてみます。

* GUIアプリケーションの実行
* Intelの[Movidius Neural Compute Stick](https://www.movidius.com/)の使用
* USBカメラ
* OpenCV
* [Neural Compute Application Zoo (NC App Zoo)](https://github.com/movidius/ncappzoo)の実行

# 環境
以下の環境を使用しました。

| デバイス等 | バージョン | 備考 |
|---|---|---|
| Raspberry Pi3 B+ | | Pi3BでもOKです |
| RASPBIAN STRETCH WITH DESKTOP | April 2018| コンテナもこのバージョンで作成 |
| [Movidius Neural Compute Stick](https://www.movidius.com/) |  | |
| [Logicool HD Webcam C270](https://www.logicool.co.jp/ja-jp/product/hd-webcam-c270) ||
| Docker | 18.05.0-ce |
| Python | 3.5.3 | Dockerコンテナ内 |
| OpenCV | 3.4.1 | Dockerコンテナ内 |
| [Movidius Neural Compute SDK](https://github.com/movidius/ncsdk/releases/tag/v2.04.00.06https://github.com/movidius/ncsdk/releases/tag/v2.04.00.06) | V2.04.00 2018-05-04 | Dockerコンテナ内 |
|[Neural Compute Application Zoo (NC App Zoo)](https://github.com/movidius/ncappzoo)|| Dockerコンテナ内 |

# 作業するディレクトリ構成
今回は以下のようなディレクトリの構成で作業します（ホスト側）。

> 任意のディレクトリ/
> 　├ Dockerfile
> 　├ ncsdk /
> 　├ コンテナにコピーする任意のファイル

任意のディレクトリを作成し、その配下で作業します。

* Dockerfileは今回作成するDockerイメージの定義ファイルです。
* ncsdkディレクトリはMoviusのSDKのディレクトリです。今回ダウンロードします。
* コンテナ内で使いたいファイルがあれば、このディレクトリに格納しておきます。

# ラズパイのスワップサイズの変更
いよいよ環境構築を始めます。

通常通りSDカードにRaspbian Stretchをインストールした後、まずはスワップサイズを増やします。増やさないと各種ビルドに時間がかかる、メモリ不足で落ちる、など問題が発生します。

スワップ領域は"/etc/dphys-swapfile"ファイルの"CONF_SWAPSIZE"に記述します。
ここでは2048MB(2GB)を確保しておきます。

```:/etc/dphys-swapfile
# スワップサイズ(MB)
CONF_SWAPSIZE=2048
```

変更したらサービスを再起動して設定を反映します。

```
$ sudo systemctl stop dphys-swapfile
$ sudo systemctl start dphys-swapfile
```

free -hコマンドで確認します。

```
$ free -h
              total        used        free      shared  buff/cache   available
Mem:           875M        218M        222M         18M        434M        581M
Swap:          2.0G          0B        2.0G
```

# Dockerのインストール
続いてDockerをインストールします。通常ユーザでも実行できるようにdockerグループを割り当てます。

なお、通常ユーザにdockerグループを割り当てるとパスワードなしで実行できてしまうため、セキュリティ上のリスクが伴うことにご留意下さい。

```
$ curl -sSL https://get.docker.com | sh
$ sudo usermod -aG docker pi
$ sudo reboot
```

# MovidiusのSDKのダウンロードと設定変更
MovidiusのSDKである[NCSDK2](https://github.com/movidius/ncsdk)をホスト側でダウンロードします。
ダウンロード後、設定ファイルを編集します。

まずはダウンロードです。

```
$ git clone -b ncsdk2 https://github.com/movidius/ncsdk.git
```

ダウンロードしたncsdkディレクトリ直下の"requirements_apt_raspbian.txt"ファイルに、"python3-setuptools python-setuptools"を追加します。

追加した結果、以下のようになります（実際は改行無し）。

```:requirements_apt_raspbian.txt
python3-markdown python3-h5py python3-lxml python3-matplotlib python3-protobuf
python3-dateutil python3-scipy python3-six python3-networkx make wget python3-dev
python3-pip python-pip python3-setuptools python-setuptools
```

# Dockerfileの作成
今回使用するDockerfileは以下の内容となります。

```:Dockerfile
FROM raspbian/stretch

################################################################
# 各種ライブラリをインストールする。
# 主にDockerコンテナ内で使用するツール、Python3、OpenCV、
# GUIアプリケーションを使うために必要なライブラリである。
################################################################
RUN apt-get update && apt-get install -y \
libraspberrypi-bin pkg-config lsb-release build-essential usbutils udev dirmngr \
sed sudo tar wget vim cmake git unzip \
x11-apps guvcview gpicview \
python3-pip python3-dev \
python-setuptools python3-decorator \
libjasper-dev \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
libqtgui4 libqt4-test \
&& apt-get clean

################################################################
# ncsdkのインストール途中でcythonが必要となるためインストールしておく。
################################################################
RUN pip3 install cython

################################################################
# 環境変数の設定、主にコンテナ内でGUIアプリを使用するための設定。
################################################################
ENV LD_LIBRARY_PATH=/vc/lib:/usr/lib:$LD_LIBRARY_PATH
ENV DISPLAY=:0.0
ENV QT_X11_NO_MITSHM=1

################################################################
# コンテナ内にncsdkを格納する。作業ディレクトリは/_workとする。
# コンテナ内で使用したい任意のディレクトリ、ファイルを格納する。
################################################################
RUN mkdir /_work
ADD ncsdk /_work/ncsdk
ADD [任意のファイル、ディレクトリ] [コンテナ内の格納先]

################################################################
# Movidiusのncsdkをインストールする。
################################################################
WORKDIR "/_work/ncsdk"
RUN make install

################################################################
# OpenCVをPythonで使用するためのライブラリをインストールする。
################################################################
RUN pip3 install opencv-python

################################################################
# コンテナ内の作業ディレクトリを/_workとする。
################################################################
WORKDIR "/_work"

################################################################
# コンテナで実行する処理を記載する。
# 今回は開発環境を作りたいため、/bin/bashとしておく。
################################################################
CMD ["/bin/bash"]
```

# Dockerイメージのビルド
いよいよDockerイメージをビルドします。この作業は時間がかかります。
Raspberry Pi3 B+で2時間30分ほどかかりました。

作成したDockerfileと同じディレクトリで以下を実行します。

```
$ docker build -t [任意のイメージ名] -f ./Dockerfile .
```

# Dockerコンテナの作成
作成したイメージからコンテナを作成します。

ホストとコンテナ間でデバイスやX11のソケットを共有しています。

```
$ docker run -ti \
--name [任意のコンテナ名] \
--privileged \
--net=host \
-v /dev:/dev \
-v /tmp/.X11-unix/:/tmp/.X11-unix \
-e DISPLAY=$DISPLAY \
-v $HOME/.Xauthority:/root/.Xauthority:rw
[作成したイメージ名]
```

# コンテナの実行や接続
Dockerの基本的な使い方ですが、、、

停止中のコンテナを実行するのは以下の通りです。
--interactiveを指定すると、コンテナに接続してコマンドを実行できる状態となります。

```
$ docker start --interactive [コンテナ名] 
```

実行中のコンテナに接続するには以下の通りです。

```
$ docker exec -ti --privileged [コンテナ名] /bin/bash
```

# 動作確認
作成したコンテナ内でUSBカメラを使って動画を映してみます。
ここではPythonでOpenCVを使ってUSBカメラの映像を表示させてみます。

```python:camera.py
import cv2

capture = cv2.VideoCapture(0)

capture.set(3,320) # width
capture.set(4,240) # height
capture.set(5,30)  # FPS

while(capture.isOpened() is False):
  capture = cv2.VideoCapture(0)

while True:
  ret, frame = capture.read()
  cv2.imshow("camera",frame)
  if cv2.waitKey(10) > 0:
    break

capture.release()
cv2.destroyAllWindows()
```

以下のように実行します。

```
# python3 camera.py
```

ホスト側のデスクトップにUSBカメラの映像のウィンドウが表示されれば成功です。
何かキーを押すと終了します。


# Movidiusを使ってみる
最後にMovidiusをDocker上で使用してみます。

ここでは[Neural Compute Application Zoo (NC App Zoo)](https://github.com/movidius/ncappzoo)のアプリケーションを実行してみます。

NC App ZooのリポジトリにはMovidius用のアプリケーションが多く登録されています。

まずはコンテナ内でダウンロードします。
今回はNCSDK V2を使用しているため、NC App Zooもncsdk2ブランチを使用します。

```
# git clone -b ncsdk2 https://github.com/movidius/ncappzoo.git
```

ダウンロードが完了すると"ncappzoo"ディレクトリが出来ており、その下の"apps"ディレクトリに各種アプリケーションが格納されています。

ここでは"[classifier-gui](https://github.com/movidius/ncappzoo/tree/ncsdk2/apps/classifier-gui)"を試してみます。

ビルドして実行してみます。

```
# cd ncappzoo/apps/classifier-gui
# make all
# make run
```

以下のように表示されます。

![pi_ncappzoo_cap1.png](https://qiita-image-store.s3.amazonaws.com/0/187299/7e84da5e-1486-30ec-61c8-5ddc78afdae3.png)

このアプリはMovidiusを使って、GoogleNet、AlexNet、SqueezeNetの3つのネットワークで画像を分類するサンプルアプリとなっています。

他にもNC App Zooには興味深いアプリがあるため、試してみると面白いと思います。

# おしまい
これで一通り、ラズパイ上のDockerでMovidiusやUSBカメラ、GUIアプリの実行環境が整うと思います。
Dockerイメージのビルドには時間がかかるのが難点ですが、一度作ってしまえば簡単にコンテナを用意でき、そこからさらに新たなバージョンのイメージを作るなど、環境の切り分けや管理が楽になるかと思います。
イメージやコンテナを作りすぎるとSDカードの容量が減ってしまうので注意が必要です・・・。