本节摘要:调理车间把原始流洗成干净量测。本节给出去噪滤波(滑动窗口、中值、低通)的选型与参数账,野值剔除的窗口判据,坐标对齐与归一化的工序位置,以及特征提取从手工统计特征到学习特征的谱系——所有处理都按"延迟换可信"定价,窗口每开大一圈都要问一句值不值。
别以为传感器吐出来的数字就是能用的数据。上一节的采集管线送来的原始流里,混着电噪声的毛刺、探头偶发的野值、不同坐标系的读数和量纲各异的杂码——直接喂给状态估计,估计器会把毛刺当成真实的突然移动,把野值当成世界的剧变。调理车间的职责就一句话:在延迟预算内,把信号洗到估计器敢用的程度。它接在阵列之后、估计段之前,是"垃圾进"与"干净出"之间那道闸。
调理不是一锅炖,工序顺序错了会白干。标准次序是:剔野值在前,平滑滤波随后,坐标对齐再后,归一化收尾。野值必须先剔——均值类滤波会被单个野值拖偏一大截,先滤后剔等于把毒药搅进汤里。坐标对齐在滤波之后做,因为滤波在各自探头的数据流内部进行更便宜,跨坐标系的量除了对齐没有别的公共处理。归一化放最后,保证进估计段的数据量纲统一。
野值剔除的朴素判据是窗口偏离度:新读数与滑动窗口中位的偏差超过阈值倍数(常用中位绝对偏差的若干倍)即判野值,用窗口中位替代。选中位而不是均值做基准,正是因为中位对野值免疫。平滑滤波按信号特性选型:静态量(温度、电量)用指数滑动就够;缓变轨迹用低通滤波;有突跳的离散量(开关状态)用中值滤波保边沿。滤波窗口的开度直接翻译成延迟——窗口越长越平滑,也越滞后,这笔账在动态空域里是要用安全来还的。
坐标对齐与归一化的实现常年被轻视,值得单独给一段骨架:
class CoordinateAligner: """坐标对齐:把各探头读数统一到机体系。""" def __init__(self, extrinsics): # 外参:探头相对机体的安装位置与朝向(标定产物) self.extrinsics = extrinsics def to_body_frame(self, sensor_name, point): R, t = self.extrinsics[sensor_name] # 旋转与平移 return R.apply(point) + t # 换算到机体系 def normalize(feats, scales): """归一化:异量纲特征压到可比区间,供估计与学习。""" out = {} for key, value in feats.items(): lo, hi = scales[key] # 标定的工作范围 out[key] = max(0.0, min(1.0, (value - lo) / (hi - lo))) return out
量测洗干净了,多数任务还要把原始数据压缩成特征——下游真正消费的量。距离读数要变成"最近障碍距离与方位",图像要变成边缘、角点或直接变成神经网络的特征向量,声音要变成频谱能量。特征工程的老原则在这里依然成立:特征是带着任务假设的压缩,压掉了任务需要的维度,后面的算法再强也救不回来。
手工特征与学习特征的分工按数据量与任务稳定性划分:任务稳定、数据少、需要可解释时用手工特征(统计量、频谱、几何描述子);数据充足、任务漂移、有训练管线时用学习特征(嵌入向量)。工程上两者常并存——手工特征做兜底与校验,学习特征做主力,谁出问题另有参照系。
from collections import deque from statistics import median class Conditioner: """单路信号的调理器:剔野值 -> 平滑 -> 归一化。""" def __init__(self, win=9, k=4.0, alpha=0.3): self.win = deque(maxlen=win) # 滑动窗口 self.k = k # 野值判据倍数 self.alpha = alpha # 指数平滑系数 self.ema = None # 平滑值 self.rejected = 0 def step(self, x): # 剔野值:与窗口中位偏离超过 MAD 的 k 倍即弃用 if len(self.win) >= 5: med = median(self.win) mad = median([abs(v - med) for v in self.win]) or 1e-9 if abs(x - med) > self.k * mad: self.rejected += 1 x = med # 用中位替代,保住节拍 self.win.append(x) # 指数平滑:alpha 小则稳但迟,动态空域宜偏大 self.ema = x if self.ema is None else \ self.alpha * x + (1 - self.alpha) * self.ema return self.ema class FeaturePipeline: """从多路调理后的量测提取任务特征。""" def __init__(self, config): self.config = config def extract(self, snapshot, board): feats = {} # 距离类:最近障碍距离与方位(规划舱避障的口粮) readings = snapshot.get("lidar") if readings: feats["min_range"] = min(readings.values()) feats["min_bearing"] = min( readings, key=readings.get) # 惯性类:姿态角速度的窗口方差(颠簸程度) feats["imu_var"] = self._window_variance(snapshot.get("imu")) # 资源类:电量直接透传,量纲已归一 board_read = board.read("battery") feats["battery"] = board_read.value if board_read else 1.0 return feats def _window_variance(self, xs): if not xs or len(xs) < 2: return 0.0 m = sum(xs) / len(xs) return sum((x - m) ** 2 for x in xs) / len(xs)
代码里隐藏着与上游的契约:aligned_snapshot 来自 2.1 节的重采样工序,board 是第 1 章的单一事实源——调理车间不私设存储,一切经状态板中转。特征字典是给 2.3 节估计器的原料,字段名在全册保持一致。
坐标对齐的目标是把各探头的读数统一到机体系(以飞行器自身为原点):相机在机头的、雷达在翼下的,各自量到的目标位置都要换算成"相对我机"的坐标,随后融合才有意义。换算依赖外参标定——探头的安装位置与朝向,出厂时标定一次,震动漂移后要复标。归一化则把不同量纲压到可比区间:距离用米、速度用米每秒、电量用零到一的比率,估计器的噪声参数才有统一口径。两件事都不难,难在没有人把它们写进验收清单时——重影、漂移、置信度失真,多半是这两道工序被跳过的后遗症。

