using System; using MyParking.Shared; namespace MultiWheelC.StateEstimation { /// /// 根据连续有效的Detour世界位姿和真实时间差估算车辆二维速度。 /// public sealed class VelocityEstimator2D { public const double DefaultLinearFilterTimeConstantSeconds = 0.15; public const double DefaultAngularFilterTimeConstantSeconds = 0.20; private readonly FirstOrderLowPassFilter _worldVelocityXFilter; private readonly FirstOrderLowPassFilter _worldVelocityYFilter; private readonly FirstOrderLowPassFilter _angularVelocityFilter; private bool _hasPreviousSample; private Pose2D _previousPoseInWorld; private double _previousTimestampSeconds; /// /// 创建使用默认0.15s线速度和0.20s角速度时间常数的估计器。 /// public VelocityEstimator2D() : this( DefaultLinearFilterTimeConstantSeconds, DefaultAngularFilterTimeConstantSeconds) { } /// /// 创建使用指定线速度和角速度滤波时间常数的估计器。 /// public VelocityEstimator2D( double linearFilterTimeConstantSeconds, double angularFilterTimeConstantSeconds) { _worldVelocityXFilter = new FirstOrderLowPassFilter( linearFilterTimeConstantSeconds); _worldVelocityYFilter = new FirstOrderLowPassFilter( linearFilterTimeConstantSeconds); _angularVelocityFilter = new FirstOrderLowPassFilter( angularFilterTimeConstantSeconds); } /// /// 使用一个新的有效定位样本更新并返回车辆状态。 /// public VehicleState Update( Pose2D poseInWorld, double sampleTimestampSeconds) { NumericGuard.EnsureFinite( poseInWorld, nameof(poseInWorld)); NumericGuard.EnsureFiniteNonNegative( sampleTimestampSeconds, nameof(sampleTimestampSeconds)); var normalizedPoseInWorld = new Pose2D( poseInWorld.XMeters, poseInWorld.YMeters, AngleMath.NormalizeRadians( poseInWorld.YawRadians)); if (!_hasPreviousSample) { return Reset( normalizedPoseInWorld, sampleTimestampSeconds); } var deltaTimeSeconds = sampleTimestampSeconds - _previousTimestampSeconds; if (deltaTimeSeconds <= 0.0) { throw new ArgumentOutOfRangeException( nameof(sampleTimestampSeconds), "新定位样本的单调时间戳必须严格大于上一帧。"); } var rawVelocityXInWorld = (normalizedPoseInWorld.XMeters - _previousPoseInWorld.XMeters) / deltaTimeSeconds; var rawVelocityYInWorld = (normalizedPoseInWorld.YMeters - _previousPoseInWorld.YMeters) / deltaTimeSeconds; var rawAngularVelocity = AngleMath.ShortestDifferenceRadians( normalizedPoseInWorld.YawRadians, _previousPoseInWorld.YawRadians) / deltaTimeSeconds; var filteredTwistInWorld = new Twist2D( _worldVelocityXFilter.Update( rawVelocityXInWorld, deltaTimeSeconds), _worldVelocityYFilter.Update( rawVelocityYInWorld, deltaTimeSeconds), _angularVelocityFilter.Update( rawAngularVelocity, deltaTimeSeconds)); _previousPoseInWorld = normalizedPoseInWorld; _previousTimestampSeconds = sampleTimestampSeconds; return new VehicleState( sampleTimestampSeconds, normalizedPoseInWorld, filteredTwistInWorld, true); } /// /// 使用当前定位重新建立差分基准,并返回速度无效的零速状态。 /// public VehicleState Reset( Pose2D poseInWorld, double sampleTimestampSeconds) { NumericGuard.EnsureFinite( poseInWorld, nameof(poseInWorld)); NumericGuard.EnsureFiniteNonNegative( sampleTimestampSeconds, nameof(sampleTimestampSeconds)); _previousPoseInWorld = new Pose2D( poseInWorld.XMeters, poseInWorld.YMeters, AngleMath.NormalizeRadians( poseInWorld.YawRadians)); _previousTimestampSeconds = sampleTimestampSeconds; _hasPreviousSample = true; _worldVelocityXFilter.Reset(); _worldVelocityYFilter.Reset(); _angularVelocityFilter.Reset(); return new VehicleState( sampleTimestampSeconds, _previousPoseInWorld, Twist2D.Zero, false); } /// /// 清除差分基准和全部滤波历史,使下一帧重新初始化估计器。 /// public void Reset() { _hasPreviousSample = false; _previousPoseInWorld = Pose2D.Identity; _previousTimestampSeconds = 0.0; _worldVelocityXFilter.Reset(); _worldVelocityYFilter.Reset(); _angularVelocityFilter.Reset(); } } }