pycvcam.ZernikeDistortion#

ZernikeDistortion Class#

class ZernikeDistortion(parameters=None, constants=None, n_params=None, n_zer=None)[source]#

Subclass of the pycvcam.core.Distortion class that represents a Zernike decomposition distortion model.

Note

This class represents the distortion transformation, which is the middle step of the process from the world_points to the image_points.

The ZernikeDistortion model consists of a Zernike polynomial decomposition of the distortion along the x and y axes. The distortion is represented as a sum of Zernike polynomials, which are orthogonal polynomials defined on the unit disk.

Lets consider normalized_points in the camera normalized coordinate system \(\vec{x}_n = (x_n, y_n)\), the corresponding distorted_points in the camera normalized coordinate system are given \(\vec{x}_d\) can be obtained by :

\[x_{d} = x_{n} + \sum_{n=0}^{N_{zer}} \sum_{m=-n}^{n} C^{x}_{n,m} Z_{nm}(\rho, \theta)\]
\[y_{d} = y_{n} + \sum_{n=0}^{N_{zer}} \sum_{m=-n}^{n} C^{y}_{n,m} Z_{nm}(\rho, \theta)\]

where \(Z_{nm}(\rho, \theta)\) are the Zernike polynomials, \(C^{x}_{n,m}\) and \(C^{y}_{n,m}\) are the Zernike coefficients for the x and y coordinates, respectively, and \(\rho\) and \(\theta\) are the polar coordinates of the normalized points in the defined unit ellipse with radius \(R_x, R_y\) and center \((x_0, y_0)\):

\[\rho = \sqrt{\left(\frac{x_{n} - x_{0}}{R_x}\right)^2 + \left(\frac{y_{n} - y_{0}}{R_y}\right)^2}\]
\[\theta = \arctan2(y_{n} - y_{0}, x_{n} - x_{0})\]

Note

For more informations about Zernike polynomials, see the package pyzernike (Artezaru/pyzernike).

This transformation is caracterized by n_params parameters and 4 constants:

  • n_params parameters \(C^{x}_{n,m}\) and \(C^{y}_{n,m}\) for the Zernike coefficients.

  • 4 constants \((R_x, R_y, x_0, y_0)\) for the radius and center of the ellipse of definition of the Zernike polynomials.

Only coefficients for \(n \leq N_{zer}\) and \(m \in [-n, n]\) and \(n-m \equiv 0 \mod 2\) are stored.

The coefficients are storred in a parameters 1D-array with the OSA/ANSI standard indices but with a x/y separation:

  • C^{x}_{0,0}, parameters[0] for the x coordinate \(n=0, m=0\)

  • C^{y}_{0,0}, parameters[1] for the y coordinate \(n=0, m=0\)

  • C^{x}_{1,-1}, parameters[2] for the x coordinate \(n=1, m=-1\)

  • C^{y}_{1,-1}, parameters[3] for the y coordinate \(n=1, m=-1\)

  • C^{x}_{1,1}, parameters[4] for the x coordinate \(n=1, m=1\)

  • C^{y}_{1,1}, parameters[5] for the y coordinate \(n=1, m=1\)

  • C^{x}_{2,-2}, parameters[6] for the x coordinate \(n=2, m=-2\)

  • C^{y}_{2,-2}, parameters[7] for the y coordinate \(n=2, m=-2\)

  • C^{x}_{2,0}, parameters[8] for the x coordinate \(n=2, m=0\)

  • C^{y}_{2,0}, parameters[9] for the y coordinate \(n=2, m=0\)

  • C^{x}_{2,2}, parameters[10] for the x coordinate \(n=2, m=2\)

  • C^{y}_{2,2}, parameters[11] for the y coordinate \(n=2, m=2\)

If the number of input parameters is not equal to the number of parameters required by the model, the other parameters are set to 0.

The number of parameters is given by the formula:

\[N_{params} = (N_{zer}+1)(N_{zer}+2)\]

Ordre of Zernike n_zer

Nparameters for X or Y

Nparameters in model n_params

None

0

0

0

1

2

1

3

6

2

6

12

3

10

20

4

15

30

Warning

If the ordre of the zernike polynomials n_zer is given during instantiation, the given parameters are truncated or extended to the given number of parameters. Same for the number of parameters n_params.

To compute the Distortion, the user must define the unit circle in which the normalized points are defined. The unit circle is defined by the radius \(R\) and the center \((x_{0}, y_{0})\).

Parameters:
  • parameters (Optional[numpy.ndarray], optional) – The parameters of the distortion transformation. It should be a numpy array of shape (n_params,) containing the distortion coefficients ordered as described above. Default is None, which means no distortion is setted.

  • constants (numpy.ndarray, optional) – The constants of the distortion transformation. It should be a numpy array of shape (4,) containing the radius and center of the ellipse of definition of the Zernike polynomials in the order: (R_x, R_y, x_0, y_0). Default is [1.0, 1.0, 0.0, 0.0], which means the unit circle is defined with radius 1 and center (0, 0).

  • n_params (Optional[Integral], optional) – The number of parameters for the distortion model. If not specified, it will be inferred from the shape of the parameters array.

  • n_zer (int, optional) – The order of the Zernike polynomials. If None, the order is set according to the number of parameters. The default is None. Only use n_zer or n_params, not both.

