Last active
July 20, 2021 07:11
-
-
Save oliver-batchelor/b6364fb8fd59fdf8098c85348a5d3b00 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
import numpy as np | |
import functools | |
from scipy.optimize import leastsq | |
def homog_points(points): | |
# Expand point vector x,y,z to x,y,z,1 | |
n = points.shape[0] | |
return np.hstack([points, np.ones( (n, 1) )]) | |
def plane_distances(plane_eq, points): | |
normal = plane_eq[0:3] | |
distance = np.tensordot(normal, points, (0, 1)) + plane_eq[3] | |
return distance / np.linalg.norm(normal) | |
def optimize_plane(plane_eq, points): | |
def eval(params): | |
return plane_distances(params, points) | |
optimized, _ = leastsq(eval, plane_eq) | |
return optimized / np.linalg.norm(optimized[0:3]) | |
def plane3(points): | |
assert points.shape == (3, 3) | |
vecA = points[1] - points[0] | |
vecB = points[2] - points[0] | |
# Now we compute the cross product of vecA and vecB to get vecC which is normal to the plane | |
normal = np.cross(vecA, vecB) | |
# The plane equation will be normal[0]*x + normal[1]*y + normal[0]*z + k = 0 | |
# We have to use a point to find k | |
normal = normal / np.linalg.norm(normal) | |
k = -np.sum(np.multiply(normal, points[1, :])) | |
return np.array([*normal, k]) | |
def ransac_plane(points, thresh=0.05, min_points=100, max_iterations=1000): | |
""" | |
Ransac plane fitting. | |
:param points: 3D point cloud as a `np.array (N,3)`. | |
:param thresh: Threshold distance from the plane which is considered inlier. | |
:param maxIteration: Number of maximum iteration which RANSAC will loop over. | |
:returns: | |
- `self.equation`: Parameters of the plane using Ax+By+Cy+D `np.array (1, 4)` | |
- `self.inliers`: points from the dataset considered inliers | |
--- | |
""" | |
n_points = points.shape[0] | |
for _ in range(max_iterations): | |
# Samples 3 random points | |
pt_samples = points[random.sample(range(0, n_points), 3)] | |
# We have to find the plane equation described by those 3 points | |
# We find first 2 vectors that are part of this plane | |
plane_eq = plane3(pt_samples) | |
distances = plane_distances(plane_eq, points) | |
# Select indexes where distance is biggers than the threshold | |
inliers = np.where(np.abs(distances) <= thresh)[0] | |
if len(inliers) > min_points: | |
return plane_eq, inliers | |
return None | |
def random_plane_points(num_points = 1000, size=5.0, noise_level=0.1): | |
plane_points = np.random.rand(3, 3) | |
plane_eq = plane3(plane_points) | |
vecA = plane_points[1] - plane_points[0] | |
vecB = plane_points[2] - plane_points[0] | |
vecA = vecA * size / np.linalg.norm(vecA) | |
vecB = vecB * size / np.linalg.norm(vecB) | |
x = np.random.randn(num_points, 2) | |
points = x[:, 0:1] * vecA.reshape(-1, 3) + x[:, 1:2] * vecB.reshape(-1, 3) + plane_points[0] | |
noise = np.random.randn(*points.shape) * noise_level | |
return plane_eq, points + noise | |
if __name__ == '__main__': | |
plane, points = random_plane_points(5000, size=5.0, noise_level=0.2) | |
found = ransac_plane(points, thresh=0.4) | |
if (found): | |
found_plane, inliers = found | |
opt_plane = optimize_plane(found_plane, points[inliers]) | |
print(plane) | |
print(len(inliers)) | |
print(found_plane) | |
print(opt_plane) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Fancier version which works as one function... I guess have a play and see which works best.