Matching#

Descriptor matchers return (dists, idxs): one value and one index pair per match. Each row of idxs is (index in desc1, index in desc2). Choose the matcher by its acceptance rule, and interpret its first output as follows:

Matcher selection and scores#

Matcher

Acceptance rule

First output

match_nn(), match_mnn()

Nearest neighbour; MNN also requires the reverse match.

Distance; lower is better.

match_snn(), match_smnn()

Nearest-to-second-nearest ratio test; SMNN requires mutual matches and tests both directions.

Ratio; lower is better. SMNN returns the larger ratio.

match_fginn()

Ratio test against a geometrically inconsistent neighbour, using LAFs.

Ratio; lower is better.

match_adalam()

Local affine consistency using LAFs.

Inverse confidence, or a squared-distance ratio in the no-seed fallback; lower is better.

LightGlueMatcher

Learned matching with pretrained weights for the selected feature.

Matching score; higher is better.

DescriptorMatcher wraps NN, MNN, SNN and SMNN as a module. GeometryAwareDescriptorMatcher wraps FGINN and AdaLAM. For padded batches of descriptor pairs, use match_smnn_batched(); its index rows include the batch index. To detect, describe and match two images in one call, use LocalFeatureMatcher with a descriptor-only matcher.

LightGlue takes feature dictionaries and returns the full network outputs; OnnxLightGlue runs an exported model. LoFTR matches images directly, without a detector. To keep matches that agree with a geometric model, use RANSAC.

Descriptor matching#

kornia.feature.match_nn(desc1, desc2, dm=None)[source]#

Match each descriptor in desc1 to its nearest neighbor in desc2, with no filtering.

dists, idxs = match_nn(desc1, desc2) takes two descriptor sets (B1, D) and (B2, D) and returns, for every row of desc1 in order, the L2 distance (B1, 1) to its nearest row of desc2 and the index pair (B1, 2) of the two rows; a precomputed dm (B1, B2) replaces the L2 distances. Every row is matched, however ambiguous its nearest neighbour, and several rows can match the same row of desc2. To keep only the reliable matches, use match_mnn() (mutual nearest neighbours), match_snn() (Lowe’s ratio test) or match_smnn() (both) instead.

Convention:
  • Every match_* function returns (dists, idxs), one row per match. idxs is an int64 (M, 2) tensor of (index in desc1, index in desc2) rows; match_smnn_batched() adds the batch index as a first column. dists is (M, 1). Without dm, it has the descriptor dtype, except that match_adalam() returns float32. Lower values are better, and their meaning depends on the function:

    • match_nn(), match_mnn(): the distance, the entry of dm when it is given.

    • match_snn(): the ratio of the nearest to the second-nearest distance, not a distance.

    • match_smnn(), match_smnn_batched(): the larger of the two ratios, desc1 to desc2 and back.

    • match_fginn(): the ratio of the nearest distance to that of a second neighbour, taken among the 10 nearest, whose lafs2 centre lies at least spatial_th from the nearest one’s, and 0 without one; see its Convention block.

    • match_adalam(): the inverse of the local RANSAC confidence; see its Convention block.

    LightGlueMatcher returns LightGlue’s matching score in the place of dists, which is higher for better matches, and LocalFeatureMatcher reports 1 - dists as confidence.

  • The ratio tests of match_snn(), match_smnn(), match_smnn_batched() and match_fginn() keep ratio <= th: a ratio equal to th passes.

  • Without dm, the functions use L2 distances. A given dm replaces them: match_nn() and match_mnn() require only that smaller entries are better, while ratio tests require non-negative distances. Use th ** 2 for squared distances.

  • Of tied nearest neighbours, match_nn() and match_mnn() take the one with the lower index.

  • An empty desc1 or desc2 gives a (0, 1) and a (0, 2) tensor ((0, 3) for match_smnn_batched()). The ratio tests need a second neighbour: match_snn() and match_fginn() return no match unless desc2 has two rows, and match_smnn(), match_smnn_batched() and match_adalam(), which scores its putative matches by the same ratio, need two rows in both.

Parameters:
  • desc1 (Tensor) – Batch of descriptors of a shape (B1, D).

  • desc2 (Tensor) – Batch of descriptors of a shape (B2, D).

  • dm (Optional[Tensor], optional) – torch.Tensor containing the distances from each descriptor in desc1 to each descriptor in desc2, shape of (B1, B2). Default: None

Return type:

Tuple[Tensor, Tensor]

Returns:

  • Descriptor distance of matching descriptors, shape of (B1, 1), or (0, 1) when desc2 is empty.

  • Long torch.Tensor indexes of matching descriptors in desc1 and desc2, shape of (B1, 2), or (0, 2) when desc2 is empty.

See also

  • match_mnn(): keeps only the pairs that are each other’s nearest neighbours.

  • match_snn(): keeps only the nearest neighbours that pass Lowe’s ratio test.

  • DescriptorMatcher: the module form, with match_mode="nn".

kornia.feature.match_mnn(desc1, desc2, dm=None)[source]#

Match the descriptors of desc1 and desc2 that are each other’s nearest neighbors.

dists, idxs = match_mnn(desc1, desc2) takes two descriptor sets (B1, D) and (B2, D) and returns the pairs whose two descriptors are each other’s nearest neighbour, the mutual nearest neighbours: their L2 distances (B3, 1) and index pairs (B3, 2), with B3 <= min(B1, B2). Each row of either set is matched at most once, but a distinctive descriptor and an ambiguous one are kept alike; to also require Lowe’s ratio test, use match_smnn() instead.

Convention:

See the Convention block on match_nn(). A pair is kept only when each of the two descriptors is the nearest neighbour of the other. Ties go to the lower index in both directions, so of several identical rows of desc2 only the first can be matched.

Parameters:
  • desc1 (Tensor) – Batch of descriptors of a shape (B1, D).

  • desc2 (Tensor) – Batch of descriptors of a shape (B2, D).

  • dm (Optional[Tensor], optional) – torch.Tensor containing the distances from each descriptor in desc1 to each descriptor in desc2, shape of (B1, B2). Default: None

Return type:

Tuple[Tensor, Tensor]

