news 2026/10/3 3:34:17

AVO正演从理论到实践:Zoeppritz方程、Aki-Richards近似与Python实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AVO正演从理论到实践:Zoeppritz方程、Aki-Richards近似与Python实现

简介:这份资源面向石油物探方向的研究生及地震数据处理初学者,聚焦AVO正演模型实验与地震数据正演这一核心课题。包内共4个cpp源码文件,压缩包约12KB,均为C++实现的正演程序,涵盖加噪音条件下的AVO正演模型实验、角度区域处理以及直接形成CDP道集等典型环节,部分代码已在中石油相关软件中投入实际应用,具备工程参考价值。目前已有197人学习下载。读者可借助这些源码理解AVO正演的基本流程与实现思路,掌握加噪音对正演结果的影响、角度域数据的组织方式以及CDP道集的直接生成方法,从而为地震数据正演建模、算法复现与后续处理研究提供可运行的代码基础,适合作为课程实验、课题入门与算法对照的参考资料。

1. AVO 正演到底在算什么:从一份研究生作业说起

如果你手上拿到一个叫AVOANDAVAForward.rar的压缩包,打开一看是几段 Fortran 或 MATLAB 脚本,注释里写着「avo正演」「地震数据正演」,那大概率是石油物探方向研究生的课程作业或课题起步代码。它要干的事其实很具体:给定一套水平层状介质模型,每层的纵波速度、横波速度、密度已知,用 Zoeppritz 方程或其近似式,算出不同入射角下反射界面的反射系数,再和子波褶积,合成出一张 CDP 道集。这张道集就是后续做 AVO 属性分析、烃类检测的输入。换句话说,AVO 正演是「已知地下模型,正着推地震响应」,和反演的方向正好相反。它适合三类人:刚进课题组要跑通第一个合成道集的研究生、需要造标签数据做深度学习的算法工程师、以及想验证自己 AVO 属性提取流程对不对的物探从业者。这一章先把「地震正演」这件事的边界划清楚,后面几章再动手。

2. 从 Zoeppritz 到 Aki-Richards:选哪个公式决定你的道集长什么样

2.1 精确解和近似解的取舍

Zoeppritz 方程是 AVO 正演的理论基石,它给出平面波在两种弹性介质分界面上,反射系数随入射角变化的精确解。四个边界条件——位移连续、应力连续——联立出四个方程,解出反射和透射的 P 波、S 波系数。问题是这个解太复杂,写出来是一堆含三角函数的有理式,物理意义不直观,而且对速度密度的小扰动不敏感,做属性分析时不好用。

所以实际正演里,绝大多数人用的是近似式。Aki-Richards 公式是最常见的一个,它把反射系数写成入射角 θ 的函数:

R(θ) ≈ (1/2)(ΔVp/Vp)(1/cos²θ) - 4(Vs/Vp)²(ΔVs/Vs)sin²θ + (1/2)(Δρ/ρ)(1 - 4(Vs/Vp)²sin²θ)

其中 ΔVp、ΔVs、Δρ 是界面两侧的速度密度差,Vp、Vs、ρ 是平均值。这个式子把 AVO 响应拆成了三个部分:零偏移距项、梯度项、曲率项。Shuey 进一步化简,把 R(θ) 写成截距 P 加梯度 G 乘 sin²θ 的形式,这就是后来 AVO 属性分析里 P、G 交会图的来源。

选哪个?我的经验是:做方法验证、写论文对比精确解和近似解误差时,用 Zoeppritz;做合成道集喂给反演或神经网络时,用 Aki-Richards 或 Shuey,因为快,而且和后续属性提取的假设一致。如果你用精确解生成道集,再用近似式去反演,误差里混着近似误差,说不清楚是谁的锅。

2.2 用 Python 实现 Aki-Richards 正演的最小代码

下面这段代码是我一般用来快速验证模型响应的最小实现,输入是上下两层介质参数和入射角数组,输出反射系数曲线。

