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

Flutter目标检测APP加载模型后打开相机崩溃求助

Flutter物体检测应用加载模型后崩溃的解决方案

问题根源分析

  1. 模型类型不匹配:加载的是SSD MobileNet模型,但推理时指定model: "YOLO",两者预处理逻辑、输出格式完全不同,直接导致解析错误引发崩溃。
  2. 帧处理逻辑冗余:_onImageAvailable中processingFrames被重复递增,帧跳过逻辑混乱,易造成任务堆积引发内存过载。
  3. 资源未释放:页面销毁时未关闭TFLite模型实例,长期运行会造成内存泄漏。
  4. 状态更新不安全:帧处理回调中调用setState未检查Widget是否仍挂载,可能引发异常。
  5. 参数配置不符:SSD MobileNet默认使用imageMean:127.5和imageStd:127.5,当前配置与模型要求不匹配,导致推理数据错误。

修复步骤

  • 匹配模型类型:将runModelOnFrame中的model参数改为"SSDMobileNet",与加载的模型对应。
  • 修正帧处理逻辑:移除重复计数,优化帧跳过规则,避免任务堆积。
  • 添加资源释放:在页面销毁时调用Tflite.close()释放模型资源。
  • 确保状态更新安全:调用setState前检查mounted状态。
  • 对齐模型参数:使用SSD MobileNet默认的均值和标准差配置。

修改后的完整代码

import 'dart:async';
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:tflite/tflite.dart';

late List<CameraDescription> _cameras;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  _cameras = await availableCameras();
  await loadModel();
  runApp(const MyApp());
}

Future<void> loadModel() async {
  String? res = await Tflite.loadModel(
    model: "assets/ssdmobilenet.tflite",
    labels: "assets/label2.txt",
    numThreads: 2, // 降低线程数减少内存占用
    isAsset: true,
    useGpuDelegate: false,
  );
  print('model loaded successfully: $res');
}

Future<List<dynamic>?> runModelOnFrame(CameraImage image) async {
  return await Tflite.detectObjectOnFrame(
    bytesList: image.planes.map((plane) => plane.bytes).toList(),
    model: "SSDMobileNet", // 匹配加载的SSD模型
    imageHeight: image.height,
    imageWidth: image.width,
    imageMean: 127.5, // SSD默认均值
    imageStd: 127.5, // SSD默认标准差
    threshold: 0.3, // 提高阈值减少无效检测
    numResultsPerClass: 1,
  );
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: FirstPage(),
    );
  }
}

class FirstPage extends StatefulWidget {
  const FirstPage({Key? key});

  @override
  State<FirstPage> createState() => _FirstPageState();
}

class _FirstPageState extends State<FirstPage> {
  String buttonName = 'HEYO';
  int currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('App Bar')),
      body: currentIndex == 0
          ? Container(
              color: Colors.blue,
              width: double.infinity,
              height: double.infinity,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton(
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.white,
                      foregroundColor: Colors.black,
                    ),
                    onPressed: () => Navigator.of(context).push(
                      MaterialPageRoute(
                        builder: (_) => const Scaffold(body: CameraApp()),
                      ),
                    ),
                    child: Text(buttonName),
                  ),
                  ElevatedButton(
                    onPressed: () => Navigator.of(context).push(
                      MaterialPageRoute(builder: (_) => const NewPage()),
                    ),
                    child: Text(buttonName),
                  ),
                ],
              ),
            )
          : Image.asset('images/light.jpg'),
      bottomNavigationBar: BottomNavigationBar(
        items: const [
          BottomNavigationBarItem(label: 'home', icon: Icon(Icons.home)),
          BottomNavigationBarItem(label: 'settings', icon: Icon(Icons.settings))
        ],
        currentIndex: currentIndex,
        onTap: (int index) => setState(() => currentIndex = index),
      ),
    );
  }
}

class NewPage extends StatelessWidget {
  const NewPage({Key? key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar());
  }
}

class CameraApp extends StatefulWidget {
  const CameraApp({Key? key});

  @override
  State<CameraApp> createState() => _CameraAppState();
}

class _CameraAppState extends State<CameraApp> {
  bool isProcessingFrame = false;
  late CameraController controller;
  List<Widget> boxesWidgets = [];
  int frameSkipCounter = 0;

  @override
  void initState() {
    super.initState();
    controller = CameraController(
      _cameras[0],
      ResolutionPreset.low, // 降低分辨率减少计算压力
      enableAudio: false, // 禁用音频节省资源
    );
    controller.initialize().then((_) {
      if (!mounted) return;
      setState(() {});
      controller.startImageStream(_onImageAvailable);
    });
  }

  Future<void> _onImageAvailable(CameraImage image) async {
    if (isProcessingFrame) return;
    isProcessingFrame = true;

    // 每3帧处理一次,降低CPU负载
    frameSkipCounter++;
    if (frameSkipCounter < 3) {
      isProcessingFrame = false;
      return;
    }
    frameSkipCounter = 0;

    try {
      final recognitions = await runModelOnFrame(image);
      if (recognitions != null && mounted) {
        final boxes = <Widget>[];
        for (final recognition in recognitions) {
          final confidence = recognition['confidence'] as double;
          if (confidence < 0.3) continue;

          final label = recognition['label'] as String;
          final rect = recognition['rect'] as Map<String, double>;
          final left = rect['x']! * image.width;
          final top = rect['y']! * image.height;
          final width = rect['w']! * image.width;
          final height = rect['h']! * image.height;

          boxes.add(
            Positioned(
              left: left,
              top: top,
              width: width,
              height: height,
              child: Container(
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.green, width: 2),
                ),
                child: Align(
                  alignment: Alignment.topLeft,
                  child: Container(
                    color: Colors.green.withOpacity(0.8),
                    padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
                    child: Text(
                      '$label ${(confidence * 100).toStringAsFixed(0)}%',
                      style: const TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.bold,
                        fontSize: 12,
                      ),
                    ),
                  ),
                ),
              ),
            ),
          );
        }
        setState(() => boxesWidgets = boxes);
      }
    } catch (e) {
      print('Frame processing error: $e');
    } finally {
      isProcessingFrame = false;
    }
  }

  @override
  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return const Center(child: CircularProgressIndicator());
    }

    final size = MediaQuery.of(context).size;
    final deviceRatio = size.width / size.height;

    return Scaffold(
      body: Stack(
        children: [
          Center(
            child: AspectRatio(
              aspectRatio: deviceRatio,
              child: CameraPreview(controller),
            ),
          ),
          ...boxesWidgets,
        ],
      ),
    );
  }

  @override
  void dispose() {
    controller.stopImageStream();
    controller.dispose();
    Tflite.close(); // 释放模型资源
    super.dispose();
  }
}

额外优化建议

  • 若设备支持GPU加速,可尝试开启useGpuDelegate: true,提升推理速度并降低CPU负载。
  • 将模型推理逻辑迁移到单独的Isolate中执行,避免阻塞主线程导致UI卡顿。
  • 对模型进行量化处理(如INT8量化),减小模型体积并提升运行效率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 06:12:04