How can I implement OpenCV's perspectiveTransform in Python - python

I'm mapping 2d points from a source rectangle to a destination rectangle. I'd like to be able to do this without requiring OpenCV. I've already got getPerspectiveTransform implemented but I'm having trouble finding a source for the math required for perspectiveTransform. Everything I've found is either about using cv2.perspectiveTransform or how to implement cv2.getPerspectiveTransform.
So far I have this and it works:
import numpy as np
import cv2
def getPerspectiveTransform(sourcePoints, destinationPoints):
"""
Calculates the 3x3 matrix to transform the four source points to the four destination points
Comment copied from OpenCV:
/* Calculates coefficients of perspective transformation
* which maps soruce (xi,yi) to destination (ui,vi), (i=1,2,3,4):
*
* c00*xi + c01*yi + c02
* ui = ---------------------
* c20*xi + c21*yi + c22
*
* c10*xi + c11*yi + c12
* vi = ---------------------
* c20*xi + c21*yi + c22
*
* Coefficients are calculated by solving linear system:
* a x b
* / x0 y0 1 0 0 0 -x0*u0 -y0*u0 \ /c00\ /u0\
* | x1 y1 1 0 0 0 -x1*u1 -y1*u1 | |c01| |u1|
* | x2 y2 1 0 0 0 -x2*u2 -y2*u2 | |c02| |u2|
* | x3 y3 1 0 0 0 -x3*u3 -y3*u3 |.|c10|=|u3|,
* | 0 0 0 x0 y0 1 -x0*v0 -y0*v0 | |c11| |v0|
* | 0 0 0 x1 y1 1 -x1*v1 -y1*v1 | |c12| |v1|
* | 0 0 0 x2 y2 1 -x2*v2 -y2*v2 | |c20| |v2|
* \ 0 0 0 x3 y3 1 -x3*v3 -y3*v3 / \c21/ \v3/
*
* where:
* cij - matrix coefficients, c22 = 1
*/
"""
if sourcePoints.shape != (4,2) or destinationPoints.shape != (4,2):
raise ValueError("There must be four source points and four destination points")
a = np.zeros((8, 8))
b = np.zeros((8))
for i in range(4):
a[i][0] = a[i+4][3] = sourcePoints[i][0]
a[i][1] = a[i+4][4] = sourcePoints[i][1]
a[i][2] = a[i+4][5] = 1
a[i][3] = a[i][4] = a[i][5] = 0
a[i+4][0] = a[i+4][1] = a[i+4][2] = 0
a[i][6] = -sourcePoints[i][0]*destinationPoints[i][0]
a[i][7] = -sourcePoints[i][1]*destinationPoints[i][0]
a[i+4][6] = -sourcePoints[i][0]*destinationPoints[i][1]
a[i+4][7] = -sourcePoints[i][1]*destinationPoints[i][1]
b[i] = destinationPoints[i][0]
b[i+4] = destinationPoints[i][1]
x = np.linalg.solve(a, b)
x.resize((9,), refcheck=False)
x[8] = 1 # Set c22 to 1 as indicated in comment above
return x.reshape((3,3))
if __name__ == "__main__":
# Create a transform to change table coordinates in inches to projector coordinates
sourceCorners = np.array([[0.0, 0.0],[120.0,0.0],[120.0,63.0],[0.0,63.0]])
destinationCorners = np.array([[4095.0,0],[3071,4095],[1024,4095],[0,0]])
perspectiveTransform = getPerspectiveTransform(sourceCorners, destinationCorners)
points = np.array([0,0,120,63,120,0,0,63,120,63,0,0,120,0], dtype=float).reshape(-1,1,2)
perspectivePoints = cv2.perspectiveTransform(points, perspectiveTransform)
print(perspectivePoints)
Result:
[[[4095. 0.]]
[[1024. 4095.]]
[[3071. 4095.]]
[[ 0. 0.]]
[[1024. 4095.]]
[[4095. 0.]]
[[3071. 4095.]]]
Graphs:
The OpenCV C source for perspectiveTransform has cryptic variable names, no comments, and is pretty unreadable.
Can anyone point me to a good source for how to implement perspectiveTransform?

