Skip to content

Commit 3dc9699

Browse files
authored
Adding point cloud sampling examples (AtsushiSakai#770)
1 parent e771054 commit 3dc9699

File tree

7 files changed

+254
-0
lines changed

7 files changed

+254
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"""
2+
Point cloud sampling example codes. This code supports
3+
- Voxel point sampling
4+
- Farthest point sampling
5+
- Poisson disk sampling
6+
7+
"""
8+
import matplotlib.pyplot as plt
9+
import numpy as np
10+
import numpy.typing as npt
11+
from collections import defaultdict
12+
13+
do_plot = True
14+
15+
16+
def voxel_point_sampling(original_points: npt.NDArray, voxel_size: float):
17+
"""
18+
Voxel Point Sampling function.
19+
This function sample N-dimensional points with voxel grid.
20+
Points in a same voxel grid will be merged by mean operation for sampling.
21+
22+
Parameters
23+
----------
24+
original_points : (M, N) N-dimensional points for sampling.
25+
The number of points is M.
26+
voxel_size : voxel grid size
27+
28+
Returns
29+
-------
30+
sampled points (M', N)
31+
"""
32+
voxel_dict = defaultdict(list)
33+
for i in range(original_points.shape[0]):
34+
xyz = original_points[i, :]
35+
xyz_index = tuple(xyz // voxel_size)
36+
voxel_dict[xyz_index].append(xyz)
37+
points = np.vstack([np.mean(v, axis=0) for v in voxel_dict.values()])
38+
return points
39+
40+
41+
def farthest_point_sampling(orig_points: npt.NDArray,
42+
n_points: int, seed: int):
43+
"""
44+
Farthest point sampling function
45+
This function sample N-dimensional points with the farthest point policy.
46+
47+
Parameters
48+
----------
49+
orig_points : (M, N) N-dimensional points for sampling.
50+
The number of points is M.
51+
n_points : number of points for sampling
52+
seed : random seed number
53+
54+
Returns
55+
-------
56+
sampled points (n_points, N)
57+
58+
"""
59+
rng = np.random.default_rng(seed)
60+
n_orig_points = orig_points.shape[0]
61+
first_point_id = rng.choice(range(n_orig_points))
62+
min_distances = np.ones(n_orig_points) * float("inf")
63+
selected_ids = [first_point_id]
64+
while len(selected_ids) < n_points:
65+
base_point = orig_points[selected_ids[-1], :]
66+
distances = np.linalg.norm(orig_points[np.newaxis, :] - base_point,
67+
axis=2).flatten()
68+
min_distances = np.minimum(min_distances, distances)
69+
distances_rank = np.argsort(-min_distances) # Farthest order
70+
for i in distances_rank: # From the farthest point
71+
if i not in selected_ids: # if not selected yes, select it
72+
selected_ids.append(i)
73+
break
74+
return orig_points[selected_ids, :]
75+
76+
77+
def poisson_disk_sampling(orig_points: npt.NDArray, n_points: int,
78+
min_distance: float, seed: int, MAX_ITER=1000):
79+
"""
80+
Poisson disk sampling function
81+
This function sample N-dimensional points randomly until the number of
82+
points keeping minimum distance between selected points.
83+
84+
Parameters
85+
----------
86+
orig_points : (M, N) N-dimensional points for sampling.
87+
The number of points is M.
88+
n_points : number of points for sampling
89+
min_distance : minimum distance between selected points.
90+
seed : random seed number
91+
MAX_ITER : Maximum number of iteration. Default is 1000.
92+
93+
Returns
94+
-------
95+
sampled points (n_points or less, N)
96+
"""
97+
rng = np.random.default_rng(seed)
98+
selected_id = rng.choice(range(orig_points.shape[0]))
99+
selected_ids = [selected_id]
100+
loop = 0
101+
while len(selected_ids) < n_points and loop <= MAX_ITER:
102+
selected_id = rng.choice(range(orig_points.shape[0]))
103+
base_point = orig_points[selected_id, :]
104+
distances = np.linalg.norm(
105+
orig_points[np.newaxis, selected_ids] - base_point,
106+
axis=2).flatten()
107+
if min(distances) >= min_distance:
108+
selected_ids.append(selected_id)
109+
loop += 1
110+
if len(selected_ids) != n_points:
111+
print("Could not find the specified number of points...")
112+
113+
return orig_points[selected_ids, :]
114+
115+
116+
def plot_sampled_points(original_points, sampled_points, method_name):
117+
fig = plt.figure()
118+
ax = fig.add_subplot(projection='3d')
119+
ax.scatter(original_points[:, 0], original_points[:, 1],
120+
original_points[:, 2], marker=".", label="Original points")
121+
ax.scatter(sampled_points[:, 0], sampled_points[:, 1],
122+
sampled_points[:, 2], marker="o",
123+
label="Filtered points")
124+
plt.legend()
125+
plt.title(method_name)
126+
plt.axis("equal")
127+
128+
129+
def main():
130+
n_points = 1000
131+
seed = 1234
132+
rng = np.random.default_rng(seed)
133+
134+
x = rng.normal(0.0, 10.0, n_points)
135+
y = rng.normal(0.0, 1.0, n_points)
136+
z = rng.normal(0.0, 10.0, n_points)
137+
original_points = np.vstack((x, y, z)).T
138+
print(f"{original_points.shape=}")
139+
print("Voxel point sampling")
140+
voxel_size = 20.0
141+
voxel_sampling_points = voxel_point_sampling(original_points, voxel_size)
142+
print(f"{voxel_sampling_points.shape=}")
143+
144+
print("Farthest point sampling")
145+
n_points = 20
146+
farthest_sampling_points = farthest_point_sampling(original_points,
147+
n_points, seed)
148+
print(f"{farthest_sampling_points.shape=}")
149+
150+
print("Poisson disk sampling")
151+
n_points = 20
152+
min_distance = 10.0
153+
poisson_disk_points = poisson_disk_sampling(original_points, n_points,
154+
min_distance, seed)
155+
print(f"{poisson_disk_points.shape=}")
156+
157+
if do_plot:
158+
plot_sampled_points(original_points, voxel_sampling_points,
159+
"Voxel point sampling")
160+
plot_sampled_points(original_points, farthest_sampling_points,
161+
"Farthest point sampling")
162+
plot_sampled_points(original_points, poisson_disk_points,
163+
"poisson disk sampling")
164+
plt.show()
165+
166+
167+
if __name__ == '__main__':
168+
main()

docs/modules/mapping/mapping_main.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Mapping
99
gaussian_grid_map/gaussian_grid_map
1010
ray_casting_grid_map/ray_casting_grid_map
1111
lidar_to_grid_map_tutorial/lidar_to_grid_map_tutorial
12+
point_cloud_sampling/point_cloud_sampling
1213
k_means_object_clustering/k_means_object_clustering
1314
circle_fitting/circle_fitting
1415
rectangle_fitting/rectangle_fitting
Loading
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
.. _point_cloud_sampling:
2+
3+
Point cloud Sampling
4+
----------------------
5+
6+
This sections explains point cloud sampling algorithms in PythonRobotics.
7+
8+
Point clouds are two-dimensional and three-dimensional based data
9+
acquired by external sensors like LIDAR, cameras, etc.
10+
In general, Point Cloud data is very large in number of data.
11+
So, if you process all the data, computation time might become an issue.
12+
13+
Point cloud sampling is a technique for solving this computational complexity
14+
issue by extracting only representative point data and thinning the point
15+
cloud data without compromising the performance of processing using the point
16+
cloud data.
17+
18+
Voxel Point Sampling
19+
~~~~~~~~~~~~~~~~~~~~~~~~
20+
.. figure:: voxel_point_sampling.png
21+
22+
Voxel grid sampling is a method of reducing point cloud data by using the
23+
`Voxel grids <https://en.wikipedia.org/wiki/Voxel>`_ which is regular grids
24+
in three-dimensional space.
25+
26+
This method determines which each point is in a grid, and replaces the point
27+
clouds that are in the same Voxel with their average to reduce the number of
28+
points.
29+
30+
API
31+
=====
32+
33+
.. autofunction:: Mapping.point_cloud_sampling.point_cloud_sampling.voxel_point_sampling
34+
35+
36+
Farthest Point Sampling
37+
~~~~~~~~~~~~~~~~~~~~~~~~~
38+
.. figure:: farthest_point_sampling.png
39+
40+
Farthest Point Sampling is a point cloud sampling method by a specified
41+
number of points so that the distance between points is as far from as
42+
possible.
43+
44+
This method is useful for machine learning and other situations where
45+
you want to obtain a specified number of points from point cloud.
46+
47+
API
48+
=====
49+
50+
.. autofunction:: Mapping.point_cloud_sampling.point_cloud_sampling.farthest_point_sampling
51+
52+
Poisson Disk Sampling
53+
~~~~~~~~~~~~~~~~~~~~~~~~~
54+
.. figure:: poisson_disk_sampling.png
55+
56+
Poisson disk sample is a point cloud sampling method by a specified
57+
number of points so that the algorithm selects points where the distance
58+
from selected points is greater than a certain distance.
59+
60+
Although this method does not have good performance comparing the Farthest
61+
distance sample where each point is distributed farther from each other,
62+
this is suitable for real-time processing because of its fast computation time.
63+
64+
API
65+
=====
66+
67+
.. autofunction:: Mapping.point_cloud_sampling.point_cloud_sampling.poisson_disk_sampling
68+
69+
70+
Loading
Loading

tests/test_point_cloud_sampling.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import conftest # Add root path to sys.path
2+
from Mapping.point_cloud_sampling import point_cloud_sampling as m
3+
4+
5+
def test_1(capsys):
6+
m.do_plot = False
7+
m.main()
8+
captured = capsys.readouterr()
9+
assert "voxel_sampling_points.shape=(27, 3)" in captured.out
10+
assert "farthest_sampling_points.shape=(20, 3)" in captured.out
11+
assert "poisson_disk_points.shape=(20, 3)" in captured.out
12+
13+
14+
if __name__ == '__main__':
15+
conftest.run_this_test(__file__)

0 commit comments

Comments
 (0)