calib3d.points

Working with homogenous coordinates

The vector used to represent 2D and 3D points are vertical vectors, which are stored as 2D matrices in numpy. Furthemore, in homogenous coordinates: a 3D point (x,y,z) in the world is represented by a 4 element vector (𝜆x,𝜆y,𝜆z,𝜆) where 𝜆 ∈ ℝ₀.

To simplify access to x and y (and z) coordinates of those points as well as computations in homogenous coordinates, we defined the types Point2D (and Point3D) extending numpy.ndarray. Therefore, access to y coordinate of point is point.y instead of point[1][0] (point[1][:] for an array of points), and access to homogenous coordinates is made easy with point.H, while it is still possible to use point with any numpy operators.

  1from abc import ABCMeta, abstractproperty
  2import warnings
  3import numpy as np
  4
  5__doc__ = r"""
  6
  7# Working with homogenous coordinates
  8
  9The vector used to represent 2D and 3D points are vertical vectors, which are stored as 2D matrices in `numpy`.
 10Furthemore, in homogenous coordinates: a 3D point (x,y,z) in the world is represented by a 4 element vector
 11(𝜆x,𝜆y,𝜆z,𝜆) where 𝜆 ∈ ℝ₀.
 12
 13To simplify access to x and y (and z) coordinates of those points as well as computations in homogenous coordinates,
 14we defined the types [`Point2D`](#Point2D) (and [`Point3D`](#Point3D))
 15extending `numpy.ndarray`. Therefore, access to y coordinate of point is `point.y` instead of
 16`point[1][0]` (`point[1][:]` for an array of points), and access to homogenous coordinates is made easy with `point.H`,
 17while it is still possible to use point with any numpy operators.
 18
 19"""
 20
 21
 22class HomogeneousCoordinatesPoint(np.ndarray, metaclass=ABCMeta):
 23    """ Extension of Numpy `np.ndarray` that implements generic homogenous coordinates points
 24        for `Point2D` and `Point3D` objects. The constructor supports multiple formats for creation
 25        of a single point or array of multiple points.
 26
 27        Example with creation of `Point2D` objects, all formulations are equivalent:
 28        ```
 29        >>> x, y = 1, 2
 30        >>> Point2D(x, y)
 31        >>> Point2D(4*x, 4*y, 4)
 32        >>> Point2D(np.array([[x], [y]]))
 33        >>> Point2D(4*np.array([[x], [y], [1]]))
 34        Point2D([[1.],
 35                 [2.]])
 36        ```
 37    """
 38    def __new__(cls, *coords):
 39        if len(coords) == 1:
 40            if isinstance(coords, list):
 41                coords = np.hstack(coords)
 42            else:
 43                coords = coords[0]
 44        array = np.array(coords) if isinstance(coords, (tuple, list)) else coords
 45        invalid_shape_message = "Invalid input shape:\n" \
 46            "Expected a 2D np.array of shape ({l1},N) or (N,{l1},1) in non-homogenous coordinates\n" \
 47            "                       or shape ({l2},N) or (N,{l2},1) in homogenous coordinates\n" \
 48            "Received a np.array of shape {shape}".format(l1=cls.D, l2=cls.D+1, shape=array.shape)
 49        if len(array.shape) == 1:
 50            array = array[:, np.newaxis]
 51        elif len(array.shape) == 2:
 52            pass
 53        elif len(array.shape) == 3:
 54            array = array[..., 0].T
 55        else:
 56            raise ValueError(invalid_shape_message)
 57
 58        if array.shape[0] == cls.D: # point(s) given in non-homogenous coordinates
 59            pass
 60        elif array.shape[0] == cls.D+1: # point(s) given in homogenous coordinates
 61            array = array[0:cls.D,:]/array[cls.D,:]
 62        elif array.shape[0] == 0: # point given from an empty list should be an empty point
 63            array = np.empty((cls.D,0))
 64        else:
 65            raise ValueError(invalid_shape_message)
 66        return array.astype(np.float64).view(cls)
 67    # def __array_ufunc__():
 68    #    TODO
 69    # def __array_wrap__(self, out_arr, context=None):
 70    #     return super().__array_wrap__(self, out_arr, context)
 71    @property
 72    @abstractproperty
 73    def _coord_names(self):
 74        raise NotImplementedError
 75
 76    x = property(fget=lambda self: self._get_coord(0), fset=lambda self, value: self._set_coord(0, value), doc="Point's x component")
 77    y = property(fget=lambda self: self._get_coord(1), fset=lambda self, value: self._set_coord(1, value), doc="Point's y component")
 78    z = property(fget=lambda self: self._get_coord(2), fset=lambda self, value: self._set_coord(2, value), doc="Point's z component (only valid for `Point3D` objects)")
 79
 80    @property
 81    def H(self):
 82        """ Point expressed in homogenous coordinates with an homogenous component equal to `1`.
 83
 84        Example:
 85        ```
 86        >>> p = Point3D(1,2,3)
 87        >>> p.H
 88        array([[1.],
 89               [2.],
 90               [3.],
 91               [1.]])
 92        ```
 93        """
 94        return np.vstack((self, np.ones((1, self.shape[1]))))
 95    # @property
 96    # def D(self):
 97    #     """ Returns the number of spacial dimensions """
 98    #     return len(self._coord_names)
 99    # /!\ a different getitem gives too much trouble with all numpy operator
