Canmetry
打开工作台
返回博客

大型 ASC 文件导入 MATLAB 内存不够怎么办

canMessageImport 读取大型 Vector ASC 日志时容易内存不足。四种把解码后的 CAN 信号导入 MATLAB 的方法:先截取、分块读取、转成 BLF,或在外部解码。

.asc.dbcMATLAB

手上有一个带 CAN 报文和时间戳的 Vector .asc 日志、一个描述信号的 DBC,还有 MATLAB。最直接的做法是先 canMessageImport 再 canSignalTimetable,可一遇到长时间的记录,就会卡住或者报内存不足。本文先讲清楚原因,再介绍四种解决办法,从最快的到最灵活的。

为什么 canMessageImport 会内存不够

每一帧都变成一行

canMessageImport(file, "Vector", db) 会读取整个 ASC 文件,返回一个每帧一行的 timetable:时间戳、ID、通道、数据字节、标志位,附上数据库时还有这条报文解码出的信号。读入时不做任何筛选,你从来不看的总线上的帧,和你需要的帧占同样多的内存。

解码又多出一份

接着 canSignalTimetable(msgs, "VehicleStatus") 会根据上面的行,为每个报文再建一个 timetable。有一段时间里,原始报文和解码后的信号同时在内存里,MATLAB 通常就是在这一步撑不住的。

多大算大型 ASC 文件

ASC 是纯文本。一帧经典 CAN 报文写成一行大约 50 字节,繁忙的整车总线每秒有几千帧。三路通道记录一小时,轻松就是 1–2 GB 的文本、几千万行。下面每种方法的思路都一样:不要让 MATLAB 看到你不会解码的帧。

方法一:先把 ASC 文件变小

如果你知道哪几分钟、哪些报文是关键,这是最快的办法。

截取时间段和需要的 ID

ASC 截取与拆分工具在浏览器里流式读取文件,不限大小。它会按通道列出每个 CAN ID 及其帧数,并画出整段记录的报文密度。选好时间段,取消勾选不需要的 ID,就能下载一个更小的 .asc。日志行原样复制,文件头也会保留,canMessageImport 读取它和读原文件一样。

拆成几份,逐个导入

如果整段记录都要用,就按时长拆分,在循环里逐个解码。用 matfile 把解码结果追加到 MAT 文件里,内存里始终只有一份:

db    = canDatabase("vehicle.dbc");
parts = dir("drive_part*.asc");

out = matfile("drive_speed.mat", "Writable", true);
out.time  = zeros(0, 1);
out.speed = zeros(0, 1);
n = 0;

for k = 1:numel(parts)
    msgs = canMessageImport(parts(k).name, "Vector", db);
    sig  = canSignalTimetable(msgs, "VehicleStatus");
    t = seconds(sig.Time);
    out.time(n+1:n+numel(t), 1)  = t;
    out.speed(n+1:n+numel(t), 1) = sig.VehicleSpeed;
    n = n + numel(t);
    clear msgs sig
end

拆分时保留原始时间戳(默认就是这样),追加的数据才会落在同一条时间轴上。

方法二:在 MATLAB 里分块读取

如果文件只能留在一台只装了 MATLAB 的电脑上,那就边读边筛。

只保留需要的帧

textscan 可以每次读固定行数。用正则表达式从每一行取出帧 ID,把要保留的行写进新文件,再导入这个新文件:

keep = ["1A0" "2B4"];          % ID 写法与日志中一致
fin  = fopen("drive.asc", "r");
fout = fopen("drive_small.asc", "w");
first = true;

while ~feof(fin)
    c = textscan(fin, "%s", 200000, "Delimiter", "\n", "Whitespace", "");
    lines = string(c{1});
    tok = regexp(lines, "^\s*\d+\.\d+\s+\d+\s+(\w+)\s+(Rx|Tx)", "tokens", "once");
    isFrame = ~cellfun(@isempty, tok);
    if first                    % 复制第一帧之前的文件头
        fprintf(fout, "%s\n", lines(1:find(isFrame, 1) - 1));
        first = false;
    end
    hit = false(size(lines));
    hit(isFrame) = ismember(cellfun(@(t) string(t{1}), tok(isFrame)), keep);
    fprintf(fout, "%s\n", lines(hit));