import numpy as np def aki_richards(vp1, vs1, rho1, vp2, vs2, rho2, angles_deg): """ Aki-Richards 近似计算反射系数 vp1, vs1, rho1: 上层纵波速度(m/s)、横波速度(m/s)、密度(g/cc) vp2, vs2, rho2: 下层参数 angles_deg: 入射角数组,单位度 返回: 反射系数数组 """ theta = np.radians(angles_deg) vp = (vp1 + vp2) / 2.0 vs = (vs1 + vs2) / 2.0 rho = (rho1 + rho2) / 2.0 dvp = vp2 - vp1 dvs = vs2 - vs1 drho = rho2 - rho1 term1 = 0.5 * (dvp / vp) / (np.cos(theta)**2) term2 = -4.0 * (vs / vp)**2 * (dvs / vs) * (np.sin(theta)**2) term3 = 0.5 * (drho / rho) * (1 - 4.0 * (vs / vp)**2 * np.sin(theta)**2) return term1 + term2 + term3 # 示例:砂岩页岩界面,含气砂岩速度降低 angles = np.arange(0, 45, 5) R = aki_richards(3000, 1500, 2.4, 2600, 1300, 2.2, angles) for a, r in zip(angles, R): print(f"入射角 {a:2d}° 反射系数 {r:+.4f}")

这段代码里几个参数需要说明。vp1/vs1/rho1是上层,通常代表页岩盖层;vp2/vs2/rho2是下层储层。含气砂岩的典型特征是 Vp 明显降低、Vs 变化小、密度略降,所以你会看到反射系数随入射角增大而变得更负——这就是所谓的第三类 AVO 异常。angles_deg一般取 0 到 40 度,超过 40 度近似误差会变大。np.cos(theta)**2在零角度时为 1,大角度时迅速增大,这是近似的固有特性,实际处理中远角道集信噪比低,通常截断在 35 到 40 度。

跑完这段,你会得到一条 R-θ 曲线。如果曲线从正变负、或者负值越来越负,说明模型有 AVO 异常。但这只是单个界面,真正的地震道集需要把多个界面的反射系数和子波褶积,再按角度排列成道集。

2.3 从反射系数到合成道集:子波褶积和角度道集排列

单个界面的反射系数只是一个数,地震记录是一个时间序列。要把反射系数变成地震道,需要和地震子波做褶积。常用的是 Ricker 子波,主频一般取 30 到 40 Hz,对应常规地震资料的主频范围。

def ricker_wavelet(freq, length, dt): """生成 Ricker 子波 freq: 主频(Hz) length: 采样点数 dt: 采样间隔(s) """ t = np.arange(length) * dt - (length * dt) / 2 pi2f2t2 = (np.pi * freq * t) ** 2 return (1 - 2 * pi2f2t2) * np.exp(-pi2f2t2) def build_angle_gather(layer_vp, layer_vs, layer_rho, layer_t, angles_deg, freq=35, dt=0.001): """ 多层模型合成角度道集 layer_vp/vs/rho: 每层参数列表,长度 n layer_t: 每层顶界面的双程旅行时列表,长度 n angles_deg: 角度数组 返回: 道集矩阵 (n_angles, n_samples) """ n_layers = len(layer_vp) n_samples = int(layer_t[-1] / dt) + 200 wavelet = ricker_wavelet(freq, 81, dt) gather = np.zeros((len(angles_deg), n_samples)) for i in range(n_layers - 1): R = aki_richards(layer_vp[i], layer_vs[i], layer_rho[i], layer_vp[i+1], layer_vs[i+1], layer_rho[i+1], angles_deg) idx = int(layer_t[i+1] / dt) for j, r in enumerate(R): gather[j, idx:idx+len(wavelet)] += r * wavelet return gather

这里layer_t是每个界面反射波的双程旅行时,需要根据层厚度和速度算出来。n_samples留了 200 个点的尾巴防止子波被截断。idx是界面在时间轴上的位置,每个角度的反射系数乘上同一个子波,叠加到对应位置。最终gather的每一行是一个角度的道,列是时间采样。把 gather 画出来就是一张角度道集图,横轴角度、纵轴时间、颜色表示振幅。

提示:子波长度一般取 81 或 101 个点,太短会截断旁瓣,太长会拖尾干扰深层反射。主频根据你的目标层深度和分辨率需求调,浅层用高频,深层用低频。

3. 模型参数怎么设:速度密度从哪来、角度范围怎么定

3.1 用测井曲线还是经验公式

