三维重建技术文档

三维重建:实现过程说明

0. 整体在算什么

输入是已完成的空三 :每张图有内外参、稀疏连接点及多视观测关系。MVS 阶段要补全稠密几何 并变成可浏览的三维模型

数据在工程里大致经历这条链:

复制代码
原始影像 + 相机畸变
  → 去畸变影像(像素与针孔模型一致)
  → 每张参考图的深度图 + 法线图
  → 多视一致的稠密点云
  → 三角网格(Delaunay + 可见性图割)
  → 网格优化、减面、滤波
  → 每三角面选源图、拼纹理、切 LOD
  → OSGB/B3DM 等瓦片 + 可选 DSM/TDOM

1. 工程接口:CC / Colmap → OSG

在做什么

把第三方空三成果翻译成统一的内存模型 ,再序列化成二进制 .OSG ,供后续所有步骤读取。这一步不做稠密匹配或构网。

CC(BCC/XML)过程

  1. 解析 XML:空间参考(优先非经纬度投影/局部系)、相机表、影像表、连接点与 tracks。
  2. 为每个物理相机建立内参向量:fx, fy, cx, cy, k1, k2, k3, p1, p2, skew(OpenCV 顺序在存储时与 OpenCV distCoeffs 有 k3/p 顺序差异,去畸变时会调换)。
  3. 每张影像绑定相机 ID、外参(旋转+中心)、原始路径;连接点带各视观测像素坐标。
  4. 可选:用调用方给的 (x,y,z) 覆盖场景坐标原点偏移(大坐标数值稳定)。
  5. 整体写入 .OSG(二进制块:相机、点、图、路径映射等)。

Colmap 过程

  1. cameras.binimages.binpointOSG.bin 等 COLMAP 导出。
  2. 映射到同一套 MVSBlock 结构(相机模型、影像位姿、稀疏点)。
  3. 同样 SaveOSG;路径与坐标系与 CC 路径一致,后续流程无差别。

本质 :一次性的格式归一化,把"空三软件私有工程"变成"本引擎可读工程"。


2. 畸变改正

在做什么

PatchMatch 假设中心透视 + 无畸变 。原始 UV 影像带径向/切向畸变,必须先重采样到新像素网格,并更新"影像路径表",使后续只读去畸变图。

实现过程

  1. 按相机分组 :同一 camera_id 的所有影像共享一组 remap 表,避免重复计算。
  2. 建 remap
    • 无 skew:OpenCV initUndistortRectifyMap,得到 mapx/mapy(浮点映射:畸变像素 → 去畸变像素)。
    • 有 skew:手写逐像素映射(考虑 sk/fy 等)。
  3. 重采样 :对每张图用 remap 输出到 working_dir/undistort/;可用 CUDA Warp、OpenCL 或 CPU。
  4. 掩码 :若提供 mask_pair.txt,按影像名加载二值掩码,与去畸变结果对齐,写入相机级 undist_mask
  5. 维护映射表 :JSON 记录 ImageID → 去畸变文件路径,空三里的几何仍用同一套内参(畸变系数在匹配阶段不再使用)。
  6. 超大图切分 (可选):若平均影像边长 > image_max_size
    • 将单张大图切成多张"虚拟影像",每张有独立 ID 和 ROI;
    • 更新空三并另存 split.OSG 与新的路径表;
    • 目的:控制单张加载显存/内存上限。

3. 稠密匹配(PatchMatch)

在做什么

对每张可作为参考图 的影像,在邻接图上估计逐像素深度 + 表面法向 ,输出 depth/ 下与影像 ID 对应的深度/法线文件(尚未有点云)。

3.1 准备:视图对与深度范围

  1. 按空三邻接关系,为每张图选约 4 张邻接图(1 参考 + N 源),邻接数不足则该图不参与稠密化。
  2. 深度范围 (每张参考图):
    • 把所有可见且被≥2/3 视观测的稀疏点投影到该相机,取深度 min/max;
    • 再用场景包围盒 8 角点投影裁剪;
    • 上下各扩 10% margin;
    • 电力线模式 :抬高 min_depth 下限(线状目标稀疏点少,避免范围过宽)。
  3. 深度图分辨率 = 去畸变图分辨率 ÷ 2^depth_scale_level(常用降一级,RGB 上 patch 更大、更稳)。

3.2 GPU PatchMatch 单像素在算什么

