Flutter 实现手机端 App,如果想利用 AI 模型添加新颖的功能,那么 ncnn 就是一种可考虑的手机端推理模型的框架。
本文即是 Flutter 上使用 ncnn 做模型推理的实践分享。有如下内容:
- ncnn 体验:环境准备、模型转换及测试
- Flutter 项目体验: 本文 demo_ncnn 体验
- Flutter 项目实现
- 创建 FFI plugin,实现 dart 绑定 C 接口
- 创建 App,于 Linux 应用 plugin 做推理
- 适配 App,于 Android 能编译运行
demo_ncnn 代码: https://github.com/ikuokuo/start-flutter/tree/main/demo_ncnn
ncnn 体验
ncnn 环境准备
获取 ncnn 源码,并编译。以下是 Ubuntu 上的步骤:
# demo 用的预编译库,建议与其版本一致
export YYYYMMDD=20230517
git clone -b $YYYYMMDD --depth 1 https://github.com/Tencent/ncnn.git
# Build for Linux
# https://github.com/Tencent/ncnn/wiki/how-to-build#build-for-linux
sudo apt install build-essential git cmake libprotobuf-dev protobuf-compiler libvulkan-dev vulkan-tools libopencv-dev
cd ncnn/
git submodule update --init
mkdir -p build; cd build
# cmake -LAH ..
cmake -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$HOME/ncnn-$YYYYMMDD \
-DNCNN_VULKAN=ON \
-DNCNN_BUILD_EXAMPLES=ON \
-DNCNN_BUILD_TOOLS=ON \
..
make -j$(nproc); make install
配置 ncnn 环境,
# 软链,以便替换
sudo ln -sfT $HOME/ncnn-$YYYYMMDD /usr/local/ncnn
cat <<-EOF >> ~/.bashrc
# ncnn
export NCNN_HOME=/usr/local/ncnn
export PATH=\$NCNN_HOME/bin:\$PATH
EOF
# 测试 tools
ncnnoptimize
测试 YOLOX 推理样例,
# 下载 YOLOX ncnn 模型,解压进工作目录 ncnn/build/examples
# 说明可见 ncnn/examples/yolox.cpp 的注释
# https://github.com/Megvii-BaseDetection/YOLOX/releases/download/0.1.1rc0/yolox_s_ncnn.tar.gz
tar -xzvf yolox_s_ncnn.tar.gz
# 下载 YOLOX 测试图片,拷贝进工作目录 ncnn/build/examples
# https://github.com/Megvii-BaseDetection/YOLOX/blob/main/assets/dog.jpg
# 进入工作目录
cd ncnn/build/examples
# 运行 YOLOX ncnn 样例
./yolox dog.jpg
ncnn 模型转换
上述 YOLOX 推理,用的是已转换好的模型。实际推理某一个模型,得了解如何做转换。
这里还以 YOLOX 模型为例,体验 ncnn 转换、修改、量化模型的过程。步骤依照的 YOLOX/demo/ncnn 的说明。此外,ncnn/tools 下有各类模型转换工具的说明。
Step 1) 下载 YOLOX 模型
- yolox_nano.onnx: YOLOX-Nano ONNX 模型
Step 2) onnx2ncnn 转换模型
# onnx 简化
# https://github.com/daquexian/onnx-simplifier
# pip3 install onnxsim
python3 -m onnxsim yolox_nano.onnx yolox_nano_sim.onnx
# onnx 转换为 ncnn
onnx2ncnn yolox_nano_sim.onnx yolox_nano.param yolox_nano.bin
报错 Unsupported slice step !
可忽略。Focus layer 已经于 demo 的 yolox.cpp
里实现了。
Step 3) 修改 yolox_nano.param
修改 yolox_nano.param
把第一个 Convolution
前的层都删掉,另加个 YoloV5Focus
层,并修改层数值。
修改前:
291 324
Input images 0 1 images
Split splitncnn_input0 1 4 images images_splitncnn_0 images_splitncnn_1 images_splitncnn_2 images_splitncnn_3
Crop 630 1 1 images_splitncnn_3 630 -23309=2,0,0 -23310=2,2147483647,2147483647 -23311=2,1,2
Crop 635 1 1 images_splitncnn_2 635 -23309=2,0,1 -23310=2,2147483647,2147483647 -23311=2,1,2
Crop 640 1 1 images_splitncnn_1 640 -23309=2,1,0 -23310=2,2147483647,2147483647 -23311=2,1,2
Crop 650 1 1 images_splitncnn_0 650 -23309=2,1,1 -23310=2,2147483647,2147483647 -23311=2,1,2
Concat Concat_40 4 1 630 640 635 650 683 0=0
Convolution Conv_41 1 1 683 1177 0=16 1=3 11=3 2=1 12=1 3=1 13=1 4=1 14=1 15=1 16=1 5=1 6=1728
修改后:
286 324
Input images 0 1 images
YoloV5Focus focus 1 1 images 683
注:onnx 简化这里用处不大,合了本来要删除的几个
Crop
层。
Step 4) ncnnoptimize 量化模型
ncnnoptimize
转为 fp16,减少一半权重:
ncnnoptimize yolox_nano.param yolox_nano.bin yolox_nano_fp16.param yolox_nano_fp16.bin 65536
如果量化为 int8,可见 Post Training Quantization Tools。
ncnn 推理实践
修改 ncnn/examples/yolox.cpp
detect_yolox()
里模型路径,重编译后测试:
cd ncnn/build/examples
./yolox dog.jpg
demo_ncnn 体验
demo_ncnn 是本文实践的演示项目,可以运行体验。效果如下:
准备 Flutter 环境
Flutter 请依照官方文档 Get started 进行准备。
准备 demo_ncnn 项目
获取 demo_ncnn 源码,
git clone --depth 1 https://github.com/ikuokuo/start-flutter.git
其中,
demo_ncnn/
: 选择图片进行 ncnn 推理的 Flutter 应用plugins/ncnn_yolox/
: ncnn 推理 yolox 模型的 Flutter FFI 插件
安装依赖,
cd demo_ncnn/
flutter pub get
sudo apt-get install libclang-dev libomp-dev
准备 Linux 预编译库,
解压进 plugins/ncnn_yolox/linux/
。
准备 Android 预编译库,
解压进 plugins/ncnn_yolox/android/
。
确认 ncnn_yolox/src/CMakeLists.txt
里 ncnn_DIR
OpenCV_DIR
的路径正确。
体验 demo_ncnn 项目
运行体验,
cd demo_ncnn/
flutter run
# 或查看设备,-d 指定运行
flutter devices
flutter run -d linux
demo_ncnn 实现
demo_ncnn 实现,分为两部分:
- Flutter FFI 插件:实现 dart 绑定 C 接口
- Flutter App 应用:实现 UI 并应用插件做推理
创建 FFI 插件
# 创建 FFI 插件
flutter create --org dev.flutter -t plugin_ffi --platforms=android,ios,linux ncnn_yolox
cd ncnn_yolox
# 更新 ffigen 版本
# 不然,可能报错 Error: The type 'YoloX' must be 'base', 'final' or 'sealed'
flutter pub outdated
flutter pub upgrade --major-versions
之后,只需在 src/ncnn_yolox.h
里定义 C 接口并实现,然后用 package:ffigen 自动生成 Dart 绑定就可以了。
Step 1) 定义 C 接口
#ifdef __cplusplus
extern "C" {
#endif
FFI_PLUGIN_EXPORT typedef int yolox_err_t;
#define YOLOX_OK 0
#define YOLOX_ERROR -1
FFI_PLUGIN_EXPORT struct YoloX {
const char *model_path; // path to model file
const char *param_path; // path to param file
float nms_thresh; // nms threshold
float conf_thresh; // threshold of bounding box prob
float target_size; // target image size after resize, might use 416 for small model
};
// ncnn::Mat::PixelType
FFI_PLUGIN_EXPORT enum PixelType {
PIXEL_RGB = 1,
PIXEL_BGR = 2,
PIXEL_GRAY = 3,
PIXEL_RGBA