☰
Python快速可视化SEG-Y地震数据:三步实现剖面绘制与批量抽检
2026/10/16 17:55:18 网站建设 项目流程

简介:这份资源面向地质勘探、地震预测与环境监测领域的技术人员及Qt开发者,提供一套基于Qt3.3框架的地震数据快速可视化实现方案,重点解决大规模地震记录的读取、处理与图形化展示问题。压缩包共77个文件,约2.07MB,以h头文件、cpp源文件、ui界面文件、png与xpm图标资源为主,另含dsp、pro、dsw等工程配置及可执行程序与依赖库,结构完整便于直接编译运行。项目支持SEGY、GRISYS、DSK等常见格式并允许自定义数据格式,内置波形、充填、便面积、彩图及插值彩图五种显示方式,分别适用于事件识别、异常检测、趋势分析与多维数据关联观察。已有907人学习下载,读者可从中获取完整视图组件、数据处理模块与交互逻辑,理解海量地震数据下的流畅渲染与界面设计思路,适合作为地震数据可视化工具开发与二次扩展的参考。

1. 地震数据的快速可视化:从 SEG-Y 到屏幕只要三步

手头有一批 SEG-Y 格式的地震记录,想快速看一眼剖面长什么样,结果打开专业软件要等几分钟加载,换个工区又得重新配坐标、调增益、设色标。这种场景在野外验收、井位论证、教学演示里太常见了——你只是想确认数据有没有问题,却被迫走一遍完整处理流程。地震数据的快速可视化,核心诉求就一个:把二进制道集在几秒内变成能看的图像,不依赖重型软件,不写冗长的配置文件。适合谁?做地震资料处理的工程师、带学生看实际数据的导师、需要快速抽检数据质量的现场人员。下面这份资源包围绕“快速”二字展开,用 Python 生态把 SEG-Y 读取、道头解析、剖面绘制串成一条最短路径,同时保留参数调整的入口,让你在快和可控之间找到平衡。

2. 读懂 SEG-Y 的道头与数据体:为什么不能直接 imshow

2.1 地震记录的二进制结构决定了读取方式

SEG-Y 文件不是一张图片,它由 3200 字节的文本头、400 字节的二进制文件头,以及若干道组成。每道又分 240 字节的道头和一个浮点数组。很多人第一次用numpy.fromfile直接读整个文件,然后 reshape 成二维数组,结果画出来的剖面全是噪声——因为道头混进了数据体。常见做法是先用segyio或obspy把道头和数据分离,拿到data矩阵和header字典,再决定怎么画。这里有个关键参数:采样间隔dt和道间距dx,它们决定了横纵坐标的物理单位。如果道头里没写,你得从观测系统表里手动补,否则画出来的图只有像素意义,没有时间深度。

import segyio import numpy as np # 打开 SEG-Y 文件,只读模式,不修改原始数据 with segyio.open('line_001.sgy', 'r', ignore_geometry=True) as f: # 读取所有道的数据,转成 (道数, 采样点数) 的二维数组 data = segyio.tools.collect(f.trace[:]) # 提取采样间隔(微秒),用于计算时间轴 dt = f.bin[segyio.BinField.Interval] # 提取道数 n_traces = f.tracecount # 提取每道的 CDP 号,用于横坐标 cdps = [f.header[i][segyio.TraceField.CDP] for i in range(n_traces)] print(f"数据体形状: {data.shape}, 采样间隔: {dt} us, 道数: {n_traces}")

这段代码的逻辑是先建立文件句柄,用collect一次性把所有道读进内存。ignore_geometry=True是为了跳过几何定义,加快打开速度——快速可视化不需要完整的观测系统。dt从二进制头里取,单位是微秒,后面画图时转成毫秒。cdps列表用来做横轴标签,如果道头里 CDP 号缺失,可以退而用道序号。参数说明:segyio.open的strict参数默认 True,遇到不规范文件会报错,快速查看时可以设strict=False跳过校验,但要注意可能读到错误数据。

2.2 数据体转图像:增益与色标是两道坎

