CoordinateTransformations
Страница в процессе перевода. |
CoordinateTransformations is a Julia package to manage simple or complex networks of coordinate system transformations. Transformations can be easily applied, inverted, composed, and differentiated (both with respect to the input coordinates and with respect to transformation parameters such as rotation angle). Transformations are designed to be light-weight and efficient enough for, e.g., real-time graphical applications, while support for both explicit and automatic differentiation makes it easy to perform optimization and therefore ideal for computer vision applications such as SLAM (simultaneous localization and mapping).
The package provide two main pieces of functionality
-
Primarily, an interface for defining
Transformation
s and applying (by calling), inverting (inv()
), composing (∘
orcompose()
) and differentiating (transform_deriv()
andtransform_deriv_params()
) them. -
A small set of built-in, composable, primitive transformations for transforming 2D and 3D points (optionally leveraging the StaticArrays and Rotations packages).
Quick start
Let’s translate a 3D point:
using CoordinateTransformations, Rotations, StaticArrays
x = SVector(1.0, 2.0, 3.0) # SVector is provided by StaticArrays.jl
trans = Translation(3.5, 1.5, 0.0)
y = trans(x)
We can either apply different transformations in turn,
rot = LinearMap(RotX(0.3)) # Rotate 0.3 radians about X-axis, from Rotations.jl
z = trans(rot(x))
or build a composed transformation using the ∘
operator (accessible at the REPL by typing \circ
then tab):
composed = trans ∘ rot # alternatively, use compose(trans, rot)
composed(x) == z
A composition of a Translation
and a LinearMap
results in an AffineMap
.
We can invert the transformation:
composed_inv = inv(composed)
composed_inv(z) == x
For any transformation, we can shift the origin to a new point using recenter
:
rot_around_x = recenter(rot, x)
Now rot_around_x
is a rotation around the point x = SVector(1.0, 2.0, 3.0)
.
Finally, we can construct a matrix describing how the components of z
differentiates with respect to components of x
:
# In general, the transform may be non-linear, and thus we require
# the value of x to compute the derivative
∂z_∂x = transform_deriv(composed, x)
Or perhaps we want to know how y
will change with respect to changes of to the translation parameters:
∂y_∂θ = transform_deriv_params(trans, x)
Interface
Transformations are derived from Transformation
. As an example, we have Translation{T} <: Transformation
. A Translation
will accept and translate points in a variety of formats, such as Vector
or SVector
, but in general your custom-defined Transformation
s could transform any Julia object.
Transformations can be reversed using inv(trans)
. They can be chained together using the ∘
operator (trans1 ∘ trans2
) or compose
function (compose(trans1, trans2)
). In this case, trans2
is applied first to the data, before trans1
. Composition may be intelligent, for instance by precomputing a new Translation
by summing the elements of two existing Translation
s, and yet other transformations may compose to the IdentityTransformation
. But by default, composition will result in a ComposedTransformation
object which simply dispatches to apply the transformations in the correct order.
Finally, the matrix describing how differentials propagate through a transform can be calculated with the transform_deriv(trans, x)
method. The derivatives of how the output depends on the transformation parameters is accessed via transform_deriv_params(trans, x)
. Users currently have to overload these methods, as no fall-back automatic differentiation is currently included. Alternatively, all the built-in types and transformations are compatible with automatic differentiation techniques, and can be parameterized by DualNumbers' DualNumber
or ForwardDiff's Dual
.
Built-in transformations
A small number of 2D and 3D coordinate systems and transformations are included. We also have IdentityTransformation
and ComposedTransformation
, which allows us to nest together arbitrary transformations to create a complex yet efficient transformation chain.
Coordinate types
The package accepts any AbstractVector
type for Cartesian coordinates. For speed, we recommend using a statically-sized container such as SVector{N}
from StaticArrays.
We do provide a few specialist coordinate types. The Polar(r, θ)
type is a 2D polar representation of a point, and similarly in 3D we have defined Spherical(r, θ, ϕ)
and Cylindrical(r, θ, z)
.
Coordinate system transformations
Two-dimensional coordinates may be converted using these parameterless (singleton) transformations:
Three-dimensional coordinates may be converted using these parameterless transformations:
However, you may find it simpler to use the convenience constructors like Polar(SVector(1.0, 2.0))
.
Translations
Translations can be be applied to Cartesian coordinates in arbitrary dimensions, by e.g. Translation(Δx, Δy)
or Translation(Δx, Δy, Δz)
in 2D/3D, or by Translation(Δv)
in general (with Δv
an AbstractVector
). Compositions of two Translation
s will intelligently create a new Translation
by adding the translation vectors.
Linear transformations
Linear transformations (a.k.a. linear maps), including rotations, can be encapsulated in the LinearMap
type, which is a simple wrapper of an AbstractMatrix
.
You are able to provide any matrix of your choosing, but your choice of type will have a large effect on speed. For instance, if you know the dimensionality of your points (e.g. 2D or 3D) you might consider a statically sized matrix like SMatrix
from StaticArrays.jl. We recommend performing 3D rotations using those from Rotations.jl for their speed and flexibility. Scaling will be efficient with Julia’s built-in UniformScaling
. Also note that compositions of two LinearMap
s will intelligently create a new LinearMap
by multiplying the transformation matrices.
Affine maps
An Affine map encapsulates a more general set of transformation which are defined by a composition of a translation and a linear transformation. An AffineMap
is constructed from an AbstractVector
translation v
and an AbstractMatrix
linear transformation M
. It will perform the mapping x -> M*x + v
, but the order of addition and multiplication will be more obvious (and controllable) if you construct it from a composition of a linear map and a translation, e.g. Translation(v) ∘ LinearMap(v)
(or any combination of LinearMap
, Translation
and AffineMap
).
AffineMap
s can be constructed to fit point pairs from_points => to_points
:
julia> from_points = [[0, 0], [1, 0], [0, 1]];
julia> to_points = [[1, 1], [3, 1], [1.5, 3]];
julia> AffineMap(from_points => to_points)
AffineMap([1.9999999999999996 0.5; -5.551115123125783e-16 2.0], [0.9999999999999999, 1.0000000000000002])
The points can be supplied as a collection of vectors or as a matrix with points as columns.
Perspective transformations
The perspective transformation maps real-space coordinates to those on a virtual "screen" of one lesser dimension. For instance, this process is used to render 3D scenes to 2D images in computer generated graphics and games. It is an ideal model of how a pinhole camera operates and is a good approximation of the modern photography process.
The PerspectiveMap()
command creates a Transformation
to perform the projective mapping. It can be applied individually, but is particularly powerful when composed with an AffineMap
containing the position and orientation of the camera in your scene. For example, to transfer points
in 3D space to 2D screen_points
giving their projected locations on a virtual camera image, you might use the following code:
cam_transform = PerspectiveMap() ∘ inv(AffineMap(cam_rotation, cam_position))
screen_points = map(cam_transform, points)
There is also a cameramap()
convenience function that can create a composed transformation that includes the intrinsic scaling (e.g. focal length and pixel size) and offset (defining which pixel is labeled (0,0)
) of an imaging system.