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 上的步骤:
1# demo 用的预编译库,建议与其版本一致 2export YYYYMMDD=20230517 3git clone -b $YYYYMMDD --depth 1 https://github.com/Tencent/ncnn.git 4 5# Build for Linux 6# https://github.com/Tencent/ncnn/wiki/how-to-build#build-for-linux 7sudo apt install build-essential git cmake libprotobuf-dev protobuf-compiler libvulkan-dev vulkan-tools libopencv-dev 8 9cd ncnn/ 10git submodule update --init 11 12mkdir -p build; cd build 13 14# cmake -LAH .. 15cmake -DCMAKE_BUILD_TYPE=Release \ 16-DCMAKE_INSTALL_PREFIX=$HOME/ncnn-$YYYYMMDD \ 17-DNCNN_VULKAN=ON \ 18-DNCNN_BUILD_EXAMPLES=ON \ 19-DNCNN_BUILD_TOOLS=ON \ 20.. 21 22make -j$(nproc); make install
配置 ncnn 环境,
1# 软链,以便替换 2sudo ln -sfT $HOME/ncnn-$YYYYMMDD /usr/local/ncnn 3 4cat <<-EOF >> ~/.bashrc 5# ncnn 6export NCNN_HOME=/usr/local/ncnn 7export PATH=\$NCNN_HOME/bin:\$PATH 8EOF 9 10# 测试 tools 11ncnnoptimize
测试 YOLOX 推理样例,
1# 下载 YOLOX ncnn 模型,解压进工作目录 ncnn/build/examples 2# 说明可见 ncnn/examples/yolox.cpp 的注释 3# https://github.com/Megvii-BaseDetection/YOLOX/releases/download/0.1.1rc0/yolox_s_ncnn.tar.gz 4tar -xzvf yolox_s_ncnn.tar.gz 5 6# 下载 YOLOX 测试图片,拷贝进工作目录 ncnn/build/examples 7# https://github.com/Megvii-BaseDetection/YOLOX/blob/main/assets/dog.jpg 8 9# 进入工作目录 10cd ncnn/build/examples 11 12# 运行 YOLOX ncnn 样例 13./yolox dog.jpg

ncnn 模型转换
上述 YOLOX 推理,用的是已转换好的模型。实际推理某一个模型,得了解如何做转换。
这里还以 YOLOX 模型为例,体验 ncnn 转换、修改、量化模型的过程。步骤依照的 YOLOX/demo/ncnn 的说明。此外,ncnn/tools 下有各类模型转换工具的说明。
Step 1) 下载 YOLOX 模型
- yolox_nano.onnx: YOLOX-Nano ONNX 模型
Step 2) onnx2ncnn 转换模型
1# onnx 简化 2# https://github.com/daquexian/onnx-simplifier 3# pip3 install onnxsim 4python3 -m onnxsim yolox_nano.onnx yolox_nano_sim.onnx 5 6# onnx 转换为 ncnn 7onnx2ncnn 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 层,并修改层数值。
修改前:
1291 324 2Input images 0 1 images 3Split splitncnn_input0 1 4 images images_splitncnn_0 images_splitncnn_1 images_splitncnn_2 images_splitncnn_3 4Crop 630 1 1 images_splitncnn_3 630 -23309=2,0,0 -23310=2,2147483647,2147483647 -23311=2,1,2 5Crop 635 1 1 images_splitncnn_2 635 -23309=2,0,1 -23310=2,2147483647,2147483647 -23311=2,1,2 6Crop 640 1 1 images_splitncnn_1 640 -23309=2,1,0 -23310=2,2147483647,2147483647 -23311=2,1,2 7Crop 650 1 1 images_splitncnn_0 650 -23309=2,1,1 -23310=2,2147483647,2147483647 -23311=2,1,2 8Concat Concat_40 4 1 630 640 635 650 683 0=0 9Convolution 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
修改后:
1286 324 2Input images 0 1 images 3YoloV5Focus focus 1 1 images 683
注:onnx 简化这里用处不大,合了本来要删除的几个
Crop层。
Step 4) ncnnoptimize 量化模型
ncnnoptimize 转为 fp16,减少一半权重:
1ncnnoptimize 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() 里模型路径,重编译后测试:
1cd ncnn/build/examples 2./yolox dog.jpg

