(続)Jetson NanoでDockerでコンパイルしたOpenCV 4.1.0を使う

# はじめに

「[Jetson NanoのDockerでOpenCV 4.1.0をコンパイルする](https://qiita.com/yamamo-to/items/d95ed920fe18cbb4b452)」ではDockerを使ってOpenCV 4.1.0を無事コンパイルすることができた。しかしこのままではDockerを抜けた環境で使うことはできない。そこで、このコンパイルしたバイナリをどうしたらDockerを抜けた環境で、もっとも簡単に利用できるか考えてみる。

結論はgithubに反映した。
https://github.com/yamamo-to/jetson_nano/tree/master/opencv4_compile

# ターゲット言語はPython

C/C++からは単にリンクすれば利用できるので、これからメインで使うであろうPythonから呼び出すことを考える。すなわち

```python
import cv2
print(cv2.__version__)
```
で4.1.0が返ってくるような、最も簡単な方法を探ろうという試みである。

# ディスク容量

## コンパイル時に必要なディスク容量

コンパイル環境はDockerイメージだけで1GBある。

```bash:ターミナル
docyamamo-to@jetson-nano:~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
opencv4_build       latest              9710cbd87862        21 hours ago        1.01GB
ubuntu              18.04               9f020c638543        4 weeks ago         63.3MB
```

さらにソースコードとビルド後のサイズ含めると追加で6.7GBである。

```bash:ターミナル
yamamo-to@jetson-nano:~/Documents/jetson_nano/opencv4_compile/home$ du -s -h build
6.7G	build
```

つまり、コンパイルには最低約8GB必要である。

## 実行時に必要なディスク容量

一方で実行環境と言えば

```bash:ターミナル
yamamo-to@jetson-nano:~/Documents/jetson_nano/opencv4_compile/home$ du -s -h dist/
199M	dist/
```

たったの約200MBである。これからやりたいことは、この200MBだけをDockerの環境から持ち出して、普通の環境でOpenCV 4.1.0を使えるようにしたい、ということである。

# Pythonモジュールの仕組み

例えばPythonで書かれたソースコードと同じディレクトリに`hoge`のようなディレクトリがあり、その中に空のファイル`__init__.py`が存在すると`import hoge`を行うことができる。同じディレクトリでなくともサーチパス(sys.pathで見られる)が通っていれば探してくれる。

```bash:Jetpack4.2(OpenCV3.1.1)
yamamo-to@jetson-nano:~$ python3
Python 3.6.8 (default, Jan 14 2019, 11:02:34) 
[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload', '/home/yamamo-to/.local/lib/python3.6/site-packages', '/usr/local/lib/python3.6/dist-packages', '/usr/lib/python3/dist-packages', '/usr/lib/python3.6/dist-packages']
>>> 
```

## OpenCVのモジュール名

Docker内でインストールされたパスがどこにあるのかを確認する。

```bash:Dockerコンテナ内(OpenCV4.1.0)
foo@e6ba62eb25b4:~$ python3
Python 3.6.8 (default, Jan 14 2019, 11:02:34) 
[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> cv2.__file__
'/usr/local/lib/python3.6/dist-packages/cv2/python-3.6/cv2.cpython-36m-aarch64-linux-gnu.so'
>>> 
```

この結果から`/usr/local/lib/python3.6/dist-packages`がサーチパスに含まれていれば良いことが分かる。

またJetPack4.2のOpenCVをPythonから呼び出すためにインストールする`python3-opencv`は次の通りである。

```bash:Jetpack4.2(OpenCV3.1.1)
yamamo-to@jetson-nano:~$ apt-file -F show python3-opencv
python3-opencv: /usr/lib/python3/dist-packages/cv2.cpython-36m-aarch64-linux-gnu.so
python3-opencv: /usr/share/doc/python3-opencv/changelog.Debian.gz
python3-opencv: /usr/share/doc/python3-opencv/copyright
```

ファイル名`cv2.cpython-36m-aarch64-linux-gnu.so`は全く同じでディレクトリが/usr/local/libか/usr/libの違いだけである。サーチパスの優先順位の高いところにOpenCV 4.1.0のモジュールが呼ばれるように配置すれば良さそうだ。

# サーチパスの追加

/usr/local以下を/opt/opencv-4.1.0にコピーした上で、PYTHONPATHにパスを通して呼び出せるか試してみる。

```bash:ターミナル
yamamo-to@jetson-nano:~/Documents/jetson_nano/opencv4_compile$ sudo cp -a  home/dist/usr/local /opt/opencv-4.1.0
export PYTHONPATH=/opt/opencv-4.1.0/lib/python3.6/dist-packages:$PYTHONPATH
```

実行すると

```bash:ターミナル
$ pipenv run python3
Python 3.6.7 (default, Oct 22 2018, 11:32:17) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/opencv-4.1.0/lib/python3.6/dist-packages/cv2/__init__.py", line 89, in <module>
    bootstrap()
  File "/opt/opencv-4.1.0/lib/python3.6/dist-packages/cv2/__init__.py", line 79, in bootstrap
    import cv2
ImportError: libopencv_hdf.so.4.1: cannot open shared object file: No such file or directory
>>> 
```
`libopencv_hdf.so.4.1`が見つからないというエラーがでた。このファイル自身は`/opt/opencv-4.1.0/lib/libopencv_hdf.so.4.1`にあるので、これはLD_LIBRARY_PATHを通せば良い。

```bash:ターミナル
export LD_LIBRARY_PATH=/opt/opencv-4.1.0/lib:$LD_LIBRARY_PATH
```

では改めて。

```bash:ターミナル
$ pipenv run python3
Python 3.6.7 (default, Oct 22 2018, 11:32:17) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> cv2.__version__
'4.1.0'
>>> 
```

呼び出すことに成功した！それならば最初からインストール先が/usr/localではなく/opt/opencv-4.1.0となるように`CMAKE_INSTALL_PREFIX`を変更すれば良さそうだ。

# 依存ライブラリ

opencvが呼び出せることと、呼び出したプログラムが正常に動くことは別である。依存ライブラリがない場合、それを必要としたライブラリが読み込まれた時点でエラーとなる。少なくとも今回/opt/opencv-4.1.0で作成したパッケージを使うためには、下記のライブラリ群がインストールされていることが必要である。

```bash:ターミナル
sudo apt install cuda-cublas-10-0 cuda-cufft-10-0 cuda-npp-10-0 libavcodec57 libavformat57 libdc1394-22 libglib2.0-dev libgstreamer-plugins-base1.0-0 libgtk2.0-0 libhdf5-100 libjpeg-turbo8 libswscale4
```

# SquashFS

存在を忘れつつあったSquashFSでディレクトリごと圧縮してファイル一つにする。

```bash:ターミナル
mksquashfs /opt/opencv-4.1.0 $HOME/opencv-4.1.0.sfs -b 1048576 -comp xz -Xdict-size 100%
```

ファイルサイズは

```bash:ターミナル
yamamo-to@jetson-nano:~/Documents/jetson_nano/opencv4_compile$ ls -lh home/opencv-4.1.0.sfs
-rw-r--r-- 1 yamamo-to yamamo-to 29M  6月 18 07:10 home/opencv-4.1.0.sfs
```

29MBになった。これを/opt/opencv-4.1.0にマウントすれば使えるようになる。

# まとめ

Dockerでコンパイルして作成されたディレクトリを圧縮した29MBのファイル`opencv-4.1.0.sfs`を持ち出して`/opt/opencv-4.1.0`にマウントする。

```bash
sudo mkdir /opt/opencv-4.1.0
sudo mount -o loop opencv-4.1.0.sfs /opt/opencv-4.1.0
```

次に環境変数`PYTHONPATH`と`LD_LIBRARY_PATH`を設定する。

```bash
export PYTHONPATH=/opt/opencv-4.1.0/lib/python3.6/dist-packages:$PYTHONPATH
export LD_LIBRARY_PATH=/opt/opencv-4.1.0/lib:$LD_LIBRARY_PATH
```

aptで関連ライブラリをインストールする。

```bash:ターミナル
sudo apt install libatlas3-base libhdf5-100
```

これでOpenCV 4.1.0が動作するようになる。