matlab fit an ellipse from 3D datapoints - matlab

I have a set of data points in 3D (X,Y,Z) in a given plane (3D). and i hope to fit an epllipse to those points.
I found a lot of answers about how to fit the ellipse in 2D points. So more precisely, my question is how to transform 3D data(x,y,z) points -> 2D data(x,y)?

Here is my Python code for this problem.
this link helped me through my implementation: https://meshlogic.github.io/posts/jupyter/curve-fitting/fitting-a-circle-to-cluster-of-3d-points/
import numpy as np
from skimage.measure import EllipseModel
#-------------------------------------------------------------------------------
# RODRIGUES ROTATION
# - Rotate given points based on a starting and ending vector
# - Axis k and angle of rotation theta given by vectors n0,n1
# P_rot = P*cos(theta) + (k x P)*sin(theta) + k*<k,P>*(1-cos(theta))
#-------------------------------------------------------------------------------
def rodrigues_rot(P, n0, n1):
# If P is only 1d array (coords of single point), fix it to be matrix
if P.ndim == 1:
P = P[np.newaxis,:]
# Get vector of rotation k and angle theta
n0 = n0/np.linalg.norm(n0)
n1 = n1/np.linalg.norm(n1)
k = np.cross(n0,n1)
k = k/np.linalg.norm(k)
theta = np.arccos(np.dot(n0,n1))
# Compute rotated points
P_rot = np.zeros((len(P),3))
for i in range(len(P)):
P_rot[i] = P[i]*np.cos(theta) + np.cross(k,P[i])*np.sin(theta) + k*np.dot(k,P[i])*(1-np.cos(theta))
return P_rot
def fit_an_ellipse(P):
P_mean = P.mean(axis=0)
P_centered = P - P_mean
# Fitting plane by SVD for the mean-centered data
U,s,V = np.linalg.svd(P_centered, full_matrices=False)
# Normal vector of fitting plane is given by 3rd column in V
# Note linalg.svd returns V^T, so we need to select 3rd row from V^T
# normal on 3d plane
normal = V[2,:]
# Project points to coords X-Y in 2D plane
P_xy = rodrigues_rot(P_centered, normal, [0,0,1])
# Use skimage EllipseModel to fit an ellipse to set of 2d points
ell = EllipseModel()
ell.estimate(P_xy[:, :2])
# Generate n 2D points on the fitted elippse
n = 100
xy = ell.predict_xy(np.linspace(0, 2 * np.pi, n))
# Convert the 2D generated points to the 3D space
points = []
for i in range(len(xy)):
points.append([xy[i, 0], xy[i, 1], 0])
points = np.array(points)
ellipse_points_3d = rodrigues_rot(points, [0,0,1], normal) + P_mean
return ellipse_points_3d
to test the code you can run this and check the output result:
import numpy as np
import plotly
import plotly.graph_objs as go
P = np.array([[52.21818786, 7.86337722, 57.83456389],
[30.55316226, 32.36591494, 14.35753359],
[59.77387002, 14.29531811, 53.6462596 ],
[42.85677086, 32.67223954, -5.95323959],
[44.46449002, 1.43144171, 54.0253186 ],
[27.6464027 , 19.80836045, -1.5754063 ],
[63.48591069, 6.88329618, 57.55556516],
[44.19484831, 28.32302575, 6.01730042],
[46.09443886, 2.71782362, 57.98617489],
[22.55050927, 30.28315605, 42.5642505 ],
[20.16244533, 18.55944689, 34.06871328],
[69.4591254 , 33.62256919, 40.91996533],
[24.42183439, 5.95578526, 35.80224431],
[70.09161495, 24.03152634, 45.77915268],
[28.68122335, -6.64788396, 37.53577535],
[59.84340586, 23.47833222, 60.01530894],
[23.98376474, 14.23114661, 32.43676647],
[73.28044481, 29.29426891, 39.28801852],
[28.48679585, -5.33220296, 36.04206575],
[54.66351746, 15.7561502 , 51.20981383],
[38.33444206, -0.08003422, 41.2639318 ],
[57.27722964, 39.91662965, 20.63778872],
[43.24856256, 7.79042068, 50.95451935],
[64.68788661, 31.78841088, 27.19632274],
[41.67377653, -0.18313508, 49.56081237],
[60.577958 , 35.8138609 , 28.9005053 ]])
ellipse_points = fit_an_ellipse(P)
lines = []
lines.append(go.Scatter3d(x=P[:, 0], y=P[:, 1], \
z=P[:, 2],name='P'\
,opacity=1, ))
lines.append(go.Scatter3d(x=ellipse_points[:, 0], y=ellipse_points[:, 1], \
z=ellipse_points[:, 2],name='ellipse_points'\
,opacity=1, ))
plotly.offline.iplot(lines)
the output result:
the 3d fitting result
you can try the code yourself in colab: https://colab.research.google.com/drive/1snI2-_S1CY8iUtszRP1bzsFULEQYdJym?usp=sharing

