# 传统摄影测量管线部署指南（公开版）

> 面向读者：同《3DGS 部署与训练指南》。本文覆盖**经典摄影测量（photogrammetry）**路线：从同一批照片/视频帧出发，用 COLMAP 完成 SfM（运动恢复结构）之后，继续走 MVS（多视图立体）稠密重建 → 点云融合 → 泊松网格化，得到**带尺度的点云与网格**——可直接进 Blender/MeshLab/3D 打印流程。
> 与 3DGS 路线的关系：两条管线**共享同一个 SfM 前半段**，分歧从稠密重建开始。选型口诀：要测量/要改模型/要进工业流程 → 本文；要展示/要沉浸/要传播 → 3DGS 路线（见姊妹篇）。
> 所有命令与耗时来自 2026-10-14 在 RTX 5090 / 32GB 容器上的实测。
>
> 来自 https://www.paddysun.top/ 

---

## 0. 环境侦察

与 3DGS 指南完全相同，先跑同一份侦察脚本（GPU/torch/nvcc/conda/磁盘/网络六项+判读表），此处不重复。本文额外依赖只有一项：**COLMAP 的 CUDA 构建**（MVS 的立体匹配在 GPU 上跑；CPU 构建能跑但慢数倍）。

## 1. COLMAP 安装（CUDA 版）

```bash
# miniconda（用你容器可达的镜像源，示例为北外）
wget https://mirrors.bfsu.edu.cn/anaconda/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/mc.sh
bash /tmp/mc.sh -b -p /root/miniconda3
cat > /root/.condarc <<'EOF'
channels: [defaults]
custom_channels:
  conda-forge: https://mirrors.bfsu.edu.cn/anaconda/cloud
show_channel_urls: true
EOF
/root/miniconda3/bin/conda create -n colmap --override-channels -c conda-forge "colmap=4.2.1=cuda_129*" -y
```

三个实测坑：
1. **build 串匹配带通配符**：完整 build 形如 `cuda_129ha585b08_0`，必须写 `colmap=4.2.1=cuda_129*`；
2. **openimageio 可能没被自动装上**（conda 解析遗漏），报 `libOpenImageIO.so.3.1: cannot open shared object file` 时补装：
   ```bash
   conda install -n colmap --override-channels -c conda-forge openimageio -y
   ```
   若补装后 OIIO 小版本与 colmap 链接版本错位（colmap 要 3.1、装到 3.2），软链兜底（实测可用）：
   ```bash
   cd /root/miniconda3/envs/colmap/lib
   ln -sf libOpenImageIO.so.3.2.1 libOpenImageIO.so.3.1
   ln -sf libOpenImageIO_Util.so.3.2.1 libOpenImageIO_Util.so.3.1
   ```
3. **不要用 `apt install colmap`**：Ubuntu 仓库构建不带 CUDA，SIFT 与立体匹配全部回落 CPU，慢数倍。

验证：
```bash
export LD_LIBRARY_PATH=/root/miniconda3/envs/colmap/lib:$LD_LIBRARY_PATH
/root/miniconda3/envs/colmap/bin/colmap -h    # 出 Usage 即成功
```
（补装 OIIO 后 RPATH 通常自足，LD 变量作为保险。）
⚠️ COLMAP 4.x：`--SiftExtraction.use_gpu` 等选项已移除（GPU 默认开）；`mapper`/`poisson_mesher` 的输出目录要求**预先存在**。

## 2. SfM 前半段（与 3DGS 管线共享）

```bash
D=/root/data/datasets/<场景>;  mkdir -p $D/images $D/sparse
cp frames/*.jpg $D/images/                                   # 抽帧见姊妹指南 §3
C=/root/miniconda3/envs/colmap/bin/colmap
$C feature_extractor  --database_path $D/database.db --image_path $D/images --ImageReader.camera_model SIMPLE_RADIAL
$C sequential_matcher --database_path $D/database.db --SequentialMatching.overlap 10 --SequentialMatching.quadratic_overlap 1
$C mapper             --database_path $D/database.db --image_path $D/images --output_path $D/sparse
```
成功判据与验收指标同姊妹指南：`sparse/0/` 三件套齐全；**配准率**（可用 pycolmap 统计"有 3D 观测的图像数/总图像数"）<80% 时先解决采集问题。
实测耗时（5090，72–197 帧）：40 秒～10 分钟。

## 3. MVS 稠密重建（本文主线）

```bash
W=/root/data/classic/<场景>;  mkdir -p $W
$C image_undistorter  --image_path $D/images --input_path $D/sparse/0 --output_path $W/undistorted
$C patch_match_stereo --workspace_path $W/undistorted --PatchMatchStereo.geom_consistency true
$C stereo_fusion      --workspace_path $W/undistorted --output_path $W/fused.ply
$C poisson_mesher     --input_path $W/fused.ply --output_path $W/meshed-poisson.ply --PoissonMeshing.depth 10
```

