def match_nn( desc1: torch.Tensor, desc2: torch.Tensor, dm: Optional[torch.Tensor] = None ) -> Tuple[torch.Tensor, torch.Tensor]: r"""Function, which finds nearest neighbors in desc2 for each vector in desc1. If the distance matrix dm is not provided, :py:func:`torch.cdist` is used. Args: desc1: Batch of descriptors of a shape :math:`(B1, D)`. desc2: Batch of descriptors of a shape :math:`(B2, D)`. dm: Tensor containing the distances from each descriptor in desc1 to each descriptor in desc2, shape of :math:`(B1, B2)`. Returns: - Descriptor distance of matching descriptors, shape of :math:`(B1, 1)`. - Long tensor indexes of matching descriptors in desc1 and desc2, shape of :math:`(B1, 2)`. """ KORNIA_CHECK_SHAPE(desc1, ["B", "DIM"]) KORNIA_CHECK_SHAPE(desc2, ["B", "DIM"]) if dm is None: dm = torch.cdist(desc1, desc2) else: if not ((dm.size(0) == desc1.size(0)) and (dm.size(1) == desc2.size(0))): raise AssertionError match_dists, idxs_in_2 = torch.min(dm, dim=1) idxs_in1: torch.Tensor = torch.arange(0, idxs_in_2.size(0), device=idxs_in_2.device) matches_idxs: torch.Tensor = torch.cat([idxs_in1.view(-1, 1), idxs_in_2.view(-1, 1)], dim=1) return match_dists.view(-1, 1), matches_idxs.view(-1, 2)
def laf_from_center_scale_ori( xy: torch.Tensor, scale: Optional[torch.Tensor] = None, ori: Optional[torch.Tensor] = None) -> torch.Tensor: """Return orientation of the LAFs, in radians. Useful to create kornia LAFs from OpenCV keypoints. Args: xy: tensor [BxNx2]. scale: tensor [BxNx1x1]. If not provided, scale = 1 is assumed ori: tensor [BxNx1]. If not provided orientation = 0 is assumed Returns: tensor BxNx2x3. """ KORNIA_CHECK_SHAPE(xy, ["B", "N", "2"]) device = xy.device dtype = xy.dtype B, N = xy.shape[:2] if scale is None: scale = torch.ones(B, N, 1, 1, device=device, dtype=dtype) if ori is None: ori = torch.zeros(B, N, 1, device=device, dtype=dtype) KORNIA_CHECK_SHAPE(scale, ["B", "N", "1", "1"]) KORNIA_CHECK_SHAPE(ori, ["B", "N", "1"]) unscaled_laf: torch.Tensor = torch.cat( [angle_to_rotation_matrix(ori.squeeze(-1)), xy.unsqueeze(-1)], dim=-1) laf: torch.Tensor = scale_laf(unscaled_laf, scale) return laf
def match_smnn( desc1: torch.Tensor, desc2: torch.Tensor, th: float = 0.8, dm: Optional[torch.Tensor] = None ) -> Tuple[torch.Tensor, torch.Tensor]: """Function, which finds mutual nearest neighbors in desc2 for each vector in desc1. the method satisfies first to second nearest neighbor distance <= th. If the distance matrix dm is not provided, :py:func:`torch.cdist` is used. Args: desc1: Batch of descriptors of a shape :math:`(B1, D)`. desc2: Batch of descriptors of a shape :math:`(B2, D)`. th: distance ratio threshold. dm: Tensor containing the distances from each descriptor in desc1 to each descriptor in desc2, shape of :math:`(B1, B2)`. Return: - Descriptor distance of matching descriptors, shape of. :math:`(B3, 1)`. - Long tensor indexes of matching descriptors in desc1 and desc2, shape of :math:`(B3, 2)` where 0 <= B3 <= B1. """ KORNIA_CHECK_SHAPE(desc1, ["B", "DIM"]) KORNIA_CHECK_SHAPE(desc2, ["B", "DIM"]) if desc1.shape[0] < 2: raise AssertionError if desc2.shape[0] < 2: raise AssertionError if dm is None: dm = torch.cdist(desc1, desc2) else: if not ((dm.size(0) == desc1.size(0)) and (dm.size(1) == desc2.size(0))): raise AssertionError dists1, idx1 = match_snn(desc1, desc2, th, dm) dists2, idx2 = match_snn(desc2, desc1, th, dm.t()) if len(dists2) > 0 and len(dists1) > 0: idx2 = idx2.flip(1) idxs_dm = torch.cdist(idx1.float(), idx2.float(), p=1.0) mutual_idxs1 = idxs_dm.min(dim=1)[0] < 1e-8 mutual_idxs2 = idxs_dm.min(dim=0)[0] < 1e-8 good_idxs1 = idx1[mutual_idxs1.view(-1)] good_idxs2 = idx2[mutual_idxs2.view(-1)] dists1_good = dists1[mutual_idxs1.view(-1)] dists2_good = dists2[mutual_idxs2.view(-1)] _, idx_upl1 = torch.sort(good_idxs1[:, 0]) _, idx_upl2 = torch.sort(good_idxs2[:, 0]) good_idxs1 = good_idxs1[idx_upl1] match_dists = torch.max(dists1_good[idx_upl1], dists2_good[idx_upl2]) matches_idxs = good_idxs1 else: matches_idxs, match_dists = torch.empty(0, 2, device=dm.device), torch.empty(0, 1, device=dm.device) return match_dists.view(-1, 1), matches_idxs.view(-1, 2)
def raise_error_if_laf_is_not_valid(laf: torch.Tensor) -> None: """Auxiliary function, which verifies that input. Args: laf: [BxNx2x3] shape. """ KORNIA_CHECK_SHAPE(laf, ["B", "N", "2", "3"])
def forward(self, patch: torch.Tensor) -> torch.Tensor: """Args: patch: (torch.Tensor) shape [Bx1xHxW] Returns: torch.Tensor: ellipse_shape shape [Bx1x3]""" KORNIA_CHECK_SHAPE(patch, ["B", "1", "H", "W"]) self.weighting = self.weighting.to(patch.dtype).to(patch.device) grads: torch.Tensor = self.gradient(patch) * self.weighting # unpack the edges gx: torch.Tensor = grads[:, :, 0] gy: torch.Tensor = grads[:, :, 1] # abc == 1st axis, mixture, 2nd axis. Ellipse_shape is a 2nd moment matrix. ellipse_shape = torch.cat( [ gx.pow(2).mean(dim=2).mean(dim=2, keepdim=True), (gx * gy).mean(dim=2).mean(dim=2, keepdim=True), gy.pow(2).mean(dim=2).mean(dim=2, keepdim=True), ], dim=2, ) # Now lets detect degenerate cases: when 2 or 3 elements are close to zero (e.g. if patch is completely black bad_mask = ((ellipse_shape < self.eps).float().sum(dim=2, keepdim=True) >= 2).to(ellipse_shape.dtype) # We will replace degenerate shape with circular shapes. circular_shape = torch.tensor([1.0, 0.0, 1.0]).to( ellipse_shape.device).to(ellipse_shape.dtype).view(1, 1, 3) ellipse_shape = ellipse_shape * (1.0 - bad_mask) + circular_shape * bad_mask # normalization ellipse_shape = ellipse_shape / ellipse_shape.max(dim=2, keepdim=True)[0] return ellipse_shape
def forward(self, input): KORNIA_CHECK_SHAPE(input, ["B", "1", "H", "W"]) B, CH, W, H = input.size() self.bin_pooling_kernel = self.bin_pooling_kernel.to(input.dtype).to(input.device) self.PoolingConv = self.PoolingConv.to(input.dtype).to(input.device) grads: torch.Tensor = spatial_gradient(input, 'diff') # unpack the edges gx: torch.Tensor = grads[:, :, 0] gy: torch.Tensor = grads[:, :, 1] mag: torch.Tensor = torch.sqrt(gx * gx + gy * gy + self.eps) ori: torch.Tensor = torch.atan2(gy, gx + self.eps) + 2.0 * pi o_big: torch.Tensor = float(self.num_ang_bins) * ori / (2.0 * pi) bo0_big_: torch.Tensor = torch.floor(o_big) wo1_big_: torch.Tensor = (o_big - bo0_big_) bo0_big: torch.Tensor = bo0_big_ % self.num_ang_bins bo1_big: torch.Tensor = (bo0_big + 1) % self.num_ang_bins wo0_big: torch.Tensor = (1.0 - wo1_big_) * mag # type: ignore wo1_big: torch.Tensor = wo1_big_ * mag ang_bins = [] for i in range(0, self.num_ang_bins): out = self.bin_pooling_kernel((bo0_big == i).to(input.dtype) * wo0_big + # noqa (bo1_big == i).to(input.dtype) * wo1_big) ang_bins.append(out) ang_bins = torch.cat(ang_bins, dim=1) out_no_norm = self.PoolingConv(ang_bins) out = F.normalize(out_no_norm, dim=1, p=2).clamp_(0, float(self.clipval)) out = F.normalize(out, dim=1, p=2) if self.rootsift: out = torch.sqrt(F.normalize(out, p=1) + self.eps) return out
def forward(self, laf: torch.Tensor, img: torch.Tensor) -> torch.Tensor: """ Args: laf: (torch.Tensor) shape [BxNx2x3] img: (torch.Tensor) shape [Bx1xHxW] Returns: torch.Tensor: laf_out shape [BxNx2x3]""" raise_error_if_laf_is_not_valid(laf) KORNIA_CHECK_SHAPE(img, ["B", "1", "H", "W"]) B, N = laf.shape[:2] PS: int = self.patch_size patches: torch.Tensor = extract_patches_from_pyramid( img, make_upright(laf), PS, True).view(-1, 1, PS, PS) ellipse_shape: torch.Tensor = self.affine_shape_detector(patches) ellipses = torch.cat( [laf.view(-1, 2, 3)[..., 2].unsqueeze(1), ellipse_shape], dim=2).view(B, N, 5) scale_orig = get_laf_scale(laf) if self.preserve_orientation: ori_orig = get_laf_orientation(laf) laf_out = ellipse_to_laf(ellipses) ellipse_scale = get_laf_scale(laf_out) laf_out = scale_laf(laf_out, scale_orig / ellipse_scale) if self.preserve_orientation: laf_out = set_laf_orientation(laf_out, ori_orig) return laf_out
def forward(self, laf: torch.Tensor, img: torch.Tensor) -> torch.Tensor: """ Args: laf: shape [BxNx2x3] img: shape [Bx1xHxW] Returns: laf_out shape [BxNx2x3] """ raise_error_if_laf_is_not_valid(laf) KORNIA_CHECK_SHAPE(img, ["B", "1", "H", "W"]) B, N = laf.shape[:2] PS: int = self.patch_size patches: torch.Tensor = extract_patches_from_pyramid( img, make_upright(laf), PS, True).view(-1, 1, PS, PS) xy = self.features(self._normalize_input(patches)).view(-1, 3) a1 = torch.cat( [1.0 + xy[:, 0].reshape(-1, 1, 1), 0 * xy[:, 0].reshape(-1, 1, 1)], dim=2) a2 = torch.cat( [xy[:, 1].reshape(-1, 1, 1), 1.0 + xy[:, 2].reshape(-1, 1, 1)], dim=2) new_laf_no_center = torch.cat([a1, a2], dim=1).reshape(B, N, 2, 2) new_laf = torch.cat([new_laf_no_center, laf[:, :, :, 2:3]], dim=3) scale_orig = get_laf_scale(laf) if self.preserve_orientation: ori_orig = get_laf_orientation(laf) ellipse_scale = get_laf_scale(new_laf) laf_out = scale_laf(make_upright(new_laf), scale_orig / ellipse_scale) if self.preserve_orientation: laf_out = set_laf_orientation(laf_out, ori_orig) return laf_out
def forward(self, input): KORNIA_CHECK_SHAPE(input, ["B", "1", f"{self.patch_size}", f"{self.patch_size}"]) B: int = input.shape[0] self.pk = self.pk.to(input.dtype).to(input.device) grads: torch.Tensor = spatial_gradient(input, 'diff') # unpack the edges gx: torch.Tensor = grads[:, :, 0] gy: torch.Tensor = grads[:, :, 1] mag: torch.Tensor = torch.sqrt(gx * gx + gy * gy + self.eps) ori: torch.Tensor = torch.atan2(gy, gx + self.eps) + 2.0 * pi mag = mag * self.gk.expand_as(mag).type_as(mag).to(mag.device) o_big: torch.Tensor = float(self.num_ang_bins) * ori / (2.0 * pi) bo0_big_: torch.Tensor = torch.floor(o_big) wo1_big_: torch.Tensor = o_big - bo0_big_ bo0_big: torch.Tensor = bo0_big_ % self.num_ang_bins bo1_big: torch.Tensor = (bo0_big + 1) % self.num_ang_bins wo0_big: torch.Tensor = (1.0 - wo1_big_) * mag # type: ignore wo1_big: torch.Tensor = wo1_big_ * mag ang_bins = [] for i in range(0, self.num_ang_bins): out = self.pk((bo0_big == i).to(input.dtype) * wo0_big + (bo1_big == i).to(input.dtype) * wo1_big) ang_bins.append(out) ang_bins = torch.cat(ang_bins, dim=1) ang_bins = ang_bins.view(B, -1) ang_bins = F.normalize(ang_bins, p=2) ang_bins = torch.clamp(ang_bins, 0.0, float(self.clipval)) ang_bins = F.normalize(ang_bins, p=2) if self.rootsift: ang_bins = torch.sqrt(F.normalize(ang_bins, p=1) + self.eps) return ang_bins
def forward(self, input: torch.Tensor) -> torch.Tensor: KORNIA_CHECK_SHAPE(input, ["B", "1", "32", "32"]) x_norm: torch.Tensor = self._normalize_input(input) x_features: torch.Tensor = self.features(x_norm) mean: torch.Tensor = torch.jit.annotate(torch.Tensor, self.mean) components: torch.Tensor = torch.jit.annotate(torch.Tensor, self.components) x_prePCA = F.normalize(x_features.view(x_features.size(0), -1)) pca = torch.mm(x_prePCA - mean, components) return F.normalize(pca, dim=1)
def match_snn( desc1: torch.Tensor, desc2: torch.Tensor, th: float = 0.8, dm: Optional[torch.Tensor] = None ) -> Tuple[torch.Tensor, torch.Tensor]: """Function, which finds nearest neighbors in desc2 for each vector in desc1. The method satisfies first to second nearest neighbor distance <= th. If the distance matrix dm is not provided, :py:func:`torch.cdist` is used. Args: desc1: Batch of descriptors of a shape :math:`(B1, D)`. desc2: Batch of descriptors of a shape :math:`(B2, D)`. th: distance ratio threshold. dm: Tensor containing the distances from each descriptor in desc1 to each descriptor in desc2, shape of :math:`(B1, B2)`. Return: - Descriptor distance of matching descriptors, shape of :math:`(B3, 1)`. - Long tensor indexes of matching descriptors in desc1 and desc2. Shape: :math:`(B3, 2)`, where 0 <= B3 <= B1. """ KORNIA_CHECK_SHAPE(desc1, ["B", "DIM"]) KORNIA_CHECK_SHAPE(desc2, ["B", "DIM"]) if desc2.shape[0] < 2: raise AssertionError if dm is None: dm = torch.cdist(desc1, desc2) else: if not ((dm.size(0) == desc1.size(0)) and (dm.size(1) == desc2.size(0))): raise AssertionError vals, idxs_in_2 = torch.topk(dm, 2, dim=1, largest=False) ratio = vals[:, 0] / vals[:, 1] mask = ratio <= th match_dists = ratio[mask] idxs_in1 = torch.arange(0, idxs_in_2.size(0), device=dm.device)[mask] idxs_in_2 = idxs_in_2[:, 0][mask] matches_idxs = torch.cat([idxs_in1.view(-1, 1), idxs_in_2.view(-1, 1)], dim=1) return match_dists.view(-1, 1), matches_idxs.view(-1, 2)
def match_mnn( desc1: torch.Tensor, desc2: torch.Tensor, dm: Optional[torch.Tensor] = None ) -> Tuple[torch.Tensor, torch.Tensor]: """Function, which finds mutual nearest neighbors in desc2 for each vector in desc1. If the distance matrix dm is not provided, :py:func:`torch.cdist` is used. Args: desc1: Batch of descriptors of a shape :math:`(B1, D)`. desc2: Batch of descriptors of a shape :math:`(B2, D)`. dm: Tensor containing the distances from each descriptor in desc1 to each descriptor in desc2, shape of :math:`(B1, B2)`. Return: - Descriptor distance of matching descriptors, shape of. :math:`(B3, 1)`. - Long tensor indexes of matching descriptors in desc1 and desc2, shape of :math:`(B3, 2)`, where 0 <= B3 <= min(B1, B2) """ KORNIA_CHECK_SHAPE(desc1, ["B", "DIM"]) KORNIA_CHECK_SHAPE(desc2, ["B", "DIM"]) if dm is None: dm = torch.cdist(desc1, desc2) else: if not ((dm.size(0) == desc1.size(0)) and (dm.size(1) == desc2.size(0))): raise AssertionError ms = min(dm.size(0), dm.size(1)) match_dists, idxs_in_2 = torch.min(dm, dim=1) match_dists2, idxs_in_1 = torch.min(dm, dim=0) minsize_idxs = torch.arange(ms, device=dm.device) if dm.size(0) <= dm.size(1): mutual_nns = minsize_idxs == idxs_in_1[idxs_in_2][:ms] matches_idxs = torch.cat([minsize_idxs.view(-1, 1), idxs_in_2.view(-1, 1)], dim=1)[mutual_nns] match_dists = match_dists[mutual_nns] else: mutual_nns = minsize_idxs == idxs_in_2[idxs_in_1][:ms] matches_idxs = torch.cat([idxs_in_1.view(-1, 1), minsize_idxs.view(-1, 1)], dim=1)[mutual_nns] match_dists = match_dists2[mutual_nns] return match_dists.view(-1, 1), matches_idxs.view(-1, 2)
def dog_response(input: torch.Tensor) -> torch.Tensor: r"""Compute the Difference-of-Gaussian response. Args: input: a given the gaussian 5d tensor :math:`(B, C, D, H, W)`. Return: the response map per channel with shape :math:`(B, C, D-1, H, W)`. """ KORNIA_CHECK_SHAPE(input, ["B", "C", "L", "H", "W"]) return input[:, :, 1:] - input[:, :, :-1]
def draw_convex_polygon(images: Tensor, polygons: Union[Tensor, List[Tensor]], colors: Tensor) -> Tensor: r"""Draws convex polygons on a batch of image tensors. Args: images: is tensor of BxCxHxW. polygons: represents polygons as points, either BxNx2 or List of variable length polygons. N is the number of points. 2 is (x, y). color: a B x 3 tensor or 3 tensor with color to fill in. Returns: This operation modifies image inplace but also returns the drawn tensor for convenience with same shape the of the input BxCxHxW. Note: This function assumes a coordinate system (0, h - 1), (0, w - 1) in the image, with (0, 0) being the center of the top-left pixel and (w - 1, h - 1) being the center of the bottom-right coordinate. Example: >>> img = torch.rand(1, 3, 12, 16) >>> poly = torch.tensor([[[4, 4], [12, 4], [12, 8], [4, 8]]]) >>> color = torch.tensor([[0.5, 0.5, 0.5]]) >>> out = draw_convex_polygon(img, poly, color) """ # TODO: implement optional linetypes for smooth edges KORNIA_CHECK_SHAPE(images, ["B", "C", "H", "W"]) b_i, c_i, h_i, w_i, device = *images.shape, images.device if isinstance(polygons, List): polygons = _batch_polygons(polygons) b_p, _, xy, device_p, dtype_p = *polygons.shape, polygons.device, polygons.dtype if len(colors.shape) == 1: colors = colors.expand(b_i, c_i) b_c, _, device_c = *colors.shape, colors.device KORNIA_CHECK(xy == 2, "Polygon vertices must be xy, i.e. 2-dimensional") KORNIA_CHECK(b_i == b_p == b_c, "Image, polygon, and color must have same batch dimension") KORNIA_CHECK(device == device_p == device_c, "Image, polygon, and color must have same device") x_left, x_right = _get_convex_edges(polygons, h_i, w_i) ws = torch.arange(w_i, device=device, dtype=dtype_p)[None, None, :] fill_region = (ws >= x_left[..., :, None]) & (ws <= x_right[..., :, None]) images = (~fill_region[:, None] ) * images + fill_region[:, None] * colors[..., None, None] return images
def forward(self, laf: torch.Tensor, img: torch.Tensor) -> torch.Tensor: """ Args: laf: shape [BxNx2x3] img: shape [Bx1xHxW] Returns: laf_out, shape [BxNx2x3] """ raise_error_if_laf_is_not_valid(laf) KORNIA_CHECK_SHAPE(img, ["B", "C", "H", "W"]) if laf.size(0) != img.size(0): raise ValueError(f"Batch size of laf and img should be the same. Got {img.size(0)}, {laf.size(0)}") B, N = laf.shape[:2] patches: torch.Tensor = extract_patches_from_pyramid(img, laf, self.patch_size).view( -1, 1, self.patch_size, self.patch_size ) angles_radians: torch.Tensor = self.angle_detector(patches).view(B, N) prev_angle = get_laf_orientation(laf).view_as(angles_radians) laf_out: torch.Tensor = set_laf_orientation(laf, rad2deg(angles_radians) + prev_angle) return laf_out
def hessian_response(input: torch.Tensor, grads_mode: str = 'sobel', sigmas: Optional[torch.Tensor] = None) -> torch.Tensor: r"""Compute the absolute of determinant of the Hessian matrix. Function does not do any normalization or nms. The response map is computed according the following formulation: .. math:: R = det(H) where: .. math:: M = \sum_{(x,y) \in W} \begin{bmatrix} I_{xx} & I_{xy} \\ I_{xy} & I_{yy} \\ \end{bmatrix} Args: input: input image with shape :math:`(B, C, H, W)`. grads_mode: can be ``'sobel'`` for standalone use or ``'diff'`` for use on Gaussian pyramid. sigmas: coefficients to be multiplied by multichannel response. Should be shape of :math:`(B)` It is necessary for performing non-maxima-suppression across different scale pyramid levels. See `vlfeat <https://github.com/vlfeat/vlfeat/blob/master/vl/covdet.c#L874>`_. Return: the response map per channel with shape :math:`(B, C, H, W)`. Shape: - Input: :math:`(B, C, H, W)` - Output: :math:`(B, C, H, W)` Examples: >>> input = torch.tensor([[[ ... [0., 0., 0., 0., 0., 0., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 0., 0., 0., 0., 0., 0.], ... ]]]) # 1x1x7x7 >>> # compute the response map hessian_response(input) tensor([[[[0.0155, 0.0334, 0.0194, 0.0000, 0.0194, 0.0334, 0.0155], [0.0334, 0.0575, 0.0339, 0.0000, 0.0339, 0.0575, 0.0334], [0.0194, 0.0339, 0.0497, 0.0000, 0.0497, 0.0339, 0.0194], [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [0.0194, 0.0339, 0.0497, 0.0000, 0.0497, 0.0339, 0.0194], [0.0334, 0.0575, 0.0339, 0.0000, 0.0339, 0.0575, 0.0334], [0.0155, 0.0334, 0.0194, 0.0000, 0.0194, 0.0334, 0.0155]]]]) """ # TODO: Recompute doctest KORNIA_CHECK_SHAPE(input, ["B", "C", "H", "W"]) if sigmas is not None: if not isinstance(sigmas, torch.Tensor): raise TypeError( f"sigmas type is not a torch.Tensor. Got {type(sigmas)}") if (not len(sigmas.shape) == 1) or (sigmas.size(0) != input.size(0)): raise ValueError( f"Invalid sigmas shape, we expect B == input.size(0). Got: {sigmas.shape}" ) gradients: torch.Tensor = spatial_gradient(input, grads_mode, 2) dxx: torch.Tensor = gradients[:, :, 0] dxy: torch.Tensor = gradients[:, :, 1] dyy: torch.Tensor = gradients[:, :, 2] scores: torch.Tensor = dxx * dyy - dxy**2 if sigmas is not None: scores = scores * sigmas.pow(4).view(-1, 1, 1, 1) return scores
def forward(self, input: torch.Tensor) -> torch.Tensor: KORNIA_CHECK_SHAPE(input, ["B", "1", "32", "32"]) x = self.features(input) x = x.view(x.size(0), -1) x = self.descr(x) return x
def gftt_response(input: torch.Tensor, grads_mode: str = 'sobel', sigmas: Optional[torch.Tensor] = None) -> torch.Tensor: r"""Compute the Shi-Tomasi cornerness function. Function does not do any normalization or nms. The response map is computed according the following formulation: .. math:: R = min(eig(M)) where: .. math:: M = \sum_{(x,y) \in W} \begin{bmatrix} I^{2}_x & I_x I_y \\ I_x I_y & I^{2}_y \\ \end{bmatrix} Args: input: input image with shape :math:`(B, C, H, W)`. grads_mode: can be ``'sobel'`` for standalone use or ``'diff'`` for use on Gaussian pyramid. sigmas: coefficients to be multiplied by multichannel response. Should be shape of :math:`(B)` It is necessary for performing non-maxima-suppression across different scale pyramid levels. See `vlfeat <https://github.com/vlfeat/vlfeat/blob/master/vl/covdet.c#L874>`_. Return: the response map per channel with shape :math:`(B, C, H, W)`. Example: >>> input = torch.tensor([[[ ... [0., 0., 0., 0., 0., 0., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 0., 0., 0., 0., 0., 0.], ... ]]]) # 1x1x7x7 >>> # compute the response map gftt_response(input) tensor([[[[0.0155, 0.0334, 0.0194, 0.0000, 0.0194, 0.0334, 0.0155], [0.0334, 0.0575, 0.0339, 0.0000, 0.0339, 0.0575, 0.0334], [0.0194, 0.0339, 0.0497, 0.0000, 0.0497, 0.0339, 0.0194], [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [0.0194, 0.0339, 0.0497, 0.0000, 0.0497, 0.0339, 0.0194], [0.0334, 0.0575, 0.0339, 0.0000, 0.0339, 0.0575, 0.0334], [0.0155, 0.0334, 0.0194, 0.0000, 0.0194, 0.0334, 0.0155]]]]) """ # TODO: Recompute doctest KORNIA_CHECK_SHAPE(input, ["B", "C", "H", "W"]) gradients: torch.Tensor = spatial_gradient(input, grads_mode) dx: torch.Tensor = gradients[:, :, 0] dy: torch.Tensor = gradients[:, :, 1] dx2: torch.Tensor = gaussian_blur2d(dx**2, (7, 7), (1.0, 1.0)) dy2: torch.Tensor = gaussian_blur2d(dy**2, (7, 7), (1.0, 1.0)) dxy: torch.Tensor = gaussian_blur2d(dx * dy, (7, 7), (1.0, 1.0)) det_m: torch.Tensor = dx2 * dy2 - dxy * dxy trace_m: torch.Tensor = dx2 + dy2 e1: torch.Tensor = 0.5 * (trace_m + torch.sqrt( (trace_m**2 - 4 * det_m).abs())) e2: torch.Tensor = 0.5 * (trace_m - torch.sqrt( (trace_m**2 - 4 * det_m).abs())) scores: torch.Tensor = torch.min(e1, e2) if sigmas is not None: scores = scores * sigmas.pow(4).view(-1, 1, 1, 1) return scores
def harris_response( input: torch.Tensor, k: Union[torch.Tensor, float] = 0.04, grads_mode: str = 'sobel', sigmas: Optional[torch.Tensor] = None, ) -> torch.Tensor: r"""Compute the Harris cornerness function. Function does not do any normalization or nms. The response map is computed according the following formulation: .. math:: R = max(0, det(M) - k \cdot trace(M)^2) where: .. math:: M = \sum_{(x,y) \in W} \begin{bmatrix} I^{2}_x & I_x I_y \\ I_x I_y & I^{2}_y \\ \end{bmatrix} and :math:`k` is an empirically determined constant :math:`k ∈ [ 0.04 , 0.06 ]` Args: input: input image with shape :math:`(B, C, H, W)`. k: the Harris detector free parameter. grads_mode: can be ``'sobel'`` for standalone use or ``'diff'`` for use on Gaussian pyramid. sigmas: coefficients to be multiplied by multichannel response. Should be shape of :math:`(B)` It is necessary for performing non-maxima-suppression across different scale pyramid levels. See `vlfeat <https://github.com/vlfeat/vlfeat/blob/master/vl/covdet.c#L874>`_. Return: the response map per channel with shape :math:`(B, C, H, W)`. Example: >>> input = torch.tensor([[[ ... [0., 0., 0., 0., 0., 0., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 1., 1., 1., 1., 1., 0.], ... [0., 0., 0., 0., 0., 0., 0.], ... ]]]) # 1x1x7x7 >>> # compute the response map harris_response(input, 0.04) tensor([[[[0.0012, 0.0039, 0.0020, 0.0000, 0.0020, 0.0039, 0.0012], [0.0039, 0.0065, 0.0040, 0.0000, 0.0040, 0.0065, 0.0039], [0.0020, 0.0040, 0.0029, 0.0000, 0.0029, 0.0040, 0.0020], [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [0.0020, 0.0040, 0.0029, 0.0000, 0.0029, 0.0040, 0.0020], [0.0039, 0.0065, 0.0040, 0.0000, 0.0040, 0.0065, 0.0039], [0.0012, 0.0039, 0.0020, 0.0000, 0.0020, 0.0039, 0.0012]]]]) """ # TODO: Recompute doctest KORNIA_CHECK_SHAPE(input, ["B", "C", "H", "W"]) if sigmas is not None: if not isinstance(sigmas, torch.Tensor): raise TypeError( f"sigmas type is not a torch.Tensor. Got {type(sigmas)}") if (not len(sigmas.shape) == 1) or (sigmas.size(0) != input.size(0)): raise ValueError( f"Invalid sigmas shape, we expect B == input.size(0). Got: {sigmas.shape}" ) gradients: torch.Tensor = spatial_gradient(input, grads_mode) dx: torch.Tensor = gradients[:, :, 0] dy: torch.Tensor = gradients[:, :, 1] # compute the structure tensor M elements dx2: torch.Tensor = gaussian_blur2d(dx**2, (7, 7), (1.0, 1.0)) dy2: torch.Tensor = gaussian_blur2d(dy**2, (7, 7), (1.0, 1.0)) dxy: torch.Tensor = gaussian_blur2d(dx * dy, (7, 7), (1.0, 1.0)) det_m: torch.Tensor = dx2 * dy2 - dxy * dxy trace_m: torch.Tensor = dx2 + dy2 # compute the response map scores: torch.Tensor = det_m - k * (trace_m**2) if sigmas is not None: scores = scores * sigmas.pow(4).view(-1, 1, 1, 1) return scores
def forward(self, input: torch.Tensor, eps: float = 1e-10) -> torch.Tensor: KORNIA_CHECK_SHAPE(input, ["B", "1", "32", "32"]) descr = self.desc_norm(self.layers(input) + eps) descr = descr.view(descr.size(0), -1) return descr
def forward(self, input: torch.Tensor) -> torch.Tensor: KORNIA_CHECK_SHAPE(input, ["B", "1", "32", "32"]) x_norm: torch.Tensor = self._normalize_input(input) x_features: torch.Tensor = self.features(x_norm) x_out = x_features.view(x_features.size(0), -1) return F.normalize(x_out, dim=1)