You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中使用TensorFlow Hub food_V1模型闪退问题求助

Flutter中使用TensorFlow Hub food_V1模型闪退问题排查与解决

问题描述

  • 在Flutter项目中尝试使用TensorFlow Hub的food_V1模型,下载后仅得到tflite文件,自行提取标签并配置到pubspec.yaml的资源中后,程序运行直接闪退
  • 替换为Teachable Machine生成的tflite模型时,代码可正常执行
  • 相关代码如下:
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:tflite/tflite.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(
                title: Text('Tensor Flow Example'),
                backgroundColor: Color.fromRGBO(61, 62, 61, 1)),
            body: Center(child: HomePage())));
  }
}

class HomePage extends StatefulWidget {
  @override
  HomePageState createState() => HomePageState();
}

class HomePageState extends State {
  File? imageURI;
  var result;
  String? path;

  Future getImageFromCamera() async {
    try {
      var image = await ImagePicker().pickImage(source: ImageSource.camera);
      File img = File(image!.path);
      setState(() {
        imageURI = img;
        path = image.path;
      });
    } catch (e) {
      print("Error getting image from camera");
    }

    classifyImage();
  }

  Future getImageFromGallery() async {
    try {
      var image = await ImagePicker().pickImage(source: ImageSource.gallery);
      File img = File(image!.path);
      setState(() {
        imageURI = img;
        path = image.path;
      });
    } catch (e) {
      print("Error getting image from gallery");
    }

    classifyImage();
  }

  Future classifyImage() async {
    await Tflite.loadModel(
        model: "assets/model_unquant.tflite", labels: "assets/labels.txt");
    var output = await Tflite.runModelOnImage(
      path: imageURI!.path,
      imageMean: 127.5,
      imageStd: 127.5,
      numResults: 2,
      threshold: 0.2,
      );

    setState(() {
      result = output;
      print(output![0]["confidence"].toStringAsFixed(3));
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Color.fromRGBO(48, 48, 48, 1),
        body: Center(
            child: SingleChildScrollView(
              child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    imageURI == null
                        ? Text(
                      'please select image from gallery or camera.',
                      style: TextStyle(color: Colors.white),
                    )
                        : ClipRRect(
                      borderRadius: BorderRadius.circular(8.0),
                      child: Image.file(imageURI!,
                          width: 300, height: 200, fit: BoxFit.cover),
                    ),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Container(
                            margin: EdgeInsets.fromLTRB(0, 30, 0, 20),
                            child: IconButton(
                              onPressed: () => getImageFromCamera(),
                              icon: Icon(
                                Icons.camera,
                                color: Colors.white,
                              ),
                              iconSize: 50,
                              color: Colors.blue,
                              padding: EdgeInsets.fromLTRB(12, 12, 12, 12),
                            )),
                        Container(
                            margin: EdgeInsets.fromLTRB(0, 0, 0, 0),
                            child: IconButton(
                              icon: Icon(Icons.image, color: Colors.white),
                              iconSize: 50,
                              onPressed: () => getImageFromGallery(),
                              color: Colors.blue,
                              padding: EdgeInsets.fromLTRB(12, 12, 12, 12),
                            )),
                      ],
                    ),
                    SizedBox(
                      height: 10,
                    ),
                    result == null
                        ? Text(
                      'Result',
                      style: TextStyle(color: Colors.white),
                    )
                        : Container(
                      child: Text(
                        "${result[0]['label'].substring(2)}",
                        style: TextStyle(
                            color: Colors.white,
                            fontWeight: FontWeight.bold,
                            fontSize: 30),
                      ),
                    )
                  ]),
            )));
  }
}

可能原因

  • 重复加载模型:每次调用classifyImage()都执行Tflite.loadModel(),导致模型资源重复加载引发冲突
  • 图像预处理参数不匹配:当前设置的imageMean=127.5、imageStd=127.5与food_V1模型要求的预处理参数不符
  • 标签文件格式问题:自行提取的labels.txt格式不符合tflite插件的解析要求(比如每行格式错误、存在多余字符)
  • 输入尺寸不匹配:food_V1模型要求输入图像尺寸为224x224,代码中未指定导致尺寸不兼容

