# Changelog: chenjianguo/3dgs_dev vs yuanweizhong/code（基准）

比较基准：
- **BASE**（基准/旧）：`/mnt/iag/yuanweizhong/code/street-gaussians-ns/`
- **NEW**（新版）：`/mnt/iag/chenjianguo/3dgs_dev/street_gaussians_ns/`

符号约定：`-` 表示基准中存在、新版中移除或回退；`+` 表示新版中新增或修改。

---

## 一、训练配置（`sgn_config.py`）

### [REGRESSION] 重新开启相机位姿优化器
```
- camera_optimizer=CameraOptimizerConfig(mode="off")    # 基准：关闭
+ camera_optimizer=CameraOptimizerConfig(mode="SO3xR3") # 新版：开启 6DoF 优化
```
**影响**：基准版关闭了相机外参优化以避免与 LiDAR 标定先验耦合。新版重新开启，在标定精度不足的数据上可能提升对齐效果，但会引入相机参数与高斯参数之间的梯度干扰，对 LiDAR 标定质量高的数据集可能降低几何稳定性。

### [REGRESSION] 默认训练轮数从 70000 降回 30000
```
- STREET_GAUSSIANS_MAX_ITERATIONS = 70000   # 基准
+ STREET_GAUSSIANS_MAX_ITERATIONS = 30000   # 新版
```
**影响**：高斯密化、SH 度逐步提升均依赖足够多的迭代步数，30000 步在复杂场景下可能训练不充分。

### [REGRESSION] 移除 LiDAR 深度监督损失
基准版新增的以下配置在新版中均缺失：
```
- depth_loss_mult: float = 0.05
- depth_loss_start_step: int = 500
- output_depth_during_training: bool = True
- depths_path: Path("depths")
```
**影响**：新版缺乏 LiDAR 深度约束，高斯几何完全依赖光度损失，浮空伪影和几何漂移会更明显。

### [REGRESSION] 使用 VanillaPipelineConfig 替换自定义 SgnPipelineConfig
```
- pipeline=SgnPipelineConfig(...)       # 基准：自定义 pipeline，兼容 PyTorch 2.6+
+ pipeline=VanillaPipelineConfig(...)   # 新版：nerfstudio 原生 pipeline
```
**影响**：PyTorch 2.6+ 环境下 checkpoint 恢复可能报错（`weights_only` 默认值变更）。

---

## 二、模型核心（`sgn_splatfacto.py`）

### [REGRESSION] 回退到旧版 gsplat cuda_legacy API
```
- from gsplat import spherical_harmonics                          # 基准
- from street_gaussians_ns.gsplat_compat import (                # 基准
-     project_gaussians, rasterize_gaussians, rasterize_gaussians_3dgut)
+ from gsplat.cuda_legacy._torch_impl import quat_to_rotmat      # 新版
+ from gsplat import project_gaussians, rasterize_gaussians, spherical_harmonics
```
**影响**：新版依赖 `gsplat.cuda_legacy` 私有内部 API，在 gsplat 1.5.x 下无法运行；基准版通过兼容层适配新 API。

### [REGRESSION] 移除密化统计量尺寸校验
基准版对密化相关张量做了尺寸一致性检查：
```
- stats_stale = (
-     self.xys_grad_norm is None
-     or self.xys_grad_norm.shape[0] != num_gaussians
-     or self.vis_counts.shape[0] != num_gaussians
-     or radii_scalar.shape[0] != num_gaussians
- )
```
新版仅检查 `is None`，高斯数量在密化/剪枝后变化时可能触发维度广播错误，导致密化统计静默失效。

### [REGRESSION] radii 处理不兼容 gsplat 新版返回格式
```
- radii_scalar = self.radii.squeeze()           # 基准：兼容 (N,) 和 (N,1)
- if radii_scalar.dim() > 1:
-     radii_scalar = radii_scalar.max(dim=-1).values
+ visible_mask = (self.radii > 0).flatten()     # 新版：直接 flatten，可能产生错误 mask
```

### [REGRESSION] `to_opengl` 硬编码 `device="cuda"`
```
- self.register_buffer("to_opengl", torch.tensor(...), persistent=False)  # 基准
+ self.to_opengl = torch.tensor(..., device="cuda")                        # 新版
```
多 GPU 或 CPU 推理时会报 device mismatch。

### [REGRESSION] meshgrid 硬编码 `device='cuda'`
```
- grid = kornia.utils.create_meshgrid(..., device=R.device)  # 基准
+ grid = kornia.utils.create_meshgrid(..., device='cuda')    # 新版
```

