Flutter ncnn 使用

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 模型

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 预编译库,

  • ncnn: ncnn-YYYYMMDD-ubuntu-2204-shared.zip
  • opencv: opencv-mobile-4.6.0-ubuntu-2204.zip

解压进 plugins/ncnn_yolox/linux/

准备 Android 预编译库,

  • ncnn: ncnn-YYYYMMDD-android-vulkan-shared.zip
  • opencv: opencv-mobile-4.6.0-android.zip

解压进 plugins/ncnn_yolox/android/

确认 ncnn_yolox/src/CMakeLists.txtncnn_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 接口

src/ncnn_yolox.h

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) 准备依赖库

准备 ncnn opencv 的预编译库,

  • 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) 写构建脚本

src/CMakeLists.txt

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 接口测试

linux/ncnn_yolox_test.cc

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 接口测试

linux/ncnn_yolox_test.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 类:

因为加载、预测都比较耗时,故用的 MobX ObservableFuture 异步方式。若要了解使用,可见:

以上就是 App 实现的关键内容,也可采取不同方案。

应用插件做推理

App 里应用插件,首先要于 pubspec.yaml 里加上插件的依赖:

1dependencies: 2 ncnn_yolox: 3 path: ../plugins/ncnn_yolox

然后,yolox_store.dart 应用了插件做推理,过程与之前 Dart 接口测试基本一致。差异主要在:

  • 多了将 assets 里的模型拷贝进临时路径的操作,因为 App 里无法获取资源的绝对路径。要么改 C 接口,模型以字节给到。
  • 多了将图片数据从 Uint8ListPointer<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 也一样修改 minSdkVersion24

最后,即可 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

更多参考

点赞
收藏

评论区

加载中...

相关推荐

字节跳动为什么选用Flutter:并非跨平台终极之选,但它可能是不一样的未来

2018年12月,Google宣布Flutter1.0版本正式发布。截至目前,Flutter在Github上已获得88000的关注和11000的Fork,其发展速度相当惊人,是今年移动端最火热的开发框架之一。Flutter大火背后的原因是什么?为什么越来越多的企业和开发者会选择使用Flutter?Flutter会

【Flutter实战】初识Flutter

1.2初识Flutter1.2.1Flutter简介Flutter是Google推出并开源的移动应用开发框架,主打跨平台、高保真、高性能。开发者可以通过Dart语言开发App,一套代码同时运行在iOS和Android平台。Flutter提供了丰富的组件、接口,开发者可以很快地为Flutter添加native扩展。同时Flu

Android&Flutter混合开发初体验

最近flutter不是一般的火呀,但对于一些成熟的产品来说,完全摒弃原有App的历史沉淀,全面转向Flutter是不现实的。因此使用Flutter去统一Android、iOS技术栈,把它作为已有原生App的扩展能力,通过有序推进来提升移动端的开发效率(1)创建flutter模块,创建module后会AS会自动进行flutter的相关配置!在这里

Flutter 笔记

最近开发需要,要接触移动端一些东西,所以看一下flutter。因为移动端经常是要做一样事情就是即要有android平台,又要有ios平台,所以,选择了Flutter。为什么不是ReactNative?因为,Flutter字比较少,而且不用打空格,我喜欢。一、安装1.下载2.解压3.设置path4.开始二、

Flutter 介绍、运行环境

Flutter是谷歌公司开发的一款开源、免费的移动UI框架,可以让我们快速的在Android和iOS上构建高质量App。它最大的特点就是跨平台、以及高性能。Flutter基于谷歌的dart语言,如果没有任何Dart语言的基础,不建议直接学习Flutter。建议先学习Dart语言的基本语法。然后再进入Flutter的学习

DStack

混合开发这项技术由来已久,目前市面上主流的有Hybird,ReactNative,Weex,Flutter等。其中,Flutter以他独有的实现方式,优秀的性能,成为近两年最火的混合开发方案,我们学而思网校1v1客户端团队也是比较早的开始了Flutter技术的研究,在学而思网校1v1家长端和学而思网校1v1教师端两个App进行了大规模的实践尝试,由此也沉淀出