如何计算距离 phone 抛出的高度

How to calculate distance from a phone thrown in height

在 Flutter 中,有传感器包 https://pub.dev/packages/sensors 可以知道速度 X、Y 和 Z。

我的问题是:如何计算 phone 抛出高度的距离?

示例:您在距离地面 0.5 米处投掷您的望远镜phone。 phone 距离您的手 1 米(因此距离地面 1.5 米)。

如何获取 1 米值?

谢谢大家!

这是我现在的代码(您需要安装传感器包):

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:sensors/sensors.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  List _velocityY = [];
  DateTime time;
  List<double> distances = [];

  List<StreamSubscription<dynamic>> _streamSubscriptions =
  <StreamSubscription<dynamic>>[];

  @override
  void initState() {
    super.initState();

    _streamSubscriptions
      .add(userAccelerometerEvents.listen((UserAccelerometerEvent event)
    {
      setState(() {
        if (event.y.abs() > 0.1) {
          if (time != null) {
            _velocityY.add(event.y);
          }
          //print((new DateTime.now().difference(time).inSeconds));
          if (_velocityY.length > 0) {
            distances.add(_velocityY[_velocityY.length - 1] * (new DateTime.now().difference(time).inMicroseconds) / 1000);
          }

          time = new DateTime.now();
        }

        //print('time' + time.toString());
      });
    }));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView(
        children: [
          for(double distance in distances.reversed.toList())
            Text(
              distance.toStringAsFixed(2),
              style: Theme.of(context).textTheme.headline4,
            ),
        ],
      ),// This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

如果我错了请纠正我,但是如果你试图在任何时刻直接从(过去和现在的)加速度计计算 phone 在 space 中的绝对位置数据,实际上是 very complex,主要是因为 phone 的加速度计在 x、y 和 z 方面的参考系是 phone 本身……并且 phones 没有固定的方向,尤其是在被抛来抛去的时候,此外......无论如何,它在空中的加速度为零。

这有点像被蒙上眼睛并被带到一个装有火箭的吊舱中进行 space 旅行,这些火箭随机向不同方向发射,并且被期望知道你最后在哪里。如果您在开始时知道自己的位置,并且您有能力跟踪沿途感觉到的每个加速度矢量,这在技术上是可行的……并将其与陀螺仪数据集成……将所有这些转换成一个单一的路径。

但是,幸运的是,我们仍然可以间接获得加速度计抛出的高度,以及其他一些测量值。

此解决方案假设:

  • 传感器包提供加速度值,而不是速度值(即使它声称提供速度,奇怪的是),因为accelerometers本身提供加速度.
  • 无论 phone 方向如何,总加速度等于 sqrt(x^2 + y^2 + z^2)。
  • 在投掷过程中加速度计读数为零(或仅重力读数)
  • This article in wired 是正确的,因为高度 = (重力 * 时间^2) / 8

我的代码的工作方式是:

  • 您(用户)按住“开始”按钮。
  • 当你把phone扔上去的时候,你自然会松开按钮,这会启动定时器,phone开始监听加速度计事件。
  • 我们假设 phone 在空中的总加速度为零(或仅为重力,取决于所选的加速度计数据类型)...所以我们实际上并没有尝试直接从加速度计数据:
  • 相反,我们仅使用加速度计来检测您何时捕捉到 phone... 通过使用阈值检测加速度的突然变化。
  • 达到此阈值时,计时器停止。
  • 现在我们有了从开始到结束的总时间值,可以计算高度了。

旁注:

  • 我使用的是 AccelerometerEvent(包括重力),而不是 UserAccelerometer 事件(不包括重力),因为我使用 UserAccelerometerEvent 在我的测试设备上得到了奇怪的数字(静止时非零)。
  • 它有助于轻轻抓住 phone ***
  • 我的数学可能已经完成了......我还没有让其他人看过这个......但至少这个答案让你开始了解一个有效的基本理论。
  • 我的 phone 落在了狗屎里所以我希望你接受这个答案。

准确性限制:

  • 放的高度和接的高度自然是不一致的。
  • 阈值是实验性的....您自己测试不同的值。我选择了 10。
  • 按下 GO 按钮和计时器开始之间可能有一些延迟。
  • *** 由于传感器包提供的加速度计更新频率非常低,因此可能无法始终准确检测到阈值,或者如果减速结束得太快则根本检测不到阈值。也许有一种方法可以使用不同的包以更高的频率获取更新。
  • 总是有可能过早按下 GO 按钮(而 phone 仍在您的手中),因此此时加速度将不为零,也许足以触发阈值。
  • 可能还没有考虑其他事情。

代码:

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Phone Throw Height',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Phone Throw Height'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<StreamSubscription<dynamic>> _streamSubscriptions =
      <StreamSubscription<dynamic>>[];

  DateTime? startTime;
  DateTime? endTime;
  bool isBeingThrown = false;
  final double GRAVITATIONAL_FORCE = 9.80665;
  final double DECELERATION_THRESHOLD = 10; // <---- experimental
  List<double> accelValuesForAnalysis = <double>[];

  @override
  void initState() {
    super.initState();

    _streamSubscriptions
        .add(accelerometerEvents.listen((AccelerometerEvent event) {
      if (isBeingThrown) {
        double x_total = pow(event.x, 2).toDouble();
        double y_total = pow(event.y, 2).toDouble();
        double z_total = pow(event.z, 2).toDouble();

        double totalXYZAcceleration = sqrt(x_total + y_total + z_total);

        // only needed because we are not using UserAccelerometerEvent
        // (because it was acting weird on my test phone Galaxy S5)
        double accelMinusGravity = totalXYZAcceleration - GRAVITATIONAL_FORCE;

        accelValuesForAnalysis.add(accelMinusGravity);
        if (accelMinusGravity > DECELERATION_THRESHOLD) {
          _throwHasEnded();
        }
      }
    }));
  }

  void _throwHasEnded() {
    isBeingThrown = false;
    endTime = DateTime.now();
    Duration totalTime = DateTime.now().difference(startTime!);
    double totalTimeInSeconds = totalTime.inMilliseconds / 1000;
    // this is the equation from the wired article
    double heightInMeters =
        (GRAVITATIONAL_FORCE * pow(totalTimeInSeconds, 2)) / 8;

    Widget resetButton = TextButton(
      child: Text("LONG PRESS TO RESET"),
      onPressed: () {},
      onLongPress: () {
        startTime = null;
        endTime = null;
        print(accelValuesForAnalysis.toString());
        accelValuesForAnalysis.clear();
        Navigator.pop(context);
        setState(() {
          isBeingThrown = false;
        });
      },
    );

    AlertDialog alert = AlertDialog(
      title: Text("Throw End Detected"),
      content: Text("total throw time in seconds was: " +
          totalTimeInSeconds.toString() +
          "\n" +
          "Total height was: " +
          heightInMeters.toString() +
          " meters. \n"),
      actions: [
        resetButton,
      ],
    );

    showDialog(
      barrierDismissible: false,
      context: context,
      builder: (BuildContext context) {
        return alert;
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SizedBox.expand(
        child: Container(
          color: Colors.green,
          //alignment: Alignment.center,
          child: SizedBox.expand(
            child: (!isBeingThrown)
                ? TextButton(
                    child: Text("GO!",
                        style: TextStyle(
                            color: Colors.white,
                            fontWeight: FontWeight.bold,
                            fontSize: 40)),
                    onPressed: () {
                      setState(() {
                        isBeingThrown = true;
                        startTime = DateTime.now();
                      });
                    },
                  )
                : Center(
                    child: Text("weeeeeeeeee!",
                        style: TextStyle(
                            color: Colors.white,
                            fontWeight: FontWeight.bold,
                            fontSize: 40)),
                  ),
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    // cancel the stream from the accelerometer somehow!! ugh!!!
    for (StreamSubscription<dynamic> subscription in _streamSubscriptions) {
      subscription.cancel();
    }
    super.dispose();
  }
}

您可能需要考虑使用设备的气压计来根据压力变化估算设备的高度。虽然不完全准确,但海拔变化是根据对海平面海拔差异的合理估计计算得出的。这应该有助于检测设备是否从高处坠落。

这是一个example posted on GitHub, using enviro_sensors plugin. You can also try using a similar plugin with support to null-safety: flutter_barometer_plugin