Flutter Google ML Kit人脸检测报错:InputImage宽高需≥32
错误详情
Flutter google ml kit face detection InputImage width and height should be at least 32!, null, null
错误日志:
I/flutter ( 6727): Error processing image: PlatformException(FaceDetectorError, com.google.mlkit.common.MlKitException: InputImage width and height should be at least 32!, null, null)
I/flutter ( 6727): #0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:653:7)
I/flutter ( 6727): #1 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:296:18)
I/flutter ( 6727):
I/flutter ( 6727): #2 MethodChannel.invokeListMethod (package:flutter/src/services/platform_channel.dart:485:35)
问题原因
- 预览比例强制错误:UI中
CameraPreview的aspectRatio被设为32/32(正方形),导致相机输出图像被异常压缩,传递给ML Kit的图像尺寸过小。 - 图像元数据尺寸传递错误:构建
InputImageMetadata时,直接使用image.planes[0]的宽高,未考虑相机传感器方向与图像实际显示尺寸的映射关系,导致元数据尺寸和真实图像尺寸不匹配。 - 无前置尺寸校验:未在检测前检查图像宽高是否满足ML Kit的最小要求(≥32像素),直接传递小尺寸图像触发报错。
修复方案
1. 修正CameraPreview的显示比例
使用相机控制器本身的比例,避免强制压缩:
AspectRatio( aspectRatio: _cameraController!.value.aspectRatio, child: CameraPreview(_cameraController!), )
2. 正确计算InputImage的尺寸和旋转
根据相机分辨率和传感器方向,调整图像元数据的尺寸参数:
Future<void> startFaceDetection() async { await _cameraController!.startImageStream((CameraImage image) async { final camera = _cameraController!.description; final sensorOrientation = camera.sensorOrientation; // 计算图像实际尺寸(考虑传感器方向) Size imageSize; if (sensorOrientation == 90 || sensorOrientation == 270) { imageSize = Size(image.height.toDouble(), image.width.toDouble()); } else { imageSize = Size(image.width.toDouble(), image.height.toDouble()); } // 确定图像旋转方向 InputImageRotation rotation; switch (sensorOrientation) { case 0: rotation = InputImageRotation.rotation0deg; break; case 90: rotation = InputImageRotation.rotation90deg; break; case 180: rotation = InputImageRotation.rotation180deg; break; case 270: rotation = InputImageRotation.rotation270deg; break; default: rotation = InputImageRotation.rotation0deg; } // 校验图像尺寸 if (imageSize.width < 32 || imageSize.height < 32) { print('跳过过小的图像,宽高需≥32像素'); return; } final inputImageMetadata = InputImageMetadata( size: imageSize, rotation: rotation, format: InputImageFormat.nv21, bytesPerRow: image.planes[0].bytesPerRow, ); final inputImage = InputImage.fromBytes( bytes: image.planes[0].bytes, metadata: inputImageMetadata, ); try { final List<Face> faces = await _faceDetector.processImage(inputImage); for (Face face in faces) { final boundingBox = face.boundingBox; final headEulerAngleY = face.headEulerAngleY; final headEulerAngleZ = face.headEulerAngleZ; // 这里可以添加人脸处理逻辑 } } catch (e, stackTrace) { print('Error processing image: $e\n$stackTrace'); } }); }
3. 优化相机切换逻辑
切换相机时,确保先停止之前的图像流,避免资源冲突:
void toggleCamera() async { // 先停止之前的图像流 await _cameraController?.stopImageStream(); await _cameraController?.dispose(); setState(() { _selectedCameraIndex = (_selectedCameraIndex + 1) % _cameras!.length; final selectedCamera = _cameras![_selectedCameraIndex]; _cameraController = CameraController( selectedCamera, ResolutionPreset.medium, // 保持一致的分辨率预设,避免尺寸波动 ); _cameraController!.initialize().then((_) { if (!mounted) return; setState(() { startFaceDetection(); }); }); }); }
完整修复后的代码
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:camera/camera.dart'; import 'package:google_ml_kit/google_ml_kit.dart'; import 'package:lottie/lottie.dart'; class MyHomePage extends StatefulWidget { MyHomePage({Key? key}) : super(key: key); @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { CameraController? _cameraController; List<CameraDescription>? _cameras; int _selectedCameraIndex = 0; late FaceDetector _faceDetector; @override void initState() { super.initState(); initializeCamera(); } Future<void> initializeCamera() async { _cameras = await availableCameras(); if (_cameras == null || _cameras!.isEmpty) { showDialog( context: context, builder: (context) { return AlertDialog( title: const Text('相机错误'), content: const Text('您的设备上没有可用相机。'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('确定'), ), ], ); }, ); return; } final selectedCamera = _cameras![_selectedCameraIndex]; _cameraController = CameraController( selectedCamera, ResolutionPreset.medium, ); await _cameraController!.initialize(); _faceDetector = GoogleMlKit.vision.faceDetector(); if (!mounted) return; setState(() => startFaceDetection()); } Future<void> startFaceDetection() async { await _cameraController!.startImageStream((CameraImage image) async { final camera = _cameraController!.description; final sensorOrientation = camera.sensorOrientation; // 计算实际图像尺寸 Size imageSize; if (sensorOrientation == 90 || sensorOrientation == 270) { imageSize = Size(image.height.toDouble(), image.width.toDouble()); } else { imageSize = Size(image.width.toDouble(), image.height.toDouble()); } // 确定旋转方向 InputImageRotation rotation; switch (sensorOrientation) { case 0: rotation = InputImageRotation.rotation0deg; break; case 90: rotation = InputImageRotation.rotation90deg; break; case 180: rotation = InputImageRotation.rotation180deg; break; case 270: rotation = InputImageRotation.rotation270deg; break; default: rotation = InputImageRotation.rotation0deg; } // 跳过尺寸不足的图像 if (imageSize.width < 32 || imageSize.height < 32) { print('跳过过小的图像,宽高需≥32像素'); return; } final inputImageMetadata = InputImageMetadata( size: imageSize, rotation: rotation, format: InputImageFormat.nv21, bytesPerRow: image.planes[0].bytesPerRow, ); final inputImage = InputImage.fromBytes( bytes: image.planes[0].bytes, metadata: inputImageMetadata, ); try { final List<Face> faces = await _faceDetector.processImage(inputImage); for (Face face in faces) { final boundingBox = face.boundingBox; final headEulerAngleY = face.headEulerAngleY; final headEulerAngleZ = face.headEulerAngleZ; // 添加人脸处理逻辑 } } catch (e, stackTrace) { print('Error processing image: $e\n$stackTrace'); } }); } @override void dispose() { _cameraController?.stopImageStream(); _cameraController?.dispose(); _faceDetector.close(); super.dispose(); } void toggleCamera() async { await _cameraController?.stopImageStream(); await _cameraController?.dispose(); setState(() { _selectedCameraIndex = (_selectedCameraIndex + 1) % _cameras!.length; final selectedCamera = _cameras![_selectedCameraIndex]; _cameraController = CameraController( selectedCamera, ResolutionPreset.medium, ); _cameraController!.initialize().then((_) { if (!mounted) return; setState(() => startFaceDetection()); }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('相机人脸检测')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ _cameraController?.value.isInitialized == true ? AspectRatio( aspectRatio: _cameraController!.value.aspectRatio, child: CameraPreview(_cameraController!), ) : Lottie.asset('Assets/face_loading_4.json'), ], ), ), floatingActionButton: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton.extended( onPressed: toggleCamera, tooltip: '切换相机', icon: const Icon(Icons.switch_camera), label: const Text('切换'), ), ], ), ); } }
内容的提问来源于stack exchange,提问作者Mehmet Kuru

