File size: 8,079 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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#!/usr/bin/env python
"""T&T round-2 probe — pinpoint remaining suspects at pixel level."""
import os, sys, inspect, struct
import numpy as np
from PIL import Image

sys.path.insert(0, '/root/autodl-tmp/3dgsAtlas_official')

DATASET_ROOT = "/root/autodl-tmp/dataset/tnt"
OUTPUT_ROOT  = "/root/autodl-tmp/SplatAtlas/outputs"
SCENES = [("truck", "PASS"), ("lighthouse", "FAIL")]


def sec(title):
    print("\n" + "=" * 70)
    print(f"  {title}")
    print("=" * 70)


def build_args(source_path, img_dir):
    from scene.dataset_readers import readColmapSceneInfo
    sig = inspect.signature(readColmapSceneInfo)
    args = []
    for i, (k, p) in enumerate(sig.parameters.items()):
        if i == 0:                    args.append(source_path)
        elif i == 1:                  args.append(img_dir)
        elif k == "eval":             args.append(True)
        elif k == "train_test_exp":   args.append(False)
        else:                         args.append(p.default if p.default != inspect.Parameter.empty else "")
    return args


def psnr_pil(a_pil, b_pil):
    """统一 resize 到较小者,RGB 对齐 PSNR。"""
    if a_pil.size != b_pil.size:
        tgt = (min(a_pil.size[0], b_pil.size[0]),
               min(a_pil.size[1], b_pil.size[1]))
        a_pil = a_pil.resize(tgt, Image.LANCZOS)
        b_pil = b_pil.resize(tgt, Image.LANCZOS)
    a = np.asarray(a_pil.convert("RGB"), dtype=np.float32) / 255.0
    b = np.asarray(b_pil.convert("RGB"), dtype=np.float32) / 255.0
    mse = float(((a - b) ** 2).mean())
    return (100.0 if mse == 0 else 10 * np.log10(1.0 / mse)), mse


# ---------- Probe 8: PINHOLE full params (cx, cy) ----------
def probe_camera_params(scene):
    sec(f"PROBE 8 — COLMAP PINHOLE full params [{scene}]")
    binf = os.path.join(DATASET_ROOT, scene, "sparse", "0", "cameras.bin")
    if not os.path.exists(binf):
        print(f"[!] no {binf}")
        return
    MODELS = {0:("SIMPLE_PINHOLE",3), 1:("PINHOLE",4), 2:("SIMPLE_RADIAL",4),
              3:("RADIAL",5), 4:("OPENCV",8), 5:("OPENCV_FISHEYE",8),
              6:("FULL_OPENCV",12), 7:("FOV",5),
              8:("SIMPLE_RADIAL_FISHEYE",4), 9:("RADIAL_FISHEYE",5),
              10:("THIN_PRISM_FISHEYE",12)}
    with open(binf, "rb") as f:
        num = struct.unpack("<Q", f.read(8))[0]
        cam_id   = struct.unpack("<I", f.read(4))[0]
        model_id = struct.unpack("<i", f.read(4))[0]
        width    = struct.unpack("<Q", f.read(8))[0]
        height   = struct.unpack("<Q", f.read(8))[0]
        model_name, n_params = MODELS.get(model_id, (f"UNK({model_id})", 0))
        params = struct.unpack(f"<{n_params}d", f.read(8 * n_params))

    print(f"  model={model_name} size={width}x{height} num_cameras={num}")
    print(f"  raw params: {[f'{p:.3f}' for p in params]}")
    if model_name == "PINHOLE":
        fx, fy, cx, cy = params
        dcx, dcy = cx - width/2, cy - height/2
        off_x, off_y = abs(dcx)/width*100, abs(dcy)/height*100
        print(f"  fx={fx:.2f} fy={fy:.2f}")
        print(f"  cx={cx:.2f} (expected {width/2:.2f}, offset {dcx:+.2f}px = {off_x:.2f}%)")
        print(f"  cy={cy:.2f} (expected {height/2:.2f}, offset {dcy:+.2f}px = {off_y:.2f}%)")
        if off_x > 0.5 or off_y > 0.5:
            print(f"  [!!] PRINCIPAL POINT OFFSET >0.5% — our K矩阵用 w/2,h/2 忽略了这个偏移!")
        else:
            print(f"  [OK] 主点在中心附近 (<0.5%偏移)")