对每个参考图像素 (x,y),维护状态:深度 d、法向 n、各源视代价

代价(NCC)

  1. (d,n) 构造单应:平面诱导 homography,把参考 patch warp 到源图。
  2. 在参考图取多尺度 patch(如 7×7 与 13×13),在源图用 CUDA 纹理双线性采样对应 patch。
  3. 计算归一化互相关 NCC;多源视取加权得分。

优化(迭代)

  1. 空间传播 :从邻像素拷贝 (d,n),沿扫描线用平面几何传播深度(水平/竖直方向解析求交)。
  2. 随机扰动 :在深度、法向锥内随机试探更优解(curand)。
  3. 视图选择SelProbCalc 用 forward/backward message 更新各源视被选概率(多视一致性)。
  4. 多尺度:scale_level 控制先在粗分辨率跑,再细化。

Batch 内:最多约 100 张参考图一批,本批所有参考+邻接图影像一次性上传 GPU;批结束释放 RGB,深度写盘。

3.3 交叉深度过滤(多视几何校验)

对每张参考深度图:

  1. 像素 (c,r) 深度 d → 用 I2W 投影矩阵变到世界点 P
  2. 对每个邻接图,用 W2IP 投到邻接像素 (x',y'),读邻接深度 d'
  3. |d'/d_proj - 1| ≤ max_diff_ratio(默认约 1%)则计一票。
  4. 票数 ≥ min_num_fuse_point_(默认 2,含自身)则保留该像素,否则置 0。
  5. 写回深度图,并写 .crs 标记已过滤。

实现上用 LRU 缓存邻接深度图,并按"下一 filter 哪张图最省 IO"贪心选序。

3.4 分块稠密(工程调度)

  • Pre:统计要跑多少"匹配块 / 过滤块"(按影像分组,非空间切块)。
  • Match 块:只跑 PatchMatch,写深度。
  • Filter 块:只跑交叉过滤。

匹配尽量整景 batch,避免块边界重复匹配;过滤可分批。


4. 点云融合与 LiDAR

4.1 深度图 → 稠密点云

目标 :把多张深度图合成带颜色、带"被哪些视看到"权重的点集(.vpc)。

过程(逐参考图、可 OpenMP)

  1. 已有融合点列表 FusePoint(3D 位置、参考法向、融合像素数、各视权重)。
  2. 扩展已有 3D 点:把 3D 点投回当前参考深度图;在圆形窗口内找深度/法向接近的未标记像素,累加 3D 坐标;记录该视贡献权重(遮挡时记 -1)。
  3. 种子新点 (当前图尚无足够融合时):扫描未标记像素,合法深度+法向 → 世界坐标,作为新 FusePoint,再对邻域做同样窗口融合。
  4. 邻域准则:相对深度差 < max_depth_error_(约 0.6%)、法向夹角 < 45°、窗口内综合代价 < 1。
  5. 融合结束后:按可见影像 从去畸变图采样 RGB(texture_bands 指定波段),得到彩色点云。
  6. 可选导出 LAS/PCD/PLY;点云 OSGB 用 Poisson 采样 + 空间分块建树(见 LOD 点云)。

平均点距 mean_distance:由融合点间距统计,供后续网格简化尺度。