Returns:

  • Descriptor distance of matching descriptors, shape of (B3, 1).

  • Long torch.Tensor indexes of matching descriptors in desc1 and desc2, shape of (B3, 2), where 0 <= B3 <= min(B1, B2)

See also

  • match_nn(): the nearest neighbour of every row of desc1, mutual or not.

  • match_smnn(): mutual nearest neighbours that also pass the ratio test in both directions.

  • match_snn(): the ratio test in one direction, without the mutual check.

  • DescriptorMatcher: the module form, with match_mode="mnn".

kornia.feature.match_snn(desc1, desc2, th=0.8, dm=None)[source]#

Match each descriptor in desc1 to its nearest neighbor in desc2 if it passes Lowe’s ratio test.

dists, idxs = match_snn(desc1, desc2, th) takes two descriptor sets (B1, D) and (B2, D), keeps the rows of desc1 whose nearest neighbour in desc2 is at most th times as far as the second-nearest, and returns their ratios (B3, 1), not distances, and index pairs (B3, 2), with B3 <= B1, in the order of the rows of desc1. Several rows of desc1 can match the same row of desc2; to keep only mutual nearest neighbours that pass the test in both directions, use match_smnn() instead.

Speed, with the same matches, on 1,024 to 8,192 RootSIFT descriptors: on an x86 CPU this is faster than faiss and OpenCV’s BFMatcher, while faiss’s exact k-nearest-neighbour search runs this ratio test up to 2.2x faster on CUDA and up to 2.0x faster on an Apple M1 CPU. See the image matching guide.

Convention:

See the Convention block on match_nn(); dists is the ratio of the nearest to the second-nearest distance.

Parameters:
  • desc1 (Tensor) – Batch of descriptors of a shape (B1, D).

  • desc2 (Tensor) – Batch of descriptors of a shape (B2, D).

  • th (float, optional) – distance ratio threshold. Default: 0.8

  • dm (Optional[Tensor], optional) – torch.Tensor containing the distances from each descriptor in desc1 to each descriptor in desc2, shape of (B1, B2). Default: None

Return type:

Tuple[Tensor, Tensor]

Returns:

  • Ratio of the nearest to the second-nearest distance of each match, shape of (B3, 1).

  • Long torch.Tensor indexes of matching descriptors in desc1 and desc2. Shape: (B3, 2), where 0 <= B3 <= B1.

See also

  • match_nn(): the nearest neighbour of every row of desc1, without the ratio test.

  • match_mnn(): mutual nearest neighbours, without the ratio test.

  • match_smnn(): the ratio test in both directions, on mutual nearest neighbours.

  • match_fginn(): the same ratio test, with a second neighbour that lies elsewhere in the image.

  • DescriptorMatcher: the module form, with match_mode="snn".

kornia.feature.match_smnn(desc1, desc2, th=0.95, dm=None)[source]#

Match mutual nearest neighbors that also pass Lowe’s ratio test in both directions.

dists, idxs = match_smnn(desc1, desc2, th) takes two descriptor sets (B1, D) and (B2, D), keeps the mutual nearest neighbours whose nearest-to-second-nearest distance ratio is at most th both from desc1 to desc2 and back, and returns the larger of their two ratios (B3, 1) and their index pairs (B3, 2), with B3 <= min(B1, B2), in the order of the rows of desc1. For many pairs of descriptor sets, padded to common sizes, use match_smnn_batched() instead of a loop.

Speed, with the same matches, on 1,024 to 8,192 RootSIFT descriptors: on an x86 CPU this is faster than faiss and OpenCV’s BFMatcher, while faiss’s exact k-nearest-neighbour search, run both ways, is up to 1.6x faster on CUDA and up to 2.0x faster on an Apple M1 CPU. See the image matching guide.

Convention:
  • See the Convention block on match_nn(). A pair is kept when the two descriptors are mutual nearest neighbours and pass the ratio test, ratio <= th, in both directions; dists is the larger of the two ratios.

  • th defaults to 0.95, where match_snn() and match_fginn() default to 0.8.

Parameters:
  • desc1 (Tensor) – Batch of descriptors of a shape (B1, D).

  • desc2 (Tensor) – Batch of descriptors of a shape (B2, D).

  • th (float, optional) – distance ratio threshold. Default: 0.95

  • dm (Optional[Tensor], optional) – torch.Tensor containing the distances from each descriptor in desc1 to each descriptor in desc2, shape of (B1, B2). Default: None

Return type:

Tuple[Tensor, Tensor]

Returns:

  • The larger of the two distance ratios of each match, shape of (B3, 1).

  • Long torch.Tensor indexes of matching descriptors in desc1 and desc2, shape of (B3, 2) where 0 <= B3 <= B1.

See also

  • match_snn(): the ratio test from desc1 to desc2 only, without the mutual check.

  • match_mnn(): mutual nearest neighbours without the ratio test.

  • match_smnn_batched(): the same matches for a batch of padded descriptor-set pairs, in one call.

  • DescriptorMatcher: the module form, with match_mode="smnn" and a default th of 0.8.

  • RANSAC: the next step, which keeps the matches one geometric model explains.

kornia.feature.match_smnn_batched(desc1, desc2, th=0.95, dm=None, mask1=None, mask2=None)[source]#

Match a batch of padded descriptor-set pairs in one call, each as match_smnn matches one pair.

ratios, idxs = match_smnn_batched(desc1, desc2, th) takes B pairs of descriptor sets, (B, N, D) and (B, M, D), padded to common sizes, with optional boolean masks mask1 (B, N) and mask2 (B, M) of the valid rows, and returns the matches of every pair at once, without a loop over the pairs: the larger of the two ratios (K, 1) and index rows (K, 3) of (batch index, index in desc1, index in desc2), which index the padded tensors and come sorted by batch index, then index in desc1. For a single pair, use match_smnn().

Convention:

See the Convention blocks on match_nn() and match_smnn(). For each batch item it returns the pairs and ratios that match_smnn() returns for finite distances between the valid descriptors of that item, with the batch index as the first column of the indices. Unlike match_smnn(), it rejects a match when the nearest or second-nearest distance in either direction is non-finite. Without dm, float16 and bfloat16 descriptors give the results of match_smnn() on float32 copies, with the ratios rounded back, and a few pairs and ratios can differ slightly from those of match_smnn() on the half-precision descriptors.