# ---------- Probe 9: Native GT vs Ours GT pixel match ----------
def probe_gt_match(scene):
    sec(f"PROBE 9 — GT content alignment (Native-saved vs images_2/<orig_name>) [{scene}]")
    from scene.dataset_readers import readColmapSceneInfo
    cell = os.path.join(OUTPUT_ROOT, f"vanilla_3dgs_{scene}")
    gt_dir = os.path.join(cell, "gt_test_30000")
    if not os.path.isdir(gt_dir):
        gt_dir = os.path.join(cell, "renders_test_30000", "gt")
    if not os.path.isdir(gt_dir):
        print(f"[!] 没找到 Native GT 目录")
        return None
    native_gts = sorted([f for f in os.listdir(gt_dir) if f.lower().endswith(('.png','.jpg','.jpeg'))])

    source_path = os.path.join(DATASET_ROOT, scene)
    img_dir = "images_2" if os.path.exists(os.path.join(source_path, "images_2")) else "images"
    scene_info = readColmapSceneInfo(*build_args(source_path, img_dir))
    our_test = scene_info.test_cameras

    print(f"Native GT dir : {gt_dir}  count={len(native_gts)}")
    print(f"Our test cams : count={len(our_test)}  (first={our_test[0].image_name})")
    if len(native_gts) != len(our_test):
        print(f"[!!] COUNT MISMATCH — 直接说明两组 test 根本不同")
        return our_test, native_gts, gt_dir

    print(f"\n假说 A: Native 按 idx rename GT,内容 == Ours。验证方式:对齐 idx 比较像素。")
    print(f"{'idx':>3} {'native_file':>15} {'ours_name':>15} {'nat_size':>11} {'our_size':>11} {'PSNR':>8} {'MSE':>10}")
    print("-" * 78)
    psnrs = []
    for i in range(len(native_gts)):
        nat_pil = Image.open(os.path.join(gt_dir, native_gts[i]))
        our_path = our_test[i].image_path
        if not os.path.exists(our_path):
            our_path = os.path.join(source_path, img_dir, our_test[i].image_name)
        our_pil = Image.open(our_path)
        p, m = psnr_pil(nat_pil, our_pil)
        psnrs.append(p)
        if i < 5:
            print(f"{i:>3} {native_gts[i]:>15} {our_test[i].image_name:>15} "
                  f"{str(nat_pil.size):>11} {str(our_pil.size):>11} {p:>8.2f} {m:>10.6f}")

    arr = np.array(psnrs)
    print(f"\nAll {len(psnrs)} pairs: mean={arr.mean():.2f}  min={arr.min():.2f}  max={arr.max():.2f}")
    print(f"  >40 dB: {(arr>40).sum()}/{len(arr)}   20-40 dB: {((arr>=20)&(arr<=40)).sum()}/{len(arr)}   <20 dB: {(arr<20).sum()}/{len(arr)}")

    if arr.mean() > 50:
        print("[A 验证通过] GT 内容完全对齐。FAIL 原因在 renderer/pose/intrinsics 层面。")
    elif arr.mean() > 30:
        print("[A 弱成立] GT 内容近似一致 (30-50 dB) — 可能有 downsample 滤波器差异,但不足以解释 -1.24 dB")
    else:
        print("[A 被推翻] Native 和 Ours 拿到的是不同的图 —— 就是 test split 真的不一样。")
    return our_test, native_gts, gt_dir


# ---------- Probe 10: Native baseline 自洽性 sanity ----------
def probe_native_sanity(scene, our_test, native_gts, gt_dir):
    sec(f"PROBE 10 — Native render vs Native GT (reproduces baseline) [{scene}]")
    cell = os.path.join(OUTPUT_ROOT, f"vanilla_3dgs_{scene}")
    candidates = [
        os.path.join(cell, "renders_test_30000", "renders"),
        os.path.join(cell, "renders_test_30000"),
        os.path.join(cell, "test", "ours_30000", "renders"),
    ]
    renders_dir = next((c for c in candidates if os.path.isdir(c)
                        and any(f.lower().endswith(('.png','.jpg')) for f in os.listdir(c))), None)
    if renders_dir is None:
        print("[!] 没找到 Native renders 目录,跳过")
        return
    native_renders = sorted([f for f in os.listdir(renders_dir) if f.lower().endswith(('.png','.jpg'))])
    print(f"Native renders: {renders_dir}  count={len(native_renders)}")
    if not native_renders:
        return

    psnrs = []
    for i in range(min(len(native_renders), len(native_gts))):
        r = Image.open(os.path.join(renders_dir, native_renders[i]))
        g = Image.open(os.path.join(gt_dir, native_gts[i]))
        p, _ = psnr_pil(r, g)
        psnrs.append(p)
    arr = np.array(psnrs)
    print(f"Native_render vs Native_GT  mean PSNR = {arr.mean():.4f} dB  "
          f"(stored baseline should match)")
    print(f"  → 这个值就是 metrics_test_iter30000.json 里的 Native baseline。复核一下。")


def main():
    for scene, label in SCENES:
        print(f"\n\n{'#'*70}\n# SCENE: {scene}  [{label}]\n{'#'*70}")
        probe_camera_params(scene)
        result = probe_gt_match(scene)
        if result:
            our_test, native_gts, gt_dir = result
            probe_native_sanity(scene, our_test, native_gts, gt_dir)


if __name__ == "__main__":
    main()