end
fclose(fin); fclose(fout);

msgs = canMessageImport("drive_small.asc", "Vector", canDatabase("vehicle.dbc"));

内存里只有一块 20 万行的数据,每 GB 大约需要几分钟。上面的正则只匹配经典 CAN 行,日志里有 CANFD 行的话需要扩展一下。

边处理边写出结果

日志特别长时,可以和方法一里的 matfile 循环结合:筛选一块、解码、追加信号,再处理下一块。matfile 创建的 MAT 文件是 7.3 版本,可以超过 2 GB。

方法三:转成 BLF,只读需要的部分

BLF 是 Vector 的压缩二进制日志,体积比 ASC 小好几倍,而且 blfread 可以在读取时筛选。

转换日志

在 CANoe 或 CANalyzer 里用 Logging File Conversion。没有 Vector 许可证的话,python-can 几行就能完成:

import can

reader = can.ASCReader("drive.asc")
with can.Logger("drive.blf") as writer:
    for msg in reader:
        writer.on_message_received(msg)

带筛选读取

blfread 可以指定通道、数据库、ID 过滤和时间范围,只有匹配的帧才会进入内存:

db   = canDatabase("vehicle.dbc");
msgs = blfread("drive.blf", 1, "Database", db, ...
               "CANStandardFilter", hex2dec(["1A0" "2B4"]), ...
               "TimeRange", [600 900]);
sig  = canSignalTimetable(msgs, "VehicleStatus");

如果记录仪本来就输出 MF4,那就继续用 MF4。它按通道组存储信号,MATLAB 可以只读取你指定的信号和时间范围。

方法四:在 MATLAB 外解码,只导入信号

能交给 MATLAB 的最小数据,就是解码后的信号本身:每个信号一个时间向量、一个数值向量,没有原始帧。

用 Python

python-can 逐行读取 ASC 文件,cantools 用你的 DBC 解码每一帧,内存里只保留你要的信号:

import can, cantools, numpy as np
from scipy.io import savemat

db = cantools.database.load_file("vehicle.dbc")
wanted = {db.get_message_by_name(n).frame_id for n in ["VehicleStatus", "BrakeStatus"]}
cols = {}
for msg in can.ASCReader("drive.asc"):
    if msg.is_error_frame or msg.arbitration_id not in wanted:
        continue
    for name, value in db.decode_message(msg.arbitration_id, msg.data, decode_choices=False).items():
        t, v = cols.setdefault(name, ([], []))
        t.append(msg.timestamp)
        v.append(value)

savemat("drive_signals.mat", {n: {"time": np.array(t), "value": np.array(v)} for n, (t, v) in cols.items()})

在 MATLAB 里 s = load("drive_signals.mat"),每个信号就是一个结构体,可以直接画图。

不写代码

ASC 转 MAT 工具在浏览器里做同样的事。给每个通道关联一个 DBC,它会为每个信号写出一个名为 报文名_信号名 的结构体,带 time、value、unit、message 和 channel 字段。不需要 Vehicle Network Toolbox,load 就能打开。单个文件最大 150 MiB,一次最多五个;更大的日志可以先用 ASC 截取与拆分按大小拆分(比如每份 140 MB),再一次性转换。

怎么选

你的情况推荐方法
知道需要的时间段和报文方法一:先截取再导入
整段日志都要导入 MATLAB方法一:拆分后循环导入,或方法四
电脑上只能用 MATLAB方法二:分块读取
有 CANoe,或有大量日志要归档方法三:转成 BLF
只需要解码后的信号方法四:Python 或 ASC 转 MAT 工具

总结

canMessageImport 处理短日志没问题,但它要先读入所有帧才能解码。面对大型 ASC 文件,关键是减少 MATLAB 要承载的数据:先截取或拆分文件、读取时筛选、换成支持筛选读取的二进制格式,或者在 MATLAB 外解码、只导入信号。大多数情况下,第一种方法最快:先用 ASC 截取与拆分把日志变小,再照常导入。

.blf.mf4.asc

打开一份日志,看见信号。

在浏览器里打开 BLF、MF4 或 ASC 文件,不会上传任何内容。

打开工作台

继续阅读