100    #def __getitem__(self, i):
101    #    if isinstance(i, int):
102    #        return self.__class__(super().__getitem__((slice(None), i)))
103    #    return super().__getitem__(i)
104
105    # /!\ iter may conflict with numpy array getitem.
106    def __iter__(self):
107        return (self.__class__(self[:,i:i+1]) for i in range(self.shape[1]))
108
109    def to_list(self):
110        """ Transforms a single point to a python list.
111
112        Raises:
113            AssertionError if the object is an array of multiple points
114        """
115        assert self.shape[1] == 1, "to_list() method can only be used on single point {}".format(self.__class__.__name__)
116        return self[:,0].flatten().tolist()
117
118    def flatten(self):
119        """ Flatten the points.
120
121        .. todo:: integrate this in the __array_ufunc__ to prevent type forwarding
122        """
123        return np.asarray(super().flatten())
124
125    def to_int_tuple(self):
126        """ Transforms a single point to a python tuple with integer coordinates
127        """
128        return tuple(int(x) for x in self.to_list())
129
130    def linspace(self, num):
131        """ Linearly interpolate points in `num-1` intervals.
132
133            Example:
134            ```
135            >>> Point2D([0,4,4],[0,0,4]).linspace(5)
136            [[0. 1. 2. 3. 4. 4. 4. 4. 4. 4.]
137            [0. 0. 0. 0. 0. 0. 1. 2. 3. 4.]]
138            ```
139        """
140        return np.transpose(np.linspace(self[:,:-1], self[:,1:], num), axes=(1,2,0)).reshape(len(self._coord_names),-1)
141
142    def close(self):
143        """ Copy the first point in an array of points and place it at the end of that array,
144            hence "closing" the polygon defined by the initial points.
145
146            TODO: add Points2D and Points3D classes
147        """
148        assert self.shape[1] > 1, f"Invalid use of 'close' method: points' shape '{self.shape}' expected to be > 1 in the second dimension"
149        return self.__class__(np.hstack((self, self[:,0:1])))
150
151    _get_coord = lambda self, i:        np.asarray(super().__getitem__((i,0)))       if self.shape[1] == 1 else np.asarray(super().__getitem__(i))
152    _set_coord = lambda self, i, value:            super().__setitem__((i,0), value) if self.shape[1] == 1 else            super().__setitem__((i), value)
153
154
155class Point2D(HomogeneousCoordinatesPoint):
156    """ Numpy representation of a single 2D point or a list of 2D points
157    """
158    D = 2
159    _coord_names = ("x","y")
160
161class Point3D(HomogeneousCoordinatesPoint):
162    """ Numpy representation of a single 3D point or a list of 3D points
163    """
164    D = 3
165    _coord_names = ("x","y","z")
166    @property
167    def V(self):
168        array = self.H
169        array[-1] = 0
170        return VanishingPoint(array)
171
172class VanishingPoint(Point3D):
173    """ Object allowing representation of Vanishing point (with null homogenous
174        coordinate). Only the `H` attribute should be used. Handle with care.
175    """
176    def __new__(cls, array):
177        warnings.warn("Vanishing Point feature has not yet been fully tested")
178        obj = array.astype(np.float64).view(cls)
179        obj.array = array
180        return obj
181    @property
182    def H(self):
183        return self.array
184    def __getattribute__(self, attr_name):
185        if attr_name not in ("H", "__array_finalize__", "array", "shape", "size", "ndim", "x", "y", "z", "_get_coord", "close", "__class__", "astype", "view"):
186            raise AttributeError(f"VanishingPoint has no `{attr_name}` attribute.")
187        return super().__getattribute__(attr_name)
class HomogeneousCoordinatesPoint(numpy.ndarray):
 23class HomogeneousCoordinatesPoint(np.ndarray, metaclass=ABCMeta):
 24    """ Extension of Numpy `np.ndarray` that implements generic homogenous coordinates points
 25        for `Point2D` and `Point3D` objects. The constructor supports multiple formats for creation
 26        of a single point or array of multiple points.
 27
 28        Example with creation of `Point2D` objects, all formulations are equivalent:
 29        ```
 30        >>> x, y = 1, 2
 31        >>> Point2D(x, y)
 32        >>> Point2D(4*x, 4*y, 4)
 33        >>> Point2D(np.array([[x], [y]]))
 34        >>> Point2D(4*np.array([[x], [y], [1]]))
 35        Point2D([[1.],
 36                 [2.]])
 37        ```
 38    """
 39    def __new__(cls, *coords):
 40        if len(coords) == 1:
 41            if isinstance(coords, list):
 42                coords = np.hstack(coords)
 43            else:
 44                coords = coords[0]
 45        array = np.array(coords) if isinstance(coords, (tuple, list)) else coords
 46        invalid_shape_message = "Invalid input shape:\n" \
 47            "Expected a 2D np.array of shape ({l1},N) or (N,{l1},1) in non-homogenous coordinates\n" \
 48            "                       or shape ({l2},N) or (N,{l2},1) in homogenous coordinates\n" \
 49            "Received a np.array of shape {shape}".format(l1=cls.D, l2=cls.D+1, shape=array.shape)
 50        if len(array.shape) == 1:
 51            array = array[:, np.newaxis]
 52        elif len(array.shape) == 2:
 53            pass
 54        elif len(array.shape) == 3:
 55            array = array[..., 0].T
 56        else:
 57            raise ValueError(invalid_shape_message)
 58
 59        if array.shape[0] == cls.D: # point(s) given in non-homogenous coordinates
 60            pass
 61        elif array.shape[0] == cls.D+1: # point(s) given in homogenous coordinates
 62            array = array[0:cls.D,:]/array[cls.D,:]
 63        elif array.shape[0] == 0: # point given from an empty list should be an empty point
 64            array = np.empty((cls.D,0))
 65        else:
 66            raise ValueError(invalid_shape_message)
 67        return array.astype(np.float64).view(cls)
 68    # def __array_ufunc__():
 69    #    TODO
 70    # def __array_wrap__(self, out_arr, context=None):
 71    #     return super().__array_wrap__(self, out_arr, context)
 72    @property
 73    @abstractproperty
 74    def _coord_names(self):
 75        raise NotImplementedError
 76
 77    x = property(fget=lambda self: self._get_coord(0), fset=lambda self, value: self._set_coord(0, value), doc="Point's x component")
 78    y = property(fget=lambda self: self._get_coord(1), fset=lambda self, value: self._set_coord(1, value), doc="Point's y component")
 79    z = property(fget=lambda self: self._get_coord(2), fset=lambda self, value: self._set_coord(2, value), doc="Point's z component (only valid for `Point3D` objects)")
 80
 81    @property
 82    def H(self):
 83        """ Point expressed in homogenous coordinates with an homogenous component equal to `1`.
 84
 85        Example:
 86        ```
 87        >>> p = Point3D(1,2,3)
 88        >>> p.H
 89        array([[1.],
 90               [2.],
 91               [3.],
 92               [1.]])
 93        ```
 94        """
 95        return np.vstack((self, np.ones((1, self.shape[1]))))
 96    # @property
 97    # def D(self):
 98    #     """ Returns the number of spacial dimensions """
 99    #     return len(self._coord_names)