### [REGRESSION] 空视野断言替代保护性返回
```
- if not (num_tiles_hit > 0).any():           # 基准：安全返回背景
-     outputs['rgb'] = background.repeat(...)
+ assert (self.num_tiles_hit > 0).any()       # 新版：直接 assert，会崩溃
```

### [REGRESSION] `load_state_dict` 缺少 strict=False 保护
```
- load_kwargs = {**kwargs, "strict": False}   # 基准
- super().load_state_dict(dict, **load_kwargs)
+ super().load_state_dict(dict, **kwargs)     # 新版：strict=True，key 不匹配时崩溃
```

### [REGRESSION] 缺少深度评估指标
基准版在训练和 eval 阶段输出 `depth_l1`、`depth_rmse`、`depth_valid_ratio`，新版无对应指标，无法监控几何重建质量。

### [REGRESSION] alpha 维度处理
```
- if alpha.ndim == 2:              # 基准：兼容 2D alpha
-     alpha = alpha.unsqueeze(-1)
+ alpha = alpha[..., None]         # 新版：假设 alpha 始终有 batch 维度
```

### [FEATURE_EXPERIMENT] 移除鱼眼/FTheta 渲染支持（3DGUT）
基准版新增的 `_uses_3dgut_render()`、`rasterize_gaussians_3dgut()` 分支在新版中均不存在，无法直接支持鱼眼相机光栅化。

---

## 三、场景图模型（`sgn_splatfacto_scene_graph.py`）

### [FEATURE_EXPERIMENT] 新增 depth_to_normal 实验代码（注释状态）
新版增加了约 85 行注释掉的深度/法线损失实验：
```python
+ def depth_to_normal(depth, fx, fy, cx, cy): ...  # scale-invariant depth loss + normal constraint
```
这些代码未被激活，对训练无实际影响，属于探索性实验。

### [REGRESSION] 移除 `_filter_state_dict_by_shape`
基准版在 `load_state_dict` 时过滤 shape 不匹配的 key（如 bbox_optimizer 变更时），新版无此保护，跨配置恢复 checkpoint 容易失败。

### [REGRESSION] bilateral grid 不传播到 background 子模型
```
- self.config.background_model.use_bilateral_grid = self.config.use_bilateral_grid  # 基准
# 新版无此行，background 模型 bilateral grid 状态与场景图不同步
```

### [REGRESSION] 移除 SAVE_EVAL_IMAGES 回调
基准版支持 `SAVE_EVAL_IMAGES=1` 环境变量在训练开始时自动保存 eval 图片，新版无此功能。

---

## 四、数据加载（`sgn_datamanager.py`）

### [FEATURE_EXPERIMENT] 改用后台线程队列预取数据
新版用两个后台 `Thread` + `Queue(maxsize=100)` 替换了基准版的主线程按需采样：
```python
+ self.train_queue = Queue(maxsize=100)
+ for _ in range(2):
+     Thread(target=self.th_queue_loader, ...).start()
```
**隐患**：`th_queue_loader` 在 undistort 时会直接修改 `dataset.cameras`（写入 fx/fy/cx/cy/width/height），多线程并发写共享状态，存在竞态条件。

### [REGRESSION] 深度图不再 lazy load
基准版的深度图 lazy loading 架构（`_lazy_load_depth`、`_materialize_batch`）在新版中完全移除，深度不通过 batch 传递，配合深度监督损失使用时无法工作。

---

## 五、数据解析（`sgn_dataparser.py`）

### [REGRESSION] 相机类型按序列赋值，而非按帧赋值
```
- camera_type = torch.tensor(                                         # 基准：per-frame
-     [CAMERA_MODEL_TO_TYPE[frame["camera_model"]].value for frame in meta["frames"]]
- )
+ camera_type = torch.tensor(                                         # 新版：per-sequence
+     [CAMERA_MODEL_TO_TYPE[model].value for model in meta["camera_model"]]
+ )
```
**影响**：混合相机序列（pinhole + fisheye）中，新版对所有帧使用单一相机类型，鱼眼帧会被当作 pinhole 处理，严重影响畸变校正和投影精度。

### [REGRESSION] frame 排序时不同步 camera_model 列表
```
- sorted_pairs = sorted(zip(frames, camera_model), key=...)   # 基准：同步排序
- frames, camera_model = (list(t) for t in zip(*sorted_pairs))
+ frames.sort(key=...)                                         # 新版：仅排序 frames
```
排序后 frames 与 camera_model 列表错位，相机类型与帧对应关系混乱。

