Flutter目标检测APP加载模型后打开相机崩溃求助
Flutter物体检测应用加载模型后崩溃的解决方案
问题根源分析
- 模型类型不匹配:加载的是SSD MobileNet模型,但推理时指定
model: "YOLO",两者预处理逻辑、输出格式完全不同,直接导致解析错误引发崩溃。 - 帧处理逻辑冗余:
_onImageAvailable中processingFrames被重复递增,帧跳过逻辑混乱,易造成任务堆积引发内存过载。 - 资源未释放:页面销毁时未关闭TFLite模型实例,长期运行会造成内存泄漏。
- 状态更新不安全:帧处理回调中调用
setState未检查Widget是否仍挂载,可能引发异常。 - 参数配置不符: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
相关产品推荐
相关产品推荐

