为什么opencv videowriter这么慢?

use*_*367 2 c++ linux opencv ffmpeg

大家好,stackoverflow 社区,我遇到了一个棘手的问题,我需要您的帮助来了解这里发生的情况。我的程序从视频采集卡(Blackmagic)捕获帧,到目前为止,它工作得很好,同时我用 opencv (cv::imshow)显示捕获的图像,它也工作得很好(但相当浪费CPU)。捕获的图像也应该存储在磁盘上,为此我将捕获的帧(cv::Mat)放在堆栈上,最后将它们与 opencv 异步写入:

cv::VideoWriter videoWriter(path, cv::CAP_FFMPEG, fourcc, fps, *size);
videoWriter.set(cv::VIDEOWRITER_PROP_QUALITY, 100);

int id = metaDataWriter.insertNow(path);

while (this->isRunning) {

    while (!this->stackFrames.empty()) {

        cv:Mat m = this->stackFrames.pop();

        videoWriter << m;
    }
    
}

videoWriter.release();
Run Code Online (Sandbox Code Playgroud)

该代码正在另一个线程中运行,将从外部停止。到目前为止,代码可以正常工作,但有时速度相当慢,这意味着我的堆栈大小增加,我的系统耗尽内存并被操作系统杀死。

目前它正在我的开发系统上运行:

  • Ubuntu 18.04.05
  • OpenCV 4.4.0用Cuda编译
  • 英特尔 i7 10. 代 32GB RAM、GPU Nvidia p620、M.2 SSD

根据编解码器 (fourcc),这会产生较高的 CPU 负载。到目前为止我主要使用“MJPG”、“x264”。有时,甚至 MJPG 也会将 CPU 的一个核心调至 100% 负载,并且我的堆栈会一直升高,直到程序运行完毕。有时,重新启动后,此问题会得到解决,并且负载似乎分布在所有核心上。

关于英特尔文档。对于我的 CPU,它集成了多个编解码器的硬件编码/解码。但我猜 opencv 没有使用它们。Opencv 甚至使用它自己的 ffmpeg,而不是我的系统。这是我的 opencv 构建命令:

cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D WITH_TBB=ON \
-D WITH_CUDA=ON \
-D BUILD_opencv_cudacodec=OFF \
-D ENABLE_FAST_MATH=1 \
-D CUDA_FAST_MATH=1 \
-D WITH_CUBLAS=1 \
-D WITH_V4L=ON \
-D WITH_QT=OFF \
-D WITH_OPENGL=ON \
-D WITH_GSTREAMER=ON \
-D OPENCV_GENERATE_PKGCONFIG=ON \
-D OPENCV_ENABLE_NONFREE=ON \
-D WITH_FFMPEG=1 \
-D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules \
-D WITH_CUDNN=ON \
-D OPENCV_DNN_CUDA=ON \
-D CUDA_ARCH_BIN=6.1 ..
Run Code Online (Sandbox Code Playgroud)

我刚刚开始使用 linux 和 C++ 进行开发,然后才开始使用 Java/Maven,所以 cmake 的使用仍然是一个正在进行的工作,请放轻松。

基本上我的问题是,如何使视频编码/写入速度更快,最多使用硬件加速?或者,如果您认为还有其他可疑之处,请告诉我。

迈克尔·BR

use*_*367 11

-------- 旧 - 查找底部的答案 --------

感谢@Micka 的很多建议,我在路上找到了正确的东西。

使用cudacodec::VideoWriter并不是那么容易,编译后我因为这个错误而无法使用它,而且即使我可以让它运行,部署PC也没有nvidia GPU。

由于我也将使用配备 AMD CPU 的 PC,因此我无法使用 cv::CAP_INTEL_MFX 作为 cv::VideoWriter 的 api-reference 参数。但还有 cv::CAP_OPENCV_MJPEG,它适用于 MJPG 编解码器(并非所有视频容器都受支持,我使用 .avi,遗憾的是 .mkv 不适用于此配置)。如果用户不使用 MJPG 作为编解码器,我使用 cv::CAP_ANY,然后 opencv 决定使用什么。

