-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add multiclass-batch inference and refactor dataset
- Loading branch information
Showing
16 changed files
with
473 additions
and
234 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import cv2 | ||
import numpy as np | ||
|
||
from .geometry import box3d_to_bev_corners | ||
|
||
|
||
def clipped_percentile(x, p=1): | ||
"""Transform to unit interval robustly.""" | ||
p0, p1 = np.percentile(x, [p, 100 - p]) | ||
x = (np.clip(x, p0, p1) - p0) / (p1 - p0 + 1e-1) | ||
return x | ||
|
||
|
||
def make_bev_map(points, pixel_size, bounds): | ||
"""Scatter points to create sparse occupancy image.""" | ||
mask = ((points > bounds[:2]) & (points < bounds[2:])).all(1) | ||
shape = np.int32(np.ceil((bounds[2:] - bounds[:2]) / pixel_size))[::-1] | ||
pixels = np.int32(np.floor((points[mask] - bounds[:2]) / pixel_size)) | ||
pixels, counts = np.unique(pixels, return_counts=True, axis=0) | ||
bev_map = np.zeros(shape, dtype=np.float32) | ||
bev_map[tuple(pixels[:, ::-1].T)] = counts | ||
bev_map = clipped_percentile(bev_map) | ||
return bev_map | ||
|
||
|
||
class Drawer: | ||
"""Draw BEV occupancy map with boxes. Store in image attribute.""" | ||
|
||
def __init__(self, | ||
points, | ||
boxes=[], | ||
labels=[], | ||
pixel_size=np.r_[0.1, 0.1], | ||
bounds=np.r_[0, -30, 60, 30]): | ||
self.pixel_size = pixel_size | ||
self.bounds = bounds | ||
self.line_kw = dict(thickness=2) | ||
self.text_kw = dict( | ||
fontScale=0.6, | ||
thickness=2, | ||
fontFace=cv2.FONT_HERSHEY_SIMPLEX, | ||
) | ||
self.image = self.build_bev(points) | ||
self.draw(boxes, labels) | ||
|
||
def build_bev(self, points): | ||
image = make_bev_map( | ||
points[:, :2], self.pixel_size, self.bounds) | ||
image = (image * 255).astype(np.uint8) | ||
image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) | ||
return image | ||
|
||
def get_text_color(self, n): | ||
color = np.tile(np.r_[255, 0, 0][None], (n, 1)) | ||
color = [list(map(int, c)) for c in color] | ||
return color | ||
|
||
def get_line_color(self, n): | ||
color = np.tile(np.r_[0, 255, 0][None], (n, 1)) | ||
color = [list(int(c) for c in ci) for ci in color] | ||
return color | ||
|
||
def draw_text(self, locs, labels): | ||
colors = self.get_text_color(locs.shape[0]) | ||
locs = map(tuple, locs.astype(np.int32).tolist()) | ||
for label, loc, color in zip(labels, locs, colors): | ||
cv2.putText(self.image, label, loc, color=color, **self.text_kw) | ||
|
||
def draw_lines(self, lines): | ||
colors = self.get_line_color(lines.shape[0]) | ||
lines = map(tuple, lines.astype(np.int32).tolist()) | ||
for line, color in zip(lines, colors): | ||
cv2.line(self.image, line[0:2], line[2:4], color, **self.line_kw) | ||
|
||
def draw(self, boxes_, labels): | ||
"""No support yet for labels.""" | ||
for boxes in boxes_: | ||
extent = self.bounds[2:] - self.bounds[:2] | ||
factor = np.r_[self.image.shape[:2]][::-1] / extent | ||
corners = (box3d_to_bev_corners(boxes) - self.bounds[:2]) * factor | ||
line_loc = corners[:, [1, 2, 2, 3, 3, 0]].reshape(-1, 4) | ||
text_loc = corners[:, 0] + 0.3 | ||
self.draw_lines(line_loc) | ||
self.draw_text(text_loc, labels=[]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import torch | ||
import math | ||
|
||
|
||
def _anchor_diagonal(A_wlh): | ||
"""Reference: VoxelNet.""" | ||
A_wl, A_h = A_wlh.split([2, 1], -1) | ||
A_norm = A_wl.norm(dim=-1, keepdim=True).expand_as(A_wl) | ||
A_norm = torch.cat((A_norm, A_h), dim=-1) | ||
return A_norm | ||
|
||
|
||
def decode(deltas, anchors): | ||
"""Both inputs of shape (*, 7).""" | ||
P_xyz, P_wlh, P_yaw = deltas.split([3, 3, 1], -1) | ||
A_xyz, A_wlh, A_yaw = anchors.split([3, 3, 1], -1) | ||
A_norm = _anchor_diagonal(A_wlh) | ||
boxes = torch.cat(( | ||
(P_xyz * A_norm + A_xyz), | ||
(P_wlh.exp() * A_wlh), | ||
(P_yaw + A_yaw)), dim=-1 | ||
) | ||
return boxes | ||
|
||
|
||
def encode(boxes, anchors): | ||
"""Both inputs of shape (*, 7).""" | ||
G_xyz, G_wlh, G_yaw = boxes.split([3, 3, 1], -1) | ||
A_xyz, A_wlh, A_yaw = anchors.split([3, 3, 1], -1) | ||
A_norm = _anchor_diagonal(A_wlh) | ||
deltas = torch.cat(( | ||
(G_xyz - A_xyz) / A_norm, | ||
(G_wlh / A_wlh).log(), | ||
(G_yaw - A_yaw) % math.pi), dim=-1 | ||
) | ||
return deltas |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import numpy as np | ||
|
||
|
||
def points_in_convex_polygon(points, polygon, ccw=True): | ||
"""points (N, 2) | polygon (M, V, 2) | mask (N, M)""" | ||
polygon_roll = np.roll(polygon, shift=1, axis=1) | ||
polygon_side = (-1) ** ccw * (polygon - polygon_roll)[None] | ||
vertex_to_point = polygon[None] - points[:, None, None] | ||
mask = (np.cross(polygon_side, vertex_to_point) > 0).all(2) | ||
return mask | ||
|
||
|
||
def box3d_to_bev_corners(boxes): | ||
""" | ||
:boxes np.ndarray shape (N, 7) | ||
:corners np.ndarray shape (N, 4, 2) (ccw) | ||
""" | ||
xy, _, wl, _, yaw = np.split(boxes, [2, 3, 5, 6], 1) | ||
c, s = np.cos(yaw), np.sin(yaw) | ||
R = np.stack([c, -s, s, c], -1).reshape(-1, 2, 2) | ||
corners = 0.5 * np.r_[-1, -1, +1, -1, +1, +1, -1, +1] | ||
corners = (wl[:, None] * corners.reshape(4, 2)) | ||
corners = np.einsum('ijk,imk->imj', R, corners) + xy[:, None] | ||
return corners | ||
|
||
|
||
class PointsInCuboids: | ||
"""Takes ~10ms for each scene.""" | ||
|
||
def __init__(self, points): | ||
self.points = points | ||
|
||
def _height_threshold(self, boxes): | ||
"""Filter to z slice.""" | ||
z1 = self.points[:, None, 2] | ||
z2, h = boxes[:, [2, 5]].T | ||
mask = (z1 > z2 - h / 2) & (z1 < z2 + h / 2) | ||
return mask | ||
|
||
def _get_mask(self, boxes): | ||
polygons = box3d_to_bev_corners(boxes) | ||
mask = self._height_threshold(boxes) | ||
mask &= points_in_convex_polygon( | ||
self.points[:, :2], polygons) | ||
return mask | ||
|
||
def __call__(self, boxes): | ||
"""Return list of points in each box.""" | ||
mask = self._get_mask(boxes).T | ||
points = list(map(self.points.__getitem__, mask)) | ||
return points | ||
|
||
|
||
class PointsNotInRectangles(PointsInCuboids): | ||
|
||
def _get_mask(self, boxes): | ||
polygons = box3d_to_bev_corners(boxes) | ||
mask = points_in_convex_polygon( | ||
self.points[:, :2], polygons) | ||
return mask | ||
|
||
def __call__(self, boxes): | ||
"""Return array of points not in any box.""" | ||
mask = ~self._get_mask(boxes).any(1) | ||
return self.points[mask] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.