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)
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.]])
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
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
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)
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.]])
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
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
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
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.]]
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
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
Inherited Members
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
Inherited Members
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.