Flutter图像分类应用中TensorFlow维度错误排查求助
Flutter图像分类应用中TensorFlow维度错误排查求助
大家好,我在开发一个基于Flutter的垃圾分类应用时,使用自定义分类器模型处理拍摄图像时遇到了维度不匹配的问题,导致分类流程失败。下面是我的扫描页面完整代码,以及目前的问题情况,希望能得到大家的帮助:
扫描页面完整代码
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:camera/camera.dart'; import 'package:waste_pal/widgets/processing-image.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:waste_pal/services/classifier_service.dart'; class ScanScreen extends StatefulWidget { @override _ScanScreenState createState() => _ScanScreenState(); } class _ScanScreenState extends State<ScanScreen> with WidgetsBindingObserver { CameraController? _controller; List<CameraDescription>? cameras; bool _isCameraInitialized = false; bool _isProcessing = false; XFile? _capturedImage; ClassificationResult? _classificationResult; ClassifierService _classifierService = ClassifierService(); String? _errorMessage; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _initializeEverything(); } Future<void> _initializeEverything() async { try { await _initCamera(); await _initializeClassifier(); } catch (e) { _showError("Initialization error: $e"); } } Future<void> _initCamera() async { try { cameras = await availableCameras(); if (cameras == null || cameras!.isEmpty) { _showError("No cameras found"); return; } _controller = CameraController(cameras![0], ResolutionPreset.medium); await _controller!.initialize(); await _controller!.setFlashMode(FlashMode.off); if (!mounted) return; setState(() { _isCameraInitialized = true; }); } catch (e) { _showError("Camera initialization failed: $e"); } } Future<void> _initializeClassifier() async { try { await _classifierService.init(); } catch (e) { _showError("Classifier initialization failed: $e"); } } @override void didChangeAppLifecycleState(AppLifecycleState state) { // Handle app lifecycle changes - important for camera resources if (_controller == null || !_controller!.value.isInitialized) return; if (state == AppLifecycleState.inactive) { _controller?.dispose(); } else if (state == AppLifecycleState.resumed) { _initCamera(); } } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _controller?.dispose(); _classifierService.dispose(); super.dispose(); } void _captureImage() async { if (_controller == null || !_controller!.value.isInitialized) { _showError("Camera not ready"); return; } setState(() { _isProcessing = true; _errorMessage = null; }); try { _showProcessingModal(); // Capture image final XFile image = await _controller!.takePicture(); if (!mounted) return; Navigator.pop(context); // Close processing modal setState(() { _capturedImage = image; }); // Classify the captured image try { final result = await _classifierService.classifyImage(File(_capturedImage!.path)); if (mounted) { setState(() { _classificationResult = result; _isProcessing = false; }); } } catch (e) { _showError("Classification failed: $e"); } } catch (e) { if (mounted) { Navigator.pop(context); // Ensure dialog is closed _showError("Failed to capture image: $e"); } } } void _showError(String message) { print("ERROR: $message"); if (mounted) { setState(() { _errorMessage = message; _isProcessing = false; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: Colors.red, duration: Duration(seconds: 3), ), ); } } void _retakePhoto() { if (_capturedImage != null) { try { File(_capturedImage!.path).delete(); } catch (e) { print("Error deleting image: $e"); } } setState(() { _capturedImage = null; _classificationResult = null; _errorMessage = null; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Colors.white, actionsPadding: EdgeInsets.only(right: 16.w), automaticallyImplyLeading: false, actions: [ IconButton( icon: Icon(Icons.menu, size: 35.sp, color: Color.fromARGB(255, 35, 62, 46)), onPressed: () { Scaffold.of(context).openEndDrawer(); }, ), ], title: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Image.asset('assets/logo.png', height: 48.h), Text( 'WASTE PAL', style: TextStyle( fontSize: 28.sp, fontFamily: 'Montserrat', fontWeight: FontWeight.w900, color: Color.fromARGB(255, 35, 62, 46)), ), ], ), ), backgroundColor: Colors.white, body: _capturedImage == null ? _buildCameraView() : _buildResultView(), ); } Widget _buildCameraView() { return Column( children: [ Padding( padding: EdgeInsets.all(10.r), child: Container( padding: EdgeInsets.symmetric(vertical: 18.r), width: double.infinity, decoration: BoxDecoration( color: const Color.fromARGB(255, 9, 93, 64), borderRadius: BorderRadius.circular(18.r), ), child: Center( child: Text( 'Take a photo of the item', style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.bold, fontFamily: 'Montserrat', color: Colors.white), ), ), ), ), SizedBox(height: 10.h), // Camera preview or error message _isCameraInitialized ? Expanded( child: CameraPreview(_controller!), ) : Center( child: _errorMessage != null ? Text(_errorMessage!, style: TextStyle(color: Colors.red)) : CircularProgressIndicator(), ), SizedBox(height: 20.h), if (!_isProcessing) ElevatedButton( onPressed: _captureImage, child: Text('Capture & Classify'), style: ElevatedButton.styleFrom( backgroundColor: Color.fromARGB(255, 9, 93, 64), foregroundColor: Colors.white, padding: EdgeInsets.symmetric(horizontal: 40.w, vertical: 15.h), textStyle: TextStyle(fontSize: 20.sp, fontFamily: 'Montserrat'), ), ), if (_isProcessing) CircularProgressIndicator(), ], ); } Widget _buildResultView() { return Column( children: [ Padding( padding: EdgeInsets.all(10.r), child: Image.file(File(_capturedImage!.path), height: 300.h), ), SizedBox(height: 20.h), if (_classificationResult != null) Column( children: [ Text( 'Classification Result', style: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.bold), ), SizedBox(height: 10.h), Text( 'Category: ${_classificationResult!.category}', style: TextStyle(fontSize: 20.sp), ), Text( 'Confidence: ${(_classificationResult!.confidence * 100).toStringAsFixed(2)}%', style: TextStyle(fontSize: 18.sp), ), ], ), SizedBox(height: 20.h), ElevatedButton( onPressed: _retakePhoto, child: Text('Retake Photo'), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey, foregroundColor: Colors.white, ), ), ], ); } void _showProcessingModal() { showDialog( context: context, barrierDismissible: false, builder: (context) { return AlertDialog( content: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(), SizedBox(height: 10.h), Text('Processing image...'), ], ), ); }, ); } }
目前遇到的问题
- 相机拍摄功能正常,能成功获取到图像文件,但调用
classifyImage时抛出张量维度不匹配的异常,示例错误信息:Classification failed: Invalid argument(s): Model expects input shape [1, 224, 224, 3] but received tensor with shape [1, 480, 640, 3] - 分类器初始化阶段没有报错,模型文件已正确加载
- 已经确认拍摄的图像文件存在且路径正确
我怀疑的排查方向(尚未解决)
- 图像预处理环节是否没有将拍摄的图像缩放到模型要求的224x224尺寸?
ClassifierService中的classifyImage方法是否在转换图像为张量时,没有正确处理维度顺序(比如NHWC和NCHW的区别)?- 模型的输入通道数是否与拍摄图像的RGB三通道不匹配?
有没有朋友遇到过类似的问题,或者能帮我检查代码中可能存在的问题?非常感谢!
内容来源于stack exchange
相关产品推荐
相关产品推荐

