Intel Edge Insights for Industrial (EII)をインストールして動画解析コンテナアプリケーションを試してみる

# はじめに
Industry 4.0やスマートシティなど、動画をデータソースにAI解析を行って業務効率化などを行うユースケースが出ていますが、動画解析やAIをこれから始める人にとっては学習コストが高いかと思います。

本記事では、動画や時系列データをエッジで取り込み、分析や保存を行うアプリケーションを容易に実装できるIntel&reg; Edge Insights for Industrial (EII)をインストールし、デフォルトで提供される不良品検知デモを構築します。
(Intel Edge Insights for Industrial だと長いので、以降、EIIと記載します)

:::note info
OpenCV、OpenVINO、Edge Insights for IndustrialはIntel社の登録商標です。
:::

# EIIとは
EIIは、Intelの開発した動画解析アプリケーションのフレームワークで、[Intelのホームページ](https://www.intel.co.jp/content/www/jp/ja/edge-computing/resources/eii-animation-overview.html)にて無料でダウンロードできます。画像解析はOpenCV、AIはOpenVINO&trade;ベースのモデルを使っています。
また、各アプリケーションはDockerまたはKubernetesを用いてdeployされます。

ソースコードがGitHubで公開されてますので参考にして下さい。

https://github.com/open-edge-insights

# deployするアプリケーション

今回は、EIIの`video-streaming`ユースケースデモをdeployしてみたいと思います。
`video-streaming`ユースケースでは、以下の5つのアプリケーションをdeployします。

|  アプリ名  |  処理内容  |
| ---- | ---- |
|  etcd  |  EIIのコアアプリケーションであり、EII上の各マイクロサービスのconfigを管理するKVSです。  |
|  etcd-ui  |  etcd内のconfigをGUIで編集するためのetcd-keeperのWeb UIです。  |
|  video-ingestion | 動画データをOpenCVやRTSPなどをインタフェースとして取り込み、jpgなどの画像取り込み、リサイズなどのフィルタリングを行なってキューへ格納します。キューはZeroMQが使われています。  |
|  video-analytics | video-ingestionで取り込んだ画像データを、video-ingestionのキューから取り込み、推論などの処理を実行します。そして、その結果をキューへ格納します。 | 
|  web-visualizer  |  EIIのサンプリアプリケーションのデモ用のWeb UIです。video-analyticsのキューから画像を取り出し、UI表示します。 |

各コンテナ間の連携イメージを以下に示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559968/c314dfee-fb73-f9b0-ea65-6b6b10c5f883.png)



# 開発環境
今回はAWSへインスタンスを1台deployしてEIIをインストールしました。

|  項目  |  値  |
| ---- | ---- |
|  OS  |  Ubuntu18.04  |
|  台数  |  1  |
|環境| AWS |
|  インスタンスタイプ | m5.xlarge|
|   DISK  |  100GB  |

- 動画解析の場合はMEM 16GBが最低限必要で、時系列の取り込みと分析は2GBで十分の様です。
- EIIのインストールはDocker、docker-compose or Kubernetes(Helm)が使われますので、OSはDockerが動けばなんでも大丈夫です。ただ、gitに記載の通り、検証済みのUbuntu 18.04LTSが確実です。
- DISKは最低でも64GB必要です。

# EIIのインストール
## 1. AWSへ開発用インスタンスをdeploy
今回は、Terraformを用いてAWSへUbuntuインスタンスを1台deployします。
任意のディレクトリに`compute.tf`と`network.tf`を設置します。

### 1-1. 作業ディレクトリの準備
```bash
$ mkdir work
$ cd work
$ tree

├── compute.tf
└── network.tf
```

### 1-2. インスタンス展開用のモジュール作成

