PointRCNN代码里的那些事儿

 

  • 优化点

(1)16384:points点数

(2)64/128:roi个数

(3)回归loss中bin数量的设置

(4)cfg中参数:RCNN:SIZE_RES_ON_ROI设置为True

(5)Kitti_rcnn_dataset中:pts_near_flag = pts_depth < 40.0 # 此处数值的设置

(6)尝试开启rgb,iou-branch等配置

 

 

  • 回归loss计算:

不直接回归location,而是把location回归问题转成分类问题,x方向6个bin,z方向6个bin,y方向1个bin,yaw角9个bin,一个bin10°

rcnn_reg 输出为 batchsize*46,这46维 = 6_xbin_crossentropy_loss + 6_zbin_crossentropy_loss + 6_xbin_l1_loss + 6_zbin_l1_loss + 1_ybin_crossentropy_loss + 9_yaw_entropy_loss + 9_yaw_l1_loss + 3_size_l1_loss.

 

  • 如果是eval,打开 kitti-rcnn-dataset.py中object-filter:增加的距离筛选
  • eval中calculate_iou_partly函数的输入参数:gt_annos 与 dt_annos位置的混淆, 以及return时total_gt_num, total_dt_num的顺序也混淆了。
  • eval中fused_compute_statistics()函数中 overlap = overlaps[ 这里gt和dt的顺序也搞反了]。

PointRCNN代码里的那些事儿_第1张图片

  • evaluate中:get-label-annos函数参数一定要加上val-image-ids参数,不然很可能dt-annos与gt-annos不对应。
def evaluate(label_path,
             result_path,
             label_split_file,
             current_class = 0,
             coco = False,
             score_thresh = -1):
    val_image_ids = _read_imageset_file(label_split_file)
    dt_annos = kitti.get_label_annos(result_path, val_image_ids)
  • eval中clean-data函数 增加距离筛选,
    MAX_DISTANCE = [60] #---add 60m filter---
   
    for i in range(num_gt): 
        distance = gt_anno["location"][i][2] #---------
        if ((gt_anno["occluded"][i] > MAX_OCCLUSION[difficulty])
                or (gt_anno["truncated"][i] > MAX_TRUNCATION[difficulty])
                or (height <= MIN_HEIGHT[difficulty]) or distance > MAX_DISTANCE[0]):
  • 旋转向量——>旋转矩阵:

rvec 为3*1矩阵,则旋转矩阵RT为:

import cv2

RT, _ = cv2.Rodrigues(rvec)

 

  • 旋转矩阵——>欧拉角,lidar坐标系:x-前,y-左,z-上,转到camera坐标系:x'-右,y'-下,z'-前。即绕z轴旋转,此时x轴--->x'轴经过的角度yaw,求此yaw:

设旋转矩阵为RT=3*3矩阵,则:

该矩阵中R[0][0]数值的正负决定:旋转的角度是否超过90度,+:表示不超过90度,-:表示超过90度

该矩阵中R[2][0]数值的正负决定:旋转的方向是顺时针(+),还是逆时针(-)

pi = 3.141592653589793
if RT[0, 0] > 0:  #R[0,0] +/- :decide the rotation degree is larger than pi or not
    theta = math.atan(RT[2, 0] / RT[0, 0])
elif RT[2, 0] > 0: #R[2,0] +/- :decide ratate towards left(-) or right(+)
    theta = math.atan(RT[2, 0] / RT[0, 0]) + pi
else:
    theta = math.atan(RT[2, 0] / RT[0, 0]) - pi

参考:

https://blog.csdn.net/reasonyuanrobot/article/details/89969676

https://blog.csdn.net/loongkingwhat/article/details/82765388

https://blog.csdn.net/sinolover/article/details/90671784

 

  • 设目标a在lidar坐标系的yaw角(与x轴夹角,顺时针为-,逆时针为+,区间范围【-pi,pi】),求此目标在新坐标系Camera中的yaw_new角(顺时针为+,逆时针为-,同kitti)为多少?
if yaw_wy + theta > pi:
    yaw_new = -(yaw_wy + theta) + 2*pi
elif yaw_wy + theta < -pi:
    yaw_new = -(yaw_wy + theta) - 2*pi
else:
    yaw_new = -(yaw_wy + theta)

yaw = str(round(yaw_new, 2))

 

  • Kitti数据3Dbox的center是3dbox的底面中心点,切换自己数据的时候要注意涉及该位置相关代码的修改:eg

kitti_rcnn_dataset中generate_rpn_training_labels函数:

#center3d[1] -= gt_boxes3d[k][3] / 2  该位置注销

kitti_utils中boxes3d_to_corners3d函数:

    #y_corners[:, 4:8] = -h.reshape(boxes_num, 1).repeat(4, axis = 1)  # (N, 8) 注释掉改为以下
    y_corners[:, 0:4] = (h/2).reshape(boxes_num, 1).repeat(4, axis = 1)  # (N, 8)
    y_corners[:, 4:8] = (-h/2).reshape(boxes_num, 1).repeat(4, axis = 1)  # (N, 8)