4.2 LiDAR(nPointFusionType

类型 过程
0 仅 MVS 融合点进入构网
1 以 LAS 为主,可用 CGAL 前进前缘等从激光点直接建壳
2 MVS 点与激光点合并后再 Delaunay;dfPointFusionArg 控制融合距离/权重

激光文件须与空三同一坐标系;水体掩码可在构网前剔除水面附近三角化。


5. 网格重建与后处理

5.1 Delaunay + 可见性图割(核心构网)

输入 :融合点云,每点带 point_views(视 ID + 可见性权重 α)。

步骤

  1. 3D Delaunay 四面体化(CGAL):点做空间排序后插入,得到四面体网格。
  2. 支撑点(可选):检测狭长四面体,按概率在内部插入辅助点,减轻"穿透"虚假面;水体边界附近特殊处理。
  3. 赋权
    • 每个四面体是一图节点;四个面是四条边,容量存在 cell_info
    • 每个相机:定位相机中心所在四面体,与源点相连;相机视锥内可见的凸包 facet 连到 sink/source。
    • 对每个点-视射线:沿射线穿过的一系列四面体,在对应面上累加 α_vis(可见性权重);结合 GSD 估计的 σ、形状惩罚等。
  4. 最大流/最小割(IBFS):划分"内部 / 外部"单元。
  5. 提取表面:相邻单元一侧在源、一侧在汇 → 公共三角面成为 mesh 三角;统一法向朝向。
  6. 清理:删小连通片、退化面、非流形等。

输出 :块目录下 raw mesh(顶点+三角,无纹理)。

5.2 网格精细化(SceneRefine)

在 raw mesh 上,用多视光度一致性微调顶点(参考 OpenMVS):

  • 建立面-邻接、选最多 3 张参考视;
  • 在 3×3 窗口内比较渲染灰度与真实影像梯度;
  • 平滑项 + 数据项迭代;可选 CUDA Refiner 加速;
  • 分辨率比构网低一级(resolution_level - 1)。

5.3 减面、滤波、边界平滑

  • QEM:二次误差度量边折叠,控制面数到与 GSD/点距匹配的量级。
  • MeshFilter(含 CUDA):拉普拉斯/双边类平滑,去尖刺噪声。
  • MeshBoundarySmoothing:对开放边界(水面、裁剪边)做公平化/洞处理,避免锯齿墙。

6. 纹理映射

在做什么

给每个三角面选一张源影像,裁 patch、拼 atlas、消除接缝,再交给 LOD 写文件。

过程

  1. 面→多视投影评分ProjectFaces2Views):

    • 建 mesh 的 AABB 树;
    • 对每张去畸变图:视锥裁剪可见三角面 → 光栅化到 face_id 缓冲 + 深度缓冲 → 处理遮挡;
    • 在投影区域内算质量分 (入射角、分辨率、可见面积等),每面得到 (view_id, quality, color) 列表。
  2. 面片选图FaceViewSelection):

    • 三角邻接建图;连通分量上做多视标签优化(数据项=投影质量,平滑项=邻面同图偏好);
    • 得到每面最佳 view_id,相邻面尽量同图以减少接缝。
  3. 参数化 + 提 patchGenerateTexture):

    • 按连通 patch 做 UV 参数化(含洞处理 ParameterizationHole);
    • 从选中影像裁三角 patch 图像,记录每面 UV。
  4. 接缝处理

    • GlobalSeamLeveling:在 patch 边界做颜色线性过渡;
    • LocalSeamLeveling:沿 seam 图做局部 Poisson/梯度域融合。
  5. 打包PackTexture):

    • 多 patch 装箱进 4096/8192 atlas,生成 face_to_atlas 与新 UV;
    • texture_format、质量参数写 jpg/png/webp。
  6. 可选 锐化nSharpeningLevel)在写 atlas 前对 patch 做反锐化。


7. 分块重建与合并

7.1 为什么要分块

单块要同时扛:多视影像缓存 + 深度/点云 + Delaunay + 纹理 ,内存随像素总量增长。分块让每块只在子空三 + 子影像 ROI 上跑完全流程。

7.2 怎么切(BlockCut)

  1. 统计全局:连接点、每图有效像素、点-视射线。
  2. 若指定 tile_size > 0规则网格 切 XY(+Z 约束),按原点 (tile_x,tile_y,tile_z) 对齐。
  3. 否则 自适应二分
    • 估计当前块内存(公式含:patch 批影像、refine、texture 像素量);
    • 若超过可用内存 80%:在 X/Y/Z 中选使两侧像素数、射线数、点数最均衡的平面切开;
    • 递归直到满足内存或 max_depth
    • 块太小(点<100 且像素<10万)直接收为一块。
  4. 每块:裁剪属于块 bbox 的连接点/影像 ROI,导出子 .OSGBlock_i/,写 JSON 记录块名与 global/tight bbox。

7.3 单块流水线

对块 i 依次:加载块 depth → Fuse → Delaunay 构网 → Refine → Texture → LOD 写 OSGB/B3DM...

块 bbox 外扩 tile_overlape 用于纹理/接边,避免缝上缺纹理。

7.4 合并

  • 各块已生成 Data/Tile_x/Tile_x.osgb 等子根;
  • 合并阶段:收集子根路径,生成顶层单一 OSGB/B3DM tilesetCollectBlocksIntoOne*);
  • smooth_overlape:在块公共边界对重叠三角做几何/颜色混合,减轻高程台阶。

8. 成果:DSM / TDOM

