YOLO3D — Monocular 3D object detection with YOLO
Monocular 3D object detection with YOLOv5 for autonomous vehicle technology
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.
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.
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.
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
.
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 al. 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.
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 al. menggunakan persamaan loss function berikut:
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:
# pytorch model module
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]# lightning model module
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:
# pytorch dataset module
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# lightning dataset module
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.
# KITTI Original labels
labels = {
'type': 'car',
'truncated': 0,
'occluded': 0,
'alpha': 1,
'bbox': [50, 25, 25, 50],
'dimensions': [1.2, 1.5, 1.2],
'location': [2.5, 4.5, 3.3],
'rotation_y': 0.75,
'score': 0,
}# YOLO3D labels
labels = {
'Class': 'car',
'Box_2D': [50, 25, 25, 50],
'Dimensions': [1.2, 1.5, 1.2],
'Alpha': 1,
'Orientation': 0.75,
'Confidence': 0.2
}
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.
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.
Repository YOLO3D dapat diakses pada github.com/ruhyadi/YOLO3D. Artikel ini lebih interaktif jika diakses pada Github Pages, akses di ruhyadi.github.io.