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

如何基于camera:^0.10.4包在侧摄像头实现陀螺仪水平校正功能?

侧摄像头陀螺仪水平校正(Straighten)实现方案(基于camera: ^0.10.4)

核心依赖准备

除了camera: ^0.10.4,还需要添加传感器依赖获取设备姿态数据,在pubspec.yaml中补充:

dependencies:
  sensors: ^2.0.1

实现步骤

1. 初始化相机与传感器

  • 遍历可用相机,通过CameraLensDirection筛选目标侧摄像头(区分前置/后置)
  • 初始化CameraController并配置预览分辨率、音频开关等参数
  • 监听加速度计事件,实时获取设备姿态数据,用于角度计算与画面校正

2. 设备角度检测与图标状态控制

通过加速度计的x、y轴数据计算设备旋转角度,设置±5°的误差范围判断是否处于180°状态,以此切换中心图标颜色:

double calculateDeviceAngle(double x, double y) {
  double angle = atan2(y, x) * (180 / pi);
  return angle < 0 ? angle + 360 : angle;
}

// 判断是否处于180°允许范围内
bool get is180DegreeAligned {
  const tolerance = 5.0;
  return _deviceAngle >= 180 - tolerance && _deviceAngle <= 180 + tolerance;
}

3. 陀螺仪水平校正实现

通过Transform.rotate动态调整相机预览画面的旋转角度,抵消设备倾斜带来的画面偏移,保持画面水平:

Transform.rotate(
  // 用当前设备角度与180°的差值作为校正角度,让画面始终保持水平
  angle: (_deviceAngle - 180) * pi / 180,
  child: CameraPreview(_controller),
)

4. UI布局整合

用Stack将相机预览与中心图标叠加,图标颜色根据角度检测结果动态切换:

Stack(
  fit: StackFit.expand,
  children: [
    // 带校正的相机预览
    Transform.rotate(...),
    // 中心状态图标
    Center(
      child: Container(
        width: 60,
        height: 60,
        decoration: BoxDecoration(
          color: is180DegreeAligned ? Colors.green : Colors.red,
          shape: BoxShape.circle,
          border: Border.all(color: Colors.white, width: 3),
        ),
        child: const Icon(Icons.check, color: Colors.white, size: 30),
      ),
    ),
  ],
)

完整示例代码

import 'dart:async';
import 'dart:math';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:sensors/sensors.dart';

class GyroStraightenCamera extends StatefulWidget {
  const GyroStraightenCamera({Key? key}) : super(key: key);

  @override
  State<GyroStraightenCamera> createState() => _GyroStraightenCameraState();
}

class _GyroStraightenCameraState extends State<GyroStraightenCamera> {
  late CameraController _controller;
  late Future<void> _initializeControllerFuture;
  double _deviceAngle = 0.0;
  StreamSubscription? _accelerometerSubscription;
  static const double _angleTolerance = 5.0;

  @override
  void initState() {
    super.initState();
    _initCamera();
    _initAccelerometerListener();
  }

  Future<void> _initCamera() async {
    final cameras = await availableCameras();
    // 选择侧摄像头,这里以前置为例,可根据需求改为后置
    final sideCamera = cameras.firstWhere(
      (cam) => cam.lensDirection == CameraLensDirection.front,
      orElse: () => cameras.first,
    );

    _controller = CameraController(
      sideCamera,
      ResolutionPreset.medium,
      enableAudio: false,
    );
    _initializeControllerFuture = _controller.initialize();
    setState(() {});
  }

  void _initAccelerometerListener() {
    _accelerometerSubscription = accelerometerEvents.listen((event) {
      final angle = calculateDeviceAngle(event.x, event.y);
      setState(() => _deviceAngle = angle);
    });
  }

  double calculateDeviceAngle(double x, double y) {
    double angle = atan2(y, x) * (180 / pi);
    return angle < 0 ? angle + 360 : angle;
  }

  bool get is180DegreeAligned {
    return _deviceAngle >= 180 - _angleTolerance &&
        _deviceAngle <= 180 + _angleTolerance;
  }

  @override
  void dispose() {
    _controller.dispose();
    _accelerometerSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<void>(
        future: _initializeControllerFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            return Stack(
              fit: StackFit.expand,
              children: [
                Transform.rotate(
                  angle: (_deviceAngle - 180) * pi / 180,
                  child: CameraPreview(_controller),
                ),
                Center(
                  child: Container(
                    width: 60,
                    height: 60,
                    decoration: BoxDecoration(
                      color: is180DegreeAligned ? Colors.green : Colors.red,
                      shape: BoxShape.circle,
                      border: Border.all(color: Colors.white, width: 3),
                    ),
                    child: const Icon(Icons.check, color: Colors.white, size: 30),
                  ),
                ),
              ],
            );
          } else {
            return const Center(child: CircularProgressIndicator());
          }
        },
      ),
    );
  }
}

注意事项

  • 传感器数据滤波:加速度计数据存在抖动,可添加滑动平均等滤波逻辑,避免图标颜色频繁切换
  • 轴方向适配:不同设备的传感器轴定义可能有差异,需根据实际设备测试调整角度计算的x、y轴参数
  • 性能优化:Transform.rotate会带来一定性能开销,若需更高效的方案,可考虑在相机帧流中处理图像旋转
  • 权限配置:需在AndroidManifest.xml(相机+传感器权限)和Info.plist(相机权限)中添加对应权限声明

内容的提问来源于stack exchange,提问作者deep mangukiya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 20:54:55