AzureVMでOpenVINOのデモを動かす

# 本記事について
Intel による OpenVINO の学生向けハンズオンをまとめたものです。
ここで参考にしているものはすべて使用許可を得ています。

## 本記事の対象である方
* とりあえず Azure で VM を立てたい
* とりあえず OpenVINO を試したい
* その他これらに近い考えを持っている方

## 本記事の対象ではない方
* ソースコードを詳しく解説してほしい
* コマンドをコピペして実行するのに抵抗がある人

## 前提条件
* 利用できる Azureアカウント を持っている

# 手順
デモを動かすための手順を大きく分けると以下の3つになります。
1. Microsoft Azure で Virtual Machine を立てる
2. デモを動かすための環境作成
3. デモを動かす


# Azure Virtual Machine を作成する
## 仮想マシンの作成
Azure ポータルで「リソースの作成」から「Data Science Virtual Machine- Ubuntu 18.04」を選択し「作成」を押す。
![作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/4bbc22b4-7bd4-73fc-012c-cecd871f47c9.png)
「Data Science Virtual Machine- Ubuntu 18.04」では、主に以下のようなものが初期から使用できる。

>
1. Anaconda Python
2. Jupyter, JupyterLab, and JupyterHub
3. Deep learning with TensorFlow and PyTorch
4. Machine learning with xgboost, Vowpal Wabbit, and LightGBM
5. Julia
6. Azure SDKs and libraries
7. Azure Machine Learning SDKs and sample notebooks
8. R support
9. Spark

「作成」を押すと以下のような画面が表示されます。
![仮想マシン作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/4adc19fa-5673-ebe0-2857-60910cc47a3f.png)
サブスクリプションについては、私が学生なので「Azure for Students」の表記になっています。
したがって、人によっては異なった表記になっている場合があります。

設定事項は次の通りです。

### 基本
ここで設定する項目は以下の通りです。

* リソースグループ
* 仮想マシン名
* 地域
* サイズ
* 認証の種類
* ユーザー名
* パスワード

リソースグループ：「新規作成」で、任意の名称を入力しましょう。

仮想マシン名：任意の名称で大丈夫です。

地域：今回は「米国東部」にします。（地域によってサポートされているツールが異なってきます）

サイズ：「サイズの選択」から「F4s_v2」を選択します。（画像の D2s_v3 で続けようとするとデモの実行時の処理がとても遅くなります）

認証の種類：「パスワード」にします。

ユーザー名：任意の名称で大丈夫です。

パスワード：任意のパスワードで大丈夫です。

（注）これらの設定における「任意」は「Azureが指定する名称の規定」を満たしている状態での任意です。使用できない名称などは教えてくれるので、対処しやすいと思います。

これらが入力できたら「次：ディスク」を選択します。

### ディスク
今回は変更する箇所はないので「次：ネットワーク」を選択します。

### ネットワーク
今回は変更する箇所はないので「次：管理」を選択します。

### 管理
次のような画面になっていると思います。
![仮想マシン作成2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/6a99a205-0e32-0bdb-14a1-f6c1ed7e62ac.png)
今回は「ブート診断」は特に必要ないので「オフ」に設定します。
（このとき「診断ストレージ アカウント」という項目が消えますが問題ありません。）

上記の設定ができたら「次：詳細」を選択します。

### 詳細
今回は変更する箇所はないので「次：タグ」を選択します。

### タグ
今回は変更する箇所はないので「次：確認及び作成」を選択します。

### 確認及び作成
次のように「検証が成功しました」と表示されていれば仮想マシンを作成できます。
![確認及び検証.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/ea221410-82ba-3b60-73f4-e4fad0f14037.png)

### 作成
「作成」を選択して仮想マシンを作成しましょう。
リソースグループへのデプロイが開始されます。
デプロイが完了したらリソースへ移動しましょう。

## ネットワークの設定
リソースへ移動したら、ネットワークを選択します。
![ネットワーク.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/fbcd9f8f-0116-959f-2825-17d9bc107e4a.png)

### Jupyterのためのポート開放
「受信ポートの規則を追加する」を選択すると次の画面が表示されます。
![規則.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/6c38bb89-ced9-de41-1ac3-227168b1a7df.png)
「宛先ポート」が「8080」になっていることを確認して追加します。
受信ポートに次のものが追加されていると思います。
![ポート.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/2c2ba295-be83-1c9c-c35c-2344b33ac631.png)

# デモを動かす
上記で作成したVMを利用してデモを動かしていきます。
VMが動いていることを確認してから次の作業を行ってください。

