☰
MATLAB SEGY读取工具包:从字节流到地震剖面
2026/10/19 17:42:13 网站建设 项目流程

简介:这套 Matlab SEGY 读取工具包面向物探与地震数据处理人员,基于 SegyMAT 实现 SEGY 格式地震数据的读取、解析、写入与转换。包内共有 75 个文件,以 63 个 m 脚本和 7 个 fig 界面文件为主体,同时提供 1 个 segy 示例数据、2 个 dat 数据及许可证与版本说明文件,压缩后仅 516KB,小巧完整。核心函数 ReadSegy、WriteSegy 支持批量导入导出,配套多个 GUI 交互界面便于无命令行基础者操作;同时包括 Su2Segy、Segy2Su 等格式转换工具以及 wiggle、绘图模块,可直接用于地震剖面显示与简单分析。目前已有 646 人学习下载,适合需要快速读取标准地震道数据、开展格式转换或二次开发的 Matlab 用户。借助该工具包可省去从零编写底层解析逻辑的繁琐过程,直接调用现成接口完成道头信息提取、数据矩阵获取和基础可视化。

1. 从SEGY到地震剖面:这个MATLAB工具包到底解决什么问题

拿到野外采集回来的SEGY文件,直接用文本编辑器打开,满屏都是乱码;用商业解释软件看剖面,又得装一套笨重的license。这个MATLAB工具包干的事很简单:把一个SEGY文件完整拆成文本头、二进制头、道头和数据体,再组装成MATLAB矩阵,让你在脚本里自由处理地震数据。它适合两类人:一类是做勘探数据处理的学生,想快速把SEGY读进MATLAB做滤波、频谱分析;另一类是搞算法验证的工程师,不想被重型软件绑架,只想命令行里一条命令把数据拽出来。SEGY这套格式在地下地球物理领域用了三十多年,看似原始,但看懂它的结构,很多数据处理痛点就迎刃而解。

2. SEGY格式与工具包设计:先把二进制黑匣子拆开

2.1 文本卷头与二进制卷头:3200 + 400 字节的元信息

SEGY文件开头是一块固定长度的元信息区。文件最前面的3200字节是文本卷头(Textual Header),按标准规定每行80字节、共40行,存放文件描述、采集参数、处理记录等人工可读信息。早期的SEGY文件这段文本用EBCDIC编码,后来不少文件直接用ASCII,所以读取时得做两种编码的兼容。

接着是400字节的二进制卷头(Binary Header),所有关键数值参数都藏在这里。它里面有几个字段做读取时绕不开:采样间隔(单位微秒)、每道采样点数、数据格式码。采样点数决定了一个道有多长,格式码则告诉你怎么解释后续的数据体字节。格式码是SEGY里最容易翻车的字段之一——1代表IBM浮点、2代表32位定点整数、3代表16位定点整数、4才是IEEE浮点。很多早期的采集数据用IBM浮点,不转换直接用fread读出来就是一堆莫名其妙的数。

2.2 道头与道数据:每条地震道的独立信息块

二进制卷头之后,整个文件剩余部分被划分为一条条的道(trace)。每条道内部又分成两部分:240字节的道头(Trace Header)和若干字节的采样数据。道头里记录的是一次激发对应的野外参数——道号、炮点坐标、检波点坐标、偏移距、采样点数等。这些信息在后期做道编辑、切除、动校正时非常有用。

数据体的大小怎么算?每道数据字节数 = 道头240字节 + 采样点数 × 单采样点字节数。单采样点字节数由格式码决定:IBM浮点和32位整数都是4字节,16位整数是2字节,IEEE浮点可能是4字节或8字节。工具包读取时先解析二进制头拿到采样点数和格式码,算出单道大小,然后就能按这个固定步长在整个文件里循环跳读。注意标准SEGY还有一种扩展文本卷头(Extended Textural Header),插在二进制卷头之后、第一道之前,占用的也是3200字节,遇到这种文件总偏移量就要多加3200。

2.3 工具包目录结构与模块划分

这套工具包按功能拆成几个独立的m文件,目录结构大致长这样:

segy_tools/ ├── read_segy.m # 主入口函数,返回结构体 ├── parse_text_header.m # 文本卷头解析(支持EBCDIC/ASCII) ├── parse_binary_header.m # 二进制卷头关键字段提取 ├── parse_trace_header.m # 单道道头解析 ├── read_trace_data.m # 按格式码读取一组道数据 ├── ibm2ieee.m # IBM浮点转IEEE浮点 ├── plot_seismic.m # 地震剖面绘图 └── example_demo.m # 完整调用示例

主入口read_segy的返回值是一个结构体S,里面包含textHeader、binHeader、traceHeaders、data四个字段。data是二维矩阵,行对应采样点、列对应道号,这也是后续绝大部分处理脚本最舒服的输入格式。工具包把格式解析、数据读取、可视化分开,你想替换某一段实现时不用动其它模块。接口上保持一个函数干一件事,方便你在此基础上扩展。

3. 核心模块拆解:一个SEGY文件从字节流到数据矩阵的完整链路

3.1 文本头解析:EBCDIC与ASCII的自动识别

文本卷头在标准SEGY里是EBCDIC编码,但实际流传的数据很多是ASCII,所以解析函数得先探测编码类型。常见的做法是统计字节分布:EBCDIC文本里大量字节落在0x81到0xC9、0xE5到0xF9区间,ASCII文本则集中在0x20到0x7E之间。

function textStr = parse_text_header(fid) % fid 为已打开的文件句柄,当前游标位于文件起始处 % 读取3200字节文本卷头原始字节 rawBytes = fread(fid, 3200, 'uint8')'; % 统计可打印ASCII字符比例,超过一半则判定为ASCII编码 asciiRatio = sum(rawBytes >= 32 & rawBytes <= 126) / 3200; if asciiRatio > 0.5 textStr = native2unicode(rawBytes, 'US-ASCII'); else textStr = ebcdic2ascii(rawBytes); % 转义EBCDIC end % 按80字符一行切分,共40行 textStr = reshape(textStr, 80, 40)'; textStr = cellstr(textStr); end

这段代码先读原始字节,通过统计可打印ASCII字符占比来决定走哪条解码路径。asciiRatio大于0.5的判断依据是EBCDIC编码中可读字符的码点分布与ASCII差异极大,误判概率很低。native2unicode按US-ASCII解码后,reshape成80列40行的矩阵,每行正好是一条文本卷头记录。返回的cellstr数组方便你逐行查看文件来源和采集参数。

3.2 二进制头参数提取与数据格式分发

二进制卷头里真正影响后续读取的只有那么四五个字段,但它们的字节偏移是固定的,直接用fseek跳过文本卷头再按偏移读取最稳妥。

function binInfo = parse_binary_header(fid) % fid: 文件句柄,已越过3200字节文本卷头区域 % 采样间隔:偏移3217-3218,单位微秒 fseek(fid, 3216, 'bof'); % 3217-1=3216,MATLAB从0开始计偏移 sampleInterval = fread(fid, 1, 'int16'); % 每道采样点数:偏移3221-3222 fseek(fid, 3220, 'bof'); samplesPerTrace = fread(fid, 1, 'int16'); % 数据格式码:偏移3225-3226 fseek(fid, 3224, 'bof'); formatCode = fread(fid, 1, 'int16'); binInfo = struct('sampleInterval', sampleInterval, ... 'samplesPerTrace', samplesPerTrace, ... 'formatCode', formatCode); end

注意fseek的第三个参数bof表示相对文件开头计算偏移。偏移3216对应规范里的字节3217,因为MATLAB计数从0开始而规范从1开始。采样间隔用的是微秒,换算成秒要除以1e6。formatCode是后面读取数据体的分派依据,常见的1、3、4三种值分别对应IBM浮点、16位整数、IEEE浮点。如果你的数据是别的格式码,比如8字节IEEE浮点,这个工具包需要自行扩展数据格式分支。

3.3 道数据循环读取与IBM浮点转换

读道数据是整个工具包里最重的一段逻辑,下面这个函数完成单道数据的读取和格式转换:

function [traceData, traceHeaders] = read_trace_data(fid, binInfo, traceCount) % 读取全部道数据,返回矩阵和道头结构 samples = binInfo.samplesPerTrace; formatCode = binInfo.formatCode; % 根据格式码确定单采样点的字节数 switch formatCode case 1 % IBM浮点,4字节 sampleBytes = 4; readFunc = @ibm2ieee; case 2 % 32位定点整数 sampleBytes = 4; readFunc = @(x) double(typecast(x, 'int32')); case 3 % 16位定点整数 sampleBytes = 2; readFunc = @(x) double(typecast(x, 'int16')); case 4 % IEEE浮点,4字节 sampleBytes = 4; readFunc = @(x) double(typecast(x, 'single')); otherwise error('不支持的数据格式码: %d', formatCode); end traceData = zeros(samples, traceCount); % 预分配矩阵 traceHeaders = cell(traceCount, 1); for k = 1:traceCount traceHeaderBytes = fread(fid, 240, 'uint8'); traceHeaders{k} = parse_trace_header(traceHeaderBytes); % 读取采样点数个原始字节 rawData = fread(fid, samples * sampleBytes, 'uint8'); traceData(:, k) = readFunc(rawData); end end

这段代码把数据读取拆成两步:先读240字节道头,再读定长的采样数据。预分配traceData矩阵是关键一步,如果边读边拼接矩阵,数据量大时性能下降非常明显。readFunc按格式码分派到不同的转换函数,IBM浮点走专门的ibm2ieee,其它格式直接用typecast重解释字节。typecast不会修改底层数据,只是换一种数据类型去看同一段内存,所以比用fread直接指定精度更可控。

3.4 IBM浮点转IEEE浮点的bit级运算

IBM浮点格式是SEGY数据的老朋友,结构上跟IEEE完全不一样:1位符号、7位指数(基数为16)、24位尾数。转换的核心逻辑就是对这32个bit做重新解释。

function value = ibm2ieee(bytesVector) % bytesVector: 4字节的uint8数组,大端序 % 将4字节拼成一个32位无符号整数 ibmInt = uint32(0); for i = 1:4 ibmInt = bitor(bitshift(ibmInt, 8), uint32(bytesVector(i))); end % 提取符号位、指数、尾数 signVal = bitget(ibmInt, 32); exponent = bitshift(bitand(ibmInt, uint32(hex2dec('7F000000'))), -24); fraction = bitand(ibmInt, uint32(hex2dec('00FFFFFF'))); % 零值特殊处理 if exponent == 0 && fraction == 0 value = 0; return; end % IBM浮点: (-1)^sign * (fraction/2^24) * 16^(exponent-64) mantissa = double(fraction) / 2^24; expVal = 16^(double(exponent) - 64); value = (1 - 2 * double(signVal)) * mantissa * expVal; end

IBM浮点的指数是以16为底的,这是最容易踩坑的点——如果你用2为底去还原指数,转换出来的振幅值会差出好几个数量级。代码里bitor和bitshift配合把4个字节按大端序拼成uint32,这是因为SEGY文件规定大端字节序。如果你读到的数据在PC上表现异常,优先怀疑字节序问题而不是IBM转换本身。

4. 跑通一个真实SEGY文件:从命令行到地震剖面

4.1 环境准备与快速调用

工具包对MATLAB版本要求不高,R2016b以上就行,核心逻辑只用到了fopen、fread、fseek、typecast这些基础函数,不需要额外的工具箱。拿到工具包目录后,先把segy_tools文件夹加进MATLAB搜索路径:

addpath('你的路径/segy_tools'); savepath; % 保存路径设置,下次启动不用重复添加

然后找一份SEGY数据文件,调用read_segy主函数。注意文件路径尽量用绝对路径,相对路径在脚本跨目录执行时容易找不到文件。调用方式如下:

S = read_segy('C:/data/line_a001.segy');

返回的S是一个结构体,S.data是二维数组,行数等于每道采样点数,列数等于总道数。S.binInfo里有采样间隔和格式码,S.traceHeaders里每一行存储了对应道的野外采集参数。想快速检查文件有没有读对,直接看数据矩阵的尺寸和值域:

size(S.data) % 期待是 [采样点数, 道数] min(S.data(:)) % 看看振幅范围是否合理 max(S.data(:))

如果发现数据范围大得离谱,先看格式码对不对;如果范围小到几乎全是0,看看是不是IBM浮点转换那条路径出了问题。

4.2 数据清洗与道编辑

读取只是第一步,实际处理前通常要做数据体检。SEGY文件里偶尔混进坏道——采样点数不对、振幅全部为0、或者某个道的数据是重复采集。下面的代码利用道头信息做一道简单的筛选:

% 提取每道的道号供后续参考 traceNums = cellfun(@(h) h.traceNumber, S.traceHeaders); % 找出振幅全为0的死道 deadTraces = find(max(abs(S.data)) == 0); % 去掉死道后重建数据矩阵 if ~isempty(deadTraces) S.data(:, deadTraces) = []; S.traceHeaders(deadTraces) = []; fprintf('移除了 %d 条死道\n', length(deadTraces)); end

这段逻辑里max(abs(S.data))按列取最大值,所有值都为零的列就是死道。cellfun从traceHeaders结构体数组里提取traceNumber字段,方便你后续按道号排序或者查道。直接从矩阵里删列,是最粗暴但有效的坏道处理手段,处理完记得同步删道头,否则道号和数据对不上。

4.3 地震剖面可视化工具箱

工具包里带了一个简易绘图函数plot_seismic,用法很简单,传入数据矩阵和采样间隔就能出图:

plot_seismic(S.data, S.binInfo.sampleInterval);

内部实现用的是imagesc加自定义色彩映射,不是海底底图那种复杂的渲染:

function plot_seismic(data, sampleIntervalUs) [nSamples, nTraces] = size(data); % 构建时间轴,单位毫秒 timeAxis = (0:nSamples-1) * sampleIntervalUs / 1000; % 用imagesc画图并设置坐标轴 imagesc(1:nTraces, timeAxis, data); set(gca, 'YDir', 'normal'); % 时间轴从上往下增大 colormap(flipud(gray)); % 地震剖面惯用黑-白-黑颜色 xlabel('道号'); ylabel('时间 (ms)'); end

这里用imagesc而不是直接用plot的wiggle方式,好处是画超多道数据的剖面时性能好很多,坏处是密集剖面看起来没有波形图那么直观。你真要做出版级图形,建议在这基础上改用传统的variable-density绘图。set(gca, 'YDir', 'normal')很关键,默认imagesc会翻转Y轴,不设置的话时间方向会被倒过来,这是新手的常见翻车点。

5. SEGY踩坑指南:字节序、编码与扩展道头

5.1 在别人机器上正常的文件,我读出来全是乱数

现象:同一个SEGY文件,同事在Linux工作站上读出来剖面正常,你在Windows笔记本上读出来数据值巨大或者全是噪声。

原因:SEGY标准规定大端字节序,而x86平台默认小端字节序。很多商业软件读取时会自动做字节序转换,但自己写的fread如果直接指定'int16'或者'single',MATLAB会按本机小端序去解释,高低字节颠倒。这时候采样间隔可能变成几千、采样点数变成负数,数据体就更不用说了。

解决:读取所有多字节字段时,在fread里显式指定机器格式,或者读完用typecast配合swapbytes。例如读采样间隔时改成:

fseek(fid, 3216, 'bof'); rawBytes = fread(fid, 2, 'uint8'); sampleInterval = double(typecast(swapbytes(uint16(typecast(rawBytes, 'uint16'))), 'int16'));

更省事的做法是fread直接加'l'结尾的精度字符串,比如'int16'改成'int16'加'b'参数(big-endian):fread(fid, 1, 'int16', 0, 'b'),这是MATLAB里绕过字节序问题的正道。

5.2 文本卷头全是乱码,一行可读内容都没有

现象:文本卷头打印出来是空行、特殊符号或者看不太懂的字符序列。