```tf:compute.tf
# ====================
#
# Variables
#
# ====================
# 自身の環境に合わせて要修正！
variable "aws_access_key" {
  default = "IAM_ACCESS_KEY"
}

# 自身の環境に合わせて要修正！
variable "aws_secret_key" {
  default = "IAM_SECRET_KEY"
}

# Ubuntu
variable "ami" {
  default = "ami-085e9421f80dbe728"
} 

# 自身の環境に合わせて要修正！
variable "ssh_key_name" {
  default = "KEY_NAME"
}

# 自身の環境に合わせて要修正！
variable "ssh_key_path" {
  default = "KEY_PATH"
}


# 要件を満たすinstance typeを選択
variable "instance_type" {
  default = "m5.xlarge"
}

# 今回はTokyoリージョンを選択
variable "aws_region" {
    default = "ap-northeast-1"
}

# 任意のAvailability Zone
variable "aws_zone" {
    default = "ap-northeast-1a"
}

provider "aws" {
    access_key = "${var.aws_access_key}"
    secret_key = "${var.aws_secret_key}"
    region     = "${var.aws_region}"
}

# ====================
#
# EC2
#
# ====================

resource "aws_instance" "instance" {
    ami           = "${var.ami}"
    vpc_security_group_ids = [aws_security_group.public.id]
    subnet_id              = aws_subnet.public.id
    key_name               = aws_key_pair.instance.id
    instance_type = "${var.instance_type}"
    monitoring    = false 
    root_block_device {
        volume_type = "gp2"
        volume_size = "100"
    }
    tags = {
        Name = "dev-instance"
    }
}


# ====================
#
# Elastic IP
#
# ====================
resource "aws_eip" "instance" {
  instance = "${aws_instance.instance.id}"
  vpc      = true
}

# ====================
#
# Key Pair
#
# ====================
resource "aws_key_pair" "instance" {
  key_name   = var.ssh_key_name
  public_key = file(var.ssh_key_path)
}

# ====================
#
# Output
#
# ====================

output "EIPはこちら" {
  value = "${aws_eip.instance.public_ip}"
}
```

### 1-3. ネットワーク作成用のモジュール作成

`network.tf`の中身は以下です。

```tf:network.tf
# ====================
#
# VPC
#
# ====================
resource "aws_vpc" "public" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "public"
  }
}

# ====================
#
# Subnet
#
# ====================
resource "aws_subnet" "public" {
  cidr_block        = "10.0.1.0/24"
  availability_zone = "ap-northeast-1a"
  vpc_id            = aws_vpc.public.id

  # trueにするとインスタンスにパブリックIPアドレスを自動的に割り当ててくれる
  map_public_ip_on_launch = true

  tags = {
    Name = "public-sub"
  }
}

# ====================
#
# Internet Gateway
#
# ====================
resource "aws_internet_gateway" "public" {
  vpc_id = aws_vpc.public.id

  tags = {
    Name = "public-gw"
  }
}

# ====================
#
# Route Table
#
# ====================
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.public.id

  tags = {
    Name = "public-rt"
  }
}

resource "aws_route" "public" {
  gateway_id             = aws_internet_gateway.public.id
  route_table_id         = aws_route_table.public.id
  destination_cidr_block = "0.0.0.0/0"
}

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

# ====================
#
# Security Group
#
# ====================
resource "aws_security_group" "public" {
  vpc_id = aws_vpc.public.id
  name   = "public-sg"

  tags = {
    Name = "public-sg"
  }
}

# Inbound(ssh)
resource "aws_security_group_rule" "in_ssh" {
  security_group_id = aws_security_group.public.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 22
  to_port           = 22
  protocol          = "tcp"
}

# Inbound(ICMP)
resource "aws_security_group_rule" "in_icmp" {
  security_group_id = aws_security_group.public.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = -1
  to_port           = -1
  protocol          = "icmp"
}

# Inbound(EII Web Visualizer)
resource "aws_security_group_rule" "in_webvisualizer" {
  security_group_id = aws_security_group.public.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 5000
  to_port           = 5001
  protocol          = "tcp"
}

# Inbound(EII etcd-ui)
resource "aws_security_group_rule" "in_etcd_ui" {
  security_group_id = aws_security_group.public.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 7070
  to_port           = 7071
  protocol          = "tcp"
}

# Outbound
resource "aws_security_group_rule" "out_all" {
  security_group_id = aws_security_group.public.id
  type              = "egress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
}
```