## VMにアクセスする
今回は「Cloud Shell」を利用します。
（最近知ったのですが、Windows10 の Power Shell や Command Prompt からもできるようですね。）

Azure Portal から Cloud Shell を起動します。
![shell.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/fae7dbbb-ae90-fda1-729b-8b939025b7c4.png)
次のような画面が表示された場合、「ストレージの作成」を押しましょう。
![ストレージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/7d947b38-3164-eb29-cbbb-2e37b27689a3.png)
次の画面が表示されていれば起動できています。
![bash前.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/0a40e78e-1834-f96e-83f1-368fff43ca4b.png)


起動できたら、次のコマンドでVMにアクセスします。

```
ssh UserName@IPAddress
```

UserNameには**VM作成時に決めたユーザーの名称**、IPAddressには仮想マシンの「概要」から見れる「**パブリックIPアドレス**」を入力してください。

英語で「接続を続行しますか」といった警告文が出た場合は、```yes```と入力しましょう。

次に、パスワードの入力を要求されるので VM作成時に指定したパスワード を入力しましょう。

```
azureuser@Azure:~$
```
が

```
UserName@VMName:~$
```
に変わっていれば VM にアクセスできています。
UserNameは**VM作成時に決めたユーザーの名称**、VMNameは**VM作成時に決めた仮想マシンの名称**になります。

# デモを動かす環境を作る
## OpenVINO の dockerイメージ をダウンロード
docker コマンドでお手軽に環境を作成します。

