3. 标定原始数据质量检查与预处理

原始数据质量检查的对象与判据

USBL标定采集的原始数据通常来自三条独立链路:换能器输出的声学观测(时间戳、斜距、方位角、俯仰角、声速、质量因子)、姿态设备输出的横滚/俯仰(通常100 Hz级)、罗经输出的航向(通常10 Hz级)。三条链路采样率不同、时基不同,且船体运动、多径、气泡、遮挡都会引入异常。预处理的目标不是"美化数据",而是把三类数据对齐到同一时间基准,并剔除会污染偏角估计的观测,最终形成一份带质量标记的清洗数据集。

检查按四个维度展开:完整性(缺帧、丢包、字段为空)、时间同步(时间戳单调性、链路间固定延迟与漂移)、物理合理性(斜距范围、角度范围、姿态跳变)、统计异常(残差离群、质量因子偏低)。判据应事先写死,避免"看着不对就删"。

原始链路(采样率不同、时基不同) 声学观测 斜距 / 方位 / 俯仰 约 1 Hz,含质量因子 姿态(横滚/俯仰) 约 100 Hz 独立时间戳 航向(罗经) 约 10 Hz 存在磁偏/延迟 四维质量检查 ① 完整性 缺帧率 / 空字段 时间戳断档 ② 时间同步 单调性 / 固定延迟 漂移与插值对齐 ③ 物理合理性 斜距/角度量程 姿态跳变阈值 ④ 统计异常 残差 3σ / MAD 质量因子门限 统一时基对齐 以声学时间戳为基准 姿态/航向线性插值 估计链路固定延迟 插值跨度 > 0.2 s 标记 为不可信 输出对齐观测表 保留 四关全通过 质量因子达标 进入偏角估计 标记待复核 插值跨度偏大 单点残差 2σ~3σ 保留但降权 剔除 时间戳倒退/断档 超量程或姿态跳变 残差 > 3σ 且成簇 清洗数据集字段(每行一条对齐后的声学观测) t_acoustic · range · azimuth · elevation · roll · pitch · heading · quality_flag · interp_span · residual quality_flag:0=保留 1=降权 2=剔除;interp_span 记录姿态/航向插值跨度,用于后续加权 同时输出质量报告:缺帧率、最大时间断档、延迟估计值、剔除比例、各航向段有效观测数

完整性检查:先看"有没有",再看"够不够"

完整性检查分两层。第一层是记录级:逐条检查时间戳、斜距、方位角、俯仰角、横滚、俯仰、航向字段是否为空或为哨兵值(如 -999、NaN)。第二层是序列级:按标称采样间隔统计缺帧率,并找出最大时间断档。声学链路若标称 1 Hz,连续 3 s 以上无记录即视为断档,断档两侧的观测不能直接用于同一段偏角估计。

实操中建议先算三个指标再决定是否继续:声学记录总数与理论应得数之比、姿态/航向链路的最大断档时长、各字段空值比例。若声学缺帧率超过 10%,或姿态链路存在超过 1 s 的断档,应先回查采集日志,而不是急着做插值——插值无法恢复真实运动。

时间同步检查:单调性、固定延迟与漂移

三条链路各自打时间戳,常见问题是:时间戳非单调(设备重启、时钟回跳)、链路间存在固定延迟(罗经滤波、串口缓冲)、长时间运行后出现漂移。检查顺序如下:

  1. 单调性:对每条链路的时间戳做差分,出现负值即标记该记录及其后一段为可疑,需结合设备日志判断是重启还是单点跳变。
  2. 固定延迟估计:利用船体转向时航向变化与声学方位角变化的互相关,估计罗经相对声学链路的延迟;姿态链路同理,可用横滚变化与斜距/俯仰的耦合关系粗估。延迟估计值应写入质量报告,供后续偏角估计时补偿。
  3. 漂移检查:把整段数据按 10 min 分段,分别估计段内延迟,若段间延迟差异超过 50 ms,说明存在时钟漂移,需分段补偿而非全局补偿。

