opencv很早就支持cuda加速,但是一般用于图像处理模块。
在视频读(包含实时视频流)写上,opencv可以使用ffmpeg作为后端进行编解码,通常是cpu软编解。 如果ffmpeg的编译支持gpu硬编解,那么opencv的接口就直接支持硬件编解码了。
1、ffmpeg avcodec库是否支持cuda编解码
1.1、系统库直接支持
如果不想安装一堆依赖软件,可以直接下载 static 版本 下载链接 ffmpeg。
linux下使用ffmpeg库,可能直接使用系统直接安装的libavcodec库(ubuntu下使用 apt install livabcodec-dev),可以直接使用 ffmpeg 工具查看)
执行 ffmpeg -codes | grep cuvid
,输出有
DEV.LS h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (decoders: h264 h264_v4l2m2m h264_qsv h264_cuvid ) (encoders: libx264 libx264rgb h264_nvenc h264_o mx h264_qsv h264_v4l2m2m h264_vaapi nvenc nvenc_h264 )
DEV.L. hevc H.265 / HEVC (High Efficiency Video Coding) (decoders: hevc hevc_qsv hevc_v4l2m2m hevc_cuvid ) (encoders: libx265 nvenc_hevc hevc_nvenc hevc _qsv hevc_v4l2m2m hevc_vaapi )
DEVIL. mjpeg Motion JPEG (decoders: mjpeg mjpeg_cuvid mjpeg_qsv ) (encoders: mjpeg mjpeg_qsv mjpeg_vaapi )
DEV.L. mpeg1video MPEG-1 video (decoders: mpeg1video mpeg1_v4l2m2m mpeg1_cuvid )
DEV.L. mpeg2video MPEG-2 video (decoders: mpeg2video mpegvideo mpeg2_v4l2m2m mpeg2_qsv mpeg2_cuvid ) (encoders: mpeg2video mpeg2_qsv mpeg2_vaapi )
DEV.L. mpeg4 MPEG-4 part 2 (decoders: mpeg4 mpeg4_v4l2m2m mpeg4_cuvid ) (encoders: mpeg4 libxvid mpeg4_omx mpeg4_v4l2m2m )
D.V.L. vc1 SMPTE VC-1 (decoders: vc1 vc1_qsv vc1_v4l2m2m vc1_cuvid )
DEV.L. vp8 On2 VP8 (decoders: vp8 vp8_v4l2m2m libvpx vp8_cuvid vp8_qsv ) (encoders: libvpx vp8_v4l2m2m vp8_vaapi )
DEV.L. vp9 Google VP9 (decoders: vp9 vp9_v4l2m2m libvpx-vp9 vp9_cuvid vp9_qsv ) (encoders: libvpx-vp9 vp9_vaapi vp9_qsv )
可以看到decoder支持解码的编码格式很多;同时,encoder下有nvenc同样支持很多编码格式。
此时linux下系统自带的avcodec是支持cuda编解码加速的,使用是可以直接指定使用硬件加速了,例如
// AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
AVCodec* codec = avcodec_find_encoder_by_name("h264_nvenc");
windows下的支持继续看下一节。
1.2、系统库不支持
当执行 ffmpeg -codes | grep cuvid
,说明系统自带的ffmpeg库不支持硬件加速。就需要自己从源码编译了。
windows下是是否支持硬件加速, win和linux使用源码编译支持硬件加速参看博客 【ffmpeg学习 源代码编译、英伟达硬件加速】 ,也可参考官网说明 【Using FFmpeg with NVIDIA GPU Hardware Acceleration】。
2、直接opencv编译源码支持硬件加速
这里主要针对opencv在cpu版本下 cv::VideoCapture、cv::VideoWriter,使用cuda加速的版本cv::cudacodec::VideoReader
、cv::cudacodec::VideoWriter
。
2.1、编译支持
opencv编译默认开启了 WITH_NVCUVID,但是编译还需要依赖nvidia针对视频编解码提供的 NVIDIA VIDEO CODEC SDK 库,该包含两个硬件加速接口:
- 用于视频编码加速的 NVENCODE API
- 用于视频解码加速的 NVDECODE API(旧称 NVCUVID API)
对不同视频编码格式的加速支持也能在该网页上查看。最新版为Video_Codec_SDK_12.0.16,基本环境要求
- Windows: Driver version 522.25 or higher
- Linux: Driver version 520.56.06 or higher
- CUDA 11.0 or higher Toolkit
系统默认安装的ffmpeg4.4、以及cuda强制使用 Video Codec SDK 8.1及以上版本, 因此如果使用新版本cuad或者 系统直接安装ffmpeg4.4以上,是可以直接使用该加速库的。
linux编译opencv要想支持使用cv::cudacodec::VideoReader
、cv::cudacodec::VideoWriter
,必须先下载 Video Codec SDK,之后将解压后的头文件目\Interface的内内容全部复制到cuda的include目录。
之后opencv编译时,必须出现如下 NVCUVID 内容:
NVIDIA CUDA: YES (ver 11.2, CUFFT CUBLAS NVCUVID FAST_MATH)
NVIDIA GPU arch: 52 61 70 75 86
NVIDIA PTX archs:
cuDNN: YES (ver 8.1.1)
2.2、测试
这里以读取视频即使用解码功能为例,直接给出源代码,
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <numeric>
#include "opencv2/opencv_modules.hpp"
#include <opencv2/core/utility.hpp>
#include <opencv2/core.hpp>
#include <opencv2/core/opengl.hpp>
#include <opencv2/cudacodec.hpp>
#include <opencv2/highgui.hpp>
int main(int argc, const char* argv[])
{
std::cout<<cv::getBuildInformation()<<std::endl;
//将这个流改成你自己的
const std::string fname = "rtmp://192.168.3.100:1935/live/xcp1";
/// 没有使用opengl编译需要关闭
//cv::cuda::setGlDevice();
//cv::cuda::setGlDevice(1);
cv::Mat frame;
cv::VideoCapture reader(fname);
cv::cuda::GpuMat d_frame;
cv::Ptr<cv::cudacodec::VideoReader> d_reader = cv::cudacodec::createVideoReader(fname);
cv::TickMeter tm;
std::vector<double> cpu_times;
std::vector<double> gpu_times;
for (int i = 0;i<100;i++)
{
tm.reset(); tm.start();
if (!reader.read(frame))
break;
tm.stop();
cpu_times.push_back(tm.getTimeMilli());
tm.reset(); tm.start();
if (!d_reader->nextFrame(d_frame))
break;
tm.stop();
gpu_times.push_back(tm.getTimeMilli());
}
if (!cpu_times.empty() && !gpu_times.empty())
{
std::cout << std::endl << "Results:" << std::endl;
std::sort(cpu_times.begin(), cpu_times.end());
std::sort(gpu_times.begin(), gpu_times.end());
double cpu_avg = std::accumulate(cpu_times.begin(), cpu_times.end(), 0.0) / cpu_times.size();
double gpu_avg = std::accumulate(gpu_times.begin(), gpu_times.end(), 0.0) / gpu_times.size();
std::cout << "CPU : Avg : " << cpu_avg << " ms FPS : " << 1000.0 / cpu_avg << std::endl;
std::cout << "GPU : Avg : " << gpu_avg << " ms FPS : " << 1000.0 / gpu_avg << std::endl;
}
return 0;
}
cmake文件内容
cmake_minimum_required(VERSION 3.18)
project(test)
set(CMAKE_BUILD_TYPE "Release")
# opencv
set(OpenCV_DIR "/softwares/opencv/opencv-4.6.0/install/lib/cmake/opencv4/")
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
message(STATUS "Found OpenCV version ${OpenCV_VERSION}")
link_libraries( ${OpenCV_LIBS} )
add_executable( ${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME}
${OpenCV_LIBS}
)
测试时的软硬件环境如下:cpu 11700, rtx 3080,cuda 11.2,cudnn 8.1.1。
2.2.1、测试1280x720, 3964 kb/s, 29.97 fps
Results:
CPU : Avg : 0.728801 ms FPS : 1372.12
GPU : Avg : 0.506997 ms FPS : 1972.4
2.2.2、测试2048x1080, q=2-31, 2785 kb/s, 25 fps
Results:
CPU : Avg : 2.83558 ms FPS : 352.662
GPU : Avg : 0.0964484 ms FPS : 10368.2
两个测试对比,在720p时,区别不明显。当视频分辨率提高到2k时,cpu解码效率明显下降,并且gpu效率是cpu的30倍,差异明显。
这里比较中两个视频后者相对,分辨率提高,但是码率下降了。 cpu从 1372帧骤降到253帧,很正常;但是 gpu反而从1972提高到了10368,简单看有点反常理,应该同时相对降低才对。可能是由于gpu吞吐提升,提高了使用效率。
另外,实际使用是,gpu方式使用cv::cuda::GpuMat
,如果腾挪到cv::Mat
上可能还存在一点耗时,需要根据事情情况选择使用。 另外,图像处理也cuda加速的,例如模块opencv_cudaimgproc
、opencv_cudafeatures2d等。