Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
mmdetection3d
Commits
32a4328b
Unverified
Commit
32a4328b
authored
Feb 24, 2022
by
Wenwei Zhang
Committed by
GitHub
Feb 24, 2022
Browse files
Bump version to V1.0.0rc0
Bump version to V1.0.0rc0
parents
86cc487c
a8817998
Changes
414
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
1350 additions
and
637 deletions
+1350
-637
mmdet3d/core/bbox/coders/partial_bin_based_bbox_coder.py
mmdet3d/core/bbox/coders/partial_bin_based_bbox_coder.py
+1
-1
mmdet3d/core/bbox/coders/pgd_bbox_coder.py
mmdet3d/core/bbox/coders/pgd_bbox_coder.py
+128
-0
mmdet3d/core/bbox/coders/point_xyzwhlr_bbox_coder.py
mmdet3d/core/bbox/coders/point_xyzwhlr_bbox_coder.py
+117
-0
mmdet3d/core/bbox/coders/smoke_bbox_coder.py
mmdet3d/core/bbox/coders/smoke_bbox_coder.py
+208
-0
mmdet3d/core/bbox/iou_calculators/iou3d_calculator.py
mmdet3d/core/bbox/iou_calculators/iou3d_calculator.py
+27
-19
mmdet3d/core/bbox/samplers/iou_neg_piecewise_sampler.py
mmdet3d/core/bbox/samplers/iou_neg_piecewise_sampler.py
+33
-8
mmdet3d/core/bbox/structures/__init__.py
mmdet3d/core/bbox/structures/__init__.py
+4
-3
mmdet3d/core/bbox/structures/base_box3d.py
mmdet3d/core/bbox/structures/base_box3d.py
+173
-52
mmdet3d/core/bbox/structures/box_3d_mode.py
mmdet3d/core/bbox/structures/box_3d_mode.py
+49
-18
mmdet3d/core/bbox/structures/cam_box3d.py
mmdet3d/core/bbox/structures/cam_box3d.py
+111
-81
mmdet3d/core/bbox/structures/coord_3d_mode.py
mmdet3d/core/bbox/structures/coord_3d_mode.py
+63
-110
mmdet3d/core/bbox/structures/depth_box3d.py
mmdet3d/core/bbox/structures/depth_box3d.py
+34
-107
mmdet3d/core/bbox/structures/lidar_box3d.py
mmdet3d/core/bbox/structures/lidar_box3d.py
+34
-94
mmdet3d/core/bbox/structures/utils.py
mmdet3d/core/bbox/structures/utils.py
+173
-52
mmdet3d/core/bbox/transforms.py
mmdet3d/core/bbox/transforms.py
+5
-5
mmdet3d/core/evaluation/indoor_eval.py
mmdet3d/core/evaluation/indoor_eval.py
+8
-9
mmdet3d/core/evaluation/kitti_utils/eval.py
mmdet3d/core/evaluation/kitti_utils/eval.py
+157
-54
mmdet3d/core/evaluation/kitti_utils/rotate_iou.py
mmdet3d/core/evaluation/kitti_utils/rotate_iou.py
+18
-18
mmdet3d/core/evaluation/lyft_eval.py
mmdet3d/core/evaluation/lyft_eval.py
+6
-5
mmdet3d/core/evaluation/seg_eval.py
mmdet3d/core/evaluation/seg_eval.py
+1
-1
No files found.
mmdet3d/core/bbox/coders/partial_bin_based_bbox_coder.py
View file @
32a4328b
...
...
@@ -29,7 +29,7 @@ class PartialBinBasedBBoxCoder(BaseBBoxCoder):
"""Encode ground truth to prediction targets.
Args:
gt_bboxes_3d (BaseInstance3DBoxes): Ground truth bboxes
\
gt_bboxes_3d (BaseInstance3DBoxes): Ground truth bboxes
with shape (n, 7).
gt_labels_3d (torch.Tensor): Ground truth classes.
...
...
mmdet3d/core/bbox/coders/pgd_bbox_coder.py
0 → 100644
View file @
32a4328b
# Copyright (c) OpenMMLab. All rights reserved.
import
numpy
as
np
import
torch
from
torch.nn
import
functional
as
F
from
mmdet.core.bbox.builder
import
BBOX_CODERS
from
.fcos3d_bbox_coder
import
FCOS3DBBoxCoder
@
BBOX_CODERS
.
register_module
()
class
PGDBBoxCoder
(
FCOS3DBBoxCoder
):
"""Bounding box coder for PGD."""
def
encode
(
self
,
gt_bboxes_3d
,
gt_labels_3d
,
gt_bboxes
,
gt_labels
):
# TODO: refactor the encoder codes in the FCOS3D and PGD head
pass
def
decode_2d
(
self
,
bbox
,
scale
,
stride
,
max_regress_range
,
training
,
pred_keypoints
=
False
,
pred_bbox2d
=
True
):
"""Decode regressed 2D attributes.
Args:
bbox (torch.Tensor): Raw bounding box predictions in shape
[N, C, H, W].
scale (tuple[`Scale`]): Learnable scale parameters.
stride (int): Stride for a specific feature level.
max_regress_range (int): Maximum regression range for a specific
feature level.
training (bool): Whether the decoding is in the training
procedure.
pred_keypoints (bool, optional): Whether to predict keypoints.
Defaults to False.
pred_bbox2d (bool, optional): Whether to predict 2D bounding
boxes. Defaults to False.
Returns:
torch.Tensor: Decoded boxes.
"""
clone_bbox
=
bbox
.
clone
()
if
pred_keypoints
:
scale_kpts
=
scale
[
3
]
# 2 dimension of offsets x 8 corners of a 3D bbox
bbox
[:,
self
.
bbox_code_size
:
self
.
bbox_code_size
+
16
]
=
\
torch
.
tanh
(
scale_kpts
(
clone_bbox
[
:,
self
.
bbox_code_size
:
self
.
bbox_code_size
+
16
]).
float
())
if
pred_bbox2d
:
scale_bbox2d
=
scale
[
-
1
]
# The last four dimensions are offsets to four sides of a 2D bbox
bbox
[:,
-
4
:]
=
scale_bbox2d
(
clone_bbox
[:,
-
4
:]).
float
()
if
self
.
norm_on_bbox
:
if
pred_bbox2d
:
bbox
[:,
-
4
:]
=
F
.
relu
(
bbox
.
clone
()[:,
-
4
:])
if
not
training
:
if
pred_keypoints
:
bbox
[
:,
self
.
bbox_code_size
:
self
.
bbox_code_size
+
16
]
*=
\
max_regress_range
if
pred_bbox2d
:
bbox
[:,
-
4
:]
*=
stride
else
:
if
pred_bbox2d
:
bbox
[:,
-
4
:]
=
bbox
.
clone
()[:,
-
4
:].
exp
()
return
bbox
def
decode_prob_depth
(
self
,
depth_cls_preds
,
depth_range
,
depth_unit
,
division
,
num_depth_cls
):
"""Decode probabilistic depth map.
Args:
depth_cls_preds (torch.Tensor): Depth probabilistic map in shape
[..., self.num_depth_cls] (raw output before softmax).
depth_range (tuple[float]): Range of depth estimation.
depth_unit (int): Unit of depth range division.
division (str): Depth division method. Options include 'uniform',
'linear', 'log', 'loguniform'.
num_depth_cls (int): Number of depth classes.
Returns:
torch.Tensor: Decoded probabilistic depth estimation.
"""
if
division
==
'uniform'
:
depth_multiplier
=
depth_unit
*
\
depth_cls_preds
.
new_tensor
(
list
(
range
(
num_depth_cls
))).
reshape
([
1
,
-
1
])
prob_depth_preds
=
(
F
.
softmax
(
depth_cls_preds
.
clone
(),
dim
=-
1
)
*
depth_multiplier
).
sum
(
dim
=-
1
)
return
prob_depth_preds
elif
division
==
'linear'
:
split_pts
=
depth_cls_preds
.
new_tensor
(
list
(
range
(
num_depth_cls
))).
reshape
([
1
,
-
1
])
depth_multiplier
=
depth_range
[
0
]
+
(
depth_range
[
1
]
-
depth_range
[
0
])
/
\
(
num_depth_cls
*
(
num_depth_cls
-
1
))
*
\
(
split_pts
*
(
split_pts
+
1
))
prob_depth_preds
=
(
F
.
softmax
(
depth_cls_preds
.
clone
(),
dim
=-
1
)
*
depth_multiplier
).
sum
(
dim
=-
1
)
return
prob_depth_preds
elif
division
==
'log'
:
split_pts
=
depth_cls_preds
.
new_tensor
(
list
(
range
(
num_depth_cls
))).
reshape
([
1
,
-
1
])
start
=
max
(
depth_range
[
0
],
1
)
end
=
depth_range
[
1
]
depth_multiplier
=
(
np
.
log
(
start
)
+
split_pts
*
np
.
log
(
end
/
start
)
/
(
num_depth_cls
-
1
)).
exp
()
prob_depth_preds
=
(
F
.
softmax
(
depth_cls_preds
.
clone
(),
dim
=-
1
)
*
depth_multiplier
).
sum
(
dim
=-
1
)
return
prob_depth_preds
elif
division
==
'loguniform'
:
split_pts
=
depth_cls_preds
.
new_tensor
(
list
(
range
(
num_depth_cls
))).
reshape
([
1
,
-
1
])
start
=
max
(
depth_range
[
0
],
1
)
end
=
depth_range
[
1
]
log_multiplier
=
np
.
log
(
start
)
+
\
split_pts
*
np
.
log
(
end
/
start
)
/
(
num_depth_cls
-
1
)
prob_depth_preds
=
(
F
.
softmax
(
depth_cls_preds
.
clone
(),
dim
=-
1
)
*
log_multiplier
).
sum
(
dim
=-
1
).
exp
()
return
prob_depth_preds
else
:
raise
NotImplementedError
mmdet3d/core/bbox/coders/point_xyzwhlr_bbox_coder.py
0 → 100644
View file @
32a4328b
# Copyright (c) OpenMMLab. All rights reserved.
import
numpy
as
np
import
torch
from
mmdet.core.bbox
import
BaseBBoxCoder
from
mmdet.core.bbox.builder
import
BBOX_CODERS
@
BBOX_CODERS
.
register_module
()
class
PointXYZWHLRBBoxCoder
(
BaseBBoxCoder
):
"""Point based bbox coder for 3D boxes.
Args:
code_size (int): The dimension of boxes to be encoded.
use_mean_size (bool, optional): Whether using anchors based on class.
Defaults to True.
mean_size (list[list[float]], optional): Mean size of bboxes in
each class. Defaults to None.
"""
def
__init__
(
self
,
code_size
=
7
,
use_mean_size
=
True
,
mean_size
=
None
):
super
(
PointXYZWHLRBBoxCoder
,
self
).
__init__
()
self
.
code_size
=
code_size
self
.
use_mean_size
=
use_mean_size
if
self
.
use_mean_size
:
self
.
mean_size
=
torch
.
from_numpy
(
np
.
array
(
mean_size
)).
float
()
assert
self
.
mean_size
.
min
()
>
0
,
\
f
'The min of mean_size should > 0, however currently it is '
\
f
'
{
self
.
mean_size
.
min
()
}
, please check it in your config.'
def
encode
(
self
,
gt_bboxes_3d
,
points
,
gt_labels_3d
=
None
):
"""Encode ground truth to prediction targets.
Args:
gt_bboxes_3d (:obj:`BaseInstance3DBoxes`): Ground truth bboxes
with shape (N, 7 + C).
points (torch.Tensor): Point cloud with shape (N, 3).
gt_labels_3d (torch.Tensor, optional): Ground truth classes.
Defaults to None.
Returns:
torch.Tensor: Encoded boxes with shape (N, 8 + C).
"""
gt_bboxes_3d
[:,
3
:
6
]
=
torch
.
clamp_min
(
gt_bboxes_3d
[:,
3
:
6
],
min
=
1e-5
)
xg
,
yg
,
zg
,
dxg
,
dyg
,
dzg
,
rg
,
*
cgs
=
torch
.
split
(
gt_bboxes_3d
,
1
,
dim
=-
1
)
xa
,
ya
,
za
=
torch
.
split
(
points
,
1
,
dim
=-
1
)
if
self
.
use_mean_size
:
assert
gt_labels_3d
.
max
()
<=
self
.
mean_size
.
shape
[
0
]
-
1
,
\
f
'the max gt label
{
gt_labels_3d
.
max
()
}
is bigger than'
\
f
'anchor types
{
self
.
mean_size
.
shape
[
0
]
-
1
}
.'
self
.
mean_size
=
self
.
mean_size
.
to
(
gt_labels_3d
.
device
)
point_anchor_size
=
self
.
mean_size
[
gt_labels_3d
]
dxa
,
dya
,
dza
=
torch
.
split
(
point_anchor_size
,
1
,
dim
=-
1
)
diagonal
=
torch
.
sqrt
(
dxa
**
2
+
dya
**
2
)
xt
=
(
xg
-
xa
)
/
diagonal
yt
=
(
yg
-
ya
)
/
diagonal
zt
=
(
zg
-
za
)
/
dza
dxt
=
torch
.
log
(
dxg
/
dxa
)
dyt
=
torch
.
log
(
dyg
/
dya
)
dzt
=
torch
.
log
(
dzg
/
dza
)
else
:
xt
=
(
xg
-
xa
)
yt
=
(
yg
-
ya
)
zt
=
(
zg
-
za
)
dxt
=
torch
.
log
(
dxg
)
dyt
=
torch
.
log
(
dyg
)
dzt
=
torch
.
log
(
dzg
)
return
torch
.
cat
(
[
xt
,
yt
,
zt
,
dxt
,
dyt
,
dzt
,
torch
.
cos
(
rg
),
torch
.
sin
(
rg
),
*
cgs
],
dim
=-
1
)
def
decode
(
self
,
box_encodings
,
points
,
pred_labels_3d
=
None
):
"""Decode predicted parts and points to bbox3d.
Args:
box_encodings (torch.Tensor): Encoded boxes with shape (N, 8 + C).
points (torch.Tensor): Point cloud with shape (N, 3).
pred_labels_3d (torch.Tensor): Bbox predicted labels (N, M).
Returns:
torch.Tensor: Decoded boxes with shape (N, 7 + C)
"""
xt
,
yt
,
zt
,
dxt
,
dyt
,
dzt
,
cost
,
sint
,
*
cts
=
torch
.
split
(
box_encodings
,
1
,
dim
=-
1
)
xa
,
ya
,
za
=
torch
.
split
(
points
,
1
,
dim
=-
1
)
if
self
.
use_mean_size
:
assert
pred_labels_3d
.
max
()
<=
self
.
mean_size
.
shape
[
0
]
-
1
,
\
f
'The max pred label
{
pred_labels_3d
.
max
()
}
is bigger than'
\
f
'anchor types
{
self
.
mean_size
.
shape
[
0
]
-
1
}
.'
self
.
mean_size
=
self
.
mean_size
.
to
(
pred_labels_3d
.
device
)
point_anchor_size
=
self
.
mean_size
[
pred_labels_3d
]
dxa
,
dya
,
dza
=
torch
.
split
(
point_anchor_size
,
1
,
dim
=-
1
)
diagonal
=
torch
.
sqrt
(
dxa
**
2
+
dya
**
2
)
xg
=
xt
*
diagonal
+
xa
yg
=
yt
*
diagonal
+
ya
zg
=
zt
*
dza
+
za
dxg
=
torch
.
exp
(
dxt
)
*
dxa
dyg
=
torch
.
exp
(
dyt
)
*
dya
dzg
=
torch
.
exp
(
dzt
)
*
dza
else
:
xg
=
xt
+
xa
yg
=
yt
+
ya
zg
=
zt
+
za
dxg
,
dyg
,
dzg
=
torch
.
split
(
torch
.
exp
(
box_encodings
[...,
3
:
6
]),
1
,
dim
=-
1
)
rg
=
torch
.
atan2
(
sint
,
cost
)
return
torch
.
cat
([
xg
,
yg
,
zg
,
dxg
,
dyg
,
dzg
,
rg
,
*
cts
],
dim
=-
1
)
mmdet3d/core/bbox/coders/smoke_bbox_coder.py
0 → 100644
View file @
32a4328b
# Copyright (c) OpenMMLab. All rights reserved.
import
numpy
as
np
import
torch
from
mmdet.core.bbox
import
BaseBBoxCoder
from
mmdet.core.bbox.builder
import
BBOX_CODERS
@
BBOX_CODERS
.
register_module
()
class
SMOKECoder
(
BaseBBoxCoder
):
"""Bbox Coder for SMOKE.
Args:
base_depth (tuple[float]): Depth references for decode box depth.
base_dims (tuple[tuple[float]]): Dimension references [l, h, w]
for decode box dimension for each category.
code_size (int): The dimension of boxes to be encoded.
"""
def
__init__
(
self
,
base_depth
,
base_dims
,
code_size
):
super
(
SMOKECoder
,
self
).
__init__
()
self
.
base_depth
=
base_depth
self
.
base_dims
=
base_dims
self
.
bbox_code_size
=
code_size
def
encode
(
self
,
locations
,
dimensions
,
orientations
,
input_metas
):
"""Encode CameraInstance3DBoxes by locations, dimensions, orientations.
Args:
locations (Tensor): Center location for 3D boxes.
(N, 3)
dimensions (Tensor): Dimensions for 3D boxes.
shape (N, 3)
orientations (Tensor): Orientations for 3D boxes.
shape (N, 1)
input_metas (list[dict]): Meta information of each image, e.g.,
image size, scaling factor, etc.
Return:
:obj:`CameraInstance3DBoxes`: 3D bboxes of batch images,
shape (N, bbox_code_size).
"""
bboxes
=
torch
.
cat
((
locations
,
dimensions
,
orientations
),
dim
=
1
)
assert
bboxes
.
shape
[
1
]
==
self
.
bbox_code_size
,
'bboxes shape dose not'
\
'match the bbox_code_size.'
batch_bboxes
=
input_metas
[
0
][
'box_type_3d'
](
bboxes
,
box_dim
=
self
.
bbox_code_size
)
return
batch_bboxes
def
decode
(
self
,
reg
,
points
,
labels
,
cam2imgs
,
trans_mats
,
locations
=
None
):
"""Decode regression into locations, dimensions, orientations.
Args:
reg (Tensor): Batch regression for each predict center2d point.
shape: (batch * K (max_objs), C)
points(Tensor): Batch projected bbox centers on image plane.
shape: (batch * K (max_objs) , 2)
labels (Tensor): Batch predict class label for each predict
center2d point.
shape: (batch, K (max_objs))
cam2imgs (Tensor): Batch images' camera intrinsic matrix.
shape: kitti (batch, 4, 4) nuscenes (batch, 3, 3)
trans_mats (Tensor): transformation matrix from original image
to feature map.
shape: (batch, 3, 3)
locations (None | Tensor): if locations is None, this function
is used to decode while inference, otherwise, it's used while
training using the ground truth 3d bbox locations.
shape: (batch * K (max_objs), 3)
Return:
tuple(Tensor): The tuple has components below:
- locations (Tensor): Centers of 3D boxes.
shape: (batch * K (max_objs), 3)
- dimensions (Tensor): Dimensions of 3D boxes.
shape: (batch * K (max_objs), 3)
- orientations (Tensor): Orientations of 3D
boxes.
shape: (batch * K (max_objs), 1)
"""
depth_offsets
=
reg
[:,
0
]
centers2d_offsets
=
reg
[:,
1
:
3
]
dimensions_offsets
=
reg
[:,
3
:
6
]
orientations
=
reg
[:,
6
:
8
]
depths
=
self
.
_decode_depth
(
depth_offsets
)
# get the 3D Bounding box's center location.
pred_locations
=
self
.
_decode_location
(
points
,
centers2d_offsets
,
depths
,
cam2imgs
,
trans_mats
)
pred_dimensions
=
self
.
_decode_dimension
(
labels
,
dimensions_offsets
)
if
locations
is
None
:
pred_orientations
=
self
.
_decode_orientation
(
orientations
,
pred_locations
)
else
:
pred_orientations
=
self
.
_decode_orientation
(
orientations
,
locations
)
return
pred_locations
,
pred_dimensions
,
pred_orientations
def
_decode_depth
(
self
,
depth_offsets
):
"""Transform depth offset to depth."""
base_depth
=
depth_offsets
.
new_tensor
(
self
.
base_depth
)
depths
=
depth_offsets
*
base_depth
[
1
]
+
base_depth
[
0
]
return
depths
def
_decode_location
(
self
,
points
,
centers2d_offsets
,
depths
,
cam2imgs
,
trans_mats
):
"""Retrieve objects location in camera coordinate based on projected
points.
Args:
points (Tensor): Projected points on feature map in (x, y)
shape: (batch * K, 2)
centers2d_offset (Tensor): Project points offset in
(delta_x, delta_y). shape: (batch * K, 2)
depths (Tensor): Object depth z.
shape: (batch * K)
cam2imgs (Tensor): Batch camera intrinsics matrix.
shape: kitti (batch, 4, 4) nuscenes (batch, 3, 3)
trans_mats (Tensor): transformation matrix from original image
to feature map.
shape: (batch, 3, 3)
"""
# number of points
N
=
centers2d_offsets
.
shape
[
0
]
# batch_size
N_batch
=
cam2imgs
.
shape
[
0
]
batch_id
=
torch
.
arange
(
N_batch
).
unsqueeze
(
1
)
obj_id
=
batch_id
.
repeat
(
1
,
N
//
N_batch
).
flatten
()
trans_mats_inv
=
trans_mats
.
inverse
()[
obj_id
]
cam2imgs_inv
=
cam2imgs
.
inverse
()[
obj_id
]
centers2d
=
points
+
centers2d_offsets
centers2d_extend
=
torch
.
cat
((
centers2d
,
centers2d
.
new_ones
(
N
,
1
)),
dim
=
1
)
# expand project points as [N, 3, 1]
centers2d_extend
=
centers2d_extend
.
unsqueeze
(
-
1
)
# transform project points back on original image
centers2d_img
=
torch
.
matmul
(
trans_mats_inv
,
centers2d_extend
)
centers2d_img
=
centers2d_img
*
depths
.
view
(
N
,
-
1
,
1
)
if
cam2imgs
.
shape
[
1
]
==
4
:
centers2d_img
=
torch
.
cat
(
(
centers2d_img
,
centers2d
.
new_ones
(
N
,
1
,
1
)),
dim
=
1
)
locations
=
torch
.
matmul
(
cam2imgs_inv
,
centers2d_img
).
squeeze
(
2
)
return
locations
[:,
:
3
]
def
_decode_dimension
(
self
,
labels
,
dims_offset
):
"""Transform dimension offsets to dimension according to its category.
Args:
labels (Tensor): Each points' category id.
shape: (N, K)
dims_offset (Tensor): Dimension offsets.
shape: (N, 3)
"""
labels
=
labels
.
flatten
().
long
()
base_dims
=
dims_offset
.
new_tensor
(
self
.
base_dims
)
dims_select
=
base_dims
[
labels
,
:]
dimensions
=
dims_offset
.
exp
()
*
dims_select
return
dimensions
def
_decode_orientation
(
self
,
ori_vector
,
locations
):
"""Retrieve object orientation.
Args:
ori_vector (Tensor): Local orientation in [sin, cos] format.
shape: (N, 2)
locations (Tensor): Object location.
shape: (N, 3)
Return:
Tensor: yaw(Orientation). Notice that the yaw's
range is [-np.pi, np.pi].
shape:(N, 1)
"""
assert
len
(
ori_vector
)
==
len
(
locations
)
locations
=
locations
.
view
(
-
1
,
3
)
rays
=
torch
.
atan
(
locations
[:,
0
]
/
(
locations
[:,
2
]
+
1e-7
))
alphas
=
torch
.
atan
(
ori_vector
[:,
0
]
/
(
ori_vector
[:,
1
]
+
1e-7
))
# get cosine value positive and negative index.
cos_pos_inds
=
(
ori_vector
[:,
1
]
>=
0
).
nonzero
(
as_tuple
=
False
)
cos_neg_inds
=
(
ori_vector
[:,
1
]
<
0
).
nonzero
(
as_tuple
=
False
)
alphas
[
cos_pos_inds
]
-=
np
.
pi
/
2
alphas
[
cos_neg_inds
]
+=
np
.
pi
/
2
# retrieve object rotation y angle.
yaws
=
alphas
+
rays
larger_inds
=
(
yaws
>
np
.
pi
).
nonzero
(
as_tuple
=
False
)
small_inds
=
(
yaws
<
-
np
.
pi
).
nonzero
(
as_tuple
=
False
)
if
len
(
larger_inds
)
!=
0
:
yaws
[
larger_inds
]
-=
2
*
np
.
pi
if
len
(
small_inds
)
!=
0
:
yaws
[
small_inds
]
+=
2
*
np
.
pi
yaws
=
yaws
.
unsqueeze
(
-
1
)
return
yaws
mmdet3d/core/bbox/iou_calculators/iou3d_calculator.py
View file @
32a4328b
...
...
@@ -31,15 +31,17 @@ class BboxOverlapsNearest3D(object):
between each aligned pair of bboxes1 and bboxes2.
Args:
bboxes1 (torch.Tensor): shape (N, 7+N) [x, y, z, h, w, l, ry, v].
bboxes2 (torch.Tensor): shape (M, 7+N) [x, y, z, h, w, l, ry, v].
bboxes1 (torch.Tensor): shape (N, 7+N)
[x, y, z, x_size, y_size, z_size, ry, v].
bboxes2 (torch.Tensor): shape (M, 7+N)
[x, y, z, x_size, y_size, z_size, ry, v].
mode (str): "iou" (intersection over union) or iof
(intersection over foreground).
is_aligned (bool): Whether the calculation is aligned.
Return:
torch.Tensor: If ``is_aligned`` is ``True``, return ious between
\
bboxes1 and bboxes2 with shape (M, N). If ``is_aligned`` is
\
torch.Tensor: If ``is_aligned`` is ``True``, return ious between
bboxes1 and bboxes2 with shape (M, N). If ``is_aligned`` is
``False``, return shape is M.
"""
return
bbox_overlaps_nearest_3d
(
bboxes1
,
bboxes2
,
mode
,
is_aligned
,
...
...
@@ -74,13 +76,15 @@ class BboxOverlaps3D(object):
calculate the actual 3D IoUs of boxes.
Args:
bboxes1 (torch.Tensor): shape (N, 7+C) [x, y, z, h, w, l, ry].
bboxes2 (torch.Tensor): shape (M, 7+C) [x, y, z, h, w, l, ry].
bboxes1 (torch.Tensor): with shape (N, 7+C),
(x, y, z, x_size, y_size, z_size, ry, v*).
bboxes2 (torch.Tensor): with shape (M, 7+C),
(x, y, z, x_size, y_size, z_size, ry, v*).
mode (str): "iou" (intersection over union) or
iof (intersection over foreground).
Return:
torch.Tensor: Bbox overlaps results of bboxes1 and bboxes2
\
torch.Tensor: Bbox overlaps results of bboxes1 and bboxes2
with shape (M, N) (aligned mode is not supported currently).
"""
return
bbox_overlaps_3d
(
bboxes1
,
bboxes2
,
mode
,
self
.
coordinate
)
...
...
@@ -102,7 +106,7 @@ def bbox_overlaps_nearest_3d(bboxes1,
Note:
This function first finds the nearest 2D boxes in bird eye view
(BEV), and then calculates the 2D IoU using :meth:`bbox_overlaps`.
Ths IoU calculator :class:`BboxOverlapsNearest3D` uses this
Th
i
s IoU calculator :class:`BboxOverlapsNearest3D` uses this
function to calculate IoUs of boxes.
If ``is_aligned`` is ``False``, then it calculates the ious between
...
...
@@ -110,15 +114,17 @@ def bbox_overlaps_nearest_3d(bboxes1,
aligned pair of bboxes1 and bboxes2.
Args:
bboxes1 (torch.Tensor): shape (N, 7+C) [x, y, z, h, w, l, ry, v].
bboxes2 (torch.Tensor): shape (M, 7+C) [x, y, z, h, w, l, ry, v].
bboxes1 (torch.Tensor): with shape (N, 7+C),
(x, y, z, x_size, y_size, z_size, ry, v*).
bboxes2 (torch.Tensor): with shape (M, 7+C),
(x, y, z, x_size, y_size, z_size, ry, v*).
mode (str): "iou" (intersection over union) or iof
(intersection over foreground).
is_aligned (bool): Whether the calculation is aligned
Return:
torch.Tensor: If ``is_aligned`` is ``True``, return ious between
\
bboxes1 and bboxes2 with shape (M, N). If ``is_aligned`` is
\
torch.Tensor: If ``is_aligned`` is ``True``, return ious between
bboxes1 and bboxes2 with shape (M, N). If ``is_aligned`` is
``False``, return shape is M.
"""
assert
bboxes1
.
size
(
-
1
)
==
bboxes2
.
size
(
-
1
)
>=
7
...
...
@@ -148,14 +154,16 @@ def bbox_overlaps_3d(bboxes1, bboxes2, mode='iou', coordinate='camera'):
calculate the actual IoUs of boxes.
Args:
bboxes1 (torch.Tensor): shape (N, 7+C) [x, y, z, h, w, l, ry].
bboxes2 (torch.Tensor): shape (M, 7+C) [x, y, z, h, w, l, ry].
bboxes1 (torch.Tensor): with shape (N, 7+C),
(x, y, z, x_size, y_size, z_size, ry, v*).
bboxes2 (torch.Tensor): with shape (M, 7+C),
(x, y, z, x_size, y_size, z_size, ry, v*).
mode (str): "iou" (intersection over union) or
iof (intersection over foreground).
coordinate (str): 'camera' or 'lidar' coordinate system.
Return:
torch.Tensor: Bbox overlaps results of bboxes1 and bboxes2
\
torch.Tensor: Bbox overlaps results of bboxes1 and bboxes2
with shape (M, N) (aligned mode is not supported currently).
"""
assert
bboxes1
.
size
(
-
1
)
==
bboxes2
.
size
(
-
1
)
>=
7
...
...
@@ -185,7 +193,7 @@ class AxisAlignedBboxOverlaps3D(object):
mode (str): "iou" (intersection over union) or "giou" (generalized
intersection over union).
is_aligned (bool, optional): If True, then m and n must be equal.
Default False.
Default
s to
False.
Returns:
Tensor: shape (m, n) if ``is_aligned`` is False else shape (m,)
"""
...
...
@@ -219,9 +227,9 @@ def axis_aligned_bbox_overlaps_3d(bboxes1,
mode (str): "iou" (intersection over union) or "giou" (generalized
intersection over union).
is_aligned (bool, optional): If True, then m and n must be equal.
Default False.
Default
s to
False.
eps (float, optional): A value added to the denominator for numerical
stability. Default 1e-6.
stability. Default
s to
1e-6.
Returns:
Tensor: shape (m, n) if ``is_aligned`` is False else shape (m,)
...
...
@@ -250,7 +258,7 @@ def axis_aligned_bbox_overlaps_3d(bboxes1,
"""
assert
mode
in
[
'iou'
,
'giou'
],
f
'Unsupported mode
{
mode
}
'
# Either the boxes are empty or the length of boxes's last dimens
t
ion is 6
# Either the boxes are empty or the length of boxes's last dimension is 6
assert
(
bboxes1
.
size
(
-
1
)
==
6
or
bboxes1
.
size
(
0
)
==
0
)
assert
(
bboxes2
.
size
(
-
1
)
==
6
or
bboxes2
.
size
(
0
)
==
0
)
...
...
mmdet3d/core/bbox/samplers/iou_neg_piecewise_sampler.py
View file @
32a4328b
...
...
@@ -9,8 +9,8 @@ from . import RandomSampler, SamplingResult
class
IoUNegPiecewiseSampler
(
RandomSampler
):
"""IoU Piece-wise Sampling.
Sampling negtive proposals according to a list of IoU thresholds.
The negtive proposals are divided into several pieces according
Sampling neg
a
tive proposals according to a list of IoU thresholds.
The neg
a
tive proposals are divided into several pieces according
to `neg_iou_piece_thrs`. And the ratio of each piece is indicated
by `neg_piece_fractions`.
...
...
@@ -18,11 +18,11 @@ class IoUNegPiecewiseSampler(RandomSampler):
num (int): Number of proposals.
pos_fraction (float): The fraction of positive proposals.
neg_piece_fractions (list): A list contains fractions that indicates
the ratio of each piece of total negtive samplers.
the ratio of each piece of total neg
a
tive samplers.
neg_iou_piece_thrs (list): A list contains IoU thresholds that
indicate the upper bound of this piece.
neg_pos_ub (float): The total ratio to limit the upper bound
number of negtive samples.
number of neg
a
tive samples.
add_gt_as_proposals (bool): Whether to add gt as proposals.
"""
...
...
@@ -59,8 +59,8 @@ class IoUNegPiecewiseSampler(RandomSampler):
neg_inds
=
torch
.
nonzero
(
assign_result
.
gt_inds
==
0
,
as_tuple
=
False
)
if
neg_inds
.
numel
()
!=
0
:
neg_inds
=
neg_inds
.
squeeze
(
1
)
if
len
(
neg_inds
)
<=
num_expected
:
return
neg_inds
if
len
(
neg_inds
)
<=
0
:
return
neg_inds
.
squeeze
(
1
)
else
:
neg_inds_choice
=
neg_inds
.
new_zeros
([
0
])
extend_num
=
0
...
...
@@ -88,12 +88,38 @@ class IoUNegPiecewiseSampler(RandomSampler):
neg_inds_choice
=
torch
.
cat
(
[
neg_inds_choice
,
neg_inds
[
piece_neg_inds
]],
dim
=
0
)
extend_num
+=
piece_expected_num
-
len
(
piece_neg_inds
)
# for the last piece
if
piece_inds
==
self
.
neg_piece_num
-
1
:
extend_neg_num
=
num_expected
-
len
(
neg_inds_choice
)
# if the numbers of nagetive samples > 0, we will
# randomly select num_expected samples in last piece
if
piece_neg_inds
.
numel
()
>
0
:
rand_idx
=
torch
.
randint
(
low
=
0
,
high
=
piece_neg_inds
.
numel
(),
size
=
(
extend_neg_num
,
)).
long
()
neg_inds_choice
=
torch
.
cat
(
[
neg_inds_choice
,
piece_neg_inds
[
rand_idx
]],
dim
=
0
)
# if the numbers of nagetive samples == 0, we will
# randomly select num_expected samples in all
# previous pieces
else
:
rand_idx
=
torch
.
randint
(
low
=
0
,
high
=
neg_inds_choice
.
numel
(),
size
=
(
extend_neg_num
,
)).
long
()
neg_inds_choice
=
torch
.
cat
(
[
neg_inds_choice
,
neg_inds_choice
[
rand_idx
]],
dim
=
0
)
else
:
piece_choice
=
self
.
random_choice
(
piece_neg_inds
,
piece_expected_num
)
neg_inds_choice
=
torch
.
cat
(
[
neg_inds_choice
,
neg_inds
[
piece_choice
]],
dim
=
0
)
extend_num
=
0
assert
len
(
neg_inds_choice
)
==
num_expected
return
neg_inds_choice
def
sample
(
self
,
...
...
@@ -111,7 +137,7 @@ class IoUNegPiecewiseSampler(RandomSampler):
assign_result (:obj:`AssignResult`): Bbox assigning results.
bboxes (torch.Tensor): Boxes to be sampled from.
gt_bboxes (torch.Tensor): Ground truth bboxes.
gt_labels (torch.Tensor, optional): Class labels of ground truth
\
gt_labels (torch.Tensor, optional): Class labels of ground truth
bboxes.
Returns:
...
...
@@ -145,7 +171,6 @@ class IoUNegPiecewiseSampler(RandomSampler):
num_expected_neg
=
neg_upper_bound
neg_inds
=
self
.
neg_sampler
.
_sample_neg
(
assign_result
,
num_expected_neg
,
bboxes
=
bboxes
,
**
kwargs
)
neg_inds
=
neg_inds
.
unique
()
sampling_result
=
SamplingResult
(
pos_inds
,
neg_inds
,
bboxes
,
gt_bboxes
,
assign_result
,
gt_flags
)
...
...
mmdet3d/core/bbox/structures/__init__.py
View file @
32a4328b
...
...
@@ -6,12 +6,13 @@ from .coord_3d_mode import Coord3DMode
from
.depth_box3d
import
DepthInstance3DBoxes
from
.lidar_box3d
import
LiDARInstance3DBoxes
from
.utils
import
(
get_box_type
,
get_proj_mat_by_coord_type
,
limit_period
,
mono_cam_box2vis
,
points_cam2img
,
rotation_3d_in_axis
,
xywhr2xyxyr
)
mono_cam_box2vis
,
points_cam2img
,
points_img2cam
,
rotation_3d_in_axis
,
xywhr2xyxyr
)
__all__
=
[
'Box3DMode'
,
'BaseInstance3DBoxes'
,
'LiDARInstance3DBoxes'
,
'CameraInstance3DBoxes'
,
'DepthInstance3DBoxes'
,
'xywhr2xyxyr'
,
'get_box_type'
,
'rotation_3d_in_axis'
,
'limit_period'
,
'points_cam2img'
,
'Coord3DMode'
,
'mono_cam_box2vis'
,
'get_proj_mat_by_coord_type'
'points_img2cam'
,
'Coord3DMode'
,
'mono_cam_box2vis'
,
'get_proj_mat_by_coord_type'
]
mmdet3d/core/bbox/structures/base_box3d.py
View file @
32a4328b
# Copyright (c) OpenMMLab. All rights reserved.
import
warnings
from
abc
import
abstractmethod
import
numpy
as
np
import
torch
from
abc
import
abstractmethod
from
mmdet3d.ops
import
points_in_boxes_all
,
points_in_boxes_part
from
mmdet3d.ops.iou3d
import
iou3d_cuda
from
.utils
import
limit_period
,
xywhr2xyxyr
...
...
@@ -18,12 +21,12 @@ class BaseInstance3DBoxes(object):
tensor (torch.Tensor | np.ndarray | list): a N x box_dim matrix.
box_dim (int): Number of the dimension of a box.
Each row is (x, y, z, x_size, y_size, z_size, yaw).
Default to 7.
Default
s
to 7.
with_yaw (bool): Whether the box is with yaw rotation.
If False, the value of yaw will be set to 0 as minmax boxes.
Default to True.
origin (tuple[float]
): The r
elative position of
origin in
the box.
Default to (0.5, 0.5, 0). This will guide the box be converted to
Default
s
to True.
origin (tuple[float]
, optional): R
elative position of the box
origin
.
Default
s
to (0.5, 0.5, 0). This will guide the box be converted to
(0.5, 0.5, 0) mode.
Attributes:
...
...
@@ -72,27 +75,29 @@ class BaseInstance3DBoxes(object):
@
property
def
dims
(
self
):
"""torch.Tensor:
Corner
s of each box
with siz
e (N,
8,
3)."""
"""torch.Tensor:
Size dimension
s of each box
in shap
e (N, 3)."""
return
self
.
tensor
[:,
3
:
6
]
@
property
def
yaw
(
self
):
"""torch.Tensor: A vector with yaw of each box."""
"""torch.Tensor: A vector with yaw of each box
in shape (N, )
."""
return
self
.
tensor
[:,
6
]
@
property
def
height
(
self
):
"""torch.Tensor: A vector with height of each box."""
"""torch.Tensor: A vector with height of each box
in shape (N, )
."""
return
self
.
tensor
[:,
5
]
@
property
def
top_height
(
self
):
"""torch.Tensor: A vector with the top height of each box."""
"""torch.Tensor:
A vector with the top height of each box in shape (N, )."""
return
self
.
bottom_height
+
self
.
height
@
property
def
bottom_height
(
self
):
"""torch.Tensor: A vector with bottom's height of each box."""
"""torch.Tensor:
A vector with bottom's height of each box in shape (N, )."""
return
self
.
tensor
[:,
2
]
@
property
...
...
@@ -100,58 +105,114 @@ class BaseInstance3DBoxes(object):
"""Calculate the center of all the boxes.
Note:
In
the
MMDetection3D's convention, the bottom center is
In MMDetection3D's convention, the bottom center is
usually taken as the default center.
The relative position of the centers in different kinds of
boxes are different, e.g., the relative center of a boxes is
(0.5, 1.0, 0.5) in camera and (0.5, 0.5, 0) in lidar.
It is recommended to use ``bottom_center`` or ``gravity_center``
for
more
clear usage.
for clear
er
usage.
Returns:
torch.Tensor: A tensor with center of each box.
torch.Tensor: A tensor with center of each box
in shape (N, 3)
.
"""
return
self
.
bottom_center
@
property
def
bottom_center
(
self
):
"""torch.Tensor: A tensor with center of each box."""
"""torch.Tensor: A tensor with center of each box
in shape (N, 3)
."""
return
self
.
tensor
[:,
:
3
]
@
property
def
gravity_center
(
self
):
"""torch.Tensor: A tensor with center of each box."""
"""torch.Tensor: A tensor with center of each box
in shape (N, 3)
."""
pass
@
property
def
corners
(
self
):
"""torch.Tensor: a tensor with 8 corners of each box."""
"""torch.Tensor:
a tensor with 8 corners of each box in shape (N, 8, 3)."""
pass
@
property
def
bev
(
self
):
"""torch.Tensor: 2D BEV box of each box with rotation
in XYWHR format, in shape (N, 5)."""
return
self
.
tensor
[:,
[
0
,
1
,
3
,
4
,
6
]]
@
property
def
nearest_bev
(
self
):
"""torch.Tensor: A tensor of 2D BEV box of each box
without rotation."""
# Obtain BEV boxes with rotation in XYWHR format
bev_rotated_boxes
=
self
.
bev
# convert the rotation to a valid range
rotations
=
bev_rotated_boxes
[:,
-
1
]
normed_rotations
=
torch
.
abs
(
limit_period
(
rotations
,
0.5
,
np
.
pi
))
# find the center of boxes
conditions
=
(
normed_rotations
>
np
.
pi
/
4
)[...,
None
]
bboxes_xywh
=
torch
.
where
(
conditions
,
bev_rotated_boxes
[:,
[
0
,
1
,
3
,
2
]],
bev_rotated_boxes
[:,
:
4
])
centers
=
bboxes_xywh
[:,
:
2
]
dims
=
bboxes_xywh
[:,
2
:]
bev_boxes
=
torch
.
cat
([
centers
-
dims
/
2
,
centers
+
dims
/
2
],
dim
=-
1
)
return
bev_boxes
def
in_range_bev
(
self
,
box_range
):
"""Check whether the boxes are in the given range.
Args:
box_range (list | torch.Tensor): the range of box
(x_min, y_min, x_max, y_max)
Note:
The original implementation of SECOND checks whether boxes in
a range by checking whether the points are in a convex
polygon, we reduce the burden for simpler cases.
Returns:
torch.Tensor: Whether each box is inside the reference range.
"""
in_range_flags
=
((
self
.
bev
[:,
0
]
>
box_range
[
0
])
&
(
self
.
bev
[:,
1
]
>
box_range
[
1
])
&
(
self
.
bev
[:,
0
]
<
box_range
[
2
])
&
(
self
.
bev
[:,
1
]
<
box_range
[
3
]))
return
in_range_flags
@
abstractmethod
def
rotate
(
self
,
angle
,
points
=
None
):
"""Rotate boxes with points (optional) with the given angle or
\
rotation
matrix.
"""Rotate boxes with points (optional) with the given angle or
rotation
matrix.
Args:
angle (float | torch.Tensor | np.ndarray):
Rotation angle or rotation matrix.
points (torch.Tensor, numpy.ndarray, :obj:`BasePoints`, optional):
points (torch.Tensor | numpy.ndarray |
:obj:`BasePoints`, optional):
Points to rotate. Defaults to None.
"""
pass
@
abstractmethod
def
flip
(
self
,
bev_direction
=
'horizontal'
):
"""Flip the boxes in BEV along given BEV direction."""
"""Flip the boxes in BEV along given BEV direction.
Args:
bev_direction (str, optional): Direction by which to flip.
Can be chosen from 'horizontal' and 'vertical'.
Defaults to 'horizontal'.
"""
pass
def
translate
(
self
,
trans_vector
):
"""Translate boxes with the given translation vector.
Args:
trans_vector (torch.Tensor): Translation vector of size
1x3
.
trans_vector (torch.Tensor): Translation vector of size
(1, 3)
.
"""
if
not
isinstance
(
trans_vector
,
torch
.
Tensor
):
trans_vector
=
self
.
tensor
.
new_tensor
(
trans_vector
)
...
...
@@ -170,7 +231,7 @@ class BaseInstance3DBoxes(object):
polygon, we try to reduce the burden for simpler cases.
Returns:
torch.Tensor: A binary vector indicating whether each box is
\
torch.Tensor: A binary vector indicating whether each box is
inside the reference range.
"""
in_range_flags
=
((
self
.
tensor
[:,
0
]
>
box_range
[
0
])
...
...
@@ -181,34 +242,21 @@ class BaseInstance3DBoxes(object):
&
(
self
.
tensor
[:,
2
]
<
box_range
[
5
]))
return
in_range_flags
@
abstractmethod
def
in_range_bev
(
self
,
box_range
):
"""Check whether the boxes are in the given range.
Args:
box_range (list | torch.Tensor): The range of box
in order of (x_min, y_min, x_max, y_max).
Returns:
torch.Tensor: Indicating whether each box is inside
\
the reference range.
"""
pass
@
abstractmethod
def
convert_to
(
self
,
dst
,
rt_mat
=
None
):
"""Convert self to ``dst`` mode.
Args:
dst (:obj:`Box3DMode`): The target Box mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from `src` coordinates to `dst` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
Returns:
:obj:`BaseInstance3DBoxes`: The converted box of the same type
\
:obj:`BaseInstance3DBoxes`: The converted box of the same type
in the `dst` mode.
"""
pass
...
...
@@ -220,28 +268,29 @@ class BaseInstance3DBoxes(object):
scale_factors (float): Scale factors to scale the boxes.
"""
self
.
tensor
[:,
:
6
]
*=
scale_factor
self
.
tensor
[:,
7
:]
*=
scale_factor
self
.
tensor
[:,
7
:]
*=
scale_factor
# velocity
def
limit_yaw
(
self
,
offset
=
0.5
,
period
=
np
.
pi
):
"""Limit the yaw to a given period and offset.
Args:
offset (float): The offset of the yaw.
period (float): The expected period.
offset (float
, optional
): The offset of the yaw.
Defaults to 0.5.
period (float
, optional
): The expected period.
Defaults to np.pi.
"""
self
.
tensor
[:,
6
]
=
limit_period
(
self
.
tensor
[:,
6
],
offset
,
period
)
def
nonempty
(
self
,
threshold
:
float
=
0.0
):
def
nonempty
(
self
,
threshold
=
0.0
):
"""Find boxes that are non-empty.
A box is considered empty,
if either of its side is no larger than threshold.
Args:
threshold (float): The threshold of minimal sizes.
threshold (float, optional): The threshold of minimal sizes.
Defaults to 0.0.
Returns:
torch.Tensor: A binary vector which represents whether each
\
torch.Tensor: A binary vector which represents whether each
box is empty (False) or non-empty (True).
"""
box
=
self
.
tensor
...
...
@@ -267,8 +316,8 @@ class BaseInstance3DBoxes(object):
subject to Pytorch's indexing semantics.
Returns:
:obj:`BaseInstance3DBoxes`: A new object of
\
:class:`BaseInstance
s
3DBoxes` after indexing.
:obj:`BaseInstance3DBoxes`: A new object of
:class:`BaseInstance3DBoxes` after indexing.
"""
original_type
=
type
(
self
)
if
isinstance
(
item
,
int
):
...
...
@@ -319,7 +368,7 @@ class BaseInstance3DBoxes(object):
device (str | :obj:`torch.device`): The name of the device.
Returns:
:obj:`BaseInstance3DBoxes`: A new boxes object on the
\
:obj:`BaseInstance3DBoxes`: A new boxes object on the
specific device.
"""
original_type
=
type
(
self
)
...
...
@@ -332,7 +381,7 @@ class BaseInstance3DBoxes(object):
"""Clone the Boxes.
Returns:
:obj:`BaseInstance3DBoxes`: Box object with the same properties
\
:obj:`BaseInstance3DBoxes`: Box object with the same properties
as self.
"""
original_type
=
type
(
self
)
...
...
@@ -363,7 +412,7 @@ class BaseInstance3DBoxes(object):
Args:
boxes1 (:obj:`BaseInstance3DBoxes`): Boxes 1 contain N boxes.
boxes2 (:obj:`BaseInstance3DBoxes`): Boxes 2 contain M boxes.
mode (str, optional): Mode of
iou
calculation. Defaults to 'iou'.
mode (str, optional): Mode of
IoU
calculation. Defaults to 'iou'.
Returns:
torch.Tensor: Calculated iou of boxes.
...
...
@@ -444,14 +493,14 @@ class BaseInstance3DBoxes(object):
def
new_box
(
self
,
data
):
"""Create a new box object with data.
The new box and its tensor has the similar properties
\
The new box and its tensor has the similar properties
as self and self.tensor, respectively.
Args:
data (torch.Tensor | numpy.array | list): Data to be copied.
Returns:
:obj:`BaseInstance3DBoxes`: A new bbox object with ``data``,
\
:obj:`BaseInstance3DBoxes`: A new bbox object with ``data``,
the object's other properties are similar to ``self``.
"""
new_tensor
=
self
.
tensor
.
new_tensor
(
data
)
\
...
...
@@ -459,3 +508,75 @@ class BaseInstance3DBoxes(object):
original_type
=
type
(
self
)
return
original_type
(
new_tensor
,
box_dim
=
self
.
box_dim
,
with_yaw
=
self
.
with_yaw
)
def
points_in_boxes_part
(
self
,
points
,
boxes_override
=
None
):
"""Find the box in which each point is.
Args:
points (torch.Tensor): Points in shape (1, M, 3) or (M, 3),
3 dimensions are (x, y, z) in LiDAR or depth coordinate.
boxes_override (torch.Tensor, optional): Boxes to override
`self.tensor`. Defaults to None.
Returns:
torch.Tensor: The index of the first box that each point
is in, in shape (M, ). Default value is -1
(if the point is not enclosed by any box).
Note:
If a point is enclosed by multiple boxes, the index of the
first box will be returned.
"""
if
boxes_override
is
not
None
:
boxes
=
boxes_override
else
:
boxes
=
self
.
tensor
if
points
.
dim
()
==
2
:
points
=
points
.
unsqueeze
(
0
)
box_idx
=
points_in_boxes_part
(
points
,
boxes
.
unsqueeze
(
0
).
to
(
points
.
device
)).
squeeze
(
0
)
return
box_idx
def
points_in_boxes_all
(
self
,
points
,
boxes_override
=
None
):
"""Find all boxes in which each point is.
Args:
points (torch.Tensor): Points in shape (1, M, 3) or (M, 3),
3 dimensions are (x, y, z) in LiDAR or depth coordinate.
boxes_override (torch.Tensor, optional): Boxes to override
`self.tensor`. Defaults to None.
Returns:
torch.Tensor: A tensor indicating whether a point is in a box,
in shape (M, T). T is the number of boxes. Denote this
tensor as A, if the m^th point is in the t^th box, then
`A[m, t] == 1`, elsewise `A[m, t] == 0`.
"""
if
boxes_override
is
not
None
:
boxes
=
boxes_override
else
:
boxes
=
self
.
tensor
points_clone
=
points
.
clone
()[...,
:
3
]
if
points_clone
.
dim
()
==
2
:
points_clone
=
points_clone
.
unsqueeze
(
0
)
else
:
assert
points_clone
.
dim
()
==
3
and
points_clone
.
shape
[
0
]
==
1
boxes
=
boxes
.
to
(
points_clone
.
device
).
unsqueeze
(
0
)
box_idxs_of_pts
=
points_in_boxes_all
(
points_clone
,
boxes
)
return
box_idxs_of_pts
.
squeeze
(
0
)
def
points_in_boxes
(
self
,
points
,
boxes_override
=
None
):
warnings
.
warn
(
'DeprecationWarning: points_in_boxes is a '
'deprecated method, please consider using '
'points_in_boxes_part.'
)
return
self
.
points_in_boxes_part
(
points
,
boxes_override
)
def
points_in_boxes_batch
(
self
,
points
,
boxes_override
=
None
):
warnings
.
warn
(
'DeprecationWarning: points_in_boxes_batch is a '
'deprecated method, please consider using '
'points_in_boxes_all.'
)
return
self
.
points_in_boxes_all
(
points
,
boxes_override
)
mmdet3d/core/bbox/structures/box_3d_mode.py
View file @
32a4328b
# Copyright (c) OpenMMLab. All rights reserved.
from
enum
import
IntEnum
,
unique
import
numpy
as
np
import
torch
from
enum
import
IntEnum
,
unique
from
.base_box3d
import
BaseInstance3DBoxes
from
.cam_box3d
import
CameraInstance3DBoxes
from
.depth_box3d
import
DepthInstance3DBoxes
from
.lidar_box3d
import
LiDARInstance3DBoxes
from
.utils
import
limit_period
@
unique
...
...
@@ -61,23 +63,28 @@ class Box3DMode(IntEnum):
DEPTH
=
2
@
staticmethod
def
convert
(
box
,
src
,
dst
,
rt_mat
=
None
):
def
convert
(
box
,
src
,
dst
,
rt_mat
=
None
,
with_yaw
=
True
):
"""Convert boxes from `src` mode to `dst` mode.
Args:
box (tuple | list | np.ndarray |
torch.Tensor | BaseInstance3DBoxes):
torch.Tensor |
:obj:`
BaseInstance3DBoxes
`
):
Can be a k-tuple, k-list or an Nxk array/tensor, where k = 7.
src (:obj:`Box3DMode`): The src Box mode.
dst (:obj:`Box3DMode`): The target Box mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from `src` coordinates to `dst` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
with_yaw (bool, optional): If `box` is an instance of
:obj:`BaseInstance3DBoxes`, whether or not it has a yaw angle.
Defaults to True.
Returns:
(tuple | list | np.ndarray | torch.Tensor | BaseInstance3DBoxes):
\
(tuple | list | np.ndarray | torch.Tensor |
:obj:`BaseInstance3DBoxes`):
The converted box of the same type.
"""
if
src
==
dst
:
...
...
@@ -100,32 +107,53 @@ class Box3DMode(IntEnum):
else
:
arr
=
box
.
clone
()
if
is_Instance3DBoxes
:
with_yaw
=
box
.
with_yaw
# convert box from `src` mode to `dst` mode.
x_size
,
y_size
,
z_size
=
arr
[...,
3
:
4
],
arr
[...,
4
:
5
],
arr
[...,
5
:
6
]
if
with_yaw
:
yaw
=
arr
[...,
6
:
7
]
if
src
==
Box3DMode
.
LIDAR
and
dst
==
Box3DMode
.
CAM
:
if
rt_mat
is
None
:
rt_mat
=
arr
.
new_tensor
([[
0
,
-
1
,
0
],
[
0
,
0
,
-
1
],
[
1
,
0
,
0
]])
xyz_size
=
torch
.
cat
([
y_size
,
z_size
,
x_size
],
dim
=-
1
)
xyz_size
=
torch
.
cat
([
x_size
,
z_size
,
y_size
],
dim
=-
1
)
if
with_yaw
:
yaw
=
-
yaw
-
np
.
pi
/
2
yaw
=
limit_period
(
yaw
,
period
=
np
.
pi
*
2
)
elif
src
==
Box3DMode
.
CAM
and
dst
==
Box3DMode
.
LIDAR
:
if
rt_mat
is
None
:
rt_mat
=
arr
.
new_tensor
([[
0
,
0
,
1
],
[
-
1
,
0
,
0
],
[
0
,
-
1
,
0
]])
xyz_size
=
torch
.
cat
([
z_size
,
x_size
,
y_size
],
dim
=-
1
)
xyz_size
=
torch
.
cat
([
x_size
,
z_size
,
y_size
],
dim
=-
1
)
if
with_yaw
:
yaw
=
-
yaw
-
np
.
pi
/
2
yaw
=
limit_period
(
yaw
,
period
=
np
.
pi
*
2
)
elif
src
==
Box3DMode
.
DEPTH
and
dst
==
Box3DMode
.
CAM
:
if
rt_mat
is
None
:
rt_mat
=
arr
.
new_tensor
([[
1
,
0
,
0
],
[
0
,
0
,
1
],
[
0
,
-
1
,
0
]])
rt_mat
=
arr
.
new_tensor
([[
1
,
0
,
0
],
[
0
,
0
,
-
1
],
[
0
,
1
,
0
]])
xyz_size
=
torch
.
cat
([
x_size
,
z_size
,
y_size
],
dim
=-
1
)
if
with_yaw
:
yaw
=
-
yaw
elif
src
==
Box3DMode
.
CAM
and
dst
==
Box3DMode
.
DEPTH
:
if
rt_mat
is
None
:
rt_mat
=
arr
.
new_tensor
([[
1
,
0
,
0
],
[
0
,
0
,
-
1
],
[
0
,
1
,
0
]])
rt_mat
=
arr
.
new_tensor
([[
1
,
0
,
0
],
[
0
,
0
,
1
],
[
0
,
-
1
,
0
]])
xyz_size
=
torch
.
cat
([
x_size
,
z_size
,
y_size
],
dim
=-
1
)
if
with_yaw
:
yaw
=
-
yaw
elif
src
==
Box3DMode
.
LIDAR
and
dst
==
Box3DMode
.
DEPTH
:
if
rt_mat
is
None
:
rt_mat
=
arr
.
new_tensor
([[
0
,
-
1
,
0
],
[
1
,
0
,
0
],
[
0
,
0
,
1
]])
xyz_size
=
torch
.
cat
([
y_size
,
x_size
,
z_size
],
dim
=-
1
)
xyz_size
=
torch
.
cat
([
x_size
,
y_size
,
z_size
],
dim
=-
1
)
if
with_yaw
:
yaw
=
yaw
+
np
.
pi
/
2
yaw
=
limit_period
(
yaw
,
period
=
np
.
pi
*
2
)
elif
src
==
Box3DMode
.
DEPTH
and
dst
==
Box3DMode
.
LIDAR
:
if
rt_mat
is
None
:
rt_mat
=
arr
.
new_tensor
([[
0
,
1
,
0
],
[
-
1
,
0
,
0
],
[
0
,
0
,
1
]])
xyz_size
=
torch
.
cat
([
y_size
,
x_size
,
z_size
],
dim
=-
1
)
xyz_size
=
torch
.
cat
([
x_size
,
y_size
,
z_size
],
dim
=-
1
)
if
with_yaw
:
yaw
=
yaw
-
np
.
pi
/
2
yaw
=
limit_period
(
yaw
,
period
=
np
.
pi
*
2
)
else
:
raise
NotImplementedError
(
f
'Conversion from Box3DMode
{
src
}
to
{
dst
}
'
...
...
@@ -135,13 +163,17 @@ class Box3DMode(IntEnum):
rt_mat
=
arr
.
new_tensor
(
rt_mat
)
if
rt_mat
.
size
(
1
)
==
4
:
extended_xyz
=
torch
.
cat
(
[
arr
[
:
,
:
3
],
arr
.
new_ones
(
arr
.
size
(
0
),
1
)],
dim
=-
1
)
[
arr
[
...
,
:
3
],
arr
.
new_ones
(
arr
.
size
(
0
),
1
)],
dim
=-
1
)
xyz
=
extended_xyz
@
rt_mat
.
t
()
else
:
xyz
=
arr
[
:
,
:
3
]
@
rt_mat
.
t
()
xyz
=
arr
[
...
,
:
3
]
@
rt_mat
.
t
()
remains
=
arr
[...,
6
:]
arr
=
torch
.
cat
([
xyz
[:,
:
3
],
xyz_size
,
remains
],
dim
=-
1
)
if
with_yaw
:
remains
=
arr
[...,
7
:]
arr
=
torch
.
cat
([
xyz
[...,
:
3
],
xyz_size
,
yaw
,
remains
],
dim
=-
1
)
else
:
remains
=
arr
[...,
6
:]
arr
=
torch
.
cat
([
xyz
[...,
:
3
],
xyz_size
,
remains
],
dim
=-
1
)
# convert arr to the original type
original_type
=
type
(
box
)
...
...
@@ -160,7 +192,6 @@ class Box3DMode(IntEnum):
raise
NotImplementedError
(
f
'Conversion to
{
dst
}
through
{
original_type
}
'
' is not supported yet'
)
return
target_type
(
arr
,
box_dim
=
arr
.
size
(
-
1
),
with_yaw
=
box
.
with_yaw
)
return
target_type
(
arr
,
box_dim
=
arr
.
size
(
-
1
),
with_yaw
=
with_yaw
)
else
:
return
arr
mmdet3d/core/bbox/structures/cam_box3d.py
View file @
32a4328b
...
...
@@ -2,9 +2,9 @@
import
numpy
as
np
import
torch
from
mmdet3d.core
.points
import
BasePoints
from
..
.points
import
BasePoints
from
.base_box3d
import
BaseInstance3DBoxes
from
.utils
import
limit_period
,
rotation_3d_in_axis
from
.utils
import
rotation_3d_in_axis
,
yaw2local
class
CameraInstance3DBoxes
(
BaseInstance3DBoxes
):
...
...
@@ -28,16 +28,14 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
The yaw is 0 at the positive direction of x axis, and decreases from
the positive direction of x to the positive direction of z.
A refactor is ongoing to make the three coordinate systems
easier to understand and convert between each other.
Attributes:
tensor (torch.Tensor): Float matrix
of N x
box_dim.
box_dim (int): Integer indicat
es
the dimension of a box
tensor (torch.Tensor): Float matrix
in shape (N,
box_dim
)
.
box_dim (int): Integer indicat
ing
the dimension of a box
Each row is (x, y, z, x_size, y_size, z_size, yaw, ...).
with_yaw (bool): If True, the value of yaw will be set to 0 as
minmax
boxes.
with_yaw (bool): If True, the value of yaw will be set to 0 as
axis-aligned boxes tightly enclosing the original
boxes.
"""
YAW_AXIS
=
1
def
__init__
(
self
,
tensor
,
...
...
@@ -76,23 +74,39 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
@
property
def
height
(
self
):
"""torch.Tensor: A vector with height of each box."""
"""torch.Tensor: A vector with height of each box
in shape (N, )
."""
return
self
.
tensor
[:,
4
]
@
property
def
top_height
(
self
):
"""torch.Tensor: A vector with the top height of each box."""
"""torch.Tensor:
A vector with the top height of each box in shape (N, )."""
# the positive direction is down rather than up
return
self
.
bottom_height
-
self
.
height
@
property
def
bottom_height
(
self
):
"""torch.Tensor: A vector with bottom's height of each box."""
"""torch.Tensor:
A vector with bottom's height of each box in shape (N, )."""
return
self
.
tensor
[:,
1
]
@
property
def
local_yaw
(
self
):
"""torch.Tensor:
A vector with local yaw of each box in shape (N, ).
local_yaw equals to alpha in kitti, which is commonly
used in monocular 3D object detection task, so only
:obj:`CameraInstance3DBoxes` has the property.
"""
yaw
=
self
.
yaw
loc
=
self
.
gravity_center
local_yaw
=
yaw2local
(
yaw
,
loc
)
return
local_yaw
@
property
def
gravity_center
(
self
):
"""torch.Tensor: A tensor with center of each box."""
"""torch.Tensor: A tensor with center of each box
in shape (N, 3)
."""
bottom_center
=
self
.
bottom_center
gravity_center
=
torch
.
zeros_like
(
bottom_center
)
gravity_center
[:,
[
0
,
2
]]
=
bottom_center
[:,
[
0
,
2
]]
...
...
@@ -137,82 +151,66 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
corners_norm
=
corners_norm
-
dims
.
new_tensor
([
0.5
,
1
,
0.5
])
corners
=
dims
.
view
([
-
1
,
1
,
3
])
*
corners_norm
.
reshape
([
1
,
8
,
3
])
# rotate around y
axis
corners
=
rotation_3d_in_axis
(
corners
,
self
.
tensor
[:,
6
],
axis
=
1
)
corners
=
rotation_3d_in_
axis
(
corners
,
self
.
tensor
[:,
6
],
axis
=
self
.
YAW_AXIS
)
corners
+=
self
.
tensor
[:,
:
3
].
view
(
-
1
,
1
,
3
)
return
corners
@
property
def
bev
(
self
):
"""torch.Tensor: A n x 5 tensor of 2D BEV box of each box
with rotation in XYWHR format."""
return
self
.
tensor
[:,
[
0
,
2
,
3
,
5
,
6
]]
@
property
def
nearest_bev
(
self
):
"""torch.Tensor: A tensor of 2D BEV box of each box
without rotation."""
# Obtain BEV boxes with rotation in XZWHR format
bev_rotated_boxes
=
self
.
bev
# convert the rotation to a valid range
rotations
=
bev_rotated_boxes
[:,
-
1
]
normed_rotations
=
torch
.
abs
(
limit_period
(
rotations
,
0.5
,
np
.
pi
))
# find the center of boxes
conditions
=
(
normed_rotations
>
np
.
pi
/
4
)[...,
None
]
bboxes_xywh
=
torch
.
where
(
conditions
,
bev_rotated_boxes
[:,
[
0
,
1
,
3
,
2
]],
bev_rotated_boxes
[:,
:
4
])
centers
=
bboxes_xywh
[:,
:
2
]
dims
=
bboxes_xywh
[:,
2
:]
bev_boxes
=
torch
.
cat
([
centers
-
dims
/
2
,
centers
+
dims
/
2
],
dim
=-
1
)
return
bev_boxes
"""torch.Tensor: 2D BEV box of each box with rotation
in XYWHR format, in shape (N, 5)."""
bev
=
self
.
tensor
[:,
[
0
,
2
,
3
,
5
,
6
]].
clone
()
# positive direction of the gravity axis
# in cam coord system points to the earth
# so the bev yaw angle needs to be reversed
bev
[:,
-
1
]
=
-
bev
[:,
-
1
]
return
bev
def
rotate
(
self
,
angle
,
points
=
None
):
"""Rotate boxes with points (optional) with the given angle or
\
rotation
matrix.
"""Rotate boxes with points (optional) with the given angle or
rotation
matrix.
Args:
angle (float | torch.Tensor | np.ndarray):
Rotation angle or rotation matrix.
points (torch.Tensor
, numpy
.ndarray
,
:obj:`BasePoints`, optional):
points (torch.Tensor
| np
.ndarray
|
:obj:`BasePoints`, optional):
Points to rotate. Defaults to None.
Returns:
tuple or None: When ``points`` is None, the function returns
\
None, otherwise it returns the rotated points and the
\
tuple or None: When ``points`` is None, the function returns
None, otherwise it returns the rotated points and the
rotation matrix ``rot_mat_T``.
"""
if
not
isinstance
(
angle
,
torch
.
Tensor
):
angle
=
self
.
tensor
.
new_tensor
(
angle
)
assert
angle
.
shape
==
torch
.
Size
([
3
,
3
])
or
angle
.
numel
()
==
1
,
\
f
'invalid rotation angle shape
{
angle
.
shape
}
'
if
angle
.
numel
()
==
1
:
rot_sin
=
torch
.
sin
(
angle
)
rot_cos
=
torch
.
cos
(
angle
)
rot_mat_T
=
self
.
tensor
.
new_tensor
([[
rot_cos
,
0
,
-
rot_sin
]
,
[
0
,
1
,
0
]
,
[
rot_sin
,
0
,
rot_cos
]]
)
self
.
tensor
[:,
0
:
3
],
rot_mat_T
=
rotation_3d_in_axis
(
self
.
tensor
[:,
0
:
3
],
angle
,
axis
=
self
.
YAW_AXIS
,
return_mat
=
True
)
else
:
rot_mat_T
=
angle
rot_sin
=
rot_mat_T
[
2
,
0
]
rot_cos
=
rot_mat_T
[
0
,
0
]
angle
=
np
.
arctan2
(
rot_sin
,
rot_cos
)
self
.
tensor
[:,
0
:
3
]
=
self
.
tensor
[:,
0
:
3
]
@
rot_mat_T
self
.
tensor
[:,
:
3
]
=
self
.
tensor
[:,
:
3
]
@
rot_mat_T
self
.
tensor
[:,
6
]
+=
angle
if
points
is
not
None
:
if
isinstance
(
points
,
torch
.
Tensor
):
points
[:,
:
3
]
=
points
[:,
:
3
]
@
rot_mat_T
elif
isinstance
(
points
,
np
.
ndarray
):
rot_mat_T
=
rot_mat_T
.
numpy
()
rot_mat_T
=
rot_mat_T
.
cpu
().
numpy
()
points
[:,
:
3
]
=
np
.
dot
(
points
[:,
:
3
],
rot_mat_T
)
elif
isinstance
(
points
,
BasePoints
):
# clockwise
points
.
rotate
(
-
angle
)
points
.
rotate
(
rot_mat_T
)
else
:
raise
ValueError
return
points
,
rot_mat_T
...
...
@@ -224,7 +222,7 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
Args:
bev_direction (str): Flip direction (horizontal or vertical).
points (torch.Tensor
, numpy
.ndarray
,
:obj:`BasePoints`,
None
):
points (torch.Tensor
| np
.ndarray
|
:obj:`BasePoints`,
optional
):
Points to flip. Defaults to None.
Returns:
...
...
@@ -251,28 +249,6 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
points
.
flip
(
bev_direction
)
return
points
def
in_range_bev
(
self
,
box_range
):
"""Check whether the boxes are in the given range.
Args:
box_range (list | torch.Tensor): The range of box
(x_min, z_min, x_max, z_max).
Note:
The original implementation of SECOND checks whether boxes in
a range by checking whether the points are in a convex
polygon, we reduce the burden for simpler cases.
Returns:
torch.Tensor: Indicating whether each box is inside
\
the reference range.
"""
in_range_flags
=
((
self
.
tensor
[:,
0
]
>
box_range
[
0
])
&
(
self
.
tensor
[:,
2
]
>
box_range
[
1
])
&
(
self
.
tensor
[:,
0
]
<
box_range
[
2
])
&
(
self
.
tensor
[:,
2
]
<
box_range
[
3
]))
return
in_range_flags
@
classmethod
def
height_overlaps
(
cls
,
boxes1
,
boxes2
,
mode
=
'iou'
):
"""Calculate height overlaps of two boxes.
...
...
@@ -296,8 +272,8 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
boxes2_top_height
=
boxes2
.
top_height
.
view
(
1
,
-
1
)
boxes2_bottom_height
=
boxes2
.
bottom_height
.
view
(
1
,
-
1
)
#
In camera coordinate system
#
from up to down is the positive direction
#
positive direction of the gravity axis
#
in cam coord system points to the earth
heighest_of_bottom
=
torch
.
min
(
boxes1_bottom_height
,
boxes2_bottom_height
)
lowest_of_top
=
torch
.
max
(
boxes1_top_height
,
boxes2_top_height
)
...
...
@@ -309,16 +285,70 @@ class CameraInstance3DBoxes(BaseInstance3DBoxes):
Args:
dst (:obj:`Box3DMode`): The target Box mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from ``src`` coordinates to ``dst`` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
Returns:
:obj:`BaseInstance3DBoxes`:
\
:obj:`BaseInstance3DBoxes`:
The converted box of the same type in the ``dst`` mode.
"""
from
.box_3d_mode
import
Box3DMode
return
Box3DMode
.
convert
(
box
=
self
,
src
=
Box3DMode
.
CAM
,
dst
=
dst
,
rt_mat
=
rt_mat
)
def
points_in_boxes_part
(
self
,
points
,
boxes_override
=
None
):
"""Find the box in which each point is.
Args:
points (torch.Tensor): Points in shape (1, M, 3) or (M, 3),
3 dimensions are (x, y, z) in LiDAR or depth coordinate.
boxes_override (torch.Tensor, optional): Boxes to override
`self.tensor `. Defaults to None.
Returns:
torch.Tensor: The index of the box in which
each point is, in shape (M, ). Default value is -1
(if the point is not enclosed by any box).
"""
from
.coord_3d_mode
import
Coord3DMode
points_lidar
=
Coord3DMode
.
convert
(
points
,
Coord3DMode
.
CAM
,
Coord3DMode
.
LIDAR
)
if
boxes_override
is
not
None
:
boxes_lidar
=
boxes_override
else
:
boxes_lidar
=
Coord3DMode
.
convert
(
self
.
tensor
,
Coord3DMode
.
CAM
,
Coord3DMode
.
LIDAR
)
box_idx
=
super
().
points_in_boxes_part
(
points_lidar
,
boxes_lidar
)
return
box_idx
def
points_in_boxes_all
(
self
,
points
,
boxes_override
=
None
):
"""Find all boxes in which each point is.
Args:
points (torch.Tensor): Points in shape (1, M, 3) or (M, 3),
3 dimensions are (x, y, z) in LiDAR or depth coordinate.
boxes_override (torch.Tensor, optional): Boxes to override
`self.tensor `. Defaults to None.
Returns:
torch.Tensor: The index of all boxes in which each point is,
in shape (B, M, T).
"""
from
.coord_3d_mode
import
Coord3DMode
points_lidar
=
Coord3DMode
.
convert
(
points
,
Coord3DMode
.
CAM
,
Coord3DMode
.
LIDAR
)
if
boxes_override
is
not
None
:
boxes_lidar
=
boxes_override
else
:
boxes_lidar
=
Coord3DMode
.
convert
(
self
.
tensor
,
Coord3DMode
.
CAM
,
Coord3DMode
.
LIDAR
)
box_idx
=
super
().
points_in_boxes_all
(
points_lidar
,
boxes_lidar
)
return
box_idx
mmdet3d/core/bbox/structures/coord_3d_mode.py
View file @
32a4328b
# Copyright (c) OpenMMLab. All rights reserved.
from
enum
import
IntEnum
,
unique
import
numpy
as
np
import
torch
from
enum
import
IntEnum
,
unique
from
mmdet3d.core.points
import
(
BasePoints
,
CameraPoints
,
DepthPoints
,
LiDARPoints
)
from
...points
import
BasePoints
,
CameraPoints
,
DepthPoints
,
LiDARPoints
from
.base_box3d
import
BaseInstance3DBoxes
from
.cam_box3d
import
CameraInstance3DBoxes
from
.depth_box3d
import
DepthInstance3DBoxes
from
.lidar_box3d
import
LiDARInstance3DBoxes
from
.box_3d_mode
import
Box3DMode
@
unique
...
...
@@ -64,119 +62,75 @@ class Coord3DMode(IntEnum):
DEPTH
=
2
@
staticmethod
def
convert
(
input
,
src
,
dst
,
rt_mat
=
None
):
"""Convert boxes or points from `src` mode to `dst` mode."""
def
convert
(
input
,
src
,
dst
,
rt_mat
=
None
,
with_yaw
=
True
,
is_point
=
True
):
"""Convert boxes or points from `src` mode to `dst` mode.
Args:
input (tuple | list | np.ndarray | torch.Tensor |
:obj:`BaseInstance3DBoxes` | :obj:`BasePoints`):
Can be a k-tuple, k-list or an Nxk array/tensor, where k = 7.
src (:obj:`Box3DMode` | :obj:`Coord3DMode`): The source mode.
dst (:obj:`Box3DMode` | :obj:`Coord3DMode`): The target mode.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from `src` coordinates to `dst` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
with_yaw (bool): If `box` is an instance of
:obj:`BaseInstance3DBoxes`, whether or not it has a yaw angle.
Defaults to True.
is_point (bool): If `input` is neither an instance of
:obj:`BaseInstance3DBoxes` nor an instance of
:obj:`BasePoints`, whether or not it is point data.
Defaults to True.
Returns:
(tuple | list | np.ndarray | torch.Tensor |
:obj:`BaseInstance3DBoxes` | :obj:`BasePoints`):
The converted box of the same type.
"""
if
isinstance
(
input
,
BaseInstance3DBoxes
):
return
Coord3DMode
.
convert_box
(
input
,
src
,
dst
,
rt_mat
=
rt_mat
)
return
Coord3DMode
.
convert_box
(
input
,
src
,
dst
,
rt_mat
=
rt_mat
,
with_yaw
=
with_yaw
)
elif
isinstance
(
input
,
BasePoints
):
return
Coord3DMode
.
convert_point
(
input
,
src
,
dst
,
rt_mat
=
rt_mat
)
elif
isinstance
(
input
,
(
tuple
,
list
,
np
.
ndarray
,
torch
.
Tensor
)):
if
is_point
:
return
Coord3DMode
.
convert_point
(
input
,
src
,
dst
,
rt_mat
=
rt_mat
)
else
:
return
Coord3DMode
.
convert_box
(
input
,
src
,
dst
,
rt_mat
=
rt_mat
,
with_yaw
=
with_yaw
)
else
:
raise
NotImplementedError
@
staticmethod
def
convert_box
(
box
,
src
,
dst
,
rt_mat
=
None
):
def
convert_box
(
box
,
src
,
dst
,
rt_mat
=
None
,
with_yaw
=
True
):
"""Convert boxes from `src` mode to `dst` mode.
Args:
box (tuple | list | np.ndarray |
torch.Tensor | BaseInstance3DBoxes):
torch.Tensor |
:obj:`
BaseInstance3DBoxes
`
):
Can be a k-tuple, k-list or an Nxk array/tensor, where k = 7.
src (:obj:`CoordMode`): The src Box mode.
dst (:obj:`CoordMode`): The target Box mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
src (:obj:`Box3DMode`): The src Box mode.
dst (:obj:`Box3DMode`): The target Box mode.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from `src` coordinates to `dst` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
with_yaw (bool): If `box` is an instance of
:obj:`BaseInstance3DBoxes`, whether or not it has a yaw angle.
Defaults to True.
Returns:
(tuple | list | np.ndarray | torch.Tensor | BaseInstance3DBoxes):
\
(tuple | list | np.ndarray | torch.Tensor |
:obj:`BaseInstance3DBoxes`):
The converted box of the same type.
"""
if
src
==
dst
:
return
box
is_numpy
=
isinstance
(
box
,
np
.
ndarray
)
is_Instance3DBoxes
=
isinstance
(
box
,
BaseInstance3DBoxes
)
single_box
=
isinstance
(
box
,
(
list
,
tuple
))
if
single_box
:
assert
len
(
box
)
>=
7
,
(
'CoordMode.convert takes either a k-tuple/list or '
'an Nxk array/tensor, where k >= 7'
)
arr
=
torch
.
tensor
(
box
)[
None
,
:]
else
:
# avoid modifying the input box
if
is_numpy
:
arr
=
torch
.
from_numpy
(
np
.
asarray
(
box
)).
clone
()
elif
is_Instance3DBoxes
:
arr
=
box
.
tensor
.
clone
()
else
:
arr
=
box
.
clone
()
# convert box from `src` mode to `dst` mode.
x_size
,
y_size
,
z_size
=
arr
[...,
3
:
4
],
arr
[...,
4
:
5
],
arr
[...,
5
:
6
]
if
src
==
Coord3DMode
.
LIDAR
and
dst
==
Coord3DMode
.
CAM
:
if
rt_mat
is
None
:
rt_mat
=
arr
.
new_tensor
([[
0
,
-
1
,
0
],
[
0
,
0
,
-
1
],
[
1
,
0
,
0
]])
xyz_size
=
torch
.
cat
([
y_size
,
z_size
,
x_size
],
dim
=-
1
)
elif
src
==
Coord3DMode
.
CAM
and
dst
==
Coord3DMode
.
LIDAR
:
if
rt_mat
is
None
:
rt_mat
=
arr
.
new_tensor
([[
0
,
0
,
1
],
[
-
1
,
0
,
0
],
[
0
,
-
1
,
0
]])
xyz_size
=
torch
.
cat
([
z_size
,
x_size
,
y_size
],
dim
=-
1
)
elif
src
==
Coord3DMode
.
DEPTH
and
dst
==
Coord3DMode
.
CAM
:
if
rt_mat
is
None
:
rt_mat
=
arr
.
new_tensor
([[
1
,
0
,
0
],
[
0
,
0
,
1
],
[
0
,
-
1
,
0
]])
xyz_size
=
torch
.
cat
([
x_size
,
z_size
,
y_size
],
dim
=-
1
)
elif
src
==
Coord3DMode
.
CAM
and
dst
==
Coord3DMode
.
DEPTH
:
if
rt_mat
is
None
:
rt_mat
=
arr
.
new_tensor
([[
1
,
0
,
0
],
[
0
,
0
,
-
1
],
[
0
,
1
,
0
]])
xyz_size
=
torch
.
cat
([
x_size
,
z_size
,
y_size
],
dim
=-
1
)
elif
src
==
Coord3DMode
.
LIDAR
and
dst
==
Coord3DMode
.
DEPTH
:
if
rt_mat
is
None
:
rt_mat
=
arr
.
new_tensor
([[
0
,
-
1
,
0
],
[
1
,
0
,
0
],
[
0
,
0
,
1
]])
xyz_size
=
torch
.
cat
([
y_size
,
x_size
,
z_size
],
dim
=-
1
)
elif
src
==
Coord3DMode
.
DEPTH
and
dst
==
Coord3DMode
.
LIDAR
:
if
rt_mat
is
None
:
rt_mat
=
arr
.
new_tensor
([[
0
,
1
,
0
],
[
-
1
,
0
,
0
],
[
0
,
0
,
1
]])
xyz_size
=
torch
.
cat
([
y_size
,
x_size
,
z_size
],
dim
=-
1
)
else
:
raise
NotImplementedError
(
f
'Conversion from Coord3DMode
{
src
}
to
{
dst
}
'
'is not supported yet'
)
if
not
isinstance
(
rt_mat
,
torch
.
Tensor
):
rt_mat
=
arr
.
new_tensor
(
rt_mat
)
if
rt_mat
.
size
(
1
)
==
4
:
extended_xyz
=
torch
.
cat
(
[
arr
[:,
:
3
],
arr
.
new_ones
(
arr
.
size
(
0
),
1
)],
dim
=-
1
)
xyz
=
extended_xyz
@
rt_mat
.
t
()
else
:
xyz
=
arr
[:,
:
3
]
@
rt_mat
.
t
()
remains
=
arr
[...,
6
:]
arr
=
torch
.
cat
([
xyz
[:,
:
3
],
xyz_size
,
remains
],
dim
=-
1
)
# convert arr to the original type
original_type
=
type
(
box
)
if
single_box
:
return
original_type
(
arr
.
flatten
().
tolist
())
if
is_numpy
:
return
arr
.
numpy
()
elif
is_Instance3DBoxes
:
if
dst
==
Coord3DMode
.
CAM
:
target_type
=
CameraInstance3DBoxes
elif
dst
==
Coord3DMode
.
LIDAR
:
target_type
=
LiDARInstance3DBoxes
elif
dst
==
Coord3DMode
.
DEPTH
:
target_type
=
DepthInstance3DBoxes
else
:
raise
NotImplementedError
(
f
'Conversion to
{
dst
}
through
{
original_type
}
'
' is not supported yet'
)
return
target_type
(
arr
,
box_dim
=
arr
.
size
(
-
1
),
with_yaw
=
box
.
with_yaw
)
else
:
return
arr
return
Box3DMode
.
convert
(
box
,
src
,
dst
,
rt_mat
=
rt_mat
)
@
staticmethod
def
convert_point
(
point
,
src
,
dst
,
rt_mat
=
None
):
...
...
@@ -184,18 +138,19 @@ class Coord3DMode(IntEnum):
Args:
point (tuple | list | np.ndarray |
torch.Tensor | BasePoints):
torch.Tensor |
:obj:`
BasePoints
`
):
Can be a k-tuple, k-list or an Nxk array/tensor.
src (:obj:`CoordMode`): The src Point mode.
dst (:obj:`CoordMode`): The target Point mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from `src` coordinates to `dst` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
Returns:
(tuple | list | np.ndarray | torch.Tensor | BasePoints):
\
(tuple | list | np.ndarray | torch.Tensor |
:obj:`
BasePoints
`
):
The converted point of the same type.
"""
if
src
==
dst
:
...
...
@@ -219,8 +174,6 @@ class Coord3DMode(IntEnum):
arr
=
point
.
clone
()
# convert point from `src` mode to `dst` mode.
# TODO: LIDAR
# only implemented provided Rt matrix in cam-depth conversion
if
src
==
Coord3DMode
.
LIDAR
and
dst
==
Coord3DMode
.
CAM
:
if
rt_mat
is
None
:
rt_mat
=
arr
.
new_tensor
([[
0
,
-
1
,
0
],
[
0
,
0
,
-
1
],
[
1
,
0
,
0
]])
...
...
@@ -248,13 +201,13 @@ class Coord3DMode(IntEnum):
rt_mat
=
arr
.
new_tensor
(
rt_mat
)
if
rt_mat
.
size
(
1
)
==
4
:
extended_xyz
=
torch
.
cat
(
[
arr
[
:
,
:
3
],
arr
.
new_ones
(
arr
.
size
(
0
),
1
)],
dim
=-
1
)
[
arr
[
...
,
:
3
],
arr
.
new_ones
(
arr
.
size
(
0
),
1
)],
dim
=-
1
)
xyz
=
extended_xyz
@
rt_mat
.
t
()
else
:
xyz
=
arr
[
:
,
:
3
]
@
rt_mat
.
t
()
xyz
=
arr
[
...
,
:
3
]
@
rt_mat
.
t
()
remains
=
arr
[
:
,
3
:]
arr
=
torch
.
cat
([
xyz
[
:
,
:
3
],
remains
],
dim
=-
1
)
remains
=
arr
[
...
,
3
:]
arr
=
torch
.
cat
([
xyz
[
...
,
:
3
],
remains
],
dim
=-
1
)
# convert arr to the original type
original_type
=
type
(
point
)
...
...
mmdet3d/core/bbox/structures/depth_box3d.py
View file @
32a4328b
...
...
@@ -3,9 +3,8 @@ import numpy as np
import
torch
from
mmdet3d.core.points
import
BasePoints
from
mmdet3d.ops
import
points_in_boxes_batch
from
.base_box3d
import
BaseInstance3DBoxes
from
.utils
import
limit_period
,
rotation_3d_in_axis
from
.utils
import
rotation_3d_in_axis
class
DepthInstance3DBoxes
(
BaseInstance3DBoxes
):
...
...
@@ -38,10 +37,11 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
with_yaw (bool): If True, the value of yaw will be set to 0 as minmax
boxes.
"""
YAW_AXIS
=
2
@
property
def
gravity_center
(
self
):
"""torch.Tensor: A tensor with center of each box."""
"""torch.Tensor: A tensor with center of each box
in shape (N, 3)
."""
bottom_center
=
self
.
bottom_center
gravity_center
=
torch
.
zeros_like
(
bottom_center
)
gravity_center
[:,
:
2
]
=
bottom_center
[:,
:
2
]
...
...
@@ -85,73 +85,50 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
corners
=
dims
.
view
([
-
1
,
1
,
3
])
*
corners_norm
.
reshape
([
1
,
8
,
3
])
# rotate around z axis
corners
=
rotation_3d_in_axis
(
corners
,
self
.
tensor
[:,
6
],
axis
=
2
)
corners
=
rotation_3d_in_axis
(
corners
,
self
.
tensor
[:,
6
],
axis
=
self
.
YAW_AXIS
)
corners
+=
self
.
tensor
[:,
:
3
].
view
(
-
1
,
1
,
3
)
return
corners
@
property
def
bev
(
self
):
"""torch.Tensor: A n x 5 tensor of 2D BEV box of each box
in XYWHR format."""
return
self
.
tensor
[:,
[
0
,
1
,
3
,
4
,
6
]]
@
property
def
nearest_bev
(
self
):
"""torch.Tensor: A tensor of 2D BEV box of each box
without rotation."""
# Obtain BEV boxes with rotation in XYWHR format
bev_rotated_boxes
=
self
.
bev
# convert the rotation to a valid range
rotations
=
bev_rotated_boxes
[:,
-
1
]
normed_rotations
=
torch
.
abs
(
limit_period
(
rotations
,
0.5
,
np
.
pi
))
# find the center of boxes
conditions
=
(
normed_rotations
>
np
.
pi
/
4
)[...,
None
]
bboxes_xywh
=
torch
.
where
(
conditions
,
bev_rotated_boxes
[:,
[
0
,
1
,
3
,
2
]],
bev_rotated_boxes
[:,
:
4
])
centers
=
bboxes_xywh
[:,
:
2
]
dims
=
bboxes_xywh
[:,
2
:]
bev_boxes
=
torch
.
cat
([
centers
-
dims
/
2
,
centers
+
dims
/
2
],
dim
=-
1
)
return
bev_boxes
def
rotate
(
self
,
angle
,
points
=
None
):
"""Rotate boxes with points (optional) with the given angle or
\
rotation
matrix.
"""Rotate boxes with points (optional) with the given angle or
rotation
matrix.
Args:
angle (float | torch.Tensor | np.ndarray):
Rotation angle or rotation matrix.
points (torch.Tensor
, numpy
.ndarray
,
:obj:`BasePoints`, optional):
points (torch.Tensor
| np
.ndarray
|
:obj:`BasePoints`, optional):
Points to rotate. Defaults to None.
Returns:
tuple or None: When ``points`` is None, the function returns
\
None, otherwise it returns the rotated points and the
\
tuple or None: When ``points`` is None, the function returns
None, otherwise it returns the rotated points and the
rotation matrix ``rot_mat_T``.
"""
if
not
isinstance
(
angle
,
torch
.
Tensor
):
angle
=
self
.
tensor
.
new_tensor
(
angle
)
assert
angle
.
shape
==
torch
.
Size
([
3
,
3
])
or
angle
.
numel
()
==
1
,
\
f
'invalid rotation angle shape
{
angle
.
shape
}
'
if
angle
.
numel
()
==
1
:
rot_sin
=
torch
.
sin
(
angle
)
rot_cos
=
torch
.
cos
(
angle
)
rot_mat_T
=
self
.
tensor
.
new_tensor
([[
rot_cos
,
-
rot_sin
,
0
]
,
[
rot_sin
,
rot_cos
,
0
]
,
[
0
,
0
,
1
]]).
T
self
.
tensor
[:,
0
:
3
],
rot_mat_T
=
rotation_3d_in_axis
(
self
.
tensor
[:,
0
:
3
],
angle
,
axis
=
self
.
YAW_AXIS
,
return_mat
=
True
)
else
:
rot_mat_T
=
angle
.
T
rot_mat_T
=
angle
rot_sin
=
rot_mat_T
[
0
,
1
]
rot_cos
=
rot_mat_T
[
0
,
0
]
angle
=
np
.
arctan2
(
rot_sin
,
rot_cos
)
self
.
tensor
[:,
0
:
3
]
=
self
.
tensor
[:,
0
:
3
]
@
rot_mat_T
self
.
tensor
[:,
0
:
3
]
=
self
.
tensor
[:,
0
:
3
]
@
rot_mat_T
if
self
.
with_yaw
:
self
.
tensor
[:,
6
]
-
=
angle
self
.
tensor
[:,
6
]
+
=
angle
else
:
# for axis-aligned boxes, we take the new
# enclosing axis-aligned boxes after rotation
corners_rot
=
self
.
corners
@
rot_mat_T
new_x_size
=
corners_rot
[...,
0
].
max
(
dim
=
1
,
keepdim
=
True
)[
0
]
-
corners_rot
[...,
0
].
min
(
...
...
@@ -165,11 +142,10 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
if
isinstance
(
points
,
torch
.
Tensor
):
points
[:,
:
3
]
=
points
[:,
:
3
]
@
rot_mat_T
elif
isinstance
(
points
,
np
.
ndarray
):
rot_mat_T
=
rot_mat_T
.
numpy
()
rot_mat_T
=
rot_mat_T
.
cpu
().
numpy
()
points
[:,
:
3
]
=
np
.
dot
(
points
[:,
:
3
],
rot_mat_T
)
elif
isinstance
(
points
,
BasePoints
):
# anti-clockwise
points
.
rotate
(
angle
)
points
.
rotate
(
rot_mat_T
)
else
:
raise
ValueError
return
points
,
rot_mat_T
...
...
@@ -180,8 +156,9 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
In Depth coordinates, it flips x (horizontal) or y (vertical) axis.
Args:
bev_direction (str): Flip direction (horizontal or vertical).
points (torch.Tensor, numpy.ndarray, :obj:`BasePoints`, None):
bev_direction (str, optional): Flip direction
(horizontal or vertical). Defaults to 'horizontal'.
points (torch.Tensor | np.ndarray | :obj:`BasePoints`, optional):
Points to flip. Defaults to None.
Returns:
...
...
@@ -208,75 +185,26 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
points
.
flip
(
bev_direction
)
return
points
def
in_range_bev
(
self
,
box_range
):
"""Check whether the boxes are in the given range.
Args:
box_range (list | torch.Tensor): The range of box
(x_min, y_min, x_max, y_max).
Note:
In the original implementation of SECOND, checking whether
a box in the range checks whether the points are in a convex
polygon, we try to reduce the burdun for simpler cases.
Returns:
torch.Tensor: Indicating whether each box is inside
\
the reference range.
"""
in_range_flags
=
((
self
.
tensor
[:,
0
]
>
box_range
[
0
])
&
(
self
.
tensor
[:,
1
]
>
box_range
[
1
])
&
(
self
.
tensor
[:,
0
]
<
box_range
[
2
])
&
(
self
.
tensor
[:,
1
]
<
box_range
[
3
]))
return
in_range_flags
def
convert_to
(
self
,
dst
,
rt_mat
=
None
):
"""Convert self to ``dst`` mode.
Args:
dst (:obj:`Box3DMode`): The target Box mode.
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from ``src`` coordinates to ``dst`` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
Returns:
:obj:`DepthInstance3DBoxes`:
\
:obj:`DepthInstance3DBoxes`:
The converted box of the same type in the ``dst`` mode.
"""
from
.box_3d_mode
import
Box3DMode
return
Box3DMode
.
convert
(
box
=
self
,
src
=
Box3DMode
.
DEPTH
,
dst
=
dst
,
rt_mat
=
rt_mat
)
def
points_in_boxes
(
self
,
points
):
"""Find points that are in boxes (CUDA).
Args:
points (torch.Tensor): Points in shape [1, M, 3] or [M, 3],
\
3 dimensions are [x, y, z] in LiDAR coordinate.
Returns:
torch.Tensor: The index of boxes each point lies in with shape
\
of (B, M, T).
"""
from
.box_3d_mode
import
Box3DMode
# to lidar
points_lidar
=
points
.
clone
()
points_lidar
=
points_lidar
[...,
[
1
,
0
,
2
]]
points_lidar
[...,
1
]
*=
-
1
if
points
.
dim
()
==
2
:
points_lidar
=
points_lidar
.
unsqueeze
(
0
)
else
:
assert
points
.
dim
()
==
3
and
points_lidar
.
shape
[
0
]
==
1
boxes_lidar
=
self
.
convert_to
(
Box3DMode
.
LIDAR
).
tensor
boxes_lidar
=
boxes_lidar
.
to
(
points
.
device
).
unsqueeze
(
0
)
box_idxs_of_pts
=
points_in_boxes_batch
(
points_lidar
,
boxes_lidar
)
return
box_idxs_of_pts
.
squeeze
(
0
)
def
enlarged_box
(
self
,
extra_width
):
"""Enlarge the length, width and height boxes.
...
...
@@ -284,7 +212,7 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
extra_width (float | torch.Tensor): Extra width to enlarge the box.
Returns:
:obj:`
LiDAR
Instance3DBoxes`: Enlarged boxes.
:obj:`
Depth
Instance3DBoxes`: Enlarged boxes.
"""
enlarged_boxes
=
self
.
tensor
.
clone
()
enlarged_boxes
[:,
3
:
6
]
+=
extra_width
*
2
...
...
@@ -331,13 +259,12 @@ class DepthInstance3DBoxes(BaseInstance3DBoxes):
-
1
,
3
)
surface_rot
=
rot_mat_T
.
repeat
(
6
,
1
,
1
)
surface_3d
=
torch
.
matmul
(
surface_3d
.
unsqueeze
(
-
2
),
surface_rot
.
transpose
(
2
,
1
)
).
squeeze
(
-
2
)
surface_3d
=
torch
.
matmul
(
surface_3d
.
unsqueeze
(
-
2
),
surface_rot
).
squeeze
(
-
2
)
surface_center
=
center
.
repeat
(
1
,
6
,
1
).
reshape
(
-
1
,
3
)
+
surface_3d
line_rot
=
rot_mat_T
.
repeat
(
12
,
1
,
1
)
line_3d
=
torch
.
matmul
(
line_3d
.
unsqueeze
(
-
2
),
line_rot
.
transpose
(
2
,
1
)).
squeeze
(
-
2
)
line_3d
=
torch
.
matmul
(
line_3d
.
unsqueeze
(
-
2
),
line_rot
).
squeeze
(
-
2
)
line_center
=
center
.
repeat
(
1
,
12
,
1
).
reshape
(
-
1
,
3
)
+
line_3d
return
surface_center
,
line_center
mmdet3d/core/bbox/structures/lidar_box3d.py
View file @
32a4328b
...
...
@@ -3,9 +3,8 @@ import numpy as np
import
torch
from
mmdet3d.core.points
import
BasePoints
from
mmdet3d.ops.roiaware_pool3d
import
points_in_boxes_gpu
from
.base_box3d
import
BaseInstance3DBoxes
from
.utils
import
limit_period
,
rotation_3d_in_axis
from
.utils
import
rotation_3d_in_axis
class
LiDARInstance3DBoxes
(
BaseInstance3DBoxes
):
...
...
@@ -15,16 +14,16 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
.. code-block:: none
up z x front (yaw=
-0.5*pi
)
^ ^
| /
| /
(yaw=
-
pi) left y <------ 0
-------- (yaw=0)
up z x front (yaw=
0
)
^ ^
| /
| /
(yaw=
0.5*
pi) left y <------ 0
The relative coordinate of bottom center in a LiDAR box is (0.5, 0.5, 0),
and the yaw is around the z axis, thus the rotation axis=2.
The yaw is 0 at the
nega
tive direction of
y
axis, and
de
creases from
the
nega
tive direction of
y
to the positive direction of
x
.
The yaw is 0 at the
posi
tive direction of
x
axis, and
in
creases from
the
posi
tive direction of
x
to the positive direction of
y
.
A refactor is ongoing to make the three coordinate systems
easier to understand and convert between each other.
...
...
@@ -36,10 +35,11 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
with_yaw (bool): If True, the value of yaw will be set to 0 as minmax
boxes.
"""
YAW_AXIS
=
2
@
property
def
gravity_center
(
self
):
"""torch.Tensor: A tensor with center of each box."""
"""torch.Tensor: A tensor with center of each box
in shape (N, 3)
."""
bottom_center
=
self
.
bottom_center
gravity_center
=
torch
.
zeros_like
(
bottom_center
)
gravity_center
[:,
:
2
]
=
bottom_center
[:,
:
2
]
...
...
@@ -83,70 +83,45 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
corners
=
dims
.
view
([
-
1
,
1
,
3
])
*
corners_norm
.
reshape
([
1
,
8
,
3
])
# rotate around z axis
corners
=
rotation_3d_in_axis
(
corners
,
self
.
tensor
[:,
6
],
axis
=
2
)
corners
=
rotation_3d_in_axis
(
corners
,
self
.
tensor
[:,
6
],
axis
=
self
.
YAW_AXIS
)
corners
+=
self
.
tensor
[:,
:
3
].
view
(
-
1
,
1
,
3
)
return
corners
@
property
def
bev
(
self
):
"""torch.Tensor: 2D BEV box of each box with rotation
in XYWHR format."""
return
self
.
tensor
[:,
[
0
,
1
,
3
,
4
,
6
]]
@
property
def
nearest_bev
(
self
):
"""torch.Tensor: A tensor of 2D BEV box of each box
without rotation."""
# Obtain BEV boxes with rotation in XYWHR format
bev_rotated_boxes
=
self
.
bev
# convert the rotation to a valid range
rotations
=
bev_rotated_boxes
[:,
-
1
]
normed_rotations
=
torch
.
abs
(
limit_period
(
rotations
,
0.5
,
np
.
pi
))
# find the center of boxes
conditions
=
(
normed_rotations
>
np
.
pi
/
4
)[...,
None
]
bboxes_xywh
=
torch
.
where
(
conditions
,
bev_rotated_boxes
[:,
[
0
,
1
,
3
,
2
]],
bev_rotated_boxes
[:,
:
4
])
centers
=
bboxes_xywh
[:,
:
2
]
dims
=
bboxes_xywh
[:,
2
:]
bev_boxes
=
torch
.
cat
([
centers
-
dims
/
2
,
centers
+
dims
/
2
],
dim
=-
1
)
return
bev_boxes
def
rotate
(
self
,
angle
,
points
=
None
):
"""Rotate boxes with points (optional) with the given angle or
\
rotation
matrix.
"""Rotate boxes with points (optional) with the given angle or
rotation
matrix.
Args:
angles (float | torch.Tensor | np.ndarray):
Rotation angle or rotation matrix.
points (torch.Tensor
, numpy
.ndarray
,
:obj:`BasePoints`, optional):
points (torch.Tensor
| np
.ndarray
|
:obj:`BasePoints`, optional):
Points to rotate. Defaults to None.
Returns:
tuple or None: When ``points`` is None, the function returns
\
None, otherwise it returns the rotated points and the
\
tuple or None: When ``points`` is None, the function returns
None, otherwise it returns the rotated points and the
rotation matrix ``rot_mat_T``.
"""
if
not
isinstance
(
angle
,
torch
.
Tensor
):
angle
=
self
.
tensor
.
new_tensor
(
angle
)
assert
angle
.
shape
==
torch
.
Size
([
3
,
3
])
or
angle
.
numel
()
==
1
,
\
f
'invalid rotation angle shape
{
angle
.
shape
}
'
if
angle
.
numel
()
==
1
:
rot_sin
=
torch
.
sin
(
angle
)
rot_cos
=
torch
.
cos
(
angle
)
rot_mat_T
=
self
.
tensor
.
new_tensor
([[
rot_cos
,
-
rot_sin
,
0
]
,
[
rot_sin
,
rot_cos
,
0
]
,
[
0
,
0
,
1
]]
)
self
.
tensor
[:,
0
:
3
],
rot_mat_T
=
rotation_3d_in_axis
(
self
.
tensor
[:,
0
:
3
],
angle
,
axis
=
self
.
YAW_AXIS
,
return_mat
=
True
)
else
:
rot_mat_T
=
angle
rot_sin
=
rot_mat_T
[
1
,
0
]
rot_sin
=
rot_mat_T
[
0
,
1
]
rot_cos
=
rot_mat_T
[
0
,
0
]
angle
=
np
.
arctan2
(
rot_sin
,
rot_cos
)
self
.
tensor
[:,
0
:
3
]
=
self
.
tensor
[:,
0
:
3
]
@
rot_mat_T
self
.
tensor
[:,
:
3
]
=
self
.
tensor
[:,
:
3
]
@
rot_mat_T
self
.
tensor
[:,
6
]
+=
angle
if
self
.
tensor
.
shape
[
1
]
==
9
:
...
...
@@ -157,11 +132,10 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
if
isinstance
(
points
,
torch
.
Tensor
):
points
[:,
:
3
]
=
points
[:,
:
3
]
@
rot_mat_T
elif
isinstance
(
points
,
np
.
ndarray
):
rot_mat_T
=
rot_mat_T
.
numpy
()
rot_mat_T
=
rot_mat_T
.
cpu
().
numpy
()
points
[:,
:
3
]
=
np
.
dot
(
points
[:,
:
3
],
rot_mat_T
)
elif
isinstance
(
points
,
BasePoints
):
# clockwise
points
.
rotate
(
-
angle
)
points
.
rotate
(
rot_mat_T
)
else
:
raise
ValueError
return
points
,
rot_mat_T
...
...
@@ -173,7 +147,7 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
Args:
bev_direction (str): Flip direction (horizontal or vertical).
points (torch.Tensor
, numpy
.ndarray
,
:obj:`BasePoints`,
None
):
points (torch.Tensor
| np
.ndarray
|
:obj:`BasePoints`,
optional
):
Points to flip. Defaults to None.
Returns:
...
...
@@ -183,11 +157,11 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
if
bev_direction
==
'horizontal'
:
self
.
tensor
[:,
1
::
7
]
=
-
self
.
tensor
[:,
1
::
7
]
if
self
.
with_yaw
:
self
.
tensor
[:,
6
]
=
-
self
.
tensor
[:,
6
]
+
np
.
pi
self
.
tensor
[:,
6
]
=
-
self
.
tensor
[:,
6
]
elif
bev_direction
==
'vertical'
:
self
.
tensor
[:,
0
::
7
]
=
-
self
.
tensor
[:,
0
::
7
]
if
self
.
with_yaw
:
self
.
tensor
[:,
6
]
=
-
self
.
tensor
[:,
6
]
self
.
tensor
[:,
6
]
=
-
self
.
tensor
[:,
6
]
+
np
.
pi
if
points
is
not
None
:
assert
isinstance
(
points
,
(
torch
.
Tensor
,
np
.
ndarray
,
BasePoints
))
...
...
@@ -200,40 +174,20 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
points
.
flip
(
bev_direction
)
return
points
def
in_range_bev
(
self
,
box_range
):
"""Check whether the boxes are in the given range.
Args:
box_range (list | torch.Tensor): the range of box
(x_min, y_min, x_max, y_max)
Note:
The original implementation of SECOND checks whether boxes in
a range by checking whether the points are in a convex
polygon, we reduce the burden for simpler cases.
Returns:
torch.Tensor: Whether each box is inside the reference range.
"""
in_range_flags
=
((
self
.
tensor
[:,
0
]
>
box_range
[
0
])
&
(
self
.
tensor
[:,
1
]
>
box_range
[
1
])
&
(
self
.
tensor
[:,
0
]
<
box_range
[
2
])
&
(
self
.
tensor
[:,
1
]
<
box_range
[
3
]))
return
in_range_flags
def
convert_to
(
self
,
dst
,
rt_mat
=
None
):
"""Convert self to ``dst`` mode.
Args:
dst (:obj:`Box3DMode`): the target Box mode
rt_mat (np.ndarray | torch.Tensor): The rotation and translation
matrix between different coordinates. Defaults to None.
rt_mat (np.ndarray | torch.Tensor, optional): The rotation and
translation matrix between different coordinates.
Defaults to None.
The conversion from ``src`` coordinates to ``dst`` coordinates
usually comes along the change of sensors, e.g., from camera
to LiDAR. This requires a transformation matrix.
Returns:
:obj:`BaseInstance3DBoxes`:
\
:obj:`BaseInstance3DBoxes`:
The converted box of the same type in the ``dst`` mode.
"""
from
.box_3d_mode
import
Box3DMode
...
...
@@ -254,17 +208,3 @@ class LiDARInstance3DBoxes(BaseInstance3DBoxes):
# bottom center z minus extra_width
enlarged_boxes
[:,
2
]
-=
extra_width
return
self
.
new_box
(
enlarged_boxes
)
def
points_in_boxes
(
self
,
points
):
"""Find the box which the points are in.
Args:
points (torch.Tensor): Points in shape (N, 3).
Returns:
torch.Tensor: The index of box where each point are in.
"""
box_idx
=
points_in_boxes_gpu
(
points
.
unsqueeze
(
0
),
self
.
tensor
.
unsqueeze
(
0
).
to
(
points
.
device
)).
squeeze
(
0
)
return
box_idx
mmdet3d/core/bbox/structures/utils.py
View file @
32a4328b
# Copyright (c) OpenMMLab. All rights reserved.
from
logging
import
warning
import
numpy
as
np
import
torch
from
logging
import
warning
from
mmdet3d.core.utils
import
array_converter
@
array_converter
(
apply_to
=
(
'val'
,
))
def
limit_period
(
val
,
offset
=
0.5
,
period
=
np
.
pi
):
"""Limit the value into a period for periodic function.
Args:
val (torch.Tensor): The value to be converted.
offset (float, optional): Offset to set the value range.
\
val (torch.Tensor
| np.ndarray
): The value to be converted.
offset (float, optional): Offset to set the value range.
Defaults to 0.5.
period ([type], optional): Period of the value. Defaults to np.pi.
Returns:
torch.Tensor: Value in the range of
\
(
torch.Tensor
| np.ndarray)
: Value in the range of
[-offset * period, (1-offset) * period]
"""
return
val
-
torch
.
floor
(
val
/
period
+
offset
)
*
period
limited_val
=
val
-
torch
.
floor
(
val
/
period
+
offset
)
*
period
return
limited_val
def
rotation_3d_in_axis
(
points
,
angles
,
axis
=
0
):
@
array_converter
(
apply_to
=
(
'points'
,
'angles'
))
def
rotation_3d_in_axis
(
points
,
angles
,
axis
=
0
,
return_mat
=
False
,
clockwise
=
False
):
"""Rotate points by angles according to axis.
Args:
points (torch.Tensor): Points of shape (N, M, 3).
angles (torch.Tensor): Vector of angles in shape (N,)
points (np.ndarray | torch.Tensor | list | tuple ):
Points of shape (N, M, 3).
angles (np.ndarray | torch.Tensor | list | tuple | float):
Vector of angles in shape (N,)
axis (int, optional): The axis to be rotated. Defaults to 0.
return_mat: Whether or not return the rotation matrix (transposed).
Defaults to False.
clockwise: Whether the rotation is clockwise. Defaults to False.
Raises:
ValueError: when the axis is not in range [0, 1, 2], it will
\
ValueError: when the axis is not in range [0, 1, 2], it will
raise value error.
Returns:
torch.Tensor: Rotated points in shape (N, M, 3)
(
torch.Tensor
| np.ndarray)
: Rotated points in shape (N, M, 3)
.
"""
batch_free
=
len
(
points
.
shape
)
==
2
if
batch_free
:
points
=
points
[
None
]
if
isinstance
(
angles
,
float
)
or
len
(
angles
.
shape
)
==
0
:
angles
=
torch
.
full
(
points
.
shape
[:
1
],
angles
)
assert
len
(
points
.
shape
)
==
3
and
len
(
angles
.
shape
)
==
1
\
and
points
.
shape
[
0
]
==
angles
.
shape
[
0
],
f
'Incorrect shape of points '
\
f
'angles:
{
points
.
shape
}
,
{
angles
.
shape
}
'
assert
points
.
shape
[
-
1
]
in
[
2
,
3
],
\
f
'Points size should be 2 or 3 instead of
{
points
.
shape
[
-
1
]
}
'
rot_sin
=
torch
.
sin
(
angles
)
rot_cos
=
torch
.
cos
(
angles
)
ones
=
torch
.
ones_like
(
rot_cos
)
zeros
=
torch
.
zeros_like
(
rot_cos
)
if
axis
==
1
:
rot_mat_T
=
torch
.
stack
([
torch
.
stack
([
rot_cos
,
zeros
,
-
rot_sin
]),
torch
.
stack
([
zeros
,
ones
,
zeros
]),
torch
.
stack
([
rot_sin
,
zeros
,
rot_cos
])
])
elif
axis
==
2
or
axis
==
-
1
:
rot_mat_T
=
torch
.
stack
([
torch
.
stack
([
rot_cos
,
-
rot_sin
,
zeros
]),
torch
.
stack
([
rot_sin
,
rot_cos
,
zeros
]),
torch
.
stack
([
zeros
,
zeros
,
ones
])
])
elif
axis
==
0
:
if
points
.
shape
[
-
1
]
==
3
:
if
axis
==
1
or
axis
==
-
2
:
rot_mat_T
=
torch
.
stack
([
torch
.
stack
([
rot_cos
,
zeros
,
-
rot_sin
]),
torch
.
stack
([
zeros
,
ones
,
zeros
]),
torch
.
stack
([
rot_sin
,
zeros
,
rot_cos
])
])
elif
axis
==
2
or
axis
==
-
1
:
rot_mat_T
=
torch
.
stack
([
torch
.
stack
([
rot_cos
,
rot_sin
,
zeros
]),
torch
.
stack
([
-
rot_sin
,
rot_cos
,
zeros
]),
torch
.
stack
([
zeros
,
zeros
,
ones
])
])
elif
axis
==
0
or
axis
==
-
3
:
rot_mat_T
=
torch
.
stack
([
torch
.
stack
([
ones
,
zeros
,
zeros
]),
torch
.
stack
([
zeros
,
rot_cos
,
rot_sin
]),
torch
.
stack
([
zeros
,
-
rot_sin
,
rot_cos
])
])
else
:
raise
ValueError
(
f
'axis should in range '
f
'[-3, -2, -1, 0, 1, 2], got
{
axis
}
'
)
else
:
rot_mat_T
=
torch
.
stack
([
torch
.
stack
([
zeros
,
rot_cos
,
-
rot_sin
]),
torch
.
stack
([
zeros
,
rot_sin
,
rot_cos
]),
torch
.
stack
([
ones
,
zeros
,
zeros
])
torch
.
stack
([
rot_cos
,
rot_sin
]),
torch
.
stack
([
-
rot_sin
,
rot_cos
])
])
if
clockwise
:
rot_mat_T
=
rot_mat_T
.
transpose
(
0
,
1
)
if
points
.
shape
[
0
]
==
0
:
points_new
=
points
else
:
raise
ValueError
(
f
'axis should in range [0, 1, 2], got
{
axis
}
'
)
points_new
=
torch
.
einsum
(
'aij,jka->aik'
,
points
,
rot_mat_T
)
if
batch_free
:
points_new
=
points_new
.
squeeze
(
0
)
return
torch
.
einsum
(
'aij,jka->aik'
,
(
points
,
rot_mat_T
))
if
return_mat
:
rot_mat_T
=
torch
.
einsum
(
'jka->ajk'
,
rot_mat_T
)
if
batch_free
:
rot_mat_T
=
rot_mat_T
.
squeeze
(
0
)
return
points_new
,
rot_mat_T
else
:
return
points_new
@
array_converter
(
apply_to
=
(
'boxes_xywhr'
,
))
def
xywhr2xyxyr
(
boxes_xywhr
):
"""Convert a rotated boxes in XYWHR format to XYXYR format.
Args:
boxes_xywhr (torch.Tensor): Rotated boxes in XYWHR format.
boxes_xywhr (torch.Tensor
| np.ndarray
): Rotated boxes in XYWHR format.
Returns:
torch.Tensor: Converted boxes in XYXYR format.
(
torch.Tensor
| np.ndarray)
: Converted boxes in XYXYR format.
"""
boxes
=
torch
.
zeros_like
(
boxes_xywhr
)
half_w
=
boxes_xywhr
[
:
,
2
]
/
2
half_h
=
boxes_xywhr
[
:
,
3
]
/
2
boxes
[
:
,
0
]
=
boxes_xywhr
[
:
,
0
]
-
half_w
boxes
[
:
,
1
]
=
boxes_xywhr
[
:
,
1
]
-
half_h
boxes
[
:
,
2
]
=
boxes_xywhr
[
:
,
0
]
+
half_w
boxes
[
:
,
3
]
=
boxes_xywhr
[
:
,
1
]
+
half_h
boxes
[
:
,
4
]
=
boxes_xywhr
[
:
,
4
]
half_w
=
boxes_xywhr
[
...
,
2
]
/
2
half_h
=
boxes_xywhr
[
...
,
3
]
/
2
boxes
[
...
,
0
]
=
boxes_xywhr
[
...
,
0
]
-
half_w
boxes
[
...
,
1
]
=
boxes_xywhr
[
...
,
1
]
-
half_h
boxes
[
...
,
2
]
=
boxes_xywhr
[
...
,
0
]
+
half_w
boxes
[
...
,
3
]
=
boxes_xywhr
[
...
,
1
]
+
half_h
boxes
[
...
,
4
]
=
boxes_xywhr
[
...
,
4
]
return
boxes
...
...
@@ -91,6 +146,10 @@ def get_box_type(box_type):
box_type (str): The type of box structure.
The valid value are "LiDAR", "Camera", or "Depth".
Raises:
ValueError: A ValueError is raised when `box_type`
does not belong to the three valid types.
Returns:
tuple: Box type and box mode.
"""
...
...
@@ -113,21 +172,24 @@ def get_box_type(box_type):
return
box_type_3d
,
box_mode_3d
@
array_converter
(
apply_to
=
(
'points_3d'
,
'proj_mat'
))
def
points_cam2img
(
points_3d
,
proj_mat
,
with_depth
=
False
):
"""Project points
from
camera coordi
c
ates to image coordinates.
"""Project points
in
camera coordi
n
ates to image coordinates.
Args:
points_3d (torch.Tensor): Points in shape (N, 3).
proj_mat (torch.Tensor): Transformation matrix between coordinates.
points_3d (torch.Tensor | np.ndarray): Points in shape (N, 3)
proj_mat (torch.Tensor | np.ndarray):
Transformation matrix between coordinates.
with_depth (bool, optional): Whether to keep depth in the output.
Defaults to False.
Returns:
torch.Tensor: Points in image coordinates with shape [N, 2].
(torch.Tensor | np.ndarray): Points in image coordinates,
with shape [N, 2] if `with_depth=False`, else [N, 3].
"""
points_num
=
list
(
points_3d
.
shape
)[:
-
1
]
points_shape
=
list
(
points_3d
.
shape
)
points_shape
[
-
1
]
=
1
points_shape
=
np
.
concatenate
([
points_num
,
[
1
]],
axis
=
0
).
tolist
()
assert
len
(
proj_mat
.
shape
)
==
2
,
'The dimension of the projection'
\
f
' matrix should be 2 instead of
{
len
(
proj_mat
.
shape
)
}
.'
d1
,
d2
=
proj_mat
.
shape
[:
2
]
...
...
@@ -140,17 +202,52 @@ def points_cam2img(points_3d, proj_mat, with_depth=False):
proj_mat_expanded
[:
d1
,
:
d2
]
=
proj_mat
proj_mat
=
proj_mat_expanded
# previous implementation use new_zeros, new_one y
e
ilds better results
points_4
=
torch
.
cat
(
[
points_3d
,
points_3d
.
new_ones
(
*
points_shape
)],
dim
=-
1
)
point_2d
=
torch
.
matmul
(
points_4
,
proj_mat
.
t
())
# previous implementation use new_zeros, new_one yi
e
lds better results
points_4
=
torch
.
cat
(
[
points_3d
,
points_3d
.
new_ones
(
points_shape
)],
dim
=-
1
)
point_2d
=
points_4
@
proj_mat
.
T
point_2d_res
=
point_2d
[...,
:
2
]
/
point_2d
[...,
2
:
3
]
if
with_depth
:
return
torch
.
cat
([
point_2d_res
,
point_2d
[...,
2
:
3
]],
dim
=-
1
)
point_2d_res
=
torch
.
cat
([
point_2d_res
,
point_2d
[...,
2
:
3
]],
dim
=-
1
)
return
point_2d_res
@
array_converter
(
apply_to
=
(
'points'
,
'cam2img'
))
def
points_img2cam
(
points
,
cam2img
):
"""Project points in image coordinates to camera coordinates.
Args:
points (torch.Tensor): 2.5D points in 2D images, [N, 3],
3 corresponds with x, y in the image and depth.
cam2img (torch.Tensor): Camera intrinsic matrix. The shape can be
[3, 3], [3, 4] or [4, 4].
Returns:
torch.Tensor: points in 3D space. [N, 3],
3 corresponds with x, y, z in 3D space.
"""
assert
cam2img
.
shape
[
0
]
<=
4
assert
cam2img
.
shape
[
1
]
<=
4
assert
points
.
shape
[
1
]
==
3
xys
=
points
[:,
:
2
]
depths
=
points
[:,
2
].
view
(
-
1
,
1
)
unnormed_xys
=
torch
.
cat
([
xys
*
depths
,
depths
],
dim
=
1
)
pad_cam2img
=
torch
.
eye
(
4
,
dtype
=
xys
.
dtype
,
device
=
xys
.
device
)
pad_cam2img
[:
cam2img
.
shape
[
0
],
:
cam2img
.
shape
[
1
]]
=
cam2img
inv_pad_cam2img
=
torch
.
inverse
(
pad_cam2img
).
transpose
(
0
,
1
)
# Do operation in homogeneous coordinates.
num_points
=
unnormed_xys
.
shape
[
0
]
homo_xys
=
torch
.
cat
([
unnormed_xys
,
xys
.
new_ones
((
num_points
,
1
))],
dim
=
1
)
points3D
=
torch
.
mm
(
homo_xys
,
inv_pad_cam2img
)[:,
:
3
]
return
points3D
def
mono_cam_box2vis
(
cam_box
):
"""This is a post-processing function on the bboxes from Mono-3D task. If
we want to perform projection visualization, we need to:
...
...
@@ -162,9 +259,9 @@ def mono_cam_box2vis(cam_box):
After applying this function, we can project and draw it on 2D images.
Args:
cam_box (:obj:`CameraInstance3DBoxes`): 3D bbox in camera coordinate
\
system before conversion. Could be gt bbox loaded from dataset
or
\
network prediction output.
cam_box (:obj:`CameraInstance3DBoxes`): 3D bbox in camera coordinate
system before conversion. Could be gt bbox loaded from dataset
or
network prediction output.
Returns:
:obj:`CameraInstance3DBoxes`: Box after conversion.
...
...
@@ -212,3 +309,27 @@ def get_proj_mat_by_coord_type(img_meta, coord_type):
mapping
=
{
'LIDAR'
:
'lidar2img'
,
'DEPTH'
:
'depth2img'
,
'CAMERA'
:
'cam2img'
}
assert
coord_type
in
mapping
.
keys
()
return
img_meta
[
mapping
[
coord_type
]]
def
yaw2local
(
yaw
,
loc
):
"""Transform global yaw to local yaw (alpha in kitti) in camera
coordinates, ranges from -pi to pi.
Args:
yaw (torch.Tensor): A vector with local yaw of each box.
shape: (N, )
loc (torch.Tensor): gravity center of each box.
shape: (N, 3)
Returns:
torch.Tensor: local yaw (alpha in kitti).
"""
local_yaw
=
yaw
-
torch
.
atan2
(
loc
[:,
0
],
loc
[:,
2
])
larger_idx
=
(
local_yaw
>
np
.
pi
).
nonzero
(
as_tuple
=
False
)
small_idx
=
(
local_yaw
<
-
np
.
pi
).
nonzero
(
as_tuple
=
False
)
if
len
(
larger_idx
)
!=
0
:
local_yaw
[
larger_idx
]
-=
2
*
np
.
pi
if
len
(
small_idx
)
!=
0
:
local_yaw
[
small_idx
]
+=
2
*
np
.
pi
return
local_yaw
mmdet3d/core/bbox/transforms.py
View file @
32a4328b
...
...
@@ -32,7 +32,7 @@ def bbox3d2roi(bbox_list):
corresponding to a batch of images.
Returns:
torch.Tensor: Region of interests in shape (n, c), where
\
torch.Tensor: Region of interests in shape (n, c), where
the channels are in order of [batch_ind, x, y ...].
"""
rois_list
=
[]
...
...
@@ -51,10 +51,10 @@ def bbox3d2result(bboxes, scores, labels, attrs=None):
"""Convert detection results to a list of numpy arrays.
Args:
bboxes (torch.Tensor): Bounding boxes with shape
of (n
, 5).
labels (torch.Tensor): Labels with shape
of (n
, ).
scores (torch.Tensor): Scores with shape
of (n
, ).
attrs (torch.Tensor, optional): Attributes with shape
of (n
, ).
\
bboxes (torch.Tensor): Bounding boxes with shape
(N
, 5).
labels (torch.Tensor): Labels with shape
(N
, ).
scores (torch.Tensor): Scores with shape
(N
, ).
attrs (torch.Tensor, optional): Attributes with shape
(N
, ).
Defaults to None.
Returns:
...
...
mmdet3d/core/evaluation/indoor_eval.py
View file @
32a4328b
...
...
@@ -9,9 +9,9 @@ def average_precision(recalls, precisions, mode='area'):
"""Calculate average precision (for single or multiple scales).
Args:
recalls (np.ndarray): Recalls with shape of (num_scales, num_dets)
\
recalls (np.ndarray): Recalls with shape of (num_scales, num_dets)
or (num_dets, ).
precisions (np.ndarray): Precisions with shape of
\
precisions (np.ndarray): Precisions with shape of
(num_scales, num_dets) or (num_dets, ).
mode (str): 'area' or '11points', 'area' means calculating the area
under precision-recall curve, '11points' means calculating
...
...
@@ -58,13 +58,13 @@ def eval_det_cls(pred, gt, iou_thr=None):
single class.
Args:
pred (dict): Predictions mapping from image id to bounding boxes
\
pred (dict): Predictions mapping from image id to bounding boxes
and scores.
gt (dict): Ground truths mapping from image id to bounding boxes.
iou_thr (list[float]): A list of iou thresholds.
Return:
tuple (np.ndarray, np.ndarray, float): Recalls, precisions and
\
tuple (np.ndarray, np.ndarray, float): Recalls, precisions and
average precision.
"""
...
...
@@ -170,10 +170,9 @@ def eval_map_recall(pred, gt, ovthresh=None):
Args:
pred (dict): Information of detection results,
which maps class_id and predictions.
gt (dict): Information of ground truths, which maps class_id and
\
gt (dict): Information of ground truths, which maps class_id and
ground truths.
ovthresh (list[float]): iou threshold.
Default: None.
ovthresh (list[float], optional): iou threshold. Default: None.
Return:
tuple[dict]: dict results of recall, AP, and precision for all classes.
...
...
@@ -218,12 +217,12 @@ def indoor_eval(gt_annos,
includes the following keys
- labels_3d (torch.Tensor): Labels of boxes.
- boxes_3d (:obj:`BaseInstance3DBoxes`):
\
- boxes_3d (:obj:`BaseInstance3DBoxes`):
3D bounding boxes in Depth coordinate.
- scores_3d (torch.Tensor): Scores of boxes.
metric (list[float]): IoU thresholds for computing average precisions.
label2cat (dict): Map from label to category.
logger (logging.Logger | str
| None
): The way to print the mAP
logger (logging.Logger | str
, optional
): The way to print the mAP
summary. See `mmdet.utils.print_log()` for details. Default: None.
Return:
...
...
mmdet3d/core/evaluation/kitti_utils/eval.py
View file @
32a4328b
# Copyright (c) OpenMMLab. All rights reserved.
import
gc
import
io
as
sysio
import
numba
import
numpy
as
np
...
...
@@ -569,13 +570,20 @@ def eval_class(gt_annos,
return
ret_dict
def
get_mAP
(
prec
):
def
get_mAP
11
(
prec
):
sums
=
0
for
i
in
range
(
0
,
prec
.
shape
[
-
1
],
4
):
sums
=
sums
+
prec
[...,
i
]
return
sums
/
11
*
100
def
get_mAP40
(
prec
):
sums
=
0
for
i
in
range
(
1
,
prec
.
shape
[
-
1
]):
sums
=
sums
+
prec
[...,
i
]
return
sums
/
40
*
100
def
print_str
(
value
,
*
arg
,
sstream
=
None
):
if
sstream
is
None
:
sstream
=
sysio
.
StringIO
()
...
...
@@ -592,8 +600,10 @@ def do_eval(gt_annos,
eval_types
=
[
'bbox'
,
'bev'
,
'3d'
]):
# min_overlaps: [num_minoverlap, metric, num_class]
difficultys
=
[
0
,
1
,
2
]
mAP_bbox
=
None
mAP_aos
=
None
mAP11_bbox
=
None
mAP11_aos
=
None
mAP40_bbox
=
None
mAP40_aos
=
None
if
'bbox'
in
eval_types
:
ret
=
eval_class
(
gt_annos
,
...
...
@@ -604,22 +614,29 @@ def do_eval(gt_annos,
min_overlaps
,
compute_aos
=
(
'aos'
in
eval_types
))
# ret: [num_class, num_diff, num_minoverlap, num_sample_points]
mAP_bbox
=
get_mAP
(
ret
[
'precision'
])
mAP11_bbox
=
get_mAP11
(
ret
[
'precision'
])
mAP40_bbox
=
get_mAP40
(
ret
[
'precision'
])
if
'aos'
in
eval_types
:
mAP_aos
=
get_mAP
(
ret
[
'orientation'
])
mAP11_aos
=
get_mAP11
(
ret
[
'orientation'
])
mAP40_aos
=
get_mAP40
(
ret
[
'orientation'
])
mAP_bev
=
None
mAP11_bev
=
None
mAP40_bev
=
None
if
'bev'
in
eval_types
:
ret
=
eval_class
(
gt_annos
,
dt_annos
,
current_classes
,
difficultys
,
1
,
min_overlaps
)
mAP_bev
=
get_mAP
(
ret
[
'precision'
])
mAP11_bev
=
get_mAP11
(
ret
[
'precision'
])
mAP40_bev
=
get_mAP40
(
ret
[
'precision'
])
mAP_3d
=
None
mAP11_3d
=
None
mAP40_3d
=
None
if
'3d'
in
eval_types
:
ret
=
eval_class
(
gt_annos
,
dt_annos
,
current_classes
,
difficultys
,
2
,
min_overlaps
)
mAP_3d
=
get_mAP
(
ret
[
'precision'
])
return
mAP_bbox
,
mAP_bev
,
mAP_3d
,
mAP_aos
mAP11_3d
=
get_mAP11
(
ret
[
'precision'
])
mAP40_3d
=
get_mAP40
(
ret
[
'precision'
])
return
(
mAP11_bbox
,
mAP11_bev
,
mAP11_3d
,
mAP11_aos
,
mAP40_bbox
,
mAP40_bev
,
mAP40_3d
,
mAP40_aos
)
def
do_coco_style_eval
(
gt_annos
,
dt_annos
,
current_classes
,
overlap_ranges
,
...
...
@@ -629,9 +646,10 @@ def do_coco_style_eval(gt_annos, dt_annos, current_classes, overlap_ranges,
for
i
in
range
(
overlap_ranges
.
shape
[
1
]):
for
j
in
range
(
overlap_ranges
.
shape
[
2
]):
min_overlaps
[:,
i
,
j
]
=
np
.
linspace
(
*
overlap_ranges
[:,
i
,
j
])
mAP_bbox
,
mAP_bev
,
mAP_3d
,
mAP_aos
=
do_eval
(
gt_annos
,
dt_annos
,
current_classes
,
min_overlaps
,
compute_aos
)
mAP_bbox
,
mAP_bev
,
mAP_3d
,
mAP_aos
,
_
,
_
,
\
_
,
_
=
do_eval
(
gt_annos
,
dt_annos
,
current_classes
,
min_overlaps
,
compute_aos
)
# ret: [num_class, num_diff, num_minoverlap]
mAP_bbox
=
mAP_bbox
.
mean
(
-
1
)
mAP_bev
=
mAP_bev
.
mean
(
-
1
)
...
...
@@ -703,33 +721,109 @@ def kitti_eval(gt_annos,
if
compute_aos
:
eval_types
.
append
(
'aos'
)
mAPbbox
,
mAPbev
,
mAP3d
,
mAPaos
=
do_eval
(
gt_annos
,
dt_annos
,
current_classes
,
min_overlaps
,
eval_types
)
mAP11_bbox
,
mAP11_bev
,
mAP11_3d
,
mAP11_aos
,
mAP40_bbox
,
mAP40_bev
,
\
mAP40_3d
,
mAP40_aos
=
do_eval
(
gt_annos
,
dt_annos
,
current_classes
,
min_overlaps
,
eval_types
)
ret_dict
=
{}
difficulty
=
[
'easy'
,
'moderate'
,
'hard'
]
# calculate AP11
result
+=
'
\n
----------- AP11 Results ------------
\n\n
'
for
j
,
curcls
in
enumerate
(
current_classes
):
# mAP threshold array: [num_minoverlap, metric, class]
# mAP result: [num_class, num_diff, num_minoverlap]
curcls_name
=
class_to_name
[
curcls
]
for
i
in
range
(
min_overlaps
.
shape
[
0
]):
# prepare results for print
result
+=
(
'{} AP@{:.2f}, {:.2f}, {:.2f}:
\n
'
.
format
(
result
+=
(
'{} AP
11
@{:.2f}, {:.2f}, {:.2f}:
\n
'
.
format
(
curcls_name
,
*
min_overlaps
[
i
,
:,
j
]))
if
mAPbbox
is
not
None
:
result
+=
'bbox AP:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAPbbox
[
j
,
:,
i
])
if
mAPbev
is
not
None
:
result
+=
'bev AP:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAPbev
[
j
,
:,
i
])
if
mAP3d
is
not
None
:
result
+=
'3d AP:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP3d
[
j
,
:,
i
])
if
mAP11_bbox
is
not
None
:
result
+=
'bbox AP11:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP11_bbox
[
j
,
:,
i
])
if
mAP11_bev
is
not
None
:
result
+=
'bev AP11:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP11_bev
[
j
,
:,
i
])
if
mAP11_3d
is
not
None
:
result
+=
'3d AP11:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP11_3d
[
j
,
:,
i
])
if
compute_aos
:
result
+=
'aos AP11:{:.2f}, {:.2f}, {:.2f}
\n
'
.
format
(
*
mAP11_aos
[
j
,
:,
i
])
# prepare results for logger
for
idx
in
range
(
3
):
if
i
==
0
:
postfix
=
f
'
{
difficulty
[
idx
]
}
_strict'
else
:
postfix
=
f
'
{
difficulty
[
idx
]
}
_loose'
prefix
=
f
'KITTI/
{
curcls_name
}
'
if
mAP11_3d
is
not
None
:
ret_dict
[
f
'
{
prefix
}
_3D_AP11_
{
postfix
}
'
]
=
\
mAP11_3d
[
j
,
idx
,
i
]
if
mAP11_bev
is
not
None
:
ret_dict
[
f
'
{
prefix
}
_BEV_AP11_
{
postfix
}
'
]
=
\
mAP11_bev
[
j
,
idx
,
i
]
if
mAP11_bbox
is
not
None
:
ret_dict
[
f
'
{
prefix
}
_2D_AP11_
{
postfix
}
'
]
=
\
mAP11_bbox
[
j
,
idx
,
i
]
# calculate mAP11 over all classes if there are multiple classes
if
len
(
current_classes
)
>
1
:
# prepare results for print
result
+=
(
'
\n
Overall AP11@{}, {}, {}:
\n
'
.
format
(
*
difficulty
))
if
mAP11_bbox
is
not
None
:
mAP11_bbox
=
mAP11_bbox
.
mean
(
axis
=
0
)
result
+=
'bbox AP11:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP11_bbox
[:,
0
])
if
mAP11_bev
is
not
None
:
mAP11_bev
=
mAP11_bev
.
mean
(
axis
=
0
)
result
+=
'bev AP11:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP11_bev
[:,
0
])
if
mAP11_3d
is
not
None
:
mAP11_3d
=
mAP11_3d
.
mean
(
axis
=
0
)
result
+=
'3d AP11:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP11_3d
[:,
0
])
if
compute_aos
:
mAP11_aos
=
mAP11_aos
.
mean
(
axis
=
0
)
result
+=
'aos AP11:{:.2f}, {:.2f}, {:.2f}
\n
'
.
format
(
*
mAP11_aos
[:,
0
])
# prepare results for logger
for
idx
in
range
(
3
):
postfix
=
f
'
{
difficulty
[
idx
]
}
'
if
mAP11_3d
is
not
None
:
ret_dict
[
f
'KITTI/Overall_3D_AP11_
{
postfix
}
'
]
=
mAP11_3d
[
idx
,
0
]
if
mAP11_bev
is
not
None
:
ret_dict
[
f
'KITTI/Overall_BEV_AP11_
{
postfix
}
'
]
=
\
mAP11_bev
[
idx
,
0
]
if
mAP11_bbox
is
not
None
:
ret_dict
[
f
'KITTI/Overall_2D_AP11_
{
postfix
}
'
]
=
\
mAP11_bbox
[
idx
,
0
]
# Calculate AP40
result
+=
'
\n
----------- AP40 Results ------------
\n\n
'
for
j
,
curcls
in
enumerate
(
current_classes
):
# mAP threshold array: [num_minoverlap, metric, class]
# mAP result: [num_class, num_diff, num_minoverlap]
curcls_name
=
class_to_name
[
curcls
]
for
i
in
range
(
min_overlaps
.
shape
[
0
]):
# prepare results for print
result
+=
(
'{} AP40@{:.2f}, {:.2f}, {:.2f}:
\n
'
.
format
(
curcls_name
,
*
min_overlaps
[
i
,
:,
j
]))
if
mAP40_bbox
is
not
None
:
result
+=
'bbox AP40:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP40_bbox
[
j
,
:,
i
])
if
mAP40_bev
is
not
None
:
result
+=
'bev AP40:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP40_bev
[
j
,
:,
i
])
if
mAP40_3d
is
not
None
:
result
+=
'3d AP40:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP40_3d
[
j
,
:,
i
])
if
compute_aos
:
result
+=
'aos AP:{:.2f}, {:.2f}, {:.2f}
\n
'
.
format
(
*
mAPaos
[
j
,
:,
i
])
result
+=
'aos AP
40
:{:.2f}, {:.2f}, {:.2f}
\n
'
.
format
(
*
mAP
40_
aos
[
j
,
:,
i
])
# prepare results for logger
for
idx
in
range
(
3
):
...
...
@@ -738,39 +832,48 @@ def kitti_eval(gt_annos,
else
:
postfix
=
f
'
{
difficulty
[
idx
]
}
_loose'
prefix
=
f
'KITTI/
{
curcls_name
}
'
if
mAP3d
is
not
None
:
ret_dict
[
f
'
{
prefix
}
_3D_
{
postfix
}
'
]
=
mAP3d
[
j
,
idx
,
i
]
if
mAPbev
is
not
None
:
ret_dict
[
f
'
{
prefix
}
_BEV_
{
postfix
}
'
]
=
mAPbev
[
j
,
idx
,
i
]
if
mAPbbox
is
not
None
:
ret_dict
[
f
'
{
prefix
}
_2D_
{
postfix
}
'
]
=
mAPbbox
[
j
,
idx
,
i
]
# calculate mAP over all classes if there are multiple classes
if
mAP40_3d
is
not
None
:
ret_dict
[
f
'
{
prefix
}
_3D_AP40_
{
postfix
}
'
]
=
\
mAP40_3d
[
j
,
idx
,
i
]
if
mAP40_bev
is
not
None
:
ret_dict
[
f
'
{
prefix
}
_BEV_AP40_
{
postfix
}
'
]
=
\
mAP40_bev
[
j
,
idx
,
i
]
if
mAP40_bbox
is
not
None
:
ret_dict
[
f
'
{
prefix
}
_2D_AP40_
{
postfix
}
'
]
=
\
mAP40_bbox
[
j
,
idx
,
i
]
# calculate mAP40 over all classes if there are multiple classes
if
len
(
current_classes
)
>
1
:
# prepare results for print
result
+=
(
'
\n
Overall AP@{}, {}, {}:
\n
'
.
format
(
*
difficulty
))
if
mAPbbox
is
not
None
:
mAPbbox
=
mAPbbox
.
mean
(
axis
=
0
)
result
+=
'bbox AP:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAPbbox
[:,
0
])
if
mAPbev
is
not
None
:
mAPbev
=
mAPbev
.
mean
(
axis
=
0
)
result
+=
'bev AP:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAPbev
[:,
0
])
if
mAP3d
is
not
None
:
mAP3d
=
mAP3d
.
mean
(
axis
=
0
)
result
+=
'3d AP:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP3d
[:,
0
])
result
+=
(
'
\n
Overall AP40@{}, {}, {}:
\n
'
.
format
(
*
difficulty
))
if
mAP40_bbox
is
not
None
:
mAP40_bbox
=
mAP40_bbox
.
mean
(
axis
=
0
)
result
+=
'bbox AP40:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP40_bbox
[:,
0
])
if
mAP40_bev
is
not
None
:
mAP40_bev
=
mAP40_bev
.
mean
(
axis
=
0
)
result
+=
'bev AP40:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP40_bev
[:,
0
])
if
mAP40_3d
is
not
None
:
mAP40_3d
=
mAP40_3d
.
mean
(
axis
=
0
)
result
+=
'3d AP40:{:.4f}, {:.4f}, {:.4f}
\n
'
.
format
(
*
mAP40_3d
[:,
0
])
if
compute_aos
:
mAPaos
=
mAPaos
.
mean
(
axis
=
0
)
result
+=
'aos AP:{:.2f}, {:.2f}, {:.2f}
\n
'
.
format
(
*
mAPaos
[:,
0
])
mAP40_aos
=
mAP40_aos
.
mean
(
axis
=
0
)
result
+=
'aos AP40:{:.2f}, {:.2f}, {:.2f}
\n
'
.
format
(
*
mAP40_aos
[:,
0
])
# prepare results for logger
for
idx
in
range
(
3
):
postfix
=
f
'
{
difficulty
[
idx
]
}
'
if
mAP3d
is
not
None
:
ret_dict
[
f
'KITTI/Overall_3D_
{
postfix
}
'
]
=
mAP3d
[
idx
,
0
]
if
mAPbev
is
not
None
:
ret_dict
[
f
'KITTI/Overall_BEV_
{
postfix
}
'
]
=
mAPbev
[
idx
,
0
]
if
mAPbbox
is
not
None
:
ret_dict
[
f
'KITTI/Overall_2D_
{
postfix
}
'
]
=
mAPbbox
[
idx
,
0
]
if
mAP40_3d
is
not
None
:
ret_dict
[
f
'KITTI/Overall_3D_AP40_
{
postfix
}
'
]
=
mAP40_3d
[
idx
,
0
]
if
mAP40_bev
is
not
None
:
ret_dict
[
f
'KITTI/Overall_BEV_AP40_
{
postfix
}
'
]
=
\
mAP40_bev
[
idx
,
0
]
if
mAP40_bbox
is
not
None
:
ret_dict
[
f
'KITTI/Overall_2D_AP40_
{
postfix
}
'
]
=
\
mAP40_bbox
[
idx
,
0
]
return
result
,
ret_dict
...
...
mmdet3d/core/evaluation/kitti_utils/rotate_iou.py
View file @
32a4328b
...
...
@@ -5,6 +5,7 @@
# Author: yanyan, scrin@foxmail.com
#####################
import
math
import
numba
import
numpy
as
np
from
numba
import
cuda
...
...
@@ -15,13 +16,13 @@ def div_up(m, n):
return
m
//
n
+
(
m
%
n
>
0
)
@
cuda
.
jit
(
'(float32[:], float32[:], float32[:])'
,
device
=
True
,
inline
=
True
)
@
cuda
.
jit
(
device
=
True
,
inline
=
True
)
def
trangle_area
(
a
,
b
,
c
):
return
((
a
[
0
]
-
c
[
0
])
*
(
b
[
1
]
-
c
[
1
])
-
(
a
[
1
]
-
c
[
1
])
*
(
b
[
0
]
-
c
[
0
]))
/
2.0
@
cuda
.
jit
(
'(float32[:], int32)'
,
device
=
True
,
inline
=
True
)
@
cuda
.
jit
(
device
=
True
,
inline
=
True
)
def
area
(
int_pts
,
num_of_inter
):
area_val
=
0.0
for
i
in
range
(
num_of_inter
-
2
):
...
...
@@ -31,7 +32,7 @@ def area(int_pts, num_of_inter):
return
area_val
@
cuda
.
jit
(
'(float32[:], int32)'
,
device
=
True
,
inline
=
True
)
@
cuda
.
jit
(
device
=
True
,
inline
=
True
)
def
sort_vertex_in_convex_polygon
(
int_pts
,
num_of_inter
):
if
num_of_inter
>
0
:
center
=
cuda
.
local
.
array
((
2
,
),
dtype
=
numba
.
float32
)
...
...
@@ -71,10 +72,7 @@ def sort_vertex_in_convex_polygon(int_pts, num_of_inter):
int_pts
[
j
*
2
+
1
]
=
ty
@
cuda
.
jit
(
'(float32[:], float32[:], int32, int32, float32[:])'
,
device
=
True
,
inline
=
True
)
@
cuda
.
jit
(
device
=
True
,
inline
=
True
)
def
line_segment_intersection
(
pts1
,
pts2
,
i
,
j
,
temp_pts
):
A
=
cuda
.
local
.
array
((
2
,
),
dtype
=
numba
.
float32
)
B
=
cuda
.
local
.
array
((
2
,
),
dtype
=
numba
.
float32
)
...
...
@@ -117,10 +115,7 @@ def line_segment_intersection(pts1, pts2, i, j, temp_pts):
return
False
@
cuda
.
jit
(
'(float32[:], float32[:], int32, int32, float32[:])'
,
device
=
True
,
inline
=
True
)
@
cuda
.
jit
(
device
=
True
,
inline
=
True
)
def
line_segment_intersection_v1
(
pts1
,
pts2
,
i
,
j
,
temp_pts
):
a
=
cuda
.
local
.
array
((
2
,
),
dtype
=
numba
.
float32
)
b
=
cuda
.
local
.
array
((
2
,
),
dtype
=
numba
.
float32
)
...
...
@@ -159,7 +154,7 @@ def line_segment_intersection_v1(pts1, pts2, i, j, temp_pts):
return
True
@
cuda
.
jit
(
'(float32, float32, float32[:])'
,
device
=
True
,
inline
=
True
)
@
cuda
.
jit
(
device
=
True
,
inline
=
True
)
def
point_in_quadrilateral
(
pt_x
,
pt_y
,
corners
):
ab0
=
corners
[
2
]
-
corners
[
0
]
ab1
=
corners
[
3
]
-
corners
[
1
]
...
...
@@ -178,7 +173,7 @@ def point_in_quadrilateral(pt_x, pt_y, corners):
return
abab
>=
abap
and
abap
>=
0
and
adad
>=
adap
and
adap
>=
0
@
cuda
.
jit
(
'(float32[:], float32[:], float32[:])'
,
device
=
True
,
inline
=
True
)
@
cuda
.
jit
(
device
=
True
,
inline
=
True
)
def
quadrilateral_intersection
(
pts1
,
pts2
,
int_pts
):
num_of_inter
=
0
for
i
in
range
(
4
):
...
...
@@ -202,7 +197,7 @@ def quadrilateral_intersection(pts1, pts2, int_pts):
return
num_of_inter
@
cuda
.
jit
(
'(float32[:], float32[:])'
,
device
=
True
,
inline
=
True
)
@
cuda
.
jit
(
device
=
True
,
inline
=
True
)
def
rbbox_to_corners
(
corners
,
rbbox
):
# generate clockwise corners and rotate it clockwise
angle
=
rbbox
[
4
]
...
...
@@ -228,7 +223,7 @@ def rbbox_to_corners(corners, rbbox):
1
]
=
-
a_sin
*
corners_x
[
i
]
+
a_cos
*
corners_y
[
i
]
+
center_y
@
cuda
.
jit
(
'(float32[:], float32[:])'
,
device
=
True
,
inline
=
True
)
@
cuda
.
jit
(
device
=
True
,
inline
=
True
)
def
inter
(
rbbox1
,
rbbox2
):
"""Compute intersection of two rotated boxes.
...
...
@@ -254,7 +249,7 @@ def inter(rbbox1, rbbox2):
return
area
(
intersection_corners
,
num_intersection
)
@
cuda
.
jit
(
'(float32[:], float32[:], int32)'
,
device
=
True
,
inline
=
True
)
@
cuda
.
jit
(
device
=
True
,
inline
=
True
)
def
devRotateIoUEval
(
rbox1
,
rbox2
,
criterion
=-
1
):
"""Compute rotated iou on device.
...
...
@@ -291,7 +286,8 @@ def rotate_iou_kernel_eval(N,
dev_query_boxes
,
dev_iou
,
criterion
=-
1
):
"""Kernel of computing rotated iou.
"""Kernel of computing rotated IoU. This function is for bev boxes in
camera coordinate system ONLY (the rotation is clockwise).
Args:
N (int): The number of boxes.
...
...
@@ -343,10 +339,14 @@ def rotate_iou_gpu_eval(boxes, query_boxes, criterion=-1, device_id=0):
in one example with numba.cuda code). convert from [this project](
https://github.com/hongzhenwang/RRPN-revise/tree/master/lib/rotation).
This function is for bev boxes in camera coordinate system ONLY
(the rotation is clockwise).
Args:
boxes (torch.Tensor): rbboxes. format: centers, dims,
angles(clockwise when positive) with the shape of [N, 5].
query_boxes (float tensor: [K, 5]): rbboxes to compute iou with boxes.
query_boxes (torch.FloatTensor, shape=(K, 5)):
rbboxes to compute iou with boxes.
device_id (int, optional): Defaults to 0. Device to use.
criterion (int, optional): Indicate different type of iou.
-1 indicate `area_inter / (area1 + area2 - area_inter)`,
...
...
mmdet3d/core/evaluation/lyft_eval.py
View file @
32a4328b
# Copyright (c) OpenMMLab. All rights reserved.
from
os
import
path
as
osp
import
mmcv
import
numpy
as
np
from
lyft_dataset_sdk.eval.detection.mAP_evaluation
import
(
Box3D
,
get_ap
,
...
...
@@ -7,7 +9,6 @@ from lyft_dataset_sdk.eval.detection.mAP_evaluation import (Box3D, get_ap,
group_by_key
,
wrap_in_box
)
from
mmcv.utils
import
print_log
from
os
import
path
as
osp
from
terminaltables
import
AsciiTable
...
...
@@ -18,7 +19,7 @@ def load_lyft_gts(lyft, data_root, eval_split, logger=None):
lyft (:obj:`LyftDataset`): Lyft class in the sdk.
data_root (str): Root of data for reading splits.
eval_split (str): Name of the split for evaluation.
logger (logging.Logger | str
| None
): Logger used for printing
logger (logging.Logger | str
, optional
): Logger used for printing
related information during evaluation. Default: None.
Returns:
...
...
@@ -96,7 +97,7 @@ def lyft_eval(lyft, data_root, res_path, eval_set, output_dir, logger=None):
res_path (str): Path of result json file recording detections.
eval_set (str): Name of the split for evaluation.
output_dir (str): Output directory for output json files.
logger (logging.Logger | str
| None
): Logger used for printing
logger (logging.Logger | str
, optional
): Logger used for printing
related information during evaluation. Default: None.
Returns:
...
...
@@ -202,9 +203,9 @@ def get_single_class_aps(gt, predictions, iou_thresholds):
Args:
gt (list[dict]): list of dictionaries in the format described above.
predictions (list[dict]): list of dictionaries in the format
\
predictions (list[dict]): list of dictionaries in the format
described below.
iou_thresholds (list[float]): IOU thresholds used to calculate
\
iou_thresholds (list[float]): IOU thresholds used to calculate
TP / FN
Returns:
...
...
mmdet3d/core/evaluation/seg_eval.py
View file @
32a4328b
...
...
@@ -77,7 +77,7 @@ def seg_eval(gt_labels, seg_preds, label2cat, ignore_index, logger=None):
seg_preds (list[torch.Tensor]): Predictions.
label2cat (dict): Map from label to category name.
ignore_index (int): Index that will be ignored in evaluation.
logger (logging.Logger | str
| None
): The way to print the mAP
logger (logging.Logger | str
, optional
): The way to print the mAP
summary. See `mmdet.utils.print_log()` for details. Default: None.
Returns:
...
...
Prev
1
…
4
5
6
7
8
9
10
11
12
…
21
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment