MATLAB 多传感器时间对齐 用 interp1 建立公共时间网格并限制外推

实测环境:MATLAB 25.2.0.2998904(R2025b),Windows 64 位(PCWIN64),2026-10-15。基础 MATLAB 即可。示例数据在代码中生成,不读取外部文件。保存为 interpolation_demo.m,切换到对应文件夹后运行:

matlab 复制代码
results = interpolation_demo;

程序输出 results/02_aligned.csv、results/02_interpolation.png 和 results/02_interpolation.mat。本文采用已实测的修正版:17 个断言通过,日志输出 INTERPOLATION PASS;CSV 写出再读回后,8 个 UTC 时间戳保持唯一,最大时间误差为 0 秒。

两台设备都记录了六个数,并不意味着它们的第六个数发生在同一时刻。直接按行相减,可能把真实变化误当成设备偏差。

下面把两组不规则时间戳的数据对齐到一个公共网格,再用一个已知真值的例子说明:时间对齐之后,插值误差仍可能影响两台设备的差值。最后给出域外查询和长缺口的明确处理办法。

公共时间基准比插值方法更先确定

本文把两个设备的时间都表示成同一个起点之后的秒数:

matlab 复制代码
t0 = datetime(2026,10,15,8,0,0,'TimeZone','UTC');
tA = [0 0.7 1.4 2.2 3.1 4.0]';
tB = [0.4 1.1 2.0 2.7 3.6 4.5]';

t0 是本次合成实验的人为起点。真实数据若已经有带时区的时间戳,可以选定一个共同起点,再分别用 seconds(timeA-t0) 和 seconds(timeB-t0) 转成数值秒。不要把 A 的第一条记录和 B 的第一条记录分别设为零,否则设备启动时差会被悄悄抹掉。

时间戳排序、时区统一与设备时钟校正是不同问题。插值不会估计时钟偏差,也不会自动修复时钟漂移;如果两个设备的时间基准本来不同,需要先处理同步问题。

在调用插值前,本例检查了长度一致、数值有限、时间严格递增。实际数据出现重复时间戳时,应先根据测量含义决定取均值、保留特定记录或报错。仅用 unique 丢掉记录,可能掩盖冲突值。

查询网格只覆盖共同有效区间

A 的时间范围是 [0,4] 秒,B 的时间范围是 [0.4,4.5] 秒。因此共同有效区间是 [0.4,4] 秒。

设网格间隔 dt=0.5 秒,并要求网格锚定同一个 t0。第一个合法网格编号是 ceil(0.4/0.5)=1,最后一个是 floor(4/0.5)=8,得到:

text 复制代码
0.5  1.0  1.5  2.0  2.5  3.0  3.5  4.0

"公共区间的起点"不一定是"公共网格的第一个点"。明确网格锚点,才能让不同批次或不同设备组合生成可比较的时间轴。本例还设置断言,防止区间虽然相交、选定网格却没有任何点时继续对空结果运算。当前这组数据不会触发该断言。

随后对两个设备使用同一组查询坐标:

matlab 复制代码
Aq = interp1(tA,yA,tq,'linear',NaN);
Bq = interp1(tB,yB,tq,'linear',NaN);

interp1 的采样坐标需要互不相同;linear 在邻近采样点间计算线性插值。这里显式把域外值设置为 NaN,让边界政策直接体现在代码中。接口细节见 R2025b interp1 文档。

真偏差恒为 3 为什么对齐结果会变化

定义已知真值:

g ( t ) = t 2 + 0.5 t + 2 g(t)=t^2+0.5t+2 g(t)=t2+0.5t+2

A 记录 g(tA),B 记录 g(tB)+3。两个设备在同一真实时刻的差值始终为 3,数据没有随机噪声。

但两组数据的邻近采样点不相同,各自的直线插值也不同。本次 MATLAB 实测对齐结果如下,为便于阅读保留两位小数:

相对时间 s A 插值 B 插值 B 减 A 相对真偏差 3 的误差
0.5 2.60 5.56 2.96 -0.04
1.0 3.62 6.56 2.94 -0.06
1.5 5.07 8.20 3.13 0.13
2.0 7.12 10.00 2.88 -0.12
2.5 9.68 12.60 2.92 -0.08
3.0 12.58 15.68 3.10 0.10
3.5 16.20 19.08 2.88 -0.12
4.0 20.00 23.20 3.20 0.20

以 4 秒为例,A 正好有样本,所以 A 的插值等于真值 20。B 需要在 3.6 秒和 4.5 秒之间插值,结果是 23.2,因此差值变成 3.2。

这个误差可以直接推导。对二次函数 g(t),用区间端点 a,b 连接成直线,在区间内得到的误差为:

L ( t ) − g ( t ) = ( t − a ) ( b − t ) L(t)-g(t)=(t-a)(b-t) L(t)−g(t)=(t−a)(b−t)

4 秒处 B 的误差就是 (4-3.6)*(4.5-4)=0.2。它来自曲率与采样间隔,并不能被解释成设备偏差突然变化了 0.2。

在长度为 h 的区间内,本例的线性插值误差最大为 h^2/4。把查询网格从 0.5 秒改成 0.01 秒,只会在同一条分段直线上生成更多点,不会缩短原始采样缺口,也不会降低这条直线相对真值的固有误差。

外推必须单独决定

程序再查询 A 数据范围之外的 -0.5 秒和 4.5 秒:

  • 使用 'linear',NaN:两个结果都是 NaN
  • 使用 'linear','extrap':分别得到 1.4 和 23.8
  • 已知合成真值分别为 2.0 和 24.5,所以外推误差分别为 -0.6 和 -0.7

'extrap' 只是允许把端点附近的方法继续用于范围之外,并不保证趋势仍然成立。这里的二次曲率已经足以让线性外推偏离真值。

不要把"interp1 默认不外推"当作适用于所有方法的规则。R2025b 文档说明 pchip、spline、makima 的默认域外行为与 linear 不同。需要保持统一边界策略时,显式提供最后一个参数更清楚。

区间内部的长缺口也可能不应填补

只保留公共区间,并不能避免跨越很长的停采段。程序另设样本时间 [0,1,4],把允许桥接的最大间隔定为 1 秒。查询 2 秒、3 秒虽然位于整体数据范围内,却跨越了 1 秒到 4 秒的三秒缺口,因此保留为 NaN。

做法是找到每个查询点左、右两侧对应的样本时刻,比较间隔:

matlab 复制代码
left  = interp1(tGap,tGap,qGap,'previous',NaN);
right = interp1(tGap,tGap,qGap,'next',NaN);
bridgesLongGap = (right-left)>1;
zGap(bridgesLongGap) = NaN;

恰好落在原始样本上的查询点,两侧坐标相同,仍保留原始观测。这里的 1 秒只是实验规则;真实阈值应根据对象变化速度、采样约定和允许误差确定。图中的红叉只标出被拒绝的位置,纵坐标借用已知合成真值帮助阅读,并不是填补后的有效数据。

CSV 导出也要验证时间精度

初版代码在内存中生成了正确的半秒网格,直接把默认格式的 datetime 写入 CSV 后,却只保留到了整秒。实际导出的 8 行数据只有 5 个不同时间戳,重新解析后的最大时间误差达到 0.5 秒。例如,原本的 1 秒与 1.5 秒都写成了 15-Oct-2026 08:00:01。

数值插值通过断言,并不表示导出的时间轴也正确。修正版先把时间明确转换到 UTC,再指定毫秒格式:

matlab 复制代码
timestampFormat = 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''';
aligned.Timestamp.TimeZone = 'UTC';
aligned.Timestamp.Format = timestampFormat;

这里格式中的 Z 是字面标记,真正的时区转换由 TimeZone='UTC' 完成,不能给未经转换的本地时间直接加上 Z。修正版首行时间为 2026-10-15T08:00:00.500Z,末行为 2026-10-15T08:00:04.000Z。

完整代码写出 CSV 后,把时间列按字符串读回,再按指定格式解析,检查 UTC 标记、时间戳唯一性、实际时间网格和相对秒数。实测 8 个时间戳全部唯一,最大回读时间误差为 0 秒。SSS 的毫秒精度足以表示本例的半秒网格;如果你的数据含有更细的时间分辨率,应提高格式精度并保留同样的往返检查。格式与写出接口分别见 R2025b datetime 和 R2025b writetable。

运行后检查输出

本次实测公共网格有 8 个点,对齐差值相对真偏差的最大绝对误差显示为 0.2;域外结果为 NaN;显式外推结果为 [1.4,23.8];长缺口规则拒绝 2 个查询点。数值检查与 CSV 回读检查全部通过。

CSV 保留公共时间戳、相对秒数、A、B、差值和已知真值下的误差。图 02_interpolation.png 包含原始样本与公共网格、对齐后差值、外推误差和长缺口政策四个面板。

实际测量通常没有已知真值,CSV 中最后一列便不能直接计算。此时应保留质量标记、插值方法、邻近样本间隔和域外状态,避免下游分析把所有网格点都视为同等可靠。对于状态码、开关量和类别值,也应重新选择适当的时间对齐方法,不能照搬连续量的线性插值。

完整可运行代码

保存为 interpolation_demo.m,文件名必须与第一行函数名一致。

matlab 复制代码
function results = interpolation_demo(outDir)
% Common-time-grid interpolation and explicit extrapolation policy.
% Base MATLAB, R2025b target. Run: results = interpolation_demo;
if nargin < 1
    outDir = fullfile(fileparts(mfilename('fullpath')), 'results');
end
if ~exist(outDir, 'dir'), mkdir(outDir); end
% All times are seconds relative to the SAME origin.
t0 = datetime(2026,10,15,8,0,0,'TimeZone','UTC');
tA = [0 0.7 1.4 2.2 3.1 4.0]';
tB = [0.4 1.1 2.0 2.7 3.6 4.5]';
g = @(t) t.^2 + 0.5*t + 2;
yA = g(tA);
yB = g(tB) + 3;
assert(all(isfinite([tA;yA;tB;yB])), 'Nonfinite input.');
assert(all(diff(tA)>0) && all(diff(tB)>0), 'Times must increase strictly.');
assert(numel(tA)==numel(yA) && numel(tB)==numel(yB), 'Length mismatch.');
dt = 0.5;
lo = max(tA(1),tB(1)); hi = min(tA(end),tB(end));
assert(lo <= hi, 'The records do not overlap.');
% Anchor the common grid to t0, and keep only the overlap.
kFirst = ceil(lo/dt); kLast = floor(hi/dt);
assert(kFirst <= kLast, 'No common grid point at this dt.');
tq = (kFirst:kLast)'*dt;
Aq = interp1(tA,yA,tq,'linear',NaN);
Bq = interp1(tB,yB,tq,'linear',NaN);
assert(all(isfinite([Aq;Bq])), 'Unexpected out-of-range interpolation.');
expectedA = [2.60 3.62 5.07 7.12 9.68 12.58 16.20 20.00]';
expectedB = [5.56 6.56 8.20 10.00 12.60 15.68 19.08 23.20]';
assert(max(abs(Aq-expectedA)) < 1e-11, 'A interpolation mismatch.');
assert(max(abs(Bq-expectedB)) < 1e-11, 'B interpolation mismatch.');
% The true offset is 3, but two different interpolation errors remain.
offsetError = (Bq-Aq)-3;
assert(abs(max(abs(offsetError))-0.20) < 1e-11, 'Offset-error mismatch.');
% Domain boundary and extrapolation are separate, explicit experiments.
tOutside = [-0.5; 4.5];
noExtrap = interp1(tA,yA,tOutside,'linear',NaN);
withExtrap = interp1(tA,yA,tOutside,'linear','extrap');
assert(all(isnan(noExtrap)), 'Outside queries must be NaN.');
assert(max(abs(withExtrap-[1.4;23.8])) < 1e-11, 'Extrapolation mismatch.');
extrapError = withExtrap-g(tOutside);
assert(max(abs(extrapError-[-0.6;-0.7])) < 1e-11, 'Extrapolation error mismatch.');
% A minimal long-gap policy: preserve samples, reject bridged gaps > 1 s.
tGap = [0;1;4]; yGap = g(tGap); qGap = [0;0.5;1;2;3;4];
zGap = interp1(tGap,yGap,qGap,'linear',NaN);
left = interp1(tGap,tGap,qGap,'previous',NaN);
right = interp1(tGap,tGap,qGap,'next',NaN);
bridgesLongGap = (right-left)>1;
zGap(bridgesLongGap) = NaN;
assert(isequal(isnan(zGap),[false;false;false;true;true;false]), ...
    'Long-gap policy failed.');
aligned = table(t0+seconds(tq),tq,Aq,Bq,Bq-Aq,offsetError, ...
    'VariableNames',{'Timestamp','TimeSeconds','A','B','BminusA','OffsetError'});
% v2: preserve fractional seconds and explicitly identify UTC in the CSV.
timestampFormat = 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''';
aligned.Timestamp.TimeZone = 'UTC';
aligned.Timestamp.Format = timestampFormat;
csvPath = fullfile(outDir,'02_aligned.csv');
writetable(aligned,csvPath);
opts = detectImportOptions(csvPath);
opts = setvartype(opts,'Timestamp','string');
roundTrip = readtable(csvPath,opts);
assert(all(endsWith(roundTrip.Timestamp,"Z")), ...
    'interpolation:CSVZone','CSV timestamps must explicitly identify UTC.');
parsedTime = datetime(roundTrip.Timestamp,'InputFormat',timestampFormat,'TimeZone','UTC');
assert(numel(parsedTime)==numel(tq) && numel(unique(parsedTime))==numel(tq), ...
    'interpolation:CSVUnique','CSV timestamps must remain distinct.');
