Skip to content

Commit 73df741

Browse files
committed
Merge branch 'util-improvements' into 'main'
RemoveGroundPlane: More control over CC filtering See merge request educelab/pgs-recon!45
2 parents bc37c0b + e3a2398 commit 73df741

File tree

4 files changed

+42
-10
lines changed

4 files changed

+42
-10
lines changed

pgs_recon/apps/reconstruct.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,8 @@ def main():
273273
'refinement (0 - auto, 1 - disabled)')
274274
opts_mvs.add_argument('--mask-value', type=int, default=0,
275275
help='Label value in the image mask to ignore during '
276-
'mesh densification. By default, image masks '
277-
'are ignored during this step. Set to a value '
278-
'< 0 to ignore masks during this step.')
276+
'mesh densification. Set to a value < 0 to '
277+
'ignore masks during this step.')
279278
opts_mvs.add_argument('--texture-max-size', type=int, default=0,
280279
help='Limits the maximum size (edge length) of the'
281280
'output texture image. If set to 0 (default), '

pgs_recon/apps/remove_ground_plane.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
import pgs_recon.utils.wavefront as wobj
44
from pgs_recon.utils import geometry as geom
55

6+
def parse_filter_cc(arg: str):
7+
arg = arg.lower()
8+
if arg == 'none':
9+
return 0
10+
elif arg == 'largest':
11+
return -1
12+
else:
13+
return int(arg)
14+
615

716
def main():
817
parser = argparse.ArgumentParser()
@@ -17,6 +26,13 @@ def main():
1726
'considered inliers if their distance from the '
1827
'plane is less than the distance threshold. This '
1928
'should be tuned based on the point density.')
29+
parser.add_argument('--filter-cc', default='largest', type=parse_filter_cc,
30+
help="Filter the mesh's connected components after "
31+
"removing the ground plane:\n"
32+
" - 'none': No filtering\n"
33+
" - 'largest': keep only the largest connected component\n"
34+
" - N: remove all connected components with fewer than N faces")
35+
parser.add_argument('--seed', type=int, default=0)
2036
args = parser.parse_args()
2137

2238
# Load the mesh
@@ -30,11 +46,17 @@ def main():
3046

3147
print('Fitting plane...')
3248
_, plane_inliers = geom.segment_plane(mesh,
33-
dist_threshold=args.distance_threshold)
49+
dist_threshold=args.distance_threshold,
50+
seed=args.seed)
51+
print('Removing plane...')
3452
geom.remove_vertices_by_index(mesh, plane_inliers)
3553

36-
print('Removing small connected components...')
37-
geom.keep_largest_connected_component(mesh, filter_vertices=True)
54+
if args.filter_cc > 0:
55+
print(f'Removing connected components smaller than {args.filter_cc} faces...')
56+
geom.remove_connected_components_by_size(mesh, num_faces=args.filter_cc)
57+
elif args.filter_cc < 0:
58+
print('Keeping largest connected component...')
59+
geom.keep_largest_connected_component(mesh, filter_vertices=True)
3860

3961
print('Saving mesh...')
4062
obj = geom.mesh_to_wavefront(mesh, obj)

pgs_recon/utils/geometry.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def mesh_to_wavefront(mesh: Mesh, obj: wobj.WavefrontOBJ = None):
4545

4646

4747
def segment_plane(mesh, dist_threshold=0.1, point_samples=3, iterations=1000,
48-
prob=0.99999999):
48+
prob=0.99999999, seed=None):
4949
class RANSACResult:
5050
inliers: List[int]
5151
error: float
@@ -100,7 +100,7 @@ def eval_from_distance(pts: np.ndarray, plane: np.ndarray,
100100
best_model = np.zeros((4,))
101101

102102
# Iterate up to some max iterations
103-
rng = np.random.default_rng()
103+
rng = np.random.default_rng(seed=seed)
104104
break_it = iterations
105105
for i in range(iterations):
106106
# Break early based on fitness/rmse
@@ -299,14 +299,25 @@ def cluster_connected_components(mesh: Mesh):
299299
return face_cluster, cluster_metrics
300300

301301

302-
def keep_largest_connected_component(mesh: Mesh, filter_vertices=False):
302+
def keep_largest_connected_component(mesh: Mesh, filter_vertices=True):
303303
_, metrics = cluster_connected_components(mesh)
304304
metrics = sorted(metrics, key=lambda m: m['area'], reverse=True)
305305
keep_triangles_by_mask(mesh, metrics[0]['faces'])
306306
if filter_vertices:
307307
remove_unreferenced_vertices(mesh)
308308

309309

310+
def remove_connected_components_by_size(mesh: Mesh, num_faces: int, filter_vertices=True):
311+
_, metrics = cluster_connected_components(mesh)
312+
faces = set()
313+
for metric in metrics:
314+
if len(metric['faces']) >= num_faces:
315+
faces = faces.union(metric['faces'])
316+
keep_triangles_by_mask(mesh, list(faces))
317+
if filter_vertices:
318+
remove_unreferenced_vertices(mesh)
319+
320+
310321
def remove_degenerate_faces(mesh: Mesh):
311322
# get list of good faces
312323
mask = []

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = pgs-recon
3-
version = 1.7.5
3+
version = 1.7.6-alpha.0
44
author = Seth Parker
55
author_email = c.seth.parker@uky.edu
66
description = A photogrammetry reconstruction pipeline

0 commit comments

Comments
 (0)