この記事はOpenCV Advent Calendar 2024の3日目の記事です。
はじめに
OpenCVの一部モジュールはNVIDIA製ライブラリを用いることができ、CMakeオプションによって有効・無効などを制御することができます。一応、OpenCV公式ドキュメントにも情報があるのですが、一部の情報しか載っていません。そのため、本記事ではOpenCVのNVIDIA製ライブラリ関連CMakeオプションについて詳しく解説します。
※本記事の内容はOpenCV 4.10.0時点のソースコードをもとに書いています。
NVIDIA製ライブラリ関連CMakeオプション
OpenCVにおけるNVIDIA製ライブラリ関連CMakeオプションを下表にまとめます。以降でこのうちいくつかピックアップして詳しく解説します。
CMakeオプション | 意味 |
---|---|
BUILD_CUDA_STUBS | OpenCVのcudaモジュールをCUDA SDKなしの環境でビルドするためのスタブをビルドするか |
ENABLE_CUDA_FIRST_CLASS_LANGUAGE | CUDA Toolkit検出時にCMakeのenable_language(CUDA) を使うかどうか |
OPENCV_DNN_CUDA | OpenCVのdnnモジュールでCUDA実装を有効にするか |
OPENCV_CMAKE_CUDA_DEBUG | OpenCVにおけるCUDA関連のCMake処理のログを詳細に表示する(OpenCV開発者向け) |
WITH_CUDA | OpenCVでCUDA実装を有効にするか |
WITH_CUDNN | OpenCVでcuDNN実装を有効にする |
WITH_CUFFT | OpenCVでcuFFT実装を有効にするか |
WITH_CUBLAS | OpenCVでcuBLAS実装を有効にするか |
WITH_NVCUVID | OpenCVでNVIDIA Video Decoder(NVCUVID)実装を有効にするか |
WITH_NVCUVENC | OpenCVでNVIDIA Video Codec SDK実装を有効にするか |
CUDA_GENERATION | どのGPU世代向けのバイナリを生成するか |
CUDA_ARCH_PTX | ターゲットのPTXアーキテクチャを指定 |
CUDA_ARCH_BIN | ターゲットのGPUアーキテクチャを指定 |
CUDA_ARCH_FEATURES | CUDA featureを指定 |
CMAKE_CUDA_ARCHITECTURES | どのNVIDIA GPUアーキテクチャ向けにビルドするかを指定 |
ENABLE_CUDA_FIRST_CLASS_LANGUAGE
ENABLE_CUDA_FIRST_CLASS_LANGUAGE=ON
の場合、OpenCVビルド時にCUDAを参照する際にenable_language(CUDA)
が使われるようになります。CMakeのenable_language(CUDA)
について詳細を知りたい方はhttps://developer.download.nvidia.com/video/gputechconf/gtc/2019/presentation/s9444-build-systems-exploring-modern-cmake-cuda-v2.pdfを読むとよいでしょう。
このオプションが出来た経緯
このオプションができた経緯について少し補足します。CMakeドキュメントのhttps://cmake.org/cmake/help/v3.31/module/FindCUDA.htmlに以下の記述があります。
It is no longer necessary to use this module or call
find_package(CUDA)
for compiling CUDA code. Instead, list CUDA among the languages named in the top-level call to the project() command, or call the enable_language() command withCUDA
. Then one can add CUDA (.cu
) sources directly to targets similar to other languages.Added in version 3.17: To find and use the CUDA toolkit libraries manually, use the FindCUDAToolkit module instead. It works regardless of the CUDA language being enabled.
従来、CMakeでCUDA Toolkitを検出する際、find_package(CUDA)
が使われていましたが、
Deprecated since version 3.10: Do not use this module in new code.
とあり、この記法はCMake 3.10以降で推奨されていません。そのため、https://cliutils.gitlab.io/modern-cmake/chapters/packages/CUDA.htmlにあるように
enable_language(CUDA)
などに移行することが推奨されています。このような背景からENABLE_CUDA_FIRST_CLASS_LANGUAGE
が追加されています。
ENABLE_CUDA_FIRST_CLASS_LANGUAGEについてもう少し掘り下げる
OpenCV内部でどうなっているかを知るためにhttps://github.com/opencv/opencv/blob/4.10.0/cmake/OpenCVFindLibsPerf.cmake#L41-L54を読んでみます。
# --- CUDA ---
if(WITH_CUDA)
if(ENABLE_CUDA_FIRST_CLASS_LANGUAGE)
include("${OpenCV_SOURCE_DIR}/cmake/OpenCVDetectCUDALanguage.cmake")
else()
include("${OpenCV_SOURCE_DIR}/cmake/OpenCVDetectCUDA.cmake")
endif()
if(NOT HAVE_CUDA)
message(WARNING "OpenCV is not able to find/configure CUDA SDK (required by WITH_CUDA).
CUDA support will be disabled in OpenCV build.
To eliminate this warning remove WITH_CUDA=ON CMake configuration option.
")
endif()
endif(WITH_CUDA)
上記ファイルを読むとわかるようにENABLE_CUDA_FIRST_CLASS_LANGUAGE
の値に応じてCMakeファイルを読み分けていることがわかります。
条件 | CMakeファイル |
---|---|
ENABLE_CUDA_FIRST_CLASS_LANGUAGE=OFF | cmake/OpenCVDetectCUDA.cmake |
ENABLE_CUDA_FIRST_CLASS_LANGUAGE=ON | cmake/OpenCVDetectCUDALanguage.cmake |
CUDA_GENERATION
CUDA_GENERATION
オプションによってどの世代のNVIDIA GPU向けにビルドするかを指定することができます。例えば、筆者が持っているGeforce RTX 2080 Ti(Turing世代)だと以下のような指定になります。
-D CUDA_GENERATION=Turing
CUDA_GENERATIONについてもう少し掘り下げる
CUDA_GENERATION
を指定した時のOpenCVのCMake周りの処理をもう少し追ってみましょう。opencv/cmake/OpenCVDetectCUDAUtils.cmakeに以下の記述があります。
macro(ocv_set_cuda_arch_bin_and_ptx nvcc_executable)
ocv_initialize_nvidia_device_generations()
set(__cuda_arch_ptx ${CUDA_ARCH_PTX})
# 中略
elseif(CUDA_GENERATION STREQUAL "Turing")
set(__cuda_arch_bin ${_arch_turing})
# 中略
例えば、CUDA_GENERATION=Turing
の場合、set(__cuda_arch_bin ${_arch_turing})
が実行されます。opencv/cmake/OpenCVDetectCUDAUtils.cmakeでは_arch_turing
の定義は以下のようになっており、結果として__cuda_arch_binに7.5
が設定されます。
set(_arch_turing "7.5")
また、CUDA_GENERATION
にはAuto
を設定することもできます。この場合、どのような挙動になるのでしょうか?挙動を理解するためにopencv/cmake/OpenCVDetectCUDAUtils.cmakeを読んでいきましょう。Autoの場合、ocv_detect_native_cuda_arch
マクロが呼ばれます。
elseif(CUDA_GENERATION STREQUAL "Auto")
ocv_detect_native_cuda_arch(${nvcc_executable} _nvcc_res _nvcc_out)
if(NOT _nvcc_res EQUAL 0)
message(STATUS "CUDA: Automatic detection of CUDA generation failed. Going to build for all known architectures")
else()
string(REGEX MATCHALL "[0-9]+\\.[0-9]" __cuda_arch_bin "${_nvcc_out}")
endif()
ocv_detect_native_cuda_arch
マクロの中身をさらに追っていきます。
macro(ocv_detect_native_cuda_arch nvcc_executable status output)
set(OPENCV_CUDA_DETECT_ARCHS_COMMAND "${nvcc_executable}" ${OPENCV_CUDA_DETECTION_NVCC_FLAGS} "${OpenCV_SOURCE_DIR}/cmake/checks/OpenCVDetectCudaArch.cu" "--run")
set(__cache_key_check "${OPENCV_CUDA_DETECT_ARCHS_COMMAND}")
if(DEFINED OPENCV_CACHE_CUDA_ACTIVE_CC AND OPENCV_CACHE_CUDA_ACTIVE_CC_check STREQUAL __cache_key_check)
set(${output} "${OPENCV_CACHE_CUDA_ACTIVE_CC}")
set(${status} 0)
else()
execute_process(
COMMAND ${OPENCV_CUDA_DETECT_ARCHS_COMMAND}
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/"
RESULT_VARIABLE ${status}
OUTPUT_VARIABLE _nvcc_out
ERROR_VARIABLE _nvcc_err
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(OPENCV_CMAKE_CUDA_DEBUG)
message(WARNING "COMMAND: ${OPENCV_CUDA_DETECT_ARCHS_COMMAND}")
message(STATUS "Result: ${${status}}")
message(STATUS "Out: ${_nvcc_out}")
message(STATUS "Err: ${_nvcc_err}")
endif()
string(REGEX REPLACE ".*\n" "" ${output} "${_nvcc_out}") #Strip leading warning messages, if any
if(${status} EQUAL 0)
# cache detected values
set(OPENCV_CACHE_CUDA_ACTIVE_CC ${${output}} CACHE INTERNAL "")
set(OPENCV_CACHE_CUDA_ACTIVE_CC_check "${__cache_key_check}" CACHE INTERNAL "")
endif()
endif()
endmacro()
おおまかには以下のことを行っています。
- nvccでopencv/cmake/checks/OpenCVDetectCudaArch.cuをビルドする
- execute_process()によってビルドしたプログラムを実行する
- プログラムの実行結果をもとに実行環境のGPU情報を取得する
さらに掘り下げるためにopencv/cmake/checks/OpenCVDetectCudaArch.cuを読んでみましょう。
#include <iostream>
#include <sstream>
#include <list>
int main()
{
std::ostringstream arch;
std::list<std::string> archs;
int count = 0;
if (cudaSuccess != cudaGetDeviceCount(&count)){ return -1; }
if (count == 0) { return -1; }
for (int device = 0; device < count; ++device)
{
cudaDeviceProp prop;
if (cudaSuccess != cudaGetDeviceProperties(&prop, device)){ continue; }
arch << prop.major << "." << prop.minor;
archs.push_back(arch.str());
arch.str("");
}
archs.unique(); // Some devices might have the same arch
for (std::list<std::string>::iterator it=archs.begin(); it!=archs.end(); ++it)
std::cout << *it << " ";
return 0;
}
このプログラムではおおまかには以下のようなことをやっています。
- cudaGetDeviceCount()で実行環境のNVIDIA GPU台数をチェックする
- 実行環境のNVIDIA GPUごとにcudaGetDeviceProperties()でプロパティを取得する
- プロパティからアーキテクチャのバージョン情報を取り出す
CUDA_ARCH_PTX、CUDA_ARCH_BIN
-
CUDA_ARCH_PTX
オプションでターゲットのPTXアーキテクチャを指定することができます -
CUDA_ARCH_BIN
オプションでターゲットのGPUアーキテクチャを指定することができます
Compute Capabilityについてはhttps://zenn.dev/eduidl/articles/cuda-comparabilityを参照ください。例えば、筆者が持っているGeforce RTX 2080 Tiを例に挙げると、このGPUのCompute Capabilityは7.5になっています(https://developer.nvidia.com/cuda-gpus参照)。例えば、ターゲットのPTXアーキテクチャ、GPUアーキテクチャともにCompute Capabilityを7.5に指定する場合、以下のようなCMakeオプション指定になります。
-D CUDA_ARCH_PTX=7.5 -D CUDA_ARCH_BIN=7.5
CUDA_ARCH_FEATURES
CUDA_ARCH_FEATURES
オプションでCUDA featureを指定することができます。指定できる値はhttps://cmake.org/cmake/help/v3.31/prop_gbl/CMAKE_CUDA_KNOWN_FEATURES.html#prop_gbl:CMAKE_CUDA_KNOWN_FEATURESを参照してください。CUDA/C++17
の場合、以下のようなCMakeオプション指定になります。
-D CMAKE_CUDA_COMPILE_FEATURES=cuda_std_17
CMakeにおけるCUDA_ARCH_FEATURES
について詳細を知りたい方はあわせてこちらを読むとよいでしょう。
- https://cmake.org/cmake/help/v3.31/variable/CMAKE_CUDA_COMPILE_FEATURES.html#variable:CMAKE_CUDA_COMPILE_FEATURES
- https://cmake.org/cmake/help/v3.31/prop_gbl/CMAKE_CUDA_KNOWN_FEATURES.html#prop_gbl:CMAKE_CUDA_KNOWN_FEATURES
- https://developer.nvidia.com/blog/building-cuda-applications-cmake/
OPENCV_CMAKE_FORCE_CUDA
コンパイル時にOpenCVにおけるCUDA実装のコンパイルを無効化するオプションになっています(FORCE_CUDAという名前から受ける印象とは逆の挙動になっているのが気になります・・・)。
if((NOT UNIX AND CV_CLANG) OR OPENCV_CMAKE_FORCE_CUDA)
message(STATUS "CUDA: Compilation is disabled (due to Clang unsupported on your platform).")
return()
endif()
具体的には以下のCMakeファイルで使用されます。
条件 | CMakeファイル |
---|---|
ENABLE_CUDA_FIRST_CLASS_LANGUAGE=OFF | cmake/OpenCVDetectCUDA.cmake |
ENABLE_CUDA_FIRST_CLASS_LANGUAGE=ON | cmake/OpenCVDetectCUDALanguage.cmake |
CMAKE_CUDA_ARCHITECTURES
CMAKE_CUDA_ARCHITECTURESはCMake本体で定義されているもので、https://cmake.org/cmake/help/v3.31/variable/CMAKE_CUDA_ARCHITECTURES.htmlにあるようにどのNVIDIA GPUアーキテクチャ向けにビルドするかを指定できます。例えば、筆者が持っているGeforce RTX 2080 TiのGPUのCompute Capabilityは7.5になっています(https://developer.nvidia.com/cuda-gpus参照)。このデバイス(Compute Capability 7.5)向けにビルドする場合の指定方法は以下の通りです。この場合、7.5
ではなく、75
となる点に注意が必要です。
-D CMAKE_CUDA_ARCHITECTURES=75
その他
NVCC_FLAGS_EXTRA
NVCC_FLAGS_EXTRA
によってNVCCによるコンパイル時に使用するフラグ追加することができます。
- https://github.com/opencv/opencv/blob/4.10.0/cmake/OpenCVDetectCUDA.cmake#L79
- https://github.com/opencv/opencv/blob/4.10.0/cmake/OpenCVDetectCUDALanguage.cmake#L81
デフォルトだとNVCC_FLAGS_EXTRAの中身は空になっています。
# NVCC flags to be set
set(NVCC_FLAGS_EXTRA "")
例えば、以下のように変更してOpenCVをビルドすることでCUDAカーネルのデバッグができるようになります。
# NVCC flags to be set
set(NVCC_FLAGS_EXTRA "-G -g")
各種ライブラリはOpenCVのどのモジュールで使われているのか?
OpenCVでcuDNN、cuFFT、cuBLASが使われることは知っていても具体的にどのモジュールで使われているかまでは知らないという方が多いのではないでしょうか?ここではどのモジュールのどの処理で使われているかをまとめました。
cuDNN
cuFFT
- cudaarithmモジュールで使用
-
cv::cuda::createDFT
、cv::cuda::createConvolution
でcuBLASが呼び出される- https://github.com/opencv/opencv_contrib/blob/4.10.0/modules/cudaarithm/src/precomp.hpp#L59-L61
- https://github.com/opencv/opencv_contrib/blob/4.10.0/modules/cudaarithm/src/arithm.cpp#L303-L418
- https://github.com/opencv/opencv_contrib/blob/4.10.0/modules/cudaarithm/src/arithm.cpp#L420-L430
- https://github.com/opencv/opencv_contrib/blob/4.10.0/modules/cudaarithm/src/arithm.cpp#L435-L587
- https://github.com/opencv/opencv_contrib/blob/4.10.0/modules/cudaarithm/src/arithm.cpp#L589-L598
-
cuBLAS
- cudaarithmモジュールで使用
- dnnモジュールで使用
NVCUVID
- cudacodecモジュールで使用
NVCUVENC
- cudacodecモジュールで使用
NVIDIA Optical Flow SDK
- cudaoptflowモジュールで使用
注意点
https://github.com/opencv/opencv/blob/4.10.0/cmake/OpenCVDetectCUDAUtils.cmake#L61に以下の説明があります。
Use CMAKE_CUDA_ARCHITECTURES if provided: order of preference CMAKE_CUDA_ARCHITECTURES > CUDA_GENERATION > CUDA_ARCH_BIN and/or CUDA_ARCH_PTX
ただし、この優先順位が守られないバグがhttps://github.com/opencv/opencv/issues/25920で報告されており、https://github.com/opencv/opencv/pull/25941で修正されているようです。
宣伝
関連情報として、OpenCVでのCUDA(GpuMat)活用については筆者のスライドhttps://www.docswell.com/s/fixstars/ZRXQ72-20220805もあわせて参照ください。
おわりに
本記事ではOpenCVのNVIDIA製ライブラリ関連CMakeオプションについて解説しました。
今回の解説でOpenCVをより使いこなしてくれる方が増えると幸いです。
明日は,@ASAKA-219 さんのOpenCVとROS 2で学ぶ画像処理入門です!