[논문 분석] DETR3D (CoRL 2021)
한줄요약 ✔
- Camera-based Object Detection입니다.
- 다중 뷰(multi-view) 2D 이미지를 활용하여 3D 공간에서 정의된 객체 쿼리들의 정보를 추론합니다.
- 이미지 특징 추출: ResNet과 (multi-scale)FPN을 사용해서 다중 뷰 입력 이미지들로 부터 multi-scale 2D 특징들을 추출합니다.
- 2D-to-3D Feature Transformation: 3D 공간의 reference point에 대한 다중 뷰 피처맵들에서의 특징을 bilinear interpolation을 통해 보간하고, 그들의 평균값을 계산합니다.
- Loss: 기존 DETR과 동일한 Hungarian Loss를 따릅니다.
- nuScenes dataset을 이용해서 성능을 평가하였고 SOTA 성능을 냈습니다.
Preliminaries 🍱
Feature Pyramid Network (FPN)
Feature Pyramid Network (FPN)은 이미지에서 다양한 스케일의 객체를 탐지하고 분할하는데 사용되는 딥러닝 네트워크 구조입니다.
FPN은 하위 레이어의 높은 해상도의 정보와 높은 레이어의 추상적 정보를 동시에 활용하여 객체 탐지 및 분할 작업의 정확도를 향상시키는데 주로 사용됩니다.
FPN은 두 가지 주요 구성 요소인 Bottom-up과 Top-down의 조합으로 이루어집니다. 아래는 각 구성 요소의 역할에 대한 설명입니다.
Bottom-up
Bottom-up은 일반적인 CNN(Convolutional Neural Network) 기반의 네트워크입니다.
높은 해상도의 입력 이미지를 저수준의 특징 맵(Feature Map)으로 변환하는 역할을 합니다.
예를 들어, ResNet과 같은 기존의 CNN 아키텍처를 Bottom-up으로 사용할 수 있습니다.
Top-down
Top-down은 Bottom-up으로부터 생성된 저수준의 특징 맵을 이용하여 고수준의 특징 맵을 생성하는 역할을 합니다
즉, Bottom-up으로부터 나온 낮은 해상도의 특징 맵들을 Upsampling한 후, 다른 높은 해상도의 특징 맵들과 결합하여 저수준의 특징과 고수준의 특징을 모두 포함하는 높은 해상도의 특징 맵을 얻어냅니다.
Bilinear Interpolation
Bilinear interpolation은 주어진 두 점의 사이에 위치하는 값들을 추정하는데 사용되는 보간 기법으로, 두 점 사이의 가중 평균으로 값을 추정하는 방법입니다.
Camera Parameters
Camera Parameter는 World Coordinate에 있는 3D Voxel \((U,V,W)\)를 Image Coordinate의 2D Pixel \((x,y)\)로 변환하기 위해 사용됩니다.
이때 3차원의 점 \((U,V,W)\)는 homogeneous counterpart로써 새로운 차원에 \(1\) 값을 새로 추가하여 \(3D \rightarrow 2D\) 차원 변환 과정을 용이하게 한다.
- 2D 차원으로의 투영 행렬의 shape이 \((3,4)\)이기 때문에 \(3\)차원의 점은 \(4\)차원으로 맞춰줄 필요가 있다.
- 새로운 차원의 값을 \(1\)로 설정함으로써 해당 차원 값은 투영 행렬에 의해 depth(깊이)가 된다.
- \(K\): 카메라 내부 파라미터(intrinsic parameter).
- \([R \vert t]\): 카메라 외부 파라미터(extrinsic parameter).
- \(R\): Rotation Matrix.
- \(t\): 3차원 값에 대한 scaling factor.
- \(K[R \vert t]\): camera matrix 또는 projection matrix (투영 행렬).
Extrinsic Parameter
카메라 좌표계(2D)와 월드 좌표계(3D) 사이의 변환 관계를 설명하는 파라미터로서 두 좌표계 사이의 회전(rotation) 및 평행이동(translation) 변환으로 표현합니다.
Intrinsic Parameter
1) Focal Length(초점거리): \(fx, fy\).
\(fx\)와 \(fy\)는 카메라의 렌즈로부터 이미지 평면까지의 거리로, 초점 거리를 나타내는 파라미터입니다.
\(fx\)는 \(x\)축 방향의 초점 거리를, \(fy\)는 \(y\)축 방향의 초점 거리를 의미합니다.
초점 거리는 렌즈의 굴절력과 관련되어 객체의 크기와 카메라 시점에 영향을 줍니다.
2) principal point(주점): \(cx, cy\).
\(cx\)와 \(cy\)는 이미지 평면 상에서 주점의 좌표를 나타내는 파라미터로, 이미지의 중심점을 의미합니다.
주점은 카메라의 광학 축과 이미지 평면의 교차점으로, 카메라 시점과 객체의 상대적인 위치를 나타내는 중요한 정보입니다.
3) skew coefficient(비대칭계수): \(skew_c = tan \alpha\)
\(skew_c\)는 카메라의 렌즈가 정사각형이 아닌 비대칭인 경우 \(y\)축이 기울어진 정도(비대칭 정도)를 나타내는 파라미터로, 일반적으로 \(tan(\alpha)\)의 형태로 표현됩니다.
비대칭 계수는 주로 카메라의 렌즈 또는 이미지 센서의 특성과 관련되며, 이를 고려하여 이미지 변환 및 보정 작업에 사용될 수 있습니다.
DETR 모델
논문 블로그 포스트 링크.
Problem Definition ✏
Given an Transformer-based model for object detection
Return a more efficient model
Such that it outperforms the original model in terms of detecting small objects and inference time while maintaining accuracy.
Challenges and Main Idea💣
C1) 기존 모델들은 depth estimation network에 의존하여 낮은 quality의 estimated depth에 대해 3D detection 성능에 해로운 compounding error를 겪을 수 있습니다.
Idea 1) DETR3D는 depth estimation network를 사용하지 않고 Camera Transformation Matrix을 사용하여 다중 뷰로 부터의 2D Feature Extraction과 3D Object Prediction을 연결하여 이후 depth 정보를 추론합니다.
C2) 기존 모델들은 후처리 과정인 NMS에 의존하게 됩니다.
Idea 2) DETR3D는 End-to-End 학습을 하는 DETR 구조를 따르기 때문에 후처리 과정이 필요가 없습니다.
Proposed Method 🧿
Data Format
- \(\mathcal{L}=\{im_1,...,im_L\} \subset \mathbb{R}^{H_{im} \times W_{im} \times 3}\): Multi-view Camera Image.
- \(\mathcal{T}=\{T_1,...,T_K\} \subset \mathbb{R}^{3 \times 4}\): Camera Transformation Matrix.
- intrinsic & extrinsic parameter를 통해 만드는 행렬입니다 여기.
- \(\mathcal{B}=\{b_1,...,b_j,...,b_M\} \subset \mathbb{R}^9\): GT Bbox; Each \(b_j\) contains (position, size, heading angle, velocity) in the birds-eye view (BEV).
- Position: \((x,y,z)\).
- Size: \((w,h,d)\).
- Heading Angle: \(\theta\).
- Velocity: \(\mathcal{v}\).
- \(\mathcal{C}=\{c_1,...,c_j,...,c_M\} \subset \mathbb{Z}\): categorical labels.
- \(\mathcal{F}_k=\{f_{k1},...,f_{k6}\} \subset \mathbb{R}^{H \times W \times C}\): a level of features of the 6 images.
- \(f_{ki}\): \(k\)번째 크기 level의 \(i\)번째 카메라 뷰 피처맵입니다.
NuScenes 벤치마크에서 다중 뷰 데이터셋은 이미지 각각 총 6개의 다중 뷰 이미지를 포함하고 있습니다.
Feature Learning
ResNet과 FPN을 사용하여서 Multi-Scale에서의 Feature를 추출합니다.
- Multi-Scale FPN: 서로 다른 size의 object를 detection 하는데 필요한 풍부한 정보를 얻을 수 있습니다.
결과적으로 \(6\)개의 다중 뷰 이미지마다 총 \(K\)개 만큼의 서로 다른 크기의 피처맵 level을 얻고, 총 \(6 \times K\)개의 피처맵을 획득합니다.
Detection Head (Main Contribution)
기존 Bottom-Up 방식들과는 달리, 본 논문에서는 Top-down 방식으로 진행하여 NMS와 depth estimation으로 부터 해방됩니다.
Bottom-Up 방식
Image당 많은 bbox를 예측하고 불필요한 bbox를 NMS와 같은 post-processing을 통하여 걸러준 후, 각각의 view에서 얻은 결과를 합쳐주는 과정입니다.
학습과정
1) Object Query를 Neural Network\((\Phi^{ref})\)에 통과시켜 Bbox Center의 집합을 예측합니다.
\(c_{li}=\Phi^{ref}(q_{li})\)
- \(q_{li}\): \(l\)번쨰 레이어의 \(i\)번째 객체 쿼리에서의 reference point입니다.
- \(c_{li}\): \(l\)번쨰 레이어의 \(i\)번째 객체 쿼리에서의 \(3\) 차원 Bbox Center입니다.
Reference Point: 현재 Target으로 Focus하고 있는 영역 혹은 픽셀입니다.
reference_points (Tensor): The normalized reference
points with shape (bs, num_query, 4),
all elements is range in [0, 1], top-left (0,0),
bottom-right (1, 1), including padding area.
or (N, Length_{query}, num_levels, 4), add
additional two dimensions is (w, h) to
form reference boxes.
상기 코드에서 정의된 reference_points
가 바로 \(c_{li}\)이며, 이것의 차원에서 \(4\)는 \((x,y,z,1)\)으로 구성되어 있습니다 (\(1\)은 homegeneous counterpart입니다).
def forward(self,
query,
key,
value,
residual=None,
query_pos=None,
key_padding_mask=None,
reference_points=None,
spatial_shapes=None,
level_start_index=None,
**kwargs):
reference_points_3d, output, mask = feature_sampling(
value, reference_points, self.pc_range, kwargs['img_metas'])
forward 함수내에서 feature_sampling
함수를 통해 3D 객체 쿼리의 reference points를 Multi-Scale 다중 뷰 카메라 이미지 피처맵이라는 2D 공간으로 사영시킨 지점의 2D 이미지 특징들을 output에 담습니다.
output
: 2D 이미지 공간에서 각 reference point에 해당하는 이미지 특징(feature).reference_points_3d
: 3D 객체 쿼리(reference_points
)에 대한 정보를 2D 이미지 공간으로 사영시킨 결과.mask
: 사영시켰을 때 2D 공간 밖으로 사영된 것들의 위치에 masking을 적용하기 위함.
2) 앞서 구한 3D Bbox Center를 Camera Tranformation Matrix를 통해 2D Feature Map으로 Projection(사영)시킵니다.
\(c^*_{li}=c_{li} \oplus 1\)
Camera Tranformation Matrix의 shape이 \((3,4)\)이기 때문에 \(3\)차원에서 \(2\)차원으로의 변환 과정을 용이하게 하고자 \(1\)을 Concat하여 Homogeneous Counterpart로 표현해줍니다.
def feature_sampling(mlvl_feats, reference_points, pc_range, img_metas):
reference_points = torch.cat((reference_points, torch.ones_like(reference_points[..., :1])), -1)
feature_sampling 함수입니다.
상기 코드에서 concat을 통해 \(c_{li}\)에 차원을 하나 더해주는 모습입니다.
\(c_{lmi}=T_mc^*_{li}\)
- \(T_m\): \(m\)번째 카메라 뷰의 투영 행렬입니다.
결과적으로 \(3\)차원의 Center를 \(4\)차원으로 확장한 후, 투영 행렬의 입력으로 넣어 최종적으로 2D 차원 \((x^{\prime},y^{\prime},1)\)으로 사영시킵니다.
\((x^{\prime},y^{\prime},1) = (x^{\prime},y^{\prime})\).
lidar2img = []
for img_meta in img_metas:
lidar2img.append(img_meta['lidar2img'])
lidar2img = np.asarray(lidar2img)
lidar2img = reference_points.new_tensor(lidar2img) # (B, N, 4, 4)
여기서 lidar2img
는 Camera Transformation Matrix \(T_m\)이며, N
은 camera view의 개수 6개입니다.
reference_points_cam = torch.matmul(lidar2img, reference_points).squeeze(-1)
상기 코드줄은 투영 행렬과 reference points를 곱하여 2D 공간으로 사영시키는 \(T_mc^*_{li}\) 연산을 의미하며, 결과적으로 reference_points_cam
에 2D 공간으로 사영시킨 reference points가 담기게 됩니다.
이때, squeeze(-1)로 homegeneous counterpart에 해당하는 차원을 제거합니다.
3) 각 카메라 뷰 이미지으로 사영된 위치에서 Bilinear Interpolation을 통해 Feature를 Sample 하고 Object Query와 더해줍니다.
\(f_{lkmi}=f^{bilinear}(\mathcal{F}_{km},c_{lmi})\)
- \(f^{bilinear}\): \(\mathcal{F}_{km}\) 피처맵에서 \(c_{lmi} \in \mathbb{R}\) 중심점을 적절한 Index로 보간해주기 위한 Bilinear Interpolation 함수입니다.
Bilinear Interpolation을 사용하는 이유는 \(3\)차원에서 \(2\)차원으로 사영된 Center는 실수값 일수도 있는데, \(2\)차원에서 알맞은 정수형의 Center Index로 보간해주기 위함입니다.
reference_points_cam_lvl = reference_points_cam.view(B*N, num_query, 1, 2)
sampled_feat = F.grid_sample(feat, reference_points_cam_lvl)
상기 코드에서 feat
는 \(\mathcal{F}_{km}\)이고, view
함수 인자의(...,1,2)
를 통해 2차원 좌표로 표현합니다.
또한, F.grid_sample
함수는 \(f^{bilinear}\) 부분으로 pytorch 문서에 다음과 같이 default 형태가 정의되어 있습니다:
torch.nn.functional.grid_sample(input, grid, mode='bilinear', padding_mode='zeros', align_corners=None)
하여 다음 수식과 같이 \(l\)번째 레이어의 \(i\)번째 객체쿼리에 대하여, 다수의 multi-view 이미지와 각각에 대한 서로 다른 해상도 피처맵의 정보를 모두 고려하여 2차원 사영 sample을 추출합니다.
\(\textbf{f}_{li}={1 \over \Sigma_k \Sigma_m \sigma_{lkmi} +\epsilon} \Sigma_k \Sigma_m \textbf{f}_{lkmi} \sigma_{lkmi}\)
- \(\sigma_{lkmi}\): \(l\)번째 레이어의 \(k\)번째 level의 \(m\)번째 카메라 뷰의 \(i\)번째 객체쿼리에 대한 Point가 Image Plane 밖으로 사영됐을 경우 필터링 하기위한 binary 값입니다.
- \(\epsilon\): 분모가 0으로 나눠지는 것을 방지하고자 아주 작은 값을 더해줍니다.
우선 mask
를 생성하고, reference_points_cam
을 정규화를 하여 Multi-Scale 피처맵들의 범위를 통일화 시켜줍니다.
eps = 1e-5
mask = (reference_points_cam[..., 2:3] > eps)
reference_points_cam = reference_points_cam[..., 0:2] / torch.maximum(reference_points_cam[..., 2:3], torch.ones_like(reference_points_cam[..., 2:3])*eps)
reference_points_cam[..., 0] /= img_metas[0]['img_shape'][0][1]
reference_points_cam[..., 1] /= img_metas[0]['img_shape'][0][0]
reference_points_cam = (reference_points_cam - 0.5) * 2
여기서 eps
는 수식에서 \(\epsilon\)을 의미하고, 상기 코드는 normalization을 수행하여 2D 피처맵을 \([-1,1]\) 범위로 정규화합니다.
이때 mask
는 [...,2:3]
를 통해 \(z\)축 좌표값이 eps
보다 큰 경우, 2차원 좌표가 아니라 간주하고 masking하여 제외하게 됩니다.
mask = (mask & (reference_points_cam[..., 0:1] > -1.0)
& (reference_points_cam[..., 0:1] < 1.0)
& (reference_points_cam[..., 1:2] > -1.0)
& (reference_points_cam[..., 1:2] < 1.0))
mask = mask.view(B, num_cam, 1, num_query, 1, 1).permute(0, 2, 3, 1, 4, 5)
mask = torch.nan_to_num(mask)
또한, 상기 코드에서 정규화 된 2D 피처맵의 범위 밖으로 사영된 것들에 대해 추가적으로 masking을 부여합니다.
reference_points_3d = reference_points.clone()
sampled_feats = []
for lvl, feat in enumerate(mlvl_feats):
B, N, C, H, W = feat.size()
feat = feat.view(B*N, C, H, W)
reference_points_cam_lvl = reference_points_cam.view(B*N, num_query, 1, 2)
sampled_feat = F.grid_sample(feat, reference_points_cam_lvl)
sampled_feat = sampled_feat.view(B, N, C, num_query, 1).permute(0, 2, 3, 1, 4)
sampled_feats.append(sampled_feat)
sampled_feats = torch.stack(sampled_feats, -1)
sampled_feats = sampled_feats.view(B, C, num_query, num_cam, 1, len(mlvl_feats))
return reference_points_3d, sampled_feats, mask
위 코드를 통해 sampled_feats
변수에 수식에서 \(f_{li}\) 값인 2D 이미지 특징맵들을 담아 리턴하게 됩니다.
reference_points_3d, output, mask = feature_sampling(value, reference_points, self.pc_range, kwargs['img_metas'])
이전 feature_sampling 함수를 invoke했던 부분으로 다시 돌아와서, output
변수에 sampled_feats
값이 담기는 과정을 이제 이해할 수 있습니다.
이때 output의 shape는 이전 코드에서 봤듯이 (B, C, num_query, num_cam, 1, len(mlvl_feats))
가 됩니다.
- \(B\): 배치 크기.
- \(C\): 채널 수 (특징 맵의 채널 수).
num_query
: 객체 쿼리의 개수.num_cam
: 다중 뷰 카메라의 개수.- \(1\): 하나의 피처맵 샘플을 나타내는 차원.
- len(
mlvl_feats
): 다중 레벨의 피처맵들의 개수.
이후 다음 코드 전개를 거치게 됩니다:
class DETR3DCrossAtten(BaseModule):
...
forward(...):
...
attention_weights = self.attention_weights(query).view(bs, 1, num_query, self.num_cams, self.num_points, self.num_levels)
reference_points_3d, output, mask = feature_sampling(value, reference_points, self.pc_range, kwargs['img_metas'])
output = torch.nan_to_num(output) // output에 있는 NaN 값을 0으로 변환
// 차원 변화 없음
mask = torch.nan_to_num(mask) // mask에 있는 NaN 값을 0으로 변환
// 차원 변화 없음
attention_weights = attention_weights.sigmoid() * mask // attention_weights에 시그모이드 함수를 적용하고 mask와 곱하여 가중치 계산
// 차원 변화 없음
output = output * attention_weights // output에 attention_weights를 곱하여 가중치를 적용
// 차원 변화 없음
output = output.sum(-1).sum(-1).sum(-1) // output의 마지막 3개 축을 합하여 새로운 형태로 변환
// 새로운 차원: (B, C, num_query)
여기서 mask
는 attention_weights
에 곱해지고, 이후 attention_weights
에 output
가 곱해져서 masking이 적용되는 모습입니다.
여기서 output.sum(-1).sum(-1).sum(-1)
부분에서 6개의 다중 카메라 뷰 이미지들에 대한 서로 다른 크기의 2D 피처맵의 특징들을 모두 더하게 되고, 이 과정은 상기 수식과 동일합니다.
\(\textbf{q}_{l+1}= \textbf{f}_{li}+ \textbf{q}_{li}\)
이렇게 \(2\)차원으로 부터 각 객체 쿼리마다 더해진 하나의 이미지 특징을 해당 객체 쿼리에 더하여 다음 레이어를 구성하게 됩니다.
output = output.permute(2, 0, 1) // output의 차원 순서를 변경하여 새로운 형태로 변환
// 새로운 차원: (num_query, B, C)
output = self.output_proj(output) // output에 self.output_proj를 적용하여 3차원 객체쿼리와 더할 수 있도록 차원 변환
// 새로운 차원: (num_query, B, embed_dims)
pos_feat = self.position_encoder(inverse_sigmoid(reference_points_3d)).permute(1, 0, 2) // reference_points_3d를 역 시그모이드 함수를 적용하여 위치 인코딩 계산하고 차원 변환
// 새로운 차원: (num_query, bs, embed_dims)
return self.dropout(output) + inp_residual + pos_feat // self.dropout을 적용한 output과 inp_residual, pos_feat를 더하여 최종 결과 반환
// 최종 차원: (num_query, B, embed_dims)
이후 return 부분에서 3D 객체 쿼리의 위치 인코딩 pos_feat
과 2D 이미지 특징 output
, 그리고 잔차 inp_residual
을 더해서 CrossAttention의 최종 출력을 생성합니다.
4) DETR처럼 Multi-Head Attention을 사용하여 서로 다른 Object 간의 Interaction을 학습합니다.
5) 각 Object Query를 Bbox Regression과 Classification을 위한 2개의 MLP의 입력으로 통과시킵니다.
6) 모델 예측으로 획득한 Class 예측과 Bbox 예측을 정답 레이블(GT)와 비교하여 Loss를 계산합니다.
이후, 이 Loss를 기반으로 역전파 과정에서 Bbox의 Center\((x,y,z)\)과 크기\((w,h,d)\)가 업데이트됩니다.
Loss
앞서 설명했듯 set-to-set loss를 사용합니다.
이는 class label을 위한 focal loss와 bbox parameter를 위한 L1 loss로 구성이 됩니다.
DETR의 Hungarian Loss와 동일합니다 (아래 포스트 참조해주세요).
DETR.
Experiment 👀
기존 방식들과의 비교 Table
- 기존의 monocular 3D object detection은 multi-view에 적용하려면 각각의 view에 따로 진행해야 하는 단점이 존재하지만, DETR3D은 multi-view를 한 번에 사용하여 매우 효율적입니다.
Multi-View Camera에서 겹치는 영역에 대한 비교 Table
- DETR3D는 Multi-View를 한번에 사용하므로 여러 View에 대해 중복되어 Object가 잘리는 현상에 대해서 효율적으로 처리가 가능합니다.
Pseudo-LiDAR 비교 Table
매우 큰 성능 차이를 보이는데, Pseudo-LiDAR가 겪는 compounding error를 DETR3D는 겪지 않기 때문입니다.
Detection Head Layer Refinement 실험
- Layer가 깊어짐에 따라 예측되는 bbox가 Ground Truth에 가까워지는 것을 볼 수 있고 Table 5를 봐도 NDS가 layer가 깊어짐에 따라 점점 오르는 것을 볼 수 있습니다.
Object Query 개수 실험
결과적으로 \(900\)개에서 saturate(포화) 되는 모습이며, 이를 통해 적당한 양의 객체 쿼리를 사용하는 것이 좋다는 것을 알게 되었습니다.
Open Reivew 💗
- Limited ablations.
- However, I do not understand why they compared against FCOS3D without fine-tuning.
- DETR 모델 설명 무족.
Discussion 🍟
NA
Major Takeaways 😃
- Camera Transformation Matrix.
Conclusion ✨
Strength
- End-to-End 학습: DETR-3D는 이미지와 3D 객체 정보를 함께 고려하여 End-to-End로 학습되는 모델이기 때문에 별도의 복잡한 파이프라인 없이 하나의 네트워크로 객체 탐지와 분류를 수행할 수 있습니다.
- 3D 객체 쿼리 사용: 3D 객체 쿼리를 활용하여 객체의 위치, 크기 및 방향 정보를 표현함으로써, 3D 공간에서의 객체 탐지를 가능하게 합니다. 이를 통해 2D 탐지 모델보다 더 정확한 3D 객체 탐지가 가능합니다.
- NMS 제약 없음.
- 별도의 Depth Estimatino Network 필요 없음.
Weakness
- 성능 대비 높은 계산 비용 : DETR-3D는 객체 쿼리를 사용하는 등의 복잡한 구조로 인해 다른 2D 객체 탐지 모델보다 높은 계산 비용을 가질 수 있습니다.
- 일부 측면에서 기존 방법과 비교적 성능 차이 : 논문에서 언급한 바와 같이 일부 경우에 FCOS3D나 CenterNet과 비교하여 성능이 더 낮게 나온 경우가 있습니다. 이러한 부분에 대한 성능 향상이 필요할 수 있습니다.
Reference
NA
댓글남기기