### 1-4. Terraform初期化
```bash
$ terraform init
```

### 1-5. dry-run

```bash
$ terraform plan
```

### 1-6. インスタンス作成

```bash
$  terraform apply
Enter a value:  yes

Apply complete! Resources: 13 added, 0 changed, 0 destroyed.

Outputs:

EIPはこちら = "<作成したインスタンスのグローバルIPアドレス>"
```

## 2. EIIインストールに必要な環境の構築
### 2-1. 作成したインスタンスへアクセス

```bash
$ ssh -i <KEY> ubuntu@<作成したインスタンスのグローバルIPアドレス>
```

### 2-2. アップデート

```bash
$ sudo apt-get -y update
```

### 2-3. Docker-CEインストール

```bash
$ sudo apt-get -y install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

$ sudo add-apt-repository \
     "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
     $(lsb_release -cs) \
     stable"
$ sudo apt-get -y update
$ sudo apt-get -y install docker-ce
$ sudo systemctl start docker
$ sudo systemctl enable docker
```

参考URL

https://docs.docker.com/engine/install/ubuntu/

### 2-4. Docker-composeインストール

```bash
$ sudo apt-get -y install jq
$ sudo curl -L https://github.com/docker/compose/releases/download/$(curl https://api.github.com/repos/docker/compose/releases/latest | jq .name -r)/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
```

## 3. EIIソースコードの入手
詳細は以下の手順を参照して下さい。

https://github.com/open-edge-insights/eii-manifests

### 3-1. EIIインストールで必要となるPython3のパッケージをインストール
```bash
$ sudo apt-get -y install python3-pip
$ sudo pip3 install jsonmerge
$ sudo pip3 install ruamel.yaml
```

### 3-2. repoツールをダウンロード
```bash
$ sudo curl https://storage.googleapis.com/git-repo-downloads/repo > repo
$ sudo mv repo /bin/repo
$ sudo chmod a+x /bin/repo
```

### 3-3. 作業ディレクトリを作成
```bash
$ mkdir -p <work-dir>
$ cd <work-dir>
```

### 3-4. gitのユーザ名とメールアドレスを設定
```bash
$ sudo git config --global user.name "USERNAME"
$ sudo git config --global user.email "MAIL_ADDRESS"
```

### 3-5. python3コマンドのシンボリックリンクを作成
ダウンロードした`repo`コマンドは`python3`コマンドでなく`python`コマンドを実行する実装になっています。
そのため、`python3`コマンドのシンボリックリンク `python`を作成しておきます。

```bash
$ sudo ln -s /usr/bin/python3 /usr/bin/python
```

### 3-6. EIIのソースコードをダウンロード

```bash
$ sudo repo init -u "https://github.com/open-edge-insights/eii-manifests.git"
...
Enable color display in this user account (y/N)? y

$ sudo repo sync
```
## 4. EIIインストールのためのconfig作成
詳細は以下の手順を参照して下さい。

https://github.com/open-edge-insights/eii-core

### 4-1. 環境変数を修正
`<work-dir>/IEdgeInsights/build/`配下にある`.env`の下記の箇所を修正します。
今回はデモのため、`DEV_MODE=true`として構築します。

`DEV_MODE=false`は、各マイクロサービスがEIIのetcdへ接続する際に、暗号化して接続するモードになります。
`DEV_MODE=true`の場合は、特に暗号化されず接続します。

その他、必要に応じて環境変数を設定して下さい。

:::note warn
同一環境にKubernetesがインストールされている場合、etcdのポート番号がバッティングしますので、
必ず下記の71-72行目を修正し、EIIのetcdのポート番号を変更して下さい。
:::

