276 lines
9.1 KiB
C#
276 lines
9.1 KiB
C#
using System;
|
|
using MyParking.Shared;
|
|
|
|
namespace MultiWheelC.StateEstimation
|
|
{
|
|
/// <summary>
|
|
/// 根据连续有效的Detour世界位姿和真实时间差估算车辆二维速度。
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// 创建使用默认0.15s线速度和0.20s角速度时间常数的估计器。
|
|
/// </summary>
|
|
public VelocityEstimator2D()
|
|
: this(
|
|
DefaultLinearFilterTimeConstantSeconds,
|
|
DefaultAngularFilterTimeConstantSeconds)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建使用指定线速度和角速度滤波时间常数的估计器。
|
|
/// </summary>
|
|
public VelocityEstimator2D(
|
|
double linearFilterTimeConstantSeconds,
|
|
double angularFilterTimeConstantSeconds)
|
|
{
|
|
_worldVelocityXFilter =
|
|
new FirstOrderLowPassFilter(
|
|
linearFilterTimeConstantSeconds);
|
|
_worldVelocityYFilter =
|
|
new FirstOrderLowPassFilter(
|
|
linearFilterTimeConstantSeconds);
|
|
_angularVelocityFilter =
|
|
new FirstOrderLowPassFilter(
|
|
angularFilterTimeConstantSeconds);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取是否已经保存了可用于下一次差分的位姿基准。
|
|
/// </summary>
|
|
public bool HasPreviousSample =>
|
|
_hasPreviousSample;
|
|
|
|
/// <summary>
|
|
/// 使用一个新的有效定位样本更新并返回车辆状态。
|
|
/// </summary>
|
|
public VehicleState Update(
|
|
Pose2D poseInWorld,
|
|
double sampleTimestampSeconds)
|
|
{
|
|
EnsureFinitePose(
|
|
poseInWorld,
|
|
nameof(poseInWorld));
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新位姿差分基准但保留当前滤波速度,避免定位跳变形成虚假速度尖峰。
|
|
/// </summary>
|
|
public VehicleState RebasePreservingVelocity(
|
|
Pose2D poseInWorld,
|
|
double sampleTimestampSeconds)
|
|
{
|
|
EnsureFinitePose(
|
|
poseInWorld,
|
|
nameof(poseInWorld));
|
|
EnsureFiniteNonNegative(
|
|
sampleTimestampSeconds,
|
|
nameof(sampleTimestampSeconds));
|
|
|
|
var normalizedPoseInWorld =
|
|
new Pose2D(
|
|
poseInWorld.XMeters,
|
|
poseInWorld.YMeters,
|
|
AngleMath.NormalizeRadians(
|
|
poseInWorld.YawRadians));
|
|
|
|
_previousPoseInWorld =
|
|
normalizedPoseInWorld;
|
|
_previousTimestampSeconds =
|
|
sampleTimestampSeconds;
|
|
_hasPreviousSample = true;
|
|
|
|
var hasValidVelocityEstimate =
|
|
_worldVelocityXFilter.IsInitialized &&
|
|
_worldVelocityYFilter.IsInitialized &&
|
|
_angularVelocityFilter.IsInitialized;
|
|
|
|
var retainedTwistInWorld =
|
|
hasValidVelocityEstimate
|
|
? new Twist2D(
|
|
_worldVelocityXFilter.Value,
|
|
_worldVelocityYFilter.Value,
|
|
_angularVelocityFilter.Value)
|
|
: Twist2D.Zero;
|
|
|
|
return new VehicleState(
|
|
sampleTimestampSeconds,
|
|
normalizedPoseInWorld,
|
|
retainedTwistInWorld,
|
|
hasValidVelocityEstimate);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 使用当前定位重新建立差分基准,并返回速度无效的零速状态。
|
|
/// </summary>
|
|
public VehicleState Reset(
|
|
Pose2D poseInWorld,
|
|
double sampleTimestampSeconds)
|
|
{
|
|
EnsureFinitePose(
|
|
poseInWorld,
|
|
nameof(poseInWorld));
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 清除差分基准和全部滤波历史,使下一帧重新初始化估计器。
|
|
/// </summary>
|
|
public void Reset()
|
|
{
|
|
_hasPreviousSample = false;
|
|
_previousPoseInWorld = Pose2D.Identity;
|
|
_previousTimestampSeconds = 0.0;
|
|
|
|
_worldVelocityXFilter.Reset();
|
|
_worldVelocityYFilter.Reset();
|
|
_angularVelocityFilter.Reset();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查位姿是否由有限数值组成。
|
|
/// </summary>
|
|
private static void EnsureFinitePose(
|
|
Pose2D pose,
|
|
string parameterName)
|
|
{
|
|
if (!IsFinite(pose.XMeters) ||
|
|
!IsFinite(pose.YMeters) ||
|
|
!IsFinite(pose.YawRadians))
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
parameterName,
|
|
"速度估计使用的车辆位姿必须由有限数值组成。");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查数值是否为非负有限值。
|
|
/// </summary>
|
|
private static void EnsureFiniteNonNegative(
|
|
double value,
|
|
string parameterName)
|
|
{
|
|
if (!IsFinite(value) || value < 0.0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
parameterName,
|
|
"速度估计使用的采样时刻必须是非负有限值。");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 判断数值是否可用于速度估计。
|
|
/// </summary>
|
|
private static bool IsFinite(double value)
|
|
{
|
|
return !double.IsNaN(value) &&
|
|
!double.IsInfinity(value);
|
|
}
|
|
}
|
|
}
|