拿到data矩阵后,直接plt.imshow(data)往往一片黑或者一片白。原因是地震振幅动态范围大,深部弱信号被浅部强能量淹没。常见做法是加一个增益函数,比如t^2增益或者 AGC(自动增益控制)。t^2增益简单:每个采样点乘以时间的平方,补偿球面扩散。AGC 更灵活,用滑动窗口计算均方根振幅做归一化。色标方面,地震剖面习惯用红白蓝或灰度,对称色标要设vmin=-vmax,让零振幅对应白色。下面是一个带增益和色标控制的绘图函数。

import matplotlib.pyplot as plt def plot_section(data, dt=2000, gain_power=2, clip_percentile=98): """ data: 二维数组 (道数, 采样点数) dt: 采样间隔,微秒 gain_power: 增益指数,常用 1~3 clip_percentile: 裁剪百分位,控制色标范围 """ n_traces, n_samples = data.shape # 时间轴,单位毫秒 time_ms = np.arange(n_samples) * dt / 1000.0 # 构建 t^gain_power 增益曲线 gain_curve = (time_ms / time_ms.max()) ** gain_power # 对每道应用增益 gained = data * gain_curve[:, np.newaxis] # 计算对称色标范围 vmax = np.percentile(np.abs(gained), clip_percentile) # 绘制剖面 plt.figure(figsize=(10, 6)) plt.imshow(gained.T, aspect='auto', cmap='seismic', vmin=-vmax, vmax=vmax, extent=[0, n_traces, time_ms.max(), 0]) plt.xlabel('道号') plt.ylabel('时间 (ms)') plt.colorbar(label='振幅') plt.title('地震剖面快速可视化') plt.show() # 调用 plot_section(data, dt=dt, gain_power=2, clip_percentile=98)

逻辑说明:gain_curve是一个从 0 到 1 的数组,乘以数据后,浅层增益小、深层增益大,补偿能量衰减。clip_percentile取绝对值第 98 百分位,避免个别强振幅把色标拉爆。extent参数把时间轴翻转,符合地震剖面深度向下的习惯。参数怎么改:如果剖面还是太暗,把gain_power调到 3;如果噪声被放得太大,降到 1。clip_percentile一般 95~99,数值越小对比度越强,但可能丢失弱反射。注意imshow的aspect='auto'必须加,否则道数和采样点数比例失调,剖面会被压扁。

3. 批量抽检与交互式浏览:把单张图变成工作流

3.1 多文件批量出图:循环里加异常捕获

实际工区往往有几十条测线,一条条手动打开不现实。常见做法是写一个批处理脚本,遍历目录下所有.sgy或.segy文件,每读一个就生成一张 PNG 缩略图,文件名带上测线号。这里有个坑:不同文件的道头格式可能不一致,有的用标准 SEG-Y,有的用扩展文本头。所以循环里要加try...except,遇到读不了的文件跳过并记录,不要整个脚本崩掉。下面是一个批量出图的骨架。

import os import glob def batch_plot(input_dir, output_dir, dt_default=2000): os.makedirs(output_dir, exist_ok=True) sgy_files = glob.glob(os.path.join(input_dir, '*.sgy')) + \ glob.glob(os.path.join(input_dir, '*.segy')) for sgy_path in sgy_files: base_name = os.path.splitext(os.path.basename(sgy_path))[0] try: with segyio.open(sgy_path, 'r', ignore_geometry=True) as f: data = segyio.tools.collect(f.trace[:]) dt = f.bin[segyio.BinField.Interval] or dt_default # 调用前面的绘图函数,保存为文件 plot_section(data, dt=dt, gain_power=2, clip_percentile=98) plt.savefig(os.path.join(output_dir, f'{base_name}.png'), dpi=120) plt.close() print(f'[OK] {base_name}') except Exception as e: print(f'[FAIL] {base_name}: {e}') # 调用 batch_plot('./sgy_data', './quicklook')

逻辑说明:glob同时匹配两种扩展名,避免漏文件。dt从文件头取,取不到就用默认值 2000 微秒。plt.savefig后必须plt.close(),否则内存里堆积几十张图会爆。dpi=120是缩略图的合适分辨率,再高文件太大。参数说明:如果目录里文件很多,可以加max_files参数限制数量,先抽检前 20 条。异常捕获里打印文件名和错误信息,方便后续排查是文件损坏还是格式不兼容。

3.2 交互式浏览:用 matplotlib 的滑块调增益