csvRoundTripMaxError = max(abs(seconds(parsedTime-t0)-tq));
assert(csvRoundTripMaxError < 1e-9, ...
    'interpolation:CSVGrid','CSV timestamps must preserve the intended time grid.');
assert(max(abs(roundTrip.TimeSeconds-tq)) < 1e-9, ...
    'interpolation:CSVSeconds','CSV relative seconds must preserve the intended grid.');
fprintf('INTERPOLATION CSV roundtrip_points = %d; max_time_error_seconds = %.16g\n', ...
    numel(unique(parsedTime)),csvRoundTripMaxError);
tFine = linspace(-0.5,4.5,501)';
fig = figure('Color','w','Position',[100 100 1100 750]);
tiledlayout(2,2,'TileSpacing','compact','Padding','compact');
nexttile;
plot(tA,yA,'o',tB,yB,'s',tq,Aq,'-',tq,Bq,'-','LineWidth',1.2);
xlabel('Seconds from common origin'); ylabel('Value'); grid on;
title('Different timestamps, one query grid');
legend('A samples','B samples','A linear','B linear','Location','northwest');
nexttile;
plot(tq,Bq-Aq,'o-','LineWidth',1.2); hold on; yline(3,'k--');
xlabel('Seconds from common origin'); ylabel('B - A'); grid on;
title('True offset = 3; interpolation errors differ');
nexttile;
plot(tFine,g(tFine),'k-',tA,yA,'o',tOutside,withExtrap,'rx','LineWidth',1.2);
xline(tA(1),'--'); xline(tA(end),'--');
xlabel('Seconds from common origin'); ylabel('Value'); grid on;
title('Endpoint linear extrapolation misses curvature');
legend('Known synthetic truth','Samples','Extrapolated','Location','northwest');
nexttile;
plot(tGap,yGap,'ko',qGap,zGap,'b.-','LineWidth',1.2); hold on;
plot(qGap(bridgesLongGap),g(qGap(bridgesLongGap)),'rx','MarkerSize',9);
xlabel('Time (s)'); ylabel('Value'); grid on;
title('Reject interpolation across gaps > 1 s');
legend('Samples','Accepted interpolation','Rejected query locations', ...
    'Location','northwest');
exportgraphics(fig,fullfile(outDir,'02_interpolation.png'),'Resolution',160);
results = struct('querySeconds',tq,'A',Aq,'B',Bq, ...
    'maxOffsetError',max(abs(offsetError)), ...
    'outsideQuery',tOutside,'extrapolated',withExtrap, ...
    'extrapolationError',extrapError,'longGapRejected',nnz(bridgesLongGap));
disp(aligned);
fprintf('INTERPOLATION common_points = %d; max_offset_error = %.12g\n', ...
    numel(tq),results.maxOffsetError);
fprintf('INTERPOLATION extrapolated = [%.12g %.12g]; errors = [%.12g %.12g]\n', ...
    withExtrap,extrapError);
fprintf('INTERPOLATION long_gap_rejected = %d\n',results.longGapRejected);
fprintf('INTERPOLATION PASS\n');
save(fullfile(outDir,'02_interpolation.mat'),'results');
end

参考资料

文中的时间戳、数据、误差分析和实验代码均为本文构造。

相关推荐
kyle~8 小时前
Fail-Safe 故障安全
机器人·传感器
北纬的小熊9 小时前
Matlab Simulink电力电子仿真-全桥 LLC 谐振变换器调频控制(PFM)仿真研究
嵌入式硬件·matlab
kyle~1 天前
实时系统通信抖动(Jitter)
计算机视觉·机器人·接口隔离原则·传感器
星夜离尘561 天前
MATLAB 清洗 CSV 采样日志:重复时间戳、无效行、分组统计与自动出图
开发语言·matlab·数据分析·数据清洗·csv
kyle~1 天前
多模态传感器软件同步后的数据关联时序误差测算
计算机视觉·机器人·相机·传感器
星夜离尘561 天前
MATLAB FFT 单边幅度谱:归一化、奇偶长度与频谱泄漏的完整实验
开发语言·matlab·信号处理·频谱分析·fft
星夜离尘561 天前
MATLAB 5 点滑动平均:filter 与 conv 的输出对齐,以及 2 样本延时的含义
开发语言·算法·matlab·滤波·fir
道道_Love2 天前
AI 调 MATLAB 用 MCP 太难用?单元设计规范和测试用例,用原生 AI 一次生成
人工智能·matlab·设计规范
菜鸟~noob2332 天前
【电子战】第31篇:3D AOA 定位——方位+俯仰联合
开发语言·matlab·3d