简介:本资源是一个面向地震资料处理初学者与MATLAB进阶用户的开源代码实践案例,聚焦SEG-Y格式地震数据的读取与解析这一关键预处理环节。核心文件altreadsegy.m完整实现了文件头解析、二进制地震道数据读取、整型/浮点型数据类型转换、元信息提取及矩阵化组织等全流程功能,为后续滤波、叠加、偏移等专业处理奠定基础。压缩包仅含1个MATLAB源文件(.m),体积仅4KB,轻量简洁,便于逐行调试与原理理解。已有133人学习下载,适合地质工程、地球物理方向学生及科研人员通过实操掌握MATLAB文件I/O、二进制数据处理与地震数据结构建模等核心技能,同时可作为扩展开发起点,快速集成质量检查、噪声压制等自定义模块。
1. 为什么你读进来的 SEG-Y 数据总是“歪的”?altreadsegy.m 不是万能钥匙,而是你理解地震数据二进制结构的第一把解剖刀
刚接手一批野外采集的地震数据,用fread(fid,'int32')硬读出来,波形全乱——振幅跳变、道序错位、时间轴拉长三倍。这不是 MATLAB 有问题,而是你跳过了 SEG-Y 文件最危险的“表皮层”:512 字节的文件头(File Header)和每道前 240 字节的道头(Trace Header)。altreadsegy.m的价值,不在于它能“一键读取”,而在于它把 SEG-Y 标准里那些被忽略的字节偏移、字节序(Big Endian)、有符号/无符号整数转换、采样点数与采样间隔的耦合关系,全部显式暴露在 MATLAB 脚本里。它适合两类人:一是地质所刚接触实际数据的实习生,需要从零建立对 SEG-Y 物理存储结构的直觉;二是地球物理软件开发者,需快速验证自研读取器与工业标准的兼容性。它不封装 GUI,不自动绘图,不调用 Parallel Computing Toolbox,只做一件事:把磁盘上那一串串十六进制字节,按 IEEE SEG-Y Rev 1 规范,逐字节映射成 MATLAB 中可索引、可计算、可 debug 的矩阵。你若想跳过这一步直接跑反演或深度学习模型,后续所有结果都可能因道头解析错误而系统性偏移。
2. 解析 SEG-Y 文件头:512 字节里的 40 个字段,为什么必须手动校验字节序与字段偏移?
SEG-Y 文件头是整个数据体的“身份证”,共 512 字节,划分为 40 个 16 字节字段(Field),每个字段承载特定元信息。altreadsegy.m并未使用matlab.io.segy(该函数在 R2021b 后才引入且默认启用自动校验),而是用fread配合swapbytes显式处理字节序,这是理解底层的关键。
2.1 文件头字段定位与字节序陷阱
SEG-Y 标准强制要求 Big Endian 存储,但 x86 架构 PC 默认 Little Endian。若直接fread(fid,40,'uint16'),得到的将是完全错乱的数值。altreadsegy.m的典型做法是:
% 打开文件并定位到文件头起始(字节 0) fid = fopen('data.segy','r','b'); fseek(fid,0,'bof'); % 读取全部 512 字节为 uint8,再按字段分组 header_bytes = fread(fid,512,'uint8'); % 按 SEG-Y Rev 1 定义,第 33-34 字节(索引 32-33)为采样率(单位:微秒) % 注意:MATLAB 索引从 1 开始,故取 header_bytes(33:34) sample_interval_bytes = header_bytes(33:34); % Big Endian 转换:高位字节在前 → 直接 uint16 解释 sample_interval_us = typecast(sample_interval_bytes,'uint16'); % 第 9-10 字节(索引 8-9)为道总数(Number of data traces) num_traces_bytes = header_bytes(9:10); num_traces = typecast(num_traces_bytes,'uint16');提示:
typecast不改变内存布局,仅重新解释字节序列;swapbytes则翻转字节顺序。对 Big Endian 数据,typecast(uint8([0x00,0x64]),'uint16')得到 100,而swapbytes(typecast(uint8([0x00,0x64]),'uint16'))得到 25600 —— 这正是字节序误判的典型症状。
2.2 关键字段校验表:避免后续数据错位的硬性检查
| 字段位置(字节索引) | 字段名 | 数据类型 | 合法值范围 | altreadsegy.m中的校验逻辑 |
|---|---|---|---|---|
| 33–34 | 采样间隔(μs) | uint16 | >0,常见 500, 1000, 2000 | if sample_interval_us == 0, error('采样间隔为0,文件头损坏'); end |
| 117–118 | 道头长度(字节) | uint16 | 必须为 240(SEG-Y Rev 1) | if trace_header_len ~= 240, warning('道头长度非240,可能为Rev 0或自定义格式'); end |
| 121–122 | 采样点数/道 | uint16 | ≥1,通常 1000–10000 | if nsamples_per_trace < 10, error('采样点数过少,疑似读取偏移错误'); end |
| 125–126 | 数据格式码 | int16 | 1=IEEE float32, 2=IEEE int32, 5=IEEE int16 | if format_code ~= 1 && format_code ~= 2 && format_code ~= 5, error('不支持的数据格式码'); end |
这些校验不是可选项。例如,若format_code误读为 0(因字节序错误导致uint16([0x00,0x00])被解释为 0),后续fread将以错误数据类型读取,整个数据体崩塌。altreadsegy.m在解析完文件头后,必执行fclose(fid)再重新fopen,确保文件指针重置,这是防止道头读取偏移的隐性保障。
2.3 实战:用 hex2dec 和 dec2hex 快速验证字段值
当怀疑某字段读取异常时,直接查看原始十六进制最可靠:
% 读取第 33-34 字节的原始 hex fseek(fid,32,'bof'); % MATLAB 索引从 1,字节偏移从 0,故 33→32 raw_bytes = fread(fid,2,'uint8'); fprintf('字节33-34原始hex: %02X %02X\n', raw_bytes(1), raw_bytes(2)); % 输出示例:字节33-34原始hex: 00 01 → 对应 uint16 256,即采样间隔256μs(非标值) % 反向验证:已知采样间隔为1000μs,其 Big Endian hex 应为? hex_str = dec2hex(1000,4); % '03E8' fprintf('1000μs的Big Endian hex: %s (高位在前)\n', hex_str); % 输出:1000μs的Big Endian hex: 03E8 → 文件中应为 03 E8 两字节此步骤能绕过任何高层函数封装,直击二进制真相。很多用户报“数据读出来是常数”,根源往往是format_code字段被错读,导致fread用int16解释了本该是float32的数据,高位字节全为 0。
3. 读取道头与地震道数据:240 字节道头如何决定每一道的时空坐标?
SEG-Y 文件中,每一道(Trace)由 240 字节道头 + N×M 字节数据组成。altreadsegy.m的核心逻辑是:先批量读取所有道头,再根据道头中的关键字段(如道号、X/Y 坐标、延迟时间)组织数据矩阵,而非简单按固定长度切分。
3.1 道头批量读取与结构化存储
altreadsegy.m通常将道头解析为struct数组,便于后续按字段索引:
% 计算道头总字节数:num_traces * 240 trace_header_total = num_traces * 240; fseek(fid,512,'bof'); % 跳过文件头 trace_headers_raw = fread(fid,trace_header_total,'uint8'); % 预分配 struct 数组 trace_headers = repmat(struct('traceno',0,'xcoord',0,'ycoord',0,'delay',0), [1,num_traces]); for i = 1:num_traces % 提取第 i 道的 240 字节 start_idx = (i-1)*240 + 1; th_bytes = trace_headers_raw(start_idx:start_idx+239); % 道号:字节 1–4(uint32,Big Endian) trace_headers(i).traceno = typecast(th_bytes(1:4),'uint32'); % X 坐标:字节 73–76(int32,单位:米) trace_headers(i).xcoord = typecast(th_bytes(73:76),'int32'); % Y 坐标:字节 77–80(int32,单位:米) trace_headers(i).ycoord = typecast(th_bytes(77:80),'int32'); % 延迟时间:字节 109–112(uint32,单位:微秒) trace_headers(i).delay = typecast(th_bytes(109:112),'uint32'); end注意:
typecast对uint32输入要求 4 字节,若th_bytes(1:4)读取正确,则无需swapbytes;若读取偏移,typecast会静默返回错误值。因此,trace_headers(i).traceno的值必须与野外记录日志比对——若首道traceno为 0 或极大值(如 2^32-1),说明道头起始偏移错误。
3.2 地震道数据读取:动态长度与数据类型适配
每道数据长度由文件头nsamples_per_trace和道头data_used字段共同决定。altreadsegy.m必须根据format_code选择fread的精度:
% 根据 format_code 确定每采样点字节数及读取类型 switch format_code case 1 % IEEE float32 bytes_per_sample = 4; read_type = 'float32'; case 2 % IEEE int32 bytes_per_sample = 4; read_type = 'int32'; case 5 % IEEE int16 bytes_per_sample = 2; read_type = 'int16'; otherwise error('不支持的 format_code: %d', format_code); end % 总数据字节数 = 道数 × 每道采样点数 × 每点字节数 total_data_bytes = num_traces * nsamples_per_trace * bytes_per_sample; % 一次性读取全部数据(高效,但需内存足够) fseek(fid,512 + trace_header_total,'bof'); all_data_raw = fread(fid,total_data_bytes,'uint8'); % 按道重塑为三维数组:[采样点, 道, 分量],此处为单分量 data_matrix = zeros(nsamples_per_trace, num_traces); for i = 1:num_traces start_byte = (i-1) * nsamples_per_trace * bytes_per_sample + 1; end_byte = start_byte + nsamples_per_trace * bytes_per_sample - 1; % 提取该道原始字节并转换 trace_bytes = all_data_raw(start_byte:end_byte); if bytes_per_sample == 4 trace_data = typecast(trace_bytes, read_type)'; else trace_data = typecast(trace_bytes, read_type)'; end data_matrix(:,i) = trace_data; end此代码的关键在于:typecast返回列向量,故用'转置为行向量再赋给data_matrix(:,i)。若忘记转置,数据将被写入错误维度,波形显示为一条水平线。
3.3 时间轴重建:从采样间隔与延迟时间生成精确时间向量
地震数据的横轴是时间,而非采样点索引。altreadsegy.m必须利用文件头sample_interval_us和道头delay构建物理时间:
% 文件头给出全局采样间隔(微秒) dt_us = sample_interval_us; % e.g., 1000 μs = 1 ms dt_s = dt_us / 1e6; % 转为秒 % 生成基础时间向量(从 t=0 开始) time_base = (0:nsamples_per_trace-1)' * dt_s; % 列向量 % 但实际起始时间 = delay(微秒) + file_header_delay(若有) % 多数情况下,file_header_delay = 0,故每道时间 = time_base + delay_s delay_s = trace_headers(1).delay / 1e6; % 首道延迟,单位秒 time_vector = time_base + delay_s; % 若各道延迟不同(如 VSP 数据),则需 per-trace: time_matrix = zeros(nsamples_per_trace, num_traces); for i = 1:num_traces delay_i_s = trace_headers(i).delay / 1e6; time_matrix(:,i) = time_base + delay_i_s; end若忽略delay,所有道的时间轴将统一从 t=0 开始,导致叠加时相位严重错动。这是初学者最常犯的错误之一。
4. 数据组织与可视化:为什么data_matrix必须是[nsamp, ntrace]而非[ntrace, nsamp]?
MATLAB 中矩阵的行列约定直接影响后续处理效率。altreadsegy.m输出的data_matrix采用[nsamp, ntrace]格式(行=时间采样点,列=空间道号),这是地震数据处理的工业惯例,也是 Seismic Unix、OpendTect 等工具的默认布局。
4.1 矩阵维度与处理操作的天然对齐
滤波、FFT、偏移等操作天然沿时间方向(行方向)进行:
% 对每一道(每列)独立做带通滤波 fs = 1/dt_s; % 采样率 Hz [b,a] = butter(4, [10 80]/(fs/2), 'bandpass'); data_filtered = filtfilt(b,a, data_matrix); % 自动沿列(dim=1)滤波 % 对每一道做 FFT,结果矩阵仍为 [nsamp, ntrace] fft_spectrum = fft(data_matrix); % 叠加(Stacking):沿道方向求平均,得到零偏移剖面 zero_offset_trace = mean(data_matrix, 2); % dim=2 → 沿列平均,输出 [nsamp,1]若data_matrix设计为[ntrace, nsamp],则filtfilt需指定dim=2,mean需dim=1,代码易错且违背领域直觉。
4.2 可视化:imagesc与plot的坐标系匹配
地震剖面图的横轴是道号(空间),纵轴是时间(或深度),imagesc要求矩阵行对应 y 轴,列对应 x 轴:
% 正确:data_matrix(nsamp, ntrace) → y=时间,x=道号 figure; imagesc(1:num_traces, time_vector*1000, data_matrix); % time_vector 单位秒 → *1000 为毫秒 axis xy; % 确保 y 轴正向向上(时间从上到下) xlabel('道号'); ylabel('时间 (ms)'); title('地震剖面图'); % 错误:若 data_matrix 是 [ntrace, nsamp],则 imagesc 会将时间当横轴,道号当纵轴 % 导致图像旋转90度,且时间轴倒置axis xy是关键。MATLAB 默认axis ij(矩阵索引),imagesc绘图时 y 轴向下增长,而地震图要求 y 轴向上增长(浅层在上,深层在下),axis xy强制 y 轴正向向上,与time_vector的递增方向一致。
4.3 元数据关联:用trace_headers驱动空间插值
真实地震数据中,道号不等于物理距离。altreadsegy.m解析出的xcoord/ycoord可用于生成空间网格:
% 提取所有道的坐标 x_coords = [trace_headers.xcoord]; y_coords = [trace_headers.ycoord]; % 计算道间距(假设直线排列) dx = diff(x_coords); dy = diff(y_coords); spacing = sqrt(dx.^2 + dy.^2); % 若 spacing 标准差 < 1m,可近似为规则采样 if std(spacing) < 1 x_grid = linspace(min(x_coords), max(x_coords), num_traces); else % 非规则采样,需用 scatteredInterpolant 插值到规则网格 F = scatteredInterpolant(x_coords, y_coords, data_matrix(:), 'natural'); [Xq,Yq] = meshgrid(linspace(min(x_coords),max(x_coords),100), ... linspace(min(y_coords),max(y_coords),50)); data_interp = reshape(F(Xq,Yq), size(Xq)); end此步骤将离散道号转化为连续空间坐标,是做偏移成像、属性分析的前提。altreadsegy.m不提供此功能,但它输出的trace_headers结构体,正是你构建空间模型的唯一可信源。
5. 排查常见故障:当altreadsegy.m报错 “Index exceeds matrix dimensions” 时,如何 5 分钟定位是文件头还是道头问题?
Index exceeds matrix dimensions是altreadsegy.m最典型的运行时错误,根源几乎总是文件结构与代码假设不符。排查必须遵循“从外到内”顺序:先验证文件头字段,再检查道头读取偏移,最后确认数据区长度。
5.1 三步快速诊断法
第一步:用fstat和fseek确认文件大小与结构
% 获取文件基本信息 info = dir('data.segy'); fprintf('文件大小: %d 字节\n', info.bytes); % 计算理论大小 = 512 + num_traces*240 + num_traces*nsamples*bytes_per_sample % 若 info.bytes 远小于此值,文件已截断 theoretical_size = 512 + num_traces*240 + num_traces*nsamples_per_trace*bytes_per_sample; if info.bytes < theoretical_size * 0.95 error('文件大小 (%d) 远小于理论值 (%d),可能已损坏或不完整', info.bytes, theoretical_size); end第二步:打印关键字段的原始字节与解析值
在altreadsegy.m的文件头解析段后,插入调试输出:
% 在读取 sample_interval_us 后立即添加 fprintf('文件头字节33-34: [%02X %02X] → 解析为 %d μs\n', ... header_bytes(33), header_bytes(34), sample_interval_us); fprintf('文件头字节121-122 (道头长度): [%02X %02X] → 解析为 %d\n', ... header_bytes(121), header_bytes(122), trace_header_len); fprintf('文件头字节125-126 (采样点数): [%02X %02X] → 解析为 %d\n', ... header_bytes(125), header_bytes(126), nsamples_per_trace);若输出为文件头字节33-34: [00 00] → 解析为 0 μs,则sample_interval_us为 0,错误源于字节序或偏移错误。
第三步:用whos检查中间变量尺寸
在报错行前加whos:
% 假设报错在 data_matrix(:,i) = trace_data; whos 'trace_data' 'nsamples_per_trace' 'i' 'data_matrix'; % 输出示例: % Name Size Bytes Class Attributes % data_matrix 1000x500 4000000 double % trace_data 1001x1 8008 double % nsamples_per_trace 1000 8 double % i 1x1 8 double % → trace_data 是 1001x1,但 data_matrix 第 i 列只接受 1000 行 → 尺寸不匹配此时trace_data长度为 1001,而nsamples_per_trace=1000,说明fread读取了额外 1 个采样点,根源是format_code解析错误导致bytes_per_sample计算偏差。
5.2 修复方案速查表
| 报错现象 | 最可能原因 | 修复指令(在altreadsegy.m中修改) |
|---|---|---|
Index exceeds...在道头解析循环 | num_traces读错(字节序错误) | 将typecast(th_bytes(1:4),'uint32')改为swapbytes(typecast(th_bytes(1:4),'uint32'))并重测 |
data_matrix全为Inf或NaN | format_code=1但数据实为int32 | 检查文件头字节 125-126,若format_code读为 1 但数据区fread出现大量Inf,强制设format_code=2 |
| 波形振幅异常小(~1e-38) | format_code=1但字节序错误,float32被解释为极小指数 | 在typecast(trace_bytes, 'float32')前加trace_bytes = flip(trace_bytes);(Little Endian 修正) |
fread返回空数组 | fseek位置错误,文件指针超出 EOF | 在fseek(fid, pos, 'bof')后加pos_check = ftell(fid); fprintf('fseek 后位置: %d\n', pos_check); |
所有修复都应在altreadsegy.m原始代码上小范围修改,而非重写。它的价值正在于“透明”——每一行代码都在告诉你:SEG-Y 不是黑盒,而是可触摸、可测量、可调试的字节序列。
本文还有配套的精品资源,点击获取