kopia lustrzana https://github.com/OpenDroneMap/ODM
208 wiersze
6.4 KiB
Python
208 wiersze
6.4 KiB
Python
import numpy as np
|
|
import random
|
|
import math
|
|
import os
|
|
|
|
# https://stackoverflow.com/questions/38754668/plane-fitting-in-a-3d-point-cloud
|
|
|
|
def PCA(data, correlation = False, sort = True):
|
|
""" Applies Principal Component Analysis to the data
|
|
|
|
Parameters
|
|
----------
|
|
data: array
|
|
The array containing the data. The array must have NxM dimensions, where each
|
|
of the N rows represents a different individual record and each of the M columns
|
|
represents a different variable recorded for that individual record.
|
|
array([
|
|
[V11, ... , V1m],
|
|
...,
|
|
[Vn1, ... , Vnm]])
|
|
|
|
correlation(Optional) : bool
|
|
Set the type of matrix to be computed (see Notes):
|
|
If True compute the correlation matrix.
|
|
If False(Default) compute the covariance matrix.
|
|
|
|
sort(Optional) : bool
|
|
Set the order that the eigenvalues/vectors will have
|
|
If True(Default) they will be sorted (from higher value to less).
|
|
If False they won't.
|
|
Returns
|
|
-------
|
|
eigenvalues: (1,M) array
|
|
The eigenvalues of the corresponding matrix.
|
|
|
|
eigenvector: (M,M) array
|
|
The eigenvectors of the corresponding matrix.
|
|
|
|
Notes
|
|
-----
|
|
The correlation matrix is a better choice when there are different magnitudes
|
|
representing the M variables. Use covariance matrix in other cases.
|
|
|
|
"""
|
|
|
|
mean = np.mean(data, axis=0)
|
|
|
|
data_adjust = data - mean
|
|
|
|
#: the data is transposed due to np.cov/corrcoef syntax
|
|
if correlation:
|
|
|
|
matrix = np.corrcoef(data_adjust.T)
|
|
|
|
else:
|
|
matrix = np.cov(data_adjust.T)
|
|
|
|
eigenvalues, eigenvectors = np.linalg.eig(matrix)
|
|
|
|
if sort:
|
|
#: sort eigenvalues and eigenvectors
|
|
sort = eigenvalues.argsort()[::-1]
|
|
eigenvalues = eigenvalues[sort]
|
|
eigenvectors = eigenvectors[:,sort]
|
|
|
|
return eigenvalues, eigenvectors
|
|
|
|
def best_fitting_plane(points, equation=False):
|
|
""" Computes the best fitting plane of the given points
|
|
|
|
Parameters
|
|
----------
|
|
points: array
|
|
The x,y,z coordinates corresponding to the points from which we want
|
|
to define the best fitting plane. Expected format:
|
|
array([
|
|
[x1,y1,z1],
|
|
...,
|
|
[xn,yn,zn]])
|
|
|
|
equation(Optional) : bool
|
|
Set the oputput plane format:
|
|
If True return the a,b,c,d coefficients of the plane.
|
|
If False(Default) return 1 Point and 1 Normal vector.
|
|
Returns
|
|
-------
|
|
a, b, c, d : float
|
|
The coefficients solving the plane equation.
|
|
|
|
or
|
|
|
|
point, normal: array
|
|
The plane defined by 1 Point and 1 Normal vector. With format:
|
|
array([Px,Py,Pz]), array([Nx,Ny,Nz])
|
|
|
|
"""
|
|
|
|
w, v = PCA(points)
|
|
|
|
#: the normal of the plane is the last eigenvector
|
|
normal = v[:,2]
|
|
|
|
#: get a point from the plane
|
|
point = np.mean(points, axis=0)
|
|
|
|
|
|
if equation:
|
|
a, b, c = normal
|
|
d = -(np.dot(normal, point))
|
|
return a, b, c, d
|
|
|
|
else:
|
|
return point, normal
|
|
|
|
def ransac_max_iterations(points, inliers, failure_probability):
|
|
if len(inliers) >= len(points):
|
|
return 0
|
|
inlier_ratio = float(len(inliers)) / len(points)
|
|
n = 3
|
|
return math.log(failure_probability) / math.log(1.0 - inlier_ratio ** n)
|
|
|
|
|
|
def ransac_best_fitting_plane(points):
|
|
if len(points) < 3:
|
|
raise Exception("Cannot estimate plane with less than 3 points: %s" % str(points))
|
|
|
|
max_iterations = 1000
|
|
threshold = 1.2
|
|
best_error = np.inf
|
|
best_model = None
|
|
best_inliers = []
|
|
i = 0
|
|
while i < max_iterations:
|
|
samples = points[random.sample(range(len(points)), 3), :]
|
|
model = np.array(best_fitting_plane(samples, equation=True))
|
|
normal = model[0:3]
|
|
normal_norm = np.linalg.norm(normal) + 1e-10
|
|
|
|
s = points.shape[:-1] + (1,)
|
|
hpts = np.hstack((points, np.ones(s)))
|
|
|
|
errors = np.abs(model.T.dot(hpts.T)) / normal_norm
|
|
errors[errors < threshold] = 0.0
|
|
errors[errors >= threshold] = threshold + 0.1
|
|
inliers = np.flatnonzero(np.fabs(errors) < threshold)
|
|
error = np.fabs(errors).clip(0, threshold).sum()
|
|
if len(inliers) and error < best_error:
|
|
best_error = error
|
|
best_model = model
|
|
best_inliers = inliers
|
|
max_iterations = min(
|
|
max_iterations, ransac_max_iterations(points, best_inliers, 0.01)
|
|
)
|
|
i += 1
|
|
|
|
return best_fitting_plane(points[best_inliers])
|
|
|
|
|
|
def get_z_from_XY_plane(x, y, minz, maxz, plane_normal, plane_center):
|
|
minz -= 1e-6
|
|
maxz += 1e-6
|
|
|
|
b = minz - maxz
|
|
|
|
d = minz * plane_normal[2] - maxz * plane_normal[2]
|
|
if d == 0:
|
|
return 0
|
|
|
|
o = np.array([x,y,maxz])
|
|
t = (plane_center.dot(plane_normal) - plane_normal.dot(o)) / d
|
|
|
|
return maxz + b * t
|
|
|
|
def write_plane_ply(shots, plane_point, plane_normal, output_dir):
|
|
# Find reconstruction X/Y boundaries on the plane based on camera shots
|
|
minx, miny, minz, maxx, maxy, maxz = np.inf, np.inf, np.inf, -np.inf, -np.inf, -np.inf
|
|
|
|
for shot in shots:
|
|
print(dir(shot))
|
|
coords = shot.coordinates
|
|
if coords[0] < minx:
|
|
minx = coords[0]
|
|
if coords[0] > maxx:
|
|
maxx = coords[0]
|
|
if coords[1] < miny:
|
|
miny = coords[1]
|
|
if coords[1] > maxy:
|
|
maxy = coords[1]
|
|
if coords[2] < minz:
|
|
minz = coords[2]
|
|
if coords[2] > maxx:
|
|
maxx = coords[2]
|
|
|
|
# Create 4 corners points
|
|
points = np.array([
|
|
[x, y, get_z_from_XY_plane(x, y, minz, maxz, plane_normal, plane_point)] for x,y in [[minx, miny], [minx, maxy], [maxx, miny], [maxx, maxy]]
|
|
])
|
|
print(points)
|
|
with open(os.path.join(output_dir, "plane.ply"), "wb") as fout:
|
|
fout.write("ply\n".encode())
|
|
fout.write("format binary_little_endian 1.0\n".encode())
|
|
fout.write("element vertex 202141\n".encode())
|
|
fout.write("property float32 x\n".encode())
|
|
fout.write("property float32 y\n".encode())
|
|
fout.write("property float32 z\n".encode())
|
|
fout.write("element face 397578\n".encode())
|
|
fout.write("property list uint8 uint32 vertex_indices\n".encode())
|
|
fout.write("end_header\n".encode()) |