Parameters:
  • desc1 (Tensor) – First descriptors, shape (B, N, D).

  • desc2 (Tensor) – Second descriptors, shape (B, M, D).

  • th (float, optional) – Inclusive distance-ratio threshold. Default: 0.95

  • dm (Optional[Tensor], optional) – Optional precomputed L2 distances, shape (B, N, M). Default: None

  • mask1 (Optional[Tensor], optional) – Optional boolean mask of valid first descriptors, shape (B, N). Default: None

  • mask2 (Optional[Tensor], optional) – Optional boolean mask of valid second descriptors, shape (B, M). Masks allow padding variable-length pairs; padding never becomes a neighbor. Default: None

Return type:

Tuple[Tensor, Tensor]

Returns:

  • Ratios, shape (K, 1), with the input device and dtype.

  • Long indices, shape (K, 3): (batch_index, index_in_desc1, index_in_desc2). Indices refer to the original padded tensors, sorted by batch and first-descriptor index. A pair with fewer than two valid descriptors on either side contributes no matches.

Note

Memory for the distance matrix scales as \(B N M\). Bucket pairs by descriptor counts and limit batch size when matching large collections. Half-precision descriptors use float32 distance computation before ratios are converted back to the input dtype. Masked inputs compute the distances directly, without the matrix-multiplication form, which avoids its cancellation but can be slower for large masked descriptor sets. As in match_smnn(), a zero second-neighbor distance produces an undefined ratio and that ambiguous match is rejected. Ties follow torch.topk() and do not have a guaranteed cross-device ordering.

See also

  • match_smnn(): the same test for one pair of descriptor sets of any sizes, with no padding.

  • match_snn(): the ratio test in one direction for one pair, without the mutual check.

Example

>>> a = torch.tensor([[[0., 0.], [1., 1.], [2., 2.]]])
>>> ratios, indices = match_smnn_batched(a, a.flip(1))
>>> indices
tensor([[0, 0, 2],
        [0, 1, 1],
        [0, 2, 0]])
kornia.feature.match_fginn(desc1, desc2, lafs1, lafs2, th=0.8, spatial_th=10.0, mutual=False, dm=None)[source]#

Match desc1 to desc2 by a ratio test against the first geometrically inconsistent neighbor (FGINN).

dists, idxs = match_fginn(desc1, desc2, lafs1, lafs2, th, spatial_th) takes two descriptor sets (B1, D) and (B2, D) and the LAFs (1, B2, 2, 3) of desc2, keeps the rows of desc1 whose nearest neighbour in desc2 passes the ratio test of match_snn() against the nearest candidate whose lafs2 centre lies at least spatial_th from the nearest one’s, and returns their ratios (B3, 1) and index pairs (B3, 2), in the order of the rows of desc1. Closer candidates are skipped (see [MMP15] for more details), so a near-duplicate detection of the same structure, such as another scale or orientation at one place, does not reject the match as it can in match_snn(). lafs1 is not read, and mutual=True adds the mutual nearest-neighbour check. To test the local affine consistency of the matches in both images, use match_adalam() instead.

Convention:
  • See the Convention block on match_nn(). dists is a ratio: the nearest distance over the distance of the second neighbour, which is the nearest of the min(10, B2) nearest candidates, the first one excluded, whose lafs2 centre lies at least spatial_th from the centre of the first one. Candidates closer than that count as the same structure, not as competitors, whatever their descriptor distance. When all of them are – a dense cluster of detections on one structure – the ratio is 0 and the match is accepted: no distinct competing structure among the candidates means the nearest neighbour is unambiguous. A descriptor beyond the min(10, B2) nearest is never the second neighbour, even when every candidate is excluded.

  • Only the centres of lafs2 are read, and spatial_th is in their units: pixels for LAFs in pixels.

Parameters:
  • desc1 (Tensor) – Batch of descriptors of a shape (B1, D).

  • desc2 (Tensor) – Batch of descriptors of a shape (B2, D).

  • lafs1 (Tensor) – LAFs of a shape (1, B1, 2, 3). Accepted for API symmetry with match_adalam() but not read by this function – only lafs2 feeds the geometric check.

  • lafs2 (Tensor) – LAFs of a shape (1, B2, 2, 3).

  • th (float, optional) – distance ratio threshold. Default: 0.8

  • spatial_th (float, optional) – minimal distance between the lafs2 centres of the 1st and the 2nd nearest neighbor, in the units of those centres. Default: 10.0

  • mutual (bool, optional) – also perform mutual nearest neighbor check. Default: False

  • dm (Optional[Tensor], optional) – torch.Tensor containing the distances from each descriptor in desc1 to each descriptor in desc2, shape of (B1, B2). Default: None

Return type:

Tuple[Tensor, Tensor]

Returns:

  • Ratio of the nearest to the second-neighbour distance of each match, see the Convention block, shape of (B3, 1).

  • Long torch.Tensor indexes of matching descriptors in desc1 and desc2. Shape: (B3, 2), where 0 <= B3 <= B1.

See also

kornia.feature.match_adalam(desc1, desc2, lafs1, lafs2, config=None, hw1=None, hw2=None, dm=None)[source]#

Match descriptors by nearest neighbour, filtered by local affine model support (AdaLAM).

dists, idxs = match_adalam(desc1, desc2, lafs1, lafs2, hw1=(H1, W1), hw2=(H2, W2)) takes two descriptor sets (B1, D) and (B2, D), their LAFs (1, B1, 2, 3) and (1, B2, 2, 3) and, optionally, the image sizes in the units of the LAF centres, pixels for LAFs in pixels. It matches every row of desc1 to its nearest neighbour in desc2 and returns the matches that the local RANSACs around confident seed matches accept, each match once: the inverse RANSAC confidences (B3, 1), float32, and the index pairs (B3, 2). The local models read the LAF orientations and scales as well as the centres, and need no global model of the scene, see [CLO+20] for more details. To keep the matches that one global model explains instead, pass the matches of match_snn() or match_smnn() to RANSAC.