```
sudo docker pull hiouchiy/openvino2020r3-configured-on-cpu-with-inteltf1.15.2
```
（[使用しているイメージはこちらです](https://hub.docker.com/r/hiouchiy/openvino2020r3-configured-on-cpu-with-inteltf1.15.2/tags)）
![pull.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/5eb140df-431b-4cb5-deeb-c7a8394f031c.png)
コマンドを入力すると上記のような画面になるので完了するまで待ちましょう。

## コンテナを起動する
完了したら次のコマンドを入力してコンテナを起動します。

```
sudo docker run -it -p 8080:8008 --privileged hiouchiy/openvino2020r3-configured-on-cpu-with-inteltf1.15.2
```
![run.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/5683d8c2-f792-12a5-059f-8e511de2330f.png)
コンテナが起動すると上記のような画面になります。（TensorFlowが動いてるんですね）
階層が```/temp```になっているので

```
cd
```
と入力しrootに行きます。
![cd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/d8ff2ff9-9bc8-5b9d-7aff-4a67061bd303.png)

## Gitからソースコードをクローンする
クローンする前に魔法のコマンドを2つ入力します。

```
cp -r /opt/intel/openvino/inference_engine/demos/python_demos/colorization_demo/ .
```

```
cp -r /opt/intel/openvino/inference_engine/demos/python_demos/handwritten_japanese_recognition_demo/ .
```

最後の```.```も入れてください。
![魔法.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/ff63fda9-4ef3-b122-4cdd-0b90a132003d.png)

Human Pose estimation 2D demo という骨格認識のソースコードをクローンします。

```
git clone https://github.com/yas-sim/human-pose-estimation-2d-demo.git
```

![Human.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/a148ba5c-e35a-a81f-1870-7e8f73cdad58.png)

## Jupyterへアクセス

```
KMP_AFFINITY=granularity=fine,compact,1,0 KMP_BLOCKTIME=1 KMP_SETTINGS=1 OMP_NUM_THREADS=2 numactl -N 0 -m 0 jupyter notebook --ip 0.0.0.0 --allow-root > /dev/null 2>&1 &
```

上記のコマンドを実行したのちに下記のURLへアクセスします。（各自「IPAddress」部分の変更をお願いします。）
[https://IPAddress:8080/](None)
IPAddress には **VM のパブリックIPアドレス**を入力してください。

下記のような警告が出ると思いますが、詳細情報を表示して「IPAddressにアクセスする」を選択してください。
![warnning.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/e5349b97-c355-05c9-c821-0f76a6ae0436.png)
アクセスできると Jupyter のパスワード入力画面になります。
「intel」でログインしてください。

# デモを動かす
## Human Pose Estimation
jupyter の human-pose-estimation-2d-demo に入って、Python3のノートブックを新規で作成してください。
新規で作成したノートブック上で下記のコマンドとソースコードを入力して、実行します。

```
!pip3 install -r requirements.txt
```
```
!python3 $INTEL_OPENVINO_DIR/deployment_tools/tools/model_downloader/downloader.py --list models.lst
```
```
!chmod +x build-poseextractor.sh
```
```
!./build-poseextractor.sh
```

```python:NewNotebook
import numpy as np
import cv2
from openvino.inference_engine import IENetwork, IECore
import os
import logging as log
import sys
from PIL import Image
import PIL
import io
import IPython.display
from IPython.display import clear_output

from pose_extractor import extract_poses

limbIds = [
        [ 1,  2], [ 1,  5], [ 2,  3], [ 3,  4], [ 5,  6], [ 6,  7], [ 1,  8], [ 8,  9], [ 9, 10], [ 1, 11],
        [11, 12], [12, 13], [ 1,  0], [ 0, 14], [14, 16], [ 0, 15], [15, 17], [ 2, 16], [ 5, 17] ]

limbColors = [
    (255,  0,  0), (255, 85,  0), (255,170,  0),
    (255,255,  0), (170,255,  0), ( 85,255,  0),
    (  0,255,  0), (  0,255, 85), (  0,255,170),
    (  0,255,255), (  0,170,255), (  0, 85,255),
    (  0,  0,255), ( 85,  0,255), (170,  0,255),
    (255,  0,255), (255,  0,170), (255,  0, 85)
]

def renderPeople(img, people, scaleFactor=4, threshold=0.5):
    global limbIDs
    global limbColors
    scalex = img.shape[1]/(57 * scaleFactor)
    scaley = img.shape[0]/(32 * scaleFactor)
    for person in people:
        for i, limbId in enumerate(limbIds[:-2]):
            x1, y1, conf1 = person[limbId[0]*3:limbId[0]*3+2 +1]
            x2, y2, conf2 = person[limbId[1]*3:limbId[1]*3+2 +1]
            if conf1>threshold and conf2>threshold:
                cv2.line(img, (int(x1*scalex),int(y1*scaley)), (int(x2*scalex),int(y2*scaley)), limbColors[i], 2)

def main(video_path):

    ie = IECore()
    model_hp = 'intel/human-pose-estimation-0001/FP32/human-pose-estimation-0001'
    net_hp  = ie.read_network(model=model_hp+'.xml', weights=model_hp+'.bin')
    input_name_hp   = next(iter(net_hp.inputs))
    input_shape_hp  = net_hp.inputs[input_name_hp].shape
    PAF_blobName    = list(net_hp.outputs.keys())[0]
    HM_blobName     = list(net_hp.outputs.keys())[1]
    PAF_shape       = net_hp.outputs[PAF_blobName].shape
    HM_shape        = net_hp.outputs[HM_blobName].shape
    exec_net_hp     = ie.load_network(net_hp, 'CPU')

    cam = cv2.VideoCapture(video_path)
    if cam.isOpened()==False:
        print('Failed to open the input movie file (or a webCam)')
        sys.exit(-1)

    while cv2.waitKey(1) != 27:

        ret, img = cam.read()
        if ret==False:
            return 0

        inblob = cv2.resize(img, (input_shape_hp[3], input_shape_hp[2]))
        inblob = inblob.transpose((2, 0, 1))
        inblob = inblob.reshape(input_shape_hp)

        res_hp = exec_net_hp.infer(inputs={input_name_hp: inblob})

        heatmaps = res_hp[HM_blobName ][0]
        PAFs     = res_hp[PAF_blobName][0]
        people = extract_poses(heatmaps[:-1], PAFs, 4)

        renderPeople(img, people, 4, 0.2)
        
        clear_output(wait=True)
            
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        f = io.BytesIO()
        PIL.Image.fromarray(img).save(f, 'jpeg')
        IPython.display.display(IPython.display.Image(data=f.getvalue()))

    cv2.destroyAllWindows()
    return 0

main("people.264")
```
骨格認識のデモが実行できます。
![HumanDemo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/a4a9ba8b-6c9e-4e7d-a8b4-20ea8afa4d0d.png)

## Colorization
jupyter の colorization_demo に入って、Python3のノートブックを新規で作成してください。
新規で作成したノートブック上で下記のコマンドとソースコードを入力して、実行します。

```
!python /opt/intel/openvino/deployment_tools/tools/model_downloader/downloader.py --list /opt/intel/openvino/inference_engine/demos/python_demos/colorization_demo/models.lst
```
```
!python /opt/intel/openvino/deployment_tools/tools/model_downloader/converter.py --name colorization-v2
```

```python:NewNotebook
from openvino.inference_engine import IECore
import cv2 as cv
import numpy as np
import os
from argparse import ArgumentParser, SUPPRESS
import logging as log
import sys
from PIL import Image
import PIL
import io
import IPython.display
from IPython.display import clear_output


def infer_video(video_path):
    coeffs = "public/colorization-v2/colorization-v2.npy"

    verbose = False
    log.basicConfig(format="[ %(levelname)s ] %(message)s",
                    level=log.INFO if not verbose else log.DEBUG, stream=sys.stdout)

    log.debug("Load network")
    ie = IECore()
    model_path = "public/colorization-v2/FP32/colorization-v2.xml"
    load_net = ie.read_network(model_path, os.path.splitext(model_path)[0] + ".bin")
    load_net.batch_size = 1
    exec_net = ie.load_network(network=load_net, device_name="CPU")

    assert len(load_net.inputs) == 1, "Expected number of inputs is equal 1"
    input_blob = next(iter(load_net.inputs))
    input_shape = load_net.inputs[input_blob].shape
    assert input_shape[1] == 1, "Expected model input shape with 1 channel"

    assert len(load_net.outputs) == 1, "Expected number of outputs is equal 1"
    output_blob = next(iter(load_net.outputs))
    output_shape = load_net.outputs[output_blob].shape
    assert output_shape == [1, 313, 56, 56], "Shape of outputs does not match network shape outputs"

    _, _, h_in, w_in = input_shape

    input_path = video_path
    try:
        input_source = int(input_path)
    except ValueError:
        input_source = input_path

    cap = cv.VideoCapture(input_source)
    if not cap.isOpened():
        assert "{} not exist".format(input_source)

    color_coeff = np.load(coeffs).astype(np.float32)
    assert color_coeff.shape == (313, 2), "Current shape of color coefficients does not match required shape"

    while True:
        log.debug("#############################")
        hasFrame, original_frame = cap.read()
        if not hasFrame:
            break
        (h_orig, w_orig) = original_frame.shape[:2]

        log.debug("Preprocessing frame")
        if original_frame.shape[2] > 1:
            frame = cv.cvtColor(cv.cvtColor(original_frame, cv.COLOR_BGR2GRAY), cv.COLOR_GRAY2RGB)
        else:
            frame = cv.cvtColor(original_frame, cv.COLOR_GRAY2RGB)

        img_rgb = frame.astype(np.float32) / 255
        img_lab = cv.cvtColor(img_rgb, cv.COLOR_RGB2Lab)
        img_l_rs = cv.resize(img_lab.copy(), (w_in, h_in))[:, :, 0]

        log.debug("Network inference")
        res = exec_net.infer(inputs={input_blob: [img_l_rs]})

        update_res = (res[output_blob] * color_coeff.transpose()[:, :, np.newaxis, np.newaxis]).sum(1)

        log.debug("Get results")
        out = update_res.transpose((1, 2, 0))
        out = cv.resize(out, (w_orig, h_orig))
        img_lab_out = np.concatenate((img_lab[:, :, 0][:, :, np.newaxis], out), axis=2)
        img_bgr_out = np.clip(cv.cvtColor(img_lab_out, cv.COLOR_Lab2BGR), 0, 1)

        no_show = False
        if not no_show:
            log.debug("Show results")
            imshowSize = (320, 240)
            original_image = cv.resize(original_frame, imshowSize)
            grayscale_image = cv.resize(frame, imshowSize)
            colorize_image = (cv.resize(img_bgr_out, imshowSize) * 255).astype(np.uint8)
            lab_image = (cv.resize(img_lab_out, imshowSize)).astype(np.uint8)

            original_image = cv.putText(original_image, 'Original', (25, 50),
                                        cv.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, cv.LINE_AA)
            grayscale_image = cv.putText(grayscale_image, 'Grayscale', (25, 50),
                                        cv.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, cv.LINE_AA)
            colorize_image = cv.putText(colorize_image, 'Colorize', (25, 50),
                                        cv.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, cv.LINE_AA)
            lab_image = cv.putText(lab_image, 'LAB interpetation', (25, 50),
                                   cv.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, cv.LINE_AA)

            ir_image = [cv.hconcat([original_image, colorize_image])]
            final_image = cv.vconcat(ir_image)
            final_image = cv.cvtColor(final_image, cv.COLOR_BGR2RGB)
            
            clear_output(wait=True)
            
            f = io.BytesIO()
            PIL.Image.fromarray(final_image).save(f, 'jpeg')
            IPython.display.display(IPython.display.Image(data=f.getvalue()))
        
            if not cv.waitKey(1) < 0:
                break

infer_video("https://github.com/hiouchiy/IntelAI_and_Cloud/raw/master/Azure/handson/bw.mp4")
```
動画着色のデモが行われます。
![color.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/e1815b04-ea1d-88db-8f81-ac8d75e0189d.png)


## Handwritten OCR
jupyter の handwritten_japanese_recognition_demo に入って、Python3のノートブックを新規で作成してください。
新規で作成したノートブック上で下記のコマンドとソースコードを入力して、実行します。

```
!pip install -r requirements.txt
```

```
!python /opt/intel/openvino/deployment_tools/tools/model_downloader/downloader.py --list models.lst
```

```python:NewNotobook
from __future__ import print_function
import os
import sys
import time
import logging as log
from argparse import ArgumentParser, SUPPRESS

import cv2
import numpy as np

from openvino.inference_engine import IECore
from utils.codec import CTCCodec

import sys
from PIL import Image
import PIL
import io
import IPython.display
from IPython.display import clear_output


def get_characters():
    '''Get characters'''
    charlist = "data/kondate_nakayosi_char_list.txt"
    with open(charlist, 'r', encoding='utf-8') as f:
        return ''.join(line.strip('\n') for line in f)


def preprocess_input(image_name, height, width):
    src = cv2.imread(image_name, cv2.IMREAD_GRAYSCALE)
    ratio = float(src.shape[1]) / float(src.shape[0])
    tw = int(height * ratio)
    rsz = cv2.resize(src, (tw, height), interpolation=cv2.INTER_AREA).astype(np.float32)
    img = rsz[None, :, :]
    _, h, w = img.shape
    pad_img = np.pad(img, ((0, 0), (0, height - h), (0, width -  w)), mode='edge')
    return pad_img


def main(image_path):
    log.basicConfig(format="[ %(levelname)s ] %(message)s", level=log.INFO, stream=sys.stdout)

    ie = IECore()
    log.info("Loading network")
    model = "intel/handwritten-japanese-recognition-0001/FP32/handwritten-japanese-recognition-0001.xml"
    net = ie.read_network(model, os.path.splitext(model)[0] + ".bin")

    assert len(net.inputs) == 1, "Demo supports only single input topologies"
    assert len(net.outputs) == 1, "Demo supports only single output topologies"

    log.info("Preparing input/output blobs")
    input_blob = next(iter(net.inputs))
    out_blob = next(iter(net.outputs))

    characters = get_characters()
    codec = CTCCodec(characters)
    assert len(codec.characters) == net.outputs[out_blob].shape[2], "The text recognition model does not correspond to decoding character list"

    input_batch_size, input_channel, input_height, input_width= net.inputs[input_blob].shape

    input_path = image_path
    input_image = preprocess_input(input_path, height=input_height, width=input_width)[None,:,:,:]
    assert input_batch_size == input_image.shape[0], "The net's input batch size should equal the input image's batch size "
    assert input_channel == input_image.shape[1], "The net's input channel should equal the input image's channel"

    log.info("Loading model to the plugin")
    exec_net = ie.load_network(network=net, device_name="CPU")

    number_iter = 1
    log.info("Starting inference ({} iterations)".format(number_iter))
    infer_time = []
    for i in range(number_iter):
        img = cv2.imread(input_path, cv2.IMREAD_GRAYSCALE)
        f = io.BytesIO()
        PIL.Image.fromarray(img).save(f, 'jpeg')
        IPython.display.display(IPython.display.Image(data=f.getvalue()))

        t0 = time.time()
        preds = exec_net.infer(inputs={input_blob: input_image})
        preds = preds[out_blob]
        result = codec.decode(preds)
        print(result)
        infer_time.append((time.time() - t0) * 1000)

    log.info("Average throughput: {} ms".format(np.average(np.asarray(infer_time))))

    sys.exit()

main("data/test.png")
```

日本語の手書き文字を認識するデモが行えます。
![Hand.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690399/d0af0f3d-dd76-db80-4cd5-fbfb0eaf6ba8.png)

# ソースコードに関して
Qiitaにコードをの出るにあたり、見やすくするために手を加えている箇所があります。
これによってエラーが出るかもしれないので、その場合下記のリンクがソースコードもとになりますので、こちらを実行してみてください。

* [Human Pose Estimation](https://github.com/hiouchiy/IntelAI_and_Cloud/blob/master/Azure/handson/human-pose-estimation.ipynb)
* [Colorization](https://github.com/hiouchiy/IntelAI_and_Cloud/blob/master/Azure/handson/colorization.ipynb)
* [Handwritten OCR](https://github.com/hiouchiy/IntelAI_and_Cloud/blob/master/Azure/handson/handwritten-ocr.ipynb)

# 参考
https://github.com/hiouchiy/IntelAI_and_Cloud/tree/master/Azure/demo1
https://github.com/hiouchiy/IntelAI_and_Cloud/tree/master/Azure/handson
https://github.com/yas-sim/openvino-workshop-jp
https://github.com/yas-sim/human-pose-estimation-2d-demo