### [REGRESSION] 时间戳未除以 1e9
```
- frame["time"] = float(im_data.name.split("/")[-1][:-4]) / 1e9  # 基准：秒
+ frame["time"] = float(im_data.name.split("/")[-1][:-4])        # 新版：纳秒，未归一化
```
**影响**：动态物体时序插值依赖 `frame["time"]`，纳秒级数值会导致插值完全错误，动态高斯位置与实际物体位置严重偏离。

### [REGRESSION] annotation 路径硬编码
```
- anno_json_path = _resolve_annotation_json(self.config.data)  # 基准：自动探测
- lidar_path = _resolve_dynamic_lidar_dir(self.config.data)
+ anno_json_path = self.config.data / 'annotation.json'        # 新版：硬编码
+ lidar_path = self.config.data / 'aggregate_lidar' / 'dynamic_objects'
```
新数据集若使用 `annotation_v1.json` 或 `aggregate_lidar_v1/` 目录则直接报错。

### [REGRESSION] 缺少相机分辨率自动对齐
基准版的 `_sync_camera_resolution_to_image()` 在磁盘图像与 COLMAP 元数据分辨率不一致时自动缩放内参，新版缺失此逻辑，下采样数据集上会产生投影偏移。

### [REGRESSION] 缺少 `undistort_fisheye` 开关
基准版可通过 `undistort_fisheye=False` 保留鱼眼畸变供 3DGUT 直接使用，新版无此选项。

---

## 六、数据工具（`data_utils.py`）

### [REGRESSION] 深度加载函数接口不统一，float64 精度问题
新版中 `get_depth_from_path` / `get_depth_image_from_path` 两个函数读取 .npz 时 key 不一致（`depth` vs `arr_0`），且使用 `float64`：
```
+ image = image.astype(np.float64) * scale_factor   # 新版：float64，PyTorch 训练中类型不匹配
- depth = depth.astype(np.float32) * depth_scale_factor  # 基准：float32
```
基准版统一为 `get_depth_and_valid_from_path()`，同时返回深度和有效 mask，优先读取 npz 中的 `valid` 字段。

---

## 七、数据集（`sgn_dataset.py`）

### [REGRESSION] 深度图加载无尺寸对齐
```
+ data["depth"] = get_depth_from_path(filepath=depth_filepath)  # 新版：返回原始尺寸
- depth, valid = self.get_depth_tensors(image_idx, h, w)        # 基准：resize 到 RGB 尺寸
```
新版深度图尺寸与 RGB 不一致时会在后续操作中触发形状错误。

### [FEATURE_EXPERIMENT] 保留 dynamic_mask 独立加载分支
新版在 `get_data()` 中有单独的 `dynamic_mask_filenames` 加载逻辑，基准版已将其合并到主 mask 流程中。

---

## 八、动态标注（`dynamic_annotation.py`）

### [FEATURE_EXPERIMENT] 新增 Cyclist 动态建模
```
- if obj['type'] not in ['Car', 'Truck']:          # 基准
+ if obj['type'] not in ['Car', 'Cyclist', 'Truck']: # 新版：额外对骑行者建动态高斯
```
**影响**：骑行者数量少、形状细小，标注噪声较大，额外建模可能引入噪声高斯。效果取决于具体场景中骑行者的标注质量。

### [REGRESSION] 动态标注时间戳未除以 1e9
```
- item['timestamp'] = parse_timestamp(item['timestamp'] / 1e9)  # 基准
+ item['timestamp'] = parse_timestamp(item['timestamp'])         # 新版：纳秒，与 dataparser 不一致
```
与 dataparser 时间戳单位不统一，动态物体时序匹配错误。

---

## 九、新版中缺失的基准功能

以下文件/功能在基准（yuanweizhong）中存在，新版（chenjianguo）中不存在：

| 基准文件 | 功能 |
|---------|------|
| `sgn_pipeline.py` | 自定义 Pipeline，修复 PyTorch 2.6 checkpoint 兼容性 |
| `gsplat_compat.py` | gsplat 1.5.x API 兼容层（project/rasterize/3dgut） |
| `utils/eval_utils.py` | 覆盖 nerfstudio eval_setup，正确处理 ckpt 恢复 |
| `scripts/generate_lidar_depth.py` | LiDAR 投影生成深度图，配合深度监督使用 |

