2. 轨道不平顺样本的异常值识别与剔除

异常类型与识别思路

实测轨道不平顺序列中常见的异常有三类:尖峰(单点或极少数点幅值远超邻域,多由轨面异物、激光测头瞬时失锁引起)、丢帧(采样点缺失或保持为同一值,表现为局部方差骤降或重复值)、传感器跳变(相邻点差分出现阶跃,之后基线整体平移,多由传感器重新锁定或里程计滑移引起)。三类异常在时域上的形态不同,因此需要组合两类判据:

  • 统计阈值判据:以滑动窗口中位数与 MAD(中位数绝对偏差)构造稳健 z 分数,识别偏离局部主体的孤立点,对尖峰最敏感。
  • 局部差分判据:计算一阶差分 d[i]=x[i]−x[i−1],用差分的中位数绝对偏差设定门限,识别阶跃型跳变;同时用连续相同值计数识别丢帧保持段。

关键约束是:轨道不平顺本身含有真实的高频成分(短波不平顺),阈值过松会漏检,过紧会把真实短波当成尖峰削掉,从而在后续 PSD 高频段造成能量损失。因此剔除策略采用“只替换被判为异常的点、保留其余原始值”,并在剔除前后对比时域波形与差分序列,确认真实成分未被破坏。

可运行的识别与剔除实现

下面给出一个自洽的 Python 实现。输入为已按里程排序、等空间间隔采样的单通道高低不平顺序列(单位 mm,空间采样间隔 0.25 m)。示例数据由“低频真实不平顺 + 真实短波 + 人工注入的三类异常”合成,便于验证算法是否只命中注入点。

import numpy as np

rng = np.random.default_rng(20240501)

# ---------- 1. 构造示例序列:真实成分 + 注入异常 ----------
dx = 0.25                      # 空间采样间隔, m
n = 4000
s = np.arange(n) * dx

# 真实长波 + 短波不平顺(模拟轨道高低, mm)
truth = (1.8 * np.sin(2 * np.pi * s / 40.0)
         + 0.6 * np.sin(2 * np.pi * s / 6.0)
         + 0.25 * np.sin(2 * np.pi * s / 1.2)
         + rng.normal(0, 0.08, n))

x = truth.copy()
spike_idx = np.array([350, 351, 1200, 2600, 2601, 2602])
x[spike_idx] += np.array([6.5, 5.8, -7.2, 8.0, -6.4, 7.1])

drop_idx = np.arange(1800, 1812)          # 丢帧:保持上一有效值
x[drop_idx] = x[1800 - 1]

jump_at = 3200                            # 传感器跳变:阶跃 + 基线平移
x[jump_at:] += 3.0

# ---------- 2. 稳健统计阈值:滑动中位数 + MAD ----------
def rolling_median(a, win):
    half = win // 2
    pad = np.pad(a, half, mode="reflect")
    return np.array([np.median(pad[i:i + win]) for i in range(len(a))])

def mad(a):
    m = np.median(a)
    return np.median(np.abs(a - m))

win = 41                                  # 约 10 m 窗口
med = rolling_median(x, win)
resid = x - med
sigma_robust = 1.4826 * mad(resid)        # 稳健标准差估计
z = np.abs(resid) / max(sigma_robust, 1e-9)
mask_spike = z > 6.0                      # 统计阈值判据

# ---------- 3. 局部差分判据:阶跃跳变 ----------
d = np.diff(x, prepend=x[0])
d_sigma = 1.4826 * mad(d)
mask_jump = np.abs(d) > 8.0 * max(d_sigma, 1e-9)

# ---------- 4. 丢帧判据:连续相同值 ----------
same = np.zeros(n, dtype=bool)
run = 1
for i in range(1, n):
    run = run + 1 if x[i] == x[i - 1] else 1
    if run >= 3:                          # 连续 3 点及以上完全相同
        same[i - run + 1:i + 1] = True

mask_bad = mask_spike | mask_jump | same

# ---------- 5. 剔除:异常点用局部中位数替换 ----------
x_clean = x.copy()
x_clean[mask_bad] = med[mask_bad]

print("注入尖峰点数:", len(spike_idx))
print("统计阈值命中:", int(mask_spike.sum()))
print("差分判据命中:", int(mask_jump.sum()))
print("丢帧判据命中:", int(same.sum()))
print("总剔除点数:", int(mask_bad.sum()))
print("剔除前后 RMS 变化: %.4f -> %.4f mm"
      % (np.sqrt(np.mean((x - truth) ** 2)),
         np.sqrt(np.mean((x_clean - truth) ** 2))))

代码中三个判据各自独立,最后取并集。替换值使用滑动中位数而非全局均值,避免在长波起伏段引入额外偏差。运行后应观察到:统计阈值判据主要命中注入的尖峰点,差分判据命中跳变起始点,丢帧判据命中保持段;总剔除点数略大于注入点数属于正常现象(跳变点附近的差分也会超限)。

剔除前后的时域对比与判读

下图给出同一段序列在剔除前后的时域波形与一阶差分序列对照,用于判断异常是否被正确清除、真实短波是否保留。

剔除前后时域波形与一阶差分对比(示例数据) 空间采样间隔 0.25 m,共 4000 点;注入尖峰 6 点、丢帧 12 点、跳变 1 处 剔除前(原始序列) 尖峰 丢帧保持段 传感器跳变(基线平移) 剔除后(异常点以局部中位数替换) 尖峰已替换 保持段已恢复 跳变已校正 一阶差分序列(判据依据) ±8σ_d 门限 ±8σ_d 门限 原始差分 剔除后差分

判读要点:

  • 尖峰在原始差分中表现为一对方向相反的大幅跳变(先上冲后回落),剔除后差分回到门限内;
  • 丢帧保持段在原始序列中表现为一段水平直线,剔除后恢复为与邻域一致的起伏;
  • 传感器跳变在差分中只有一个方向的阶跃,剔除后基线平移被消除,但跳变点之后的真实起伏形态保持不变;
  • 若剔除后差分序列出现成片超门限的振荡,说明阈值过紧,真实短波被误判,应放宽 z 门限或增大滑动窗口。

参数选择与验证清单

参数示例取值作用调整方向
滑动窗口 win41 点(约 10 m)局部中位数与 MAD 的统计范围短波丰富时增大窗口,避免把真实短波当异常
统计阈值 z6.0尖峰判据灵敏度漏检多则降低,误删多则提高
差分门限倍数8.0 × σ_d阶跃跳变判据跳变幅度小则降低倍数
连续相同值长度≥3 点丢帧保持段判据采样间隔大时可适当减小
验证时不要只看剔除点数,应同时检查:剔除前后序列的 RMS 差值是否集中在异常段、剔除后差分是否全部落在门限内、以及后续 PSD 计算中高频段能量是否与未注入异常的参考序列一致。若三者同时满足,说明异常处理未破坏真实不平顺成分。
不要用全局均值或全局标准差做阈值:轨道不平顺沿里程存在明显的长波起伏,全局统计量会被长波主导,导致短波段的异常漏检或误删。必须使用滑动窗口的稳健统计量。