日志获取与预处理:从飞控到分析台的完整链路

做定位漂移分析,第一步不是看数据,而是拿到干净、完整、时间对齐的日志。我见过太多人花几小时调参,结果发现日志本身就有问题——时间戳错位、消息丢失、甚至飞控型号选错了日志格式。嗯,这节课我们就把这层窗户纸捅破。

核心原则:日志预处理的质量,直接决定漂移诊断的成败。垃圾进,垃圾出——这句老话在飞控分析里一点不假。

3.1 日志导出:两种主流地面站的实操对比

目前最常用的地面站就两个:Mission Planner 和 QGroundControl。我个人习惯两个都装,因为不同飞控固件对它们的支持度不一样。

对比项 Mission Planner QGroundControl
日志格式 默认 .bin(二进制),可转 .log .ulog(PX4原生格式)
导出路径 Flight Data → Download Log Analyze → Log Download
数据完整性 自动校验CRC,极少丢包 依赖USB/Telem链路质量
适用固件 ArduPilot 全系列 PX4 为主,也支持 ArduPilot

具体操作其实不复杂。Mission Planner 里,连接飞控后点「Flight Data」,再点「Download Log」,会列出所有历史日志。我建议按时间排序,选最近一次飞行的日志。QGroundControl 则是进「Analyze」页面,点「Log Download」,它会自动识别飞控上的 ulog 文件。

我的小技巧:导出时顺手把日志文件名改成「日期_机型_飞行任务」,比如「20250315_M300_漂移测试」。不然过两周你自己都分不清哪个是哪个。

3.2 二进制日志解析:pyMAVLink 实战

拿到原始日志后,我们需要把它解析成 Python 能处理的结构。pyMAVLink 是官方推荐的库,我几乎每天都在用。

先装库:

pip install pymavlink

然后写个解析脚本。这是我常用的模板:

from pymavlink import mavutil

# 打开二进制日志
log_path = "20250315_M300_漂移测试.bin"
mlog = mavutil.mavlink_connection(log_path)

# 获取所有消息类型
msg_types = set()
while True:
    msg = mlog.recv_match(blocking=False)
    if msg is None:
        break
    msg_types.add(msg.get_type())

print(f"日志包含 {len(msg_types)} 种消息类型")
print(sorted(msg_types))

运行后你会看到类似这样的输出:

日志包含 47 种消息类型
['AHRS2', 'ATT', 'BAT', 'CTUN', 'GPS', 'GPS2', 'IMU', 'MAG', 'NKF1', 'NKF2', 'POS', 'RCIN', 'RCOU', 'SIM', 'STAT', 'TIME', ...]

这里要注意:不是所有消息都有用。做定位漂移分析,我们主要关注 GPS、POS、ATT、NKF1/NKF2(EKF 状态)这几类。其他像 BAT、RCIN 可以先不管。

我曾经踩过的坑:有一次解析日志时发现 GPS 消息全为空,折腾了半天才发现是飞控的 GPS 模块在飞行中途断电了。所以解析完后,先检查关键消息的数量是否合理——比如飞行 10 分钟,GPS 消息应该有 6000 条左右(10Hz)。

3.3 数据清洗:把脏数据挡在门外

解析出来的数据,直接扔进分析脚本?别急。我总结了三类必须清洗的脏数据:

  1. 飞控启动阶段的数据——飞控刚上电时,传感器还没稳定,GPS 还没定位。这段数据会严重干扰分析。
  2. 明显超出物理极限的值——比如 GPS 速度显示 500 m/s,或者高度突然跳变 1000 米。
  3. 重复或乱序的时间戳——日志记录偶尔会出 bug,导致时间戳回退或重复。

清洗代码示例:

import pandas as pd
import numpy as np

def clean_log_data(df):
    # 1. 去掉飞控启动阶段(前 10 秒)
    df = df[df['timestamp'] > df['timestamp'].min() + 10]
    
    # 2. 去掉物理极限外的值
    df = df[(df['gps_speed'] > 0) & (df['gps_speed'] < 100)]
    df = df[(df['altitude'] > -100) & (df['altitude'] < 5000)]
    
    # 3. 去掉时间戳重复的行
    df = df.drop_duplicates(subset=['timestamp'])
    
    # 4. 按时间戳排序
    df = df.sort_values('timestamp').reset_index(drop=True)
    
    return df

经验之谈:清洗时别太激进。我曾经一刀切掉所有高度跳变超过 5 米的数据,结果把真实的 GPS 漂移也给切了。建议先可视化看看数据分布,再定阈值。

3.4 时间戳对齐:多传感器融合的命门

定位漂移分析最头疼的问题之一,就是不同传感器的时间戳基准不一样。GPS 用的是 UTC 时间,IMU 用的是飞控内部时钟,EKF 输出又有自己的时间轴。这三者不对齐,你根本没法判断漂移是发生在哪个环节。

我的对齐策略分三步:

  1. 统一基准:把所有时间戳转为相对于日志开始时间的偏移量(单位:秒)。
  2. 线性插值:IMU 频率高(200-400Hz),GPS 频率低(5-10Hz)。把低频数据插值到高频时间轴上。
  3. 交叉验证:用 EKF 的预测时间戳做校验,看对齐后误差是否在 1ms 以内。

代码实现:

def align_timestamps(gps_df, imu_df, ekf_df):
    # 统一基准:取最早时间戳为 0
    t0 = min(gps_df['timestamp'].min(), 
             imu_df['timestamp'].min(), 
             ekf_df['timestamp'].min())
    
    gps_df['t_rel'] = gps_df['timestamp'] - t0
    imu_df['t_rel'] = imu_df['timestamp'] - t0
    ekf_df['t_rel'] = ekf_df['timestamp'] - t0
    
    # 以 IMU 时间轴为基准(最高频)
    imu_time = imu_df['t_rel'].values
    
    # 对 GPS 数据进行线性插值
    gps_interp = np.interp(imu_time, 
                           gps_df['t_rel'].values, 
                           gps_df['latitude'].values)
    
    # 对 EKF 数据进行线性插值
    ekf_interp = np.interp(imu_time,
                           ekf_df['t_rel'].values,
                           ekf_df['pos_north'].values)
    
    return imu_time, gps_interp, ekf_interp

注意:插值会引入误差。如果 GPS 丢星超过 3 秒,那段数据就别插值了,直接标记为无效。我吃过这个亏——插值出来的「假 GPS」让我误判了漂移方向。

3.5 本章知识体系总览

下面这张图概括了从原始日志到可分析数据的完整流程。我建议你把它打印出来贴在工位上。

日志获取与预处理流程 日志获取与预处理流程 Mission Planner 导出 QGroundControl 导出 原始日志文件 pyMAVLink 解析二进制日志 → 提取 GPS/POS/ATT/EKF 消息 数据清洗:去启动段 / 去异常值 / 去重复时间戳 时间戳对齐:统一基准 → 线性插值 → 交叉验证 ✅ 干净、对齐的分析数据集 关键:不同地面站导出的日志格式不同,但解析后的清洗和对齐流程是通用的

你看,整个流程其实就四个环节:导出 → 解析 → 清洗 → 对齐。每个环节都有坑,但只要你按这套流程走一遍,拿到手的日志就是「干净」的。后面做漂移分析时,你才能确信问题出在飞控上,而不是出在数据上。

一句话总结:日志预处理不是体力活,是技术活。花 80% 的时间把数据搞干净,剩下 20% 的时间做分析,这才是高效的工作流。