对齐时以声学时间戳为基准,对姿态和航向做线性插值。插值跨度超过 0.2 s 的对齐结果标记为"降权",超过 0.5 s 直接标记为"剔除"——因为船体在 0.5 s 内可能已完成数度的姿态变化,插值误差会直接进入偏角估计。

物理合理性与统计异常检查

物理合理性用硬阈值:斜距应落在换能器量程内(例如 5~500 m),方位角在 ±180°、俯仰角在 ±90° 内,横滚/俯仰单步变化超过 5°/s、航向单步变化超过 10°/s 视为跳变。硬阈值只剔除"不可能"的值,不剔除"可疑"的值。

统计异常用软判据:先做一次粗偏角估计(或直接用安装初值),计算每条观测的残差,用 MAD 稳健估计尺度,残差超过 3 倍 MAD 的观测标记为离群。注意区分"孤立离群"与"成簇离群":孤立离群多为单点多径,可降权保留;成簇离群往往对应某段航向或某个距离段系统性偏差,应整段剔除并回查该段航迹是否满足激励要求。

不要用"剔除后残差变小"来反向证明清洗正确。清洗判据必须在看结果之前确定,否则容易把真实的安装偏角信号当成异常删掉。建议保留原始数据副本,清洗只生成新表并记录每条记录的处置理由。

可执行的质量检查脚本(Python)

下面脚本读取三份 CSV(声学、姿态、航向),完成完整性、单调性、对齐插值与残差离群标记,输出清洗数据集与质量报告。示例数据由脚本内构造,便于直接运行验证逻辑。

import numpy as np
import pandas as pd

# ---------- 构造示例原始数据(实际使用时替换为读取CSV) ----------
rng = np.random.default_rng(7)
t_ac = np.arange(0, 600, 1.0)                 # 声学 1 Hz,600 s
t_att = np.arange(0, 600, 0.01)               # 姿态 100 Hz
t_hdg = np.arange(0, 600, 0.1)                # 航向 10 Hz

ac = pd.DataFrame({
    "t": t_ac,
    "range": 80 + 20*np.sin(t_ac/40) + rng.normal(0, 0.3, t_ac.size),
    "azimuth": 30*np.sin(t_ac/60) + rng.normal(0, 0.2, t_ac.size),
    "elevation": 5*np.sin(t_ac/50) + rng.normal(0, 0.1, t_ac.size),
    "quality": rng.integers(60, 100, t_ac.size),
})
att = pd.DataFrame({
    "t": t_att,
    "roll": 3*np.sin(t_att/30) + rng.normal(0, 0.05, t_att.size),
    "pitch": 2*np.sin(t_att/45) + rng.normal(0, 0.05, t_att.size),
})
hdg = pd.DataFrame({
    "t": t_hdg,
    "heading": (90 + 40*np.sin(t_hdg/70)) % 360 + rng.normal(0, 0.1, t_hdg.size),
})

# 人为注入问题:声学缺帧、时间戳回跳、姿态断档、单点离群
ac = ac.drop(index=[50, 51, 52, 300]).reset_index(drop=True)
ac.loc[120, "t"] = ac.loc[119, "t"] - 0.5          # 时间戳回跳
ac.loc[200, "range"] = 999.0                        # 超量程
att = att[(att.t < 250) | (att.t > 251.5)].reset_index(drop=True)  # 1.5 s 断档

# ---------- ① 完整性 ----------
def completeness(df, name, nominal_dt):
    n_missing = int(np.sum(np.diff(df.t) > 1.5*nominal_dt))
    max_gap = float(np.max(np.diff(df.t))) if len(df) > 1 else 0.0
    empty = int(df.isna().sum().sum())
    print(f"[{name}] 记录数={len(df)} 断档次数={n_missing} 最大间隔={max_gap:.2f}s 空值={empty}")
    return max_gap

