1.环境

操作系统:Ubuntu18.04

GPU:Nvidia GeForce RTX 2080TI

2.安装2080TI驱动

请参考文章(158条消息) NVIDIA-GPU 驱动程序安装_洪流之源的博客-CSDN博客

3.安装cuda

请参考文章(158条消息) CUDA安装与卸载_洪流之源的博客-CSDN博客

4.安装cudnn

请参考文章(158条消息) cuDNN安装_洪流之源的博客-CSDN博客

5.安装nvidia-video-codec-sdk

nvidia-video-codec-sdk下载链接如下:

https://developer.nvidia.com/nvidia-video-codec-sdk/download

下载得到Video_Codec_SDK_12.0.16.zip的压缩包,解压后的目录有Read_Me.pdf文档,文档中有该版本SDK对驱动与CUDA最低版本的要求,比如12.0版本要求如下:

如果驱动版本或者CUDA不满足要求,可更新版本或者下载更低版本的nvidia-video-codec-sdk。

接下来安装nvidia-video-codec-sdk,其实在GPU驱动安装过程中,已经将nvidai-video-codec-sdk的库文件进行了安装,一般安装在/usr/lib/x86_64-linux-gnu/目录下,比如525.89.02版本的GPU驱动安装后,在/usr/lib/x86_64-linux-gnu/目录下存在libnvcuvid.so.525.89.02、libnvidia-encode.so.525.89.02的库文件。因此只需要安装头文件即可,如下命令将头文件拷贝至cuda/目录:

cp Video_Codec_SDK_12.0.16/Interface/* /usr/local/cuda/include/

注:上述只用了nvidia-video-codec-sdk中的头文件,而没有使用nvidia-video-codec-sdk中的libnvcuvid.so、libnvidia-encode.so库,原因是在安装显卡驱动的时候会默认安装与驱动版本兼容的libnvcuvid.so、libnvidia-encode.so,而nvidia-video-codec-sdk中的库很可能与我们安装的显卡驱动版本不一致,如果使用了nvidia-video-codec-sdk中的libnvcuvid.so、ibnvidia-encode.so编译的时候,可能不会有问题,但是运行时很可能会因为与驱动版本不兼容而报错,因为,拒绝使用nvidia-video-codec-sdk中的libnvcuvid.so、ibnvidia-encode.so库。这个可谓是Nvidia的天坑,一定要注意。

6.编译FFmpeg

opencv硬解码依赖nvidia-video-codec-sdk,如果不安装ffmpeg也不会影响opencv的硬解码,但是opencv软解码依赖ffmpeg,如果未安装ffmpeg的话,opencv无法进行软解码,因此为了保证opencv既能硬解码也能软件,接下来也安装ffmpeg,并且提供了ffmpeg英伟达硬解码的编译方式进行安装,这样ffmpeg也可通过Nvidia GPU进行硬解码。

安装依赖

sudo apt updatesudo apt install autoconf \automake \build-essential \cmake \git-core \libass-dev \libfreetype6-dev \libgnutls28-dev \libsdl2-dev \libtool \libva-dev \libvdpau-dev \libvorbis-dev \libxcb1-dev \libxcb-shm0-dev \libxcb-xfixes0-dev \pkg-config \texinfo \wget \yasm \zlib1g-dev

注意:

如果要在docker中编译ffmpeg nvidia硬解码,需要将在安装显卡驱动的时候安装的libnvcuvid.so、libnvidia-encode.so库,从宿主机拷贝到docker中,这两个库在宿主机的路径一般在/usr/lib/x86_64-linux-gnu/目录下,可提前将上述两个库拷贝至docker中,然后拷贝到docker的/lib64目录下,(一定要从宿主机目录进行拷贝,不要使用Video_Codec_SDK中的库,因为Video_Codec_SDK中的库很可能与本机安装的驱动不匹配,即便编译通过,但是运行时会出现驱动不兼容的问题)比如两个库是libnvcuvid.so.525.89.02、libnvidia-encode.so.525.89.02,在docker中操作如下:

cp libnvcuvid.so.525.89.02 /lib64/cp libnvidia-encode.so.525.89.02 /lib64/ln -s /lib64/libnvcuvid.so.525.89.02 /lib64/libnvcuvid.so.1ln -s /lib64/libnvidia-encode.so.525.89.02 /lib64/libnvidia-encode.so.1echo '/lib64' >> /etc/ld.so.confldconfig

下载FFMPEG

考虑到opencv4.7.0开始支持ffmpeg5.x版本,因此下载了ffmpeg 5.1版本:

git clone https://github.com/FFmpeg/FFmpeg.git -b release/5.1

克隆ffnvcodec

ffnvcodec是ffmpeg英伟达硬解码的头文件,需要下载

git clone https://git.videolan.org/git/ffmpeg/nv-codec-headers.git

下载的nv-codec-header目录下有README的文件,内容如下:

这个文件中指明了当前的nv-codec-header需要与Video Code SDK 12.0.6匹配(Video Code SDK即为上述下载的nvidia-video-codec-sdk,上述下载的也为12.0.6版本,满足要求),以及需要的最低驱动版本,如果驱动版本不满足要求,可更新驱动版本。

安装 ffnvcodec:

cd nv-codec-headers && sudo make install

编译ffmpeg脚本如下:

#!/bin/bash./configure --enable-nonfree --enable-cuda-nvcc --enable-libnpp --extra-cflags=-I/usr/local/cuda/include --extra-ldflags=-L/usr/local/cuda/lib64 --disable-static --enable-sharedmake -j$(nproc)sudo make installecho '/usr/local/ffmpeg/lib' >> /etc/ld.so.confldconfig

测试ffmpeg硬解码支持:

分别执行如下两条命令,查看ffmpeg硬件访问与cuvid解码器:

ffmpeg -hwaccelsffmpeg -codecs | grep cuvid

如下:

测试硬解码,执行如下命令:

ffmpeg -y -vsync 0 -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 5 -i 1.mp4 -c:a copy -c:v h264_nvenc -b:v 5M output.mp4

在执行上述命令的过程中,出现如下报错:

不清楚是什么原因导致的,但是好在依然能够正常解码,如果有知道原因的同学,可留言告知。

7.编译OpenCV

opencv的编译请参考文章(163条消息) Ubuntu20.04 编译opencv-4.5.0与opencv-contrib-4.5.0_洪流之源的博客-CSDN博客,中的1、2、3步骤。

假定你已经按照上述文章,执行了1、2、3步骤(注意opencv的源码都要换成4.7.0版本),接下来进行如下步骤:

安装Qt依赖

后续cmake配置命令开启了WITH_OPENGL选项,但是单纯的开启这个选项并不能真正的进行OPENGL的编译,需要安装Qt,因为OPENGL的编译依赖Qt,如果系统没有安装Qt,即便开启了OPENGL编译选项,因为找不到Qt,也不会进行OPENGL的编译,Qt的安装比较简单,如果安装Qt5,可直接执行如下命令:

sudo apt install qt5-default

配置cmake

因为Nvidia GPU是2080TI,需要设置CUDA_ARCH_BIN=7.5,如果是其它型号的GPU可在如下链接进行查询:

https://developer.nvidia.com/zh-cn/cuda-gpus#compute

配置命令如下:

mkdir buildcd buildcmake -D CMAKE_BUILD_TYPE=RELEASE \-D CMAKE_INSTALL_PREFIX=install \-D WITH_TBB=ON \-D BUILD_TBB=ON\-D ENABLE_FAST_MATH=1 \-D CUDA_FAST_MATH=1 \-D WITH_CUBLAS=1 \-D WITH_V4L=ON \-D WITH_LIBV4L=ON \-D WITH_CUDA=ON \-D WITH_CUDNN=ON \-D WITH_CUDEV=ON \-D WITH_GTK_2_X=ON \-D WITH_NVCUVID=ON \-D CUDA_ARCH_BIN=7.5 \-D OPENCV_EXTRA_MODULES_PATH=../opencv_contrib-4.7.0/modules \-D WITH_QT=ON \-D WITH_OPENGL=ON \-D WITH_FFMPEG=ON \..

配置完成后,注意如下红框配置选项是否为“YES”,否则可能之前的依赖选项有问题,需要重新安装依赖,重新配置:

opencv是否支持Nvidia GPU硬解码,注意如下红框选项一定要是“YES”,并且NVIDIA CUDA选项括号中必须包含NVCUVID NVCUENV,否则编译出的opencv用Nvidia GPU硬解码时会报错:

注:如果NVDIA CUDA不包含NVCUVID NVCUENV的选项,考虑是不是没有把libnvcuvid.so、libnvidia-encode.so追加到库的搜索路径路径下,一般docker环境中会出现这种问题,可从宿主机/usr/lib/x86_64-linux-gnu目录下拷贝上述的库(一定要从宿主机目录进行拷贝,不要使用Video_Codec_SDK中的库,因为Video_Codec_SDK中的库很可能与本机安装的驱动不匹配,即便编译通过,但是运行时会出现驱动不兼容的问题),比如libnvcuvid.so.525.89.02、libnvidia-encode.so.525.89.02拷贝到docker中的/usr/lib/x86_64-linux-gnu目录下,并创建软连接,创建软连接脚本如下:

#!/bin/bashsopath=/usr/lib/x86_64-linux-gnuif [ ! -L ${sopath}/libcuda.so ]; thenfiles=(`find $sopath/libcuda.so*`)raw_so=${files[0]}echo Create soft link ${raw_so}ln -s ${raw_so} ${sopath}/libcuda.sofiif [ ! -L ${sopath}/libnvcuvid.so ]; thenecho Create soft link ${sopath}/libnvcuvid.so.1ln -s ${sopath}/libnvcuvid.so.1 ${sopath}/libnvcuvid.sofiif [ ! -L ${sopath}/libnvidia-encode.so ]; thenecho Create soft link ${sopath}/libnvidia-encode.so.1ln -s ${sopath}/libnvidia-encode.so.1 ${sopath}/libnvidia-encode.sofildconfig

配置完成后,分别执行如下命令编译、安装

make -j$(nproc)make install

8. 测试opencv 硬解码与软件

代码如下:

#include #include #include int gpu_test() {cv::cuda::printCudaDeviceInfo(cv::cuda::getDevice());int count = cv::cuda::getCudaEnabledDeviceCount();printf("GPU Device Count : %d \n", count);const std::string filename = "videos/1.mp4";// const std::string filename = "rtsp://172.17.0.1:554/1.mp4";cv::Ptr reader = cv::cudacodec::createVideoReader(filename);cv::cuda::GpuMat gpu_frame;int frame_id = 0;std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();while (reader->nextFrame(gpu_frame)) {frame_id = frame_id + 1;}std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();std::chrono::duration time_useds = std::chrono::duration_cast<std::chrono::duration>(end - start);double time_ms = time_useds.count() * 1000.0f;double fps = double(frame_id) / time_ms * 1000.0f;printf("GPU test took time: %f ms, frames: %d , FPS: %f\n", time_ms, frame_id, fps);reader.release();return 0;}int cpu_test(){const std::string filename = "videos/1.mp4";// const std::string filename = "rtsp://172.17.0.1:554/1.mp4";cv::VideoCapture capture;capture.open(filename);if (!capture.isOpened()){printf("Open video failed !!! \n");return -1;}int width = (int)capture.get(cv::CAP_PROP_FRAME_WIDTH);int height = (int)capture.get(cv::CAP_PROP_FRAME_HEIGHT);printf("src video width: %d , %d \n", width, height);cv::Mat frame;int frame_id = 0;std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();while (capture.read(frame)){frame_id = frame_id + 1;}std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();std::chrono::duration time_useds = std::chrono::duration_cast<std::chrono::duration>(end - start);double time_ms = time_useds.count() * 1000.0f;double fps = double(frame_id) / time_ms * 1000.0f;printf("CPU test took time: %f ms, frames: %d , FPS: %f\n", time_ms, frame_id, fps);capture.release();return 0;}int main(int argc, char const *argv[]){gpu_test();cpu_test();return 0;}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.4)project(MoveDetection)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fPIC -O3 -std=c++11")include_directories(opencv/include/opencv4)link_directories( opencv/lib)set(OPENCV_LIBSopencv_coreopencv_highguiopencv_imgprocopencv_imgcodecsopencv_videoopencv_videoioopencv_cudaimgprocopencv_cudacodecopencv_cudafiltersopencv_cudabgsegmopencv_cudaarithmopencv_cudawarpingopencv_gapi)add_executable(prosrc/main.cpp)target_link_libraries(pro${OPENCV_LIBS})

测试结果:

1920×1080分辨率的视频,GPU解码帧率是1565FPS,CPU解码帧率是441FPS,GPU解码效率是CPU的4倍。