```bash
$ cd IEdgeInsights/build/
$ sudo vi .env
 ...
 26 DEV_MODE=true #DEV_MODEでdeployする
 ...
# 初回インストール時は設定しないでください。(イメージのpull先として指定されます)
 40 DOCKER_REGISTRY=
 41 DOCKER_USERNAME=
 42 DOCKER_PASSWORD=
...
 71 ETCD_CLIENT_PORT=2379
 72 ETCD_PEER_PORT=2380
...
```

### 4-2. ビルド用docker-compose.yaml生成
`builder.py`を実行し、docker-composeのファイルを出力します。
また、今回は`video-streaming`ユースケースデモをdeployしますので、`-f`に対象のyamlを指定します。

```bash
$ pwd
<work-dir>/IEdgeInsights/build/

$ sudo python builder.py -f ./usecases/video-streaming.yml
```

`builder.py`を実行すると、`docker-compose.yml` `docker-compose-build.yml` `docker-compose-push.yml` が生成されます。それぞれの用途は以下の通りです。

|  項目  |  値  |
| ---- | ---- |
|docker-compose.yml  　　　　　　　　　　　　| コンテナのdeploy  |
|docker-compose-build.yml  |  コンテナイメージのbuid  |
|docker-compose-push.yml| コンテナイメージのRegistryへのpush |


また、以下も同様に作成されます。

|  作成されるもの  |  内容  |作成場所|
| ---- | ---- | ---- |
| eii-config.json  | EII上へdeployしたマイクロサービスの設定  | IEdgeInsights/build/provision/config|
| values.yaml  |  Helmのvalues.yaml  | IEdgeInsights/build/helm-eii/eii-provision|


### 4-3. etcdとetcd-provisionを起動
```
$ pwd
<work-dir>/IEdgeInsights/build
$ cd provision  && sudo -E ./provision.sh ../docker-compose.yml
```

EIIはetcdで各マイクロサービスのconfigを一元化します。
`etcd-provision`は`eii_config.json`の内容を`etcd`へ登録するコンテナです。

### 4-4. イメージ作成
```
$ pwd
<work-dir>/IEdgeInsights/build
$ sudo -E docker-compose -f docker-compose-build.yml build
```

1-2時間くらいかかりますので気長に待ちます。

### 4-5. コンテナdeploy
```
$ sudo docker-compose up -d
[+] Running 8/8
 ⠿ Volume "edgeinsightssoftware_vol_eii_socket"   Created                                                                                                                                                                           0.0s
 ⠿ Volume "edgeinsightssoftware_vol_temp_webv"    Created                                                                                                                                                                           0.0s
 ⠿ Volume "edgeinsightssoftware_vol_etcd_keeper"  Created                                                                                                                                                                           0.1s
 ⠿ Container ia_etcd_ui                           Started                                                                                                                                                                           3.6s
 ⠿ Container ia_video_analytics                   Started                                                                                                                                                                           2.4s
 ⠿ Container ia_visualizer                        Started                                                                                                                                                                           2.5s
 ⠿ Container ia_web_visualizer                    Started                                                                                                                                                                           1.6s
 ⠿ Container ia_video_ingestion                   Started 


$ sudo docker-compose ps
WARN[0000] network eii: network.external.name is deprecated in favor of network.name 
NAME                 COMMAND                  SERVICE              STATUS               PORTS
ia_etcd              "./start_etcd.sh"        ia_etcd              running (healthy)    0.0.0.0:2379->2379/tcp, :::2379->2379/tcp
ia_etcd_provision    "python3 etcd_provis…"   ia_etcd_provision    exited (0)           
ia_etcd_ui           "python3 start_etcdk…"   ia_etcd_ui           running (starting)   0.0.0.0:7070-7071->7070-7071/tcp, :::7070-7071->7070-7071/tcp
ia_video_analytics   "./VideoAnalytics/va…"   ia_video_analytics   running (starting)   0.0.0.0:65013->65013/tcp, :::65013->65013/tcp
ia_video_ingestion   "./VideoIngestion/vi…"   ia_video_ingestion   running (starting)   0.0.0.0:64013->64013/tcp, :::64013->64013/tcp
ia_visualizer        "./visualizer_start.…"   ia_visualizer        restarting           
ia_web_visualizer    "./web_visualizer_st…"   ia_web_visualizer    running (starting)   0.0.0.0:5000-5001->5000-5001/tcp, :::5000-5001->5000-5001/tcp
```

