Related
I want to apply a transformation matrix to a set of points. So the set of points:
points = np.array([[0 ,20], [0, 575], [0, 460]])
And I want to use the matrix I calculated with cv2.getPerspectiveTransform() which is a 3x3 matrix.
matrix = np.array([
[ -4. , -3. , 1920. ],
[ -2.25 , -1.6875 , 1080. ],
[ -0.0020833, -0.0015625, 1. ]])
Then I pass the array and a matrix to the following function:
def poly_points_transform(poly_points, matrix):
poly_points_transformed = np.empty_like(poly_points)
for i in range(len(poly_points)):
point = np.array([[poly_points[i]]])
transformed_point = cv2.perspectiveTransform(point, matrix)
np.append(poly_points_transformed, transformed_point)
return poly_points_transformed
Now It doesn't throw an error, but it just copies the src array to the poly_points_transformed. It might be something really rudimentary and stupid. If it is the case, I am sorry, but could someone give me a hint on what is wrong? Thanks in advance
We may solve it with one line of code:
transformed_point = cv2.perspectiveTransform(np.array([points], np.float64), matrix)[0]
As Micka commented cv2.perspectiveTransform takes a list of points (and returns a list of points as output).
np.array([points]) is used because cv2.perspectiveTransform expects 3D array.
For details see trouble getting cv.transform to work.
np.float64 is used in case the dtype of points is int32 (the method accepts float64 and float32 types).
[0] is used for removing the redundant dimension (convert from 3D to 2D).
For fixing the loop, replace np.append(poly_points_transformed, transformed_point) with:
poly_points_transformed[i] = transformed_point[0].
Since the array is initialized to poly_points_transformed = np.empty_like(poly_points), we can't use np.append().
Code sample:
import cv2
import numpy as np
points = np.array([[0.0 ,20.0], [0.0, 575.0], [0.0, 460.0]])
matrix = np.array([
[ -4. , -3. , 1920. ],
[ -2.25 , -1.6875 , 1080. ],
[ -0.0020833, -0.0015625, 1. ]])
# transformed_point = cv2.perspectiveTransform(np.array([points], np.float64), matrix)[0]
def poly_points_transform(poly_points, matrix):
poly_points_transformed = np.empty_like(poly_points)
for i in range(len(poly_points)):
point = np.array([[poly_points[i]]])
transformed_point = cv2.perspectiveTransform(point, matrix)
poly_points_transformed[i] = transformed_point[0] #np.append(poly_points_transformed, transformed_point)
return poly_points_transformed
poly_points_transformed = poly_points_transform(points, matrix)
The result is:
poly_points_transformed =
array([[1920., 1080.],
[1920., 1080.],
[1920., 1080.]])
Why are we getting [1920.0, 1080.0] value for all the transformed points?
Lets transform the middle point mathematically:
Multiply matrix by point (with 1 in the third index)
[ -4. , -3. , 1920. ] [ 0]
[ -2.25 , -1.6875 , 1080. ] * [575] =
[ -0.0020833, -0.0015625, 1. ] [ 1]
p = matrix # np.array([[0.0], [575.0], [1.0]]) =
[1.950000e+02]
[1.096875e+02]
[1.015625e-01]
Now divide the coordinates by the last element (converting homogeneous coordinates to Euclidian coordinates):
[1.950000e+02/1.015625e-01] [1920]
[1.096875e+02/1.015625e-01] = p / p[2] = [1080]
[1.015625e-01/1.015625e-01] [ 1]
The equivalent Euclidian point is [1920, 1080].
The transformation matrix may be wrong, because it transforms all the input points (with x coordinate equals 0) to the same output point...
I have a distributional analysis algorithm for words. It generates observational vectors for each target word and from this table, I use stats.spearmanr() to calculate the distances (rescaled from [-1,1] to [0,1]), generating a distance matrix (Y). Then I use hierarchy.average() to obtain the clustering (Z). Finally, a dendrogram is generated and plotted.
The problem I have is this: the dendrogram scale varies with the number of target words. I was assuming that its distance axis varied along the [0,1] range (as obtained (and rescaled) with spearmanr()), as presented above. But it is [0, 0.5] for, say, 50 words, but [0, 1] for 150, and [0, 2] for 1000.
Why is that so (that the distance scale have values bigger than the ones in Y)?
I'd appreciate any ideas on this issue, because I can't seem to find any hint in the documentation and over the web (which makes me worried about making the wrong questions...). And I'd need a fixed scale or at least a way to know which one the dendrogram is using, for cut level specification purposes. Thanks in advance for any help.
The simplified code:
# coding: utf-8
# Estatísticas e visualização
import numpy as np
import scipy, random
import scipy.stats
# Clusterização e visualização do dendrograma
import scipy.cluster.hierarchy as hac
import matplotlib.pyplot as plt
def remap(x, in_min, in_max, out_min, out_max):
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
random.seed('7622')
sizes = [50, 250, 500, 1000, 2000]
for n in sizes:
# Generate observation matrix
X = []
for i in range(n):
vet = []
for j in range(300):
# Generate random observations
vet.append(random.randint(0, 50))
X.append(vet)
# X is a matrix where lines are variables (target words) and columns are observations (contexts of occurrence)
Y = scipy.stats.spearmanr(X, axis=1)
# Y rescaling
for i in range(len(Y[0])):
Y[0][i] = [ remap(v, -1, 1, 0, 1) for v in Y[0][i] ]
print 'Y [', np.matrix(Y[0]).min(), ',', np.matrix(Y[0]).max(), ']'
# Clustering
Z = hac.average(Y[0])
print 'n=', n, \
'Z [', min([ el[2] for el in Z ]), ',', max([ el[2] for el in Z ]), ']'
[UPDATE] Results of the above code:
Y [ 0.401120498124 , 1.0 ]
n= 50 Z [ 0.634408300876 , 0.77633631869 ]
Y [ 0.379375733574 , 1.0 ]
n= 250 Z [ 0.775241869849 , 0.969704246048 ]
Y [ 0.37559031365 , 1.0 ]
n= 500 Z [ 0.935671154717 , 1.16505319575 ]
Y [ 0.370600337649 , 1.0 ]
n= 1000 Z [ 1.19646327361 , 1.47897594053 ]
Y [ 0.359010408057 , 1.0 ]
n= 2000 Z [ 1.56890165007 , 1.96898566034 ]
I have a set of points pts which form a loop and it looks like this:
This is somewhat similar to 31243002, but instead of putting points in between pairs of points, I would like to fit a smooth curve through the points (coordinates are given at the end of the question), so I tried something similar to scipy documentation on Interpolation:
values = pts
tck = interpolate.splrep(values[:,0], values[:,1], s=1)
xnew = np.arange(2,7,0.01)
ynew = interpolate.splev(xnew, tck, der=0)
but I get this error:
ValueError: Error on input data
Is there any way to find such a fit?
Coordinates of the points:
pts = array([[ 6.55525 , 3.05472 ],
[ 6.17284 , 2.802609],
[ 5.53946 , 2.649209],
[ 4.93053 , 2.444444],
[ 4.32544 , 2.318749],
[ 3.90982 , 2.2875 ],
[ 3.51294 , 2.221875],
[ 3.09107 , 2.29375 ],
[ 2.64013 , 2.4375 ],
[ 2.275444, 2.653124],
[ 2.137945, 3.26562 ],
[ 2.15982 , 3.84375 ],
[ 2.20982 , 4.31562 ],
[ 2.334704, 4.87873 ],
[ 2.314264, 5.5047 ],
[ 2.311709, 5.9135 ],
[ 2.29638 , 6.42961 ],
[ 2.619374, 6.75021 ],
[ 3.32448 , 6.66353 ],
[ 3.31582 , 5.68866 ],
[ 3.35159 , 5.17255 ],
[ 3.48482 , 4.73125 ],
[ 3.70669 , 4.51875 ],
[ 4.23639 , 4.58968 ],
[ 4.39592 , 4.94615 ],
[ 4.33527 , 5.33862 ],
[ 3.95968 , 5.61967 ],
[ 3.56366 , 5.73976 ],
[ 3.78818 , 6.55292 ],
[ 4.27712 , 6.8283 ],
[ 4.89532 , 6.78615 ],
[ 5.35334 , 6.72433 ],
[ 5.71583 , 6.54449 ],
[ 6.13452 , 6.46019 ],
[ 6.54478 , 6.26068 ],
[ 6.7873 , 5.74615 ],
[ 6.64086 , 5.25269 ],
[ 6.45649 , 4.86206 ],
[ 6.41586 , 4.46519 ],
[ 5.44711 , 4.26519 ],
[ 5.04087 , 4.10581 ],
[ 4.70013 , 3.67405 ],
[ 4.83482 , 3.4375 ],
[ 5.34086 , 3.43394 ],
[ 5.76392 , 3.55156 ],
[ 6.37056 , 3.8778 ],
[ 6.53116 , 3.47228 ]])
Actually, you were not far from the solution in your question.
Using scipy.interpolate.splprep for parametric B-spline interpolation would be the simplest approach. It also natively supports closed curves, if you provide the per=1 parameter,
import numpy as np
from scipy.interpolate import splprep, splev
import matplotlib.pyplot as plt
# define pts from the question
tck, u = splprep(pts.T, u=None, s=0.0, per=1)
u_new = np.linspace(u.min(), u.max(), 1000)
x_new, y_new = splev(u_new, tck, der=0)
plt.plot(pts[:,0], pts[:,1], 'ro')
plt.plot(x_new, y_new, 'b--')
plt.show()
Fundamentally, this approach not very different from the one in #Joe Kington's answer. Although, it will probably be a bit more robust, because the equivalent of the i vector is chosen, by default, based on the distances between points and not simply their index (see splprep documentation for the u parameter).
Your problem is because you're trying to work with x and y directly. The interpolation function you're calling assumes that the x-values are in sorted order and that each x value will have a unique y-value.
Instead, you'll need to make a parameterized coordinate system (e.g. the index of your vertices) and interpolate x and y separately using it.
To start with, consider the following:
import numpy as np
from scipy.interpolate import interp1d # Different interface to the same function
import matplotlib.pyplot as plt
#pts = np.array([...]) # Your points
x, y = pts.T
i = np.arange(len(pts))
# 5x the original number of points
interp_i = np.linspace(0, i.max(), 5 * i.max())
xi = interp1d(i, x, kind='cubic')(interp_i)
yi = interp1d(i, y, kind='cubic')(interp_i)
fig, ax = plt.subplots()
ax.plot(xi, yi)
ax.plot(x, y, 'ko')
plt.show()
I didn't close the polygon. If you'd like, you can add the first point to the end of the array (e.g. pts = np.vstack([pts, pts[0]])
If you do that, you'll notice that there's a discontinuity where the polygon closes.
This is because our parameterization doesn't take into account the closing of the polgyon. A quick fix is to pad the array with the "reflected" points:
import numpy as np
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt
#pts = np.array([...]) # Your points
pad = 3
pts = np.pad(pts, [(pad,pad), (0,0)], mode='wrap')
x, y = pts.T
i = np.arange(0, len(pts))
interp_i = np.linspace(pad, i.max() - pad + 1, 5 * (i.size - 2*pad))
xi = interp1d(i, x, kind='cubic')(interp_i)
yi = interp1d(i, y, kind='cubic')(interp_i)
fig, ax = plt.subplots()
ax.plot(xi, yi)
ax.plot(x, y, 'ko')
plt.show()
Alternately, you can use a specialized curve-smoothing algorithm such as PEAK or a corner-cutting algorithm.
Using the ROOT Framework and the pyroot interface I was able to generate the following image
With the following code(I converted your data to a CSV called data.csv so reading it into ROOT would be easier and gave the columns titles of xp,yp)
from ROOT import TTree, TGraph, TCanvas, TH2F
c1 = TCanvas( 'c1', 'Drawing Example', 200, 10, 700, 500 )
t=TTree('TP','Data Points')
t.ReadFile('./data.csv')
t.SetMarkerStyle(8)
t.Draw("yp:xp","","ACP")
c1.Print('pydraw.png')
To fit a smooth closed curve through N points you can use line segments with the following constraints:
Each line segment has to touch its two end points (2 conditions per line segment)
For each point the left and right line segment have to have the same derivative (2 conditions per point == 2 conditions per line segment)
To be able to have enough freedom for in total 4 conditions per line segment the equation of each line segment should be y = ax^3 + bx^2 + cx + d. (so the derivative is y' = 3ax^2 + 2bx + c)
Setting the conditions as suggested would give you N * 4 linear equations for N * 4 unknowns (a1..aN, b1..bN, c1..cN, d1..dN) solvable by matrix inversion (numpy).
If the points are on the same vertical line special (but simple) handling is required since the derivative will be "infinite".
Here's a function I have to calculate the centroid of a polygon, as described here: http://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon
import numpy as np
# a polygon approximating a circle with geometric centre at (37.5, 37.5)
testpoly = np.array(
[[ 50., 37.5 ],
[ 48.91931822, 42.58420804],
[ 45.86413258, 46.78931032],
[ 41.36271243, 49.38820645],
[ 36.19339421, 49.93152369],
[ 31.25, 48.32531755],
[ 27.38728757, 44.84731565],
[ 25.27315499, 40.09889614],
[ 25.27315499, 34.90110386],
[ 27.38728757, 30.15268435],
[ 31.25, 26.67468245],
[ 36.19339421, 25.06847631],
[ 41.36271243, 25.61179355],
[ 45.86413258, 28.21068968],
[ 48.91931822, 32.41579196]])
def calculate_poly_centroid(vertex_coords, num_vertices):
xs = vertex_coords[:, 0]
ys = vertex_coords[:, 1]
centroid = np.zeros(2, dtype=np.float64)
area = 0
for index in range(num_vertices-1):
area += 0.5*(xs[index]*ys[index+1] - xs[index+1]*ys[index])
area_factor = (1/6.)*area
sum_x = 0
sum_y = 0
for index in range(num_vertices-1):
k = (xs[index]*ys[index+1] - xs[index+1]*ys[index])
sum_x += area_factor*(xs[index] + xs[index+1])*k
sum_y += area_factor*(ys[index] + ys[index+1])*k
centroid[0] = sum_x
centroid[1] = sum_y
print "centroid: ", centroid
return centroid
calculate_poly_centroid(testpoly, len(testpoly))
It is not written in efficient Python (expanded for loops, etc.) because it was initially meant to be Cython. I have converted it to Python for the sake of easily understanding what's going on.
Here's the output I get:
centroid: [ 5307119.41815466 5689101.16579313]
...which is way off [37.5, 37.5].
Where does the error lie? I have triple checked the code myself, and am now beginning to doubt my sanity.
You should be doing:
area_factor = 1/(6.*area)
I have an application where I am using matplotlib to display elliptical regions on an image. To do this I'm using mpl.patches.Circle along with mp.patches.Affine2D to warp unit circles into an elliptical shape.
import numpy as np
import matplotlib as mpl
import pyplot as plt
invVR_mats = np.array([
[[ 7.80247545, 0. , 92.9254837 ],
[ -3.46026921, 10.85727882, 17.53866959],
[ 0. , 0. , 1. ]],
[[ 11.42656994, 0. , 76.86006927],
[ -3.26515651, 9.61946297, 24.79440498],
[ 0. , 0. , 1. ]],
[[ 10.40444851, 0. , 140.62428284],
[ -10.94557095, 10.59212685, 24.91024971],
[ 0. , 0. , 1. ]],])
invVR_aff2Ds = [mpl.transforms.Affine2D(invVR)
for invVR in invVR_mats]
ell_actors = [mpl.patches.Circle((0, 0), 1, transform=invVR)
for invVR in invVR_aff2Ds]
coll = mpl.collections.PatchCollection(ell_actors)
plt.figure()
ax = plt.gca()
ax.set_ylim(0, 100)
ax.set_xlim(0, 300)
ax.add_collection(coll)
There is a point in my application that the ellipses in one image are put in correspondence with ellipses from a second image using a homography matrix. So far I have been using it to warp points from image1 into image2.
I would like to get a visual idea of how these ellipses warp into image2. I can transform my affine matrices with this homography matrix, but the resulting matrix is no longer affine. (I believe it represents a general conic, either a circle, ellipse, hyperbola, or parabola)
from numpy.core.umath_tests import matrix_multiply
H = np.array([[ -0.70098, 0.12273, 5.18734],
[ 0.12444, -0.63474, 14.13995],
[ 0.00004, 0.00025, -0.64873]])
HinvVR_mats = matrix_multiply(H, invVR_mats)
print(HinvVR_mats)
#---------
np.array([
[[ -5.89405808e+00, 1.33251383e+00, -5.77990446e+01],
[ 3.16731132e+00, -6.89154916e+00, 1.45711021e+01],
[ -5.52968284e-04, 2.71431970e-03, -6.40628313e-01]],
[[ -8.41052966e+00, 1.18059669e+00, -4.56470140e+01],
[ 3.49444781e+00, -6.10585793e+00, 7.96641640e+00],
[ -3.59226330e-04, 2.40486574e-03, -6.39456996e-01]],
[[ -8.63666024e+00, 1.29997173e+00, -9.03302348e+01],
[ 8.24232128e+00, -6.72324660e+00, 1.58277039e+01],
[ -2.32021480e-03, 2.64803171e-03, -6.36877466e-01]]])
If I de-homogenize only the last column I can find the center of where the ellipse was projected, but I would like to see some shape information as well.
So far the best I've done is just de-homogenizing the last column and ignoring the values in [:, 2, 0] and [:, 2, 1]
HinvVR_mats = np.divide(HinvVR_mats , HinvVR_mats[:, None, None, 2, 2])
print(HinvVR_mats)
array([[[ 9.20043332e+00, -2.08001083e+00, 9.02224323e+01],
[ -4.94407015e+00, 1.07574845e+01, -2.27450173e+01],
[ 8.63165541e-04, -4.23696494e-03, 1.00000000e+00]],
[[ 1.31526118e+01, -1.84624877e+00, 7.13840248e+01],
[ -5.46471120e+00, 9.54850438e+00, -1.24580956e+01],
[ 5.61767769e-04, -3.76079354e-03, 1.00000000e+00]],
[[ 1.35609449e+01, -2.04116458e+00, 1.41832989e+02],
[ -1.29417694e+01, 1.05565779e+01, -2.48520394e+01],
[ 3.64311021e-03, -4.15783546e-03, 1.00000000e+00]]])
Is there a way I can tranform mpl.patches.Circle (or any other patch for that matter) using a non-affine matrix. The documentation seems to suggest it is possible, but I'm not seeing any way to go about it.
I have
I was able to solve this by looking at the tutorial posted by tcaswell
I had to create my own tranformation class though which looked like this
class HomographyTransform(mpl.transforms.Transform):
"""
References:
http://stackoverflow.com/questions/28401788/using-homogeneous-transforms-non-affine-with-matplotlib-patches?noredirect=1#comment45156353_28401788
http://matplotlib.org/users/transforms_tutorial.html
"""
input_dims = 2
output_dims = 2
is_separable = False
def __init__(self, H, axis=None, use_rmin=True):
mpl.transforms.Transform.__init__(self)
self._axis = axis
self._use_rmin = use_rmin
self.H = H
def transform_non_affine(self, input_xy):
"""
The input and output are Nx2 numpy arrays.
"""
import vtool as vt
_xys = input_xy.T
xyz = vt.add_homogenous_coordinate(_xys)
xyz_t = vt.matrix_multiply(self.H, xyz)
xy_t = vt.remove_homogenous_coordinate(xyz_t)
output_xy = xy_t.T
return output_xy
#transform_non_affine.__doc__ = mpl.transforms.Transform.transform_non_affine.__doc__
def transform_path_non_affine(self, path):
vertices = path.vertices
if len(vertices) == 2 and vertices[0, 0] == vertices[1, 0]:
return mpl.path.Path(self.transform(vertices), path.codes)
ipath = path.interpolated(path._interpolation_steps)
return mpl.path.Path(self.transform(ipath.vertices), ipath.codes)
#transform_path_non_affine.__doc__ = mpl.transforms.Transform.transform_path_non_affine.__doc__
The functions called by my own library vtool are:
def add_homogenous_coordinate(_xys):
assert _xys.shape[0] == 2
_zs = np.ones((1, _xys.shape[1]), dtype=_xys.dtype)
_xyzs = np.vstack((_xys, _zs))
return _xyzs
def remove_homogenous_coordinate(_xyzs):
assert _xyzs.shape[0] == 3
_xys = np.divide(_xyzs[0:2], _xyzs[None, 2])
return _xys
and matrix_multiply is the same matrix_multiply used earlier.
and my function to create the transform matrices currently looks like this:
def get_invVR_aff2Ds(kpts, H=None):
""" Returns matplotlib keypoint transformations (circle -> ellipse) """
#invVR_mats = ktool.get_invV_mats(kpts, with_trans=True, with_ori=True)
invVR_mats = ktool.get_invVR_mats3x3(kpts)
if H is None:
invVR_aff2Ds = [mpl.transforms.Affine2D(invVR)
for invVR in invVR_mats]
else:
# not actually affine
invVR_aff2Ds = [HomographyTransform(H.dot(invVR))
for invVR in invVR_mats]
return invVR_aff2Ds