如何将Firebase上的Custom ML Kit TFLite模型集成到Flutter Android端
Flutter Android端集成Firebase Custom ML Kit自定义TFLite图像分类模型实现步骤
所有代码片段的添加位置均明确标注,按顺序配置即可跑通。
1 基础环境配置
- 先确认你已经完成Firebase核心接入:将Firebase控制台下载的
google-services.json文件放到项目android/app目录下,这步是所有Firebase服务的基础,没完成的先补。 - 打开项目根目录下的
pubspec.yaml文件,在dependencies块下添加以下依赖,之后执行flutter pub get拉取包:
dependencies: flutter: sdk: flutter firebase_core: ^2.24.2 # 和你项目其他Firebase依赖版本保持一致即可 firebase_ml_model_downloader: ^0.2.4+16 # 负责拉取你托管在Firebase上的自定义模型 tflite_flutter: ^0.10.4 # 负责TFLite模型推理 image: ^4.1.3 # 负责图片解码、尺寸调整等预处理操作
- 打开
android/app/build.gradle文件,修改defaultConfig块下的minSdkVersion为21及以上,ML Kit自定义模型不支持更低版本:
android { defaultConfig { minSdkVersion 21 // 原有其他配置保持不动 } }
如果后续运行时报
libc++_shared.so冲突错误,就在上述android块下追加packagingOptions配置:packagingOptions { pickFirst 'lib/x86_64/libc++_shared.so' pickFirst 'lib/arm64-v8a/libc++_shared.so' pickFirst 'lib/armeabi-v7a/libc++_shared.so' }
2 模型加载逻辑
这部分代码写在你需要触发图像分类功能的页面(比如拍照/选图页面)对应的State类中,不要写在main函数里,会导致启动白屏。
- 先在State类里定义状态变量:
// 存储从Firebase下载到本地的模型文件 File? _localModelFile; // 标记模型是否加载完成 bool _isModelReady = false; // 替换成你在Firebase ML控制台上传模型时填写的自定义名称,大小写必须完全一致 final String _deployedModelName = "your_tflite_model_name";
- 在State的initState生命周期里调用模型加载方法,页面初始化时自动拉取模型:
@override void initState() { super.initState(); _fetchModelFromFirebase(); } Future<void> _fetchModelFromFirebase() async { try { // 拉取规则:优先使用本地缓存模型,后台静默检查更新 final customModel = await FirebaseModelDownloader.instance.getModel( _deployedModelName, FirebaseModelDownloadType.localModelUpdateInBackground, FirebaseModelDownloadConditions( androidChargingRequired: false, androidWifiRequired: false, androidDeviceIdleRequired: false, ), ); _localModelFile = customModel.file; setState(() { _isModelReady = true; }); } catch (e) { debugPrint("模型拉取失败: $e"); } }
3 图像预处理与推理逻辑
这部分代码写在同一个State类里,在用户选完图片/拍完照拿到图片File对象之后调用即可。
- 先实现图像预处理方法,把原始图片转换成模型要求的输入格式,注意所有预处理参数必须和你训练模型时的参数完全一致,否则推理结果会完全错误:
Future<Uint8List> _imagePreprocess(File rawImageFile) async { // 解码原始图片 final rawBytes = await rawImageFile.readAsBytes(); final originalImg = img.decodeImage(rawBytes); if (originalImg == null) throw Exception("图片解码失败"); // 替换成你模型要求的输入尺寸,比如常见的224*224、320*320 const int inputSize = 224; // 调整图片尺寸匹配模型输入 final resizedImg = img.copyResize(originalImg, width: inputSize, height: inputSize); // 按模型要求转换像素格式、做归一化,以下是通用0-1归一化示例,按你训练时的逻辑修改 final convertedInput = Float32List(1 * inputSize * inputSize * 3); int pixelPos = 0; for (int y = 0; y < inputSize; y++) { for (int x = 0; x < inputSize; x++) { final currentPixel = resizedImg.getPixel(x, y); // 如果你的模型要求归一化到-1~1,就改成 (currentPixel.r / 127.5) - 1 这种逻辑 convertedInput[pixelPos++] = currentPixel.r / 255.0; convertedInput[pixelPos++] = currentPixel.g / 255.0; convertedInput[pixelPos++] = currentPixel.b / 255.0; } } return convertedInput.buffer.asUint8List(); }
- 实现推理方法,输出排序后的分类结果:
Future<List<Map<String, dynamic>>> _runClassification(File inputImage) async { if (!_isModelReady || _localModelFile == null) { throw Exception("模型未加载完成,请稍后重试"); } // 加载本地模型初始化解释器 final tfliteInterpreter = Interpreter.fromFile(_localModelFile!); // 预处理输入图片 final modelInput = await _imagePreprocess(inputImage); // 替换成你模型的输出维度,比如1000分类就写[1,1000] final modelOutput = List.filled(1 * 1000, 0.0).reshape([1, 1000]); // 执行推理 tfliteInterpreter.run(modelInput, modelOutput); // 推理完成后及时关闭解释器释放内存 tfliteInterpreter.close(); // 替换成你训练模型时对应的标签列表,顺序必须和训练时完全一致 const labelList = ["标签1", "标签2", "标签3" /* 补全所有分类标签 */]; List<Map<String, dynamic>> predictResult = []; for (int i = 0; i < labelList.length; i++) { predictResult.add({ "label": labelList[i], "confidence": modelOutput[0][i] }); } // 按置信度从高到低排序 predictResult.sort((a, b) => b["confidence"].compareTo(a["confidence"])); return predictResult; }
4 常见问题排查
- 模型拉取失败:首先检查设备网络是否正常,其次检查模型名称是否和Firebase控制台填写的完全一致,不要有大小写、空格错误。
- 推理结果不准:90%以上的情况是预处理逻辑和训练时不匹配,重点检查resize尺寸、归一化公式、通道顺序(部分模型用BGR顺序)、标签顺序。
- 运行卡顿:图像预处理和推理逻辑不要放在主线程执行,如果图片尺寸大、模型体积大,可以用compute方法放到后台isolate执行。
内容的提问来源于stack exchange,提问作者rafayaar
相关产品推荐
相关产品推荐