completeness(ac, "声学", 1.0)
completeness(att, "姿态", 0.01)
completeness(hdg, "航向", 0.1)

# ---------- ② 时间同步:单调性 ----------
def monotonic_mask(df):
    dt = np.diff(df.t, prepend=df.t.iloc[0])
    return dt > 0

ac_ok = monotonic_mask(ac)
print("声学时间戳非单调条数:", int((~ac_ok).sum()))
ac = ac[ac_ok].reset_index(drop=True)

# ---------- ③ 物理合理性 ----------
phys_ok = (
    ac.range.between(5, 500) &
    ac.azimuth.between(-180, 180) &
    ac.elevation.between(-90, 90) &
    (ac.quality >= 50)
)
ac["phys_ok"] = phys_ok

# ---------- ④ 对齐插值 ----------
ac["roll"] = np.interp(ac.t, att.t, att.roll)
ac["pitch"] = np.interp(ac.t, att.t, att.pitch)
ac["heading"] = np.interp(ac.t, hdg.t, hdg.heading)

# 插值跨度:取相邻姿态样本间隔
att_dt = np.diff(att.t, prepend=att.t.iloc[0])
ac["interp_span"] = np.interp(ac.t, att.t, att_dt)

# ---------- ⑤ 残差离群(用简单球面模型做粗残差) ----------
# 粗模型:假设安装偏角为0,仅用于演示离群检测
az = np.deg2rad(ac.azimuth)
el = np.deg2rad(ac.elevation)
x = ac.range*np.cos(el)*np.cos(az)
y = ac.range*np.cos(el)*np.sin(az)
z = ac.range*np.sin(el)
res = np.sqrt((x - x.mean())**2 + (y - y.mean())**2 + (z - z.mean())**2)
mad = 1.4826*np.median(np.abs(res - np.median(res)))
ac["residual"] = res
ac["outlier"] = res > 3*mad

# ---------- ⑥ 质量标记 ----------
ac["quality_flag"] = 0
ac.loc[ac.interp_span > 0.2, "quality_flag"] = 1
ac.loc[ac.interp_span > 0.5, "quality_flag"] = 2
ac.loc[~ac.phys_ok, "quality_flag"] = 2
ac.loc[ac.outlier & (ac.quality_flag == 0), "quality_flag"] = 1

clean = ac[ac.quality_flag < 2].copy()
print("保留:", int((ac.quality_flag == 0).sum()),
      "降权:", int((ac.quality_flag == 1).sum()),
      "剔除:", int((ac.quality_flag == 2).sum()))
clean.to_csv("usbl_clean.csv", index=False)

运行后应看到:声学链路因人为注入的缺帧与回跳出现断档与非单调记录,姿态链路报告 1.5 s 断档,超量程的 999 m 记录被剔除,插值跨度超过 0.2 s 的对齐结果被降权。这些输出与注入的问题一一对应,说明检查逻辑生效。

质量报告与通过条件

清洗完成后应形成一份简短质量报告,至少包含下表字段。通过条件建议事先约定:声学缺帧率 ≤ 10%、最大时间断档 ≤ 3 s、剔除比例 ≤ 15%、各主要航向段有效观测数 ≥ 30。任一条件不满足,应补采或调整航迹,而不是继续做偏角估计。

指标含义示例值通过条件
声学缺帧率缺失记录数 / 理论记录数0.7%≤ 10%
最大时间断档相邻记录最大时间间隔3.0 s≤ 3 s
链路延迟估计罗经/姿态相对声学的固定延迟0.12 s写入报告并补偿
剔除比例quality_flag=2 的记录占比6.5%≤ 15%
各航向段有效数每个 30° 航向区间保留观测数最少 42≥ 30

清洗数据集与质量报告一并归档,后续偏角估计只使用清洗数据集,并在结果中注明所用数据的剔除比例与延迟补偿值,保证标定过程可追溯。