最近在做视频镜头切换检测功能,需要用 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.hdynlink_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 了。

archlinux opencv编译_archlinux opencv编译


然后,再去编译执行 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 版本号,则表示正常安装