原因:早期采集设备生成的SEGY文件用EBCDIC编码而非ASCII。EBCDIC里字母和数字的码位和ASCII完全不同,按ASCII去解码自然是一团乱码。

解决:用解析函数里的ebcdic2ascii做转换,或者先检测编码再决定路径。我在工具包里直接实现了简化版EBCDIC映射表,覆盖空格、数字、大写字母、小写字母这些常用字符。需要说明的是,EBCDIC也有多种方言变体,如果遇到转换后个别字符还是不对,多半是变体差异,补映射表就行。

5.3 读出来的采样点数跟道头里写的不一致

现象:数据矩阵尺寸跟预期的采样点数对不上,或者读完一道后文件游标位置错乱,后面所有道的道头都解析错误。

原因:二进制卷头里的每道采样点数字段是全局设定,但具体某一道的道头里也有采样点数字段。有些处理系统会修改局部道头里的采样点数,导致文件内不同道的实际字节长度不一致,不能按固定步长跳读。

解决:读取每道时,不要信任二进制卷头的全局采样点数,而是以当前道的道头字段为准做动态计算。工具包在read_trace_data里先解析道头、取出该道实际的采样点数,再按这个数读取数据体。代价是每道都要多读一次240字节道头,但换来的是对非标准文件的鲁棒性,值。

5.4 文件里有扩展文本卷头,读到的数据全是偏移的

现象:读完文本头和二进制头之后,fread读道头时第一个道的240字节里全是乱码,或者首次读就报错文件过早结束。

原因:SEG-Y rev1以后允许在二进制卷头后面插入一个额外的3200字节扩展文本卷头,而不少采集系统默认就写这个扩展区。你按标准的3600字节起始偏移去找第一道,自然就偏了3200字节。

解决:读完二进制卷头后,再往后检查有没有扩展文本卷头。最直接的办法是读取二进制头第3517-3518字节的扩展文本卷头计数(或直接看文件偏移3600字节处的头部是否像道头)。稳妥的做法是工具包里加一个选项:读二进制头后先尝试解析偏移3600处的240字节,看道号字段是否合理,不合理就把起始游标往后挪3200字节再解析一次。这类玄学问题,说白了就是SEGY标准衍生出多个版本,处理时多留条后路总没错。

6. 二次开发:把工具包改造成自己的地震数据处理管线的三个方向

工具包到手之后,最常见的改造需求是把读取结果喂给后续的数据处理脚本。我建议重点做三件事:批量读取、格式转换、并行加速。

批量读取的场景很典型——一个工区几百个SEGY文件,每个文件对应一条测线,想一次性全部载入。改造思路不复杂:写一个外层脚本,遍历文件列表,逐个调用read_segy,把每个文件的S.data存到cell数组里。

fileList = dir('C:/data/raw/*.segy'); allData = cell(length(fileList), 1); for i = 1:length(fileList) S = read_segy(fullfile(fileList(i).folder, fileList(i).name)); allData{i} = S; end

如果文件太多导致内存吃紧,可以只读道头信息不读数据体,或者按道索引分段读取。工具包目前的read_segy是整文件读入,做内存映射改造时你可以把fread改成按需跳读,思路跟read_trace_data一致。

格式转换是另一个常用改造点。SEGY是勘探行业通用格式,但深度学习、信号处理社区更常用npy或h5。在read_segy返回的S.data基础上,直接调用MATLAB的save或者写一个转h5的脚本就能对接下游:

Sw = S.data'; writematrix(Sw, 'seismic_data.csv'); % 道行为主,方便导入Python

并行加速方面,read_trace_data里逐道读取的循环是天然的并行候选。用parfor替换for,注意每个迭代里fread是独立文件游标操作,MATLAB的fread并不会自动互斥,所以需要先预分配游标或者改用每道单独fopen。我踩过的坑是:直接parfor跑同一个fid会随机出错,最后拆成每道开一次文件句柄才稳定。从这里往后,我每次改完并行逻辑都会强制跑一遍全工区数据比对结果,确认振幅和道头信息没被并行搞乱,这个习惯救了我好几次。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询