Skip to content

YOLO3D

Monocular 3D object detection with YOLOv5 for autonomous vehicle technology

inference

3D Object Detection

What's 3D object detection? 3D object detection sama seperti object detection umumnya (2D object detection), hanya saja mempunyai fitur kedalaman, sehingga bounding-box dapat berupa kubus ketimbang kotak pada 2D object detection.

Salah satu bidang yang membutuhkan object detection secara 3D adalah autonomous vehicle atau self-driving car. Mobil bergerak dalam dunia 3D yang membuatnya mempunyai 6 DoF (Degree of Freedom), diilustrasikan pada gambar dibawah.

image

6 DoF of Car

Tujuan self-driving car adalah untuk membawa kendaraan dari poin A ke poin B, dalam perjalanannya kendaraan juga harus dapat menghindari obstacle. Obstacle dalam dunia 3D juga harus dideteksi secara 3D, oleh karena itu dibutuhkan 3D object detection.

Perbedaan lain dari 3D object detection adalah anotasi dari dataset yang digunakan. Anotasi 2D object detection umumnya hanya memerlukan anotasi bounding-box (xmin, ymin) dan (xmax, ymax). Tetapi anotasi 3D object detection dapat beragam seperti terdapat bounding-box 2D, dimensi objek, lokasi objek, orientasi objek, dll.

Dataset for 3D Object Detection

Where to get the dataset? Untuk saat ini terdapat tiga dataset yang umumnya digunakan sebagai dataset benchmark. Dataset tersebut adalah KITTI, nuScene, dan Lyft. KITTI merupakan dataset yang paling awal, dirilis pada tahun 2012 dan rilis terbaru pSada tahun 2017. Penulis menggunakan dataset KITTI dalam implementasi YOLO3D. Sementara itu, dataset nuScene dan Lyft merupakan dataset terbaru yang juga menggunakan konfigurasi sensor terbaru.

kitti-sensor

nuscenes lyft

Perbandingan konfigurasi sensor yang digunakan oleh ketiga dataset dapat dilihat pada gambar di bawah. Perbedaan terbesar adalah bahwa KITTI tidak menggunakan omnidirectional camera view, dan malah menggunakan konfigurasi dual-stereo camera (color, and gray camera). Perbedaan lainnya antara ketiga dataset terletak pada konfigurasi Lidar dan Radar. Perbedaan juga terletak pada format anotasi data, nuScene dan Lyft menggunakan format token dalam file .json, sedangakan KITTI menggunakan format .txt.

How to Detect Object in 3D

What method to use? Metode akan bergantung dari modalities sensor yang digunakan. YOLO3D sendiri menggunakan modalities monocular camera. Modalities yang sama juga digunakan dalam model RTM3D, SMOKE, dan AutoShape. State-of-The-Art (SOTA) dari 3D object detection sendiri masih dipegang oleh modalities LiDAR. Leadherboard untuk masing-masing dataset dapat dilihat pada: KITTI, nuScene, dataset lain.

YOLO3D terinspirasi dari Mousavian et al1 dalam papernya 3D Bounding Box Estimation Using Deep Learning and Geometry. Perbedaan pendekatan yang penulis lakukan adalah penggantian 2D detector dari Faster-RCNN menjadi YOLOv5, dan regressor model dari VGG19 menjadi ResNet18. Arsitektur YOLO3D dapat dilihat pada gambar di bawah.

model

Detector berfungsi untuk mendeteksi objek dari data gambar yang diberikan. Keluaran dari detektor adalah 2D bounding-box dari setiap objek yang terdeteksi. 2D bounding-box akan menjadi masukan bagi model Regressor. Luaran model regressor akan masuk pada masing-masing Head FCN. Head terbagi menjadi tiga task: task orientation dan task confidence digunakan untuk meregressi orientasi objek, sedangkan task dimension digunakan untuk meregressi ukuran 3D objek.

What about loss function? loss function dalam implementasi kali ini berfungsi untuk membandingkan prediksi orientasi dan dimensi dengan ground-thruth orientasi dan dimenasi dari dataset. Mousavian et al1 menggunakan persamaan loss function berikut:

\[ L = \alpha \times L_{dims} + L_{\theta} \]
\[ L_{dims} = \frac{1}{n} \sum (D^{*} - \overline{D} - \delta) \]
\[ L_{\theta} = L_{conf} + w \times L_{loc} \]
\[ L_{conf} = softmax (bins) \]
\[ L_{loc} = - \frac{1}{n_{\theta^{*}}} \sum \cos (\theta^{*} - c_{i} - \Delta \theta_{i}) \]

Model & Dataset Module

Penulis menggunakan framework PyTorch dan PyTorch Lightning dalam pembuatan module untuk model dan dataset loader. Sehingga model YOLO3D dapat ditraining menggunakan PyTorch ataupun PyTorch Lightning. Perlu diperhatikan disini bahwa penulis hanya melatih model regressor dan tidak melatih model detekto YOLOv5.

How to make model module? Modul model dapat dibuat mengikuti format dari official framework masing-masing. Perbedaan dari kedua framework tersebut terletak pada mode training. PyTorch menggunakan mode training yang terpisah dengan kode module, sedangkan mode training Lightning digabung dengan kode module pada fungsi training_step dan validation_step. Pseudocode modul model dapat dilihat pada kode di bawah:

class Model(nn.Module):
    def __init__(self):
        self.backbone = backbone
        self.orientation = nn.Sequential(...)
        self.confidence = nn.Sequential(...)
        self.dimension = nn.Sequential(...)
    def forward(self, x):
        x = self.backbone
        orientation = self.orientation(x)
        confidence = self.confidence(x)
        dimension = self.dimension(x)
        return [orientation, confidence, dimension]
class Model(pl.LightningModule):
    def __init__(self):
        # same as pytorch
    def forward(self, x):
        # same as pytorch
    def traing_step(self, batch, batch_idx):
        x, labels = batch
        # forward
        [orientation, confidence, dimension] = self(x)
        # compute loss
        orient_loss = self.orient_loss_func(orientation, gt_orientation, get_confidence)
        conf_loss = self.conf_loss_func(confidence, get_confidence)
        dim_loss = self.dim_loss_func(dimension, gt_dimension)
        loss = alpha*dim_loss + (orient_loss*w + conf_loss)
        return {'loss': loss}
    def validation_step(self, batch, batch_idx):
        # same as training_step, instead use validation data
    def configure_optimizer(self):
        # for configure optimizer, lr schedulder, etc.

How to make dataset module? Modul dataset dibuat mengikuti format PyTorch, sedangkang modul Lightning hanya melakukan dataset loader dari modul dataset PyTorch. Pseudocode modul dataset dapat dilihat pada kode di bawah:

class Dataset(data.Dataset):
    def __init__(self, data_path):
        self.data_path = data_path
        ...
    def __getitem__(self, index):
        self.img = cv2.imread(os.path.join(self.data_path, index))
        self.label = self.labels[index]
        # preprocessing image and label
        obj = DetectedObject(self.img, self.label, ...)
        return obj.img, label
    def __len__(self)
        return len(self.object_list)
    def preprocessing_stuff(self)
        return final_img, final_label
class DetectedObject:
    ...
    # for preprocessing stuff
class KITTIDataModule(pl.LightningDataModule):
    def __init__(self, data_path, batch_size, val_split):
        self.dataset = data_path
        self.params = {'batch_size': batch_size, ...}
        self.split = val_split
        ...
    def setup(self, stage=None):
        # get pytorch dataset module
        self.KITTI = Dataset(path=self.dataset)
        # split dataset into training and validation
        ...
    def train_dataloader(self):
        # loader for training data
        ...
    def val_dataloader(self):
        # loader for testing data
        ...

Fungsi modul dataset adalah untuk mendapatkan data yang diperlukan model dari label pada dataset. Dataset KITTI mempunyai label dengan data: bounding-box 2D, object dimension, object location, orientation of object, dll. Data pada label tersebut tidak sepenuhnya digunakan, atau membutuhkan preprocessing lebih lanjut. Oleh karena itu, data label tersebut diubah menjadi data yang dibutuhkan oleh model. Lebih lengkapnya lihat perbandingan data sebelum dan sesudah diproses oleh modul Dataset.

labels = {
    'type': 'car',                   # Describes the type of object: 'Car','Pedestrian', etc
    'truncated': 0,                  # 0 to 1, Truncated refers to the object leaving image boundaries
    'occluded': 0,                   # indicating occlusion state: 0 = fully visible, 1 = partly occluded 2 largely occluded, 3 = unknown
    'alpha': 1,                      # Observation angle of object, ranging [-pi..pi]
    'bbox': [50, 25, 25, 50],        # 2D bounding box of object in the image (0-based index): contains left, top, right, bottom pixel coordinates
    'dimensions': [1.2, 1.5, 1.2],   # 3D object dimensions: height, width, length (in meters)
    'location': [2.5, 4.5, 3.3],     # 3D object location x,y,z in camera coordinates (in meters)
    'rotation_y':  0.75,             # Rotation ry around Y-axis in camera coordinates [-pi..pi]
    'score': 0,                      # Only for results: Float
}
labels = {
    'Class': 'car',                 # Describes the type of object: 'Car','Pedestrian', etc
    'Box_2D': [50, 25, 25, 50],     # 2D bounding box of object
    'Dimensions': [1.2, 1.5, 1.2],  # 3D object dimensions
    'Alpha': 1,                     # Observation angle of object
    'Orientation': 0.75,            # Rotation ry around Y-axis
    'Confidence': 0.2               # Confidence of orientation bins
}

Training Model

How the results of the training model? Penulis mentraining model ResNet18 dan VGG11 menggunakan dataset KITTI masing-masing pada 10 epoch. Perbandingan grafik loss training dapat dilihat pada gambar di bawah.

resnet18 vgg11

Pada epoch ke 10 kedua model masing-masing mempunyai loss -0.273 untuk ResNet18 dan -0.320 untuk VGG11. Perbandingan performa model dengan loss bukan perbandingan yang baik. Perbandingan harus dilakukan menggunakan mAP dari data evaluasi. Untuk saat ini sendiri kode evaluasi belum penulis buat.

Reference


  1. Mousavian, A., Anguelov, D., Flynn, J., & Kosecka, J. (2017). 3d bounding box estimation using deep learning and geometry. In Proceedings of the IEEE conference on Computer Vision and Pattern Recognition (pp. 7074-7082).