如何基于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
相关产品推荐
相关产品推荐

