From 352af149fde1a8a418df863f4700cfc9680ea4ba Mon Sep 17 00:00:00 2001 From: Flag Date: Thu, 23 Apr 2026 23:10:11 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20viser=5Fply=20filtrage=20outliers=20stat?= =?UTF-8?q?istiques=20=E2=80=94=20supprime=20gros=20p=C3=A2t=C3=A9s=20brui?= =?UTF-8?q?t=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/viser_ply.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/scripts/viser_ply.py b/scripts/viser_ply.py index 05c810d..3713f4f 100644 --- a/scripts/viser_ply.py +++ b/scripts/viser_ply.py @@ -1,15 +1,7 @@ #!/usr/bin/env python3 -"""Minimal standalone viser viewer for a single PLY point cloud. - -Usage: - python3 viser_ply.py path/to/cloud.ply --port 8200 -""" +"""Minimal standalone viser viewer for a single PLY point cloud.""" from __future__ import annotations - -import argparse -import sys -import time - +import argparse, sys, time import numpy as np @@ -18,10 +10,9 @@ def main(): ap.add_argument("ply", help="PLY file to visualize") ap.add_argument("--port", type=int, default=8200) ap.add_argument("--point-size", type=float, default=0.01) - ap.add_argument("--downsample", type=float, default=0.0, - help="Voxel downsample size (0 = no downsample)") - ap.add_argument("--max-points", type=int, default=2_000_000, - help="Random subsample to this many points if cloud is larger") + ap.add_argument("--downsample", type=float, default=0.0) + ap.add_argument("--max-points", type=int, default=2_000_000) + ap.add_argument("--no-filter", action="store_true", help="Disable outlier removal") args = ap.parse_args() try: @@ -31,15 +22,30 @@ def main(): sys.exit(f"missing dep: {e}") pcd = o3d.io.read_point_cloud(args.ply) + n_raw = len(pcd.points) + print(f"loaded {n_raw:,} pts from {args.ply}", flush=True) + if args.downsample > 0: pcd = pcd.voxel_down_sample(args.downsample) + print(f"after voxel downsample: {len(pcd.points):,} pts", flush=True) + + if not args.no_filter and len(pcd.points) > 100: + # Remove statistical outliers (blobs / noise from low-confidence frames) + pcd, _ = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0) + print(f"after outlier removal: {len(pcd.points):,} pts", flush=True) + # Second pass tighter for dense clouds + if len(pcd.points) > 200_000: + pcd, _ = pcd.remove_statistical_outlier(nb_neighbors=30, std_ratio=1.5) + print(f"after 2nd pass: {len(pcd.points):,} pts", flush=True) + pts = np.asarray(pcd.points, dtype=np.float32) cols = np.asarray(pcd.colors, dtype=np.float32) if pcd.has_colors() else np.ones_like(pts) * 0.7 + if len(pts) > args.max_points: idx = np.random.choice(len(pts), args.max_points, replace=False) pts, cols = pts[idx], cols[idx] - print(f"loaded {len(pts):,} pts from {args.ply}", flush=True) + print(f"displaying {len(pts):,} pts", flush=True) server = viser.ViserServer(host="0.0.0.0", port=args.port) server.scene.add_point_cloud( "/cloud", points=pts, colors=(cols * 255).astype(np.uint8), point_size=args.point_size