---

## 总结：影响重建效果的核心差异

以下差异解释了为何新版（chenjianguo）相比基准（yuanweizhong）重建效果存在差距：

| 优先级 | 差异点 | 对新版的影响 |
|--------|--------|------------|
| ★★★ | 时间戳未除以 1e9 | 动态高斯轨迹插值完全错误，动态物体严重错位 |
| ★★★ | 相机类型 per-sequence 而非 per-frame | 混合相机序列中鱼眼帧投影计算错误 |
| ★★★ | frame 排序未同步 camera_model | 帧与相机参数错位，每帧用了错误相机类型 |
| ★★★ | 缺少深度监督损失 | 几何完全靠光度，浮空伪影多，几何漂移明显 |
| ★★ | 重新开启相机位姿优化器 | 对高质量 LiDAR 标定场景引入梯度干扰 |
| ★★ | 默认迭代次数降为 30000 | 复杂场景训练不充分 |
| ★★ | 密化统计量无尺寸校验 | 密化策略静默失效，高斯分布不均匀 |
| ★★ | 深度图 float64 + 无尺寸对齐 | 深度加载出错或形状不匹配 |
| ★ | annotation 路径硬编码 | 新格式数据集直接报错 |
| ★ | 缺少相机分辨率自动对齐 | 下采样数据集投影偏移 |

---

## 十、实际产物对比（本场景）

对比路径：
- **chenjianguo（新版）**：`/mnt/iag/chenjianguo/l29_data/3dgs_format_v1/`，最具代表性训练运行：
  - `v_dynamic/street-gaussians-ns/2026-07-02_122909`（主要对比，7路针孔，无深度监督）
  - `v_lidar/street-gaussians-ns/2026-07-06_070606`（深度监督实验，但 `depths_path=null` 实际未生效）
- **yuanweizhong（基准）**：`v1/street-gaussians-ns/2026-07-07_010329`（11路相机，含4路fov195鱼眼，深度监督开启）

### 产物总览

| 项目 | chenjianguo（新版） | yuanweizhong（基准） |
|------|---------------------|---------------------|
| 训练相机数 | 7路（纯针孔） | 11路（7针孔 + 4路 fov195 鱼眼） |
| 渲染产物格式 | PNG序列（v_dynamic）+ render.mp4（v_lidar） | 每路独立 MP4 |
| 渲染通道 | rgb, gt-rgb, depth, accumulation, background_rgb/acc, object_rgb/acc, sky, postprocess-rgb | rgb, gt-rgb（仅2通道） |
| checkpoint 最大步数 | step-000069999（70k跑满） | step-000069999（70k跑满） |
| 动态物体（ply） | 19个（Car×12, Cyclist×4, Truck×3） | 35个（Car×26, Cyclist×7, Truck×2） |
| 深度监督实际启用 | **否**（depths_path=null，无 depths/ 目录） | **是**（depth_loss_mult=0.05，depths_path=depths） |
| camera_optimizer | **SO3xR3**（v_dynamic） | off |
| 鱼眼相机建模 | 无（本数据集无 fov195） | 有（4路，undistort_fisheye=false） |

### 排查清单逐条比对

#### ★★★ 1. 时间戳 ÷1e9（动态轨迹插值）
**本场景：两版均内部一致，不触发此 bug。**

annotation.json 的 `timestamp` 为 13位毫秒（如 `1776399157600`），图像文件名也是 13位毫秒。
- chenjianguo：dataparser 和 dynamic_annotation.py 均**未除以 1e9**，两侧单位一致。
- yuanweizhong：dataparser 和 dynamic_annotation.py 均**除以 1e9**，两侧一致。

`parse_timestamp()` 会将数值标准化到16位字符串，只要两侧比较时单位相同匹配就正确。**此 bug 在本场景不触发。**

#### ★★★ 2 & 3. 相机类型 per-frame / frame 排序同步
**本场景：chenjianguo 不受影响（纯针孔），但跨场景迁移风险高。**

- chenjianguo 数据集：纯针孔7路，per-sequence 赋值与 per-frame 等价，排序不同步无影响。
- yuanweizhong 数据集：7针孔 + 4鱼眼 fov195（混合）。若用 chenjianguo 代码训练此数据集，4路鱼眼帧会被赋 PINHOLE 类型，整体投影变形，且因排序未同步会随帧跳变。

