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

Flutter Google ML Kit人脸检测报错:InputImage宽高需≥32

Flutter人脸检测报错: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)

问题原因

  1. 预览比例强制错误:UI中CameraPreview的aspectRatio被设为32/32(正方形),导致相机输出图像被异常压缩,传递给ML Kit的图像尺寸过小。
  2. 图像元数据尺寸传递错误:构建InputImageMetadata时,直接使用image.planes[0]的宽高,未考虑相机传感器方向与图像实际显示尺寸的映射关系,导致元数据尺寸和真实图像尺寸不匹配。
  3. 无前置尺寸校验:未在检测前检查图像宽高是否满足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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 08:10:56