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

如何调整Flutter人脸识别场景下被压缩的相机宽高比

解决Flutter相机宽高比拉伸导致人脸检测框偏移问题

你的问题根源在于直接将CameraPreview强制铺满屏幕宽高,没有遵循相机本身的宽高比,导致画面拉伸,进而人脸检测的原始坐标和屏幕显示坐标不匹配,出现框偏移的情况。

修复步骤:

1. 按相机原生宽高比渲染预览画面

替换代码中直接设置宽高的CameraPreview,改用AspectRatio组件适配相机真实比例,避免画面拉伸:

@override
Widget build(BuildContext context) {
  print(_detectedFaces.isNotEmpty ? _detectedFaces[0] : 'No faces detected');
  List<Widget> stackChildren = [];
  final size = MediaQuery.sizeOf(context);
  if (_controller != null) {
    // 获取相机原生宽高比
    final cameraAspectRatio = _controller.value.aspectRatio;
    
    stackChildren.add(Positioned(
      top: 0.0,
      left: 0.0,
      width: size.width,
      height: size.height,
      // 用AspectRatio保证预览画面比例正确
      child: AspectRatio(
        aspectRatio: cameraAspectRatio,
        child: CameraPreview(_controller),
      ),
    ));

    stackChildren.add(
      Positioned(
        top: 0.0,
        left: 0.0,
        width: size.width,
        height: size.height,
        // 将比例和屏幕尺寸传入,用于坐标转换
        child: buildResult(cameraAspectRatio, size),
      ),
    );
  }
  return Stack(children: stackChildren);
}

2. 修正人脸检测框的坐标映射

在buildResult方法中,需要根据相机预览的实际显示区域,把人脸检测返回的原始坐标转换为屏幕上的正确位置:

Widget buildResult(double cameraAspectRatio, Size screenSize) {
  return CustomPaint(
    painter: FacePainter(
      detectedFaces: _detectedFaces,
      cameraAspectRatio: cameraAspectRatio,
      screenSize: screenSize,
      previewSize: _controller.value.previewSize!,
    ),
  );
}

class FacePainter extends CustomPainter {
  final List<Face> detectedFaces;
  final double cameraAspectRatio;
  final Size screenSize;
  final Size previewSize;

  FacePainter({
    required this.detectedFaces,
    required this.cameraAspectRatio,
    required this.screenSize,
    required this.previewSize,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.red
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2.0;

    for (var face in detectedFaces) {
      // 计算预览画面在屏幕上的实际显示区域
      Rect previewRect;
      if (cameraAspectRatio > screenSize.width / screenSize.height) {
        // 相机比例更宽,预览上下留黑边
        final previewHeight = screenSize.width / cameraAspectRatio;
        final topOffset = (screenSize.height - previewHeight) / 2;
        previewRect = Rect.fromLTWH(0, topOffset, screenSize.width, previewHeight);
      } else {
        // 相机比例更窄,预览左右留黑边
        final previewWidth = screenSize.height * cameraAspectRatio;
        final leftOffset = (screenSize.width - previewWidth) / 2;
        previewRect = Rect.fromLTWH(leftOffset, 0, previewWidth, screenSize.height);
      }

      // 将人脸原始坐标(基于相机分辨率)转换为屏幕坐标
      final faceRect = face.boundingBox;
      // 计算缩放比例
      final scaleX = previewRect.width / previewSize.width;
      final scaleY = previewRect.height / previewSize.height;

      final scaledLeft = faceRect.left * scaleX + previewRect.left;
      final scaledTop = faceRect.top * scaleY + previewRect.top;
      final scaledWidth = faceRect.width * scaleX;
      final scaledHeight = faceRect.height * scaleY;

      // 绘制修正后的人脸框
      canvas.drawRect(
        Rect.fromLTWH(scaledLeft, scaledTop, scaledWidth, scaledHeight),
        paint,
      );
    }
  }

  @override
  bool shouldRepaint(covariant FacePainter oldDelegate) {
    return oldDelegate.detectedFaces != detectedFaces;
  }
}

关键说明:

  • 相机预览分辨率和屏幕分辨率比例不同时,强制铺满会导致拉伸,AspectRatio能保证画面比例正确,多余区域留黑边。
  • 人脸检测返回的坐标基于相机原始预览尺寸,必须转换为屏幕上预览区域的坐标,才能让框准确对应到人脸上。
  • 若应用支持横竖屏切换,需额外处理方向变化时的坐标转换逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 19:19:54