Related
I am very sorry for asking a question that is probably very easy if you know how to solve it, and where many versions of the same question has been asked before. However, I am creating a new post since I have not found an answer to this specific question.
Basically, I have a 200cm x 200cm square that I am recording with a camera above it. However, the camera distorts the square slightly, see example here.. I am wondering how I go from transforming the x,y coordinates in the camera to real-life x,y coordinates (e.g., between 0-200 cm for each side). I understand that I probably need to apply some kind of transformation matrix, but I do not know which one, nor how to determine the transformation matrix. I haven't done any serious linear-algebra in a long time, so I appreciate any pointers for what to read up on, or how to get it done. I am working in python, so if there is some ready code for doing the transformation that would also be useful to know.
Thanks a lot!
I will show this using python and numpy.
import numpy as np
First, you have to understand the projection model
def apply_homography(H, p1):
p = H # p1.T
return (p[:2] / p[2]).T
With some algebraic manipulation you can determine the points at the plane z=1 that produced the given points.
def revert_homography(H, p2):
Hb = np.linalg.inv(H)
# 1 figure out which z coordinate should be added to p2
# order to get z=1 for p1
z = 1/(Hb[2,2] + (Hb[2,0] * p2[:,0] + Hb[2,1]*p2[:,1]))
p2 = np.hstack([p2[:,:2] * z[:,None], z[:, None]])
return p2 # Hb.T
The projection is not invertible, but under the complanarity assumption it may be inverted successfully.
Now, let's see how to determine the H matrix from the given points (assuming they are coplanar).
If you have the four corners in order in order you can simply specify the (x,y) coordinates of the cornder, then you can use the projection equations to determine the homography matrix like here, or here.
This requires at least 5 points to be determined as there is 9 coefficients, but we can fix one element of the matrix and make it an inhomogeneous equation.
def find_homography(p1, p2):
A = np.zeros((8, 2*len(p1)))
# x2'*(H[2,0]*x1+H[2,1]*x2)
A[6,0::2] = p1[:,0] * p2[:,0]
A[7,0::2] = p1[:,1] * p2[:,0]
# - (H[0,0]*x1+H[0,1]*y1+H[0,2])
A[0,0::2] = -p1[:,0]
A[1,0::2] = -p1[:,1]
A[2,0::2] = -1
# y2'*(H[2,0]*x1+H[2,1]*x2)
A[6,1::2] = p1[:,0] * p2[:,1]
A[7,1::2] = p1[:,1] * p2[:,1]
# - (H[1,0]*x1+H[1,1]*y1+H[1,2])
A[3,1::2] = -p1[:,0]
A[4,1::2] = -p1[:,1]
A[5,1::2] = -1
# assuming H[2,2] = 1 we can pass its coefficient
# to the independent term making an inhomogeneous
# equation
b = np.zeros(2*len(p2))
b[0::2] = -p2[:,0]
b[1::2] = -p2[:,1]
h = np.ones(9)
h[:8] = np.linalg.lstsq(A.T, b, rcond=None)[0]
return h.reshape(3,3)
Here a complete usage example. I pick a random H and transform four random points, this is what you have, I show how to find the transformation matrix H_. Next I create a test set of points, and I show how to find the world coordinates from the image coordinates.
# Pick a random Homography
H = np.random.rand(3,3)
H[2,2] = 1
# Pick a set of random points
p1 = np.random.randn(4, 3);
p1[:,2] = 1;
# The coordinates of the points in the image
p2 = apply_homography(H, p1)
# testing
# Create a set of random points
p_test = np.random.randn(20, 3)
p_test[:,2] = 1;
p_test2 = apply_homography(H, p_test)
# Now using only the corners find the homography
# Find a homography transform
H_ = find_homography(p1, p2)
assert np.allclose(H, H_)
# Predict the plane points for the test points
p_test_predicted = revert_homography(H_, p_test2)
assert np.allclose(p_test_predicted, p_test)
I have coordinates corresponding to a set of 2D contours, each corresponding to different heights. These contours do not draw out a perfect ellipsoid in 3D, and instead what I would like to do is to find the best fitting ellipsoid. I do not have any knowledge on the origin of this ellipsoid.
My first thought was to incorporate some type of least squares algorithm, where I find the ellipsoid parameters that minimize the distance between points. I imagine this would be quite expensive and not too far from a brute force approach. I am convinced there is a more elegant and efficient way of doing this. If there is an existing library that handles this (preferably in Python) that would be even better.
I have already seen a related question (Fitting an ellipsoid to 3D data points), but figured I would ask again as it has been over a decade since that post.
So you have a set of (x,y) values for each contour, which describe a portion of an ellipse (blue dots below).
The best fit ellipse is described by the general equation
A x^2 + B y^2 + 2C x y + 2D x + 2E y = 1
and once the coefficients (A,B,C,D,E) are found, the ellipse of fully described. See below in how to find the the curve coordinates (x,y) from the coefficients and a parameter t=0 .. 1.
To find the coefficients of the ellipse, form 5 vectors, each a column of a n×5 matrix Q
for i = 1 to n
Q(i,1) = x(i)^2
Q(i,2) = y(i)^2
Q(i,3) = 2*x(i)*y(i)
Q(i,4) = 2*x(i)
Q(i,5) = 2*y(i)
next i
and a vector K filled with 1 for the right-hand side
for i = 1 to n
K(i) = 1.0
next i
Find the coefficients using a least-squares fit with some linear algebra
[A,B,C,D,E] = inv(tr(Q)*Q)*tr(Q)*K
where tr(Q) is the transpose of Q and * is matrix/vector product
Now we need to extract the geometric properties of the ellipse from the coefficient. I want to have a the semi-major axis, b the semi-minor axis, φ the rotation angle, xc the x-axis center, yc the y-axis center.
xc = -(B*D-C*E)/(A*B-(C^2))
yc = -(A*E-C*D)/(A*B-(C^2))
φ = atan( 2*C/(A-B) )/2
a = SQRT(2*(A*(B+E^2)+B*D^2-C*(C+2*D*E))/((A*B-C^2)*(A+B-SQRT((A-B)^2+4*C^2))))
b = SQRT(2*(A*(B+E^2)+B*D^2-C*(C+2*D*E))/((A*B-C^2)*(A+B+SQRT((A-B)^2+4*C^2))))
Finally to plot the ellipse you need to generate a set of points (x,y) from the curve parameter t=0..1 using the above 5 coefficients.
Generate the centered aligned coordinates (u,v) with
u = a*cos(2*π*t)
v = b*sin(2*π*t)
Generate the centered rotated coordinates (x',y') with
x' = u*cos(φ) - v*sin(φ)
y' = u*sin(φ) + v*cos(φ)
Generate the ellipse coordinates (x,y) with
x = x' + xc
y = y' + yc
The result is observed above in the first picture.
Now for the total solution, each 2D slice would have its own ellipse. But all the slices would not generate an ellipsoid this way.
Extending the above into 3D coordinates (x,y,z) is doable, but the math is quite involved and I feel [SO] is not a good place to develop such an algorithm. You can hack it together, by finding the average center for each slice (weighted by the ellipse area π*a*b). Additionally, the rotation angle should be the same for all contours, and so another averaging is needed. Finally, the major and minor axis values would fall on an elliptical curve along the z-axis and it would require another least-fit solution. This one is driven by the equation
(x/a)^2 + (y/b)^2 + (z/c)^2 = 1
but rather in the aligned coordinates (u,v,w)
(u/a)^2 + (v/b)^2 + (w/c)^2 = 1
From what I have read, using FFTW.jl / AbstractFFTs.jl's fft(A) when A is a 2D array should perform fft in 2D, not column-wise. Any idea why I am seeing only column-wise diffusion when (I think) I'm adding scaled second spatial derivative to u(t,x), as if using explicit solver for time?
Thank you! I am quite new to this.
code and heatmap screenshot
using Random
using FFTW
using Plots
gr()
N = (100,100)
# initialize with gaussian noise
u = randn(Float16, (N[1], N[2])).*0.4.+0.4
# include square of high concentration to observe diffusion clearly
u[40:50,40:50] .= 3
N = size(x)
L = 100
k1 = fftfreq(51)
k2 = fftfreq(51)
lap_mat = -(k1.^2 + k2.^2)
function lap_fft(x)
lapF = rfft(x)
lap = irfft(lap_mat.*lapF, 100)
return lap
end
# ode stepper or Implicit-Explicit solver
for i in 1:100000
u+=lap_fft(u)*0.0001
end
# plot state
heatmap(u)
Just because you are performing a real FFT, doesn't mean that you can real inverse fft the result. rfft goes from R -> C. What you can however do is the following:
function lap_fft(x)
lapF = complex(zeros(100,100)); # only upper half filled
lapF[1:51,1:100] = rfft(x) .* lap_mat; # R -> C
return abs.(ifft(lapF)); # C -> R
end
Real FFT to complex frequency domain (only upper half filled because of data redundancy), multiply your filter in frequency domain, inverse FFT into complex image domain and obtain the magnitude abs.(), real part real.() etc.
But honestly, why the hassle with the real fft?
using Random
using FFTW
using Plots
gr()
N = (100,100)
# initialize with gaussian noise
u = randn(Float16, (N[1], N[2])).*0.4.+0.4;
# include square of high concentration to observe diffusion clearly
u[40:50,40:50] .= 3;
N = size(u);
L = 100;
k1 = fftfreq(100);
k2 = fftfreq(100);
tmp = -(k1.^2 + k2.^2);
lap_mat = sqrt.(tmp.*reshape(tmp,1,100));
function lap_fft(x)
return abs.(ifftshift(ifft(fftshift(ifftshift(fft(fftshift(x))).*lap_mat))));
end
# ode stepper or Implicit-Explicit solver
for i in 1:100000
u+=lap_fft(u)*0.001;
end
# plot state
heatmap(u)
I want to plot a simple object in scilab (3d). To understand the way scilab works in that regard, I wrote the following example:
xx = [[2;2;1;3;],[2;2;3;3],[2;2;3;1],[2;2;1;1],[1;3;3;1],[3;3;3;3],[3;3;1;1],[1;1;1;1],[1;2;2;3],[1;1;2;2],[3;2;2;3],[3;2;2;1]]
yy = [[2;2;1;1;],[2;2;1;3],[2;2;3;3],[2;2;3;1],[1;1;1;1],[1;3;3;1],[3;3;3;3],[3;1;1;3],[1;2;2;1],[1;3;2;2],[1;2;2;3],[3;2;2;3]]
zz = [[0;0;1;1;],[0;0;1;1],[0;0;1;1],[0;0;1;1],[1;1;2;2],[1;1;2;2],[1;2;2;1],[1;1;2;2],[2;3;3;2],[2;2;3;3],[2;3;3;2],[2;3;3;2]]
col = ones(12,1)*3
plot3d(xx,yy,list(zz,col))
//h = get("hdl")
//h.hiddencolor = -1 // backside and frontside same color
with the following result:
While the structure is absolutley fine, the coloring on 2 faces is inside out. I tried to draw the points of the affected faces in different ways counterclockwise/clockwise, different starting points, etc.. But the faces seem to keep oriented inwards the structure. I found a workaround by setting the backside of the faces equal to the frontside (the 2 commented lines in the code) but I want to understand how scilab determines the orientation of the faces for later work. Any clues?
EDIT:
So i tried PTRK's suggestions. While his provided Matrices are definitely different:
The result is still the same. Even the output of the provided Testscript is different:
Perhaps thats some kind of version/system thing? I'm using Scilab 6.0.0 on Windows 10.
Let a surface defined by 3 nodes: [P1,P2,P3]. Then you must cycle clockwise trough theses nodes to have the right orientation of inside and outside. Here is a drawing explaining it:
3 of your polygones are defined conterclockwise, thoses with y=1, y=3 and x = 1. When drawing 4 points polygones, to switch the rotation from clockwise to counterclockwise, just swap the 2nd and 4th nodes or 1st and 3rd.
Thus you must set your points as:
xx = [[2;2;1;3;],[2;2;3;3],[2;2;3;1],[2;2;1;1],[1;1;3;3],[3;3;3;3],[3;3;1;1],[1;1;1;1],[1;2;2;3],[1;1;2;2],[3;2;2;3],[3;2;2;1]]
yy = [[2;2;1;1;],[2;2;1;3],[2;2;3;3],[2;2;3;1],[1;1;1;1],[1;1;3;3],[3;3;3;3],[3;3;1;1],[1;2;2;1],[1;3;2;2],[1;2;2;3],[3;2;2;3]]
zz = [[0;0;1;1;],[0;0;1;1],[0;0;1;1],[0;0;1;1],[1;2;2;1],[1;2;2;1],[1;2;2;1],[1;2;2;1],[2;3;3;2],[2;2;3;3],[2;3;3;2],[2;3;3;2]]
This will give the desired output :
Scilab 6.0.0 bug
In this version, if your surfaces are parallel to the cartesian axes, then Scilab will direct it along the axis, no matter how you defined it. Thus your problem. A workaround could be to offset one of the coordinate by a small delta, which must be not too small, as shown in below example.
Regarding your problem, if we want to keep the geometry of your object, we could tilt it with a tiny angle: using rotation matrix, if the computational cost induced by the rotation of all the coordinates doesn't bother you. Here's your script with the tilted object
clc
clear
xdel(winsid())
xx = [[2;2;1;3;],[2;2;3;3],[2;2;3;1],[2;2;1;1],[1;1;3;3],[3;3;3;3],[3;3;1;1],[1;1;1;1],[1;2;2;3],[1;1;2;2],[3;2;2;3],[3;2;2;1]]
yy = [[2;2;1;1;],[2;2;1;3],[2;2;3;3],[2;2;3;1],[1;1;1;1],[1;1;3;3],[3;3;3;3],[3;3;1;1],[1;2;2;1],[1;3;2;2],[1;2;2;3],[3;2;2;3]]
zz = [[0;0;1;1;],[0;0;1;1],[0;0;1;1],[0;0;1;1],[1;2;2;1],[1;2;2;1],[1;2;2;1],[1;2;2;1],[2;3;3;2],[2;2;3;3],[2;3;3;2],[2;3;3;2]]
col = ones(12,1)*3
figure(1)
set(gcf(),'background',-2)
subplot(2,1,1)
plot3d(xx,yy,list(zz,col))
title('Object with surfaces orthogonal to cartesian axis')
subplot(2,1,2)
// t is angle in radian showing the tilt
t = %pi/10000
c = cos(t)
s = sin(t)
rot = [1,0,0;0,c,-s;0,s,c]*[c,0,s;0,1,0;-s,0,c]*[c,-s,0;s,c,0;0,0,1]
for i=1:size(xx,1)
for j = 1:size(xx,2)
xyz=(rot*[xx(i,j);yy(i,j);zz(i,j)])
x(i,j)=xyz(1)
y(i,j)=xyz(2)
z(i,j)=xyz(3)
end
end
plot3d(x,y,list(z,col))
title('Object with surfaces tildted by an angle of '+string(t)+' rad')
Script showing 2 surfaces defined by the same nodes but in opposite order.
clc
clear
xdel(winsid())
figure(1)
set(gcf(),'background',-2)
cr=color('red') // color of the outside surface
P1 = [0,0,0] //
P2 = [0,1,0]
P3 = [1,0,0]
F1 = [P1;P2;P3] // defining surface clockwise
F2 = [P1;P3;P2] // counterclockwise
subplot(2,2,1)
plot3d(F1(:,1),F1(:,2),list(F1(:,3),cr*ones(F1(:,3))))
xstring(F1(:,1),F1(:,2),['P1','P2','P3'])
title('surface is [P1,P2,P3] with z_P3=0')
set(gca(),'data_bounds',[0,1,0,1,-1,1])
subplot(2,2,2)
plot3d(F2(:,1),F2(:,2),list(F2(:,3),cr*ones(F2(:,3))))
xstring(F2(:,1),F2(:,2),['P1','P3','P2'])
title('surface is [P1,P3,P2] with z_P3=0, broken with Scilab 6.0.0')
set(gca(),'data_bounds',[0,1,0,1,-1,1])
subplot(2,2,3)
plot3d(F2(:,1),F2(:,2),list(F2(:,3)+[0;0;10^-7],cr*ones(F2(:,3))))
xstring(F2(:,1),F2(:,2),['P1','P3','P2'])
title('surface is [P1,P3,P2] with |z_P3| < 10^-8')
set(gca(),'data_bounds',[0,1,0,1,-1,1])
subplot(2,2,4)
plot3d(F2(:,1),F2(:,2),list(F2(:,3)+[0;0;10^-8],cr*ones(F2(:,3))))
xstring(F2(:,1),F2(:,2),['P1','P3','P2'])
title('surface is [P1,P3,P2] with |z_P3| = 10^-8, broken in 6.0.0')
set(gca(),'data_bounds',[0,1,0,1,-1,1])
Scilab 5.5.1
Scilab 6.0.0
I have been wracking my brain to come up with a solution to this problem.
I have a lookup table that returns height values for various points (x,z) on the grid. For instance I can calculate the height at A, B, C and D in Figure 1. However, I am looking for a way to interpolate the height at P (which has a known (x,z)). The lookup table only has values at the grid intervals, and P lies between these intervals. I am trying to calculate values s and t such that:
A'(s) = A + s(C-A)
B'(t) = B + t(P-B)
I would then use the these two equations to find the intersection point of B'(t) with A'(s) to find a point X on the line A-C. With this I can calculate the height at this point X and with that the height at point P.
My issue lies in calculating the values for s and t.
Any help would be greatly appreciated.
Try also bilinear interpolation or bicubic interpolation.
Depending on if you want to interpolate between ABC or ABCD the algorithm will change.
To interpolate between ABC (which I assume is what you want to do since you draw the diagonal) you will need to find the barycentric coordinates of P relative to ABC x and y positions then apply the barycentric coordinate to the height (z is assumed here) component of those triangles.
What about going this way: find u and v so that
P = B + u(A-B) + v(C-B)
If you write this out, you'll see that this is a 2x2 linear system with unknowns u and v, so I guess you know how to go on from there.
Oh, and once you have u and v you use the same exact formula as above for the height, only this time A,B,C,P will be the heights at these points.
Considering points value are available at four corners of a square of unit length, interpolated value at any point(x,y) inside the square is given by:
f(x,y) = [ (1-y)f(0,0) + yf(0,1) ](1-x) + [ (1-y)f(1,0)+y(f(1,1)) ]x
If square has length other than 1,say L then f(x,y) is given by:
f(x,y) = [ (L-y)f(0,0) + yf(0,L) ](L-x)/L^2 + [ (L-y)f(L,0)+y(f(L,L)) ]x/L^2
image
Here's an explicit example based on shape functions.
Consider the functions:
u1(x,z) = (x-x_b)/(x_c-x_b)
One has u1(x_b,z_b) = u1(x_a,z_a) = 0 (because x_a = x_b) and u1(x_c,z_c) = u1(x_d,z_d) = 1
u2(x,z) = 1 - u1(x,z)
Now we have u2(x_b,z_b) = u2(x_a,z_a) = 1 and u2(x_c,z_c) = u2(x_d,z_d) = 0
v1(x,z) = (z-z_b)/(z_a-z_b)
This function satisfies v1(x_a,z_a) = v1(x_d,z_d) = 1 and v1(x_b,z_b) = v1(x_c,z_c) = 0
v2(x,z) = 1 - v1(x,z)
We have v2(x_a,z_a) = v2(x_d,z_d) = 0 and v2(x_b,z_b) = v2(x_c,z_c) = 1
Now let's build new functions as follows:
S_D(x,z) = u1(x,z) * v1(x,z)
We get S_D(x_d, z_d) = 1 and S_D(x_a,z_a) = S_D(x_b,z_b) = S_D(x_c,z_c) = 0
S_C(x,z) = u1(x,z) * v2(x,z)
We get S_C(x_c, z_c) = 1 and S_C(x_a,z_a) = S_C(x_b,z_b) = S_C(x_d,z_d) = 0
S_A(x,z) = u2(x,z) * v1(x,z)
We get S_A(x_a, z_a) = 1 and S_A(x_b,z_b) = S_A(x_c,z_c) = S_A(x_d,z_d) = 0
S_B(x,z) = u2(x,z) * v2(x,z)
We get S_B(x_b, z_b) = 1 and S_B(x_a,z_a) = S_B(x_c,z_c) = S_B(x_d,z_d) = 0
Now define your interpolating function as
H(x,z) = h_a * S_A(x,z) + h_b * S_B(x,z) + h_c * S_C(x,z) + h_d * S_D(x,z),
where h_a is the heigh at point A, h_b is the height at point B, and so on.
You can easily verify that H is indeed an interpolating function:
H(x_a,z_a) = h_a, H(x_b,z_b) = h_b, H(x_c,z_c) = h_c and H(x_d,z_d) = h_d.
Now, in order to approximate the height at P, all you need to do is evaluate H at this point:
h_p = H(x_p, z_p)
The functions S are normally referred to as "shape functions". There's one such function for each node you want your interpolated value to depend on, and in this case they all satisfy Kronecker's delta property (they take the value one at one node and zero at all other nodes).
There are many ways to build shape functions for a given set of nodes. If I remember correctly, the construction of 2D shape functions by multiplication of 1D shape functions (as we've done in this case) is called "tensor product of functions" (easy in this case because the grid is rectangular). We have ended up with four functions (one per node), all of them linear combinations of {1, x, z, xz}.
If you want to use only three points for your interpolation, then you should be able to easily build three shape functions as linear combinations of {1, x, z} only, but you will loose a 25% of the height information provided by the grid and your interpolant will not be smooth inside the rectangle when h_b != h_d.