Convention:
  • See the Convention block on match_nn(). dists is the inverse of the confidence of the most confident local RANSAC that accepted the match (overlapping neighbourhoods often accept a match several times), in float32 whatever the descriptor dtype, so at most 1 / min_confidence (1 / 200 by default). If no seed survives, for example with very few keypoints, the matches of a ratio test on squared distances, ratio <= 0.95, are returned with that ratio instead.

  • The putative matches are the nearest neighbours in the distance matrix dm, scored by the ratio of the two smallest distances; with force_seed_mnn only mutual nearest neighbours can be seeds. dm holds plain distances, like the dm of match_nn() and the other matchers: AdaLAM squares it internally (its thresholds are tuned for squared distances), so dm=torch.cdist(desc1, desc2) gives the matches of dm=None, where the squared L2 distances between the descriptors are used.

  • The LAFs give each keypoint’s centre, orientation in degrees and scale. Orientations and scales are read only through how their change between the two images varies from match to match: adding an angle to the orientations of one image or both, negating both, or scaling the LAFs of one image or both alike changes nothing.

  • hw1 and hw2 enter only through the area of their image, which sets the neighbourhood radius in that image, so (H, W) and (W, H) give the same matches. Without them, the extent of each image’s keypoints is used.

  • config is merged into the defaults of kornia.feature.adalam.get_adalam_default_config(); an unknown key warns and is ignored. The computation runs on the device of desc1 unless config sets "device".

  • Local affine consensus counts distinct source and target locations, reserving three samples for the seed and minimal fitting pair. Repeated locations use the first observation in descriptor-score order as evidence; a later conflicting match cannot replace it. Equal residuals do not imply duplicate keypoints. The full accepted radius is returned, including repeated observations tied at its boundary and mixtures of exact and noisy matches. Confidence uses a conservative residual-area floor of 2**-24 in normalized coordinates, remaining finite for exact fits.

  • Unlike the reference implementation, which merges nearly equal residuals into float16 bins, the consensus radius is the largest at which both the cumulative inlier density and the density over the last min_inliers samples reach min_confidence.

Parameters:
  • desc1 (Tensor) – Batch of descriptors of a shape (B1, D).

  • desc2 (Tensor) – Batch of descriptors of a shape (B2, D).

  • lafs1 (Tensor) – LAFs of a shape (1, B1, 2, 3).

  • lafs2 (Tensor) – LAFs of a shape (1, B2, 2, 3).

  • config (Optional[AdalamConfig], optional) – dict with AdaLAM config, merged into the defaults; see the Convention block. Default: None

  • hw1 (Optional[Sequence[int]], optional) – size of the first image, (H, W) or (W, H). Default: None

  • hw2 (Optional[Sequence[int]], optional) – size of the second image, (H, W) or (W, H). Default: None

  • dm (Optional[Tensor], optional) – torch.Tensor containing the non-negative distances (not squared) from each descriptor in desc1 to each descriptor in desc2, shape of (B1, B2). Smaller is better. Default: None

Return type:

Tuple[Tensor, Tensor]

Returns:

  • Inverse local RANSAC confidence of each match, float32, shape of (B3, 1); see the Convention block.

  • Long torch.Tensor indexes of matching descriptors in desc1 and desc2. Shape: (B3, 2), where 0 <= B3 <= B1.

See also

  • match_fginn(): a ratio test that reads the LAF centres of desc2 only, with no local models.

  • GeometryAwareDescriptorMatcher: the module form, with match_mode="adalam" and no image sizes.

  • RANSAC: one global model, such as a homography or a fundamental matrix, for all the matches.

class kornia.feature.DescriptorMatcher(match_mode='snn', th=0.8)[source]#

Match two descriptor sets by nearest neighbours, mutual ones or a ratio test, as a module.

matcher = DescriptorMatcher("snn", th=0.8) builds the module and dists, idxs = matcher(desc1, desc2) takes two descriptor sets (B1, D) and (B2, D) and returns what match_nn(), match_mnn(), match_snn() or match_smnn() returns for them, by match_mode: (B3, 1) qualities and (B3, 2) index pairs. It holds no parameters and reads no LAFs, which makes it a matcher for LocalFeatureMatcher. For matchers that also read the LAFs, use GeometryAwareDescriptorMatcher instead, and for a learned one, LightGlueMatcher.

Convention:

See the Convention block on match_nn(). forward returns what the function of match_mode (case-insensitive) returns for the two descriptor sets, with the module’s th for snn and smnn. th defaults to 0.8 for both, while match_smnn() defaults to 0.95, and nn and mnn do not read it. There is no dm.

Parameters:
  • match_mode (str, optional) – type of matching, can be nn, snn, mnn, smnn. Default: "snn"

  • th (float, optional) – distance ratio threshold of the snn and smnn modes. Default: 0.8

Raises:

NotImplementedError – for another match_mode.

See also

forward(desc1, desc2)[source]#

Run forward.

Parameters:
  • desc1 (Tensor) – Batch of descriptors of a shape (B1, D).

  • desc2 (Tensor) – Batch of descriptors of a shape (B2, D).

Return type:

Tuple[Tensor, Tensor]

Returns:

  • dists of the matching function, see match_nn(), shape of (B3, 1).

  • Long torch.Tensor indexes of matching descriptors in desc1 and desc2,

    shape of (B3, 2) where \(0 <= B3 <= B1\).

class kornia.feature.GeometryAwareDescriptorMatcher(match_mode='fginn', params=None)[source]#

Match two descriptor sets that come with their LAFs, by FGINN or AdaLAM, as a module.

matcher = GeometryAwareDescriptorMatcher("fginn", params) builds the module and dists, idxs = matcher(desc1, desc2, lafs1, lafs2) takes two descriptor sets (B1, D) and (B2, D) and their LAFs (1, B1, 2, 3) and (1, B2, 2, 3), and returns what match_fginn() or match_adalam() returns for them, by match_mode: (B3, 1) ratios or inverse confidences and (B3, 2) index pairs. It passes no dm, and no image sizes to AdaLAM; to give them, call the functions directly. For descriptors without LAFs, use DescriptorMatcher instead.

Convention:

See the Convention block on match_nn(). forward returns what the function of match_mode (case-insensitive) returns:

  • "fginn" reads the keys th, spatial_th and mutual of params and ignores any other key. th defaults to 0.85, while match_fginn() defaults to 0.8.

  • "adalam" passes params as the AdaLAM config, and no hw1 or hw2, so AdaLAM takes the extent of each image’s keypoints as the image size.

