【三维目标检测】PointRCNN(二)
? ? ??? ? ? ? PointRCNN數據和源碼配置過程請參考上一篇博文:【三維目標檢測】PointRCNN(一)_Coding的葉子的博客-CSDN博客。本文主要詳細介紹PointRCNN網絡結構及其運行中間狀態。
????????PointRCNN是用于點云三維目標檢測模型算法,發表在CVPR2019《PointRCNN: 3D Object Proposal Generation and Detection From Point Cloud》。論文網址為https://arxiv.org/abs/1812.04244。PointRCNN核心思想在于使用點云前景點生成候選框,充分利用了目標點與候選框的關聯性。相比之下,之前的目標檢測網絡候選框基本上是基于二維特征圖來批量生成的,前景點和背景點對候選的貢獻是一樣的。作者使用PointRCNN在KITTI數據集上進行了大量實驗并在當時達到了最佳效果。目前,Point RCNN以KITTI三維目標檢測平均精度AP為75.42%排在第20位,排名來源于paperwithcode網站,網址為:https://paperswithcode.com/sota/3d-object-detection-on-kitti-cars-moderate。
1 PointRCNN模型總體過程
? ? ? ? PointRCNN模型分為兩個階段,第一階段使用語義分割的方法分割出前景點并初步生成候選框;第二階段對候選框進一步篩選并結合特征融合與坐標系變換等方法對篩選后的候選框進行特征提取、正負樣本分類和特征回歸。
? ? ? ? PointRCNN模型總體過程如下圖所示,第一階段主要包括特征提取、候選框生成、RPN損失計算;第二階段主要包括候選框再篩選、正負樣本選擇、特征提取和ROI損失。
2 主要模塊解析
2.1 點云特征提取??
????????Point RCNN點云特征提取層包括backbone和neck兩個網絡,其中backbone網絡主要用于提取多尺度高維度特征,neck網絡將高維特征映射回待解決的問題空間,如分類或語義分割。
2.1.1?主干網絡backbone
????????以KITTI數據為例,Point RCNN網絡輸入為Nx4(N=16384)維度的點云,4個維度分別是坐標x、y、z和反射強度r。Point RCNN主干網絡采用多尺度PointNet++結構,即PointNet2SAMSG,詳細介紹請參考博文:【三維深度學習】PointNet++(三):多尺度分組MSG詳解_Coding的葉子的博客-CSDN博客_pointnet msg ssg。
????????PointNetSAMSG中采用四個級聯的SAMSG結構依次進行特征提取。SA采樣點數分別是4096、1024、256和64,因此,采樣后點云坐標維度sa_xyz為[16384x3, 4096x3, 1024x3, 256x3, 64x3],特征維度sa_feature的維度為[1x16384, 96x4096, 256x1024, 512x256, 1024x64],同時返回各個采樣點在原始點云中的索引sa_indices,其維度為[16384, 4096, 1024, 256, 64]。各個維度計算過程中默認忽略了Batch的維度,即BxNxM僅考慮NxM。B為batch size大小,可以根據需要自行設置。
? ? ? ? PointRCNN主干網絡的輸出包括點云特征列表sa_feature [1x16384, 96x4096, 256x1024, 512x256, 1024x64]、采樣后點云坐標列表sa_xyz [16384x3, 4096x3, 1024x3, 256x3, 64x3]和采樣點索引列表sa_indices [16384, 4096, 1024, 256, 64]。關鍵函數調用及源碼如下所示。
N =16384 # points B x N x 4 16384x4, x = self.extract_feat(points)Backbone PointNet2SAMSG x = self.backbone(points) #backbone返回值包括:sa_xyz、sa_feature、sa_indices sa_xyz:[16384x3, 4096x3, 1024x3, 256x3, 64x3],SAMSG采樣后的點云坐標 sa_feature:[1x16384, 96x4096, 256x1024, 512x256, 1024x64],SAMSG采樣后的點云特征 sa_indices:[16384, 4096, 1024, 256, 64],SAMSG采樣后的點云索引?2.1.2 Neck網絡
????????PointRCNN的Neck網絡結構采用了PointNet++中的FP(Pointnet2_fp_neck feature propagation)特征上采樣層。點云特征上采樣原理和實現的詳細介紹請參考博文:【點云上采樣】三維點云特征上采樣_Coding的葉子的博客-CSDN博客_點云上采樣。
????????PointNet++的上采樣是通過插值來實現的,并且插值依賴于前后兩層特征。假設前一層的點數N=64,特征維度為D1;后一層點數S=16,特征維度為D2。那么插值的任務就是把后一層的點數插值成64,或者把前一層的特征維度由D1插值到D2。主要步驟如下:
????????(1)以前一層的64個點為參考點,分別計算這64個點和待插值的16個點的距離,得到64x16的距離矩陣。
?????????(2)分別在待插值的16個點中選擇k=3個最接近各個參考點的點,然后將這k個點特征的加權平均值作為插值點的特征。每個參考點都會得到一個新的特征,新的特征來自于后一層點特征的加權平均。加權系數等于各個點的距離倒數除以3個點的距離倒數之和。距離越近,加權系數越大。
?????????(3)通常插值后的特征維度會與原特征進行拼接,即維度為D1+D2。為了使特征維度保持為D2,通道為(D1+D2, D2)的卷積被用來將輸出特征對齊到D2。
?? ????????PointRCNN的Neck網絡計算關鍵過程如下所示。
Neck Pointnet2_fp_neck feature propagation,輸入為backbone輸出的特征列表,反向逐層上采樣 FP4:輸入:1024x64、512x256->特征上采樣:1024x256->特征拼接:1536x256->特征對齊: (Conv2d(1536, 512, 1, 1)、Conv2d(512, 512)->輸出:512x256 FP3:輸入:512x256、256x1024->特征上采樣:512x1024->特征拼接:768x512->特征對齊(Conv2d(768, 512, 1, 1)、Conv2d(512, 512)->輸出:512x1024 FP2:輸入:512x1024、96x4096->特征上采樣:512x4096->特征拼接:608x4096->特征對齊:(Conv2d(608, 256, 1, 1)、Conv2d(256, 256)->輸出:256x4096 FP1:輸入:256x4096、1x16384->特征上采樣:256x16384 ->特征拼接:257x16384->特征對齊:(Conv2d(257, 128, 1, 1)、Conv2d(128, 128)->輸出:128x16384 特征提取網絡最終輸出: fp_xyz:16384x3,FP上采樣后的點云坐標 fp_features:128x16384,FP上采樣后的點云特征 #---extract_feats end????????PointRCNN的特征提取網絡的Backbone和Neck與PoinNet++完全一致,最終為點云中每個點提取到128維特征。特征提取網絡最終輸出由兩部分組成,即各點坐標fp_xyz(16384x3)和特征(128x16384)。
2.2 候選框生成rpn網絡
????????RPN網絡的目的是產生可能存在目標的三維候選框(proposal)。這也是PointRCNN核心所在。在此之前,目標檢測的候選框通常是由二維特征圖直接產生。這種方法是以特征圖上的點為中心,設置特定的尺寸和方向,進而產生候選框并提取其分類和回歸特征。PointRCNN則是以前景點為中心來直接提取各點的分類和候選框回歸特征。當某個點屬于前景點(目標點)時,它對應的候選框才更有可能成為真實的候選框。而基于特征圖的候選框是對所有位置進行候選框生成,不考慮對應位置是否包含目標信息。相比之下,PointRCNN生成候選框的方法更加高效,能夠獲得少量高質量(高召回率)的候選框。
????????RPN網絡包括rpn head和rpn loss兩部分,即候選框生成與損失函數計算。
????????rpn_head網絡根據每個點的特征初步生成候選框的分類和位置信息。經過特征提取層,每個點具有128維特征,經過 Linear(128, 256)、Linear(256, 256)、 Linear(256, C)等3層全連接層后維度為C。這里C等于3,即預測3個不同的類別。類別的數量可根據實際情況和需求自行調整。因此,類別cls_preds預測結果的維度為16384x3,3表示類別的數量。后續這3個維度的預測結果通過sigmoid函數直接轉為預測概率。
????????在PointRCNN原始論文中,候選框生成采用的時bin生成方法,即對原始空間尺寸重新按照bin的尺寸作為單位長度重新進行劃分,然后生成候選框。候選框通過將128維特征降到76個維度后與候選框坐標、尺寸、角度等8個維度進行一一對應。作者發現采用這些方法,模型在訓練過程中能夠更快收斂。
????????Mmdetection3d源碼中采用的方式比較常規,與大部分模型的候選框生成方式一樣,而沒有采用bin候選框生成方式。這也說明很多模型的大體思路和結構是一致的,功能相近的部分可以進行替換,這也是模型優化的思路之一。128維特征經過 Linear(128, 256)、Linear(256, 256)、 Linear(256, 8)等3層全連接層后維度為8,預測框bbox_preds預測結果的維度為16384x8。其中,0~2前三個維度表示預測框相對于真實框的中心偏差;3~5中間三個維度表示預測框相對于真實框的尺寸偏差;6~7最后兩個維度表示預測框的角度方向,即分別為角度的余弦和正弦。假設這8個維度變量分別為xt、yt、zt、dxt、dyt、dzt、cost、sint,對應的真實框坐標為xa、ya、za、dxa、dya、dza,對應的預測框坐標和角度為xg、yg、zg、dxg、dyg、dzg、rg。xa、ya、za為各個點自身的坐標。它們之間的關系定義如下:
????????rpn head網絡結構相關代碼如下:
bbox_preds, cls_preds = self.rpn_head(x) point_cls_preds = self.cls_layers(feat_cls).reshape(batch_size, -1, self._get_cls_out_channels()) self.cls_layer: Linear(128, 256) -> Linear(256, 256) -> Linear(256, 3) 16384x128 -> 16384x3 point_box_preds = self.reg_layers(feat_reg).reshape(batch_size, -1, self._get_reg_out_channels()) self.reg_layer: Linear(128, 256) -> Linear(256, 256) -> Linear(256, 8) 16384x128 -> 16384x8????????rpn輸出的損失包含bbox位置損失bbox_loss和語義損失semantic_loss兩部分。其中,bbox的真實值bbox_targets的維度與bbox_preds保持一致,16384x8。實驗測試可得到bbox_targets最后兩維的平方和為1,映證了角度是用余弦和正弦來表征的。bbox_loss對應的損失函數為SmoothL1Loss,輸入為bbox_preds、bbox_targets、box_loss_weights。box_loss_weights為每個點損失的權重,最終損失為各個點損失的加權求和。
????????語義損失semantic_loss的輸入為cls_preds分類結果、真實類別semantic_points_label、權重semantic_loss_weight,采用FocalLoss損失函數,并將計算結果除以實際前景點數量來進行平均。
? ? ? ? rpn 損失關鍵代碼解析如下所示。
rpn_loss = self.rpn_head.loss(bbox_preds=bbox_preds, cls_preds=cls_preds, points=points, gt_bboxes_3d=gt_bboxes_3d, gt_labels_3d=gt_labels_3d, img_metas=img_metas) #獲取真實標簽 targets = self.get_targets(points, gt_bboxes_3d, gt_labels_3d) (bbox_targets, mask_targets, positive_mask, negative_mask, box_loss_weights, point_targets) = targets # bbox loss SmoothL1Loss() bbox_loss = self.bbox_loss(bbox_preds, bbox_targets, box_loss_weights.unsqueeze(-1)) # Semantic_loss FocalLoss() semantic_points = cls_preds.reshape(-1, self.num_classes) semantic_loss = self.cls_loss(semantic_points, semantic_points_label.reshape(-1), semantic_loss_weight.reshape(-1)) semantic_loss /= positive_mask.float().sum() losses = dict(bbox_loss=bbox_loss, semantic_loss=semantic_loss) return losses2.3 bbox候選框再篩選
????????上面PointRCNN第一階段網絡結構初步計算了rpn過程所有預測框的位置損失,而我們更加關心比較可能回歸到真實框的候選框。因此,PointRCNN對候選框進行了進一步篩選,主要步驟如下:
????????(1)根據上述rpn_head網絡部分公式將bbox偏差xt、yt、zt、dxt、dyt、dzt、cost、sint,轉換為對應的預測框bbox3d坐標和角度xg、yg、zg、dxg、dyg、dzg、rg。預測框維度由8維變為7維,正弦和余弦由角度直接表征。
????????(2)分別統計各個候選框bbox3d中包含真實點的個數,篩選出含真實點的候選框。
????????(3)按照目標預測概率,選擇出概率較大的前9000個候選框。
????????(4)利用NMS(非極大值抑制)從9000個候選框中最多選擇出512個候選框。
? ? ?(5)返回512個候選框坐標位置和方向bbox_selected(512x7),及其對應的目標概率?score_selected(512x1)、目標類別label(512x1)和?目標分類cls_preds(512x3)。
????????通過以上步驟,最終篩選出的bbox_list或proposal_list包含了第(5)步中的四個組成部分。關鍵代碼解讀如下所示。
bbox_list = self.rpn_head.get_bboxes(points_cat, bbox_preds, cls_preds, img_metas) sem_scores = cls_preds.sigmoid() #目標概率 obj_scores = sem_scores.max(-1)[0] #目標類別 object_class = sem_scores.argmax(dim=-1) pooled_point_feats = self.point_roi_extractor(features, points, batch_size, rois) #將預測結果差值坐標轉換為實際坐標,由8個維度變為7個維度,轉換公式見rpn_head網絡部分。 bbox3d = self.bbox_coder.decode(bbox_preds[b], points[b, ..., :3], object_class[b]) bbox_selected, score_selected, labels, cls_preds_selected = self.class_agnostic_nms(obj_scores[b], sem_scores[b], bbox3d, points[b, ..., :3], input_metas[b]) #每個點所在候選框的序號,-1表示點不在任何預測框中,即被預測為背景點 box_idx = bbox.points_in_boxes(points) box_indices = box_idx.new_zeros([num_bbox + 1]) #背景點標簽由-1變為num_box,16384 box_idx[box_idx == -1] = num_bbox #每個位置表示第N個bbox之中包含的真實點個數 box_indices.scatter_add_(0, box_idx.long(), box_idx.new_ones(box_idx.shape)) box_indices = box_indices[:-1] #包含真實點的預測框的mask,應修改成box_indices > 0 nonempty_box_mask = box_indices >= 0 bbox = bbox[nonempty_box_mask] #按照目標概率,選出前topk個索引,topk=9000 obj_scores_nms, indices = torch.topk(obj_scores, k=topk) #對選擇的9000個候選框,經過NMS非極大值抑制后,并提取前nms_cfg.nms_post=512個候選框。 keep = nms_func(bbox_for_nms, obj_scores_nms, nms_cfg.iou_thr) keep = keep[:nms_cfg.nms_post] bbox_selected = bbox.tensor[indices][keep] score_selected = obj_scores_nms[keep] cls_preds = sem_scores_nms[keep] labels = torch.argmax(cls_preds, -1) #bbox_selected 512x7, score_selected 512x1, labels 512x1, cls_preds 512x3 return bbox_selected, score_selected, labels, cls_preds2.4 roi損失
????????roi的意義是感興趣區域,也就是上面篩選出的候選框。Roi損失roi_losses將根據篩選候選框特征來進行計算。由于目標在檢測場景中占據的比例有限,大部分被背景占據,所以在目標檢測過程中大部分候選框都是負樣本,框住的是背景。和二維目標檢測損失相似,計算roi損失需要選擇一定數量的正負樣本,并控制正負樣本的比例。針對選中的正負樣本,模型通常采用交叉熵損失來計算分類損失,但僅對正樣本進行位置回歸。
????????PointRCNN在計算roi損失的過程中主要包含下面三個步驟:
????????(1)選擇正負樣本。正負樣本總數為128。正樣本最多占50%,即正樣本數量最多為64。
????????(2)特征提取。選擇出的128個候選框區域作為roi再次進行特征提取。RPN階段提取的分類分數、特征(128)、目標深度(歸一化深度坐標)拼接成16384x130維度新特征,然后根據roi pool從roi區域選擇512個點,將點的坐標進一步拼接到特征。這樣每個選擇的roi樣本對應512個點,每個點維度為133,即總維度為128x133x512。針對新特征,通過PointNet SA結構和卷積,模型輸出了每個樣本的分類分數(128)和預測位置(128x7)。
????????(3)計算損失。Roi損失包含分類損失和回歸損失,其中回歸損失用位置損失和角點損失,即loss_cls、loss_bbox、loss_corner。
????????Roi損失計算函數如下所示。
roi_losses = self.roi_head.forward_train(rcnn_feats, img_metas, proposal_list, gt_bboxes_3d, gt_labels_3d)2.4.1?正負樣本選擇
????????正負樣本總體數量為128,即從上文篩選的512個候選框中進一步篩選出128個。正樣本最多占50%,即正樣本數量最多為64。如果不足正樣本不超過64個,以實際數量為主;如果數量超過64個,則隨機選出64個作為正樣本。總樣本數量128減去正樣本數量就是負樣本數量,可以看到負樣本數量至少占50%。預測框與真實框的IOU大于0.55時屬于正樣本,否則為負樣本。
????????正負樣本選擇的詳細代碼解析如下。正負樣本選擇函數返回結果為返回sample_results,主要包含采樣后的128個bboxes、正負樣本索引、正負樣本對應的bbox。
#選擇一定比例的正樣本和負樣本 sample_results = self._assign_and_sample(proposal_list, gt_bboxes_3d, gt_labels_3d) #按照類別逐一選擇正負樣本 gt_per_cls = (cur_gt_labels == i) pred_per_cls = (cur_labels_3d == i) #返回AssignResult(num_gts, assigned_gt_inds, max_overlaps, labels=assigned_labels) cur_assign_res = assigner.assign(cur_boxes.tensor[pred_per_cls], cur_gt_bboxes.tensor[gt_per_cls], gt_labels=cur_gt_labels[gt_per_cls]) #計算真實框和各個預測框的重疊比例 k x n overlaps = self.iou_calculator(gt_bboxes, bboxes) #gt_inds:每個bbox對應的真實框序號,負樣本為0,正樣本為對應真實框序號(從1開始),-1為非正非負樣本,max_overlaps為最大重疊IOU assign_result = self.assign_wrt_overlaps(overlaps, gt_labels) # for each anchor, the max iou of all gts, n max_overlaps, argmax_overlaps = overlaps.max(dim=0) # for each gt, the max iou of all proposals, k gt_max_overlaps, gt_argmax_overlaps = overlaps.max(dim=1) #與真實框重疊比例在0~0.55之間的屬于負樣本,標記為0 assigned_gt_inds[(max_overlaps >= 0) & (max_overlaps < self.neg_iou_thr)] = 0 # 3. assign positive: above positive IoU threshold pos_inds = max_overlaps >= self.pos_iou_thr 0.55 #正樣本為對應真實框序號(從1開始) assigned_gt_inds[pos_inds] = argmax_overlaps[pos_inds] + 1 #正樣本對應的目標類別,類別默認為-1,表示背景點 assigned_labels[pos_inds] = gt_labels[assigned_gt_inds[pos_inds] - 1] #返回真實框個數k、正負樣本判別結果n、最大重疊比例n、正樣本標簽 return AssignResult(num_gts, assigned_gt_inds, max_overlaps, labels=assigned_labels) # gt inds (1-based),當前類別在所有真實框中的序號,從1開始,即第幾個真實框屬于當前label gt_inds_arange_pad = gt_per_cls.nonzero(as_tuple=False).view(-1) + 1 # convert to 0~gt_num+2 for indices gt_inds_arange_pad += 1 # now 0 is bg, >1 is fg in batch_gt_indis,每個預測框匹配的真實框序號,從1開始,即第幾個真實框匹配當前預測框l,0表示負樣本,-1表示背景 batch_gt_indis[pred_per_cls] = gt_inds_arange_pad[cur_assign_res.gt_inds + 1] - 1 #預測框與真實框重疊的最大比例 batch_gt_labels[pred_per_cls] = cur_assign_res.labels sampling_result = self.bbox_sampler.sample(assign_result, cur_boxes.tensor, cur_gt_bboxes.tensor, cur_gt_labels) #正樣本最多保留128*0.5,即從上述的gt_inds選擇出大于0的部分;如果數量不超過64,則保留全部正樣本;如果數量超過64,則隨機選擇出64個正樣本。 num_expected_pos = int(self.num * self.pos_fraction) #總樣本數保持為128,減去正樣本數量即為負樣本數量 num_expected_neg = self.num - num_sampled_pos #負樣本從兩部分選擇,80%來源于IOU在0.1~0.55之間,剩余的從IOU在0~0.1中隨機選擇。 piece_neg_inds = torch.nonzero( (max_overlaps >= min_iou_thr)& (max_overlaps < max_iou_thr), as_tuple=False).view(-1) sampling_result = SamplingResult(pos_inds, neg_inds, bboxes, gt_bboxes, assign_result, gt_flags) return sampling_result2.4.2 特征提取
????????特征提取目的是為了對選擇的roi候選框進行更加精細的特征提取。RPN階段提取的分類分數、特征(128)、目標深度(歸一化深度坐標)拼接成16384x130維度新特征,然后根據roi pool從roi區域選擇512個點,將點的坐標進一步拼接到特征。這樣每個選擇的roi樣本對應512個點,每個點維度為133,即總維度為128x133x512。Roi pool的目的可以看作是對每個候選框選擇出固定數量的點,并且標記候選框是包含原始點云中的點。
????????利用坐標Canonical變換,roi的中心點坐標作為原點,并且將其傾斜角度旋轉到零。模型用3個PointNet SA模塊PointSAModule對roi的512個點進行特征提取,得到l_features = [128x128x512, 128x128x128, 128x256x32, 128x512x1],l_xyz = [128x512x3, 128x128x3, 128x32x3],對應的采樣點數分別為128、32、1。最后用一個點的特征來作為512個點的宏觀全局特征,即最終特征維度為128x512x1。對這128x512x1特征進行卷積操作,模型輸出了每個樣本的分類分數cls_score(128)和bbox_pred預測位置(128x7)。
? ? ? ? 關鍵過程代碼解析如下所示。
# concat the depth, semantic features and backbone features features = features.transpose(1, 2).contiguous() point_depths = points.norm(dim=2) / self.depth_normalizer - 0.5 features_list = [point_scores.unsqueeze(2), point_depths.unsqueeze(2), features] #拼接后的特征維度為1+1+128 = 130,即16384 x 130 features = torch.cat(features_list, dim=2) bbox_results = self._bbox_forward_train(features, points, sample_results) #rois為篩選的128個bbox,128x7 box_results = self._bbox_forward(features, points, batch_size, rois) pooled_point_feats = self.point_roi_extractor(features, points, batch_size, rois) #將坐標與現有特征拼接,特征維度由130改為133,然后從每個rois候選框選擇512個點,同時返回rois候選框是否空,即不包含前景點。因此,pooled_roi_feat維度為128x512x133,pooled_empty_flag維度為128,其中0表示不為空,1表示為空。 pooled_roi_feat, pooled_empty_flag = self.roi_layer(coordinate, feats, rois) # canonical transformation 正交變換 roi_center = rois[:, :, 0:3] #中心坐標作為原點 pooled_roi_feat[:, :, :, 0:3] -= roi_center.unsqueeze(dim=2) #對坐標進行旋轉,相當于讓bbox預測框的傾角旋轉到零。 pooled_roi_feat[:, :, 0:3] = rotation_3d_in_axis(pooled_roi_feat[:, :, 0:3], -(rois.view(-1, rois.shape[-1])[:, 6]), axis=2) #將為空的預測框特征置為零。 pooled_roi_feat[pooled_empty_flag.view(-1) > 0] = 0 #128x133x512 return pooled_roi_feat 特征提取 cls_score, bbox_pred = self.bbox_head(pooled_point_feats) #將特征133個維度的前5個單獨提取出來,再次提一遍特征。這5個維度的意義分別是3個坐標、目標概率和歸一化深度,128x5x512 xyz_input = input_data[..., 0:self.in_channels].transpose( 1, 2).unsqueeze(dim=3).contiguous().clone().detach() #卷積Conv2d(5, 128)、Conv2d(128, 128),128x128x512 xyz_features = self.xyz_up_layer(xyz_input) #rpn階段提取的128維特征,128x128x512 rpn_features = input_data[..., self.in_channels:].transpose(1, 2).unsqueeze(dim=3) #特征拼接融合,也可以改為加減乘除的融合方式,128x256x512 merged_features = torch.cat((xyz_features, rpn_features), dim=1) #卷積Conv2d(256, 128)降維,128x128x512 merged_features = self.merge_down_layer(merged_features) #相當于有512點,坐標三個維度,特征為128個維度 l_xyz, l_features = [input_data[..., 0:3].contiguous()], [merged_features.squeeze(dim=3)] #用3個SA模塊PointSAModule對這512個點進行特征提取,l_features = [128x128x512, 128x128x128, 128x256x32, 128x512x1],l_xyz = [128x512x3, 128x128x3, 128x32x3] li_xyz, li_features, cur_indices = self.SA_modules[i](l_xyz[i], l_features[i]) #最后用一個點的特征來作為512個點的宏觀全局特征。 #128x512x1 shared_features = l_features[-1] x_cls = shared_features x_reg = shared_features #Conv1d(512, 256)、Conv2d(256, 256),128x256x1 x_cls = self.cls_convs(x_cls) #Conv1d(256, 1),128x1x1 rcnn_cls = self.conv_cls(x_cls) #Conv1d(512, 256)、Conv2d(256, 256),128x256x1 x_reg = self.reg_convs(x_reg) #Conv1d(256, 7),128x256x1 rcnn_reg = self.conv_reg(x_reg) rcnn_cls = rcnn_cls.transpose(1, 2).contiguous().squeeze(dim=1) rcnn_reg = rcnn_reg.transpose(1, 2).contiguous().squeeze(dim=1) #bbox_results:cls_score, bbox_pred,128,128x7 return rcnn_cls, rcnn_reg2.4.3 roi loss
????????roi損失包含分類損失和回歸損失,其中回歸損失用位置損失和角點損失,即loss_cls、loss_bbox、loss_corner。在計算分類損失loss時,roi的真實標簽label根據iou重疊比列大大小轉換為0~1之間的數值,0表示iou小于0.25的負樣本,1表示iou大于0.7的正樣本。Roi分類損失loss_cls的損失函數為CrossEntropyLoss,bbox位置損失loss_bbox損失函數為SmoothL1Loss,焦點損失loss_corner函數為 HuberLoss。
#分類正樣本iou>0.7 cls_pos_mask = ious > cfg.cls_pos_thr #分類負樣本iou<0.25 cls_neg_mask = ious < cfg.cls_neg_thr #iou在0.25~0.7之間的樣本mask,困難樣本,hard sample interval_mask = (cls_pos_mask == 0) & (cls_neg_mask == 0) # iou regression target,正樣本label為1,否則label為0 label = (cls_pos_mask > 0).float() #對困難樣本label歸一化至0~1,相當于中間標簽,那么正樣本為1,負樣本為0,也可以理解為分類概率。 label[interval_mask] = (ious[interval_mask] - cfg.cls_neg_thr) / (cfg.cls_pos_thr - cfg.cls_neg_thr) # label weights,每個標簽賦予了相同的權重,128個 label_weights = (label >= 0).float() # box regression target,僅對bbox正樣本進行回歸 reg_mask = pos_bboxes.new_zeros(ious.size(0)).long() reg_mask[0:pos_gt_bboxes.size(0)] = 1 #僅對bbox正樣本進行回歸 bbox_weights = (reg_mask > 0).float() #label 128 0~1,bbox_targets m,pos_gt_bboxes m,reg_mask 128, label_weights 128, bbox_weights 128 (label, bbox_targets, pos_gt_bboxes, reg_mask, label_weights, bbox_weights) = targets #loss_cls、loss_bbox、loss_corner loss_bbox = self.bbox_head.loss(bbox_results['cls_score'], bbox_results['bbox_pred'], rois, *bbox_targets) # calculate class loss CrossEntropyLoss(avg_non_ignore=False) cls_flat = cls_score.view(-1) loss_cls = self.loss_cls(cls_flat, labels, label_weights) #SmoothL1Loss() loss_bbox = self.loss_bbox(pos_bbox_pred.unsqueeze(dim=0), bbox_targets.unsqueeze(dim=0).detach(), bbox_weights_flat.unsqueeze(dim=0)) # calculate corner loss huber loss loss_corner = self.get_corner_loss_lidar(pred_boxes3d, pos_gt_bboxes)2.5 總體損失函數?
????????總體損失包括rpn損失和roi損失。rpn位置損失函數bbox_loss對應的損失函數為SmoothL1Loss。rpn語義損失semantic_loss采用FocalLoss損失函數,并將計算結果除以實際前景點數量來進行平均。roi分類損失loss_cls的損失函數為CrossEntropyLoss,bbox位置損失loss_bbox損失函數為SmoothL1Loss,焦點損失loss_corner函數為 HuberLoss。
????????總體損失類型如下所示。
bbox_loss:SmoothL1Loss semantic_loss:FocalLoss loss_cls:CrossEntropyLoss loss_bbox:SmoothL1Loss loss_corner:HuberLoss2.6 頂層結構
????????頂層結構主要包含以下三部分:
? ? ? ? (1)特征提取:self.extract_feat,得到128x16384特征,見2.5節。
? ? ? ? (2)檢測頭:見2.6節。
? ? ? ? (3)損失函數:見2.7節。
def forward_train(self, points, img_metas, gt_bboxes_3d, gt_labels_3d):losses = dict()points_cat = torch.stack(points)x = self.extract_feat(points_cat)# features for rcnnbackbone_feats = x['fp_features'].clone()backbone_xyz = x['fp_xyz'].clone()rcnn_feats = {'features': backbone_feats, 'points': backbone_xyz}bbox_preds, cls_preds = self.rpn_head(x)rpn_loss = self.rpn_head.loss(bbox_preds=bbox_preds,cls_preds=cls_preds,points=points,gt_bboxes_3d=gt_bboxes_3d,gt_labels_3d=gt_labels_3d,img_metas=img_metas)losses.update(rpn_loss)bbox_list = self.rpn_head.get_bboxes(points_cat, bbox_preds, cls_preds,img_metas)proposal_list = [dict(boxes_3d=bboxes,scores_3d=scores,labels_3d=labels,cls_preds=preds_cls)for bboxes, scores, labels, preds_cls in bbox_list]rcnn_feats.update({'points_cls_preds': cls_preds})roi_losses = self.roi_head.forward_train(rcnn_feats, img_metas,proposal_list, gt_bboxes_3d,gt_labels_3d)losses.update(roi_losses)return losses#extract_feat模塊 def extract_feat(self, points):x = self.backbone(points)if self.with_neck:x = self.neck(x)3 訓練命令
python tools/train.py configs/point_rcnn/point_rcnn_2x8_kitti-3d-3classes.py4 運行結果
?
?5?【python三維深度學習】python三維點云從基礎到深度學習_Coding的葉子的博客-CSDN博客_python點云分割
更多三維、二維感知算法和金融量化分析算法請關注“樂樂感知學堂”微信公眾號,并將持續進行更新。
總結
以上是生活随笔為你收集整理的【三维目标检测】PointRCNN(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: KSZ9897 switch 交换机
- 下一篇: PMP模拟考试系统-pmp模拟考试题库