100    # /!\ a different getitem gives too much trouble with all numpy operator
101    #def __getitem__(self, i):
102    #    if isinstance(i, int):
103    #        return self.__class__(super().__getitem__((slice(None), i)))
104    #    return super().__getitem__(i)
105
106    # /!\ iter may conflict with numpy array getitem.
107    def __iter__(self):
108        return (self.__class__(self[:,i:i+1]) for i in range(self.shape[1]))
109
110    def to_list(self):
111        """ Transforms a single point to a python list.
112
113        Raises:
114            AssertionError if the object is an array of multiple points
115        """
116        assert self.shape[1] == 1, "to_list() method can only be used on single point {}".format(self.__class__.__name__)
117        return self[:,0].flatten().tolist()
118
119    def flatten(self):
120        """ Flatten the points.
121
122        .. todo:: integrate this in the __array_ufunc__ to prevent type forwarding
123        """
124        return np.asarray(super().flatten())
125
126    def to_int_tuple(self):
127        """ Transforms a single point to a python tuple with integer coordinates
128        """
129        return tuple(int(x) for x in self.to_list())
130
131    def linspace(self, num):
132        """ Linearly interpolate points in `num-1` intervals.
133
134            Example:
135            ```
136            >>> Point2D([0,4,4],[0,0,4]).linspace(5)
137            [[0. 1. 2. 3. 4. 4. 4. 4. 4. 4.]
138            [0. 0. 0. 0. 0. 0. 1. 2. 3. 4.]]
139            ```
140        """
141        return np.transpose(np.linspace(self[:,:-1], self[:,1:], num), axes=(1,2,0)).reshape(len(self._coord_names),-1)
142
143    def close(self):
144        """ Copy the first point in an array of points and place it at the end of that array,
145            hence "closing" the polygon defined by the initial points.
146
147            TODO: add Points2D and Points3D classes
148        """
149        assert self.shape[1] > 1, f"Invalid use of 'close' method: points' shape '{self.shape}' expected to be > 1 in the second dimension"
150        return self.__class__(np.hstack((self, self[:,0:1])))
151
152    _get_coord = lambda self, i:        np.asarray(super().__getitem__((i,0)))       if self.shape[1] == 1 else np.asarray(super().__getitem__(i))
153    _set_coord = lambda self, i, value:            super().__setitem__((i,0), value) if self.shape[1] == 1 else            super().__setitem__((i), value)