In standard projections the ellipse (and circle is elipse with a = b = r) will be projected in ellipse or a line. So I will use this behaviour so the 3D ellipse you want will be defined by different ellipse you can calculate.
I will not show the code, but the approach can be:
Make the data be defined in M x 3 matrix, where M is number of points, in form of [x,y,z]
Define the plane in form of z=f(x,y)
Search the data matrix for rows equal or simillar to [x,y,f(x,y)] vectors
Suppose the resulting point are elipse-shape-spaced. Then use the answer how to fit ellipse to [x,y] pairs in the matrix resulted from the search (ignoring the z part can be understtod as projection to x-y plane).
Transform the fitted data to N x 2 matrix in form of [x_fit,y_fit]
Expand the last matrix into form of [x_fit,y_fit,f(x_fit,y_fit)]
Voila - here we have fitted elipsis.

Related

How to extract pixel value corresponds to a 2D image point represented by (x,y) coordinate?

I'm trying to implement a space carving algorithm to reconstruct 3D shape of a moving person. I have all the camera calibration data and projection matrices. Also I could get 2D image points from the 3D world coordinate system using the following steps:
i = 1;
x = P * [ X Y Z 1 ]';
u(i) = x(1)/x(3);
v(i) = x(2)/x(3);
where P is the projection matrix, [ X Y Z 1 ] is the 3D world coordinate system.
u & v represents the mapped 2D image points. I'm getting example values u=8.51e+02 and v=8.33e+02.
The question is how I can check what is the pixel intensity value corresponds to point (u,v)?

How to create a 3D matrix in MATLAB by rotating 2D matrix around its center column or center row?

