多起村干部被灭门案,需要反思了!

高铁打人者身份被曝光,人脉资源碾压普通人!却遇到了硬茬

OPPO芯片业务解散不一定是坏事

明确了!任期届满后,他不再寻求连任!

去泰国看了一场“成人秀”,画面尴尬到让人窒息.....

生成图片,分享到微信朋友圈

自由微信安卓APP发布,立即下载! | 提交文章网址
查看原文

flutter小技巧6-10 开发深度学习插件的几个要点 | 第128期

lyu无忆 青衣极客 2022-08-25

       flutter作为一款跨平台的UI框架,让开发者可以只学习这一门技术,就能开发各个平台下的应用程序。一般来说,我们倾向于将UI和业务逻辑分开,特别是计算负荷比较重的逻辑。深度学习就是一种计算负荷重的业务逻辑。对于深度学习类型的应用程序,用flutter开发UI,用C/C++开发深度学习,再在flutter中调用C/C++即可。这种开发框架的关键是解决flutter深度学习插件(plugin)开发的问题。

6. plugin与package的区别是什么

         使用flutter开发应用程序的朋友,想必常常见到plugin和package这两种描述。如果只是使用第三方包,似乎也可以认为这两个描述是同一个含义。但如果要去开发自己的包,就需要再多了解一些。

在flutter的官网上也提供了说明,用于解释plugin与package的区别。

官方描述

          这里使用工具直接翻译成中文。从描述来看, 插件是与平台有关的功能封装,而包是与平台无关的dart程序集合。

         分别创建plugin和package工程,然后比较这两种工程的目录结构。发现package工程只有lib目录存放源码,没有各个平台命名的目录。plugin工程是包含了平台命名的目录,意味着plugin可以针对不同平台进行各自适配。

        经过对plugin和package的对比,终于是明白了官方所说的区别。在自己的项目开发的过程中,如何选择该使用plugin还是package呢?与平台有关的功能使用plugin,使用ffi调用其他语言程序的也使用plugin. 总结起来就是,能全用dart实现的就选package,否则就选plugin。比如接下来,想要在flutter应用中集成一部分C++程序,这就需要用plugin。

7. 怎样调用C++程序?

示例代码: https://github.com/jielyu/flutter_demos/tree/master/d0009_ffi

         近些年推出了很多高级编程语言,它们都有提供调用其他语言程序的能力。dart语言也提供了这种特性,特别是对于C语言的支持比较完备。一方面是由于C语言程序的运行效率极高,另一方面也能够复用已有的代码,提升开发效率。flutter开发者可以使用ffi特性,在自己的应用程序中集成已有的高效C/C++代码。

ffi特性

         官方提供了一个开发ffi的例子,在例子中使用C/C++实现原生的加法,然后在dart层调用。

官方例子

        在C/C++实现层面与一般的C/C++程序开发是一样的,由于flutter本质上,是将C/C++编译成动态链接库。因此在C/C++程序里面,需要按照动态库的方式声明导出的接口函数。

C++导出

        在dart中使用ffi调用C/C++程序时,flutter编译工具链,会自动将编译出来的动态链接库放在合适的位置。因此在程序中只需直接按名字加载动态链接库即可。

         ffi接口开发也是一个专门的知识点,需要使用ffi的朋友可以到官网学习,或者也可以阅读开源例子。

ffi教程

        这里我们直接查看调用效果。首先调用获取版本号的函数,验证程序的基本通路正常。然后调用C/C++计算一个简单的加法,验证程序基本的计算逻辑可以实现。

ffi调用效果

        如果你有大量现成的C/C++程序,或者有加速计算的需求,可以考虑使用ffi。在程序设计上,最好将计算负荷比较重的逻辑与前端UI界面分开。ffi可以很方便地满足这种需求。

8. 怎样调用OpenCV?

示例代码:https://github.com/jielyu/flutter_demos/tree/master/d0010_opencv

        开发图像处理类型应用程序的朋友,可能免不了使用OpenCV。在ios和安卓下使用OpenCV,相对来说还简单一点,因为官方已经打包好了。在macos下稍微麻烦些,需要自己编译源码。

opencv官方支持

       github上有一个项目,提供了在移动端和桌面端五个平台下调用OpenCV的例子。之前验证过ios和安卓上没什么问题,这次来验证一下在macos桌面版的情况。

