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()
|