Extension of Numpy np.ndarray that implements generic homogenous coordinates points for Point2D and Point3D objects. The constructor supports multiple formats for creation of a single point or array of multiple points.

Example with creation of Point2D objects, all formulations are equivalent:

>>> x, y = 1, 2
>>> Point2D(x, y)
>>> Point2D(4*x, 4*y, 4)
>>> Point2D(np.array([[x], [y]]))
>>> Point2D(4*np.array([[x], [y], [1]]))
Point2D([[1.],
         [2.]])
x
77    x = property(fget=lambda self: self._get_coord(0), fset=lambda self, value: self._set_coord(0, value), doc="Point's x component")

Point's x component

y
78    y = property(fget=lambda self: self._get_coord(1), fset=lambda self, value: self._set_coord(1, value), doc="Point's y component")

Point's y component

z
79    z = property(fget=lambda self: self._get_coord(2), fset=lambda self, value: self._set_coord(2, value), doc="Point's z component (only valid for `Point3D` objects)")

Point's z component (only valid for Point3D objects)

H
81    @property
82    def H(self):
83        """ Point expressed in homogenous coordinates with an homogenous component equal to `1`.
84
85        Example:
86        ```
87        >>> p = Point3D(1,2,3)
88        >>> p.H
89        array([[1.],
90               [2.],
91               [3.],
92               [1.]])
93        ```
94        """
95        return np.vstack((self, np.ones((1, self.shape[1]))))

Point expressed in homogenous coordinates with an homogenous component equal to 1.

Example:

>>> p = Point3D(1,2,3)
>>> p.H
array([[1.],
       [2.],
       [3.],
       [1.]])
def to_list(self):
110    def to_list(self):
111        """ Transforms a single point to a python list.
112
113        Raises:
114            AssertionError if the object is an array of multiple points
115        """
116        assert self.shape[1] == 1, "to_list() method can only be used on single point {}".format(self.__class__.__name__)
117        return self[:,0].flatten().tolist()