做 AVO 正演,模型参数是命根子。最理想的情况是有一口井的纵波速度、横波速度、密度曲线,直接读出来做层状简化。但很多研究生手里没有实测横波曲线,这时候就得用经验公式估算。常见的有 Castagna 泥岩线:

Vp = 1.16 * Vs + 1360 (m/s)

或者 Gardner 公式从速度估密度:

ρ = 0.23 * Vp^0.25 (g/cc, Vp 单位 ft/s)

这些公式有适用条件,Castagna 适用于水饱和碎屑岩,Gardner 适用于常规沉积岩。如果你做的是碳酸盐岩或者含气层,经验公式误差会很大,这时候宁可用岩石物理模型(比如 Gassmann 流体替换)去算,也不要硬套。

我一般会建一个表格,把每层的 Vp、Vs、密度、厚度列清楚,再检查一下 Vp/Vs 比值是否合理。砂岩的 Vp/Vs 一般在 1.6 到 1.8,页岩在 1.8 到 2.0,含气砂岩可能低到 1.5。如果算出来 Vp/Vs 是 1.2 或者 2.5,那八成是参数填错了。

岩性Vp (m/s)Vs (m/s)密度 (g/cc)Vp/Vs
页岩盖层300015002.402.00
含水砂岩280016002.301.75
含气砂岩240015502.151.55
致密灰岩550030002.651.83

这张表是我做正演时的起手模板,你可以根据实际工区调整。注意含气砂岩的 Vp 比含水砂岩低了 400 m/s,但 Vs 只降了 50 m/s,密度降了 0.15,这就是 AVO 异常的来源。

3.2 角度范围、子波主频和采样率

角度范围不是随便定的。常规海上拖缆最大入射角能到 40 到 45 度,陆上可控震源可能只有 30 到 35 度。你做正演时如果取到 50 度,合成道集在远角部分会失真,因为 Aki-Richards 近似在大角度误差急剧增大。我的习惯是最大取 40 度,步长 5 度,这样得到 9 个角度的道集,足够做 AVO 属性拟合。

子波主频决定分辨率。主频 35 Hz、采样率 1 ms 是常规配置。如果你要模拟薄层调谐,主频可以提到 50 Hz,采样率 0.5 ms。但要注意,主频越高,子波旁瓣越明显,合成道集上会出现假的同相轴,别把它当成真实反射。

采样率的选择要满足 Nyquist 定理,1 ms 采样对应 500 Hz Nyquist 频率,远高于地震信号带宽,没问题。但如果你做的是高频正演,比如 100 Hz 主频,采样率至少 0.5 ms。

3.3 层厚和调谐效应

层厚小于子波波长四分之一时,顶底反射会干涉,形成调谐。调谐效应会让振幅和 AVO 梯度都发生变化,如果你用调谐后的道集去反演,得到的阻抗和真实值有偏差。做正演时,如果目标层很薄,要么把层厚设得足够大避开调谐,要么就专门研究调谐对 AVO 的影响。

我一般会先算一下子波的主波长:λ = Vp / f。比如 Vp 3000 m/s,f 35 Hz,λ 约 86 m,四分之一波长约 21 m。如果储层厚度小于 21 m,就要小心调谐。这时候可以做一个层厚扫描,从 5 m 到 50 m 变化,看 AVO 梯度的变化趋势,找到稳定区间。

4. 跑通正演后怎么验证:三个检查点和两个对比实验

4.1 检查点一:零角度反射系数是否等于波阻抗差

零角度时,Aki-Richards 退化为:

R(0) = (ρ2Vp2 - ρ1Vp1) / (ρ2Vp2 + ρ1Vp1)

也就是波阻抗差除以波阻抗和。你可以在代码里加一行,把 angles 设为 0,看输出是否等于手算的波阻抗反射系数。如果不等,检查公式实现有没有漏项或者符号错误。这是最基本的自检,但很多人跳过,结果后面道集极性反了都不知道。

4.2 检查点二:道集同相轴是否随角度变化

合成道集画出来后,看目标层对应的同相轴。如果振幅随角度不变,说明你的反射系数计算里角度项没起作用,可能是np.radians忘了加,或者sin²θ写成了sinθ。如果振幅随角度变化但趋势不对,比如含水砂岩应该振幅减小,结果反而增大,那可能是 Vp/Vs 比值设反了。