**预期症状（迁移到鱼眼场景后）**：4路 fov195 mp4 整体弧形变形、边缘拉伸，相邻帧闪烁质量跳变；针孔7路相对正常。

#### ★★★ 4. 深度监督缺失
**本场景：chenjianguo 所有 run 均未实际启用深度监督。**

- `v_dynamic/2026-07-02_122909`：`depths_path: null`，`output_depth_during_training: false`，无 depth_loss_mult。
- `v_lidar/2026-07-06_070606`：名称暗示有深度，但 `depths_path: null`，深度路径未挂载，深度监督**未生效**。
- 数据集根目录无 `depths/` 子目录（LiDAR 深度图未预生成）。

yuanweizhong 基准：`depths_path: depths`，`depth_loss_mult: 0.05`，`depth_loss_start_step: 500`，训练时从 step 500 起有深度 L1 约束。

**可见产物差异**：
- chenjianguo `renders/all/depth/` 通道：远景深度层次混乱，无LiDAR锚定的平滑过渡。
- chenjianguo `renders/all/rgb/`：远景路牌/树木/地面边界出现浮空伪影或半透明层。

#### ★★ 5. 相机位姿优化器开启（camera_opt = SO3xR3）
**本场景：chenjianguo v_dynamic 开启，基准关闭，是几何偏差的直接原因之一。**

- chenjianguo `v_dynamic`：`camera_optimizer: mode: SO3xR3`（场景图层级）。
- yuanweizhong 基准：`mode: off`。

**可见产物差异**：
- chenjianguo renders 中，静态背景在不同视角下出现细微但系统性的位移（多视角重影）。
- fov120/fov30 等宽角针孔相机边缘处与 GT 有整幅平移偏差，对比 gt-rgb 可见。
- 对 L29 这类 LiDAR 标定精度高的数据，camera_opt 的修正反而把高斯位置拉偏。

#### ★★ 6. 迭代次数（30k vs 70k 默认）
**本场景：两版均跑满 70000 步，迭代差异未体现。**

chenjianguo 实际 config `max_num_iterations: 70000`（运行时通过环境变量覆盖了代码层级的 30000 默认值）。checkpoint 均为 step-000069999，迭代次数一致。

**30000 默认值是跨场景风险**：若不设置 `STREET_GAUSSIANS_MAX_ITERATIONS` 环境变量，chenjianguo 代码只跑 30000 步即停，yuanweizhong 基准跑 70000 步，差距明显。

#### ★ 9. annotation 路径硬编码
**本场景：路径匹配，未触发报错。**

chenjianguo 数据集含 `annotation.json`（非 `annotation_v1.json`）和 `aggregate_lidar/dynamic_objects/`（非 `aggregate_lidar_v1/`），硬编码路径完全匹配。

#### ★ 11. Cyclist 建模差异
**本场景：chenjianguo 数据集有 Cyclist ply 且代码对其建模，基准无 Cyclist 高斯。**

- chenjianguo 动态过滤：`['Car', 'Cyclist', 'Truck']` → Cyclist 有独立动态高斯（共 4 个 track）。
- yuanweizhong 基准：`['Car', 'Truck']` → Cyclist 归入背景。

chenjianguo 动态对象数量（19）远少于基准（35），部分源于预处理流程差异（yuanweizhong 有更多 Car 轨迹），与代码无关。

**可见产物差异**：chenjianguo `renders/all/object_rgb/` 中可见骑行者独立渲染层，基准无此通道；基准背景帧中骑行者区域可能有残影（高斯被背景吸收）。

### 关键可视化验证指引

| 排查点 | 文件路径 | 预期表现 |
|--------|----------|---------|
| 深度监督缺失 | `v_dynamic/renders/all/depth/{cam}/` 任一帧 vs 基准 depth | 新版噪声大、层次混乱、无 LiDAR 锚点 |
| camera_opt 拉偏 | `v_dynamic/renders/all/rgb/` vs `gt-rgb/` 同帧对比 | 整幅静态区域有系统性轻微偏移 |
| 鱼眼 bug（迁移） | 跑 chenjianguo 代码于 yuanweizhong 数据集时 | fov195 路变形，针孔路正常 |
| 深度 loss 未生效（v_lidar） | `v_lidar` config 里 `depths_path: null` | 虽有 depth 渲染通道，但训练期无约束 |
| Cyclist 高斯 | `v_dynamic/renders/all/object_rgb/*/` 骑行者帧 | 新版有独立骑行者渲染，基准无 |