静态图只能看个大概,想细看某段同相轴,还是得能实时调参数。matplotlib.widgets里的Slider可以挂在图上,拖动时重绘。常见做法是把增益指数和色标百分位做成两个滑块,范围分别设 0.5~4 和 90~100。这样不用改代码就能找到最佳显示效果。下面是一个最小交互示例。

from matplotlib.widgets import Slider def interactive_section(data, dt=2000): n_traces, n_samples = data.shape time_ms = np.arange(n_samples) * dt / 1000.0 fig, ax = plt.subplots(figsize=(10, 6)) plt.subplots_adjust(bottom=0.25) # 初始增益和裁剪 init_gain = 2.0 init_clip = 98 gain_curve = (time_ms / time_ms.max()) ** init_gain gained = data * gain_curve[:, np.newaxis] vmax = np.percentile(np.abs(gained), init_clip) img = ax.imshow(gained.T, aspect='auto', cmap='seismic', vmin=-vmax, vmax=vmax, extent=[0, n_traces, time_ms.max(), 0]) ax.set_xlabel('道号') ax.set_ylabel('时间 (ms)') # 增益滑块 ax_gain = plt.axes([0.15, 0.1, 0.65, 0.03]) slider_gain = Slider(ax_gain, '增益指数', 0.5, 4.0, valinit=init_gain) # 裁剪滑块 ax_clip = plt.axes([0.15, 0.05, 0.65, 0.03]) slider_clip = Slider(ax_clip, '裁剪百分位', 90, 100, valinit=init_clip) def update(val): g = slider_gain.val c = slider_clip.val gc = (time_ms / time_ms.max()) ** g gd = data * gc[:, np.newaxis] vm = np.percentile(np.abs(gd), c) img.set_data(gd.T) img.set_clim(-vm, vm) fig.canvas.draw_idle() slider_gain.on_changed(update) slider_clip.on_changed(update) plt.show() # 调用 interactive_section(data, dt=dt)

逻辑说明:update函数在滑块变化时重新计算增益和色标范围,只更新图像数据不重建画布,所以响应很快。set_clim控制颜色映射范围,draw_idle触发重绘。参数说明:滑块范围根据经验设,增益指数低于 0.5 几乎没效果,高于 4 噪声会盖过信号。裁剪百分位低于 90 会把有效反射裁掉,高于 100 没意义。注意plt.subplots_adjust(bottom=0.25)给滑块留出空间,否则滑块会盖住横轴标签。

4. 避坑与排查:快速可视化里最容易翻车的五件事

4.1 现象:剖面上下颠倒,浅层跑到下面去了

原因:imshow默认原点在左上角,而地震数据的时间轴是向下增加的。如果extent参数写成[0, n_traces, 0, time_ms.max()],时间轴就是从上到下递增,浅层反而在底部。解决:把extent的第三、四个参数对调,写成[0, n_traces, time_ms.max(), 0],让 y 轴反向。或者用ax.invert_yaxis(),但extent方式更直接。

4.2 现象:读大文件时内存爆了,程序被系统杀掉

原因:segyio.tools.collect会把所有道一次性读进内存。一条 10GB 的 SEG-Y 文件,读进来就是 10GB 的float32数组,加上增益计算时的临时数组,内存翻倍。解决:分块读取。用f.trace[i:i+chunk]逐块处理,每块画完就释放。或者用segyio.tools.cube只读需要的道范围。快速可视化不需要全精度,可以隔道抽样,比如每 5 道取 1 道,数据量直接降到 20%。

4.3 现象:色标范围被个别异常振幅主导,剖面一片灰

原因:数据里有个别坏道或者尖脉冲,振幅是正常值的几百倍。np.percentile取 98% 时,如果坏点占比超过 2%,vmax会被拉得很大,正常信号全被压成灰色。解决:先做异常值剔除。用np.median和np.std算每道的稳健统计量,把超过 5 倍标准差的采样点置零。或者把clip_percentile降到 95,牺牲一点弱信号对比度,换取整体可读性。

4.4 现象:道头里的 CDP 号全是 0,横轴没有物理意义

原因:有些处理系统输出的 SEG-Y 文件不写 CDP 号,或者写在非标准字节位置。segyio按标准道头读,读出来就是 0。解决:先打印道头字典看看哪些字段有值,常见的有TraceField.TRACE_SEQUENCE_LINE或TraceField.FieldRecord。如果都没有,就用道序号代替,并在图注里说明“横轴为道序号,非 CDP”。不要硬编一个假 CDP 号,否则后续对比会出错。