Basically the perspective transformation is
[ ] [source_x] [target_x * w]
[perspectivesMatrix] x [source_y] = [target_y * w]
[ ] [ 1 ] [ w ]
where perspectiveMatrix is a 3x3 matrix of the form
[c00 c01 c02]
[c10 c11 c12]
[c20 c21 c22]
Since you already have perspectiveMatrix, all we need to do is replicate the previous formula.
def perspectiveTransform(perspectiveMatrix, sourcePoints):
'''
perspectiveMatrix as above
sourcePoints has shape (n,2)
'''
# first we extend source points by a column of 1
# augment has shape (n,1)
augment = np.ones((sourcePoints.shape[0],1))
# projective_corners is a 3xn matrix with last row all 1
# note that we transpose the concatenation
projective_corners = np.concatenate( (sourceCorners, augment), axis=1).T
# projective_points has shape 3xn
projective_points = perspectiveMatrix.dot(projective_corners)
# obtain the target_points by dividing the projective_points
# by its last row (where it is non-zero)
# target_points has shape (3,n).
target_points = np.true_divide(projective_points, projective_points[-1])
# so we want return points in row form
return target_points[:2].T
if __name__=='__main__':
# Create a transform to change table coordinates in inches to projector coordinates
sourceCorners = np.array([[0.0, 0.0],[120.0,0.0],[120.0,63.0],[0.0,63.0]],dtype=np.float32)
destinationCorners = np.array([[4095.0,0],[3071,4095],[1024,4095],[0,0]],dtype=np.float32)
perspectiveMatrix = getPerspectiveTransform(sourceCorners, destinationCorners)
# test points
points = np.array([0,0,120,63,120,0,0,63,120,63,0,0,120,0], dtype=float)
# perspectiveTransform by cv2
cv_perspectivePoints = cv2.perspectiveTransform(points.reshape(-1,1,2), perspectiveMatrix)
# our implementation of perspectiveTransform
perspectivePoints = perspectiveTransform(perspectiveMatrix, points)
# should yields something close to 0.0
print(cv_perspectivePoints.reshape(-1,2) - perspectivePoints)

Related

How to use numy linalg lstsq to fit two datasets with same slope but different intercept?

I am trying to do weighted least-square fitting, and came across numpy.linalg.lstsq. I need to fit the weighted least squares. So, the following works:
# Generate some synthetic data from the model.
N = 50
x = np.sort(10 * np.random.rand(N))
yerr = 0.1 + 0.5 * np.random.rand(N)
y = 10.0 * x + 15
y += yerr * np.random.randn(N)
#do the fitting
err = 1/yerr**2
W = np.sqrt(np.diag(err))
x = x.flatten()
y = y.flatten()
A = np.vstack([x, np.ones(len(x))]).T
xw = np.dot(W,A)
yw = np.dot(W,y)
m, b = np.linalg.lstsq(xw, yw)[0]
which gives me the best-fit slope and intercept. Now, suppose I have two datasets with same slope but different intercepts? How would I do a joint fit such that I get best-fit slope plus two intercepts. I still need to have the weighted least square version. For an unweighted case, I found that the following works:
(m,b1,b2),_,_,_ = np.linalg.lstsq(np.stack([np.concatenate((x1,x2)),
np.concatenate([np.ones(len(x1)),np.zeros(len(x2))]),
np.concatenate([np.zeros(len(x1)),np.ones(len(x2))])]).T,
np.concatenate((y1,y2)))
First of all I rewrite your first approach as it can be written clearer in my opinion like this
weights = 1 / yerr
m, b = np.linalg.lstsq(np.c_[weights * x, weights], weights * y, rcond=None)[0]
To fit 2 datasets you can stack 2 arrays but make 0 some elements of matrix.
np.random.seed(12)
N = 3
x = np.sort(10 * np.random.rand(N))
yerr = 0.1 + 0.5 * np.random.rand(N)
y = 10.0 * x + 15
y += yerr * np.random.randn(N)
M = 2
x1 = np.sort(10 * np.random.rand(M))
yerr1 = 0.1 * 0.5 * np.random.rand(M)
y1 = 10.0 * x1 + 25
y1 += yerr1 * np.random.randn(M)
#do the fitting
weights = 1 / yerr
weights1 = 1 / yerr1
first_column = np.r_[weights * x, weights1 * x1]
second_column = np.r_[weights, [0] * x1.size]
third_column = np.r_[[0] * x.size, weights1]
a = np.c_[first_column, second_column, third_column]
print(a)
# [[ 4.20211437 2.72576342 0. ]
# [ 24.54293941 9.32075195 0. ]
# [ 13.22997409 1.78771428 0. ]
# [126.37829241 0. 26.03711851]
# [686.96961895 0. 124.44253391]]
c = np.r_[weights * y, weights1 * y1]
print(c)
# [ 83.66073785 383.70595203 159.12058215 1914.59065915 9981.85549321]
m, b1, b2 = np.linalg.lstsq(a, c, rcond=None)[0]
print(m, b1, b2)
# 10.012202998026055 14.841412336510793 24.941219918240172
EDIT
If you want different slopes and one intercept you can do it this way. Probably it is better to grasp the general idea on the one slope 2 intercepts case. Take a look to array a: you construct it from weights as well as c so now it is unweighted problem. You try to find such vector = [slope, intercept1, intercept2] that a # vector = c (as much as possible by minimizing sum of squares of differences). By putting zeros in a we make it separable: upper part of matrix a vary slope and intercept1 and down part of a vary slope and intercept2. Similar to 2 slopes case with vector = [slope1, slope2, intercept].
first_column = np.r_[weights * x, [0] * x1.size]
second_column = np.r_[[0] * x.size, weights1 * x1]
third_column = np.r_[weights, weights1]

