Docker x 可視化がうまくいかずハマったのでまとめてみた！

#はじめに
Docker上でOpenCV等のビジョン系ライブラリを使用する時は、DockerコンテナとローカルPCのディスプレイを接続する必要がありますが、この方法が分からず結構な時間を要してしまいましたので、途中で遭遇したトラブルとその解決策を含めてまとめていきたいと思います！

#開発環境
Ubuntuです！詳細は下記コマンドでチェックしておきます！

```terminal
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.3 LTS"

$ arch
x86_64
```

#Dockerイメージのダウンロード
まずはDocker HubからDockerイメージをダウンロードします。Docker Hubで公開されているDockerイメージは、自由にダウンロード、保存することができます。 

```terminal
$ docker pull ubuntu:18.04
```

#Dockerコンテナのデプロイ
保存済みのDockerイメージを用いて、Dockerコンテナを立ち上げます。Dockerコンテナをデプロイする際、様々なオプションがあります[[1]](https://docs.docker.com/v17.12/edge/engine/reference/commandline/run/)[[2]](https://qiita.com/tnarihi/items/275c009e9dec1306893f)。
今回使用するオプションは、`-it`、`--privileged`、`--env`、`--volume`、`--name`です。
まず`-it`により、Dockerコンテナに対して擬似端末(pseudo terminal)を割り当てることで、Dockerコンテナ上でBashを使用できるようにします。
続いて`--privileged`により、全てのデバイスに対するアクセスを許可します。
`--env`では、Dockerコンテナ内で使用する環境変数を指定します。
`--volume`では、ホストOSのディレクトリをゲストOSにマウントすることができます[[3]](https://docs.docker.com/v17.09/engine/admin/volumes/bind-mounts/)。
`--name`ではコンテナ名を指定することができます。

```terminal
$ docker run -it \
             --privileged \
             --env="DISPLAY=$DISPLAY" \
             --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" \
             --volume="$HOME/ws/:/home/" \
             --name="ubuntu_cv2" \
             ubuntu:18.04
```

#パッケージのインストール
まずはUbuntuお決まりのアップデート、アップグレードです。

```terminal
# apt-get update
```

```terminal
# apt-get upgrade
```
続いて、エディタとしてVimをインストールしておきます。

```terminal
# apt-get install vim
```

プログラミング言語にはPythonを使用します。

```terminal
# apt-get install python3.6 \
                  python3-dev \
                  python3-pip \
                  libffi-dev \
                  libssl-dev
```

pipを使用してOpenCVをインストールします。

```terminal
# pip3 install opencv-python
```

#画像表示プログラムを作成

OpenCVを使用して画像を表示する基本的なプログラムを作成しておきます。

```open_image.py
import cv2

# Load an color image
img = cv2.imread('test.jpg')

# Show the image
cv2.imshow('image',img)

# Stop when 0 pressed
cv2.waitKey(0)
cv2.destroyAllWindows()
```

#トラブル1：OpenCVを正しくインポートできない

まずPythonを実行して、OpenCVが正しくインポートできるかテストしてみます。

```terminal
# python3
>>> import cv2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/dist-packages/cv2/__init__.py", line 3, in <module>
    from .cv2 import *
ImportError: libSM.so.6: cannot open shared object file: No such file or directory
```

案の定、エラーによりOpenCVをインポートできません。このエラーは、`libsm6`、`libxext6`、`libxrender-dev`のインストール[[4]](https://github.com/NVIDIA/nvidia-docker/issues/864)により解決できます。

```terminal
# apt-get install -y libsm6 libxext6 libxrender-dev
```

インストール後、もう一度OpenCVをインポートできるかテストしてみます。

```terminal
# python3
>>> import cv2
```

OKですね！

#トラブル2：画像を表示できない

先程作成した`open_image.py`を実行してみましょう。

```terminal
# python3 open_image.py
No protocol specified
: cannot connect to X server :1
```

PCとのディスプレイ接続に失敗しているようです。ホストPC側で、ゲストPCからのホストPCディスプレイへの接続要求を拒否していることが理由でした。別のターミナルを開けて、ホストPC側で下記コマンドを実行すると、ディスプレイへの接続を許可[[5]](https://unix.stackexchange.com/questions/121716/unable-to-open-x-server)[[6]](https://www.stitson.com/pub/book_html/node72.html)[[7]](https://wiki.archlinux.jp/index.php/Xhost)することができます。

```terminal
$ xhost +
access control disabled, clients can connect from any host
```

再度Dockerコンテナを実行していたターミナルに戻り、`open_image.py`を実行してみましょう。

```terminal
# python3 open_image.py
X Error: BadAccess (attempt to access private resource denied) 10
  Extension:    130 (MIT-SHM)
  Minor opcode: 1 (X_ShmAttach)
  Resource id:  0x247
...
```

別のエラーが出てきました。エラー原因を調査したところ、環境変数に`QT_X11_NO_MITSHM=1`を追加することで解決できるそうです。まずは`.bashrc`ファイルをVimで開きます。

```
# vim ~/.bashrc
```

続いて、環境変数`QT_X11_NO_MITSHM=1`を追記します。

```vim
export QT_X11_NO_MITSHM=1
```

`.bashrc`ファイルを保存した後、環境変数の追加を反映させるため、`source`コマンド[[8]](https://bash.cyberciti.biz/guide/Source_command)を実行します。

```terminal
# source ~/.bashrc
```

`echo`コマンドを使用して、環境変数が正しく追加されているか確認してみましょう。

```terminal
# echo $QT_X11_NO_MITSHM
1
```

OKですね！三度目の正直です。`open_image.py`を実行してみましょう！

```terminal
# python3 open_image.py
```

画像が表示されました！！！

#おわりに
様々なトラブルがありましたが、無事にDockerコンテナとPCディスプレイを接続して、OpenCVにより画像を表示させることができました。今回の検証より、Dockerコンテナをデプロイする際には、下記のように環境変数`QT_X11_NO_MITSHM=1`を追加しておくと良いことも分かりました。

```terminal
$ docker run -it \
             --privileged \
             --env="DISPLAY=$DISPLAY" \
             --env="QT_X11_NO_MITSHM=1" \
             --volume="/tmp/.X11-unix:/tmp/.X11-unix:rw" \
             --volume="$HOME/ws/:/home/" \
             --name="ubuntu_cv2" \
             ubuntu:18.04
```

これからDockerマスターを目指して頑張っていこうと思います！
読んで頂いた方々、ありがとうございます！！！

#参考文献
[1] Docker run options, https://docs.docker.com/v17.12/edge/engine/reference/commandline/run/
[2] Example of docker run, https://qiita.com/tnarihi/items/275c009e9dec1306893f
[3] How to mount on docker, https://docs.docker.com/v17.09/engine/admin/volumes/bind-mounts/
[4] How to solve opencv import error, https://github.com/NVIDIA/nvidia-docker/issues/864
[5] Open X server, https://unix.stackexchange.com/questions/121716/unable-to-open-x-server
[6] Allowing computers access, https://www.stitson.com/pub/book_html/node72.html
[7] About xhost, https://wiki.archlinux.jp/index.php/Xhost
[8] Source command, https://bash.cyberciti.biz/guide/Source_command