自英特尔资深图形图像软件工程师 郭叶军在LiveVideoStack线上分享的内容,详细介绍了FFmpeg中深度学习模块的历史、现状及未来计划,并针对深度学习模块总体架构与代码实践做详细解析。
文 / 郭叶军
整理 / LiveVideoStack
https://www.livevideostack.cn/video/online-gyj/
很高兴能有机会在LiveVideoStack做有关于FFmpeg深度学习模块相关内容的技术分享。
1
为什么需要在FFmpeg中实现深度学习模块
首先,为什么需要FFmpeg深度学习模块,FFmpeg对输入的码流,首先进行解码,解码后得到一个个的视频帧,以及相应的音频数据等。经过一系列的Filter之后,最后可以进行重新编码或者直接播放出来。这些filter的功能包括传统的图像处理,也包括一些统计功能,比如直方图等。在深度学习的算法出来之后,由于它太有效了,包括很多的算法功能,例如超分辨率、目标识别、人脸检测、目标检测、人脸识别等等,都需要深度学习的算法来实现。所以,我们需要拓展filter的功能,使它可以支持深度学习算法。
当我们将深度学习的算法加入到FFmpeg的Filter之后,Filter就拥有了很多计算机视觉相关的算法。那么它与比如说OpenCV的计算机视觉软件包有什么关系?这个问题我们可以从两个角度来看:在支持深度学习算法之前,FFmpeg与OpenCV是相互配合、相互支持的一种关系。例如在OpenCV中,可能需要先调用FFmpeg进行视频解码,解码完成后回到OpenCV中进行相应的计算机视觉算法应用。此外,在FFmpeg中有一些Filter,这些Filter背后调用的就是OpenCV,通过这种方式来使用OpenCV中的一些算法功能。但是在比较新版本的OpenCV中,接口语言切换成了C++,而在FFmpeg中要求的是C代码和并且调用的是C库的接口, 因此FFmpeg基于OpenCV的Filter就很难用上OpenCV的最新算法功能,所以在这种情况下就很有必要在FFmpeg增加深度学习模块来支持多个Filter应用深度学习算法。
从另外一个角度来看,在之前,对于计算机视觉的算法开发者来说,会在OpenCV中进行算法实现,代码完成后基本不太可能再次在其它应用中重新实现一次,那么这也就造成了很多的计算机视觉算法都只在类似于OpenCV的软件包中完成。而在深度学习模块,算法实现的载体已经从以前的代码变成了模型文件,模型文件可以很容易的分发出去,被各个软件包通过恰当的方式进行加载、执行,从而实现一些深度学习算法的功能。综上所述,无论从什么角度来看,我们都有必要在FFmpeg中增加深度学习相应的模块。在增加深度学习模块之后,对于FFmpeg的开发者来说开发工作会更灵活,速度更快。想象一下,在这之前对于FFmpeg的开发者来说,需要首先调用FFmpeg的API进行解码,解码完成后,再编写代码或者调用第三方库等实现深度学习算法的应用,最后再回到FFmpeg中进行编码。而深度学习模块就相当于将之前用户需要自行开发的深度学习实现相关的部分转移到FFmpeg内部,以Filter的形式对外暴露,因此对于FFmpeg的开发者来说开发变得更加便捷。
2
FFmpeg深度学习模块的发展历史
其实在2018年FFmpeg才开始支持深度学习模块,是经过一个GsoC项目,即谷歌关于鼓励在校学生做实习生的活动,鼓励大家进入各个开源社区做一些代码工作。因为具体的代码实现一般都在7、8月份,所以称为Summer of Code,项目的效果还不错。2018年我主要通过邮件参与讨论深度学习模块如何应用,但在GsoC2018完成之后,发现没人进行接下来的维护。于是在2019年,我基于这一部分做了很多工作,包括贡献了绝大部分的代码。除此之外,大师兄刘歧还带了一个GsoC2019的项目,带领一名北大的学生基于深度学习的模块做了些有关Filter的工作。2019年年底,我成为了深度学习模块的Maintainer。
到了2020年,发生了一个比较大的变化是我们将OpenVINO(英特尔的一个深度学习的推理引擎)加入到了FFmpeg的深度学习模块中,同时也开展了一个GsoC项目,希望将深度学习模块中的一些功能进行优化,能够使用汇编语言做一些速度的提升。希望在2021年看到GsoC2021在FFmpeg深度学习模块中的一些项目进展。
3
FFmpeg深度学习模块总体架构
深入到FFmpeg中看一下深度学习模块的总体架构:FFmpeg是由多个库组成的,上图左边列出了部分:libavcodec是音视频解码相关的、libavdevice是音视频输入输出相关的、libavformat是如何将音视频码流组合到一个文件中、libswscale是对视频帧的一些处理、libswresample是对音频数据的重采样,以及所有util相关的Tool放在libavutil中。与深度学习模块密切相关的是libavfilter模块,因为我们的深度学习模块目前主要是为Filter服务的,所以放在libavfilter模块中。对外,我们的深度学习模块可能会调用TensorFlow的 C动态库,也可以调用OpenVINO的C动态库,也就是说需要提前在系统中安装TensorFlow或者OpenVINO的C库的.so文件、.h文件等等。
对于libavFilter内部,前面提到,我们可以有很多基于深度学习算法的Filter,也可以调用多个深度学习模块,所以中间出现DNN接口层,对Filter以及下面的backend进行解耦。目前系统中有三个Filter与深度学习算法相关。第一个是Super-Resolution的Filter,是在GsoC2018的项目中加入的,支持了两个算法模型:一个是ESPCN,即输入的是一张低分辨率的图片,输出的是高分辨率图片,模型直接进行超分应用;另外一个模型叫做SRCNN,首先将一张低分辨率的图片经过快速放大,变成一张高分辨率但质量较为一般的图片,这张图片经过SRCNN算法之后,分辨率保持不变、图片质量会变得更好,在后面我们会用到SRCNN算法举例进行说明。
第二个Filter叫derain:输入一个RGB格式的图片,调用的模型的算法可以将图片中的雨滴去除掉,这是在GsoC2019完成的。我们分析前面两个Filter就会发现,其实这两个Filter实现的算法功能是在模型文件中体现的,在Filter里面的代码更多的是将FFmpeg的数据结构与模型文件的输入输出进行连接。因此从这个角度来看,我们就没有必要为每一个算法功能实现一个Filter,我们可以做一个比较general的Filter —— dnn_processing。只要调用这个Filter,无论模型文件提供的算法功能是什么,只要将FFmpeg与模型文件执行前后的数据流串通起来,就可以实现任何与图像处理相关算法的支持。
下面一层就是三个backend:TensorFlow backend对外调用TensorFlow的C动态库,OpenVINO backend就会对外调用OpenVINO的C动态库。如果考虑到系统中既没有TensorFlow也没有OpenVINO,我们可以选择转换到Native backend,即相应的代码实现都在FFmpeg代码树中,可以直接拿来用。后面将会以Super-Resolution里面的算法模型为例,通过dnn_processing这个Filter举例说明如何使用这三个不同的后端。
4
FFmpeg深度学习模块接口
目前接口主要分为三块:第一,有一个最基本的函数,输入的是backend type,可选值是Native backend、TensorFlow backend或者OpenVINO backend,根据这个backend会返回一个DNNModule。在DNNModule数据结构中首先要做的就是给出模型文件所在的路径,告知Module加载模型文件,加载完成后会在Filter中进行判断,查询加载的模型输入的信息(包括输入NHWC中的每个维度的数字是什么,formate是float32、RGB还是int8等等)。查到相关信息后再与FFmpeg的数据流进行比较是否能够匹配,如果channel的数据匹配不上,则返回error,如果是其它的一些数据结构的大小有变化的话,可以提前做一些scale的转换,或者做color spaceconvertion的一些变换。当一切都变换好之后,我们就会在为模型设置好输入、输出之后完成执行。当然,用set_input_output来设置模型的输入/输出主要是将输入/输出的变量名字告知Model,方便后续执行时清楚的知道要将FFmpeg的数据填充到模型的哪个地方,以及模型的哪个地方是输出,获取到输出的数据重新放到FFmpeg的pipeline里。然后对每个Filter进行这样的操作,做模型的推理,当所有的码流或者图片执行完毕之后,执行Model即可。以上就是深度学习模块主要接口的使用方法。
5
三种后端应用实例
5.1 重现编译FFmpeg
前面提到深度学习模块有三种后端,分别是OpenVINO、TensorFlow和Native。下面举例说明应该如何应用:首先第一步,TensorFlow backend在默认编译FFmpeg时是非enable的,所以我们需要加入一个选项,告知FFmpeg的build system,需要enable TensorFlow。在此之前我们要在Tensorflow的官方网站下载1.14(建议)版本,解压、安装到系统目录下,包括.h文件和.so文件,然后再在configure进行以上配置,就可以生成支持TensorFlow backend的FFmpeg的可执行程序了。
如果需要用的是Native backend,任何选项都不需要加,因为相应的代码已经直接写在代码树中。
如果是使用OpenVINO backend,则与Tensorflow backend类似,也要加入选项,告知FFmpeg接下来我们要启用OpenVINO backend。一般来说,OpenVINO的默认安装目录不是系统目录,所以需要增加一些额外的cflags,指出OpenVINO的头文件在哪里,用ldflags指出OpenVINO的.so库在哪里。在configure的同时,其内部会写一个最简单的OpenVINO应用程序进行编译并且执行,只有一切都通过,才会启用OpenVINO的后端,因此前面需要加LD_LIBRARY_PATH指出OpenVINO的.so所在的位置。
通过这样的方式,我们就可以得到三种后端,即Tensorflow+backend的后端、Native+backend的后端或者OpenVINO+backend的后端,得出的结果都是一个可执行的程序,叫ffmpeg或者ffplay等等。
5.2 准备SR模型文件
准备好可执行的程序之后,我们就可以准备Super-Resolution的模型文件了。首先我们要来准备TensorFlow格式的模型文件。一开始这个是在vf_sr.c的Filter中写的,也就是说我们需要下载一个第三方的库,运行python脚本,就会生成srcnn.pb。这就是基于Tensorflow格式的模型文件,包括了已经训练好的数据。
在这个基础上,如果我们需要使用Native backend,就需要在我们的FFmpeg/tools/python目录下调用convert.ty这个python脚本,它的输入就是Tensorflow的pb文件srcnn.pb。然后会生成在FFmpeg中自定义的一种新的格式——srcnn.model文件,这个.model文件就可以被Native backend加载和执行,这是我们在FFmpeg中自定义出来的,比较简洁。
如果需要使用OpenVINO后端,其实OpenVINO可以支持多个不同格式,例如Tensorflow格式、通过中间转换的方式支持pyTorch格式,支持ONNX格式等等。OpenVINO的源代码中有一个model-optimizer目录,在这个目录中有若干python脚本。例如mo_tf.py脚本文件就可以将Tensorflow格式的模型文件转换为OpenVINO支持的格式,OpenVINO的模型文件有两个,分别是.xml和.bin文件,我们只需要将这两个模型文件放在同一个目录下即可。
如果大家想重现上述内容,而转换又比较麻烦的话,大家可以在图中最下方的网址中下载相应文件。
5.3 应用SR算法
准备好相应的程序和模型文件后,接下来要执行的FFmpeg的命令行如图所示。
TensorFlow backend命令行:
-i:输入文件是什么。一般来说FFmpeg处理的是视频。在这里为了方便展示,我们输入一个.jpg文件,解码之后就经过若干的Filter。由于我们的模型需要的是YUV格式,我们首先应用了format的Filter进行格式的转换,以确保输出的是YUV格式。然后我们调用scale的Filter,其目的是为了将输入图片的长和宽简单的放大一倍,得到高分辨率低质量的一张图片。这张图片再经过dnn_processing的Filter,就可以变成相同分辨率高质量的图片。在该Filter中的参数部分,我们需要指出后端是Tensorflow,其模型文件是什么,以及模型的输入输出的变量名是什么,才能将FFmpeg的数据结构与模型串联。
如我们需要的是Native backend,只需要改变backend的flag,并改变模型文件的文件名即可。同样的,对于OpenVINO来讲,其它参数都保持不变,只要将OpenVINO的flag传进去,并调整模型文件为srcnn.xml。Filter会根据flag经过dnn interface再调用希望应用的backend,最终完成相应的算法功能。
6
下一步计划
那么接下来我们会有哪些新的实现计划?
第一,我们现在执行的是同步的处理,即给到一个frame,执行完毕后才会返回,这样在有些时候会影响整体效率。因此接下来我们会实现一个异步的执行,即在接收到一个视频帧后,直接将视频帧输入到深度学习模块中,在推理完成之前返回调用,希望能够加快处理的速度。并且在这个基础上,我们就可以支持batch support,也就是每次推理并非一帧一帧进行,而是支持一次多帧的处理,尽可能用上下面硬件的并行处理功能。
除此之外,刚才一直强调的模型的输入输出与FFmpeg的数据结构之间需要做一些恰当的转换。由于模型的输入千变万化,存在很大可能性,而目前只是支持一些比较固定模式的转化,因此希望后续可以加入一些比较灵活的转化。再接下来,因为不同的backend会有自己的特性,需要单独设置flag,那么我们就需要在接口中为每个backend实现其私有的操作选项,从Filter向下传递到相应的backend中。另外,从Filter的角度来讲,其实还有很多事情是可以做的。例如目前增加的Filter只是可以用来对图片进行处理,而没有实现分析的功能,例如检测和识别等这些都是下一步需要做的。甚至包括音频相关的Filter,例如将识别音频转化为文字内容等等。
最后以FFmpeg深度学习模块Maintainer的角色,非常欢迎大家对FFmpeg深度学习模块提出建议或意见,包括增加更多的基于深度学习模块的Filter,欢迎大家一起讨论。
最后为大家推荐一本书籍《OpenCV深度学习应用与性能优化实践》。书中主要讲解了OpenCV深度学习模块中加速相关的内容,很多加速的代码都是由作者实现并贡献出来的,并且将实现时的一些想法都写入了书中,算是比较少见的一手信息。在书籍的最后,我们还介绍了一个完整的人脸活体检测的过程。包括先将人脸检测出来,再确保该检测出来的人脸是一个真实的人,不是视频、图片或者面具等,即所谓的“活体”。确保为活体之后,再进行识别这个人是谁。即从检测到防欺诈再到识别的全过程,整个过程包括源代码都在书中有详细介绍,有需要或者有兴趣的朋友可以了解一下。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net