对照信号法的剧本也可以固化成注入器,方便回归:
class SignalInjector: """对照信号:往干净序列里叠污染,考调理器。""" def __init__(self, clean_series): self.clean = clean_series def with_spikes(self, rate=0.02, magnitude=8.0): return [x * magnitude if random() < rate else x for x in self.clean] # 野值注入 def with_noise(self, sigma=0.1): return [x + gauss(0, sigma) for x in self.clean] # 毛刺注入 def with_lag(self, ticks=3): return self.clean[ticks:] + [self.clean[-1]] * ticks # 时移注入 def grade_conditioner(cond, clean): """给调理器打分:收敛速度与滞后各占一秤。""" dirty = SignalInjector(clean).with_spikes() out = [cond.step(x) for x in dirty] lag = sum(abs(o - c) for o, c in zip(out, clean)) / len(clean) rough = sum(abs(b - a) for a, b in zip(out, out[1:])) return {"lag": lag, "roughness": rough} # 两头都要在限内
调理段的联调用对照信号法:向管线注入录制的真实数据叠加人工污染——叠毛刺看滤波曲线的收敛速度,叠野值看剔除计数与替代值是否合理,叠时移看重影是否出现。三组对照跑过,调理参数基本就有了出厂依据。
按症状排查:曲线迟钝(环境已变仪表未动)是窗口过大或平滑系数过小,往快的一头收;毛刺直通(估计器频繁误判突变)是窗口过小或野值阈值过松;周期性尖峰多半是某一探头的系统性干扰(供电、时钟串扰),回 2.1 节查硬件;剔除率异常(野值计数长期走高)别急着调阈值,先看是不是探头在退化——剔除率是探头健康的免费体检指标。调理车间的一切参数都该挂着注释:为何取这个值、在哪种空域下该改、改向哪头。
💡 关键直觉:调理不是越干净越好。滤波把曲线洗得越漂亮,延迟付得越多;动态空域里一根略糙但跟得紧的曲线,胜过一根光滑的旧闻。参数跟着空域属性走,不跟着审美走。
原始流已洗成干净量测,但量测仍是瞬时的。下一节进感知舱的心脏:状态估计——预测与更新如何把噪声里的碎片,缝合成值得托付的仪表读数。