解决步骤

  1. 仅加载一次模型:将模型加载逻辑移到initState()中,避免重复加载
  2. 调整预处理参数:将imageMean和imageStd改为food_V1模型要求的128.0
  3. 指定输入图像尺寸:在runModelOnImage()中添加inputSize: 224参数
  4. 验证标签文件格式:确保labels.txt每行仅包含一个标签名称,无编号或额外符号

修改后的代码

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:tflite/tflite.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(
                title: Text('Tensor Flow Example'),
                backgroundColor: Color.fromRGBO(61, 62, 61, 1)),
            body: Center(child: HomePage())));
  }
}

class HomePage extends StatefulWidget {
  @override
  HomePageState createState() => HomePageState();
}

class HomePageState extends State<HomePage> {
  File? imageURI;
  var result;
  String? path;

  @override
  void initState() {
    super.initState();
    // 仅在初始化时加载一次模型
    loadModel();
  }

  Future loadModel() async {
    await Tflite.loadModel(
      model: "assets/model_unquant.tflite",
      labels: "assets/labels.txt",
    );
  }

  Future getImageFromCamera() async {
    try {
      var image = await ImagePicker().pickImage(source: ImageSource.camera);
      if (image == null) return;
      File img = File(image.path);
      setState(() {
        imageURI = img;
        path = image.path;
      });
      classifyImage();
    } catch (e) {
      print("Error getting image from camera: $e");
    }
  }

  Future getImageFromGallery() async {
    try {
      var image = await ImagePicker().pickImage(source: ImageSource.gallery);
      if (image == null) return;
      File img = File(image.path);
      setState(() {
        imageURI = img;
        path = image.path;
      });
      classifyImage();
    } catch (e) {
      print("Error getting image from gallery: $e");
    }
  }

  Future classifyImage() async {
    if (imageURI == null) return;
    var output = await Tflite.runModelOnImage(
      path: imageURI!.path,
      imageMean: 128.0, // 匹配food_V1模型的预处理参数
      imageStd: 128.0,
      numResults: 2,
      threshold: 0.2,
      inputSize: 224, // 指定模型要求的输入尺寸
    );

    setState(() {
      result = output;
      if (output != null) {
        print(output[0]["confidence"].toStringAsFixed(3));
      }
    });
  }

  @override
  void dispose() {
    // 页面销毁时释放模型资源
    Tflite.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Color.fromRGBO(48, 48, 48, 1),
        body: Center(
            child: SingleChildScrollView(
              child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    imageURI == null
                        ? const Text(
                      'please select image from gallery or camera.',
                      style: TextStyle(color: Colors.white),
                    )
                        : ClipRRect(
                      borderRadius: BorderRadius.circular(8.0),
                      child: Image.file(imageURI!,
                          width: 300, height: 200, fit: BoxFit.cover),
                    ),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Container(
                            margin: const EdgeInsets.fromLTRB(0, 30, 0, 20),
                            child: IconButton(
                              onPressed: getImageFromCamera,
                              icon: const Icon(
                                Icons.camera,
                                color: Colors.white,
                              ),
                              iconSize: 50,
                              color: Colors.blue,
                              padding: const EdgeInsets.fromLTRB(12, 12, 12, 12),
                            )),
                        Container(
                            margin: const EdgeInsets.fromLTRB(0, 0, 0, 0),
                            child: IconButton(
                              icon: const Icon(Icons.image, color: Colors.white),
                              iconSize: 50,
                              onPressed: getImageFromGallery,
                              color: Colors.blue,
                              padding: const EdgeInsets.fromLTRB(12, 12, 12, 12),
                            )),
                      ],
                    ),
                    const SizedBox(
                      height: 10,
                    ),
                    result == null
                        ? const Text(
                      'Result',
                      style: TextStyle(color: Colors.white),
                    )
                        : Container(
                      child: Text(
                        "${result[0]['label'].substring(2)}",
                        style: const TextStyle(
                            color: Colors.white,
                            fontWeight: FontWeight.bold,
                            fontSize: 30),
                      ),
                    )
                  ]),
            )));
  }
}

额外注意事项

  • 确认pubspec.yaml中已正确配置资源路径:
assets:
  - assets/model_unquant.tflite
  - assets/labels.txt
  • 测试前先清理项目缓存,重新构建应用

内容的提问来源于stack exchange,提问作者martin Hm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 09:37:06