EIIでは、まず`ia_etcd_provision`が`eii_config.json`を読み込み、etcdへ格納します。
`ia_etcd_provision`にて以下の様なログが出力されていれば、正常にプロビできてます。

```bash
$ sudo docker-compose logs ia_etcd_provision
WARN[0000] network eii: network.external.name is deprecated in favor of network.name 
ia_etcd_provision  | =======Adding key/values to etcd========
ia_etcd_provision  | Added /EtcdUI/config key successfully
ia_etcd_provision  | Added /EtcdUI/interfaces key successfully
ia_etcd_provision  | Added /GlobalEnv/ key successfully
ia_etcd_provision  | Added /VideoAnalytics/config key successfully
ia_etcd_provision  | Added /VideoAnalytics/interfaces key successfully
ia_etcd_provision  | Added /VideoIngestion/config key successfully
ia_etcd_provision  | Added /VideoIngestion/interfaces key successfully
ia_etcd_provision  | Added /Visualizer/config key successfully
ia_etcd_provision  | Added /Visualizer/interfaces key successfully
ia_etcd_provision  | Added /WebVisualizer/config key successfully
ia_etcd_provision  | Added /WebVisualizer/interfaces key successfully
ia_etcd_provision  | =======Reading key/values from etcd========
```

`ia_video_ingestion`、`ia_video_analytics`、`ia_etcd_ui`、`ia_web_visualizer`、`ia_visualizer`は、etcdへ接続し、`eii_config.json`に設定されたパラメータを読み込み、処理を行います。

尚、`ia_visualizer`はXWindowへ接続している状態でないと起動しません。

### 4-6. デモアプリケーションの表示

`ia_video-ingestion`、`ia_video-analytics`、`ia_web_visualizer`のSATUSが`running`になったら、ブラウザを開き`5001`番ポートへ接続します。

http://<作成したインスタンスのグローバルIPアドレス>:5001/

すると、以下の様なログイン画面が表示されます。
ユーザ名とパスワードは`eii_config.json`の`/WebVisualizer/config/username`と`/WebVisualizer/config/password`に記載されています。


```bash:
$ pwd
<work-dir>/IEdgeInsights/build

$ cd provision/config/

$ cat eii_config.json | jq '."/WebVisualizer/config" | .username, .password'
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559968/3a3bfe3c-e89d-d697-d198-765819497e32.png)

ログインすると、下記の画面が表示されます。
`ia_video_ingestion`が`<work>/IEdgeInsights/VideoIngestion/test_videos`に配置された`pcb_d2000.avi`を読み込み、`jpg`ファイルへ変換して、`ia_video_analytics`が不良箇所を検知するデモになってます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559968/f8d659e9-cb83-915e-b679-4d64753ea871.png)

:::note warning
ブラウザはGoogle Chrome推奨です。
:::


### 4-7. eii_config.jsonの編集
`eii_config.json`にはdeployした各マイクロサービスの設定パラメータが記載されています。
パラメータを修正したい場合に、改めてコンテナイメージをビルドするのも一つの手段ですが、etcd-keeperのWeb UIを使用して変更することも可能です。Web UIはブラウザを開いて`7071`番ポートへアクセスします。

http://<作成したインスタンスのグローバルIPアドレス>:7071/

表示された画面上で`etcdkeeper/`をクリックすると以下の画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559968/5d55847b-4d6a-3ad7-75fa-e24ba82e3912.png)

この画面上で`eii_config.json`を修正して[save]ボタンを押した後、対象のコンテナを再作成すれば設定が反映されます。
(今回の手順ではetcdのdataを永続化していませんので、etcdコンテナは再作成しないでください)