File size: 3,698 Bytes
23e73f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import os
import json
import numpy as np
from scipy.spatial import cKDTree
from plyfile import PlyData # 仅用于读取 COLMAP 的标准真实点云

def umeyama_transform(P, Q):
    """标准的 Umeyama 算法求解 7-DoF (s, R, t) 变换矩阵"""
    mu_P = P.mean(axis=0)
    mu_Q = Q.mean(axis=0)
    P_centered = P - mu_P
    Q_centered = Q - mu_Q
    
    C = P_centered.T @ Q_centered / P.shape[0]
    U, S, Vt = np.linalg.svd(C)
    
    R = Vt.T @ U.T
    if np.linalg.det(R) < 0:
        Vt[2, :] *= -1
        R = Vt.T @ U.T
        
    var_P = np.var(P, axis=0).sum()
    if var_P < 1e-8:
        scale = 1.0
    else:
        scale = (1.0 / var_P) * np.sum(S) 
        
    t = mu_Q - scale * (R @ mu_P)
    
    T = np.eye(4)
    T[:3, :3] = scale * R
    T[:3, 3] = t
    return T

def icp_umeyama_align(source_pts, target_pts, max_iters=20, tolerance=1e-5):
    """使用 ICP 匹配点对,使用 Umeyama 求解矩阵"""
    if len(source_pts) > 50000:
        idx = np.random.choice(len(source_pts), 50000, replace=False)
        src = source_pts[idx].copy()
    else:
        src = source_pts.copy()
        
    target_tree = cKDTree(target_pts)
    T_accum = np.eye(4)
    prev_dist = float('inf')
    
    for i in range(max_iters):
        dists, indices = target_tree.query(src)
        matched_target = target_pts[indices]
        
        T = umeyama_transform(src, matched_target)
        src_homo = np.hstack((src, np.ones((src.shape[0], 1))))
        src = (T @ src_homo.T).T[:, :3]
        T_accum = T @ T_accum
        
        if i > 0 and np.mean(dists) - prev_dist > -tolerance:
            break
        prev_dist = np.mean(dists)
        
    return T_accum

def assess_manifold_collapse(model, model_dir, colmap_ply_path=None):
    try:
        # 优雅降级:如果模型没有提供离散空间点(比如隐式表征),直接跳过此项检测
        if not hasattr(model, 'get_spatial_centers'):
            return {"manifold_collapse": False, "reason": "Model lacks explicit discrete centers"}
            
        try:
            xyz_pred = model.get_spatial_centers().cpu().numpy()
        except NotImplementedError:
            return {"manifold_collapse": False, "reason": "Method not implemented"}

        if len(xyz_pred) < 100:
            return {"manifold_collapse": True, "reason": "Too few points"}
            
        # --- 拓扑学流形坍塌检测 ---
        cov = np.cov(xyz_pred.T)
        eigenvalues, _ = np.linalg.eigh(cov)
        if eigenvalues[0] < 1e-6:
            return {"manifold_collapse": True, "reason": "Zero variance in one dimension (Flat collapse)"}
            
        # --- 锚点绝对坐标系对齐 (ICP-Umeyama) ---
        matrix = np.eye(4)
        if colmap_ply_path and os.path.exists(colmap_ply_path):
            try:
                colmap_data = PlyData.read(colmap_ply_path)
                cv = colmap_data.elements[0].data
                xyz_colmap = np.vstack([cv['x'], cv['y'], cv['z']]).T
                
                print(f"  [Alignment] 正在对齐到绝对宇宙 (ICP)... 锚点数量: {len(xyz_colmap)}")
                matrix = icp_umeyama_align(xyz_pred, xyz_colmap)
            except Exception as e:
                print(f"  [Alignment] 锚点对齐失败 ({e}),回退为单位矩阵。")
        
        matrix_path = os.path.join(model_dir, "alignment_matrix.json")
        with open(matrix_path, 'w') as f:
            json.dump(matrix.tolist(), f, indent=4)
            
        return {"manifold_collapse": False, "reason": "Normal"}
    except Exception as e:
        return {"manifold_collapse": False, "reason": f"Detection skipped due to error: {str(e)}"}