DepthART / src /tool /evaluate_nyu654_depth.py
sunyijie
first commit
efaa3d7
Raw History Blame Contribute Delete
8.42 kB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
NYU Depth V2 Eigen test split (654 张) 相对深度精度评估。
输入:
--mat nyu_depth_v2_labeled.mat(GT 直接从这里读,不落盘)
--split export_nyu654_test.py 生成的 split.json
--pred 一个或多个 <名称>=<NPZ 目录>,NPZ 由 infer 脚本 --dump-npz 生成
评估协议:
1. 有效像素:rawDepths > 0 且 depths 在 (0, max_depth];
2. 预测上采样到 GT 分辨率(bilinear);
3. 相对深度对齐:
- disparity(默认,MiDaS 协议):拟合 a*pred + b ≈ 1/gt,再取倒数得米制深度;
- depth:拟合 a*pred + b ≈ gt;
最小二乘在有效像素上求解;
4. 逐图计算 AbsRel / SqRel / RMSE / RMSE_log / δ<1.25 / 1.25² / 1.25³,
对 654 张取平均;
5. 第一个 --pred 作为基准,其余输出相对掉点 (%)。
注意:未做 Eigen crop,绝对数值不能直接与论文表格对比;同一批图上不同后端的
相对掉点不受影响。
用法:
../../venv/bin/python src/tool/evaluate_nyu654_depth.py \
--mat ../../datasets/nyu_depth_v2_labeled.mat \
--split datasets/nyu654_test/split.json \
--pred fp32=results/nyu654_fp32/npz axonnx=results/nyu654_axonnx/npz \
--out results/nyu654_eval
"""
import argparse
import json
from pathlib import Path
import cv2
import h5py
import numpy as np
from tqdm import tqdm
METRICS = ('AbsRel', 'SqRel', 'RMSE', 'RMSE_log', 'd1', 'd2', 'd3')
SIGMA = (1.25, 1.25 ** 2, 1.25 ** 3)
def solve_scale_shift(pred, target, mask):
"""在 mask 上最小二乘求解 a, b 使 a*pred + b ≈ target。"""
p = pred[mask]
t = target[mask]
A = np.stack([p, np.ones_like(p)], axis=1)
(a, b), *_ = np.linalg.lstsq(A, t, rcond=None)
return float(a), float(b)
def compute_metrics(pred_depth, gt_depth, mask, align, max_depth):
clipped = 0.0
if align == 'disparity':
gt_target = 1.0 / gt_depth
a, b = solve_scale_shift(pred_depth, gt_target, mask)
if a <= 0:
raise ValueError(f'disparity 对齐斜率为负 (a={a:.4f}),'
f'预测不是"越大越近"的视差量')
aligned = a * pred_depth + b
# 极少数图(低纹理/异常预测)会拟出负视差,裁剪到最大深度的下限
neg = aligned[mask] <= 0
clipped = float(neg.mean())
aligned = np.maximum(aligned, 1.0 / max_depth)
pred = 1.0 / aligned
else:
a, b = solve_scale_shift(pred_depth, gt_depth, mask)
pred = a * pred_depth + b
pred = pred[mask]
gt = gt_depth[mask]
if np.any(pred <= 0):
raise ValueError('对齐后预测深度出现非正值')
diff = pred - gt
out = {
'AbsRel': float(np.mean(np.abs(diff) / gt)),
'SqRel': float(np.mean(diff ** 2 / gt)),
'RMSE': float(np.sqrt(np.mean(diff ** 2))),
'RMSE_log': float(np.sqrt(np.mean((np.log(pred) - np.log(gt)) ** 2))),
}
ratio = np.maximum(pred / gt, gt / pred)
for name, s in zip(('d1', 'd2', 'd3'), SIGMA):
out[name] = float(np.mean(ratio < s))
return out, a, b, clipped
def load_predictions(path):
"""读取预测:汇总 NPZ(names/depths)、分块汇总目录,或旧的逐图 NPZ 目录。"""
path = Path(path)
if path.is_file():
files = [path]
elif path.is_dir():
files = sorted(path.glob('*.npz'))
if not files:
raise ValueError(f'目录中没有 NPZ: {path}')
else:
raise FileNotFoundError(f'预测路径不存在: {path}')
preds = {}
for fp in files:
with np.load(fp, allow_pickle=False) as data:
if 'depths' in data and 'names' in data:
for name, depth in zip(data['names'], data['depths']):
preds[str(name)] = depth
elif 'depth' in data:
preds[fp.name[:-len('.npz')]] = data['depth']
else:
raise ValueError(f'无法识别的 NPZ 格式: {fp}')
return preds
def main():
parser = argparse.ArgumentParser(description='NYU 654 相对深度评测')
parser.add_argument('--mat', required=True)
parser.add_argument('--split', required=True, help='split.json')
parser.add_argument('--pred', nargs='+', required=True, help='名称=NPZ目录,第一个为基准')
parser.add_argument('--out', required=True, help='结果目录')
parser.add_argument('--align', choices=['disparity', 'depth'], default='disparity')
parser.add_argument('--max-depth', type=float, default=10.0)
args = parser.parse_args()
pred_sets = []
for item in args.pred:
name, _, path = item.partition('=')
if not path:
raise ValueError(f'--pred 需要 名称=路径 格式: {item}')
pred_sets.append((name, load_predictions(path)))
split = json.load(open(args.split))
out_dir = Path(args.out)
out_dir.mkdir(parents=True, exist_ok=True)
per_image = {name: [] for name, _ in pred_sets}
failed = {name: [] for name, _ in pred_sets}
with h5py.File(args.mat, 'r') as f:
for record in tqdm(split, desc=f'eval({args.align})', ncols=80):
i = record['mat_index']
gt = np.asarray(f['depths'][i], dtype=np.float32)
raw = np.asarray(f['rawDepths'][i], dtype=np.float32)
mask = (raw > 0) & (gt > 0) & (gt <= args.max_depth)
for name, preds in pred_sets:
if record['file'] not in preds:
raise KeyError(f'{name} 缺少预测: {record["file"]}')
pred = np.asarray(preds[record['file']], dtype=np.float32)
if pred.ndim == 3:
pred = pred[0]
pred = cv2.resize(pred, (gt.shape[1], gt.shape[0]), interpolation=cv2.INTER_LINEAR)
try:
m, a, b, clipped = compute_metrics(pred, gt, mask, args.align, args.max_depth)
except ValueError as error:
failed[name].append({'file': record['file'], 'reason': str(error)})
continue
m.update({'file': record['file'], 'scale': a, 'shift': b,
'clipped_frac': clipped, 'valid_pixels': int(mask.sum())})
per_image[name].append(m)
results = {}
for name, _ in pred_sets:
rows = per_image[name]
results[name] = {k: float(np.mean([r[k] for r in rows])) for k in METRICS}
results[name]['p90_absrel'] = float(np.percentile([r['AbsRel'] for r in rows], 90))
results[name]['clipped_images'] = int(sum(r['clipped_frac'] > 0 for r in rows))
results[name]['clipped_pixel_frac'] = float(np.mean([r['clipped_frac'] for r in rows]))
results[name]['evaluated_images'] = len(rows)
results[name]['failed_images'] = len(failed[name])
base = pred_sets[0][0]
drops = {}
for name, _ in pred_sets[1:]:
drops[name] = {}
for k in METRICS:
b, v = results[base][k], results[name][k]
drops[name][k] = float((v - b) / b * 100)
b, v = results[base]['p90_absrel'], results[name]['p90_absrel']
drops[name]['p90_absrel'] = float((v - b) / b * 100)
summary = {
'dataset': 'nyu_depth_v2_labeled / eigen test split',
'num_images': len(split),
'align': args.align,
'max_depth': args.max_depth,
'eigen_crop': False,
'results': results,
'baseline': base,
'drop_percent': drops,
'failed': failed,
'per_image': {name: per_image[name] for name, _ in pred_sets},
}
with open(out_dir / 'metrics.json', 'w') as fp:
json.dump(summary, fp, indent=2)
print(f'\n{"指标":<10}' + ''.join(f'{n:>14}' for n, _ in pred_sets))
for k in METRICS + ('p90_absrel',):
print(f'{k:<10}' + ''.join(f'{results[n][k]:>14.4f}' for n, _ in pred_sets))
for name, d in drops.items():
print(f'\n{name} 相对 {base} 掉点 (%): ' +
', '.join(f'{k}={v:+.2f}' for k, v in d.items()))
for name, _ in pred_sets:
if failed[name]:
print(f'\n{name}: {len(failed[name])} 张对齐失败(方向反了或输出异常),未计入均值')
print(f'\n结果已保存: {out_dir / "metrics.json"}')
if __name__ == '__main__':
main()