4.5 现象:批量出图时中文标题变成方框

原因:matplotlib默认字体不支持中文,plt.title('地震剖面')会显示成豆腐块。解决:在脚本开头设置plt.rcParams['font.sans-serif'] = ['SimHei']和plt.rcParams['axes.unicode_minus'] = False。如果系统没有 SimHei,换成Microsoft YaHei或WenQuanYi Micro Hei。注意axes.unicode_minus要设 False,否则负号也会变方框。

5. 进阶技巧:把可视化嵌进处理流程做质量监控

5.1 用子图矩阵对比不同增益参数的效果

调增益时经常纠结选哪个指数。与其反复改代码,不如一次性画出 2x2 子图,分别用 1、2、3、4 四个增益指数,直观对比。下面是一个快速对比函数。

def compare_gains(data, dt=2000, gains=(1, 2, 3, 4)): n_traces, n_samples = data.shape time_ms = np.arange(n_samples) * dt / 1000.0 fig, axes = plt.subplots(2, 2, figsize=(12, 8)) for ax, g in zip(axes.flat, gains): gc = (time_ms / time_ms.max()) ** g gd = data * gc[:, np.newaxis] vm = np.percentile(np.abs(gd), 98) ax.imshow(gd.T, aspect='auto', cmap='seismic', vmin=-vm, vmax=vm, extent=[0, n_traces, time_ms.max(), 0]) ax.set_title(f'增益指数 = {g}') ax.set_xlabel('道号') ax.set_ylabel('时间 (ms)') plt.tight_layout() plt.show() # 调用 compare_gains(data, dt=dt)

逻辑说明:zip(axes.flat, gains)把四个子图和四个增益值配对,循环里复用同一套绘图逻辑。tight_layout自动调整间距,避免标签重叠。参数说明:gains元组可以按需改成(0.5, 1, 2, 3),覆盖更宽的范围。如果数据量大,这个函数会开四个图像缓冲,内存吃紧时改成逐个画、逐个保存。

5.2 把缩略图拼成工区概览图

一个工区几十条测线,每条的缩略图单独看太碎。常见做法是用PIL或matplotlib把缩略图拼成网格,加上测线号标签,一张图看全工区。下面是用PIL拼接的示例。

from PIL import Image import os def mosaic_thumbnails(thumb_dir, output_path, cols=5): files = sorted([f for f in os.listdir(thumb_dir) if f.endswith('.png')]) if not files: print('没有找到缩略图') return # 读取第一张图获取尺寸 first = Image.open(os.path.join(thumb_dir, files[0])) w, h = first.size rows = (len(files) + cols - 1) // cols # 创建空白画布 mosaic = Image.new('RGB', (cols * w, rows * h), 'white') for idx, fname in enumerate(files): img = Image.open(os.path.join(thumb_dir, fname)) r, c = divmod(idx, cols) mosaic.paste(img, (c * w, r * h)) mosaic.save(output_path) print(f'工区概览已保存: {output_path}') # 调用 mosaic_thumbnails('./quicklook', './overview.png', cols=5)

逻辑说明:divmod同时算出所在行和列,paste把每张缩略图贴到对应位置。cols=5表示每行 5 张,根据缩略图数量和屏幕宽度调整。参数说明:如果缩略图尺寸不一致,paste会按原尺寸贴,导致错位。所以批量出图时要固定figsize和dpi,保证每张缩略图尺寸相同。Image.new的背景色设白色,和地震剖面的底色一致,拼出来更整洁。

5.3 验证可视化结果是否忠实于原始数据

快速可视化最怕“好看但失真”。验证方法很简单:选一道数据,把增益后的振幅曲线和原始振幅曲线画在一起,看增益是否只改变了幅度比例,没有引入虚假同相轴。另外,用np.allclose检查增益计算前后的数据形状和道数是否一致。我一般会在批量出图后随机抽 3 条测线,用segyio重新读原始文件,对比data矩阵的均方根振幅,误差超过 1% 就说明读取环节有问题。从那以后我每次做快速可视化,都强制走一遍“读一道、画一道、对一道”的流程,确认无误再批量跑。希望帮到你。

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

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

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

立即咨询