pyenv環境下でOpenCVのビルドに失敗する

# この記事は何？

pyenv環境下でOpenCVを野良ビルドしようとして`cmake`を実行した際に，

```
--
--   Python 2:
--     Interpreter:                 /home/username/.pyenv/shims/python2.7 (ver 2.7.14)
-- 
--   Python 3:
--     Interpreter:                 /usr/bin/python3.4 (ver 3.4.3)
-- 
--   Python (for build):            /home/username/.pyenv/shims/python2.7
--
```

などと表示されて，`make`してもOpenCVのPython bindings (`cv2.so`など) がビルドされない問題への対処方法．

# 方法

## `libpython*.so`の準備

通常，pyenvでPythonを入れるとstatic library (`libpython*.a`) が導入される．
しかし，static libraryではOpenCVのビルド時に参照できない．

そこで，pyenvでPython環境をインストールする際にshared libraryを導入する．
`pyenv install`の際に，以下のように環境変数を設定してからインストールを行う．

```bash
$ CONFIGURE_OPTS="--enable-shared" pyenv install <version_to_install>
```

`libpython*.so`が導入されているかどうかは，pyenvのインストールディレクトリ以下を見ることで確認できる．

```bash
$ ls $(pyenv root)/versions/3.6.3/lib/
libpython3.6m.so  libpython3.6m.so.1.0  libpython3.so  pkgconfig  python3.6
$ ls $(pyenv root)/versions/2.7.14/lib/
libpython2.7.so  libpython2.7.so.1.0  pkgconfig  python2.7
```

Pythonを入れた後に各バージョンに切り替えてnumpyも入れておく．

## `cmake`時のパス設定

運が良ければ，上記の設定をした後に`cmake`をすれば正しくPython bindingsのビルド設定が行われる．
しかし，私の環境ではビルド設定が正しく行われなかったため，以下のオプションを`cmake`時に追加した．

OpenCVのバージョンは`3.4.4`と`4.0.0`，Pythonのバージョンは`2.7.15`と`3.6.7`で確認しています．

```bash
$ PYTHON3_VERSION="3.6.7"
$ PYTHON2_VERSION="2.7.15"
$ rm CMakeCache.txt
$ cmake .. \
  -Dbuild_opencv_python3=YES -Dbuild_opencv_python2=YES \
  -DPYTHON3_EXECUTABLE="$(pyenv global $PYTHON3_VERSION; pyenv which python)" \
  -DPYTHON3_INCLUDE_DIR="$(pyenv global $PYTHON3_VERSION; python -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())")" \
  -DPYTHON3_NUMPY_INCLUDE_DIRS="$(pyenv global $PYTHON3_VERSION; python -c "import os, numpy.distutils; print(os.pathsep.join(numpy.distutils.misc_util.get_numpy_include_dirs()))")" \
  -DPYTHON3_LIBRARY="$(pyenv global $PYTHON3_VERSION; python -c "import distutils.sysconfig as sysconfig; print(sysconfig.get_config_var('LIBDIR') + '/' + sysconfig.get_config_var('LDLIBRARY'))")" \
  -DPYTHON2_EXECUTABLE="$(pyenv global $PYTHON2_VERSION; pyenv which python)" \
  -DPYTHON2_INCLUDE_DIR="$(pyenv global $PYTHON2_VERSION; python -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())")" \
  -DPYTHON2_NUMPY_INCLUDE_DIRS="$(pyenv global $PYTHON2_VERSION; python -c "import os, numpy.distutils; print(os.pathsep.join(numpy.distutils.misc_util.get_numpy_include_dirs()))")" \
  -DPYTHON2_LIBRARY="$(pyenv global $PYTHON2_VERSION; python -c "import distutils.sysconfig as sysconfig; print(sysconfig.get_config_var('LIBDIR') + '/' + sysconfig.get_config_var('LDLIBRARY'))")"
```

これらのオプションを指定しておくと，`cmake`時のビルド設定が正しく行われる．
(正しく設定されない場合は，キャッシュ`CMakeCache.txt`を消してからもう一度．)

```
--
--   Python 2:
--     Interpreter:                 /home/username/.pyenv/versions/2.7.15/bin/python (ver 2.7.14)
--     Libraries:                   /home/username/.pyenv/versions/2.7.15/lib/libpython2.7.so (ver 2.7.14)
--     numpy:                       /home/username/.pyenv/versions/2.7.15/lib/python2.7/site-packages/numpy/core/include (ver 1.13.3)
--     packages path:               lib/python2.7/site-packages
-- 
--   Python 3:
--     Interpreter:                 /home/username/.pyenv/versions/3.6.7/bin/python (ver 3.6.3)
--     Libraries:                   /home/username/.pyenv/versions/3.6.7/lib/libpython3.6m.so (ver 3.6.3)
--     numpy:                       /home/username/.pyenv/versions/3.6.7/lib/python3.6/site-packages/numpy/core/include (ver 1.14.2)
--     packages path:               lib/python3.6/site-packages
-- 
--   Python (for build):            /home/username/.pyenv/versions/2.7.15/bin/python
--
```

