Local affine frames (LAF)#
A local affine frame (LAF) describes a keypoint by a 2 x 3 matrix [A | c], and a set of them is a
(B, N, 2, 3) tensor. The centre c = (x, y) is in pixels, and the shape A maps the canonical patch frame
onto the image, in pixels: its scale is \(\sqrt{|\det A|}\) and its orientation atan2(a12, a11), in degrees
counter-clockwise on screen for a LAF of positive determinant (clockwise for a mirrored one).
Local features gives the frame and the conversion of OpenCV keypoints. kornia’s
scale-space and KeyNet detectors (ScaleSpaceDetector, MultiResolutionDetector,
KeyNetDetector) return LAFs, LAFDescriptor describes them, and match_fginn(),
match_adalam() and LightGlueMatcher read them; laf_from_center_scale_ori() turns the keypoints of
the other detectors into LAFs.
To keep the detections of a fixed-size detector output, use
laf_is_filled(); to also drop singular and non-finite frames,laf_is_valid(); to keep the regions that lie inside the image,laf_is_inside_image().To sample patches to describe, use
extract_patches_from_pyramid(), which reads each LAF from a pyramid level chosen by its scale and limits aliasing.extract_patches_simple()reads the full-resolution image only and aliases for large LAFs; a highly anisotropic LAF can still alias along its long axis.To drop the orientation, use
make_upright(); to set or add angles,set_laf_orientation()orrotate_laf(); to estimate the orientation from the image,LAFOrienter.To adapt the affine shape to the image, use
LAFAffineShapeEstimator(the second-moment matrix of the patch) orLAFAffNetShapeEstimator(the learned AffNet);PassLAFleaves the LAFs as they are.
Creating and reading LAFs#
- kornia.feature.laf_from_center_scale_ori(xy, scale=None, ori=None)[source]#
Build circular LAFs from keypoint centres, scales and orientations.
laf = laf_from_center_scale_ori(xy, scale, ori)takes keypoint centresxy(B, N, 2), scales(B, N, 1, 1)(1 when omitted) and orientations(B, N, 1)in degrees (0 when omitted), and returns the(B, N, 2, 3)LAFs[scale * R(ori) | xy], whose regions are circles of radiusscalearoundxy. This turns the keypoints of a detector that returns no LAFs, such asDISKorXFeat, into the LAFs thatLAFDescriptorand the LAF matchers take, e.g.laf_from_center_scale_ori(keypoints[None]). For an elliptical region, useellipse_to_laf()instead.- Convention:
A LAF is a
(B, N, 2, 3)tensor[A | c], with the centrec = (x, y)andAin pixels.Amaps the patch frame onto the image: its first column is the image of the patch’s x axis and its second column that of the patch’s y axis. Local features gives the pixel frame, the patch thatextract_patches_from_pyramid()samples and the conversion of OpenCV keypoints.This function returns
A = scale * R, withR = [[cos, sin], [-sin, cos]]oforiin degrees, the matrix ofangle_to_rotation_matrix(). A positiveoriturns the patch’s x axis counter-clockwise on screen, with image y pointing down.For any LAF, the scale is \(\sqrt{|\det A|}\) (
get_laf_scale(), which adds1e-10to the determinant) and the orientation isatan2(a12, a11)in degrees (get_laf_orientation()), within[-180, 180]: a LAF made withori = 181reads back-179. For \(\det A > 0\),A = U RwithUthe uprightAthatmake_upright()returns, up to its stabilizers, andRthe rotation matrix of that orientation.A negative determinant mirrors the patch frame. Increasing its orientation turns its x axis clockwise on screen. With a nonzero scale, this constructor produces a positive determinant.
- Parameters:
- Return type:
- Returns:
LAF
(B, N, 2, 3)
See also
get_laf_center(): reads the centres back.get_laf_scale(): reads the scales back.get_laf_orientation(): reads the orientations back, within[-180, 180].ellipse_to_laf(): LAFs of elliptical regions, given by their ellipse coefficients.laf_from_three_points(): LAFs from the images of three patch points.
Example
>>> xy = torch.tensor([[[5.0, 7.0]]]) >>> laf_from_center_scale_ori(xy, torch.full((1, 1, 1, 1), 2.0), torch.tensor([[[30.0]]])) tensor([[[[ 1.7321, 1.0000, 5.0000], [-1.0000, 1.7321, 7.0000]]]])
- kornia.feature.ellipse_to_laf(ells)[source]#
Convert ellipses in the Oxford
[x, y, a, b, c]format to upright LAFs.laf = ellipse_to_laf(ells)takes(B, N, 5)ellipses[x, y, a, b, c], as the Oxford affine-region files write them, and returns the upright(B, N, 2, 3)LAFs of those regions: centred on(x, y), with an orientation of 0. To draw the region of a LAF, uselaf_to_boundary_points().- Convention:
An ellipse
[x, y, a, b, c]is the region \(a u^2 + 2 b u v + c v^2 \le 1\) in the coordinates(u, v)relative to its centre(x, y), as in the Oxford affine-region files.(x, y)becomes the LAF centre unchanged.Ais the lower-triangular matrix with a positive diagonal andA @ A.T == inverse([[a, b], [b, c]]): the Cholesky factor of the inverse ellipse matrix, as VLFeat’svl_frame2oell.mbuilds it, not the symmetric inverse square root (the two differ by a rotation). Either one maps the unit circle onto the ellipse; the lower-triangular one is whatmake_upright()returns, up to its stabilizers, for any LAF of that region.The conversion checks only the shape
(B, N, 5); it does not check the ellipse values and does not raise on them. An ellipse that is degenerate or indefinite (a,cora * c - b * bnot positive) is not a bounded region; its LAF hasinfornanentries, or huge finite ones where rounding leavesa - b * b / cbarely positive. Rounding also works the other way: a nearly singular valid ellipse can giveinfornan.laf_is_valid()screens out theinfandnanresults, which anisnantest misses forinf; the huge finite ones pass it, so check the ellipse itself where that matters.
See also vlfeat/vlfeat
- Parameters:
ells (
Tensor) – torch.Tensor(B, N, 5)of ellipses in Oxford format [x y a b c].- Return type:
- Returns:
LAF
(B, N, 2, 3)
See also
laf_to_boundary_points(): points on the outline of a LAF’s region, to draw it.make_upright(): the same upright LAF, from any LAF of the region.laf_from_center_scale_ori(): circular LAFs from keypoint centres, scales and orientations.
Example
>>> input = torch.ones(1, 10, 5) # BxNx5 >>> output = ellipse_to_laf(input) # BxNx2x3
- kornia.feature.laf_from_three_points(threepts)[source]#
Build LAFs from three points each: the tips of the patch’s x and y axes, then the centre.
laf = laf_from_three_points(pts)takes(B, N, 2, 3)tensors whose columns are three pointsp0,p1andcand returns the(B, N, 2, 3)LAFs[p0 - c, p1 - c | c], whose patch x and y axes end atp0andp1. Collinear points give a singular shapeA. For LAFs from centres, scales and orientations, uselaf_from_center_scale_ori()instead.- Convention:
See the Convention block on
laf_to_three_points(), which this inverts: the columns ofthreeptsare the images of the patch points(1, 0),(0, 1)and(0, 0), in that order.
See also
laf_to_three_points(): the inverse.laf_from_center_scale_ori(): circular LAFs from centres, scales and orientations.
- kornia.feature.get_laf_center(LAF)[source]#
Read the centre of each LAF, its keypoint position
(x, y).xy = get_laf_center(laf)takes(B, N, 2, 3)LAFs and returns their centresc = (x, y), the last column of each LAF, as a(B, N, 2)tensor in the LAF’s units and dtype. The result is a view oflaf, not a copy. To build LAFs from keypoints, uselaf_from_center_scale_ori().- Convention:
The centre is
(x, y)in pixels, with pixel centres at integers; see Local features.
See also
laf_from_center_scale_ori(): LAFs built from centres, with optional scales and orientations.get_laf_scale(): the scale of the same LAFs.get_laf_orientation(): the orientation of the same LAFs.laf_to_three_points(): the centre together with the tips of the patch’s two axes.
Example
>>> input = torch.ones(1, 5, 2, 3) # BxNx2x3 >>> output = get_laf_center(input) # BxNx2
- kornia.feature.get_laf_scale(LAF)[source]#
Read the scale of each LAF, the square root of the absolute determinant of its shape.
scale = get_laf_scale(laf)takes(B, N, 2, 3)LAFs and returns their scales \(\sqrt{|\det A|}\) as a(B, N, 1, 1)tensor, in pixels for LAFs in pixels: the radius of a circular LAF’s region, and the geometric mean of the two semi-axes of an elliptical one. The result broadcasts against the shapeA, so usescale_laf(laf, 1.0 / scale)to normalize an ordinary LAF. For the orientation and centre, useget_laf_orientation()andget_laf_center().- Convention:
See the Convention block on
laf_from_center_scale_ori(), which defines the scale.The
1e-10added to the determinant makes frames smaller than about1e-4px read too large.The determinant of a float16 LAF is formed in float32, so that a LAF whose entry products leave the float16 range does not overflow; the scale is returned in the LAF dtype.
See also
scale_laf(): multiplies the scale by a coefficient, keeping the centre.get_laf_orientation(): the orientation of the same LAFs.get_laf_center(): the centre of the same LAFs.laf_from_center_scale_ori(): LAFs of given scales, built from keypoint centres.
Example
>>> input = torch.ones(1, 5, 2, 3) # BxNx2x3 >>> output = get_laf_scale(input) # BxNx1x1
- kornia.feature.get_laf_orientation(LAF)[source]#
Read the orientation of each LAF in degrees, from the first row of its shape.
ori = get_laf_orientation(laf)takes(B, N, 2, 3)LAFs and returns their orientationsatan2(a12, a11)in degrees, within[-180, 180], as a(B, N, 1)tensor. It reads only the first row of the shapeA, which forA = U R, an upright factor times the rotation matrix of an angle, gives that angle whatever the shear ofU. For a circular similarity frameA = s R, OpenCV’sKeyPoint.angleis(-ori) mod 360. For a general affine frame, useatan2(a21, a11)in degrees modulo 360; an OpenCV keypoint cannot retain its affine shape or reflection. To change the orientation, useset_laf_orientation()orrotate_laf().- Convention:
See the Convention block on
laf_from_center_scale_ori(), which defines the orientation.
See also
set_laf_orientation(): sets the orientation to given angles.rotate_laf(): adds angles to the orientation.make_upright(): removes the rotation, so that the orientation reads 0.laf_from_center_scale_ori(): LAFs of given orientations, built from keypoint centres.get_laf_scale(): the scale of the same LAFs.get_laf_center(): the centre of the same LAFs.
Example
>>> input = torch.ones(1, 5, 2, 3) # BxNx2x3 >>> output = get_laf_orientation(input) # BxNx1
- kornia.feature.laf_to_three_points(laf)[source]#
Convert LAFs to three points: the tips of the patch’s x and y axes, then the centre.
pts = laf_to_three_points(laf)takes(B, N, 2, 3)LAFs and returns(B, N, 2, 3)tensors whose columns are the image pointsc + A[:, 0],c + A[:, 1]andc. Mapping the three columns through a point transformation and converting them back withlaf_from_three_points()transforms the LAFs, asperspective_transform_lafs()does for a homography.- Convention:
The columns of the result are the points
c + A[:, 0],c + A[:, 1]andc, in that order: the images of the patch points(1, 0),(0, 1)and(0, 0), at the frame’s scale rather than of unit length.laf_from_three_points()is the inverse.
See also
laf_from_three_points(): the inverse.perspective_transform_lafs(): maps these three points through a homography and converts them back.laf_to_boundary_points(): points on the whole outline of the region.
- kornia.feature.laf_to_boundary_points(LAF, n_pts=50)[source]#
Sample points on the outline of each LAF’s region, after its centre, to draw it.
pts = laf_to_boundary_points(laf, n_pts)takes(B, N, 2, 3)LAFs and returns(B, N, n_pts, 2)points(x, y)in the LAF’s units: the centre, thenn_pts - 1points around the ellipsec + A uover the unit vectorsu. Drawn as one line, the points of a LAF show its region and a radius from the centre to the tip of the patch’s y axis. For the centre and the tips of both axes only, uselaf_to_three_points().- Convention:
The first point is the centre
c. The othern_pts - 1points arec + A @ (sin t, cos t)fortfrom 0 to \(2 \pi\) in equal steps: they start and end atc + A[:, 1], the tip of the patch’s y axis, and so close the outline of the region.
- Parameters:
- Returns:
(B, N, n_pts, 2)- Return type:
torch.Tensor of boundary points LAF
See also
laf_to_three_points(): only the centre and the tips of the patch’s two axes.ellipse_to_laf(): the LAF of an ellipse given by its coefficients.
Transforming LAFs#
- kornia.feature.scale_laf(laf, scale_coef)[source]#
Multiply the shape of each LAF by a coefficient, keeping its centre.
scaled = scale_laf(laf, k)takes(B, N, 2, 3)LAFs and a coefficientk, a float or a tensor such as one coefficient per LAF in(B, N, 1, 1), and returns the(B, N, 2, 3)LAFs[k A | c], whose regions are scaled by|k|about the same centres. A descriptor then sees a larger or smaller area around each keypoint, as with thescaling_coefofLocalFeature. To read the scale, useget_laf_scale().- Convention:
The result is
[k A | c]fork = scale_coef, a product taken elementwise: the centre is kept. With one coefficient per LAF, the scale is multiplied by|k|, and a negativekalso turns the frame by 180 degrees.scale_coefis afloator a tensor that broadcasts against(B, N, 2, 2), such as one coefficient per LAF in a(B, N, 1, 1)tensor; anintis rejected.
- Parameters:
- Return type:
- Returns:
LAF
(B, N, 2, 3)- Raises:
TypeError – if
scale_coefis neither afloatnor a tensor.
See also
get_laf_scale(): reads the scale that this multiplies.laf_from_center_scale_ori(): new LAFs of given scales, built from their centres.LocalFeature: applies this with itsscaling_coefto the detector’s LAFs before describing them.
Example
>>> input = torch.ones(1, 5, 2, 3) # BxNx2x3 >>> scale = 0.5 >>> output = scale_laf(input, scale) # BxNx2x3
- kornia.feature.rotate_laf(LAF, angles_degrees)[source]#
Rotate each LAF’s patch frame by an angle in degrees, adding it to the orientation.
rotated = rotate_laf(laf, angles)takes(B, N, 2, 3)LAFs and angles(B, N, 1)in degrees and returns the(B, N, 2, 3)LAFs whose orientation is the input’s plusangles. Only the patch frame turns: the centre, scale and region, the ellipsec + A uover unit vectorsu, stay the same. To set given orientations instead, useset_laf_orientation().- Convention:
The result is
[A @ R | c], withRthe rotation matrix ofangles_degreesfromangle_to_rotation_matrix(). The orientation grows byangles_degrees(set_laf_orientation()sets it instead), and the centre and the upright factor thatmake_upright()returns are kept.
- Parameters:
- Return type:
- Returns:
LAF oriented with angles
(B, N, 2, 3)
See also
set_laf_orientation(): rotates to given orientations rather than by given angles.get_laf_orientation(): reads the orientation that this adds to.LAFOrienter: rotates each LAF by the angle estimated on its image patch.
- kornia.feature.set_laf_orientation(LAF, angles_degrees)[source]#
Set the orientation of each LAF to an angle in degrees, keeping its region.
oriented = set_laf_orientation(laf, angles)takes(B, N, 2, 3)LAFs and orientations(B, N, 1)in degrees and returns the(B, N, 2, 3)LAFs rotated to them, asrotate_laf()rotates: the centre, the scale and region are kept. With angles of 0, a LAF of positive determinant becomes the upright LAF ofmake_upright(), up to itseps. To add angles to the current orientation, userotate_laf()instead.- Convention:
See the Convention block on
rotate_laf(). This rotates byangles_degreesminus the current orientation, so the orientation reads back asangles_degreeswrapped into[-180, 180].
- Parameters:
- Return type:
- Returns:
LAF oriented with angles
(B, N, 2, 3)
See also
rotate_laf(): adds angles to the current orientation instead.get_laf_orientation(): reads the orientation back.make_upright(): sets the orientation to 0 and drops a reflection too.laf_from_center_scale_ori(): new LAFs with given orientations, built from their centres.
- kornia.feature.make_upright(laf, eps=1e-9)[source]#
Remove the rotation and reflection of LAFs, keeping their regions, centres and scales.
upright = make_upright(laf)takes(B, N, 2, 3)LAFs and returns the(B, N, 2, 3)LAFs of the same regions and centres with an orientation of 0 and no reflection: the shapeAbecomes the lower-triangularUof the Convention block. To set another orientation, useset_laf_orientation(); to re-estimate the shape from the image instead, useLAFAffineShapeEstimator.- Convention:
The result is
[U | c]: the centre is kept, andUis the lower-triangular matrix with a positive diagonal and \(U U^\top = A A^\top\). The region, the ellipsec + A uover the unit vectorsu, and the scale are therefore kept, and the orientation becomes 0.For \(\det A > 0\),
A = U RwithRthe rotation matrix of the LAF’s orientation, as inlaf_from_center_scale_ori(). For \(\det A < 0\) the reflection is dropped too:Uhas the determinant \(|\det A|\).The equalities above hold up to two stabilizers:
Uis built from the norm of the first row ofApluseps, and from the scale ofget_laf_scale(), which adds1e-10to the determinant. Both are negligible for a LAF in pixels, but a tiny frame such asA = diag(1e-12, 1)keeps neither its region nor its scale. The centre and the zero orientation are exact.The all-zero LAF that pads an unfilled detector slot (see
laf_is_filled()) is returned unchanged, so the padding stays recognizable.A float16 LAF is rectified in float32, so that a LAF whose entry products leave the float16 range does not overflow; the result is returned in the LAF dtype.
- Parameters:
- Returns:
(B, N, 2, 3)- Return type:
laf
See also
set_laf_orientation(): with angles of 0, the same LAFs for a positive determinant, but a reflection is kept.LAFAffineShapeEstimator: re-estimates the shape from the image patch; withpreserve_orientation=Falseits output is upright too.ellipse_to_laf(): the same upright LAF, built from the coefficients of the region’s ellipse.
Example
>>> input = torch.ones(1, 5, 2, 3) # BxNx2x3 >>> output = make_upright(input) # BxNx2x3
- kornia.feature.perspective_transform_lafs(trans_01, lafs_1)[source]#
Map the LAFs of one image into another through a homography, by mapping three points of each.
lafs_0 = perspective_transform_lafs(trans_01, lafs_1)takes(B, 3, 3)homographies from the pixels of image 1 to those of image 0 and(B, N, 2, 3)LAFs of image 1, and returns the(B, N, 2, 3)LAFs of the same regions in image 0: the centres mapped exactly and the shapes approximated through the mapped tips of the patch axes. To map points, such as the LAF centres alone, usetransform_points().- Convention:
trans_01maps pixels of frame 1 to pixels of frame 0, as intransform_points(), and the centre maps through it exactly. The newAis the chord through the mapped tips of the patch axes: the three points oflaf_to_three_points()are transformed and turned back into a LAF. For an affinetrans_01this equals the Jacobian oftrans_01at the centre timesA; for a perspective one the two differ, the more so the larger the LAF.
- Parameters:
- Return type:
- Returns:
the transformed LAFs in frame 0, of shape
(B, N, 2, 3).- Raises:
TypeError – if
trans_01is not a tensor or is on another device thanlafs_1.ValueError – if the batch sizes differ or
trans_01is not3 x 3.
See also
transform_points(): maps points through the same homographies.laf_to_three_points(): the three points of each LAF that this maps.
Examples
>>> rng = torch.manual_seed(0) >>> lafs_1 = torch.rand(2, 4, 2, 3) # BxNx2x3 >>> lafs_1 tensor([[[[0.4963, 0.7682, 0.0885], [0.1320, 0.3074, 0.6341]], [[0.4901, 0.8964, 0.4556], [0.6323, 0.3489, 0.4017]], [[0.0223, 0.1689, 0.2939], [0.5185, 0.6977, 0.8000]], [[0.1610, 0.2823, 0.6816], [0.9152, 0.3971, 0.8742]]], [[[0.4194, 0.5529, 0.9527], [0.0362, 0.1852, 0.3734]], [[0.3051, 0.9320, 0.1759], [0.2698, 0.1507, 0.0317]], [[0.2081, 0.9298, 0.7231], [0.7423, 0.5263, 0.2437]], [[0.5846, 0.0332, 0.1387], [0.2422, 0.8155, 0.7932]]]]) >>> trans_01 = torch.eye(3).repeat(2, 1, 1) # Bx3x3 >>> trans_01.shape torch.Size([2, 3, 3]) >>> lafs_0 = perspective_transform_lafs(trans_01, lafs_1) # BxNx2x3
- kornia.feature.normalize_laf(LAF, images)[source]#
Normalize pixel LAFs by the image size: centre to [0, 1], shape by min(W - 1, H - 1).
nlaf = normalize_laf(laf, images)takes(B, N, 2, 3)LAFs in pixels and an image tensor(B, CH, H, W), read for its height and width only, and returns the(B, N, 2, 3)LAFs with the centre divided by(max(W - 1, 1), max(H - 1, 1))andAby the smaller of the two. A singleton axis (H == 1orW == 1) has no spatial extent and counts as one pixel, so the conversion stays finite instead of dividing by zero.denormalize_laf()on an image of another size then moves the LAFs to that resolution.- Convention:
The centre becomes
(x / max(W - 1, 1), y / max(H - 1, 1)), which lies in[0, 1]for0 <= x <= W - 1and0 <= y <= H - 1.Ais divided by the smaller of the two denominators as a whole, not per axis, and can exceed 1.Taken to an image of another size with
denormalize_laf(),xbecomesx max(W' - 1, 1) / max(W - 1, 1), which maps corner to corner when neither width is 1. A resize withalign_corners=False, the default ofresize()in bilinear mode, maps pixelxto(x + 0.5) W' / W - 0.5instead, so the two disagree away from the image centre.
- Parameters:
- Returns:
(B, N, 2, 3)- Return type:
the normalized LAF
See also
denormalize_laf(): the inverse, back to the pixels of an image of any size.extract_patches_from_pyramid(): takes LAFs in this frame withnormalize_lafs_before_extraction=False.
- kornia.feature.denormalize_laf(LAF, images)[source]#
Convert LAFs from the frame of normalize_laf back to the pixels of an image.
laf = denormalize_laf(nlaf, images)takes(B, N, 2, 3)LAFs in the frame ofnormalize_laf()and an image tensor(B, CH, H, W), read for its height and width only, and returns the(B, N, 2, 3)LAFs in that image’s pixels: the centre multiplied by(max(W - 1, 1), max(H - 1, 1))andAby the smaller of the two. A singleton axis (H == 1orW == 1) has no spatial extent and counts as one pixel, so the conversion stays finite and round-trips withnormalize_laf(). An image of another size than the one the LAFs were normalized on moves them to its resolution.- Convention:
See the Convention block on
normalize_laf(), which this inverts.
- Parameters:
- Returns:
(B, N, 2, 3), scale in pixels- Return type:
the denormalized LAF
See also
normalize_laf(): the inverse, from pixels to this frame.extract_patches_from_pyramid(): takes LAFs in the normalized frame withnormalize_lafs_before_extraction=False.
Checking LAFs#
- kornia.feature.laf_is_valid(laf)[source]#
Check that each LAF is finite and has a finite, nonzero determinant.
valid = laf_is_valid(laf)takes(B, N, 2, 3)LAFs and returns a boolean(B, N)mask, true where every entry is finite and the shapeAhas a finite, nonzero determinant, so that it maps the patch onto a region of nonzero area. It does not check where the LAF lies: for the LAFs whose region lies inside the image, uselaf_is_inside_image(), and to drop only the padding of a detector’s unfilled slots, uselaf_is_filled()instead.- Convention:
A LAF with a negative determinant, a mirrored frame, is valid. The all-zero padding LAF (see
laf_is_filled()), a rank-1Aand anAwhose determinant overflows are not.The determinant of a float16 LAF is formed in float32, so the LAF is not judged by an intermediate outside the float16 range: one whose entry products overflow float16 (for instance any LAF of scale 256 px or more) is valid, and so is one whose determinant is too small for float16. The determinant of a float32, float64 or bfloat16 LAF is formed in that dtype, where it can overflow.
See also
laf_is_filled(): rejects only the all-zero padding, and accepts singular and non-finite frames.laf_is_inside_image(): checks where the region lies, not whether the frame is regular.KORNIA_CHECK_LAF(): checks only the(B, N, 2, 3)shape, not the values.
Example
>>> laf = torch.eye(2, 3).view(1, 1, 2, 3) >>> laf_is_valid(laf) tensor([[True]])
- kornia.feature.laf_is_filled(laf)[source]#
Check which slots hold a detection rather than the zero-LAF padding.
filled = laf_is_filled(lafs)takes the(B, N, 2, 3)LAFs of a detector and returns a boolean(B, N)mask, false for all-zero padded frames: index the LAFs, responses and descriptors of imagebwithlaf_is_filled(lafs)[b]to keep its detections. A filled frame need not be usable; to also drop singular and non-finite frames, uselaf_is_valid()instead.- Convention:
kornia’s LAF detectors pad unused slots with an all-zero LAF and a zero response, so that a batch is one tensor. Drop those slots before matching: the padding frames are identical to one another, so a mutual nearest-neighbour test does not reject them and they match each other at zero distance.
Only an all-zero frame is padding. Any other frame is filled: a detection at the image origin, which has a zero centre but a nonzero shape, and also a singular or non-finite frame, which
laf_is_valid()screens out.Occupancy cannot be read off the response instead, because a pluggable signed response may have a genuine maximum at exactly zero.
- Parameters:
laf (
Tensor) –(B, N, 2, 3).- Return type:
- Returns:
mask of the slots a detection filled
(B, N).
See also
laf_is_valid(): rejects singular and non-finite frames as well as the padding.laf_is_inside_image(): counts the padding as inside the image at the default border, so combine the two masks.LocalFeatureMatcher: drops the padded slots itself before matching.
Example
>>> laf = torch.tensor([[[[2., 0., 5.], [0., 3., 7.]], [[0., 0., 0.], [0., 0., 0.]]]]) >>> laf_is_filled(laf) tensor([[ True, False]])
- kornia.feature.laf_is_inside_image(laf, images, border=0)[source]#
Check which LAFs have their elliptical region fully inside the image, within a border.
inside = laf_is_inside_image(laf, images, border)takes(B, N, 2, 3)LAFs in pixels and an image tensor(B, CH, H, W), read for its height and width only, and returns a boolean(B, N)mask, true where the elliptical region of the LAF lies at leastborderpixels inside the outer pixel centres. The patch ofextract_patches_from_pyramid()covers the squarec + A [-1, 1]^2, which reaches beyond the region, so a LAF that is inside can still have patch cells that read the image border.- Convention:
The region of a LAF
[A | c]is the ellipsec + A uover the unit vectorsu. Along image axisi(0 for x, 1 for y) it spans exactlyc_i - r_itoc_i + r_i, wherer_iis the Euclidean norm of rowiofA. A LAF is inside when both spans lie inborder <= x <= w - 1 - borderandborder <= y <= h - 1 - border, so a region that touches the last valid pixel coordinate is inside.The all-zero padding LAF is a point at the origin, so it counts as inside for
border <= 0, the default included; combine the mask withlaf_is_filled()to drop the padding.
- Parameters:
- Return type:
- Returns:
mask with shape
(B, N).
See also
laf_is_filled(): tells detections from the padding, which this counts as inside.laf_is_valid(): checks that the frame is finite and regular, wherever it lies.
- kornia.feature.KORNIA_CHECK_LAF(laf, raises=True)[source]#
Check that a tensor has the shape of local affine frames, (B, N, 2, 3).
KORNIA_CHECK_LAF(laf)returnsTruewhenlafis(B, N, 2, 3); it isKORNIA_CHECK_SHAPE(laf, ["B", "N", "2", "3"]). On failure it raisesShapeError.- Convention:
Only the shape is checked: any dtype and any values pass,
N = 0included.
- Parameters:
- Raises:
ShapeError – if
lafdoes not have a shape(B, N, 2, 3)and raises is True.- Return type:
Note
Skipped while checks are disabled; see
disable_checks().See also
laf_is_valid(): checks the values: finite entries and a nonzero determinant.KORNIA_CHECK_SHAPE(): checks any other shape pattern.
Example
>>> lafs = torch.rand(2, 10, 2, 3) >>> KORNIA_CHECK_LAF(lafs) True
Extracting patches#
- kornia.feature.extract_patches_from_pyramid(img, laf, PS=32, normalize_lafs_before_extraction=True)[source]#
Sample a patch per LAF from the image pyramid level that matches its scale.
patches = extract_patches_from_pyramid(img, laf, PS)takes images(B, CH, H, W)and their LAFs(B, N, 2, 3)in pixels and returns onePS x PSpatch per LAF,(B, N, CH, PS, PS)in the image dtype: the squarec + A [-1, 1]^2resampled from the level of a Gaussian pyramid ofimgthat matches the LAF’s scale, which limits aliasing. It is the sampler ofget_laf_descriptors(),LAFOrienterand the affine shape estimators. Withnormalize_lafs_before_extraction=False,lafis read in the frame ofnormalize_laf(). To sample the full-resolution image only, useextract_patches_simple().- Convention:
Cell
(i, j)of aPS x PSpatch samples the image atc + A @ (u_j, u_i), withu_k = (2 k + 1) / PS - 1and the LAF[A | c]in pixels, pixel centres at integers. Row 0 is on the patch’s-yside, the top for an upright LAF, the columns run along the patch’s x axis, and the cells tile the squarec + A [-1, 1]^2. For an oddPS, a LAF withA = (PS / 2) Icentred on a pixel returns the image crop around that pixel.The pyramid level is
floor(log2(2 s / PS))for the scales = sqrt(|det A|), clamped to the levels that exist:pyrdown()builds them while the shorter side stays at leastPS, so a larger LAF is sampled from the coarsest level that can still provide a full patch. Level 0, the image itself, always exists: an image whose shorter side is belowPShas only that level, so even a large LAF is sampled from it. The level uses the geometric-mean scale, so a highly anisotropic LAF can still alias along its long axis. A level samples the same image positions as level 0, in its own pixels.A cell outside the image reads the nearest border pixel of the level it samples.
A caller’s autocast region does not change the sampling grid or the pyramid, outside TorchScript. Under
torch.compileon MPS it still changes the pyramid: a convolution under the guard raises there, sopyrdown()runs in the region’s dtype.The patches have the image’s dtype. The sampling runs in the promoted dtype of the image and the LAF, and in at least
float32. A LAF with a non-finite entry gives a zero patch.
- Parameters:
img (
Tensor) – images, LAFs are detected in(B, CH, H, W).laf (
Tensor) –(B, N, 2, 3).PS (
int, optional) – patch size. Default:32normalize_lafs_before_extraction (
bool, optional) – if True, lafs are normalized to image size; if False,lafmust already be in the frame thatnormalize_laf()gives forimg. Default:True
- Return type:
- Returns:
patches with shape
(B, N, CH, PS,PS).
See also
extract_patches_simple(): the same positions, always sampled from the full-resolution image.get_laf_descriptors(): describes the patches that this samples with a patch descriptor.laf_is_inside_image(): keeps the LAFs whose region lies inside the image, though the corners of their patches can reach beyond it.
- kornia.feature.extract_patches_simple(img, laf, PS=32, normalize_lafs_before_extraction=True)[source]#
Sample a patch per LAF from the full-resolution image only, without anti-aliasing.
patches = extract_patches_simple(img, laf, PS)takes images(B, CH, H, W)and their LAFs(B, N, 2, 3)in pixels and returns onePS x PSpatch per LAF,(B, N, CH, PS, PS)in the image dtype, sampled bilinearly fromimgat the positions thatextract_patches_from_pyramid()samples. For LAFs of a scale belowPSthe two return the same patches up to rounding; a larger LAF is not smoothed first, so its patch aliases. For patches to describe, orient or adapt, useextract_patches_from_pyramid()instead, asLAFDescriptor,LAFOrienterand the affine shape estimators do.- Convention:
See the Convention block on
extract_patches_from_pyramid(). This function samples the same positions, always fromimgitself: a LAF larger than the patch is not smoothed first, so it aliases.
- Parameters:
img (
Tensor) – images, LAFs are detected in(B, CH, H, W).laf (
Tensor) –(B, N, 2, 3).PS (
int, optional) – patch size. Default:32normalize_lafs_before_extraction (
bool, optional) – if True, lafs are normalized to image size; if False,lafmust already be in the frame thatnormalize_laf()gives forimg. Default:True
- Return type:
- Returns:
patches with shape
(B, N, CH, PS,PS).
See also
extract_patches_from_pyramid(): samples each LAF from a pyramid level chosen by its scale to limit aliasing.
Orientation and affine shape estimation#
- class kornia.feature.LAFOrienter(patch_size=32, num_angular_bins=36, angle_detector=None)[source]#
Orient LAFs by the angle that a patch orientation estimator finds in each LAF’s patch.
lafs = LAFOrienter(patch_size)(laf, img)takes(B, N, 2, 3)LAFs in pixels and their grayscale images(B, 1, H, W), samples each LAF’spatch_sizepatch withextract_patches_from_pyramid(), runsangle_detectoron it,PatchDominantGradientOrientationwithnum_angular_binsbins by default, and returns the(B, N, 2, 3)LAFs rotated by the angles found. Passed to a detector asori_module, it runs after theaff_module. To keep the LAFs upright, passPassLAFinstead; to rotate them by known angles, userotate_laf().- Convention:
Each LAF’s patch (
extract_patches_from_pyramid()) gives an angle, measured in the patch, and the output orientation is the input orientation plus that angle, converted to degrees, modulo 360 degrees.get_laf_orientation()reads it back in[-180, 180]. The centre, the scale and the upright shape (make_upright()) of the LAF are kept.For an isotropic LAF with positive determinant, the default angle detector turns the LAF’s x axis onto the dominant gradient, whatever the input orientation: content turned 30 degrees counter-clockwise on screen turns the output orientation by +30, the sign of
laf_from_center_scale_ori()(see Local features for the orientation convention).
- Parameters:
patch_size (
int, optional) – side of the square patch sampled for each LAF. Default:32num_angular_bins (
int, optional) – number of histogram bins of the default angle detector; not used whenangle_detectoris given. Default:36angle_detector (
Optional[Module], optional) – Patch orientation estimator, e.g.PatchDominantGradientOrientationorOriNet. Default:None
See also
PatchDominantGradientOrientation: the defaultangle_detector, the dominant gradient direction.OriNet: a learnedangle_detectorfor 32 x 32 patches.PassLAF: keeps the input orientation, for upright features.rotate_laf(): rotates LAFs by known angles, as this module does with the estimated ones.LAFAffineShapeEstimator: estimates the affine shape, which a detector runs before this module.LAFDescriptor: describes the oriented LAFs, from patches that follow their orientation.
- class kornia.feature.PatchDominantGradientOrientation(patch_size=32, num_angular_bins=36, eps=1e-8)[source]#
Estimate the orientation of patches from the peak of their histogram of gradient directions.
angles = PatchDominantGradientOrientation(patch_size)(patches)takes(B, 1, patch_size, patch_size)patches and returns the direction of each patch’s dominant gradient,(B,)in radians, 0 for a gradient that points to the right: the peak, refined between bins, of anum_angular_bins-bin histogram of the gradient directions, each pixel weighted by its gradient magnitude and a Gaussian centred on the patch, as in SIFT’s orientation assignment. It returns one angle per patch, that of the highest peak. To orient LAFs on an image, run it throughLAFOrienter, whose default it is; for a learned estimate, useOriNetinstead.- Convention:
The angle is that of the dominant gradient, which points toward brighter values, counter-clockwise on screen with image y pointing down: \(-\operatorname{atan2}(g_y, g_x)\) in pixel coordinates. A gradient pointing down the patch gives \(-\pi / 2\).
LAFOrienteradds the angle to the LAF orientation.The angle is not wrapped into \((-\pi, \pi]\): it can exceed \(\pi\) by up to
pi / num_angular_bins.A patch whose Sobel gradients are all exactly zero, such as an all-zero patch, gets the angle \(\pi\) from its empty histogram, as does any constant patch. A nearly flat patch gets an arbitrary angle set by rounding noise, such as a patch sampled from a flat image region by
extract_patches_from_pyramid().A float16 or bfloat16 patch is computed in float32, whatever the module’s dtype and any enclosing autocast, and only the returned angle is cast back: the histogram bins of a textured patch are subnormal in float16. A float32 patch under autocast gets its float32 result.
The backward casts the patch gradient to float16 too, so a gradient above 65504 becomes
inffor a patch of very low amplitude. Throughextract_patches_from_pyramid()it reaches the image gradient asinfand NaN.
- Parameters:
Note
Each gradient pixel is accumulated into its two neighbouring histogram bins with
scatter_add_. On CUDA that accumulation uses atomics, so two calls on identical input can differ at the ulp level, and a patch whose two strongest bins are nearly tied can return the neighbouring peak.torch.use_deterministic_algorithms(True)selects the deterministic kernel and restores run-to-run reproducibility.See also
OriNet: a learned orientation of 32 x 32 patches.LAFOrienter: samples each LAF’s patch, runs this module on it by default and rotates the LAF.PatchAffineShapeEstimator: the affine shape of a patch, from the second moments of the same Sobel gradients.
- class kornia.feature.OriNet(pretrained=True, eps=1e-8)[source]#
Estimate the orientation of 32 x 32 patches with the learned OriNet CNN.
angles = OriNet()(patches)builds the network with its pretrained weights, in eval mode, and takes(B, 1, 32, 32)patches and returns a learned canonical orientation per patch,(B,)in radians, in the sense of the angle ofPatchDominantGradientOrientation; its zero is learned, so on a patch it need not match the dominant gradient direction.pretrained=Falseleaves the weights random. To orient LAFs, pass it toLAFOrienterasangle_detector, asKeyNetHardNetandKeyNetAffNetHardNetdo. For an estimate that needs no weights, usePatchDominantGradientOrientationinstead. This is based on the original code from the paper “Repeatability Is Not Enough: Learning Discriminative Affine Regions via Discriminability”. See [MRM18] for more details.- Convention:
With the pretrained weights, the angle turns with the patch content, in the sense of
PatchDominantGradientOrientation: content turned counter-clockwise on screen increases the angle. SeeLAFOrienterfor its use.Each patch is normalised first: its mean is subtracted and the result is divided by its standard deviation plus
1e-6. An offset of its intensity therefore does not change the angle, and a positive gain changes it only through that1e-6: little on a textured patch, more as the patch flattens, up topirad on a nearly flat one. An inverted patch generally gets a different angle.
- Parameters:
- Returns:
Angle in radians.
- Shape:
Input:
(B, 1, 32, 32)Output:
(B,)
See also
PatchDominantGradientOrientation: the hand-crafted dominant gradient direction, with no weights.LAFOrienter: runs it on each LAF’s patch, asangle_detector, and rotates the LAF.LAFAffNetShapeEstimator: AffNet, the affine shape network of the same paper.
Examples
>>> input = torch.rand(16, 1, 32, 32) >>> orinet = OriNet(pretrained=False).eval() >>> angle = orinet(input) # 16
- class kornia.feature.LAFAffineShapeEstimator(patch_size=32, affine_shape_detector=None, preserve_orientation=True)[source]#
Adapt the affine shape of LAFs to the second-moment matrix of each LAF’s image patch.
lafs = LAFAffineShapeEstimator(patch_size)(laf, img)takes(B, N, 2, 3)LAFs in pixels and their grayscale images(B, 1, H, W), samples each LAF’s patch through its upright frame, estimates the shape withaffine_shape_detector(PatchAffineShapeEstimatorby default) and returns the(B, N, 2, 3)LAFs with the adapted shape and the input’s centres and scales. For the shape that AffNet predicts, useLAFAffNetShapeEstimatorinstead; to drop the rotation and the reflection and keep the shape, without reading the image, usemake_upright().- Convention:
Each patch is sampled through the upright form of its LAF (
make_upright()), so the shape is estimated in the coordinates of that frame, and the output region is the frame composed with the estimated shape. The estimated shape’s ellipse \(A A^\top\) is proportional to the inverse of the second moment matrix thataffine_shape_detectorreturns. For an isotropic input LAF (a circle), the output shape is the estimated shape itself. An input whose shape already matches the local structure gives a nearly isotropic patch, so its shape changes little: one call is one step of the iterative shape adaptation of [Baumberg00].The centre and the scale of the input LAF are kept. With
preserve_orientation=True(the default) its orientation is kept too; otherwise the output is upright, and an orientation estimator such asLAFOrientercan be run after this module.An adapted LAF has a positive determinant: a mirrored input loses its reflection. A LAF that
laf_is_valid()rejects, such as the all-zero padding of a detector’s unfilled slot, comes back unchanged withpreserve_orientation=True, and with a zeroAand its centre otherwise. A valid LAF whose patch gives no finite, positive-definite shape is not adapted either: it comes back unchanged withpreserve_orientation=True, and asmake_upright()returns it otherwise.
- Parameters:
patch_size (
int, optional) – the input image patch size. Default:32affine_shape_detector (
Optional[Module], optional) – Patch affine shape estimator,PatchAffineShapeEstimator. Default:Nonepreserve_orientation (
bool, optional) – if True, the original orientation is preserved. Default:True
See also
LAFAffNetShapeEstimator: the same adaptation, with the shape that AffNet predicts.PatchAffineShapeEstimator: the defaultaffine_shape_detector, on patches.make_upright(): drops the rotation and the reflection of a LAF and keeps its shape, without the image.LAFOrienter: estimates the orientation, run after this module withpreserve_orientation=False.
- class kornia.feature.PatchAffineShapeEstimator(patch_size=19, eps=1e-10)[source]#
Estimate the affine shape of patches from the second-moment matrix of their gradients.
abc = PatchAffineShapeEstimator(patch_size)(patches)takes(B, 1, patch_size, patch_size)patches and returns the second-moment matrix of each patch’s Gaussian-weighted gradients as the ellipse coefficients(a, b, c)thatellipse_to_laf()reads,(B, 1, 3), divided by the largest entry, as in [Baumberg00]. It returns coefficients, not LAFs: to adapt LAFs on an image, useLAFAffineShapeEstimator, which runs it on each LAF’s patch.For float16 and bfloat16 inputs, the gradients, Gaussian weighting, moments, and normalization are computed in float32; for a float64 input, the Gaussian weighting is built in float64. The output keeps the input dtype.
- Convention:
The output \((a, b, c)\) is \((\langle g_x^2 \rangle, \langle g_x g_y \rangle, \langle g_y^2 \rangle)\), the means over the patch of the products of its Sobel gradients, x right and y down, each gradient weighted by a Gaussian centred on the patch. It is divided by its largest entry, which is therefore 1. When two of the three entries are below
epsbefore that division – a flat or low-contrast patch, or one whose gradient lies along an image axis – the output is \((1, 0, 1)\), a circle.The patch must be
patch_sizepixels square.Known defects: a negative \(b\) counts as below
eps, so a patch and its mirror image can get a thin ellipse and a circle (#5824).
- Parameters:
See also
LAFAffineShapeEstimator: runs it on each LAF’s patch and adapts the LAF’s shape to the result.LAFAffNetShapeEstimator: a learned shape, which AffNet predicts on each LAF’s patch.PatchDominantGradientOrientation: the orientation of a patch, from the same Sobel gradients.ellipse_to_laf(): turns a centre and these coefficients into a LAF.
- class kornia.feature.LAFAffNetShapeEstimator(pretrained=True, preserve_orientation=True)[source]#
Adapt the affine shape of LAFs to the shape the learned AffNet CNN predicts on each LAF’s patch.
lafs = LAFAffNetShapeEstimator()(laf, img)builds AffNet with its pretrained weights, in eval mode, and takes(B, N, 2, 3)LAFs in pixels and their grayscale images(B, 1, H, W), predicts each LAF’s shape on its 32 x 32 patch, sampled through its upright frame, and returns the(B, N, 2, 3)LAFs with that shape and the input’s centres and scales.GFTTAffNetHardNet,HesAffNetHardNet,DoGAffNetHardNetandKeyNetAffNetHardNetrun it withpreserve_orientation=False, before their orientation step. For the second-moment estimate, which needs no weights, useLAFAffineShapeEstimatorinstead. This is based on the original code from the paper “Repeatability Is Not Enough: Learning Discriminative Affine Regions via Discriminability”. See [MRM18] for more details.- Convention:
As
LAFAffineShapeEstimator, with AffNet’s prediction on a 32 x 32 patch in place of the second moment estimate; see its Convention block for the frame the patch is sampled through and for what the output keeps of the input LAF.
- Parameters:
See also
LAFAffineShapeEstimator: the same adaptation, with the second-moment matrix and no weights.OriNet: the orientation network of the same paper.KeyNetAffNetHardNet: a local feature that runs it on KeyNet detections, before OriNet and HardNet.
- class kornia.feature.PassLAF(*args, **kwargs)[source]#
Return LAFs unchanged, in place of an orientation or affine shape estimator.
out = PassLAF()(laf, img)takes(B, N, 2, 3)LAFs and an image and returns itslafargument itself, not a copy, without readingimg. It is the defaultori_moduleandaff_moduleofScaleSpaceDetector,MultiResolutionDetectorandKeyNetDetector, and theori_modulethat keeps features upright. To estimate the orientation, useLAFOrienter; to estimate the affine shape,LAFAffineShapeEstimatororLAFAffNetShapeEstimator.See also
LAFOrienter: rotates each LAF by the orientation estimated on its image patch.LAFAffineShapeEstimator: adapts each LAF’s shape to the second-moment matrix of its patch.LAFAffNetShapeEstimator: adapts each LAF’s shape with the learned AffNet.