Parameters:
  • match_mode (str, optional) – type of matching, can be fginn or adalam. Default: "fginn"

  • params (Optional[Dict[str, Tensor]], optional) – dictionary of parameters for the matching function. Default: None

Raises:

NotImplementedError – for another match_mode.

See also

  • match_fginn(): the function of the default match_mode="fginn", whose own th defaults to 0.8.

  • match_adalam(): the function of match_mode="adalam", which also takes the image sizes.

  • DescriptorMatcher: the module form of the matchers that read descriptors only.

  • LightGlueMatcher: a learned matcher with the same call, which also takes the image sizes.

forward(desc1, desc2, lafs1, lafs2)[source]#

Run forward.

Parameters:
  • desc1 (Tensor) – Batch of descriptors of a shape (B1, D).

  • desc2 (Tensor) – Batch of descriptors of a shape (B2, D).

  • lafs1 (Tensor) – LAFs of a shape (1, B1, 2, 3).

  • lafs2 (Tensor) – LAFs of a shape (1, B2, 2, 3).

Return type:

Tuple[Tensor, Tensor]

Returns:

  • dists of the matching function, see match_nn(), shape of (B3, 1).

  • Long torch.Tensor indexes of matching descriptors in desc1 and desc2,

    shape of (B3, 2) where \(0 <= B3 <= B1\).

class kornia.feature.LocalFeatureMatcher(local_feature, matcher)[source]#

Detect, describe and match the local features of two images with a descriptor matcher.

matcher = LocalFeatureMatcher(local_feature, DescriptorMatcher("snn", 0.8)) builds the module and out = matcher({"image0": img0, "image1": img1}) runs local_feature on both batches of images, which the LocalFeature presets take as grayscale (B, 1, H, W), except SIFTFeature, KeyNetHardNet and KeyNetAffNetHardNet, which take one image per call, (1, 1, H, W). It matches the descriptors of each pair of images and returns a dict of the matches of all pairs: keypoints0 and keypoints1 (NC, 2), lafs0 and lafs1 (1, NC, 2, 3), confidence (NC,) and the pair of each match, batch_indexes (NC,). Features already computed for an image can be passed instead of running local_feature on it. For a matcher that reads the LAFs, such as LightGlueMatcher, call it on the outputs of the local feature yourself; for matching without a detector, use LoFTR.

Convention:
Parameters:
Returns:

Dictionary with image correspondences and confidence scores.

Return type:

Dict[str, torch.Tensor]

See also

  • LocalFeature: the detector and descriptor pipeline that local_feature usually is.

  • DescriptorMatcher: the descriptor-only matcher it takes.

  • LightGlueMatcher: a learned matcher, which takes the LAFs and the image sizes as well.

  • LoFTR: matches two images directly, without a detector.

  • RANSAC: the next step, which keeps the matches one geometric model explains.

Example

>>> img1 = torch.rand(1, 1, 320, 200)
>>> img2 = torch.rand(1, 1, 128, 128)
>>> input = {"image0": img1, "image1": img2}
>>> gftt_hardnet_matcher = LocalFeatureMatcher(
...     GFTTAffNetHardNet(10), kornia.feature.DescriptorMatcher('snn', 0.8)
... )
>>> out = gftt_hardnet_matcher(input)
forward(data)[source]#

Run forward.

Parameters:

data (Dict[str, Tensor]) – dictionary containing the input data in the following format:

Keyword Arguments:
  • image0 – left image with shape (N, 1, H1, W1). Required also with lafs0 and descriptors0: its first dimension gives the number of image pairs.

  • image1 – right image with shape (N, 1, H2, W2); not read when lafs1 and descriptors1 are given.

  • mask0 (optional) – left image mask. ‘0’ suppresses detection, with shape (N, H1, W1) or (N, 1, H1, W1).

  • mask1 (optional) – right image mask. ‘0’ suppresses detection, with shape (N, H2, W2) or (N, 1, H2, W2).

  • lafs0 (optional) – pre-extracted LAFs of image0, (N, M0, 2, 3). When lafs0 and descriptors0 are both given, they are matched and local_feature does not run on image0.

  • descriptors0 (optional) – pre-extracted descriptors of image0, (N, M0, D).

  • lafs1 (optional) – pre-extracted LAFs of image1, (N, M1, 2, 3); with descriptors1, as for image0.

  • descriptors1 (optional) – pre-extracted descriptors of image1, (N, M1, D).

Return type:

Dict[str, Tensor]

Returns:

  • keypoints0, matching keypoints from image0 (NC, 2).

  • keypoints1, matching keypoints from image1 (NC, 2).

  • confidence, 1 - descriptor distance (NC,). This lies in \([0, 1]\) only for the ratio-based DescriptorMatcher modes (snn, smnn); nn and mnn return raw distances, which are unbounded for an arbitrary descriptor, so the confidence has no lower bound in general – it goes at least down to -1, the value reached by unit-norm descriptors, whose distance tops out at 2.0.

  • lafs0, matching LAFs from image0 (1, NC, 2, 3).

  • lafs1, matching LAFs from image1 (1, NC, 2, 3).

  • batch_indexes, batch indexes for the keypoints and lafs (NC,).

Learned matchers#

class kornia.feature.LightGlueMatcher(feature_name='disk', params=None)[source]#

Match descriptors and LAFs with LightGlue, called like GeometryAwareDescriptorMatcher.

matcher = LightGlueMatcher("disk") loads the LightGlue weights trained for that local feature, and scores, idxs = matcher(desc1, desc2, lafs1, lafs2, hw1, hw2) takes the descriptors (B1, D) and (B2, D) of two images (or (1, B1, D) and (1, B2, D)), their LAFs (1, B1, 2, 3) and (1, B2, 2, 3) in pixels and, optionally, the image sizes (H, W), and returns LightGlue’s matching scores (B3, 1), higher for better matches, and the index pairs (B3, 2), in the format of GeometryAwareDescriptorMatcher. The descriptors must come from the feature the weights were trained for, see feature_name in the Convention block. To pass batches of keypoints in dicts and read every output of the network, use LightGlue; for an exported ONNX model, use OnnxLightGlue. It is based on the original code of “LightGlue: Local Feature Matching at Light Speed”, see [LSP23] for more details.

