11 KiB
反演剖面三维体:客户目标方法(Surfer)对照与客户端差距 — 2026-06-27
来源:客户提供的演示视频
ScreenShot/9f67e80cb823170bb9374d779ec4c0cb.mp4(Golden Software Surfer,13min) 与参考成果图ScreenShot/Weixin Image_20260627080012_429_2117.png。 结论一句话:客户用的就是"合并点云 + 3D 反距离加权(IDW) + 边界裁剪(Blanking) + 体绘制/等值面"。 客户端插值内核已与之一致,差距只在「搜索半径 / 边界裁剪 / 等值面」三点。
0. 背景与一次认知纠偏
用户反馈:"选多个 dd_inversion_data(江西理工四条井字相交测线)做三维体,得到的是四个剖面各自左右 拉伸的薄板,不连成完整体。"
排查中曾推荐"逐深度层各向异性插值"——看了客户演示视频后确认是过度设计。客户没有逐层做, 就是朴素的三维 IDW(对合并后的整团散点云一次性 3D 网格化)。本文以视频实证为准。
1. 客户目标方法(Surfer 实测,逐帧为证)
| 步骤 | 操作 | 视频帧(时间戳)证据 |
|---|---|---|
| 1. 合并点云 | 所有测线/剖面反演单元合并成一个 XYZC 文件 combine-e_m_m.xyz,列= X, Y, Elevation, Resistivity, Conductivity, Sensitivity |
t48.7:Grid Data 导入对话框,Data Type=XYZC |
| 2. 三维网格化 | Grid Data → 方法 Inverse Distance to a Power(IDW,幂次) → XYZC 直接生成 3D 网格体;注脚明示"does not extrapolate beyond the range of data" | t48.7:Gridding Method 选中 IDW;t97.5:Gridding 进度;t195:.grd has been created |
| 3. 边界裁剪 | 数字化测区边界多边形(Base vector / Polyline)→ Blanking 把体裁到测区真实足迹 | t292.5:Polygon 工具数字化边界 |
| 4. 三维渲染 | 3D View → 3D Grid Volume(out-BAIHUA.vtk):Volume render(Sliced / Slice count 500 / Tri-linear / Alpha blending / Opacity 80%) + Isosurface(阈值 isovalue) + Image slice(YZ/Z) |
t585:Volume render 属性;t682:Isosurface(isovalue=1794.39);t633:Image slice(YZ) |
要点:
- 真三维 IDW,对合并点云一次成体(非逐层 2D)。
- IDW 不外推到数据范围外;测区足迹靠 Blanking 多边形裁出(参考图那个不规则边界即来源于此)。
- 红色异常体 = 等值面抽取(Isosurface 按阈值)。
- 坐标为真实投影坐标 + 高程,可叠地形/影像底图。
2. 客户端现状(已实现部分)
生产路径:Api3dRepository::createVolume(src/data/api/Api3dRepository.cpp)
→ 把所有选中 ds 的反演单元按测线真实几何配准合并成 PointSet
→ buildVolume(pts, cellXY, cellZ, power, maxDist)(src/core/algo/VolumeBuilder.cpp)
→ 三维 IDW(src/core/algo/IdwInterpolator.cpp):maxDist 外置 NaN 留空。
即:「合并点云 + 3D IDW」内核与 Surfer 一致。参数见 src/data/repo/VolumeBuildParams.hpp:
cellXY=1.0, cellZ=0.5, power=2.0, maxDist=4.0。
渲染:VoxelActor(src/render/actors/VoxelActor.hpp)仅 GPU 体绘制(NaN→透明),无等值面
(GridContourActor/ContourBands 是 2D 网格等值线,非 3D 等值面)。地形/影像/坐标轴/电极点已有
(TerrainActor/TileBasemap/AxesActor/ElectrodeActor)。
3. 差距与修复(共 3 点)
不需改插值算法(内核已对);改的是搜索域、裁剪、与等值面。
G1. 搜索半径 maxDist 太小 → "四块板"
maxDist=4m 远小于井字测线间距 → IDW 只填测线 ±4m 管套,线间留空 → 四块薄板。
修复:把搜索半径放大到覆盖测区(或提供"覆盖全域"选项),对齐 Surfer 默认搜索域行为。
G2. 缺边界裁剪(Blanking) → 单纯放大半径只会"变粗"
这是用户观察到"调大 maxDist 只是让体看起来更粗"的真正原因:没有足迹裁剪,放大半径会把体 鼓满整个外接盒 → 粗大臃肿。Surfer 不粗,是因为 Blanking 把体裁到了测区真实多边形足迹。 修复:加足迹掩膜——
- 自动:散点平面凸包 / alpha-shape(或沿测线 buffer 并集);
- 或手动:支持用户数字化/导入边界多边形(对齐 Surfer Blanking)。 掩膜外体素整列置空(NaN/透明)。
G3. 缺 3D 等值面 → 出不来红色异常体
VoxelActor 只有体绘制。
修复:在体上加 vtkFlyingEdges3D / vtkContourFilter 抽等值面,阈值可调(对齐 Surfer Isosurface)。
4. 修复落地顺序
- G1+G2 一起做(插值搜索域放大 + 足迹掩膜)→ 出满铺、裁到测区足迹的体。这是核心,先做。
- G3 等值面(阈值可调)→ 出红色异常体。参考图第二主角,紧接着做。
- 影像底图/坐标轴/电极点复用现有。
5. 必须先和客户对齐的预期(避免"又看起来不对")
参考图是密集测网(顶面可见很多条测线点阵);江西项目只有四条井字线。
- 形态可复刻(满铺体 + 等值面 + 影像底图);
- 但框内细节出不来——参考图的细碎红异常源于密集采样,四条线之间只能给出平滑趋势, 等值面会是几个光滑大团。要那种精度需加密测线。
6. 非目标 / 说明
- 不改 IDW 内核算法本身(已与 Surfer 一致),不引入逐层各向异性(客户未用)。
- 各向异性搜索椭球可作为后续可选增强(Surfer 亦为可选项,非默认),本期不做。
- Kriging 仍为占位(
VolumeBuildParams::Model::Kriging,core 未实现),本期不依赖。
7. 续:白化方式之争 + 体绘制边界「梯田」(2026-06-28,branch fix/3d-volume-blanking-mask)
本节记录 §1–6 之后这一轮的来龙去脉、技术取舍与权威佐证,供后续决策不再反复。
7.1 前因后果(时间线)
客户原话:"我们这个白化确实有问题,填色了,填了蓝色"——无数据区被填成蓝色,而非 Surfer 那种透明白化。排查分三层、逐个修:
- 切片填蓝:
vtkImagePlaneWidget会按【输入标量范围】(含哨兵)自动 window/level,把哨兵顶到 LUT 最低色格(蓝)且不透明。 修复:SliceTool钉死SetWindowLevel([vmin,vmax])+ColorLutBuilder预留 0 号"白化槽"(全透明),哨兵(<vmin)钳到该槽即透明。实测证据:tests/spike/slice_alpha_probe.cpp(真 widget 离屏渲染 + 回读像素:背板透出=透明成功)。这条同时纠正了切片颜色映射(之前 colorbar 被错误拉伸到切片局部范围)。 - 早期"满屏蓝"是误判:实为
maxDist=0(自动覆盖测区,kDefMaxDist=0.0)把整个凸包填实=真实低值数据(蓝),不是空值(见 §3 G1)。日志实证请求体"maxDist":0。 - 三维体渗蓝:无数据格设哨兵
vmin-1、不透明度 0;三线性插值在"哨兵↔真值"交界处插出低值(蓝)且不透明度非零 → 渗一圈蓝。为消除,给体绘制加二值 mask(VoxelActor::makeMaskLike+assembleVolume用vtkGPUVolumeRayCastMapper+SetMaskInput+SetMaskTypeToBinary,mask=0 体素被光线投射硬跳过)。
mask 的副作用 = 用户 2026-06-28 截图的「竖条/梳齿/底边锯齿」:硬跳过=边界不再三线性插值/羽化。maxDist=0 下体填满凸包足迹(带斜边多边形),斜边在 1m 规则网格上离散成体素阶梯(staircase)。以前(SmartVolumeMapper+哨兵→不透明度0 软消隐)斜边被羽化抹圆、看不出;加 mask 后变成逐体素硬台阶。即使色阶不透明度=100% 也可见——因 mask 在不透明度传函【之前】就把体素从光线上删了(两个并行子代理:渲染侧 + 数据/建体侧共同确诊)。
7.2 取舍的本质
| 方案 | 边界观感 | 数据诚实度 | 误判风险 |
|---|---|---|---|
| 二值 mask(当前) | 体素梯田(难看) | ✅ 只画真数据、零假值 | 低(梯田明显是网格对齐的足迹边界,不像地质体) |
| 软消隐(哨兵→不透明度0,无 mask) | 平滑 | ❌ 边界是插值出的假值 | 高 |
7.3 「细蓝边」是什么 + 为何【不能】回退软消隐(权威佐证)
软消隐回退后的"细蓝边" = 真数据格与哨兵格(vmin-1)三线性插值出的、数据里根本不存在的假低阻值。低阻在物探通常指含水/导电体,这圈蓝出现在测区最外缘会被误读成"边界存在真实低阻(导电)异常带"——真有数据分析歧义。
多个权威来源一致认定"插值进 NoData 产生边缘伪影"是错误做法、应 mask 排除:
- ESRI 官方:双线性/三次插值 "interpolate incorrectly into the NoData and background areas... producing artifacts or black ridges" —— 我们的"蓝脊"与之同源(哨兵钳到最低色=蓝)。https://support.esri.com/en-us/knowledge-base/faq-why-do-bilinear-interpolation-and-cubic-convolution-000003271
- GDAL 官方:正确做法是把 masked NoData 的 "weights of contributing source pixels are set to zero to ignore them" / "will not be used in interpolation"。https://gdal.org/en/stable/programs/gdalwarp.html
- rasterio:bilinear 重采样在 NoData 边界产生 invalid 值(已知 issue #1721)。https://github.com/rasterio/rasterio/issues/1721
- Golden Software Surfer(客户参照工具):NoData "removed from the neighborhood",不跨它插值。https://surferhelp.goldensoftware.com/gridmisc/Blanked_Nodes_Grid_Filter.htm(定义 https://surferhelp.goldensoftware.com/glossary/def_blanking.htm)
- 凸包外 = 外推:"Extrapolated data is usually meaningless and misleading." https://github.com/fatiando/fatiando/pull/44、https://www.spatialanalysisonline.com/HTML/gridding_and_interpolation_met.htm
结论:二值 mask = 业界标准的"把 NoData 排除出插值"做法,是对的;梯田只是 mask 在斜足迹边界上的网格离散观感(诚实、不误导)。不应回退软消隐(=让哨兵参与插值=以上权威明确反对的造假值做法)。
7.4 决策与待办(截至 2026-06-28,本分支未提交)
- ✅ 保留二值 mask(数据诚实/合规,符合 ESRI/GDAL/Surfer 标准)。
- 梯田若要压平,走不造假值的路(二选一,待用户/客户拍板):
- (a) 细化 XY 网格:
cellXY1m→0.5m,阶梯缩到亚像素;代价:体素×4、耗时 ~3.5s→~14s、内存×4。 - (b) 接受梯田:它诚实、且明显是足迹边界,不会被当成地质体。
- (a) 细化 XY 网格:
- 渲染侧本轮其它已落地修复(排查"分层/稠密"时做、确认非主因但保留):GPU 探测+CPU 回退(
setVolumeGpuSupported)、细采样距离+UseJittering、ScalarOpacityUnitDistance=对角/10、去kMaxOpacity(改由色阶「不透明度」单一控制、100%=实心)、移除工具条「透」滑块。 - 附带缺陷(待修):
VoxelGenerateRequest::maxDist结构体默认4.0(src/data/dto/Vtk3dRequests.hpp:18)与对话框kDefMaxDist=0.0(VolumeParamsDialog.cpp:34)不一致——绕过对话框直建会拿到 4m → 退回"四块板/线间空隙"老问题,应统一为 0。 - 抽稀空间哈希
(ix*p1)^(iy*p2)^(iz*p3)(VolumeBuilder.cpp~146-151)为 XOR 非单射、有碰撞风险(与本症无关,但宜换(iz*ny+iy)*nx+ix线性键)。