首页
学习
活动
专区
工具
TVP
发布

视频AI应用中的C+插件化实践

本文由优图实验室原创

引子

本文主要介绍了插件机制,特别是C++语言的插件框架设计,并对实际业务中的服务进行了插件化改造的实践。

对于一名开发人员,唯一不变的就是不断变化的需求。尤其是 to B 业务,来自外部客户的定制需求、来自内部的算法策略优化需求等,都容易使得项目变得非常复杂,从而可能导致维护管理耗时耗力又容易出错。

那么,如何改变这一现状呢?既然变化的需求是永远存在的,我们可以想想其他一些成功的软件是怎么设计的。

举个简单的例子:程序员的开发需求各种各样,从开发语言,到编译工具、调试工具、版本管理工具、代码格式对齐工具,甚至是一些如编程时听歌这种个性化需求,真是五花八门;那设计一个IDE时如果全考虑进去变成无数个版本或者是一个超大型软件,必然是不现实的。

那现在非常著名的一些工程是怎么实现的呢?IDE中,著名的Eclipse只有一个支持插件扩展功能的微内核,其他所有功能都是通过插件实现的;编辑器里,我们常用的VS Code同样依赖众多功能丰富的插件;浏览器界,Chrome、Firefox、Safari,无不支持插件扩展其功能。

那么,微内核架构+插件,是否就是我们要的答案?

01

什么是插件?

说到插件(plug-in),很多人可能感到既陌生又熟悉。陌生的地方在于很多人没有开发过插件,更没有设计过插件系统架构;熟悉的地方在于我们日常就在浏览器、编辑器、IDE中使用插件。

插件的好处是可以在不修改核心系统源代码的情况下扩展更多的特性,且插件之间通常不会相互依赖、开发也相对独立,这也简化了软件的开发。一个典型的插件系统架构如下图所示:

▲插件系统架构

02

C++插件系统架构设计

一般而言,构建一个插件系统需要下列要素:

面向接口编程

动态加载插件

插件管理器

一、面向接口编程

在 C++ 中虽然不能直接利用Duck Typing的概念,也没有类似于Java里的 interface,但我们也可以通过使用抽象类来实现接口类。

二、动态加载插件

动态加载插件包含两层含义:

1、在运行时载入插件的动态库(.so/.dll文件);

2、在运行时创建插件对象

对于第一点,C++很容易实现,操作系统提供了运行时载入动态库的API,如Win32下我们可以使用LoadLibrary函数,Linux下我们可以使用dlopen函数。打开动态库后,我们需要找到要调用的函数入口,在Linux中我们用dlsym函数来实现,这里简单介绍一下:

dlsym()函数的原型是

其中,handle是打开动态链接库后返回的指针,symbol是要获取的函数的名称,函数返回是需要获取的函数的地址指针。

不过这里有个细节需要注意:C++由于语言支持函数重载等机制,为了保证每个函数的符号名(symbol)唯一性,C++编译器用了名字修饰(name mangling)的技术来修改编译到库中的函数名,这就导致我们使用dlsym可能找不到要使用的函数入口;而C语言中一般函数名与符号名是相同的。因此,我们一般把需要动态库暴露给外部的函数加上extern "C"的声明,告诉编译器用C编译器的方式编译此函数,不要修改函数的符号名。一般情况下,我们习惯写一个这样的宏,放在需要动态库导出的函数声明前(这里暂不考虑windows平台,windows下还需要加__declspec(dllexport),这里不再赘述):

对于第二点,对主程序(微内核)而言,在代码编写和编译的阶段并不知道有哪些插件需要加载,也不知道插件类名,如何在运行时创建插件对象呢?

Java中我们可以使用反射机制(JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先知道运行对象是谁)来实现,而 C++ 语言特性原生并不支持反射机制,因此需要我们自己实现。在 C++ 中,我们可以通过对象工厂模式+宏定义的方式来实现这个功能。简单来说,对象工厂类提供了创建插件类的方式,而宏定义提供了注册每种类型的对象工厂的方式。前者由于由于dlsym返回的是函数指针,我们无法通过 new 来创建类的示例,但我们可以调用工厂类的 create 函数来创建类实例;后者利用 C/C++ 的宏定义中的##,从而将字符串和变量串接起来,使得能自动拼接出对象工厂的类名,避免了自己手写 switch 函数。

三、插件管理器

03

视频AI应用算法处理服务插件化实践

学习了插件框架后,我们回到实际的业务中,想要把服务做成插件化形式,首先要考虑的是:哪些模块做成核心微服务,而哪些模块做成插件?一般来说,我们首先需要对自己的服务进行足够的抽象化,程序入口及公共不变的核心部分作为微内核,可变的部分作为插件,一些底层公用的部分作为公共函数库。

那我们就来梳理和抽象一下视频AI应用算法处理服务的整个pipeline:

▲抽象化视频AI应用算法处理服务pipeline

1.获取视频流数据

视频流数据在实验室环境获取可能比较简单,比如直接用 OpenCV 提供的 VideoCapture 类,但是在实际工程环境要考虑很多复杂的问题(如不同平台硬解码的加速、RTSP等协议的支持、断线重连机制、buffer缓冲及延时控制、跳帧控制等),我们可以把视频流解码处理做成独立的服务,使用共享内存队列传输视频流数据,算法处理服务只是从共享内存队列读数据。既然是统一的从共享内存队列获取数据,我们可以将这一部分放入微内核模块,做成接口类的公共函数。

2.AI算法处理

AI算法处理包括如人脸检测跟踪、人脸识别、人体ReID、车牌识别等等,一般会对视频帧图像做一些深度学习算法获取 Object,然后再对各个 Object 进行进一步处理,不同算法不尽相同,这里不做展开。

显然,这一部分的逻辑可变化空间非常大,很适合做成插件的形式。

当然,该模块的内部还是有很多共性的部分可以挖掘,比如初始化流程、配置文件的加载方式、处理视频帧和处理 Object 的线程等;这块我们暂时没有考虑做成插件的插件,而是做了一个模板样例,编写插件时可以有参考填充。如果有更好的方式的话也欢迎探讨。

3.结构化数据后处理

AI算法处理完后一般会得到一些结构化数据,我们需要推送到云端或推送到本地业务层处理展示等等。

数据推送一般有些公共的方式,如把数据放入本地一个消息队列,使用 HTTP POST 请求、TCP Socket、UNIX Domain Socket 等方式发送,这一部分我们可以做成公共函数,放入微内核模块来调用。

然而,结构化数据的封装各式各样,有 protobuf、JSON 等等,不同算法的字段内容也不一致,这块消息格式的定义我们可以考虑做成插件来加载。

通过插件化的改造,我们的服务从4个版本收敛到了1个版本+多个插件,版本维护的成本降低很多,也便于新业务开发与分工。

04

总结

视频AI应用的插件化框架里还有诸如配置管理、设备管理等比较琐碎的模块,在此就不做赘述,后续完善后会计划开源此框架。有不足和建议欢迎指正和探讨。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200509A0I60B00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券