各环节说明与实测（72 帧 1080p，5090）：
| 环节 | 作用 | 实测耗时 |
|---|---|---|
| image_undistorter | 去畸变+整理 MVS 工作区 | 秒级 |
| patch_match_stereo | 逐图像对计算深度图（几何一致性过滤开启） | **28.1 分钟** |
| stereo_fusion | 多深度图融合成稠密点云 | 4.9 分钟 |
| poisson_mesher | 点云→三角网格 | **⚠️ 见下** |

实测注意：
- patch_match 耗时随帧数线性增长（197 帧预估 75+ 分钟）；1080p 源可用 `--PatchMatchStereo.max_image_size 1024` 降采样换速度；
- **poisson_mesher 在 COLMAP 4.2.1 上实测两次静默卡死**（默认深度 52 分钟无输出、CPU 归零；depth 10 重试同样未出片）——相同输入在 MeshLab/CloudCompare 里跑泊松可绕开。工程建议：**稠密点云（fused.ply）即可作为经典管线的代表性产物**，网格化在桌面软件里按需做；
- fused.ply 为二进制 PLY，含 x/y/z + 法线 + RGB（实测 239 万点 / 64.5MB，对应 72 帧）。

## 4. 产物能力边界（与 3DGS 对照）

| 维度 | 经典 MVS 产物（点云/网格） | 3DGS 产物 |
|---|---|---|
| 几何语义 | 有尺度、真实表面采样，**可测量** | 外观拟合，不可测量 |
| 可编辑性 | 网格可改、可进 CAD/3D 打印 | 仅删点/调色/裁剪 |
| 光照 | 烘焙在顶点色/贴图里（固定） | 视角相关（球谐，随镜头变化） |
| 透明/反光材质 | **立体匹配原理性失效**（折射使视差不成立）→ 出洞 | 半透明材质有表现 |
| 天空/无纹理区 | 无数据 → 空洞 | 有表现（代价是可能出浮点伪影） |
| 质感 | 点云颗粒化 | 照片级连续 |
| 单图输入 | 原理性不可能（需要多视基线） | 生成式前馈模型可跨过 |

## 5. 点云的浏览器展示（可选）

COLMAP 点云 PLY 不是高斯格式，通用 splat 工具不收。用下面的脚本转成 .splat（32 字节/点的简易格式），即可在 [antimatter15/splat](https://github.com/antimatter15/splat) 查看器里与 3DGS 成果并排对比：

```python
# ply2splat.py — 把 COLMAP fused.ply（x/y/z/nx/ny/nz/red/green/blue[/alpha]）转 .splat
# 用法: python ply2splat.py fused.ply output.splat [scale]
import sys
import numpy as np

def read_ply(path):
    with open(path, "rb") as f:
        assert f.readline().strip() == b"ply"
        assert b"binary_little_endian" in f.readline()
        props, n = [], 0
        while True:
            line = f.readline().strip()
            if line.startswith(b"element vertex"):
                n = int(line.split()[-1])
            elif line.startswith(b"property"):
                t, name = line.split()[1], line.split()[2]
                props.append((name.decode(), {"float": "<f4", "uchar": "u1"}[t.decode()]))
            elif line == b"end_header":
                break
        dt = np.dtype(props)
        return np.frombuffer(f.read(n * dt.itemsize), dtype=dt, count=n)

pts = read_ply(sys.argv[1])
scale = float(sys.argv[3]) if len(sys.argv) > 3 else 0.004   # 按场景尺度调
n = len(pts)
out = np.zeros(n, dtype=[("p", "<f4", 3), ("s", "<f4", 3), ("rgba", "u1", 4), ("rot", "u1", 4)])
out["p"] = np.stack([pts["x"], pts["y"], pts["z"]], 1)
out["s"] = scale
out["rgba"] = np.concatenate([np.stack([pts["red"], pts["green"], pts["blue"]], 1).astype(np.uint8),
                              (pts["alpha"].astype(np.uint8) if "alpha" in pts.dtype.names
                               else np.full(n, 255, np.uint8))[:, None]], 1)
out["rot"] = np.array([255, 0, 0, 0], np.uint8)   # 恒等四元数
with open(sys.argv[2], "wb") as f:
    out.tofile(f)
print(f"OK {n} points")
```
查看器部署与坐标系修正（COLMAP Y 向下，需 `-r 90,0,0`）同姊妹指南 §5/§4.4。

## 6. 实测成绩单与选型结论

| 环节 | scene1（72帧）实测 |
|---|---|
| SfM（与 3DGS 共享） | 40 秒 |
| undistort | 秒级 |
| patch_match_stereo | 28.1 分钟 |
| stereo_fusion | 4.9 分钟 |
| poisson 网格 | 两次卡死未出片（桌面软件绕行） |
| **合计** | **33 分钟起，产物=稠密点云** |

同一素材 3DGS 路线：10.3 分钟，产物可直接网页分享。
**结论**：经典管线换来"可测量、可编辑、进工业流程"的几何资产，代价是数倍的算力时间、更多的工序、以及对透明/无纹理区域的原理性无解。两者不是替代关系——按交付物形态选路线。