On the division and multiplication of matrices in python

Here the question with details and I think it's clearer,
suppose I have a matrix h of size 4 x 4 , and a vector of x of size 4 x 1, if we have y is the output of multiplication between h and x which means y = h * x; whose size is 1 x 4. So when I multiply again the inverse of every column in h by vector y, I should be able to get a vector equivalent of vector x which means $x = h^{-1} * y $. But unfortunately, I can't get that in python.
for example, let's first do that in MATLAB:
clear all
clc
h = (randn(4,4) + 1j*randn(4,4)); %any matrix of 4 x 4
x = [1 + 1j ; 0; 0 ; 0]; % a vector of 4 x 1
y = h * x ; % y is the output of multiplication
x2 = [];
for ii = 1 : 4
x1 = pinv(h(:,ii))*y; %multiply every column of h^(-1) with y
x2 = [x2 x1]; % the output
end
in that case, the output x2 is as expected, a vector 1 x 4 as below:
x2 =
1.0000 + 1.0000i 0.7249 + 0.5054i -0.0202 + 0.0104i 0.2429 + 0.0482i
In MATLAB, that's ok.
Now let's do that in python:
import numpy as np
h = np.random.randn(4,4) + 1j*np.random.randn(4,4)
x = [[1+1j],[0+0j],[0+0j],[0+0j]]
y = h.dot(x)
x2 = []
for ii in range(4):
x1 = np.divide(y, h[:,ii])
x2.append(x1)
print(x2)
Although x2 is supposed to be a vector of dimension 1 x 4 similar as in output of above MATLAB code, but in that case, I get x2 a matrix of size 4 x 4 !!
please any help.
There are two issues here:
np.divide() is for element-wise division, you may be looking for np.linalg.pinv() instead.
MATLAB is col major (FORTRAN-style), while NumPy is row major (C-style) so getting a list as a NumPy array will get you to a shape (n,) with n the length of the list and not an object of size (1, n) as MATLAB would.
The Python code equivalent (sort of, I'll do preallocation) to your MATLAB one, would be:
import numpy as np
h = np.random.randn(4, 4) + 1j * np.random.randn(4, 4)
x = np.array([[1 + 1j], [0 + 0j], [0 + 0j], [0 + 0j]])
# y = h.dot(x) <-- now NumPy supports also `#` in place of `np.dot()`
y = h # x
x2 = np.zeros((1, 4), dtype=np.complex)
for i in range(4):
x2[0, i] = np.linalg.pinv(h[:, i:i + 1]) # y
as you can see, the shape of the output is enforced right away.

I need to concatenate two vectors same dimension but different shape

I have a vector y of size 4 x 1 , and another vector y2 of size 4 x 1 too, I concatenated the vectors y and real and imaginary parts of y2 and got two different vectors with same dimension but different shape ! . . I don't know what's the difference between both them.
For example, here is the first code:
import numpy as np
h = np.random.randn(4, 4) + 1j * np.random.randn(4, 4)
x = np.array([[1 + 1j], [0 + 0j], [0 + 0j], [0 + 0j]])
y = h # x
n = 3
y2 = np.zeros((1, 4), dtype=np.complex)
for ii in range(n):
y2[: , ii] = np.linalg.pinv(h[: , ii].reshape(-1,1)).dot(y)
y_con = np.concatenate((np.real(y2),np.imag(y2)))
y_m = np.absolute(y)
Y3 = np.concatenate([y_con.reshape(-1,1), y_m])
So, in this case, the output Y3 is a vector of dimension 12 x 1 when I check its shape, it's (12,1)
now, let's run the code in another way:
import numpy as np
h = np.random.randn(4, 4) + 1j * np.random.randn(4, 4)
x = np.array([[1 + 1j], [0 + 0j], [0 + 0j], [0 + 0j]])
y = h # x
y2 = np.linalg.pinv(h).dot(y)
y_con = np.concatenate((np.real(y2),np.imag(y2)))
y_m = np.absolute(y)
Y3 = np.concatenate([y_con, y_m])
In this case, Y3 is a vector of dimension 12, when I check its shape, it's (12,)
First I don't know what's the difference between the two vectors in their shape? .. and what I want is to use the first code to get a code of dimension of (12,) instead of (12,1)? how can I do that ?
You created a "column" vector which is 2-dimensional. Just add .flatten() to the end of the last line in your first code to make a 1-dimensional "row" vector.

I keep getting the error "index 2 is out of bounds for axis 0 with size 2" in my loop

I'm basically trying to sum a gradient here, see the screenshots I have attached for a better idea. Rho is an nx1 input vector, the screenshot I have attached shows the idea for a 3x1 rho vector but it really has an undefined length.
enter image description here
enter image description here
# JACOBIAN
def derivative(rho, a, A, tilde_k, x, y, vecinc, chi):
n = rho.shape[0]
result1 = np.array([n,1],complex)
result2 = np.array([n,1],complex)
result = np.array([n,1],complex)
u = np.zeros((n, 3))
W_tilde = np.array([3,3],complex)
loop1 = 0
loop2 = 0
for i in range(n):
for j in range(n):
u[i] = x[i] - y[j] # n x 3
W_tilde = A_matrix * chi.imag * A_matrix * G(u[i],k) * A_matrix # 3 x 3
ei_block = np.exp(1j * np.vdot(x[i], tilde_k)) * vecinc # 3 x 1
ej_block = np.exp(1j * np.vdot(x[j], tilde_k)) * vecinc # 3 x 1
eiT_block = np.matrix.getH(ei_block) # 1 x 3
mm = np.matmul(W_tilde, ej_block) # (3 x 3)(3 x 1) = 3 x 1
alpha_tilde = np.dot(eiT_block, mm) # (1 x 3)(3 x 1) = 1 x 1 = scalar
loop1 = loop1 + (2 * rho[i] * alpha_tilde * rho[j]) # scalar
if (i != j):
loop2 = loop2 + ((rho[j]**2) * alpha_tilde) # scalar
result1[i] = loop1
result2[i] = loop2
result = result1 + result2 # (n x 1) + (n x 1) = n x 1 vector
return result
I am getting "IndexError: index 2 is out of bounds for axis 0 with size 2" for the line, result1[i] = loop1. Pls help :(
That error means that you are attempting to access the third element (index 2) of an array with only two elements (size 2).
It looks like you're defining your arrays in a funny way; np.array([n,1],complex) creates an array of length 2, not n. What you want is probably np.zeros(n,complex), which will create an n-length array filled with 0s.

How to calculate a Normal Distribution percent point function in python

How do I do the equivalent of scipy.stats.norm.ppf without using Scipy. I have python's Math module has erf built in but I cannot seem to recreate the function.
PS: I cannot just use scipy because Heroku does not allow you to install it and using alternate buildpacks breaches the 300Mb maximum slug size limit.
There's not a simple way to use erf to implement norm.ppf because norm.ppf is related to the inverse of erf. Instead, here's a pure Python implementation of the code from scipy. You should find that the function ndtri returns exactly the same value as norm.ppf:
import math
s2pi = 2.50662827463100050242E0
P0 = [
-5.99633501014107895267E1,
9.80010754185999661536E1,
-5.66762857469070293439E1,
1.39312609387279679503E1,
-1.23916583867381258016E0,
]
Q0 = [
1,
1.95448858338141759834E0,
4.67627912898881538453E0,
8.63602421390890590575E1,
-2.25462687854119370527E2,
2.00260212380060660359E2,
-8.20372256168333339912E1,
1.59056225126211695515E1,
-1.18331621121330003142E0,
]
P1 = [
4.05544892305962419923E0,
3.15251094599893866154E1,
5.71628192246421288162E1,
4.40805073893200834700E1,
1.46849561928858024014E1,
2.18663306850790267539E0,
-1.40256079171354495875E-1,
-3.50424626827848203418E-2,
-8.57456785154685413611E-4,
]
Q1 = [
1,
1.57799883256466749731E1,
4.53907635128879210584E1,
4.13172038254672030440E1,
1.50425385692907503408E1,
2.50464946208309415979E0,
-1.42182922854787788574E-1,
-3.80806407691578277194E-2,
-9.33259480895457427372E-4,
]
P2 = [
3.23774891776946035970E0,
6.91522889068984211695E0,
3.93881025292474443415E0,
1.33303460815807542389E0,
2.01485389549179081538E-1,
1.23716634817820021358E-2,
3.01581553508235416007E-4,
2.65806974686737550832E-6,
6.23974539184983293730E-9,
]
Q2 = [
1,
6.02427039364742014255E0,
3.67983563856160859403E0,
1.37702099489081330271E0,
2.16236993594496635890E-1,
1.34204006088543189037E-2,
3.28014464682127739104E-4,
2.89247864745380683936E-6,
6.79019408009981274425E-9,
]
def ndtri(y0):
if y0 <= 0 or y0 >= 1:
raise ValueError("ndtri(x) needs 0 < x < 1")
negate = True
y = y0
if y > 1.0 - 0.13533528323661269189:
y = 1.0 - y
negate = False
if y > 0.13533528323661269189:
y = y - 0.5
y2 = y * y
x = y + y * (y2 * polevl(y2, P0) / polevl(y2, Q0))
x = x * s2pi
return x
x = math.sqrt(-2.0 * math.log(y))
x0 = x - math.log(x) / x
z = 1.0 / x
if x < 8.0:
x1 = z * polevl(z, P1) / polevl(z, Q1)
else:
x1 = z * polevl(z, P2) / polevl(z, Q2)
x = x0 - x1
if negate:
x = -x
return x
def polevl(x, coef):
accum = 0
for c in coef:
accum = x * accum + c
return accum
The function ppf is the inverse of y = (1+erf(x/sqrt(2))/2. So we need to solve this equation for x, given y between 0 and 1. Here is a code doing this by the bisection method. I imported SciPy function to illustrate that the result is the same.
from math import erf, sqrt
from scipy.stats import norm # only for comparison
y = 0.123
z = 2*y-1
a = 0
while erf(a) > z or erf(a+1) < z: # looking for initial bracket of size 1
if erf(a) > z:
a -= 1
else:
a += 1
b = a+1 # found a bracket, proceed to refine it
while b-a > 1e-15: # 1e-15 ought to be enough precision
c = (a+b)/2.0 # bisection method
if erf(c) > z:
b = c
else:
a = c
print sqrt(2)*(a+b)/2.0 # this is the answer
print norm.ppf(y) # SciPy for comparison
Left for you to do:
preliminary bound checks (y must be between 0 and 1)
scaling and shifting if other mean / variance are desired; the code is for standard normal distribution (mean 0, variance 1).

Categories

Resources