Convention:
  • lafs1 and lafs2 use the pixel convention of laf_from_center_scale_ori() (see Local features), and their centres go to LightGlue unchanged as the keypoints. hw1 and hw2 are the (H, W) of each image, which become LightGlue’s image_size (W, H); without them, the image size is taken as the largest keypoint coordinates of that image, (max x, max y).

  • Choose feature_name for the source of the descriptors (case-insensitive). The network receives the following data besides keypoints:

    • "aliked": use the 128-D descriptors of ALIKED. The 64-D "aliked-t16" descriptors are incompatible with these weights.

    • "dedodeb", "dedodeg": use the B, respectively G, descriptors of DeDoDe. Build the LAFs at keypoints minus 0.5, because DeDoDe puts pixel i at i + 0.5.

    • "disk", "raco-aliked", "superpoint", "xfeat": use descriptors from the named feature and no additional LAF attributes. For "xfeat", pass the 64-D descriptors of detectAndCompute() and LAFs centred on its keypoints, such as laf_from_center_scale_ori(keypoints[None]), and call the matcher under torch.no_grad() or torch.inference_mode(): it raises on XFeat’s inference tensors with gradients enabled (#6533).

    • "sift": use SIFTFeatureScaleSpace with descriptor_layout="opencv". The weights expect the scale and orientation of the COLMAP keypoints they were trained on: the negated LAF orientation in \((-\pi, \pi]\), and a sixth of the LAF scale, which is the detection sigma for 6-sigma DoG frames such as SIFTFeatureScaleSpace’s. The descriptors must be in OpenCV’s SIFT layout, which these weights expect, while kornia’s SIFT descriptors use another order of the same values: create the feature with descriptor_layout="opencv", or convert the descriptors with convert_sift_descriptor_layout(descs, "kornia", "opencv") (convert_sift_descriptor_layout()).

    • "doghardnet", "dog_affnet_hardnet": use DoGHardNet or DoGAffNetHardNet. Pass the LAFs the HardNet descriptors were computed on, at the scale the weights were trained with: 12 sigma, six times OpenCV’s KeyPoint.size and twice the 6-sigma DoG frames of SIFTFeatureScaleSpace. DoGHardNet and DoGAffNetHardNet return such LAFs and their descriptors; with another 6-sigma DoG detector, apply scale_laf() with 2.0 to its LAFs before describing them. The network takes a sixth of the LAF scale, which is KeyPoint.size for these frames, and the LAF orientation in \([0, 2\pi)\).

    • "keynet_affnet_hardnet": use KeyNetAffNetHardNet. The network receives the ends of the two LAF axes, c + A[:, 0] and c + A[:, 1], normalised like the keypoints.

  • The first returned value is LightGlue’s matching score, not a distance: higher is better, and only matches scoring above filter_threshold (0.1 by default) are returned. The index rows are (index in desc1, index in desc2). Fewer than two descriptors on either side give no match.

  • All-zero padding LAFs are not filtered out: such a slot reaches the network as a keypoint at (0, 0). Drop them with laf_is_filled() first.

Parameters:
  • feature_name (str, optional) – type of feature for matching, one of known_modes. Default: "disk"

  • params (Optional[Dict], optional) – keyword arguments of LightGlue, such as filter_threshold. Default: None

See also

forward(desc1, desc2, lafs1, lafs2, hw1=None, hw2=None)[source]#

Run forward.

Parameters:
  • desc1 (Tensor) – Batch of descriptors of a shape (B1, D), or (1, B1, D) for a batch of one.

  • desc2 (Tensor) – Batch of descriptors of a shape (B2, D), or (1, B2, D) for a batch of one.

  • lafs1 (Tensor) – LAFs of a shape (1, B1, 2, 3).

  • lafs2 (Tensor) – LAFs of a shape (1, B2, 2, 3).

  • hw1 (Optional[Tuple[int, int]], optional) – (H, W) of the first image; see the Convention block. Default: None

  • hw2 (Optional[Tuple[int, int]], optional) – (H, W) of the second image; see the Convention block. Default: None

Return type:

Tuple[Tensor, Tensor]

Returns:

  • LightGlue’s matching score of each match, higher is better, shape of (B3, 1).

  • Long torch.Tensor indexes of matching descriptors in desc1 and desc2,

    shape of (B3, 2) where \(0 <= B3 <= B1\).

class kornia.feature.LightGlue(features='superpoint', **conf_)[source]#

Match the keypoints and descriptors of image pairs with the LightGlue transformer, given as dicts.

lightglue = LightGlue("disk") loads the weights trained for that local feature and out = lightglue({"image0": feats0, "image1": feats1}) takes, for each image, a dict of keypoints (B, M, 2) as (x, y) pixels, descriptors (B, M, D) and image_size (B, 2) as (W, H), and returns a dict whose matches0 (B, M) holds the index in image1 of the match of each keypoint of image0, -1 for an unmatched one, with its score in matching_scores0 (B, M); matches and scores list the matched index pairs (S, 2) and scores (S,) of each pair of images. The network is a series of transformer layers, with early stopping and point pruning unless the configuration turns them off. For the call of the other kornia matchers, on descriptors and LAFs, use LightGlueMatcher; for an exported ONNX model run by onnxruntime, use OnnxLightGlue.

Convention:
  • Complete learned weights return the model in evaluation mode, even if derived buffers are absent from the checkpoint. Random or partially loaded learned weights leave it in training mode.

  • The keypoints of each image are (x, y) pixels and image_size is (W, H); an image tensor gives the size from its last two dimensions instead. forward normalises the keypoints to (k - size / 2) / (max(size) / 2), as the reference implementation does, and raises for a keypoint outside \([-1, 1]\) after that. The keypoints can be wider than the model, like the float32 keypoints of kornia’s detectors with a float16 or bfloat16 model; the positional encoding runs in the model’s dtype.

  • The "sift", "doghardnet" and "dog_affnet_hardnet" configurations, which set add_scale_ori, also read scales and oris (radians) of each image; the last two multiply scales by their scale_coef of 1/6 first, so they take the LAF scale. "keynet_affnet_hardnet", which sets add_laf, reads lafs. LightGlueMatcher builds them from LAFs.

  • The matching scores, matching_scores0, matching_scores1 and scores, are in the dtype of the descriptors with mp=False. With mp=True, autocast sets it, for every configuration and also when point pruning removes every point. On CPU, autocast runs in float16 for a float16 model and in bfloat16, CPU autocast’s default, for any other: a float16 model gives float16 scores for float16 and float32 descriptors, a float32 or bfloat16 model gives bfloat16 scores for float32 and bfloat16 descriptors, and a float64 model gives float64 scores for float64 descriptors. Float16 descriptors with any other model, and bfloat16 descriptors with a float16 model, are not supported there (#5929). On MPS, float32, float16 and bfloat16 descriptors give float32 scores; on CUDA, float32 descriptors give float32 scores, and a float64 model gives float64 scores for float64 descriptors, as on CPU, because autocast does not cast float64. An image without descriptors runs no layer, and its zero scores keep the descriptor dtype, also with mp=True.

  • Each pair of a batch gives the matches and scores it gives on its own when point pruning can run, that is with width_confidence > 0, no padding to the static_lengths of LightGlue.compile (which wraps the transformer layers in torch.compile()), and an image with more keypoints than pruning_min_kpts(device) returns: any number on CPU and MPS, more than 1536 on CUDA with the default flash=True and more than 1024 without. Pruning keeps a different number of points in each pair, so then the pairs run one at a time: matches0, matches1, matching_scores0, matching_scores1, prune0 and prune1 stack the rows of the pairs, matches and scores list them, stop is the largest number of layers that a pair ran, and there is no log_assignment. Otherwise a batch runs as one, and early stopping (depth_confidence) ends it at a layer only when every pair meets its own criterion. A pair can therefore run more layers in a batch than on its own, and its matches can differ. Early stopping counts the real keypoints only, so padding to the static_lengths of LightGlue.compile does not change the layer at which a pair stops.

Parameters:
  • features (Optional[str], optional) – The type of local features to match, whose configuration and pretrained weights are loaded: one of "aliked", "dedodeb", "dedodeg", "disk", "dog_affnet_hardnet", "doghardnet", "keynet_affnet_hardnet", "raco-aliked", "sift", "superpoint" and "xfeat". None builds the model from default_conf and the keyword arguments and downloads no weights. Default: "superpoint"

  • **conf – keyword arguments that override entries of default_conf and of the configuration of features, such as filter_threshold or depth_confidence. The configuration of features sets the defaults, and an explicit keyword argument always wins, except weights, which must name the checkpoint of features when features is given; weights=None, the value in default_conf, counts as not given. This departs on purpose from cvg/LightGlue, which applies the configuration of features after the keyword arguments, so that the tuning knobs a preset sets, such as "xfeat"’s width_confidence, can be changed.

See also

  • LightGlueMatcher: the same network on descriptors and LAFs, returning scores and index pairs like the other kornia matchers.

  • OnnxLightGlue: an exported LightGlue model run by onnxruntime, for one image pair on CPU or CUDA.

  • LoFTR: a transformer matcher that takes the images themselves, without a detector.

forward(data)[source]#

Match keypoints and descriptors between two images.

Return type:

dict

Input (dict):
image0: dict

keypoints: [B x M x 2] descriptors: [B x M x D] image: [B x C x H x W] or image_size: [B x 2] scales, oris: [B x M], with add_scale_ori lafs: [B x M x 2 x 3], with add_laf

image1: dict

keypoints: [B x N x 2] descriptors: [B x N x D] image: [B x C x H x W] or image_size: [B x 2] scales, oris: [B x N], with add_scale_ori lafs: [B x N x 2 x 3], with add_laf

Output (dict):

log_assignment: [B x M+1 x N+1], not for a batch that prunes points (see the Convention block) matches0: [B x M] matching_scores0: [B x M] matches1: [B x N] matching_scores1: [B x N] matches: List[[Si x 2]], scores: List[[Si]] stop, prune0, prune1: diagnostics of early stopping and point pruning

class kornia.feature.OnnxLightGlue(weights=None, device='cpu')[source]#

Match the keypoints and descriptors of one image pair with an exported LightGlue ONNX model.

matcher = OnnxLightGlue("disk", device="cpu") downloads an exported LightGlue [LSP23] model, or loads your own ONNX file, into an onnxruntime session, and out = matcher(data) takes the dicts of LightGlue for a batch of one: float32 keypoints (1, M, 2) in pixels and descriptors (1, M, D) on that device, with image_size (1, 2) as (W, H) or an image. It returns the matched index pairs matches (S, 2) and their scores (S,). It needs onnxruntime and runs on CPU or CUDA only. For batches, other devices or dtypes, scales, orientations or LAFs, or the full outputs of the network, use LightGlue instead.

If you have trained your own LightGlue model, see fabio-sim/LightGlue-ONNX for how to export the model to ONNX and optimize it.

Convention:
  • See the Convention block on LightGlue: the keypoints are normalised the same way, with image_size in the order (W, H), and the same range check applies. Unlike LightGlue, it takes float32 inputs with a batch of one and no scales, orientations or LAFs, and it runs on CPU or CUDA only.

  • Known defect: the session and its inputs are bound to GPU 0 for every CUDA device, so device="cuda:1" does not run on GPU 1 (#6427).

Parameters:
  • weights (str | None, optional) – Pretrained weights, or a path to your own exported ONNX model. Available pretrained weights are 'disk', 'superpoint', 'disk_fp16', and 'superpoint_fp16'. Note that FP16 requires CUDA. Defaults to 'disk_fp16' if device is CUDA, and 'disk' if CPU. Default: None

  • device (Union[str, device, None], optional) – 'cpu' or 'cuda' to run inference on; another device type raises ValueError. None means 'cpu', as in the other kornia feature constructors, not torch’s current default device. Default: "cpu"

See also

  • LightGlue: the PyTorch network, which also runs batches, on MPS, and in float16, bfloat16 or float64 (see its Convention block), and returns every output.

  • LightGlueMatcher: LightGlue on descriptors and LAFs, returning scores and index pairs like the other kornia matchers.

forward(data)[source]#

Match keypoints and descriptors between two images.

The output contains the matches (the indices of the matching keypoint pairs between the first and second image) and the corresponding confidence scores. Only a batch size of 1 is supported.

Parameters:

data (dict[str, dict[str, Tensor]]) – Dictionary containing both images and the keypoints and descriptors thereof.

Return type:

dict[str, Tensor]

Returns:

Dictionary containing the matches and scores.

data (dict):
image0 (dict):

keypoints (float32): (1, M, 2)

descriptors (float32): (1, M, D)

image: (1, C, H, W) or image_size: (1, 2)

image1 (dict):

keypoints (float32): (1, N, 2)

descriptors (float32): (1, N, D)

image: (1, C, H, W) or image_size: (1, 2)

output (dict):

matches (int64): (S, 2)

scores (float32): (S,)

class kornia.feature.LoFTR(pretrained='outdoor', config=default_cfg)[source]#

Match two grayscale images without a detector, by transformer-matched dense features (LoFTR).

loftr = LoFTR("outdoor") loads pretrained weights and out = loftr({"image0": img0, "image1": img1}) takes two batches of N grayscale images (N, 1, H, W) in \([0, 1]\), the two possibly of different sizes, and returns a dict of the matches of all pairs: the pixel positions keypoints0 and keypoints1 (NC, 2), as (x, y) in the frame of the reference implementation, half a pixel off kornia’s (see the Convention block), their confidence (NC,) and the pair of each match, batch_indexes (NC,). It needs no keypoints or descriptors: the matches of image0 lie on a grid of 8-pixel cells, not at detected keypoints. To match one image against many, compute its backbone features, which do not depend on the other image, once with extract_features() and pass them as features0 or features1. For matching detected keypoints and their descriptors, use LightGlueMatcher or LocalFeatureMatcher instead.

This is based on the original code of “LoFTR: Detector-Free Local Feature Matching with Transformers”, see [SSW+21] for more details.

Convention:
  • image0 and image1 are grayscale (N, 1, H, W) images in \([0, 1]\), the input of the pretrained models. With the default config, whose resolution (8, 2) every pretrained model uses, the two images can differ in size, and their heights and widths do not need to be multiples of 8 (the reference implementation expects multiples of 8).

  • With that resolution, keypoints0 lie on the grid of the 1/8-resolution coarse feature map, at 8 * (column, row) of a coarse cell, and keypoints1 at 8 * (column, row) of the matching cell of image1 plus the offset found by the fine stage, at most 4 pixels along each axis. Both are \((x, y)\) in the frame of the reference implementation, in which the pretrained models put the centre of pixel \((x, y)\) at \((x + 0.5, y + 0.5)\): subtract 0.5 to get kornia’s frame (Local features), in which pixel centres have integer coordinates. The keypoints are float32 for a float16 or bfloat16 model and float64 for a float64 one; confidence keeps the model dtype.

  • confidence is the coarse matching score of the match, a dual-softmax score with the default match_type; only matches above max(thr, 0) are returned, with the thr of config["match_coarse"], 0.2 by default.

  • Coarse matches within border_rm cells of an edge are dropped (2 cells, 16 pixels by default). With mask0 or mask1, each sample’s border is measured from its valid region, assumed to be a top-left-aligned rectangle with padding on the right and bottom. Either mask may be supplied alone; the other image is then treated as fully valid. Masks are sampled at the coarse cell centres.

Parameters:
  • pretrained (Optional[str], optional) – Download and set pretrained weights to the model. Options: ‘outdoor’, ‘indoor’, ‘indoor_new’. ‘outdoor’ is trained on the MegaDepth dataset and ‘indoor’ and ‘indoor_new’ on the ScanNet. ‘indoor_new’ also switches the positional encoding of the copy to temp_bug_fix=True, which is what its checkpoint was trained with. The weighted model comes back in evaluation mode. None builds random weights in training mode, where CoarseMatching runs its supervised training branch and needs ground-truth data; call .eval() for inference, also after loading your own weights. Default: "outdoor"

  • config (dict[str, Any], optional) – Dict with initialization parameters. Do not pass it, unless you know what you are doing. The model works on its own deep copy, so the dict you pass is never modified. Default: default_cfg

Returns:

Dictionary with image correspondences and confidence scores.

See also

Example

>>> img1 = torch.rand(1, 1, 320, 200)
>>> img2 = torch.rand(1, 1, 128, 128)
>>> input = {"image0": img1, "image1": img2}
>>> loftr = LoFTR('outdoor')
>>> out = loftr(input)
extract_features(image)[source]#

Compute the features of images that do not depend on the image they are matched with.

These are the backbone’s coarse and fine feature maps, the coarse one with its positional encoding. Pass the returned dict to forward() as features0 or features1 instead of image0 or image1 to match an image against many others without running the backbone again for every pair.

Parameters:

image (Tensor) – grayscale images with shape (N, 1, H, W).

Return type:

dict[str, Tensor]

Returns:

  • coarse, position-encoded 1/8-resolution features (N, C_c, ceil(H / 8), ceil(W / 8)).

  • fine, 1/2-resolution features (N, C_f, ceil(H / 2), ceil(W / 2)). With the default config this is 128 channels, about 96 MB in float32 for a 1024 x 736 image.

Example

>>> loftr = LoFTR(None).eval()
>>> images = [torch.rand(1, 1, 64, 80), torch.rand(1, 1, 72, 64), torch.rand(1, 1, 64, 64)]
>>> features = [loftr.extract_features(image) for image in images]
>>> out = loftr({"features0": features[0], "features1": features[1]})
>>> out = loftr({"features0": features[0], "image1": images[2]})
forward(data)[source]#

Run forward.

Parameters:

data (dict[str, Any]) – dictionary containing the input data in the following format:

Keyword Arguments:
  • image0 – left image with shape (N, 1, H1, W1).

  • image1 – right image with shape (N, 1, H2, W2).

  • features0 (optional) – instead of image0, its extract_features() output.

  • features1 (optional) – instead of image1, its extract_features() output.

  • mask0 (optional) – left image mask. ‘0’ indicates a padded position (N, H1, W1).

  • mask1 (optional) – right image mask. ‘0’ indicates a padded position (N, H2, W2).

Return type:

dict[str, Tensor]

Returns:

  • keypoints0, matching keypoints from image0 (NC, 2).

  • keypoints1, matching keypoints from image1 (NC, 2).

  • confidence, the coarse matching score, see the Convention block (NC,).

  • batch_indexes, batch index of each match (NC,).