4.3 检查点三:和精确 Zoeppritz 解对比

找一个公开的 Zoeppritz 实现,或者自己写一个,把同一组模型参数输入,对比近似解和精确解在 0 到 40 度的差异。一般来说,Aki-Richards 在 30 度以内误差小于 5%,40 度时可能到 10%。如果你发现误差超过 20%,检查一下速度对比度是不是太大——近似式假设速度差远小于平均速度,如果上下层速度差了一倍,近似就失效了。

4.4 对比实验:含气与含水砂岩的 AVO 响应

这是最直观的验证。用同一套骨架参数,只把孔隙流体从水换成气,看道集变化。含水砂岩的反射系数随角度可能变化不大,含气砂岩则会出现明显的振幅增大或极性反转。如果你做出来的含气道集和含水道集几乎一样,那说明流体替换没做对,或者参数里 Vs 没跟着变。

4.5 对比实验:不同子波主频对 AVO 梯度的影响

用 25 Hz、35 Hz、45 Hz 三个主频分别合成道集,提取 AVO 梯度,看梯度值是否稳定。如果主频变化导致梯度大幅波动,说明调谐效应严重,你的层厚可能太薄,或者子波旁瓣干扰了反射系数提取。这时候要么加厚层,要么在提取属性前做谱白化。

5. 避坑与排查:AVO 正演里最容易翻车的五个地方

5.1 道集极性反转但没发现

现象:合成道集上目标层振幅随角度从负变正,你以为这是第三类 AVO,结果检查发现是反射系数符号搞反了。 原因:Aki-Richards 公式里 ΔVp 定义为下层减上层,如果你写成上层减下层,整个曲线极性就反了。 解决:在代码里固定dvp = vp2 - vp1,并在零角度检查波阻抗差符号。如果上层波阻抗大于下层,反射系数应为负,道集上表现为波峰还是波谷取决于子波极性,但相对关系要对。

5.2 角度单位混用

现象:反射系数曲线形状怪异,大角度时数值爆炸。 原因:np.sin和np.cos接受弧度,但你传进去的是角度值,35 度当成 35 弧度算,结果完全不对。 解决:在函数入口统一用np.radians转换,或者在参数名里写明angles_deg,调用时检查。

5.3 子波采样率和道集采样率不一致

现象:褶积后道集同相轴变宽或变窄,时间厚度对不上。 原因:子波的dt和道集的dt不一致,比如子波用 1 ms 生成,道集用 2 ms 采样,褶积时没有重采样。 解决:生成子波和构建道集用同一个dt,或者在褶积前用scipy.signal.resample统一采样率。

5.4 层厚设得太薄导致调谐

现象:目标层顶底反射分不开,AVO 梯度随层厚剧烈变化。 原因:层厚小于四分之一波长,顶底反射干涉。 解决:先算主波长,确保层厚大于四分之一波长。如果实际储层就是薄,那就把正演目的改成研究调谐效应,而不是提取真实 AVO 属性。

5.5 忽略横波速度的流体敏感性

现象:含水换含气后,Vp 降了,但 Vs 没变,AVO 异常不明显。 原因:Gassmann 流体替换中,Vs 对流体不敏感,但 Vp 和密度敏感。如果你只改 Vp 不改密度,反射系数变化不够。 解决:用 Gassmann 方程同时计算 Vp、Vs、密度变化,或者至少按经验把密度也调低 0.1 到 0.2 g/cc。

6. 进阶技巧:用 AVO 正演造深度学习训练集

如果你跑通了单个模型的正演,下一步很可能是批量生成道集,用来训练神经网络做 AVO 反演或流体识别。这时候单条曲线的手工操作就不够了,需要参数化扫描。

我一般会定义一个参数空间:Vp 从 2200 到 3200 m/s,Vs 从 1200 到 1800 m/s,密度从 2.0 到 2.5 g/cc,层厚从 10 到 50 m,子波主频从 25 到 45 Hz。用拉丁超立方采样抽 5000 组,每组生成一个角度道集,标签是对应的 Vp、Vs、密度、流体类型。生成脚本的核心循环和前面一样,只是外面套一层采样。

