很早之前接触到了飞桨(PaddlePaddle)以及PaddleDetection工具,被他们的简单易用吸引,同时,这些工具极大降低了训练模型的门槛并减少了所需时间,非常适合新手入门。在很多实际应用场景也有不俗的表现。
在端侧部署方面,Paddle Lite是飞桨产品栈中用于端侧高性能轻量化AI应用部署的推理引擎,给了移动端等场景更多可能。这款引擎允许我们在很多硬件平台上实现轻量化的高效预测,进行一次预测耗时较短,也不需要太多的计算资源。
那么如果我们想开发一款既能在本地进行预测又能在Android和iOS上面有一致体验的App的话,Flutter无疑是一个好选择。其作为开源移动UI框架已然成为跨平台移动开发一大趋势,在开发时可以保留状态进行热重载,内置许多令人眼前一亮的组件和漂亮的动画,同时还能保证性能达到和原生应用一样。也正因为这样,不少公司开始把自己的应用向Flutter迁移,有许多我们耳熟能详的App其实已经是基于Flutter开发。假如你已经对安卓原生开发十分熟悉的话,不妨去试试。
这次我们就基于Flutter来开发一个实时目标检测程序,这也得益于Flutter支持访问iOS和Android上的原生系统功能和系统SDK。
我们项目的GitHub地址:
https://github.com/KernelErr/realtime-object-detector
其中内置了水果识别模型,下载下来就能直接编译体验。约定:
Flutter端:Flutter项目主目录。
Android端:项目的Android子目录,原生安卓。
开发环境
我们在开发的时候环境如下:
Flutter version 1.12.13+hotfix.8
Dart version 2.7.0
Android Studio (version 3.6)
Android toolchain - develop for Android devices (Android SDK version 29.0.3)
Flutter在更新的同时加入了越来越多新的特性,网上一些基于老版本的实现方法已经不太实用,我们需要进行一些修改。
准备模型
Paddle Lite需要通过opt工具生成其支持的轻量化模型,如果你手上已经有PaddleDetection训练出来的模型,那么你需要先在PaddleDetection导出模型,然后通过opt工具进行转换。
如果你有其他框架训练出来的模型,如caffe、tensorflow、onnx等,可以利用X2Paddle来转换。
假设我们已经得到了两个文件:
model.nb - 基于Yolov3 Tiny训练且已经通过opt优化好的模型
label - 模型预测一一对应的标签
我们只需要通过Android Studio创建一个新的Flutter项目,这里我们假设名字是realtime_od。
由于我们使用的是安卓原生代码,所以我们需要在Android端进行开发,而不是Flutter端。我们在Paddle Lite提供的预编译预测库里面下载需要的预编译库,放到Android端的相应文件夹内,和原生安卓的目录类似。之后我们继续在android文件夹内放置模型文件,在realtime_od/android/app/src/main/下面新建assets文件夹,并分别把模型和标签放到models和labels子文件夹内。这时候你的目录结果应该是这样:
我们使用口罩模型作为样例,模型位置是:models/mask/model.nb,标签位置是:labels/mask_label_list。因此你需要在MainActivity里面赋值:
在生成APK的时候,我们的模型会被压缩,所以我们需要修改build.gradle配置文件来禁用assets文件夹的压缩。
如果为了Flutter的支持,给Paddle Lite专门写一套Dart调用代码是工作巨大的,所以我们不妨直接基于官方的Demo进行修改。在Android端,我们直接使用了官方Demo中的代码,并在MainActivity内注册了Channel。
由于Flutter中加入了进程安全机制,我们使用了一个MethodResultWrapper保证在主进程里面返回result。新版Flutter中你需要使用configureFlutterEngine而不是onCreate来注册组件。
让我们来给Flutter提供来自摄像头的实时影像!添加一下Flutter的camera插件,Dart 已经有很多现成的包给我们使用:
同时需要确保项目的最低Android SDK版本在21以上。在官方提供的Demo中,图片输入使用的是Bitmap图片,但是我们从插件得到的格式是android.graphics.ImageFormat.YUV_420_888,在Predictor类的最下面我们进行了相应的转换,转换代码的使用已经联系原作者获得授权。我们在其中使用了RenderScript进行高效计算,避免延迟过高。
大量的工作都花在了Android端上面,下面让我们来Flutter端做些工作。
在main.dart和object_detector.dart里面你可以发现我们调用Android端提供的方法,即loadModel以及detectObject。同时在DrawObjects类里面提供了标注目标的功能,代码相对比较简单,利用得到的预测结果进行画图。
这里使用的是群友提供的口罩模型,label文件里面只有两行,分别是戴口罩和未带口罩。我们在Android 9设备上面用PaddlePaddle官方示例图片测试一下。
从日志上面可以看出Paddle Lite预测的时间是接近700 ms。
更改模型和优化方案
我们是参考群友的解决方案(参考链接里面给出)适配的YOLO v3,主要的修改在Predictor内的模型输入以及MainActivity的初始化。因为官方使用的是其他模型,输入的Shape和我们不一样,我们的是320。
如果你需要使用其他模型,请同步修改输入处的:
以及输出处的:
标注函数处也需要做相应修改,修改main.dart:
实际上我们的模型还不够快,选择合适的模型,可以把预测时间缩短到更短。具体还是看自己的需要,Paddle Lite支持许多主流的模型,大家可以进行选用。
记录的问题包括Flutter开发过程中遇到的和Paddle Lite使用中遇到的:
1. Methods marked with @UiThread must be executed on the main thread.
这是因为Flutter引入了进程安全,不能直接在子进程里面返回result,需要在主进程里面返回,网上现在有很多解决办法,我们的也是来自GitHub。
2. 错误: 不兼容的类型: MainActivity无法转换为FlutterEngine
很可能你看的教程是旧版本,请直接参考官方文档写原生安卓。我们在原生安卓开发的时候指定了v2。
3. Paddle Lite出现库错误
一开始以为是官方的问题,但是自己手动编译一次库就能解决。我已经内置了arm64的无问题的库。
4. 其他问题
官方文档 -> GitHub -> 搜索引擎 -> Paddle Lite官方QQ群(696965088),一般都能解决。:)
参考链接
Paddle Lite Github地址:
https://github.com/PaddlePaddle/Paddle-Lite
Paddle Lite中文文档:
https://paddle-lite.readthedocs.io/zh/latest/index.html
Paddle Lite 安卓端部署:
https://www.ralphlu.top/article/2020/3/4/7.html
Real-Time Object Detection with Flutter, TensorFlow Lite and Yolo -Part 1:
https://blog.francium.tech/real-time-object-detection-on-mobile-with-flutter-tensorflow-lite-and-yolo-android-part-a0042c9b62c6
Writing custom platform-specific code:
https://flutter.dev/docs/development/platform-integration/platform-channels
PaddleDetection地址:
https://github.com/PaddlePaddle/PaddleDetection
飞桨官网地址:
https://www.paddlepaddle.org.cn
飞桨开源框架项目地址:
GitHub: https://github.com/PaddlePaddle/Paddle
Gitee: https://gitee.com/paddlepaddle/Paddle
END