PythonでOpenCV with CUDAを使う方法

## 概要

pip経由でOpenCVをインストールすると使用できないモジュールや機能がありますが、GPU利用もその一つです。公式ドキュメントや参考文献を見ながらOpenCVをC++からビルドしてPythonでGPUを使用できるようにします。


## OpenCV with GPU

OpenCVでGPUを使うことができます。もう少し具体的に言うとOpenCVで用意されているCUDAモジュールを使用することでNVIDIA GPUを使うことができます。他にもOpenCLを使うこともできたり細かい話は色々ありますが詳細は割愛します。


## pipでインストールしたOpenCVには制限がある？
PythonでOpenCVを使う場合、pipを使って下記のコマンドでインストールすると思います。


```shell
pip install opencv-python
```

または

```shell
pip install opencv-contrib-python
```

このようにpip経由でインストールされたOpenCVではCUDAを使用することができません。例えば次のようなCUDAを使用するコードを書くとエラーになります。

```python
import sys
import cv2

### Read source image
img_src = cv2.imread("/image.jpg")

### Run with GPU
img_gpu_src = cv2.cuda_GpuMat()
img_gpu_dst = cv2.cuda_GpuMat()
for i in range(100):
    img_gpu_src.upload(img_src)

> OpenCV(4.5.5) /io/opencv/modules/core/include/opencv2/core/private.cuda.hpp:106: error:
 (-216:No CUDA support) The library is compiled without CUDA support in function 'throw_no_cuda'
```

GPUを使用するにはC++からオプションを指定してビルドしたものを使う必要があります。

## C++からビルドって何の話？

突然C++がでてきて何の話だと思われるかもしれませんので補足です。

OSSであるOpenCVのソースコードは[こちら](https://github.com/opencv/opencv)で見ることが可能で、例えば`dnnモジュール`の中身が知りたければ`opencv/modules/dnn/src/`で見ることができます。ソースコードを見てみると`.cpp`ファイルがたくさんあることがわかります。なのでOpenCVはC++から実行することができます・・・というよりはもともとC++から実行できるものをPythonで実行できるようにビルドしているという方が正確です。

ビルドはCMakeを使用するのですが、その際にCUDAが使えるようにWITH_CUDNNやWITH_CUDAといったオプションをONにする必要があります。

なお、CMakeについては下記を参照ください。

https://atmarkit.itmedia.co.jp/ait/articles/1704/10/news134.html

## OpenCVをビルドする

前置きが長くなりました。それでは実際にビルドしていきましょう。実行環境は`Google Colaboratory`です。オプションについては[こちら](https://docs.opencv.org/4.x/db/d05/tutorial_config_reference.html)に説明がありますが、とりあえずGPUを使うだけであれば下記のソースコードで問題ないと思います。また、注意点としてこのビルドは時間がかかります。


```shell
%cd /content
# opencvおよびopencv_contribをcloneする。
!git clone https://github.com/opencv/opencv
# opencv_contribが不要であればコメントアウト
!git clone https://github.com/opencv/opencv_contrib

# ビルド先のディレクトリの作成
!mkdir /content/build
%cd /content/build
# オプションの指定
!cmake -DOPENCV_EXTRA_MODULES_PATH=/content/opencv_contrib/modules  -DBUILD_SHARED_LIBS=OFF  -DBUILD_TESTS=OFF  -DBUILD_PERF_TESTS=OFF -DBUILD_EXAMPLES=OFF -DWITH_OPENEXR=OFF -DWITH_CUDA=ON -DWITH_CUBLAS=ON -DWITH_CUDNN=ON -DOPENCV_DNN_CUDA=ON /content/opencv
# ビルド
!make -j8 install
```

`OPENCV_EXTRA_MODULES_PATH`ではcontribのパスを指定しています。今回はcontribも含めてビルドしていますが、もしcontribが不要であれば削除して構いません。

ビルド後は`/content/opencv/build/lib/python3`以下に`cv2.cpython-37m-x86_64-linux-gnu.so`が作成されています。MyDriveに保存する場合は次のようにcpコマンドを使えば良いかと思います。

```shell
!cp /content/build/lib/python3/cv2.cpython-37m-x86_64-linux-gnu.so /content/cv2.cpython-37m-x86_64-linux-gnu.so
!mkdir /content/drive/{your_saved_path}/cv2_with_GPU
!cp /content/cv2.cpython-37m-x86_64-linux-gnu.so /content/drive/{your_saved_path}/cv2.cpython-37m-x86_64-linux-gnu.so
```



```shell
!pip uninstall opencv-python -y

# ビルド済みのOpenCV（GPU対応）を呼び出す
!cp "/content/drive/{your_saved_path}/cv2.cpython-37m-x86_64-linux-gnu.so" .
```

ランタイムを再起動して`import`して`getCudaEnabledDeviceCount`の結果を確認して1がでればOKです。また、ビルドしたバージョンは`__version__`で確認できます。

```
import cv2

print(cv2.cuda.getCudaEnabledDeviceCount()) # 結果が1であればOK
cv2.__version__
```

## 参考になりそうな記事一覧
今回は`Google Colaboratory`で実行したので環境構築をスキップできましたがUbuntuやDockerで実行する場合は環境構築が必要です。その際に役に立ちそうな記事を列挙します。

https://doitu.info/blog/448768ab50ca208f7648018a6a4ea9f1ef7b1311

https://qiita.com/Fal318/items/ef1bfe6e69622a563615

https://qiita.com/shinohe/items/d41f77edac27be9cef57

https://qiita.com/orphiria/items/3b8ba0c3e23144d3f4df

https://qiita.com/iwatake2222/items/dd7ea3efe65ac77bf783

## その他参考文献

https://atmarkit.itmedia.co.jp/ait/articles/1608/17/news115_2.html

https://opencv.org/cuda/

http://www.e-em.co.jp/tutorial/chap8.htm

https://developer.nvidia.com/cudnn

https://docs.opencv.org/4.5.5/db/d05/tutorial_config_reference.html

https://learnopencv.com/how-to-use-opencv-dnn-module-with-nvidia-gpu-on-windows/