Python OpenCVのLinux向けbinaryと動画の関係

# TL;DR

* pip install opencv-pythonでインストールできるLinux向けバイナリパッケージは動画の入出力をサポートしていない
* skvideoで代替できるがインタフェースが若干異なる

## 概要

Debian, Ubuntu等の環境でpythonをvirtualenv環境に入れ、OpenCVが使いたいとなった時に取れる選択肢は限られています。

* virtualenv環境構築時に--system-site-packagesを適用しdebパッケージのpython-opencvを使う
  * debバージョンのpythonにしか適用できない(Debian 9, Ubuntu 16ではpython3用のOpenCVパッケージがない)
  * OpenCVのバージョン自体が2系と古い
* 自力でOpenCVとPythonモジュールをビルドする
  * 一番融通が効く方法ではあるが、手間が非常にかかる
* pip install opencv-pythonでバイナリパッケージを入れる
  * pyenv等で入れた任意のバージョンのPythonに適用できる
  * 使える機能が制限されている

ここではpipで入れる方法と動画ファイル周りの関係について述べます。

# OpenCVのビルド情報

以下の方法でOpenCVがどのような環境でビルドされているかを調べることができます。

```shell-session
$ python -c 'import cv2; print(cv2.getBuildInformation())'
General configuration for OpenCV 3.3.0 =====================================
  Version control:               3.3.0
(中略)
  Video I/O:
    DC1394 1.x:                  NO
    DC1394 2.x:                  NO
    FFMPEG:                      NO
      avcodec:                   NO
      avformat:                  NO
      avutil:                    NO
      swscale:                   NO
      avresample:                NO
    GStreamer:                   NO
    OpenNI:                      NO
    OpenNI PrimeSensor Modules:  NO
    OpenNI2:                     NO
    PvAPI:                       NO
    GigEVisionSDK:               NO
    Aravis SDK:                  NO
    UniCap:                      NO
    UniCap ucil:                 NO
    V4L/V4L2:                    NO/NO
    XIMEA:                       NO
    Xine:                        NO
    Intel Media SDK:             NO
    gPhoto2:                     NO
(以下略)
```

opencv-pythonはこのように動画入出力に関しては完全に無効化された状態でビルドされています。これはbinary moduleを極力同一のファイルで異なるプラットフォーム(distribution, version)に対応させようとした結果なのでしょう。

Ubuntu 16のパッケージ版だと以下のようになっています。

```shell-session
$ python2 -c 'import cv2; print(cv2.getBuildInformation())'
General configuration for OpenCV 2.4.9.1 =====================================
  Version control:               unknown
(中略)
  Video I/O:
    DC1394 1.x:                  NO
    DC1394 2.x:                  YES (ver 2.2.4)
    FFMPEG:                      YES
      codec:                     YES (ver 56.60.100)
      format:                    YES (ver 56.40.101)
      util:                      YES (ver 54.31.100)
      swscale:                   YES (ver 3.1.101)
      gentoo-style:              YES
    GStreamer:                   NO
    OpenNI:                      NO
    OpenNI PrimeSensor Modules:  NO
    PvAPI:                       NO
    GigEVisionSDK:               NO
    UniCap:                      NO
    UniCap ucil:                 NO
    V4L/V4L2:                    Using libv4l (ver 1.10.0)
    XIMEA:                       NO
    Xine:                        NO
(以下略)
```

debパッケージ版ffmpegが対応している範囲であれば一通り動作するようになっています。しかしながらPython2向けのdebパッケージしか存在していないので、Python3で手早く環境を作ろうと思うとwheel binary packageに頼るのが一番お手軽です。

# skvideo.ioで動画ファイルを読む

Linux環境においてcv2.VideoCapture()が動かない、という情報で検索するとだいたいStackOverflowあたりで「skvideo.ioを使え」という情報が出てきます。

skvideo.ioはpip install sk-videoでインストールできるバイナリパッケージです。裏ではffmpeg/avconvを呼び出して処理をするようです。したがって、これらのインストールされていない状態でimportすると警告が出ます。

```py3
$ python3
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import skvideo.io
/opt/p36/lib/python3.6/site-packages/skvideo/__init__.py:306: UserWarning: ffmpeg/ffprobe not found in path: 
  warnings.warn("ffmpeg/ffprobe not found in path: " + str(path), UserWarning)
/opt/p36/lib/python3.6/site-packages/skvideo/__init__.py:356: UserWarning: avconv/avprobe not found in path: 
  warnings.warn("avconv/avprobe not found in path: " + str(path), UserWarning)
>>> 
```

当然ながら、警告だけでなく実際の動画ファイル読み込みもできません。この場合、ffmpeg, libav-toolsをインストールする必要があります。

```shell-session
$ sudo apt-get -y install ffmpeg libav-tools
```

# 使用例

```python3
import cv2
import skvideo.io

fname = 'video.mp4'

# cap = cv2.VideoCapture(fname)
cap = skvideo.io.vreader(fname) # こちらを代わりに使う

# ret, frame = cap.read()
frame = next(cap) # retに相当するものはないが、True/Falseを返すだけなのでそれほど問題ではない
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) # カラーチャンネルの並びがOpenCVデフォルトと異なる点に注意
```

# 類似・関連記事

* [PythonのOpenCVで VideoCapture 関数が動画ファイルを読み込んでくれない](https://qiita.com/gekkoukisi/items/159ab94c56d57934a8ce)
* [OpenCV3.3で動画ファイルのリードができない(Ubuntu16.04)](https://qiita.com/yutakau@github/items/a7ff936989055b092444)