from scipy.stats import qmc def generate_dataset(n_samples=5000): sampler = qmc.LatinHypercube(d=5) samples = sampler.random(n=n_samples) # 映射到参数范围 vp_shale = 3000 + samples[:, 0] * 200 vp_sand = 2200 + samples[:, 1] * 1000 vs_sand = 1200 + samples[:, 2] * 600 rho_sand = 2.0 + samples[:, 3] * 0.5 thickness = 10 + samples[:, 4] * 40 # 对每组参数调用 build_angle_gather # 保存道集和标签

这里用拉丁超立方而不是均匀网格,是因为 5 维均匀网格点数会爆炸,拉丁超立方能用更少样本覆盖更均匀。生成 5000 组大概需要几分钟到十几分钟,取决于采样点数和角度数。保存成 npy 或 hdf5 格式,训练时直接读。

注意:生成训练集时,角度范围要和实际资料一致。如果你用 0 到 40 度训练,实际资料只有 0 到 30 度,网络在远角部分会外推,误差不可控。另外,子波主频也要和实际资料匹配,否则网络学到的是子波特征而不是 AVO 特征。

还有一个技巧是加噪声。合成道集太干净,网络会过拟合。我一般加 5% 到 10% 的高斯噪声,或者按实际资料的信噪比加。加噪后再做 AVO 属性提取,看梯度是否稳定,如果噪声一加梯度就乱飞,说明你的正演参数太理想,实际资料更差。

最后说一个我自己的习惯:每次生成完数据集,随机抽 10 个道集画出来,肉眼扫一遍。如果看到某个道集同相轴断裂、振幅异常大、或者时间轴对不齐,大概率是某组参数越界了。比如 Vp 小于 Vs,或者密度为负,这些在采样时就要卡住边界。别小看这一步,我见过有人生成了几万条道集,训练完才发现里面有 30% 是物理上不可能的模型,网络学了一堆垃圾。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 3:34:15

Lumerical farfieldpolar3d复电场远场分析全解析

1. 这不是个“命令”,而是一把打开远场光学世界的三维标尺如果你刚在Lumerical FDTD Script里敲下farfieldpolar3d,却只看到一串报错或空数组,别急着翻文档——这根本不是个孤立的函数调用,而是整套远场建模逻辑的终点站。我第一次…

作者头像 李华
网站建设 2026/10/3 3:33:46

PostgreSQL v19 新特性解读:INSERT ON CONFLICT DO SELECT 与 UPSERT 语义补全

最近在跟进 PostgreSQL 新版本动态时,我用 DeepSeek 把社区里零零散散的讨论梳理了一遍,最值得展开聊的一条是 v19 的 INSERT ... ON CONFLICT ... DO SELECT。刚开始我也以为这只是 UPSERT 语法多了一个分支,后来把邮件列表、commitfest 议题…

作者头像 李华
网站建设 2026/10/3 3:33:17

宽带GSC波束形成实战:麦克风阵列语音增强Python实现

1. 为什么宽带GSC波束形成是智能音箱落地的“咽喉要道”你拆开市面上任何一款中高端智能音箱,比如某米、某度、某为的主力型号,十有八九会看到一块印着4~8个麦克风的小PCB板。它不发声,却决定着整台设备的“听觉智商”。很多人以为…

作者头像 李华
网站建设 2026/10/3 3:33:16

ARIMA-CNN-LSTM混合模型实战:Python实现与参数调优全攻略

做时间序列预测这些年,ARIMA、CNN、LSTM这三个词经常被单独拎出来讲,但真正把它们拧成一个模型去干活的项目其实不多。我最近刚好完成了一个基于ARIMA-CNN-LSTM混合模型的预测研究,用Python整套实现下来,踩了不少坑,也…

作者头像 李华
网站建设 2026/10/3 3:32:39

MATLAB单相桥式晶闸管有源逆变仿真建模与参数计算

做单相桥式有源逆变仿真的时候,很多人最常犯的误区是先去找“逆变电路”有没有现成模型,但其实逆变和整流在主电路拓扑上根本是同一种东西,区别只在于触发控制角的范围。这次我用MATLAB 2018a从零搭了一个单相桥式晶闸管有源逆变电路&#xf…

作者头像 李华