Accessing the parameters of ZernikeDistortion objects#

The parameters and constants properties can be accessing using pycvcam.core.Transform methods. Some additional convenience methods are provided to access commonly used parameters of the Cv2Distortion model:

ZernikeDistortion.center

Get or set the center of the unit circle in which the normalized points are defined.

ZernikeDistortion.center_x

Get or set the x coordinate of the center of the unit circle in which the normalized points are defined.

ZernikeDistortion.center_y

Get or set the y coordinate of the center of the unit circle in which the normalized points are defined.

ZernikeDistortion.n_zer

Get or set the order of the Zernike polynomials.

ZernikeDistortion.parameters_x

Get or set the Zernike coefficients for the x coordinate.

ZernikeDistortion.parameter_x_names

["Cx(0, 0), Cx(1, -1), Cx(1, 1), ...]

ZernikeDistortion.parameters_y

Get or set the Zernike coefficients for the y coordinate.

ZernikeDistortion.parameter_y_names

["Cy(0, 0), Cy(1, -1), Cy(1, 1), ...]

ZernikeDistortion.radius

Get or set the radius of the unit circle in which the normalized points are defined.

ZernikeDistortion.radius_x

Get or set the radius of the unit circle in the x direction.

ZernikeDistortion.radius_y

Get or set the radius of the unit circle in the y direction.

Optionnaly, the parameters of the ZernikeDistortion can be accessed using set using the convenience methods based on Zernike polynomials indexing:

ZernikeDistortion.get_index(n, m, coord)

Get the index of the Zernike coefficient for the given order and azimuthal frequency.

ZernikeDistortion.get_Cx(n, m)

Get the Zernike coefficient for the x coordinate.

ZernikeDistortion.get_Cy(n, m)

Get the Zernike coefficient for the y coordinate.

ZernikeDistortion.set_Cx(n, m, value)

Set the Zernike coefficient for the x coordinate.

ZernikeDistortion.set_Cy(n, m, value)

Set the Zernike coefficient for the y coordinate.

Performing distortion with ZernikeDistortion objects#

The transform and inverse_transform methods can be used to perform distortion and undistortion using the Cv2Distortion model (as described in the pycvcam.core.Transform documentation).

The implementation of theses transformations and more details on the options available can be found in the following methods:

ZernikeDistortion._transform(...[, dx, dp])

Compute the transformation from the normalized_points to the distorted_points.

ZernikeDistortion._inverse_transform(...[, ...])

Compute the inverse transformation from the distorted_points to the normalized_points.

Examples#

Create an distortion object with a specific order of Zernike polynomials and parameters:

import numpy
from pycvcam import ZernikeDistortion

# Create a distortion object with 6 parameters
distortion = ZernikeDistortion(numpy.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6])) # Model with n_zer=1, -> n_params=6

Then you can use the distortion object to transform normalized_points to distorted_points:

normalized_points = numpy.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]) # shape (n_points, 2)

result = distortion.transform(normalized_points)
distorted_points = result.distorted_points # Shape (n_points, 2)
print(distorted_points)

You can also access to the jacobian of the distortion transformation:

result = distortion.transform(normalized_points, dx=True, dp=True)
distorted_points_dx = result.jacobian_dx  # Shape (n_points, 2, 2)
distorted_points_dp = result.jacobian_dp  # Shape (n_points, 2, n_params = 5)
print(distorted_points_dx)
print(distorted_points_dp)

The inverse transformation can be computed using the inverse_transform method:

inverse_result = distortion.inverse_transform(distorted_points, dx=True, dp=True)
normalized_points = inverse_result.normalized_points  # Shape (n_points, 2)
print(normalized_points)

Note

The jacobian with respect to the depth is not computed.

See also

For more information about the transformation process, see:

If you want to define the Zernike unit disk to encapsulate an image, with a centered distortion and a circular distortion in the image plane, you can use the constants parameter:

import numpy
import cv2
from pycvcam import ZernikeDistortion

# Load the image
image = cv2.imread('image.jpg')
image_height, image_width = image.shape[:2]

# Compute the center and radius of the unit disk in the image plane
x0 = (image_width - 1) / 2
y0 = (image_height - 1) / 2
R_x = R_y = numpy.sqrt(((image_width - 1) / 2) ** 2 + ((image_height - 1) / 2) ** 2)

# Extract the intrinsic focal length (fx, fy) from the camera calibration and the principal point (cx, cy) form the intrinsic transformation
x0 = (x0 - cx) / fx
y0 = (y0 - cy) / fy
R_x /= fx
R_y /= fy

# Create a distortion object with a specific unit disk
constants = numpy.array([R_x, R_y, x0, y0])
distortion = ZernikeDistortion(
   parameters=numpy.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6]),
   constants=constants
)