github开源例子

        制作好兼容x86_64和arm64架构的framework之后,在flutter里面编译报错。老报错x86_64或者arm64链接失败。可能因为测试的电脑是Intel芯片的,改成只编译支持本机架构的目标还是报错。

         在调用framework失败后,就想直接调用brew安装的opencv。因为这种opencv的库在c++里用过很多次,所以剩下的问题是在flutter里配置本地opencv库。

        之前对cocoapods比较陌生,经过一些尝试之后,终于按照一般的本地库方式配置好了。关键就是设置头文件和库文件的路径,并且指定需要用到的库文件名,当然最简单的就是用通配符加载所有库文件。除此之外,由于本地库只包含本机一种CPU架构,因此还需要去掉默认的arm64架构的目标。配置完成之后,编译通过。

cocoapods关键设置

        接下来,运行示例程序查看效果。首先获取opencv的版本号,验证最基本的调用通路和库版本信息。然后就是使用opencv处理一张图片。这里随便加载一张图片,可以看到c/c++程序处理的输出,结果图像是一张边缘图,那段C/C++程序正是实现的这个逻辑。调用OpenCV成功。

OpenCV调用效果

9. 桌面应用怎样调用深度学习模型?

示例代码:https://github.com/jielyu/flutter_demos/tree/master/d0011_libtorch

       桌面版调用pytorch模型一般使用libtorch,在用C++命令行程序成功调用libtorch之后,就想将libtorch集成到flutter项目中,这样就可以在应用程序中玩一些深度学习的模型。

控制台调用成功

        其实使用本地库的方式调用libtorch,与上一次调用opencv本地库是一样的。不过总还是遇到一些不一样的问题。

         首先libtorch库文件一部分用静态库,另一部分用动态库,并且还必须都设置到项目中。否则连编译都通不过。

          然后遇到运行深度学习模型耗时较长,导致整个应用程序卡住。在flutter中遇到这种耗时长的计算逻辑,应当使用一个新的线程去调用,因为必须保证当前的UI线程不被阻塞。这个问题使用flutter提供的compute函数可以轻松解决。

compute轻量级多线程

         在应用程序运行起来之后,还是先查看一下版本号,验证基本调用通路正常。然后是调用C/C++程序处理图像,加载模型,加载图像,预处理图像,运行模型得到结果。例子中是直接将结果写入到本地的文件中,可以看到模型正常将原图转换为简笔画图。使用libtorch调用深度学习模型成功。

LibTorch运行效果

10. ffigen自动生成接口

示例代码:https://github.com/jielyu/flutter_demos/tree/master/d0012_ffigen

        使用ffi可以很方便地调用C/C++程序,但手动逐个编写dart接口却并不容易。一两个接口还好说,在实际开发中,常常有几十上百个接口,而且还可能随时作出修改。这时,最好还是用一款自动化工具生成代码,也就是接下来会说到的 ffigen。

ffigen包

        对于工具来说,需要告诉它输入和输出。这里的输入就是C/C++函数声明,一般在头文件中。输出是将要生成的dart文件路径,一般放在lib目录下的任意子目录中。除此之外,使用这款工具依赖llvm工具解析源码文件,需要提前安装好。至于其他更多的配置项目,看看就行,一般也不需要用到。

ffigen配置

        官方提供了flutter开发常用的几种底层语言的例子,这里暂时只关心C语言接口的生成。C/C++语言开发一般都会将接口声明放在头文件中,也就直接提供了ffigen工具的输入,省去了手动适配输入的麻烦。实例中的配置文件已经添加了ffigen的设置,由此生成的dart文件就是对接口的封装。如果手动编写的话,需要记住类型的对应关系,各种兼容的类型看样子就不是那么好记忆。

ffigen官方生成代码的示例

          自己创建一个flutter例子,练习一下使用ffigen工具生成ffi接口。将之前调用opencv的函数接口搬到这里,之前没有做成头文件是为了方便,实际开发时都会把接口放在头文件中。

        然后参照官方介绍,配置ffigen工具的输入和输出。接着就可以使用指令生成代码了。不过,这里报错了。查看报错信息,发现是头文件的编写不合规范,稍加修改之后再次尝试,就可以正常生成dart文件了。

ffigen报错

        现在的示例中只有这一个开发工具的指令,在实际的项目中可能会有很多其他工具。实践经验表明,具体的指令是很容易忘记的,以至于每次都要查寻使用方法。最好还是把每个具体指令放在脚本中,每次使用时调用脚本就行。如果有必要,还可以在脚本中添加一些注释,说明相关功能和注意事项。可以试试看,应该是方便多了。

        以上就是开发桌面版深度学习插件需要用到的几个要点。更多代码层面的细节,可以根据对应链接查看。


文章有问题?点此查看未经处理的缓存