Transforms a single point to a python list.

Raises: AssertionError if the object is an array of multiple points

def flatten(self):
119    def flatten(self):
120        """ Flatten the points.
121
122        .. todo:: integrate this in the __array_ufunc__ to prevent type forwarding
123        """
124        return np.asarray(super().flatten())

Flatten the points.

.. todo:: integrate this in the __array_ufunc__ to prevent type forwarding

def to_int_tuple(self):
126    def to_int_tuple(self):
127        """ Transforms a single point to a python tuple with integer coordinates
128        """
129        return tuple(int(x) for x in self.to_list())

Transforms a single point to a python tuple with integer coordinates

def linspace(self, num):
131    def linspace(self, num):
132        """ Linearly interpolate points in `num-1` intervals.
133
134            Example:
135            ```
136            >>> Point2D([0,4,4],[0,0,4]).linspace(5)
137            [[0. 1. 2. 3. 4. 4. 4. 4. 4. 4.]
138            [0. 0. 0. 0. 0. 0. 1. 2. 3. 4.]]
139            ```
140        """
141        return np.transpose(np.linspace(self[:,:-1], self[:,1:], num), axes=(1,2,0)).reshape(len(self._coord_names),-1)

Linearly interpolate points in num-1 intervals.

Example:

>>> Point2D([0,4,4],[0,0,4]).linspace(5)
[[0. 1. 2. 3. 4. 4. 4. 4. 4. 4.]
[0. 0. 0. 0. 0. 0. 1. 2. 3. 4.]]
def close(self):
143    def close(self):
144        """ Copy the first point in an array of points and place it at the end of that array,
145            hence "closing" the polygon defined by the initial points.
146
147            TODO: add Points2D and Points3D classes
148        """
149        assert self.shape[1] > 1, f"Invalid use of 'close' method: points' shape '{self.shape}' expected to be > 1 in the second dimension"
150        return self.__class__(np.hstack((self, self[:,0:1])))

Copy the first point in an array of points and place it at the end of that array, hence "closing" the polygon defined by the initial points.

TODO: add Points2D and Points3D classes

class Point2D(HomogeneousCoordinatesPoint):
156class Point2D(HomogeneousCoordinatesPoint):
157    """ Numpy representation of a single 2D point or a list of 2D points
158    """
159    D = 2
160    _coord_names = ("x","y")

Numpy representation of a single 2D point or a list of 2D points

D = 2
class Point3D(HomogeneousCoordinatesPoint):
162class Point3D(HomogeneousCoordinatesPoint):
163    """ Numpy representation of a single 3D point or a list of 3D points
164    """
165    D = 3
166    _coord_names = ("x","y","z")
167    @property
168    def V(self):
169        array = self.H
170        array[-1] = 0
171        return VanishingPoint(array)

Numpy representation of a single 3D point or a list of 3D points

D = 3
V
167    @property
168    def V(self):
169        array = self.H
170        array[-1] = 0
171        return VanishingPoint(array)
class VanishingPoint(Point3D):
173class VanishingPoint(Point3D):
174    """ Object allowing representation of Vanishing point (with null homogenous
175        coordinate). Only the `H` attribute should be used. Handle with care.
176    """
177    def __new__(cls, array):
178        warnings.warn("Vanishing Point feature has not yet been fully tested")
179        obj = array.astype(np.float64).view(cls)
180        obj.array = array
181        return obj
182    @property
183    def H(self):
184        return self.array
185    def __getattribute__(self, attr_name):
186        if attr_name not in ("H", "__array_finalize__", "array", "shape", "size", "ndim", "x", "y", "z", "_get_coord", "close", "__class__", "astype", "view"):
187            raise AttributeError(f"VanishingPoint has no `{attr_name}` attribute.")
188        return super().__getattribute__(attr_name)

Object allowing representation of Vanishing point (with null homogenous coordinate). Only the H attribute should be used. Handle with care.

H
182    @property
183    def H(self):
184        return self.array

Point expressed in homogenous coordinates with an homogenous component equal to 1.

Example:

>>> p = Point3D(1,2,3)
>>> p.H
array([[1.],
       [2.],
       [3.],
       [1.]])