Movidius Neural Compute Stickの導入記事を清書する

# 自己紹介

こんにちは。北海道の片隅のしがないユーザ企業で社内SEをしているMeguMegu1978です。普段は社内で唯一のエンジニアとして、Djangoを使ってWebシステム作ったり、Docker使ってインフラやったり、はたまたスマホアプリ作ったりしてます。Qiitaでの記事投稿は久しぶりになります。

自分のボスはエンジニアではないのですが、ハードに明るかったり簡単なコーディングは出来る人で、自分と同じガジェット好きなところもあり、出社すると稀に怪しいガジェットが増えていたりします。

今回、アドカレを書くタイミングで [この記事](https://www.rs-online.com/designspark/ai-powered-identification-with-the-pidentifier-jp?&intcmp=JP-WEB-_-Pi-DS2-_-Mar-18-_-Pidentifier)を元にパーツを揃えて、画像認識で~~遊ぼうとしていた~~苦戦していたボスをヘルプした時の知見をまとめたいと思います。(なのでPythonは出てきますが、Python主体の話ではないです。)

# セットアップしてみる

早速セットアップを開始します。ラズパイにOSをインストールするまでは割愛します。ラズパイに必要なパッケージを入れていく手順は元記事にそって、「DesignSpark Pmod HATサポートライブラリ」のインストールまではそのまま進めます。

次にubuntuマシンを別途用意し、モデルのコンパイルを行う手順が記載されていますがここが最初の躓きポイント。手元にubuntuなマシンがあればよいのですが、昨今こういうケースでは仮想環境を用意する人も多いでしょう。特にWindowsな人は手っ取り早くWSLでubuntu環境を構築してしまう人も多いと思いますが、自分はWSL環境では上手く設定できませんでした。というのも、NCSをUSBに挿してもホストOSであるWindowsが認識してしまい、WSL上のubuntuからは見えないからです。結局オフィスに転がっていたubunuでセットアップして上手くいったのですが、VMWareでゲストOSにNCSを認識させても問題なくセットアップ出来ると思います。

躓きポイントはこれだけではないです。元記事にある下記のコマンドですが(慣れている人は気付くと思いますが)誤りがあります。

```bash
user@laptop:~$ mkdir workspace
user@laptop:~$ cd workspace
user@laptop:~$ git clone https://github.com/movidius/ncsdk 
user@laptop:~$ cd ← ここ
user@laptop:~$ sudo make install
```
ncsdkをgit clone後にmake installするのですが2回目のcdにパラメータを渡していないので、ホームディレクトリに戻ってしまいます。正しくは、以下の通り。

```bash
user@laptop:~$ mkdir workspace
user@laptop:~$ cd workspace
user@laptop:~$ git clone https://github.com/movidius/ncsdk 
user@laptop:~$ cd ncsdk
user@laptop:~$ sudo make install
```

躓きポイントは続きます。元記事では、ハードウェアのテストなる手順に入っていきますがおもむろに、

```bash
pi@pidentifier:~$ raspivid -d
```
の後に、「これにより、画面にビデオが表示されるはずだ。」と記載されており、「？？？」な感じになると思います。raspividコマンドでググれば分かりますが、このコマンドはカメラで動画を撮影するコマンドで動画を再生するコマンドではありません。作成された動画ファイルは手元のPCでVLCを使って再生できました。

この後しばらく記事通りに進めていくと、アプリケーションというタイトルの後にubuntuマシンでコンパイルしたモデルをラズパイにコピーする手順が出てきます。

元記事

```bash
user@laptop:~$ scp workspace/ncsdk/examples/caffe/GoogLeNet/graph \
user@laptop:~$ pi@pidentifier.local:workspace/ncappzoo/caffe/GoogLeNet/
```

間違ってはいないのですが、若干不親切。scpに慣れている人はすぐ気付くのですが、ホスト名(pidentifier.local)の指定方法が、Bonjour前提になっています。Windowsな人も多いでしょうし、ラズパイのホスト名もデフォルトのものではないので、ここは直接、ラズパイのIPアドレス(下記の例では192.168.100.1)でしてやるのが良いでしょう。

```bash
user@laptop:~$ scp workspace/ncsdk/examples/caffe/GoogLeNet/graph \
user@laptop:~$ pi@192.168.100.1:workspace/ncappzoo/caffe/GoogLeNet/
```

# 動かしてみる

さて、元記事はいよいよサンプルスクリプトで画像認識をテストするスクリプトに入っていきますがここにも躓きポイントがあります。元記事はスクリプトが切り貼りされていますが、手元で動いているものが以下になります。蛇足になりますが、なぜかスクリプトを書くのに半強制的にviを使うように記載されていますが、その必要はありません。nanoでもemacsでも好きなエディタで作成すれば良いと思います。

このサンプルスクリプトにも2点ほど躓きポイントがあります。

・gNetworkCategoriesのスコープがおかしい。下記のソースコードの★の位置で空の配列として宣言してやることで回避
・googlenet_categories.txtが手順上でてくるリポジトリから消えている。自分は、[ここ](https://github.com/AbinMM/ncappzoo/tree/master/apps/stream_infer)から該当ファイルをダウンロードして、stream_inferディレクトリを作成してファイルを格納しました

ここまできて、ようやくサンプルスクリプトが動きました！！

```python
#!/usr/bin/python3
import os
import sys
import time
import numpy

import picamera

import mvnc.mvncapi as mvnc
import skimage
from skimage import io, transform

from DesignSpark.Pmod.HAT import createPmod
from luma.core.render import canvas
from luma.oled.device import ssd1331

NCAPPZOO_PATH           = os.path.expanduser( '~/workspace/ncappzoo' )
GRAPH_PATH              = NCAPPZOO_PATH + '/caffe/GoogLeNet/graph'
IMAGE_PATH              = '/tmp/i-spy.jpg'
IMAGE_MEAN              = [ 104.00698793, 116.66876762, 122.67891434]
IMAGE_STDDEV            = 1
IMAGE_DIM               = ( 224, 224 )

NETWORK_STAT_TXT = NCAPPZOO_PATH + '/apps/stream_infer/googlenet_stat.txt'
NETWORK_CATEGORIES_TXT = NCAPPZOO_PATH + '/apps/stream_infer/googlenet_categories.txt'

camera = picamera.PiCamera()
camera.resolution = IMAGE_DIM

ncsdevices = mvnc.EnumerateDevices()
if len( ncsdevices ) == 0:
        print( 'No NCS devices found' )
        quit()

ncs = mvnc.Device( ncsdevices[0] )
ncs.OpenDevice()

pmoddev = createPmod('OLEDrgb','JA')
oled = pmoddev.getDevice()

with open( GRAPH_PATH, mode='rb' ) as f:
        blob = f.read()

graph = ncs.AllocateGraph( blob )
gNetworkCategories = [] ★
with open(NETWORK_CATEGORIES_TXT, 'r') as f:
    for line in f:
        cat = line.split('\n')[0]
        if cat != 'classes':
            gNetworkCategories.append(cat)
    f.close()

last = len(gNetworkCategories)-1

def getTopInference(img):
    img = print_img = skimage.io.imread( IMAGE_PATH )
    img = skimage.transform.resize( img, IMAGE_DIM, preserve_range=True )

    img = img[:, :, ::-1]

    img = img.astype( numpy.float32 )
    img = ( img - IMAGE_MEAN ) * IMAGE_STDDEV

    graph.LoadTensor( img.astype( numpy.float16 ), 'user object' )

    output, userobj = graph.GetResult()
    order = output.argsort()
    top = gNetworkCategories[order[last-0]]

    return top

def display(message):
    with canvas(oled) as draw:
        draw.text((16,20), message, fill="white")

try:
    while True:
        camera.capture(IMAGE_PATH)
        thing = getTopInference(IMAGE_PATH)
        display(thing)
        time.sleep(2)

except KeyboardInterrupt:
        pass

finally:
        graph.DeallocateGraph()
        ncs.CloseDevice()
        oled.cleanup()
```