123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- # Copyright (c) OpenMMLab. All rights reserved.
- from math import sqrt
- import torch
- import torch.nn.functional as F
- def gaussian2D(radius, sigma=1, dtype=torch.float32, device='cpu'):
- """Generate 2D gaussian kernel.
- Args:
- radius (int): Radius of gaussian kernel.
- sigma (int): Sigma of gaussian function. Default: 1.
- dtype (torch.dtype): Dtype of gaussian tensor. Default: torch.float32.
- device (str): Device of gaussian tensor. Default: 'cpu'.
- Returns:
- h (Tensor): Gaussian kernel with a
- ``(2 * radius + 1) * (2 * radius + 1)`` shape.
- """
- x = torch.arange(
- -radius, radius + 1, dtype=dtype, device=device).view(1, -1)
- y = torch.arange(
- -radius, radius + 1, dtype=dtype, device=device).view(-1, 1)
- h = (-(x * x + y * y) / (2 * sigma * sigma)).exp()
- h[h < torch.finfo(h.dtype).eps * h.max()] = 0
- return h
- def gen_gaussian_target(heatmap, center, radius, k=1):
- """Generate 2D gaussian heatmap.
- Args:
- heatmap (Tensor): Input heatmap, the gaussian kernel will cover on
- it and maintain the max value.
- center (list[int]): Coord of gaussian kernel's center.
- radius (int): Radius of gaussian kernel.
- k (int): Coefficient of gaussian kernel. Default: 1.
- Returns:
- out_heatmap (Tensor): Updated heatmap covered by gaussian kernel.
- """
- diameter = 2 * radius + 1
- gaussian_kernel = gaussian2D(
- radius, sigma=diameter / 6, dtype=heatmap.dtype, device=heatmap.device)
- x, y = center
- height, width = heatmap.shape[:2]
- left, right = min(x, radius), min(width - x, radius + 1)
- top, bottom = min(y, radius), min(height - y, radius + 1)
- masked_heatmap = heatmap[y - top:y + bottom, x - left:x + right]
- masked_gaussian = gaussian_kernel[radius - top:radius + bottom,
- radius - left:radius + right]
- out_heatmap = heatmap
- torch.max(
- masked_heatmap,
- masked_gaussian * k,
- out=out_heatmap[y - top:y + bottom, x - left:x + right])
- return out_heatmap
- def gaussian_radius(det_size, min_overlap):
- r"""Generate 2D gaussian radius.
- This function is modified from the `official github repo
- <https://github.com/princeton-vl/CornerNet-Lite/blob/master/core/sample/
- utils.py#L65>`_.
- Given ``min_overlap``, radius could computed by a quadratic equation
- according to Vieta's formulas.
- There are 3 cases for computing gaussian radius, details are following:
- - Explanation of figure: ``lt`` and ``br`` indicates the left-top and
- bottom-right corner of ground truth box. ``x`` indicates the
- generated corner at the limited position when ``radius=r``.
- - Case1: one corner is inside the gt box and the other is outside.
- .. code:: text
- |< width >|
- lt-+----------+ -
- | | | ^
- +--x----------+--+
- | | | |
- | | | | height
- | | overlap | |
- | | | |
- | | | | v
- +--+---------br--+ -
- | | |
- +----------+--x
- To ensure IoU of generated box and gt box is larger than ``min_overlap``:
- .. math::
- \cfrac{(w-r)*(h-r)}{w*h+(w+h)r-r^2} \ge {iou} \quad\Rightarrow\quad
- {r^2-(w+h)r+\cfrac{1-iou}{1+iou}*w*h} \ge 0 \\
- {a} = 1,\quad{b} = {-(w+h)},\quad{c} = {\cfrac{1-iou}{1+iou}*w*h}
- {r} \le \cfrac{-b-\sqrt{b^2-4*a*c}}{2*a}
- - Case2: both two corners are inside the gt box.
- .. code:: text
- |< width >|
- lt-+----------+ -
- | | | ^
- +--x-------+ |
- | | | |
- | |overlap| | height
- | | | |
- | +-------x--+
- | | | v
- +----------+-br -
- To ensure IoU of generated box and gt box is larger than ``min_overlap``:
- .. math::
- \cfrac{(w-2*r)*(h-2*r)}{w*h} \ge {iou} \quad\Rightarrow\quad
- {4r^2-2(w+h)r+(1-iou)*w*h} \ge 0 \\
- {a} = 4,\quad {b} = {-2(w+h)},\quad {c} = {(1-iou)*w*h}
- {r} \le \cfrac{-b-\sqrt{b^2-4*a*c}}{2*a}
- - Case3: both two corners are outside the gt box.
- .. code:: text
- |< width >|
- x--+----------------+
- | | |
- +-lt-------------+ | -
- | | | | ^
- | | | |
- | | overlap | | height
- | | | |
- | | | | v
- | +------------br--+ -
- | | |
- +----------------+--x
- To ensure IoU of generated box and gt box is larger than ``min_overlap``:
- .. math::
- \cfrac{w*h}{(w+2*r)*(h+2*r)} \ge {iou} \quad\Rightarrow\quad
- {4*iou*r^2+2*iou*(w+h)r+(iou-1)*w*h} \le 0 \\
- {a} = {4*iou},\quad {b} = {2*iou*(w+h)},\quad {c} = {(iou-1)*w*h} \\
- {r} \le \cfrac{-b+\sqrt{b^2-4*a*c}}{2*a}
- Args:
- det_size (list[int]): Shape of object.
- min_overlap (float): Min IoU with ground truth for boxes generated by
- keypoints inside the gaussian kernel.
- Returns:
- radius (int): Radius of gaussian kernel.
- """
- height, width = det_size
- a1 = 1
- b1 = (height + width)
- c1 = width * height * (1 - min_overlap) / (1 + min_overlap)
- sq1 = sqrt(b1**2 - 4 * a1 * c1)
- r1 = (b1 - sq1) / (2 * a1)
- a2 = 4
- b2 = 2 * (height + width)
- c2 = (1 - min_overlap) * width * height
- sq2 = sqrt(b2**2 - 4 * a2 * c2)
- r2 = (b2 - sq2) / (2 * a2)
- a3 = 4 * min_overlap
- b3 = -2 * min_overlap * (height + width)
- c3 = (min_overlap - 1) * width * height
- sq3 = sqrt(b3**2 - 4 * a3 * c3)
- r3 = (b3 + sq3) / (2 * a3)
- return min(r1, r2, r3)
- def get_local_maximum(heat, kernel=3):
- """Extract local maximum pixel with given kernel.
- Args:
- heat (Tensor): Target heatmap.
- kernel (int): Kernel size of max pooling. Default: 3.
- Returns:
- heat (Tensor): A heatmap where local maximum pixels maintain its
- own value and other positions are 0.
- """
- pad = (kernel - 1) // 2
- hmax = F.max_pool2d(heat, kernel, stride=1, padding=pad)
- keep = (hmax == heat).float()
- return heat * keep
- def get_topk_from_heatmap(scores, k=20):
- """Get top k positions from heatmap.
- Args:
- scores (Tensor): Target heatmap with shape
- [batch, num_classes, height, width].
- k (int): Target number. Default: 20.
- Returns:
- tuple[torch.Tensor]: Scores, indexes, categories and coords of
- topk keypoint. Containing following Tensors:
- - topk_scores (Tensor): Max scores of each topk keypoint.
- - topk_inds (Tensor): Indexes of each topk keypoint.
- - topk_clses (Tensor): Categories of each topk keypoint.
- - topk_ys (Tensor): Y-coord of each topk keypoint.
- - topk_xs (Tensor): X-coord of each topk keypoint.
- """
- batch, _, height, width = scores.size()
- topk_scores, topk_inds = torch.topk(scores.view(batch, -1), k)
- topk_clses = topk_inds // (height * width)
- topk_inds = topk_inds % (height * width)
- topk_ys = topk_inds // width
- topk_xs = (topk_inds % width).int().float()
- return topk_scores, topk_inds, topk_clses, topk_ys, topk_xs
- def gather_feat(feat, ind, mask=None):
- """Gather feature according to index.
- Args:
- feat (Tensor): Target feature map.
- ind (Tensor): Target coord index.
- mask (Tensor | None): Mask of feature map. Default: None.
- Returns:
- feat (Tensor): Gathered feature.
- """
- dim = feat.size(2)
- ind = ind.unsqueeze(2).repeat(1, 1, dim)
- feat = feat.gather(1, ind)
- if mask is not None:
- mask = mask.unsqueeze(2).expand_as(feat)
- feat = feat[mask]
- feat = feat.view(-1, dim)
- return feat
- def transpose_and_gather_feat(feat, ind):
- """Transpose and gather feature according to index.
- Args:
- feat (Tensor): Target feature map.
- ind (Tensor): Target coord index.
- Returns:
- feat (Tensor): Transposed and gathered feature.
- """
- feat = feat.permute(0, 2, 3, 1).contiguous()
- feat = feat.view(feat.size(0), -1, feat.size(3))
- feat = gather_feat(feat, ind)
- return feat
|