I have a 2D MATLAB matrix, which is symmetric with respect to its center column. I want to rotate this matrix around its center column to produce a 3D matrix representing an object with a cylindrical symmetry.
The same thing I want to do with a different matrix, which is symmetric with respect to its center row. (This time I want to rotate it around its center row to produce the 3D matrix).
What I had in mind is to generalize to 3D the idea given in the link:
How to create a 2D image by rotating 1D vector of numbers around its center element?
But not knowing MATLAB well enough it is not a so straight forward task for me.
Can someone help please?
I just modified the accepted answer to 3D:
% generate symetric matrix
A = zeros(11,31);
A(4:8,10:22) = repmat([1:7 6:-1:1],[5 1]);
% symmetric x axis
n = floor(size(A,2)/2);
% A vector of distance (measured in pixels) from the center of vector V to each element of V
[r,y] = meshgrid([n:-1:0, 1:n],1:size(A,1));
% Now find the distance of each element of a square 2D matrix from it's centre. #(x,y)(sqrt(x.^2+y.^2)) is just the Euclidean distance function.
ri = sqrt( bsxfun( #(x,y)x.^2+y.^2,r,permute(r,[1 3 2]) ) );
yi = repmat(y,[1 1 size(A,2)]);
% Now use those distance matrices to interpole V
obj = interp2(r(:,1:n+1),y(:,1:n+1),A(:,1:n+1),ri,yi,'nearest');
obj(isnan(obj)) = 0;
% show
[xg,yg,zg] = meshgrid(1:size(obj,2),1:size(obj,1),1:size(obj,3));
scatter3(xg(:),yg(:),zg(:),10,obj(:)+1,'filled')
axis equal
UPDATE - if you don't want to use interp2 you can do:
obj = interp1(r(1,1:n+1).',A(:,1:n+1).',ri(1,:,:),'nearest');
obj = permute(obj,[4,3,2,1]);
obj(isnan(obj)) = 0;

How to calculate the distance from all points in a 3d matrix to a plane in matlab?

This question is from my last post,
But I will just focus on a particular part,
If I have a 3d matrix M, 256*256*124,and a plane defined by 3 points that intersect the 3d matrix, A,B,C, how to find the distance of all points in M to the plane defined by A,B,C
Here is the suggestion I got,
[X Y Z] = meshgrid(1:256,1:256,1:124)
A = [X(:)';Y(:)';Z(:)'];
n = cross([coor_A-coor_B],[coor_B-coor_C]); %The norm vector of the plane
d = norm(mean([coor_A,coor_B,coor_C]),2); %The distance from origin to the plane
According to Hesse normal form,
L = ((n*A)-d); %The distance from all points in M to the plane
But all the values in L are huge, which indicates that no points are found on the intersection plane.
Can anyone tell me what's wrong?
You missed one line on the Wikipedia page
where
So add this line
n0 = n / norm(n);
and change the final line to
L = ((n0*A)-d);

Want to filter horizontal normals in xyz vector file (matlab) Code working but not correct

I have a vector dataset (Nx3) with xyz positions. I calculated the normals for all the points in my dataset.
What I want to do is filter out all horizontal normals (that is, when the y points represent a vertical feature in my data).
I have this thus far but it's only filtering out points in the x-space. I'm confused if I should be in xz space or yz space to filter out horizontal normals? Suggestions?
%dimension Nx3 (N variable by scene used)
normals = 'normals.csv';
P1 = csvread(normals);
%dimension Nx3 (N variable by scene used)
pts = 'pts.csv';
P0 = csvread(pts);
threshold = .5
%calculate angle between normal vectors and rotated data
ang = atan2( P0(:,2) - P1(:,2), P0(:,1) - P1(:,1) )
%filter data
filter = abs(ang) < threshold;
newNormals = P1(~filter, :);
newPts = P0(~filter,:);
dlmwrite('newpts.txt', finalPts, 'delimiter',' ')
Assuming that the y-axis points upward, it seems like you are only checking the projection of your vector on the xy-plane. If you are trying to filter all vertical vectors, then you should do the exact same check on the yz-plane as well.
% xy-projection
x_filter = abs(atan2(P0(:,2)-P1(:,2),P0(:,1)-P1(:,1))<threshold;
% yz-projection
z_filter = abs(atan2(P0(:,2)-P1(:,2),P0(:,3)-P1(:,3))<threshold;
% xy-projection and yz-projections are both vertical
filter = x_filter & z_filter;
But another check would be that diff(x) and diff(z) are both zero.
threshold = 1e-3;
% what is relative difference between vector's length and diff(y)?
filter = abs(1-norm(P0-P1)./(P0(:,2)-P1(:,2)))<threshold;
then use your filter as previously.
newNormals = P1(~filter, :);
newPts = P0(~filter,:);

Test if points lie on a 2D fitted plane in 4D

Suppose I have 3+ coplanar but not collinear points in R^4. To find the 2D plane (not hyperplane) in which they all lie, I used the following plane fit algorithm from MatlabCentral:
function [n,V,p] = affine_fit(X)
% Computes the plane that fits best (least square of the normal distance
% to the plane) a set of sample points.
% INPUTS:
% X: a N by 3 matrix where each line is a sample point
%OUTPUTS:
%n : a unit (column) vector normal to the plane
%V : a 3 by 2 matrix. The columns of V form an orthonormal basis of the plane
%p : a point belonging to the plane
%NB: this code actually works in any dimension (2,3,4,...)
%Author: Adrien Leygue
%Date: August 30 2013
% the mean of the samples belongs to the plane
p = mean(X,1);
% The samples are reduced:
R = bsxfun(#minus,X,p);
% Computation of the principal directions of the samples cloud
[V,D] = eig(R'*R);
% Extract the output from the eigenvectors
n = V(:,1);
V = V(:,2:end);
end
I employed the algorithm in a higher dimension than specified, so X is a 4x4 matrix which holds 4 points in 4 coordinate dimensions. The generated output is something like this.
[n,V,p] = affine_fit(X);
n = -0.0252
-0.0112
0.9151
-0.4024
V = 0.9129 -0.3475 0.2126
0.3216 0.2954 -0.8995
0.1249 0.3532 0.1493
0.2180 0.8168 0.3512
p = -0.9125 1.0526 0.2325 -0.0621
What I want to do now is find out if other points of my choosing are part of the plane, too. I'm sure it's fairly easy given the information above, yet at this point I only know that I need two linear equations to describe a 2D plane in 4D or parametric equations of two variables. I can set them up in theory, but writing up the code has been problematic. Perhaps there is a more straightforward way to test this in matlab?
You can use the Matlab function pca (see for example here). For example, you can determine the basis of your plane, the normal vectors to your plane and a point m on the plane as follows:
coeff = pca(X);
basis = coeff(:,1:2);
normals = coeff(:,3:4);
m = mean(X);
To check if a point p lies in this plane, it suffices to verify that m-p is orthogonal (dot product equal to zero) to the normal vectors onto the plane using dot.