- 本圖文從介紹配置文件開始,逐步構(gòu)建一個新的配置文件,并依次構(gòu)建相關(guān)模型,最終使用一條點(diǎn)云數(shù)據(jù)簡單走了一下處理流程
- 關(guān)于mmdetection3d的安裝,參考官方文檔安裝 — MMDetection3D 1.0.0rc4 文檔
1、讀取配置文件
1.1 mmdetection3d配置文件的組成
官方文檔:教程 1: 學(xué)習(xí)配置文件 — MMDetection3D 1.0.0rc4 文檔
在mmdetection3d中,主要思想是通過繼承默認(rèn)配置來實(shí)現(xiàn)自定義模型,當(dāng)然,也可以將模型的所有配置寫在一個文件里,按需使用。
配置文件存放于mmdetection3d/config目錄下,其中**_base_目錄為mmdetection3d自帶的基礎(chǔ)配置,即原始配置,從_base_**目錄的組成來看,mmdetection3d將配置文件分為四種,分別是:數(shù)據(jù)集 (dataset),模型 (model),訓(xùn)練策略 (schedule) 和運(yùn)行時的默認(rèn)設(shè)置 (default runtime)
下面基于一個配置文件的部分內(nèi)容,解釋一下該怎么看
# configs/centerpoint/centerpoint_01voxel_second_secfpn_4x8_cyclic_20e_nus.py
_base_ = [
'../_base_/datasets/nus-3d.py',
'../_base_/models/centerpoint_01voxel_second_secfpn_nus.py', # 繼承了這個模型的基礎(chǔ)文件
'../_base_/schedules/cyclic_20e.py', '../_base_/default_runtime.py'
]
model = dict(
pts_voxel_layer=dict(point_cloud_range=point_cloud_range),
pts_bbox_head=dict(bbox_coder=dict(pc_range=point_cloud_range[:2])),
# model training and testing settings
train_cfg=dict(pts=dict(point_cloud_range=point_cloud_range)),
test_cfg=dict(pts=dict(pc_range=point_cloud_range[:2])))
可以看出,在centerpoint_01voxel_second_secfpn_4x8_cyclic_20e_nus.py這個文件中,model部分只有一小段內(nèi)容,這是因?yàn)槔^承了centerpoint_01voxel_second_secfpn_nus.py,只是在繼承文件的基礎(chǔ)上來修改或添加某些特定字段
為了方便說明,來一份簡化版的配置文件
# configs/_base_/models/centerpoint_01voxel_second_secfpn_nus.py
model = dict(
type='CenterPoint',
pts_voxel_layer=dict(
max_num_points=10, voxel_size=voxel_size, max_voxels=(90000, 120000)),
pts_voxel_encoder=dict(type='HardSimpleVFE', num_features=5),
pts_middle_encoder=dict(),
pts_backbone=dict(),
pts_neck=dict(),
pts_bbox_head=dict(),
# model training and testing settings
train_cfg=dict(),
test_cfg=dict())
為了查看具體網(wǎng)絡(luò)是怎么實(shí)現(xiàn)的,我們首先從model最開始出發(fā),根據(jù)配置文件,第一個字段為type,上述例子中使用了CenterPoint,我們需要在mmdetection3d/mmdet3d/models/detectors/__init__.py中,找到CenterPoint,看一下是從哪里引入的,如下圖所示,這樣一來,我們找到了實(shí)現(xiàn)網(wǎng)絡(luò)的具體位置,路徑為:mmdetection3d/mmdet3d/models/detectors/centerpoint.py
再往下走,有一句:python pts_voxel_layer=dict( max_num_points=10, voxel_size=voxel_size, max_voxels=(90000, 120000)),
我們在mmdetection3d/mmdet3d/models/detectors/centerpoint.py中的__init__方法中,找到對應(yīng)初始化字段pts_voxel_layer
但是我們在此發(fā)現(xiàn),這里并沒有使用這個字段,這是因?yàn)镃enterPoint類繼承了MVXTwoStageDetector類,我們順藤摸瓜,查看MVXTwoStageDetector類,發(fā)現(xiàn)這個類使用了pts_voxel_layer字段,并且給出了使用過程。Voxelization(**pts_voxel_layer)
為封裝好的一個體素化函數(shù),它返回一組能表示體素的參數(shù),這里我們不關(guān)心具體實(shí)現(xiàn)。另外,根據(jù)上圖可以看出,init方法里所有的字段與配置文件中的字段是對應(yīng)著順下來的,也就是說,我們可以從centerpoint類里順藤摸瓜,找到所有配置的具體實(shí)現(xiàn)
至此,第一行分析完畢,再往下走,是一句pts_voxel_encoder=dict(type='HardSimpleVFE', num_features=5)
,分析方法與上面相同。
1.2 使用base配置文件構(gòu)建自己的配置文件
這部分我們繼承基礎(chǔ)配置文件,構(gòu)建一個簡單的配置文件,以便接下來使用
-
首先,我們在configs目錄下創(chuàng)建一個文件夾,用于保存自己的配置文件,并新建一個my_config.py文件
-
在新建的配置文件中,寫入以下內(nèi)容
_base_ = [ '../_base_/datasets/nus-3d-mini.py', # 這里我繼承了基礎(chǔ)文件中的nus-3d.py構(gòu)建了一個mini版本,主要就是修改了一下數(shù)據(jù)集路徑 '../_base_/schedules/schedule_2x.py', '../_base_/default_runtime.py', ] voxel_size = [0.1, 0.1, 0.1] norm_cfg = None DOUBLE_FLIP = False # 為了簡單演示,這里只實(shí)現(xiàn)了體素構(gòu)造層和編碼層 model = dict( type="MY_MODEL", voxel_layer=dict( max_num_points=32, point_cloud_range=[0, -39.68, -3, 69.12, 39.68, 1], voxel_size=voxel_size, max_voxels=(16000, 40000)), voxel_encoder=dict( type='VoxelFeatureExtractorV3', num_input_features=4 ), train_cfg=dict(), test_cfg=dict()) data = dict( samples_per_gpu=1, workers_per_gpu=4 )
1.3 根據(jù)配置文件搭建網(wǎng)絡(luò)
接下來要做的,是根據(jù)我們配置文件中的model部分,開始搭建網(wǎng)絡(luò),具體步驟如下:
-
在mmdet3d/models/detectors目錄下,創(chuàng)建一個py文件,這里取名為my_model.py
-
構(gòu)造一個類,類名要和配置文件中的type一致,當(dāng)然也可以在注冊的時候用import … as …來替換:
from ..builder import DETECTORS # 引入構(gòu)造器 @DETECTORS.register_module() # 注冊,這一句必須要有 class MY_MODEL(): def __init__(self): pass
-
在mmdet3d/models/detectors/_init_.py中注冊:
-
在此例中,便于理解,我們就不繼承任何文件了,僅寫出初始化方法
-
接下來要做的,是要在init方法中定義相關(guān)參數(shù)并給出相應(yīng)實(shí)現(xiàn)
from mmcv.ops import Voxelization # 引入mmcv中的體素化方法 from .. import builder # 引入構(gòu)造器 from ..builder import DETECTORS @DETECTORS.register_module() class my_model(): def __init__(self, voxel_layer, voxel_encoder, train_cfg, test_cfg): self.voxel_layer = Voxelization(**voxel_layer) # 這一層是mmcv自帶的,在3.4中會再介紹一下 self.voxel_encoder = builder.build_voxel_encoder(voxel_encoder) # 這里表示這個層是需要我們自己構(gòu)造的
-
再一步,是實(shí)現(xiàn)我們的voxel_encoder層,我們在mmdet3d/models/voxel_encoders目錄下,新建一個文件也好,直接寫在現(xiàn)有文件里也行,這里我寫在了voxel_encoder.py文件下
@VOXEL_ENCODERS.register_module() # 注冊為體素編碼層 class VoxelFeatureExtractorV3(nn.Module): def __init__( self, num_input_features=4, norm_cfg=None, name="VoxelFeatureExtractorV3" ): super(VoxelFeatureExtractorV3, self).__init__() self.name = name self.num_input_features = num_input_features def forward(self, features, num_voxels, coors=None): """ features: 輸入的體素 num_voxels: 體素數(shù)目 """ points_mean = features[:, :, : self.num_input_features].sum( dim=1, keepdim=False ) / num_voxels.type_as(features).view(-1, 1) return points_mean.contiguous()
-
再一步,是在mmdet3d/models/voxel_encoders/__init__.py文件中,引入寫好的VoxelFeatureExtractorV3,這樣,我們就能在配置文件中,使用
voxel_encoder=dict(type='VoxelFeatureExtractorV3', num_input_features=4)
來調(diào)用我們的體素編碼模塊了 -
關(guān)于根據(jù)配置文件的model部分搭建網(wǎng)絡(luò)就寫到這里,有時間的話我會補(bǔ)充更多細(xì)節(jié)
2、構(gòu)建模型
此部分,我們使用jupyter notebook逐步、分解的從數(shù)據(jù)抓取開始,演示一下數(shù)據(jù)在我們搭建的網(wǎng)絡(luò)中的運(yùn)行流程
2.1 讀取配置文件
在真正的訓(xùn)練過程中,是通過傳入的參數(shù),根據(jù)配置文件路徑導(dǎo)入整個參數(shù)的,相關(guān)代碼位于tools/train.py。這里為簡便期間,我們直接使用路徑讀取配置文件
# 讀取配置文件
from mmcv import Config
config_file = "/home/wistful/work/mmdetection3d/configs/my_config/my_config.py"
cfg = Config.fromfile(config_file)
print("cfg type:",type(cfg))
print("cfg.model type:",type(cfg.model))
cfg.model # 打印模型部分
可以看出,打印出來的模型結(jié)構(gòu),與我們配置文件中的一樣。其中,cfg和cfg.model等等的數(shù)據(jù)類型在此就不介紹了
2.2 讀取數(shù)據(jù)
# 取數(shù)據(jù)
from mmdet3d.datasets import build_dataset
datasets = [build_dataset(cfg.data.train)]
print("datastes type:", type(datasets))
print("datastes[0] type", type(datasets[0]))
print("datastes[0][0] type", type(datasets[0][0]))
datasets[0][0].keys()
這里,就不再解釋相關(guān)內(nèi)容了,只需要明白datasets為一個長度1的列表,datasets[0]為一個nuscenes數(shù)據(jù)集類型,datasets[0][i]是nuscenes數(shù)據(jù)集的所有內(nèi)容,每一項(xiàng)包含了四部分內(nèi)容:‘img_metas’, ‘points’, ‘gt_bboxes_3d’, ‘gt_labels_3d’
實(shí)際上,在真正訓(xùn)練或測試過程中,還需要一個data_loader迭代器,方便我們?nèi)?strong>多線程地讀取數(shù)據(jù),并且可以實(shí)現(xiàn)batch以及shuffle的讀取等,mmdet已經(jīng)幫我們實(shí)現(xiàn)了,這里我們由于只需要一條數(shù)據(jù)模擬一下流程,就不構(gòu)造data_loader了
2.3 構(gòu)造模型
# 構(gòu)建模型
from mmdet3d.models import build_model
model = build_model(
cfg.model,
train_cfg=cfg.get('train_cfg'),
test_cfg=cfg.get('test_cfg')
)
model
3、運(yùn)行流程
3.1 voxel_layer:點(diǎn)云 -> 體素
# 示例文件配置中,第一步是voxel_layer層,將點(diǎn)云編碼為體素
voxel_layer = model.voxel_layer
# 取點(diǎn)云數(shù)據(jù)
points = datasets[0][0].get('points').data
# 將點(diǎn)云數(shù)據(jù)送入 voxel_layer
voxels_out, coors_out, num_points_per_voxel_out = voxel_layer(points)
上述代碼中,voxel_layer(points)
執(zhí)行的是self.voxel_layer = Voxelization(**voxel_layer)
,Voxelization的輸入輸出大家可以去具體看一下
我們再使用voxel_layer.parameters
打印一下參數(shù),得到下面輸出:
現(xiàn)在再來回想一下我們自定義的配置文件,這里再放一下:
voxel_size = [0.1, 0.1, 0.1]
model = dict(
type="MY_MODEL",
voxel_layer=dict(
max_num_points=32,
point_cloud_range=[0, -39.68, -3, 69.12, 39.68, 1],
voxel_size=voxel_size,
max_voxels=(16000, 40000)),
voxel_encoder=dict(
type='VoxelFeatureExtractorV3',
num_input_features=4
),
train_cfg=dict(),
test_cfg=dict())
可以看出,voxel_layer層傳入的參數(shù)是我們配置文件中的內(nèi)容,根據(jù)前面提到過的Voxelization的輸入輸出,可以看到,forward部分只缺一個points input,即點(diǎn)云。在本節(jié)剛開始的代碼塊中,voxels_out, coors_out, num_points_per_voxel_out = voxel_layer(points)
執(zhí)行的便是將點(diǎn)云轉(zhuǎn)換為體素操作,我們輸出前后形狀來看一下:
即將自定范圍內(nèi)的點(diǎn)云,按照自定體素大小[0.1,0.1,0.1],每塊體素最多保留32個點(diǎn),最終將32242個點(diǎn),轉(zhuǎn)換為了6051個體素,每個體素包含的點(diǎn)不一樣,但都記錄下來了。
3.2 voxel_encoder:體素編碼
這一層,主要是將上一層(voxel_layer)的輸出進(jìn)行encoder,往上翻到1.3,我們給出了相應(yīng)實(shí)現(xiàn),這里實(shí)現(xiàn)較為簡單,即求每個體素中的平均點(diǎn)。我們現(xiàn)在這里記一下實(shí)現(xiàn)中的forward函數(shù):def forward(self, features, num_voxels, coors=None)
我們先打印一下這一層的參數(shù)
發(fā)現(xiàn)沒有輸出,這是因?yàn)槲覀儧]有定義相關(guān)方法,我們在VoxelFeatureExtractorV3類中加一個方法:
def __repr__(self):
s = self.__class__.__name__ + '('
s += 'num_input_features=' + str(self.num_input_features)
s += ')'
return s
再次執(zhí)行就有輸出了,參數(shù)也是與配置文件相同。下面代碼將上一層的輸出傳遞到這一層
import torch
voxel_encoder = model.voxel_encoder
print(voxel_encoder.parameters)
voxel_encoder_inputs = voxels_out # 將上一層的輸出作為輸入
num_voxels = torch.tensor(voxels_out.shape[0]) # 這里只用一條數(shù)據(jù)作為演示,所以要轉(zhuǎn)一下tensor
voxel_encoder_result = voxel_encoder(voxel_encoder_inputs, num_voxels)
print("voxel_encoder output shape:", voxel_encoder_result.shape)
文章來源:http://www.zghlxwxcb.cn/news/detail-449985.html
我們的配置文件和網(wǎng)絡(luò)只給出了兩個基礎(chǔ)層的定義和實(shí)現(xiàn),剩下的幾層(neck、backbone…)都大同小異,都是這么個流程,完整的流程還會有損失函數(shù)的計算、反向更新等等,這一步是寫在模型的forward里,此篇就不再詳解了文章來源地址http://www.zghlxwxcb.cn/news/detail-449985.html
到了這里,關(guān)于零基礎(chǔ)熟悉mmdetection3d數(shù)據(jù)提取、模型搭建過程的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!