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
![](https://img-blog.csdnimg.cn/img_convert/c4b9e3a675084ea9aefc312e8095fa20.jpeg)
下载得到Video_Codec_SDK_12.0.16.zip的压缩包,解压后的目录有Read_Me.pdf文档,文档中有该版本SDK对驱动与CUDA最低版本的要求,比如12.0版本要求如下:
![](https://img-blog.csdnimg.cn/img_convert/b16477941cad4cb0acceb98010a68243.jpeg)
如果驱动版本或者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 update
sudo 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.1
ln -s /lib64/libnvidia-encode.so.525.89.02 /lib64/libnvidia-encode.so.1
echo '/lib64' >> /etc/ld.so.conf
ldconfig
下载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的文件,内容如下:
![](https://img-blog.csdnimg.cn/img_convert/ffea5dd7bccf42bfbacc683efd21a423.jpeg)
这个文件中指明了当前的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
#!/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-shared
make -j$(nproc)
sudo make install
echo '/usr/local/ffmpeg/lib' >> /etc/ld.so.conf
ldconfig
测试ffmpeg硬解码支持:
分别执行如下两条命令,查看ffmpeg硬件访问与cuvid解码器:
ffmpeg -hwaccels
ffmpeg -codecs | grep cuvid
如下:
![](https://img-blog.csdnimg.cn/img_convert/724ace5c43794374ac3ce7d175dedc46.png)
测试硬解码,执行如下命令:
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
在执行上述命令的过程中,出现如下报错:
![](https://img-blog.csdnimg.cn/img_convert/d4971f250ae8445ba1b79a401420a757.png)
不清楚是什么原因导致的,但是好在依然能够正常解码,如果有知道原因的同学,可留言告知。
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 build
cd build
cmake -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",否则可能之前的依赖选项有问题,需要重新安装依赖,重新配置:
![](https://img-blog.csdnimg.cn/img_convert/0551cb83b46b468f978324a7fcbf82ce.jpeg)
opencv是否支持Nvidia GPU硬解码,注意如下红框选项一定要是“YES”,并且NVIDIA CUDA选项括号中必须包含NVCUVID NVCUENV,否则编译出的opencv用Nvidia GPU硬解码时会报错:
![](https://img-blog.csdnimg.cn/img_convert/38a838824499434495b4e88ed7e2385c.jpeg)
注:如果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/bash
sopath=/usr/lib/x86_64-linux-gnu
if [ ! -L ${sopath}/libcuda.so ]; then
files=(`find $sopath/libcuda.so*`)
raw_so=${files[0]}
echo Create soft link ${raw_so}
ln -s ${raw_so} ${sopath}/libcuda.so
fi
if [ ! -L ${sopath}/libnvcuvid.so ]; then
echo Create soft link ${sopath}/libnvcuvid.so.1
ln -s ${sopath}/libnvcuvid.so.1 ${sopath}/libnvcuvid.so
fi
if [ ! -L ${sopath}/libnvidia-encode.so ]; then
echo Create soft link ${sopath}/libnvidia-encode.so.1
ln -s ${sopath}/libnvidia-encode.so.1 ${sopath}/libnvidia-encode.so
fi
ldconfig
配置完成后,分别执行如下命令编译、安装
make -j$(nproc)
make install
8. 测试opencv 硬解码与软件
代码如下:
#include <opencv2/opencv.hpp>
#include <opencv2/cudacodec.hpp>
#include <chrono>
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<cv::cudacodec::VideoReader> 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<double> time_useds = std::chrono::duration_cast<std::chrono::duration<double>>(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<double> time_useds = std::chrono::duration_cast<std::chrono::duration<double>>(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_LIBS
opencv_core
opencv_highgui
opencv_imgproc
opencv_imgcodecs
opencv_video
opencv_videoio
opencv_cudaimgproc
opencv_cudacodec
opencv_cudafilters
opencv_cudabgsegm
opencv_cudaarithm
opencv_cudawarping
opencv_gapi
)
add_executable(pro src/main.cpp)
target_link_libraries(
pro
${OPENCV_LIBS}
)
测试结果:
![](https://img-blog.csdnimg.cn/img_convert/42e69cbf80004a4d84534b1131c9ce81.jpeg)
1920x1080分辨率的视频,GPU解码帧率是1565FPS,CPU解码帧率是441FPS,GPU解码效率是CPU的4倍。