### よくわかる解説

#### `PYTHON3_EXECUTABLE`, `PYTHON2_EXECUTABLE`

このオプションには，Pythonインタプリタのパスを設定すれば良い．
しかし，pyenv環境ではsym link (`~/.pyenv/shims/python`) がPythonインタプリタとしてデフォルトで検出されるようで，インクルードディレクトリやライブラリとの整合性が取れなくなる場合がある．
そこで，Pythonインタプリタの**リンク元の**パスをオプションで指定する．

コマンドの実行結果は以下のようになる．

```bash
$ echo "$(pyenv global 3.6.7; pyenv which python)"
/home/username/.pyenv/versions/3.6.3/bin/python
$ echo "$(pyenv global 2.7.15; pyenv which python)"
/home/username/.pyenv/versions/2.7.14/bin/python
```

#### `PYTHON3_INCLUDE_DIR`, `PYTHON2_INCLUDE_DIR`

このオプションには，Pythonのインクルードパスを指定する．
何も設定せずに`cmake`した場合，システムのPythonのインクルードパスを検知することが多い模様である．

コマンドの実行結果は以下のようになる．

```bash
$ echo "$(pyenv global 3.6.7; python -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())")"
/home/username/.pyenv/versions/3.6.3/include/python3.6m
$ echo "$(pyenv global 2.7.15; python -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())")"
/home/username/.pyenv/versions/2.7.14/include/python2.7
```

#### `PYTHON3_NUMPY_INCLUDE_DIRS`, `PYTHON2_NUMPY_INCLUDE_DIRS`

このオプションには，numpyのインクルードパスを指定する．
何も設定せずに`cmake`した場合，システムのPythonにインストールされているnumpyのインクルードパスを検知することが多い模様である．

コマンドの実行結果は以下のようになる．

```bash
$ echo "$(pyenv global 3.6.7; python -c "import os, numpy.distutils; print(os.pathsep.join(numpy.distutils.misc_util.get_numpy_include_dirs()))")"
/home/username/.pyenv/versions/3.6.3/lib/python3.6/site-packages/numpy/core/include
$ echo "$(pyenv global 2.7.15; python -c "import os, numpy.distutils; print(os.pathsep.join(numpy.distutils.misc_util.get_numpy_include_dirs()))")"
/home/username/.pyenv/versions/2.7.14/lib/python2.7/site-packages/numpy/core/include
```

#### `PYTHON3_LIBRARY`, `PYTHON2_LIBRARY`

このオプションには，Pythonのshared libraryのパスを指定する．
何も設定せずに`cmake`した場合，システムのPythonのshared libraryを検知することが多い模様である．
また，pyenvでPython環境を用意した際に`libpython*.so`が導入されていないと，ここで詰む．

コマンドの実行結果は以下のようになる．

```bash
$ echo "$(pyenv global 3.6.7; python -c "import distutils.sysconfig as sysconfig; print(sysconfig.get_config_var('LIBDIR') + '/' + sysconfig.get_config_var('LDLIBRARY'))")"
/home/username/.pyenv/versions/3.6.3/lib/libpython3.6m.so
$ echo "$(pyenv global 2.7.15; python -c "import distutils.sysconfig as sysconfig; print(sysconfig.get_config_var('LIBDIR') + '/' + sysconfig.get_config_var('LDLIBRARY'))")"
/home/username/.pyenv/versions/2.7.14/lib/libpython2.7.so
```

## `PYTHONPATH`の設定

OpenCVのモジュールパスを環境変数`PYTHONPATH`に登録する．
OpenCVのインクルードパスに依存するが，例えば`~/usr`以下にインストールしている (＝`CMAKE_PREFIX_PATH`を`$HOME/usr`に設定した) 場合には，

```
$ ls ~/usr/lib/python2.7/site-packages/
cv2.so
$ ls ~/usr/lib/python3.6/site-packages/
cv2.cpython-36m-x86_64-linux-gnu.so
```

といった場所にモジュールが配置されている．これらを`PYTHONPATH`に登録する．

Python2系であれば

```bash
export PYTHONPATH="$HOME/usr/lib/python2.7/site-packages:$PYTHONPATH"
```

Python3系であれば

```bash
export PYTHONPATH="$HOME/usr/lib/python3.6/site-packages:$PYTHONPATH"
```

を`~/.profile`等に記述する．

(間違ったバージョンのパスが登録されていると`import`時にエラーを吐く．)

もし，Pythonのバージョンに応じて動的に`PYTHONPATH`を変更したい場合は，

```bash                                                                                                                                                                                 
PYTHON_MAJOR_VERSION=$(python -c "import sys; print(sys.version_info.major)")
if [ $PYTHON_MAJOR_VERSION = "3" ]; then
    export PYTHONPATH="$HOME/usr/lib/python3.6/site-packages:$PYTHONPATH"
elif [ $PYTHON_MAJOR_VERSION = "2" ]; then
    export PYTHONPATH="$HOME/usr/lib/python2.7/site-packages:$PYTHONPATH"
fi
```

のように書いておく．

## 終わりに

OpenCVをビルドするのが日課になってきた．