DSM(GenerateDSM

  1. 用块点云或深度,按 dfDsmGsd 在 XY 上铺格网;
  2. 每格取最高点(表面模型,非地面);
  3. pszBoundary 栅格化掩码,范围外 NoData;
  4. GDAL 写出 GeoTIFF 等。

TDOM(GenerateTDOM

  1. 已有带纹理 mesh:正射投影到水平格网;
  2. 每像素找最可见/最前三角面,用重心坐标插值纹理颜色;
  3. 可同时输出 DSM(同一网格 Z 极值);
  4. ConvertModel2TDOM:不跑 OSG,直接对已有 OSGB+metadata 做同样栅格化。

9. 点云分类、轻量化、裁剪

点云分类

  1. 读块 .vpc 点;
  2. 对每个点,用其 point_views 里权重最大的视,把 3D 点投到该影像坐标;
  3. 用 GDAL 读与原始影像同名 的分类矢量,空间查询 class_id
  4. 多点投票得类别,写回 LAS classification 字段。

模型轻量化

遍历 OSGB 的 PageLOD 树,用 OSG Simplifier 按档位减少三角形、压缩纹理分辨率,输出新目录树。

模型裁剪

读范围矢量,对 OSGB 节点做包围盒相交测试,保留/删除子树(bClipMode 控制保留内或外)。


10. 白膜(建筑物 + DSM)

独立于 MVS,输入只有矢量和栅格:

  1. 读建筑物 footprint 多边形;
  2. 在 XY 上对 footprint 做 约束 Delaunay 2D 三角剖分(CGAL CDT,孔洞用 nesting level 标记域内域外);
  3. 三角顶点 Z 从 DSM 采样(顶点在 DSM 上双线性/最近邻取高);
  4. 沿轮廓竖直拉伸到统一底高(或地形),形成封闭棱柱体;
  5. 可选 DOM 作为顶面纹理;
  6. tessellate 后写 OSG Geode → OSGB/OBJ。

得到的是无倾斜摄影纹理的简化建筑壳,用于 LOD1 城市场景。


11. 格式导出(实现层面)

LOD 阶段把同一套 mesh+atlas 栅格化为不同容器:

  • OSGB :OSG PagedLOD 树,子文件相对路径,根节点 Model.osgb
  • B3DM :三角网格 + glTF 二进制 + 3D Tiles tileset.json,;
  • OBJ/PLY:顶点、面、mtl/贴图文件;
  • S3MB/C3MX:调用第三方 SDK 写行业格式。

本质是:同拓扑 + 同纹理图集,换封装与坐标元数据


12. LOD:网格与点云

网格 LOD(LOD

  1. 对块 mesh 的 AABB 递归八叉分割(CutNode);
  2. 叶子节点:若三角面数仍多,用 QEM 再简化一档;
  3. 每层生成较低分辨率纹理(缩小 atlas 或重采样);
  4. 为每个 tile 计算 geometric_error(与节点尺寸相关),写入 OSGB PagedLOD 的切换距离;
  5. 远处加载粗 mesh+糊纹理,近处 REPLACE 为细 mesh。

点云 LOD(PLOD

  1. 对块内彩色点做 Poisson Disk 采样 降密;
  2. 空间均匀立方格(如 200 格/边)划分;
  3. 每格若点数 > 5000 则四分,子格递归;
  4. 叶节点打包为 OSGB Point Geode,父节点 PagedLOD 引用子文件;
  5. 形成与网格类似的多级点云树
相关推荐
小O的算法实验室2 小时前
2026年ASOC,基于多目标优化去噪双存档进化算法+路径规划,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
2601_954526753 小时前
逆向解析Temu底层动销算法:基于API高并发轮询与全域存量透视的自动化架构重构
算法·架构·自动化
Σίσυφος19003 小时前
数据标准化(拟合的时候使用非常重要)
人工智能·算法
knight_9___3 小时前
大模型project面试7
人工智能·python·算法·面试·大模型·agent
NashSKY4 小时前
EM 算法完整推导与本质剖析
算法·机器学习·概率论
foundbug9995 小时前
MATLAB实现:基于图像对比度和波段相关性的高光谱波段选择算法
开发语言·算法·matlab
嘿嘿嘿x35 小时前
Linux-实践
linux·运维·算法
Godspeed Zhao5 小时前
从零开始学AI14——最大似然估计与对数损失函数
算法·逻辑回归·最大似然