proposal_layer中:

#proposals[:, 1] += proposals[:, 3] / 2  # set y as the center of bottom 注释掉

 

  • data_augmentation 数据增强中,在对数据样本进行rotation时候,会用到label中的alpha参数,之前模型yaw角检测有问题,就是因为样本rotation之后,新的yaw角yaw_new的获取是通过alpha得来,而原来alpha都是标注了-1,因此导致数据增强后的样本的新yaw角yaw_new与实际不再相符,故推理时候得到的检测结果存在较大的yaw问题。

 

  • eval.py 中fused_compute_statistics函数中:

overlap = overlaps[gt_num: gt_num + gt_nums[i], dt_num:dt_num + dt_nums[i]]  : gt_num 和 dt_num的顺序要跟前面overlaps的顺序对应起来,先gt后dt

 

  • 两阶段网络合并类别,单类检测改成多类:例如检测类别为3类;每类一个anchor,

RPN的分类部分:

(1)如果计算loss使用交叉熵crossentropy:   

rpn_cls的输出维度为 [n, 3*2],然后reshape到[n, 3, 2](其中最后两维为背景和目标两类),和cls_label=[0,1,2,3....] (其中0代表背景,1,2,3代表类别)进行cross-entropy交叉熵计算分类损失cls-loss。

(2)如果计算loss使用BCE或者focal-loss:

rpn_cls的输出维度为[n,3](其中3为1,2,3目标类别,不包括背景),cls_label = [n,3] ,表示方式维度同rpn_cls,(其中 每一个类别都由0或者1表示,0背景,1目标),进行binary-cross-entropy的loss计算。其中rpn_cls的输出要先经过softmax,not sigmoid。如果是crossentropyloss则不用。

RPN的回归部分:

rpn_reg的输出维度为[n, 76*3],然后reshape到[n,3,76],reg_label为[n, 7],rpn_rep要先根据label的类别选择anchor,定好anchor之后,rpn_reg的维度就变为[n,76],然后进行decoder解析成[n,7]的维度,再和reg_label计算回归loss。回归loss通常为l1loss,由于point-rcnn的位置回归采用了bin的方式,将回归计算转为了分类,所以里面计算了分类+回归两次loss相加。x,y,z,yaw,都是如此,size只用了L1,iou不需要。

RCNN的分类和回归部分,同RPN一致。

 

  • rcnn模块中的特征维度构成:
class RCNNNet(nn.Module):
    def __init__(self, num_classes, input_channels=0, use_xyz=True):
        super().__init__()

        self.SA_modules = nn.ModuleList()
        channel_in = input_channels

        if cfg.RCNN.USE_RPN_FEATURES: #True
            self.rcnn_input_channel = 3 + int(cfg.RCNN.USE_INTENSITY) + int(cfg.RCNN.USE_MASK)+3 + int(cfg.RCNN.USE_DEPTH) #USE_INTENSITY False,USE_MASK:True, USE_DEPTH:True
            self.xyz_up_layer = pt_utils.SharedMLP([self.rcnn_input_channel] + cfg.RCNN.XYZ_UP_LAYER,
                                                   bn=cfg.RCNN.USE_BN) #MLP共享权值网络提取特征

这里面的self.rcnn_input_channel的维度=3(x,y,z)+(use_intensity=1) + (use_mask= num_class目标类别个数) + (use_depth=0),要注意use_mask的地方。一个类别对应一个mask。

 

  • anchor的生成

ProposalLayer中和train_functions中都设计anchor的生成,因为reg回归部分只是回归位置的偏移量,所以依靠anchor才能生成roi。

设anchor=[[h_car,w_car,l_car],[h_ped,w_ped,l_ped],[h_cyc,w_cyc,l_cyc]]

anchor要在所有点云的位置都要进行生成,三个anchor是生成 [n*car, n*ped,n*cyc]还是生成[n*(car,ped,cyc)]要根据rpn的输出来定。例如rpn的cls输出为 [n, 3*2]-->[n,3, 2]的维度,这个中间的3里面其实是car,ped和cyc,一个位置3个类别,所以anchor的生成就是torch.repeat_internal 而不用torch.repeat,因为前者是生成的[n*(car,ped,cyc)]。

之前出现能检测到车不能检测到人的原因是在于anchor生成的时候,会在每个点的位置生成anchor,然后按照这些anchor的score进行排序,通过NMS进行anchor过滤。车和人的anchor要分开进行生成,不然同一个位置生成三个类别的anchor,车anchor的score比较大,会通过NMS把其他类别anchor覆盖掉,因此,要按类别分别进行roi生成。

 

 

 

 

 

 

你可能感兴趣的:(3D目标检测,3D目标检测,PointRCNN,EPNet)