Skip to content

Correspondence

Correspondence(object_points, image_points)

Correspondence between a set of object points and one or more sets of image points.

Initialize a correspondence object.

Parameters:

Name Type Description Default
object_points ndarray

3D object points as an Nx3 numpy array.

required
image_points ndarray

2D image points as an MxNx2 numpy array. For single-view, the first dimension may be omitted (i.e. just Nx2).

required
Source code in src/compas_camcal/models/correspondence.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def __init__(self, object_points: np.ndarray, image_points: np.ndarray) -> None:
    """
    Initialize a correspondence object.

    Args:
        object_points: 3D object points as an Nx3 numpy array.
        image_points: 2D image points as an MxNx2 numpy array. For single-view, the first dimension may be omitted (i.e. just Nx2).
    """
    if image_points.ndim == 2:  # handle single-view case
        image_points = image_points[np.newaxis, :, :]

    if object_points.ndim != 2 or object_points.shape[1] != 3:
        raise ValueError("Object points must be Nx3.")
    if image_points.ndim != 3 or image_points.shape[2] != 2:
        raise ValueError("Image points must be MxNx2 or Nx2.")
    if image_points.shape[1] != object_points.shape[0]:
        raise ValueError("Number of object points must match number of image points.")

    self.object_points = object_points
    self.image_points = image_points

__add__(other)

Add two correspondences with the same set of object points.

Source code in src/compas_camcal/models/correspondence.py
42
43
44
45
46
47
48
49
50
51
def __add__(self, other: "Correspondence") -> "Correspondence":
    """
    Add two correspondences with the same set of object points.
    """
    if (self.object_points != other.object_points).any():
        raise ValueError("Cannot combine correspondences with different sets of object points.")

    return Correspondence(
        self.object_points, np.concatenate((self.image_points, other.image_points), axis=0)
    )

__iadd__(other)

Add another correspondence to this one.

Source code in src/compas_camcal/models/correspondence.py
53
54
55
56
57
58
59
60
61
def __iadd__(self, other: "Correspondence") -> "Correspondence":
    """
    Add another correspondence to this one.
    """
    if (self.object_points != other.object_points).any():
        raise ValueError("Cannot combine correspondences with different sets of object points.")

    self.image_points = np.concatenate((self.image_points, other.image_points), axis=0)
    return self

compose(*correspondences) classmethod

Compose a set of correspondences into a single correspondence.

Source code in src/compas_camcal/models/correspondence.py
70
71
72
73
74
75
76
77
78
79
80
81
82
@classmethod
def compose(cls, *correspondences: Tuple["Correspondence"]) -> "Correspondence":
    """
    Compose a set of correspondences into a single correspondence.
    """
    if len(correspondences) == 0:
        raise ValueError("Cannot compose empty set of correspondences.")

    object_points = correspondences[0].object_points
    image_points = np.concatenate(
        [correspondence.image_points for correspondence in correspondences], axis=0
    )
    return Correspondence(object_points, image_points)

decompose()

Decompose this correspondence into a set of correspondences with different sets of object points.

Source code in src/compas_camcal/models/correspondence.py
63
64
65
66
67
68
def decompose(self) -> Iterable["Correspondence"]:
    """
    Decompose this correspondence into a set of correspondences with different sets of object points.
    """
    for i in range(self.n_views):
        yield Correspondence(self.object_points, self.image_points[i, :, :])

draw(images, radius=3)

Draw the correspondence on an image (or images in the case of multi-view).

Parameters:

Name Type Description Default
images Iterable[ndarray]

The images to draw the correspondence on.

required
radius int

The radius of the circles to draw.

3

Yields:

Type Description
Iterable[ndarray]

The images with the correspondence drawn on them.

Source code in src/compas_camcal/models/correspondence.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
def draw(self, images: Iterable[np.ndarray], radius: int = 3) -> Iterable[np.ndarray]:
    """
    Draw the correspondence on an image (or images in the case of multi-view).

    Args:
        images: The images to draw the correspondence on.
        radius: The radius of the circles to draw.

    Yields:
        The images with the correspondence drawn on them.
    """
    hue_values = np.linspace(0, 179, self.n_points, dtype=np.uint8)
    colors = [np.array([hue, 255, 255], dtype=np.uint8).tolist() for hue in hue_values]

    for image, view_idx in zip(images, range(self.n_views)):
        output_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        for point_idx in range(self.n_points):
            cv2.circle(
                output_image,
                tuple(self.image_points[view_idx, point_idx, :].astype(int)),
                radius,
                colors[point_idx],
                -1,
            )
        yield cv2.cvtColor(output_image, cv2.COLOR_HSV2BGR)

load(path) classmethod

Load a correspondence from a compressed Numpy file.

Source code in src/compas_camcal/models/correspondence.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
@classmethod
def load(cls, path: Union[Path, str]) -> "Correspondence":
    """
    Load a correspondence from a compressed Numpy file.
    """
    path = Path(path)
    if not path.exists():
        raise FileNotFoundError("Correspondence file not found.")

    with np.load(str(path)) as data:
        return cls(data["object_points"], data["image_points"])

save(path)

Save this correspondence to a compressed Numpy file.

Source code in src/compas_camcal/models/correspondence.py
84
85
86
87
88
89
90
91
92
93
def save(self, path: Union[Path, str]):
    """
    Save this correspondence to a compressed Numpy file.
    """
    path = Path(path)
    path.parent.mkdir(parents=True, exist_ok=True)

    np.savez_compressed(
        str(path), object_points=self.object_points, image_points=self.image_points
    )