demo_ncnn 体验
demo_ncnn 是本文实践的演示项目,可以运行体验。效果如下:

准备 Flutter 环境
Flutter 请依照官方文档 Get started 进行准备。
准备 demo_ncnn 项目
获取 demo_ncnn 源码,
1git clone --depth 1 https://github.com/ikuokuo/start-flutter.git
其中,
demo_ncnn/: 选择图片进行 ncnn 推理的 Flutter 应用plugins/ncnn_yolox/: ncnn 推理 yolox 模型的 Flutter FFI 插件
安装依赖,
1cd demo_ncnn/ 2 3flutter pub get 4 5sudo 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 项目
运行体验,
1cd demo_ncnn/ 2flutter run 3 4# 或查看设备,-d 指定运行 5flutter devices 6flutter run -d linux
demo_ncnn 实现
demo_ncnn 实现,分为两部分:
- Flutter FFI 插件:实现 dart 绑定 C 接口
- Flutter App 应用:实现 UI 并应用插件做推理
创建 FFI 插件
1# 创建 FFI 插件 2flutter create --org dev.flutter -t plugin_ffi --platforms=android,ios,linux ncnn_yolox 3 4cd ncnn_yolox 5 6# 更新 ffigen 版本 7# 不然,可能报错 Error: The type 'YoloX' must be 'base', 'final' or 'sealed' 8flutter pub outdated 9flutter pub upgrade --major-versions
之后,只需在 src/ncnn_yolox.h 里定义 C 接口并实现,然后用 package:ffigen 自动生成 Dart 绑定就可以了。
Step 1) 定义 C 接口
1#ifdef __cplusplus 2extern "C" { 3#endif 4 5FFI_PLUGIN_EXPORT typedef int yolox_err_t; 6 7#define YOLOX_OK 0 8#define YOLOX_ERROR -1 9 10FFI_PLUGIN_EXPORT struct YoloX { 11 const char *model_path; // path to model file 12 const char *param_path; // path to param file 13 14 float nms_thresh; // nms threshold 15 float conf_thresh; // threshold of bounding box prob 16 float target_size; // target image size after resize, might use 416 for small model 17}; 18 19// ncnn::Mat::PixelType 20FFI_PLUGIN_EXPORT enum PixelType { 21 PIXEL_RGB = 1, 22 PIXEL_BGR = 2, 23 PIXEL_GRAY = 3, 24 PIXEL_RGBA = 4, 25 PIXEL_BGRA = 5, 26}; 27 28FFI_PLUGIN_EXPORT struct Rect { 29 float x; 30 float y; 31 float w; 32 float h; 33}; 34 35FFI_PLUGIN_EXPORT struct Object { 36 int label; 37 float prob; 38 struct Rect rect; 39}; 40 41FFI_PLUGIN_EXPORT struct DetectResult { 42 int object_num; 43 struct Object *object; 44}; 45 46FFI_PLUGIN_EXPORT struct YoloX *yoloxCreate(); 47FFI_PLUGIN_EXPORT void yoloxDestroy(struct YoloX *yolox); 48 49FFI_PLUGIN_EXPORT struct DetectResult *detectResultCreate(); 50FFI_PLUGIN_EXPORT void detectResultDestroy(struct DetectResult *result); 51 52FFI_PLUGIN_EXPORT yolox_err_t detectWithImagePath( 53 struct YoloX *yolox, const char *image_path, struct DetectResult *result); 54FFI_PLUGIN_EXPORT yolox_err_t detectWithPixels( 55 struct YoloX *yolox, const uint8_t *pixels, enum PixelType pixelType, 56 int img_w, int img_h, struct DetectResult *result); 57 58#ifdef __cplusplus 59} 60#endif
Step 2) 实现 C 接口
src/ncnn_yolox.cc 实现参考 ncnn/examples/yolox.cpp 来做的。
Step 3) 更新 Dart 绑定接口
lib/ncnn_yolox_bindings_generated.dart,
1flutter pub run ffigen --config ffigen.yaml
如果要了解 dart 怎么与 C 交互,可见:C interop using dart:ffi。
Step 4) 准备依赖库
- Linux,解压进
linux/- ncnn-YYYYMMDD-ubuntu-2204-shared.zip
- opencv-mobile-4.6.0-ubuntu-2204.zip
- Android,解压进
android/- ncnn-YYYYMMDD-android-vulkan-shared.zip
- opencv-mobile-4.6.0-android.zip
Step 5) 写构建脚本
1# packages 2 3if(CMAKE_SYSTEM_NAME STREQUAL "Linux") 4 set(ncnn_DIR "${MY_PROJ}/linux/ncnn-20230517-ubuntu-2204-shared/lib/cmake") 5 set(OpenCV_DIR "${MY_PROJ}/linux/opencv-mobile-4.6.0-ubuntu-2204/lib/cmake") 6elseif(CMAKE_SYSTEM_NAME STREQUAL "Android") 7 set(ncnn_DIR "${MY_PROJ}/android/ncnn-20230517-android-vulkan-shared/${ANDROID_ABI}/lib/cmake/ncnn") 8 set(OpenCV_DIR "${MY_PROJ}/android/opencv-mobile-4.6.0-android/sdk/native/jni") 9else() 10 message(FATAL_ERROR "system not support: ${CMAKE_SYSTEM_NAME}") 11endif() 12 13if(NOT EXISTS ${ncnn_DIR}) 14 message(FATAL_ERROR "ncnn_DIR not exists: ${ncnn_DIR}") 15endif() 16if(NOT EXISTS ${OpenCV_DIR}) 17 message(FATAL_ERROR "OpenCV_DIR not exists: ${OpenCV_DIR}") 18endif() 19 20## ncnn 21 22find_package(ncnn REQUIRED) 23message(STATUS "ncnn_FOUND: ${ncnn_FOUND}") 24 25## opencv 26 27find_package(OpenCV 4 REQUIRED) 28message(STATUS "OpenCV_VERSION: ${OpenCV_VERSION}") 29message(STATUS "OpenCV_INCLUDE_DIRS: ${OpenCV_INCLUDE_DIRS}") 30message(STATUS "OpenCV_LIBS: ${OpenCV_LIBS}") 31 32# targets 33 34include_directories( 35 ${MY_PROJ}/src 36 ${OpenCV_INCLUDE_DIRS} 37) 38 39## ncnn_yolox 40 41add_library(ncnn_yolox SHARED 42 "ncnn_yolox.cc" 43) 44target_link_libraries(ncnn_yolox ncnn ${OpenCV_LIBS}) 45 46set_target_properties(ncnn_yolox PROPERTIES 47 PUBLIC_HEADER ncnn_yolox.h 48 OUTPUT_NAME "ncnn_yolox" 49) 50 51target_compile_definitions(ncnn_yolox PUBLIC DART_SHARED_LIB)
测试 ncnn 推理
首先,把准备好的模型放进 assets 目录。如:
1assets/ 2├── dog.jpg 3├── yolox_nano_fp16.bin 4└── yolox_nano_fp16.param
之后,于 Linux 可以自测 C & Dart 接口实现。
Step 1) C 接口测试
1std::string assets_dir("../assets/"); 2std::string image_path = assets_dir + "dog.jpg"; 3std::string model_path = assets_dir + "yolox_nano_fp16.bin"; 4std::string param_path = assets_dir + "yolox_nano_fp16.param"; 5 6auto yolox = yoloxCreate(); 7yolox->model_path = model_path.c_str(); 8yolox->param_path = param_path.c_str(); 9yolox->nms_thresh = 0.45; 10yolox->conf_thresh = 0.25; 11yolox->target_size = 416; 12// yolox->target_size = 640; 13 14auto detect_result = detectResultCreate(); 15 16auto err = detectWithImagePath(yolox, image_path.c_str(), detect_result); 17if (err == YOLOX_OK) { 18 auto num = detect_result->object_num; 19 printf("yolox detect ok, num=%d\n", num); 20 for (int i = 0; i < num; i++) { 21 Object *obj = detect_result->object + i; 22 printf(" object[%d] label=%d prob=%.2f rect={x=%.2f y=%.2f w=%.2f h=%.2f}\n", 23 i, obj->label, obj->prob, obj->rect.x, obj->rect.y, obj->rect.w, obj->rect.h); 24 } 25} else { 26 printf("yolox detect fail, err=%d\n", err); 27} 28 29draw_objects(image_path.c_str(), detect_result); 30 31detectResultDestroy(detect_result); 32yoloxDestroy(yolox);
Step 2) Dart 接口测试
1final yoloxLib = NcnnYoloxBindings(dlopen('ncnn_yolox', 'build/shared')); 2 3const assetsDir = '../assets'; 4final imagePath = '$assetsDir/dog.jpg'.toNativeUtf8(); 5final modelPath = '$assetsDir/yolox_nano_fp16.bin'.toNativeUtf8(); 6final paramPath = '$assetsDir/yolox_nano_fp16.param'.toNativeUtf8(); 7 8final yolox = yoloxLib.yoloxCreate(); 9yolox.ref.model_path = modelPath.cast(); 10yolox.ref.param_path = paramPath.cast(); 11yolox.ref.nms_thresh = 0.45; 12yolox.ref.conf_thresh = 0.25; 13yolox.ref.target_size = 416; 14// yolox.ref.target_size = 640; 15 16final detectResult = yoloxLib.detectResultCreate(); 17 18final err = 19 yoloxLib.detectWithImagePath(yolox, imagePath.cast(), detectResult); 20 21if (err == YOLOX_OK) { 22 final num = detectResult.ref.object_num; 23 print('yolox detect ok, num=$num'); 24 for (int i = 0; i < num; i++) { 25 var obj = detectResult.ref.object.elementAt(i).ref; 26 print(' object[$i] label=${obj.label}' 27 ' prob=${obj.prob.toStringAsFixed(2)} rect=${obj.rect.str()}'); 28 } 29} else { 30 print('yolox detect fail, err=$err'); 31} 32 33calloc.free(imagePath); 34calloc.free(modelPath); 35calloc.free(paramPath); 36 37yoloxLib.detectResultDestroy(detectResult); 38yoloxLib.yoloxDestroy(yolox);
Step 3) 运行测试
1cd ncnn_yolox/linux 2make 3 4# cpp test 5./build/ncnn_yolox_test 6 7# dart test 8dart ncnn_yolox_test.dart
创建 App 写 UI
创建 App 项目,
1flutter create --project-name demo_ncnn --org dev.flutter --android-language java --ios-language objc --platforms=android,ios,linux demo_ncnn
本文项目添加了如下些依赖:
1cd demo_ncnn 2 3dart pub add path logging image easy_debounce 4 5flutter pub add mobx flutter_mobx provider path_provider 6flutter pub add -d build_runner mobx_codegen
App 状态管理用的 MobX。若要了解使用,可见:
App 主要就两个功能:选图片、做推理。对应实现了两个 Store 类:
- image_store.dart: 给图片路径,异步加载图片数据
- yolox_store.dart: 给图片数据,异步预测图片对象
因为加载、预测都比较耗时,故用的 MobX ObservableFuture 异步方式。若要了解使用,可见:
以上就是 App 实现的关键内容,也可采取不同方案。
应用插件做推理
App 里应用插件,首先要于 pubspec.yaml 里加上插件的依赖:
1dependencies: 2 ncnn_yolox: 3 path: ../plugins/ncnn_yolox
然后,yolox_store.dart 应用了插件做推理,过程与之前 Dart 接口测试基本一致。差异主要在:
- 多了将
assets里的模型拷贝进临时路径的操作,因为 App 里无法获取资源的绝对路径。要么改 C 接口,模型以字节给到。 - 多了将图片数据从
Uint8List到Pointer<Uint8>的拷贝,因为要从 Dart 堆内存进 C 堆内存。可见注释的 Issue 了解。
1import 'dart:ffi'; 2import 'dart:io'; 3 4import 'package:ffi/ffi.dart'; 5import 'package:flutter/services.dart'; 6import 'package:image/image.dart' as img; 7import 'package:mobx/mobx.dart'; 8 9import 'package:ncnn_yolox/ncnn_yolox_bindings_generated.dart' as yo; 10import 'package:path/path.dart' show join; 11import 'package:path_provider/path_provider.dart'; 12 13import '../util/image.dart'; 14import '../util/log.dart'; 15import 'future_store.dart'; 16 17part 'yolox_store.g.dart'; 18 19class YoloxStore = YoloxBase with _$YoloxStore; 20 21class YoloxObject { 22 int label = 0; 23 double prob = 0; 24 Rect rect = Rect.zero; 25} 26 27class YoloxResult { 28 List<YoloxObject> objects = []; 29 Duration detectTime = Duration.zero; 30} 31 32abstract class YoloxBase with Store { 33 late yo.NcnnYoloxBindings _yolox; 34 35 YoloxBase() { 36 final dylib = Platform.isAndroid || Platform.isLinux 37 ? DynamicLibrary.open('libncnn_yolox.so') 38 : DynamicLibrary.process(); 39 40 _yolox = yo.NcnnYoloxBindings(dylib); 41 } 42 43 44 FutureStore<YoloxResult> detectFuture = FutureStore<YoloxResult>(); 45 46 47 Future detect(ImageData data) async { 48 try { 49 detectFuture.errorMessage = null; 50 51 detectFuture.future = ObservableFuture(_detect(data)); 52 53 detectFuture.data = await detectFuture.future; 54 } catch (e) { 55 detectFuture.errorMessage = e.toString(); 56 } 57 } 58 59 Future<YoloxResult> _detect(ImageData data) async { 60 final timebeg = DateTime.now(); 61 // await Future.delayed(const Duration(seconds: 5)); 62 63 final modelPath = await _copyAssetToLocal('assets/yolox_nano_fp16.bin', 64 package: 'ncnn_yolox', notCopyIfExist: false); 65 final paramPath = await _copyAssetToLocal('assets/yolox_nano_fp16.param', 66 package: 'ncnn_yolox', notCopyIfExist: false); 67 log.info('yolox modelPath=$modelPath'); 68 log.info('yolox paramPath=$paramPath'); 69 70 final modelPathUtf8 = modelPath.toNativeUtf8(); 71 final paramPathUtf8 = paramPath.toNativeUtf8(); 72 73 final yolox = _yolox.yoloxCreate(); 74 yolox.ref.model_path = modelPathUtf8.cast(); 75 yolox.ref.param_path = paramPathUtf8.cast(); 76 yolox.ref.nms_thresh = 0.45; 77 yolox.ref.conf_thresh = 0.45; 78 yolox.ref.target_size = 416; 79 // yolox.ref.target_size = 640; 80 81 final detectResult = _yolox.detectResultCreate(); 82 83 final pixels = data.image.getBytes(order: img.ChannelOrder.bgr); 84 // Pass Uint8List to Pointer<Void> 85 // https://github.com/dart-lang/ffi/issues/27 86 // https://github.com/martin-labanic/camera_preview_ffi_image_processing/blob/master/lib/image_worker.dart 87 final pixelsPtr = calloc.allocate<Uint8>(pixels.length); 88 for (int i = 0; i < pixels.length; i++) { 89 pixelsPtr[i] = pixels[i]; 90 } 91 92 final err = _yolox.detectWithPixels( 93 yolox, 94 pixelsPtr, 95 yo.PixelType.PIXEL_BGR, 96 data.image.width, 97 data.image.height, 98 detectResult); 99 100 final objects = <YoloxObject>[]; 101 if (err == yo.YOLOX_OK) { 102 final num = detectResult.ref.object_num; 103 for (int i = 0; i < num; i++) { 104 final o = detectResult.ref.object.elementAt(i).ref; 105 final obj = YoloxObject(); 106 obj.label = o.label; 107 obj.prob = o.prob; 108 obj.rect = Rect.fromLTWH(o.rect.x, o.rect.y, o.rect.w, o.rect.h); 109 objects.add(obj); 110 } 111 } 112 113 calloc 114 ..free(pixelsPtr) 115 ..free(modelPathUtf8) 116 ..free(paramPathUtf8); 117 118 _yolox.detectResultDestroy(detectResult); 119 _yolox.yoloxDestroy(yolox); 120 121 final result = YoloxResult(); 122 result.objects = objects; 123 result.detectTime = DateTime.now().difference(timebeg); 124 return result; 125 } 126 127 // ... 128}
最后,于 UI home_page.dart 里使用,
1class HomePage extends StatefulWidget { 2 const HomePage({super.key, required this.title}); 3 4 final String title; 5 6 7 State<HomePage> createState() => _HomePageState(); 8} 9 10class _HomePageState extends State<HomePage> { 11 late ImageStore _imageStore; 12 late YoloxStore _yoloxStore; 13 late OptionStore _optionStore; 14 15 16 void didChangeDependencies() { 17 _imageStore = Provider.of<ImageStore>(context); 18 _yoloxStore = Provider.of<YoloxStore>(context); 19 _optionStore = Provider.of<OptionStore>(context); 20 21 _imageStore.load(); 22 23 super.didChangeDependencies(); 24 } 25 26 void _pickImage() async { 27 final result = await FilePicker.platform.pickFiles(type: FileType.image); 28 if (result == null) return; 29 30 final image = result.files.first; 31 _imageStore.load(imagePath: file.path); 32 } 33 34 void _detectImage() { 35 if (_imageStore.loadFuture.futureState != FutureState.loaded) return; 36 _yoloxStore.detect(_imageStore.loadFuture.data!); 37 } 38 39 40 Widget build(BuildContext context) { 41 const pad = 20.0; 42 return Scaffold( 43 appBar: AppBar( 44 backgroundColor: Theme.of(context).colorScheme.inversePrimary, 45 title: Text(widget.title), 46 ), 47 body: Padding( 48 padding: const EdgeInsets.all(pad), 49 child: Column( 50 mainAxisAlignment: MainAxisAlignment.spaceBetween, 51 crossAxisAlignment: CrossAxisAlignment.stretch, 52 children: [ 53 // 图片与结果 54 Expanded( 55 flex: 1, 56 child: Observer(builder: (context) { 57 if (_imageStore.loadFuture.futureState == 58 FutureState.loading) { 59 return const Center(child: CircularProgressIndicator()); 60 } 61 62 if (_imageStore.loadFuture.errorMessage != null) { 63 return Center( 64 child: Text(_imageStore.loadFuture.errorMessage!)); 65 } 66 67 final data = _imageStore.loadFuture.data; 68 if (data == null) { 69 return const Center(child: Text('Image load null :(')); 70 } 71 72 _yoloxStore.detectFuture.reset(); 73 74 return Container( 75 decoration: BoxDecoration( 76 border: Border.all(color: Colors.orangeAccent)), 77 child: DetectResultPage(imageData: data), 78 ); 79 })), 80 const SizedBox(height: pad), 81 // 三个按钮:选图、推理、是否显示框 82 Row( 83 mainAxisAlignment: MainAxisAlignment.center, 84 children: [ 85 Expanded( 86 child: ElevatedButton( 87 child: const Text('Pick image'), 88 onPressed: () => _debounce('_pickImage', _pickImage), 89 ), 90 ), 91 const SizedBox(width: pad), 92 Expanded( 93 child: ElevatedButton( 94 child: const Text('Detect objects'), 95 onPressed: () => _debounce('_detectImage', _detectImage), 96 ), 97 ), 98 const SizedBox(width: pad), 99 Expanded( 100 child: Observer(builder: (context) { 101 return ElevatedButton.icon( 102 icon: Icon(_optionStore.bboxesVisible 103 ? Icons.check_box_outlined 104 : Icons.check_box_outline_blank), 105 label: const Text('Binding boxes'), 106 onPressed: () => _optionStore 107 .setBboxesVisible(!_optionStore.bboxesVisible), 108 ); 109 }), 110 ), 111 ], 112 ), 113 ], 114 ), 115 ), 116 ); 117 } 118}
适配 Android 工程
Android 构建脚本在 android/build.gradle,也用的 CMake,与 Linux 共享了 src/CMakeLists.txt。不过要把 minSdkVersion 改成 24,以使用 Vulkan。
Vulkan 于 Android 7.0 (Nougat), API level 24 or higher 开始支持,可见 NDK / Get started with Vulkan。
plugins/ncnn_yolox/android/build.gradle 配置:
1android { 2 defaultConfig { 3 minSdkVersion 24 4 ndk { 5 moduleName "ncnn_yolox" 6 abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" 7 } 8 } 9}
demo_ncnn/android/app/build.gradle 也一样修改 minSdkVersion 为 24。
最后,即可 flutter run 运行。更多可见 Build and release an Android app。
适配 iOS 工程
本文项目未适配 iOS。如何适配 iOS,请见:
Xcode 14 不再支持提交含有 bitcode 的应用,Flutter 3.3.x 之后也移除了 bitcode 的支持,可见 Creating an iOS Bitcode enabled app。
