最近在做视频镜头切换检测功能,需要用 OpenCV 计算某一帧图像的直方图,于是尝试着在 Linux 上编译安装 OpenCV。然而仅用软解码和 CPU 计算着实慢了些,所以就想使用 Cuda 计算来加速,然而调用时却报错说 function/feature is not implement,the called functionality is disabled for current build or platform in function ‘throw_no_cuda’,经过一番努力,总算是解决了这个问题。
Linux Ubuntu 编译安装 OpenCV
源码下载
通过官网链接(https://opencv.org/releases/)即可选择自己希望使用的版本来编译安装,这里,我们选择了 4.6.0,使用 GitHub ssh 链接 clone 到本地。
编译脚本
进入源码目录,看得到有个 CMakeLists.txt,因此使用 CMake 命令进行编译配置。参考网上其他文章,大致命令如下:
cmake . -Bbuild \ # CMake 使用当前目录(.) 下的 CMakeLists.txt 进行编译配置并将配置临时及最终文件放到新建文件夹 build 目录下
-DCMAKE_BUILD_TYPE=RELEASE \ # 使用 release 版本,可能会有一些优化
-DCMAKE_INSTALL_PREFIX=/usr/local \ # 指定安装目录
-DWITH_TBB=ON -DWITH_V4L=ON -DWITH_QT=OFF -DWITH_OPENGL=ON -DWITH_CUDA=ON \ # TBB V4L 是干啥滴,我也不清楚
-DCUDA_GENERATION="Auto" \ # 让编译脚本自动查询 Cuda 架构
-DENABLE_FAST_MATH=1 -DCUDA_FAST_MATH=1 -DCUDA_NVCC_FLAGS="-D_FORCE_INLINES" \
-DOPENCV_GENERATE_PKGCONFIG=1 # 这一句我在使用的时候没加,是用于生成 pc 文件的,以便支持 pkg-config 功能
执行上述命令后,会进行一堆测试,然后出现一个报错
CMake Error at modules/core/CMakeLists.txt:53 (message):
CUDA: OpenCV requires enabled 'cudev' module from 'opencv_contrib'
repository: https://github.com/opencv/opencv_contrib
这意思是,这需要使用到 opencv_contrib 包,需要从上述 github 库中把代码库 opencv_contrib 也克隆到本地,并且通过特定的指令告诉编译脚本这个位置,例如,我们将 opencv_contrib 和 opencv 源码并列放在同一目录下,于是,CMake 命令就变成了如下:
cmake . -Bbuild \ # CMake 使用当前目录(.) 下的 CMakeLists.txt 进行编译配置并将配置临时及最终文件放到新建文件夹 build 目录下
-DCMAKE_BUILD_TYPE=RELEASE \ # 使用 release 版本,可能会有一些优化
-DCMAKE_INSTALL_PREFIX=/usr/local \ # 指定安装目录
-DWITH_TBB=ON -DWITH_V4L=ON -DWITH_QT=OFF -DWITH_OPENGL=ON -DWITH_CUDA=ON \ # TBB V4L 是干啥滴,我也不清楚
-DCUDA_GENERATION="Auto" \ # 让编译脚本自动查询 Cuda 架构
-DENABLE_FAST_MATH=1 -DCUDA_FAST_MATH=1 -DCUDA_NVCC_FLAGS="-D_FORCE_INLINES" \
-DOPENCV_GENERATE_PKGCONFIG=1 # 这一句我在使用的时候没加,是用于生成 pc 文件的,以便支持 pkg-config 功能
-DOPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules
执行之后会有一个清单打印出来,告诉你哪些模块支持了哪些东西。并且在最后会有一个
Configuring done
Genrating done
Build file have been written to: ....
然后进入 build 目录执行 make,并在 make -jn
这里的 n 我用的 41,完成之后执行 sudo make install
即可。n 用其他值也行,取决于你的 cpu 能力,小一些的值编译过程会慢一些,但是会稳定一些。
测试 Demo
参考 Github 工程 https://github.com/sam09/shot-detector 写了个利用 OpenCV 的 Demo 来验证是否能正常使用。
但是最开始编译到最后链接时貌似弹出了找不到 OpenCV 依赖库的错误,理论上我把它 install 到了 /usr/local/ 这个默认搜索路径下,应该是没问题的,但是的确使用 ldconfig -p | grep opencv
的时候,找不到任何结果,通过调用把 /usr/local/lib 这个路径放到 /etc/ld.so.conf.d/usr_local.conf 中之后重新调用 ldconfig,貌似解决了这个问题。
Demo 中有一句 cv::Ptr<cv::cudacodec::VideoReader> reader = cv::cudacodec::createVideoReader(url_);
,在执行时报错如下:
OpenCV(4.6.0) /home/liuyike/Codes/Refs/opencv/modules/core/include/opencv2/core/private.cuda.hpp:112:
error: (-213:The function/feature is not implemented) The called functionality is disabled for current
build or platform in function 'throw_no_cuda'
很奇怪啊,明明 /usr/local/lib 目录下有 libopencv_cudacodec.so 等一堆 cuda 的 OpenCV 库,而且整个 Demo 的编译过程中也需要链接到一些 libopencv_cudaxxx.so,也都没有问题,怎么会在执行报错呢?
按图索骥
通过查看上述报错中提到的 prvate.cuda.hpp:112 行发现
// ...
#ifndef HAVE_CUDA
static inline CV_NORETURN void throw_no_cuda() { CV_Error(cv::Error::GpuNotSupported, "The library is compiled without CUDA support"); }
#else // HAVE_CUDA
#define nppSafeSetStream(oldStream, newStream) { if(oldStream != newStream) { cudaStreamSynchronize(oldStream); nppSetStream(newStream); } }
static inline CV_NORETURN void throw_no_cuda() { CV_Error(cv::Error::StsNotImplemented, "The called functionality is disabled for current build or platform"); }
// ...
它抛出的是第二个错误,The called functionality ...
,也就是说,HAVE_CUDA
是成立的,那为啥有 CUDA 了,还要报这个错呢?于是我又找到 Demo 源码那句 cv::Ptr<cv::cudacodec::VideoReader> reader = cv::cudacodec::createVideoReader(url_);
在 OpenCV 中的源码实现,但它的具体实现并不在 OpenCV 源码中,而是在与它同目录的 opencv_contrib 目录下:
// opencv_contrib/modules/cudacodec/src/video_reader.cpp
#include "precomp.hpp"
using namespace cv;
using namespace cv::cuda;
using namespace cv::cudacodec;
#ifndef HAVE_NVCUVID
Ptr<VideoReader> cv::cudacodec::createVideoReader(const String&, const std::vector<int>&, const VideoReaderInitParams) { throw_no_cuda(); return Ptr<VideoReader>(); }
Ptr<VideoReader> cv::cudacodec::createVideoReader(const Ptr<RawVideoSource>&, const VideoReaderInitParams) { throw_no_cuda(); return Ptr<VideoReader>(); }
#else // HAVE_NVCUVID
这就不难发现了,如果 HAVE_NVCUVID
是 0,或者未定义过,那么就会抛出 no cuda 那个异常了。
那么,HAVE_NVCUVID
为啥未定义呢?全局搜索 HAVE_NVCUVID
发现,在 opencv/CMakeLists.txt 第 266 行写着:
OCV_OPTION(WITH_NVCUVID "Include NVidia Video Decoding library support" OFF # disabled, details: https://github.com/opencv/opencv/issues/14850
VISIBLE_IF WITH_CUDA
VERIFY HAVE_NVCUVID)
OCV_OPTION(WITH_EIGEN "Include Eigen2/Eigen3 support" (NOT CV_DISABLE_OPTIMIZATION AND NOT CMAKE_CROSSCOMPILING)
这意思是说,WITH_NVCUVID
这个选项,默认是 OFF
的,那是谁把它 disable 了的呢?参见后面那个 Github 上的问题链接,貌似是有人发现编译后会有个问题,于是就把这个 默认选项置为 OFF 了。我们发现其他几个选项,例如 WITH_CUFFT
WITH_CUBLAS
WITH_CUDNN
的默认选项都是 WITH_CUDA
,也就是说,只要你设置了 -DWITH_CUDA=ON
,那么这些 cuda 相关的选项都会默认为 ON
,唯独这个 WITH_NVCUVID
。当然也有网友在那个问题链接下面吐槽为啥会合并了这样一个 mr。不过,我们需要做的是,给最开始的 CMake 命令里加上 -DWITH_NVCUVID=ON
这个参数。
这样,CMake 命令就变成了:
cmake . -Bbuild \ # CMake 使用当前目录(.) 下的 CMakeLists.txt 进行编译配置并将配置临时及最终文件放到新建文件夹 build 目录下
-DCMAKE_BUILD_TYPE=RELEASE \ # 使用 release 版本,可能会有一些优化
-DCMAKE_INSTALL_PREFIX=/usr/local \ # 指定安装目录
-DWITH_TBB=ON -DWITH_V4L=ON -DWITH_QT=OFF -DWITH_OPENGL=ON -DWITH_CUDA=ON \ # TBB V4L 是干啥滴,我也不清楚
-DCUDA_GENERATION="Auto" \ # 让编译脚本自动查询 Cuda 架构
-DENABLE_FAST_MATH=1 -DCUDA_FAST_MATH=1 -DCUDA_NVCC_FLAGS="-D_FORCE_INLINES" \
-DOPENCV_GENERATE_PKGCONFIG=1 # 这一句我在使用的时候没加,是用于生成 pc 文件的,以便支持 pkg-config 功能
-DOPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules \
-DWITH_NVCUVID=ON
然后重新 make,make install,然后重新编译 Demo 执行,发现,报错仍在。问题仍未解决。
继续深究发现,如果全局搜索 WITH_NVCUVID
会发现,在 opencv/cmake/OpenCVDetectCUDA.cmake
这个脚本里第 56 行写着:
if(WITH_NVCUVID)
macro(ocv_cuda_SEARCH_NVCUVID_HEADER _filename _result)
# place header file under CUDA_TOOLKIT_TARGET_DIR or CUDA_TOOLKIT_ROOT_DIR
find_path(_header_result
${_filename}
PATHS "${CUDA_TOOLKIT_TARGET_DIR}" "${CUDA_TOOLKIT_ROOT_DIR}"
ENV CUDA_PATH
ENV CUDA_INC_PATH
PATH_SUFFIXES include
NO_DEFAULT_PATH
)
if("x${_header_result}" STREQUAL "x_header_result-NOTFOUND")
set(${_result} 0)
else()
set(${_result} 1)
endif()
unset(_header_result CACHE)
endmacro()
ocv_cuda_SEARCH_NVCUVID_HEADER("nvcuvid.h" HAVE_NVCUVID_HEADER)
ocv_cuda_SEARCH_NVCUVID_HEADER("dynlink_nvcuvid.h" HAVE_DYNLINK_NVCUVID_HEADER)
find_cuda_helper_libs(nvcuvid)
if(WIN32)
find_cuda_helper_libs(nvcuvenc)
endif()
if(CUDA_nvcuvid_LIBRARY AND (${HAVE_NVCUVID_HEADER} OR ${HAVE_DYNLINK_NVCUVID_HEADER}))
# make sure to have both header and library before enabling
set(HAVE_NVCUVID 1)
endif()
if(CUDA_nvcuvenc_LIBRARY)
set(HAVE_NVCUVENC 1)
endif()
endif()
也就是说,如果我们传入了 -DWITH_NVCUVID
参数之后,它就会通过这些脚本来搜索 nvcuvid.h
和 dynlink_nvcuvid.h
这两个头文件是否存在,并通过 find_cuda_helper_libs(nvcuvid)
来搜索库 libnvcuvid.so 是否存在。我在搜索库目录这句之后打印了一下 log 发现,它并没有找到 nvcuvid.h
,于是通过打印 ${CUDA_TOOLKIT_TARGET_DIR}
变量的值和 ${CUDA_TOOLKIT_ROOT_DIR}
变量的值,找到了它认为的 CUDA toolkit 的目录,然后把位于 Video_Codec_SDK_11.1.5/interface 目录下的 nvcuvid.h 文件拷贝到 CUDA Toolkit 目录下,并在 opencv/CMakeLists.txt 第 1628 行插入以下内容之后:
if(HAVE_CUDA)
status(" NVIDIA GPU arch:" ${OPENCV_CUDA_ARCH_BIN})
status(" NVIDIA PTX archs:" ${OPENCV_CUDA_ARCH_PTX})
if (HAVE_NVCUVID) # 插入内容判断 HAVE_NVCUVID 是否被置为了 1
status(" NVIDIA WITH NVCUVID")
endif()
endif()
endif()
再次执行 OpenCV 的 CMake 命令,发现执行结果中出现了 NVIDIA WITH NVCUVID 字样,说明 HAVE_NVCUVID
被置为 1 了。
然后,再去编译执行 Demo,就能正常运行了,问题解决!
此外,上述提到的 CUDA Toolkit 在 Nvidia 官网中有其具体的安装方法,大致如下:
sudo apt-get autoremove --purge nvidia-*
sudo apt-add-repository ppa:graphics-drivers/ppa
sudo apt-get update
sudo apt-get install nvidia-driver-515
sudo reboot
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-ubuntu1804.pin
sudo mv cuda-ubuntu1804.pin /etc/apt/preferences.d/cuda-repository-pin-600
wget https://developer.download.nvidia.com/compute/cuda/11.7.1/local_installers/cuda-repo-ubuntu1804-11-7-local_11.7.1-515.65.01-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu1804-11-7-local_11.7.1-515.65.01-1_amd64.deb
sudo cp /var/cuda-repo-ubuntu1804-11-7-local/cuda-*-keyring.gpg /usr/share/keyrings/
sudo apt-get update
sudo apt-get -y install cuda
以上命令执行完之后,执行命令
nvidia-smi
若命令能正常执行且不报错,正常显示 NVIDIA-SMI 版本号,则表示正常安装