所以,

cv::VideoWriter videoWriter(path, cv::CAP_OPENCV_MJPEG, fourcc, fps, *size);
Run Code Online (Sandbox Code Playgroud)

即使在我的旧系统上也运行得很好。

不幸的是,我之前从未更改过 api-reference 参数,只是从 ffmpeg 更改为 gstreamer,我在opencv 的文档中只读到了最后一行“cv::CAP_FFMPEG 或 cv::CAP_GSTREAMER”。而且我之前没有看到有一个“eg”...谢谢@Micka让我再读一遍。

PS 对于我的 cv::imshow 性能问题,我从

cv::namedWindow(WINDOW_NAME, cv::WINDOW_NORMAL);
Run Code Online (Sandbox Code Playgroud)

cv::namedWindow(WINDOW_NAME, cv::WINDOW_OPENGL);
Run Code Online (Sandbox Code Playgroud)

显然它使用了 OpenGL,并且做得更好。另外从 cv::Mat 更改为 cv::UMat 可以加快性能,请参见此处

-------------- 编辑更好的解决方案----------------

由于我在某些系统上使用 OpenCV VideoWriter 时仍然遇到问题,因此我一直在寻找另一种解决方案。现在我用 FFMPEG 编写帧。对于 FFMPEG,我可以根据我使用的编解码器使用 GPU 或 CPU。如果 FFMPEG 通过 snapd (Ubuntu 18.04) 安装,则默认启用 cuda:

sudo snap install ffmpeg --devmode
Run Code Online (Sandbox Code Playgroud)

(--devmode 是可选的,但我在特定位置写入文件时遇到问题,这是我修复它的唯一方法)

这是我的代码:

//this string is automatically created in my program, depending on user input and the parameters of the input frames

string ffmpegCommand = "ffmpeg -y -f rawvideo -vcodec rawvideo -framerate 50 -pix_fmt bgr24 -s 1920x1080 -i - -c:v h264_nvenc -crf 14 -maxrate:v 10M -r 50 myVideoFile.mkv";

FILE *pipeout = popen(ffmpegCommand.data(), "w");

int id = metaDataWriter.insertNow(path);

//loop will be stopped from another thread
while (this->isRunning) {
    //this->frames is a stack with cv::Mat elements in the right order
    //it is filled by another thread
    while (!this->frames.empty()) {


        cv::Mat mat = frames.front();
        frames.pop();
        fwrite(mat.data, 1, s, pipeout);

    }  
}

fflush(pipeout);
pclose(pipeout);
Run Code Online (Sandbox Code Playgroud)

因此使用文件(pipeout)将mat.data写入ffmpeg,ffmpeg本身正在进行编码和文件写入。至参数:

-y = 不询问就覆盖输出文件

-f = 格式,在本例中用于输入原始视频

-vcodec = 输入的编解码器也是原始视频,因为使用的 cv::Mat.data 没有压缩/编解码器

-framerate = 我从采集卡/OpenCv 收到的输入帧率

-pix_fmt = 我的原始数据的格式,在本例中为 bgr24,因此每个通道 8 位,因为我使用常规 OpenCV bgr cv::Mat

-s = 每帧的大小,在我的例子中为 1920x1080

-i = 输入,在这种情况下,我们从 stdinput 读取,您可以在这里看到它“-”,因此文件(管道输出)被 ffmpeg 捕获

-c:v = 输出编解码器,所以这是对视频进行编码,这里使用了h264_nvenc,这是一个GPU编解码器

-r = 帧输出速率,在本例中也是 50 myVideoFile.mkv = 这只是 ffmpeg 生成的文件的名称,您可以更改此文件和路径

更高质量的附加参数:-crf 14 -maxrate:v 10M

这对我来说非常有用,并且使用我的 GPU 硬件加速或与负责 CPU 的另一个编解码器一起使用。我希望这也能帮助其他开发人员。