File size: 6,543 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 | import os
import sys
import math
import argparse
import torch
import numpy as np
from PIL import Image, ImageOps
from plyfile import PlyData
import gsplat
sys.path.insert(0, '/root/autodl-tmp/3dgsAtlas_official')
@torch.no_grad()
def load_ply(path):
plydata = PlyData.read(path)
v = plydata['vertex']
means = np.stack((v['x'], v['y'], v['z']), axis=-1)
quats = np.stack((v['rot_0'], v['rot_1'], v['rot_2'], v['rot_3']), axis=-1)
scales = np.stack((v['scale_0'], v['scale_1'], v['scale_2']), axis=-1)
opacities = v['opacity']
f_dc = np.stack((v['f_dc_0'], v['f_dc_1'], v['f_dc_2']), axis=-1)
f_rest = np.stack([v[f'f_rest_{i}'] for i in range(45)], axis=-1)
device = torch.device("cuda")
means = torch.tensor(means, dtype=torch.float32, device=device)
quats = torch.tensor(quats, dtype=torch.float32, device=device)
scales = torch.exp(torch.tensor(scales, dtype=torch.float32, device=device))
opacities = torch.sigmoid(torch.tensor(opacities, dtype=torch.float32, device=device))
f_dc = torch.tensor(f_dc, dtype=torch.float32, device=device).unsqueeze(1)
f_rest = torch.tensor(f_rest, dtype=torch.float32, device=device)
f_rest = f_rest.view(-1, 3, 15).transpose(1, 2)
shs = torch.cat([f_dc, f_rest], dim=1)
return means, quats, scales, opacities, shs
def load_test_cameras(source_path, resolution):
from scene.dataset_readers import readColmapSceneInfo, readNerfSyntheticInfo
from utils.graphics_utils import getWorld2View2
import inspect
parsed = []
is_synthetic = os.path.exists(os.path.join(source_path, "transforms_test.json")) and not os.path.exists(os.path.join(source_path, "sparse"))
if not is_synthetic:
img_dir = f"images_{resolution}" if resolution > 1 and os.path.exists(os.path.join(source_path, f"images_{resolution}")) else "images"
sig = inspect.signature(readColmapSceneInfo)
args_list = []
for i, (k, p) in enumerate(sig.parameters.items()):
if i == 0: args_list.append(source_path)
elif i == 1: args_list.append(img_dir)
elif k == "eval": args_list.append(True) # <--- 核心修改:让 Native 决定谁是 Test 集
elif k == "train_test_exp": args_list.append(False)
else: args_list.append(p.default if p.default != inspect.Parameter.empty else "")
scene_info = readColmapSceneInfo(*args_list)
test_cams = scene_info.test_cameras # 直接沿用无损的 Native Test 阵列
else:
sig = inspect.signature(readNerfSyntheticInfo)
args_list = []
for i, (k, p) in enumerate(sig.parameters.items()):
if i == 0: args_list.append(source_path)
elif k == "eval": args_list.append(True)
elif k == "extension": args_list.append(".png")
else: args_list.append(p.default if p.default != inspect.Parameter.empty else "")
scene_info = readNerfSyntheticInfo(*args_list)
test_cams = scene_info.test_cameras
for c in test_cams:
w = int(round(c.width / resolution))
h = int(round(c.height / resolution))
viewmat = getWorld2View2(np.array(c.R), np.array(c.T)).astype(np.float32)
fx = w / (2 * math.tan(c.FovX / 2))
fy = h / (2 * math.tan(c.FovY / 2))
K = np.array([[fx, 0, w/2], [0, fy, h/2], [0, 0, 1]], dtype=np.float32)
pil_img = getattr(c, 'image', None)
if pil_img is None:
img_path = getattr(c, 'image_path', None)
if not img_path or not os.path.exists(img_path):
folder = f"images_{resolution}" if resolution > 1 and os.path.exists(os.path.join(source_path, f"images_{resolution}")) else "images"
img_path = os.path.join(source_path, folder, c.image_name)
pil_img = Image.open(img_path)
pil_img = ImageOps.exif_transpose(pil_img)
parsed.append({
'name': c.image_name,
'pil_image': pil_img,
'viewmat': viewmat, 'K': K, 'width': w, 'height': h,
})
return parsed
def compute_psnr(a, b):
mse = torch.mean((a - b) ** 2)
return 100.0 if mse == 0 else (10 * torch.log10(1.0 / mse)).item()
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--ply_path", required=True)
parser.add_argument("--source_path", required=True)
parser.add_argument("--model_path", required=True)
parser.add_argument("--output_dir", required=True)
parser.add_argument("--resolution", type=int, default=2)
parser.add_argument("--bg_color", type=str, default="0,0,0")
args = parser.parse_args()
from utils.general_utils import PILtoTorch
device = torch.device("cuda")
means, quats, scales, opacities, shs = load_ply(args.ply_path)
cameras = load_test_cameras(args.source_path, args.resolution)
bg_parts = [float(x) for x in args.bg_color.split(",")]
bg_color = torch.tensor(bg_parts, dtype=torch.float32, device=device)
bg_color_chw = bg_color.view(3, 1, 1)
psnrs = []
for i, cam in enumerate(cameras):
viewmat = torch.tensor(cam['viewmat'], device=device).unsqueeze(0)
K = torch.tensor(cam['K'], device=device).unsqueeze(0)
colors, _, _ = gsplat.rasterization(
means=means, quats=quats, scales=scales, opacities=opacities, colors=shs,
viewmats=viewmat, Ks=K,
width=cam['width'], height=cam['height'],
sh_degree=3, packed=True, render_mode='RGB', backgrounds=bg_color.unsqueeze(0), near_plane=0.01, far_plane=100.0,
)
render_img = colors[0].clamp(0, 1)
gt_chw_full = PILtoTorch(cam['pil_image'], (cam['width'], cam['height'])).to(device)
if gt_chw_full.shape[0] == 4:
alpha = gt_chw_full[3:4, ...]
rgb = gt_chw_full[:3, ...]
gt_chw = rgb * alpha + bg_color_chw * (1.0 - alpha)
else:
gt_chw = gt_chw_full[:3, ...]
gt_img = gt_chw.permute(1, 2, 0)
psnr_val = compute_psnr(render_img, gt_img)
psnrs.append(psnr_val)
if not psnrs:
print("\nRENDER_SINGLE DONE — STATUS: FAIL — DELTA: nan dB")
return
mean_psnr = float(np.mean(psnrs))
baseline_path = os.path.join(args.model_path, "metrics_test_iter30000.json")
baseline = 32.4354
|