Docker上でOpenCVの公式sampleを実行してみた

Dockerコンテナ上でOpenCVを実行する環境を構築し、
OpenCVの公式サンプルを実行しました。

その手順とハマったことを中心に整理しました。

コンテナ上でGUIプログラムを
実行する手順もまとめています。

**今回作成したDocke Image**
https://hub.docker.com/r/rimacdocker/opencv_demo

# やったこと
以下の手順で
Docker上でOpenCVの公式サンプルを実行しました。

1. [Dockerコンテナ上でOpenCVが動く環境の構築](#1-dockerコンテナ上でopencvが動く環境の構築)
2. [コンテナの画面出力をホストPCのディスプレイに設定](#2-コンテナの画面出力をホストpcのディスプレイに設定)
3. [コンテナ上でサンプルプログラムの実行](#3-コンテナでサンプルプログラムの実行)

==> [実行結果](#実行結果)

# ホストPC側の環境
MacOS 10.13.1
Docker 18.09.2

# 1. Dockerコンテナ上でOpenCVが動く環境の構築
詳細な手順は以下の通り

1-1. OSイメージの取得、コンテナの起動
1-2. コンテナ内での必要なパッケージ、ライブラリのインストール
1-3. コンテナへのOpenCVリポジトリのコピー

## 1-1. OSイメージの取得、コンテナの起動
コンテナを起動して環境を構築していきます。
今回はUbuntuの最新イメージを取得して、コンテナを起動します。

```
$ docker run -it ubuntu
```

## 1-2. 必要なパッケージ、ライブラリのインストール
OpenCV公式sampleを実行するのに必要なパッケージ、ライブラリをインストールします。
sampleを実行するだけなら、それほど複雑な環境は要求されません。

### インストールしたパッケージ
- Ubunutuパッケージ
  - python3
  - python3-pip
  - python3-tk
  - libsm6
  - libxext6
  - libxrender-dev
 
- Pyhtonライブラリ
 - opencv-python
 - numpy
 - matplotlib

### コンテナ内で実行したコマンド
```
# パッケージマネージャの最新化
$ apt update
$ apt upgrade

# Python, Pipのインストール
$ apt install python3
$ apt install python3-pip --fix-missing

# opencvのインストール
$ pip3 install opencv-python

# opencv内部で使っているのでインストール
$ pip3 install numpy

# opencvの実行に必要なUbuntuパッケージをインストール
$ apt install -y libsm6
$ apt install -y libxext6 libxrender-dev —fix-missing

# OpenCV公式のsampleの実行に必要なのでインストール
$ apt install python3-tk
$ pip3 install matplotlib
```

## 1-3. コンテナへのOpenCVリポジトリのコピー
`ctrl + p + q`

で一度、コンテナを停止させずに抜けます。
以降の作業はホストPC側で行ってください。

ホストPCでOpenCVのリポジトリをクローンします。

```
$ git clone https://github.com/opencv/opencv.git
```
| 

その後、起動しているコンテナIDを調べます。

```
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
123456789ABC        opencv_demo             "/bin/bash"         2 minutes ago       Up 2 minutes                            hoge_hoge
```
| 

調べたコンテナID(上の例では'123456789ABC')で指定して
ホストPCからコンテナへ
OpenCVリポジトリをコピーします。
この時に、
ホスト側のopencvローカルリポジトリがある場所は相対パスで、
Dockerコンテナ側の配置パスは絶対パスで書きます。

```
docker cp ./opencv/ 123456789ABC:/root/
```
| 

これで作成するコンテナとしては完成です。
docker commitでここまでの状態を
イメージとして保存しておくと良いです。

```
docker commit 123456789ABC opencv_demo
```

# 2. コンテナの画面出力をホストPCのディスプレイに設定
OpenCVの公式サンプルは実行結果を画面に出力しますが、
コンテナプロセスには紐付くディスプレイがないので
コンテナの画面出力をホストPCのディスプレイに飛ばす必要があります。

そのための設定を行っていきます。
この作業はホストPCで実施する作業です。

詳細な手順は以下の通り。

2-1. Mac用X11インストール
2-2. ホストPCのディスプレイを外からアクセス可能にする
2-3. コンテナへのOpenCVリポジトリのコピー

## 2-1. Mac用X11インストール
2016年以降MacにはX11が入ってないので
X11をインストールする必要があります。
使用するPCにX11が入っていればこの手順は不要です。

```
$ brew cask install xquartz
```

尚、`brew install`では入らないので
brewを拡張したcaskを使ってインストールしました。
インストール後にマシンを再起動します。

## 2-2. ホストPCのディスプレイを外からアクセス可能にする
xhost コマンドでホストPC以外からも
ホストPCのディスプレイへアクセス可能にします。

```
$ xhost +
```

## 2-3. コンテナからのアクセスを待ち受ける
socatコマンドを使って、
ホストPCのディスプレイへのアクセスを待ち受けます。

```
# socatのインストール
$ brew install socat

# socat実行
$ socat TCP-LISTEN:6000,reuseaddr,fork UNIX-CLIENT:\"$DISPLAY\"
```

socatコマンド実行後、
標準出力が帰ってこない状態で待ち受けるので
以降の作業は別ターミナルを立ち上げて行います。

### 注意点: xhost -
```
xhost +
```
でディスプレイへの外からのアクセスを全て可能にするのは
セキュリティ的には望ましくないので
sampleプログラムの実行が終わったら

```
$ xhost -
```
でセキュリティレベルを元に戻しておいた方が良いです。

# 3. コンテナでサンプルプログラムの実行
[手順1](#1-dockerコンテナ上でopencvが動く環境の構築)で作成したコンテナイメージをdocker runします。

この時にホストPCのX11の認証情報を
コンテナのボリュームにマウントします。

```
$ docker run -it -e DISPLAY=XXX.XXX.XXX.XXX:0 \
-v /Users/r_i/.Xauthority/:/root/.Xauthority \
-v /tmp/.X11-unix/:/tmp/.X11-unix opencv_demo /bin/bash
```

※ `XXX.XXX.XXX.XXX`の部分はホストPCのIPアドレスに読み替えて下さい。

これによりコンテナの画面出力を
ホストPCのウィンドウに表示可能にしてコンテナを起動します。

起動したコンテナで以下のコマンドで、
OpenCVのサンプルプログラムを実行します。

```
python3 /root/opencv/samples/python/demo.py
```


# 実行結果
コマンド実行後、以下の画面が立ち上がって
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259302/ff78d1d6-22a1-8dac-756a-e02a81bdfd00.png)

以下のようにGUIで、選択したプログラムを実行できる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259302/1ad074b1-c429-57c1-b155-b62c794b9acd.png)


# まとめ

docker上でOpenCVを実行することで
ホストPC環境に手を入れることなく、
OpenCVのデモアプリを実行出来ました。

docker上で実行したプログラムの画面出力を
ホストPCのディスプレイに表示させると
コンテナの可能性が広がるんじゃないでしょうか。