3D目标检测实用技巧(二)- 实现点云(or 体素)向图像平面的投影并可视化

一、引言

受Focals Conv的启发,该论文中通过将点云投影到图片中清晰展现出点云学习后的情况:

本次实现的是体素向图像投影并显示,实现出来的效果如下:

二、 实现细节

1、体素投影到图像坐标系

这里我们参考的是VirConv的投影函数:

def index2uv(batch_dict, indices, batch_size, calib, stride, x_trans_train, trans_param):
    """
    convert the 3D voxel indices to image pixel indices.将3D的voxel索引转换成图像像素
    """
    # 变换维度的索引
    inv_idx = torch.Tensor([2, 1, 0]).long().cuda()
    batch_index = indices[:, 0]
    new_uv = indices.new(size=(indices.shape[0], 3))
    depth = indices.new(size=(indices.shape[0], 1)).float()
    for b_i in range(batch_size):
        # 找到对应的batch
        cur_in = indices[indices[:, 0] == b_i]
        # 转换成voxel3d特征
        cur_pts = index2points(cur_in, stride=stride)
        if trans_param is not None:
            # 反增强
            transed = x_trans_train.backward_with_param({'points': cur_pts[:, 1:4],
                                                         'transform_param': trans_param[b_i]})
            cur_pts = transed['points']  # .cpu().numpy()
        else:
            cur_pts = cur_pts[:, 1:4]  # .cpu().numpy() 除了batch以外都取出来
        # 投影到相机坐标系
        pts_rect = calib[b_i].lidar_to_rect_cuda(cur_pts[:, 0:3])
        # 投影到图像坐标系
        pts_img, pts_rect_depth = calib[b_i].rect_to_img_cuda(pts_rect)
        # 转换成整型
        pts_img = pts_img.int()
        # pts_img = torch.from_numpy(pts_img).to(new_uv.device)
        # 找到对应的batch,把他的像素坐标取出来
        new_uv[indices[:, 0] == b_i, 1:3] = pts_img
        # pts_rect_depth = torch.from_numpy(pts_rect_depth).to(new_uv.device).float()
        # 深度信息
        depth[indices[:, 0] == b_i, 0] = pts_rect_depth[:]

    # batch对应
    new_uv[:, 0] = indices[:, 0]
    # 控制一下像素坐标
    # new_uv[:, 1] = torch.clamp(new_uv[:, 1], min=0, max=1400 - 1) // stride
    # new_uv[:, 2] = torch.clamp(new_uv[:, 2], min=0, max=600 - 1) // stride

    return new_uv, depth

以上代码具体可参考VirConv源码,这里不赘述。

调用:

# 读取体素坐标
pointxyz = sparse_t.indices
# 传入参数:从左向右依次是batch_dict总字典,pointxyz体素坐标,batchsize大小,calib标定矩阵,stride步长,TED独有的全局变换,trans_param增强参数
pc_xy, depth_pc = index2uv(batch_dict, pointxyz, batch_size, calib, 1, x_trans_train,
                               trans_param)

以上是我目前能找的最有效,最简单的体素投影方法。

2、读取图像并根据深度来画体素

# 根据batch id 依次取出数据    
for batch_id in range(batch_size):
        # image的路径,这里我是放到batch_dict提前存好,所以这里可以直接改为路径
        img_path = str(batch_dict['image_path'][batch_id]).split('/')[-1]
        # 像素大小×255
        pcimg_batch = pcimg[batch_id] * 255
        # 根目录
        root_path = '/home/xd/xyy/VirConv-master/voxel_vision/vision_pcimg/' + img_path + '/'
        # 输出路径
        output_path = root_path + name + '_' + str(batch_id) + '.jpg'
        # 创建路径
        if not os.path.exists(root_path):
            os.makedirs(root_path)
        # 根据batch_id读取出当前batch下的体素坐标
        pc_xyz_indices = pc_xyz[pc_xyz[:, 0] == batch_id][:, 1:]
        for idx, pc in enumerate(pc_xyz_indices):
            # 根据深度确定颜色深度
            color = int((pc[2] / depth_max) * 255)
            # rectangle函数的主要作用是给指定的区域加上长宽分别为2的矩形边框
            if isinstance(pcimg_batch, torch.Tensor):
                pcimg_batch = pcimg_batch.cpu().numpy()
            if pc[0] < W and pc[1] < H:
                # 画体素
                cv2.rectangle(pcimg_batch, (int(pc[0] - 1), int(pc[1] - 1)), (int(pc[0] + 1), int(pc[1] + 1)),
                              (0, 0, color), -1)
        cv2.imwrite(output_path, pcimg_batch)

3、完整代码

def vision_pc_img(sparse_t, pcimg, name, batch_dict, batch_size, calib, x_trans_train, trans_param, H=376, W=1241):
    pointxyz = sparse_t.indices

    pc_xy, depth_pc = index2uv(batch_dict, pointxyz, batch_size, calib, 1, x_trans_train,
                               trans_param)
    # 保存pc_xy
    # for batch_id in range(batch_size):
    #     img_path = str(batch_dict['image_path'][batch_id]).split('/')[-1]
    #     name_2d = img_path + '/' + name
    #     save_2d(pc_xy[pc_xy[:,0]==batch_id], name_2d)
    # vision(sparse_t)

    pc_xyz = torch.cat((pc_xy, depth_pc), dim=1)
    depth_max, _ = torch.max(depth_pc.squeeze(1), dim=0)

    for batch_id in range(batch_size):
        img_path = str(batch_dict['image_path'][batch_id]).split('/')[-1]
        pcimg_batch = pcimg[batch_id] * 255
        root_path = '/home/xd/xyy/VirConv-master/voxel_vision/vision_pcimg/' + img_path + '/'
        output_path = root_path + name + '_' + str(batch_id) + '.jpg'
        if not os.path.exists(root_path):
            os.makedirs(root_path)
        pc_xyz_indices = pc_xyz[pc_xyz[:, 0] == batch_id][:, 1:]
        for idx, pc in enumerate(pc_xyz_indices):
            color = int((pc[2] / depth_max) * 255)
            # rectangle函数的主要作用是给指定的区域加上长宽分别为2的矩形边框
            if isinstance(pcimg_batch, torch.Tensor):
                pcimg_batch = pcimg_batch.cpu().numpy()
            if pc[0] < W and pc[1] < H:
                cv2.rectangle(pcimg_batch, (int(pc[0] - 1), int(pc[1] - 1)), (int(pc[0] + 1), int(pc[1] + 1)),
                              (0, 0, color), -1)
        cv2.imwrite(output_path, pcimg_batch)

结合多传感设备以实现高级的感知能力是自动驾驶汽车导航的关键要求。传感器融合用于获取有关周围环境的丰富信息。摄像头和激光雷达传感器的融合可获取精确的范围信息,该信息可以投影到可视图像数据上。这样可以对场景有一个高层次的认识,可以用来启用基于上下文的算法,例如避免碰撞更好的导航。组合这些传感器时的主要挑战是将数据对齐到一个公共域中。由于照相机的内部校准中的误差,照相机与激光雷达之间的外部校准以及平台运动导致的误差,因此这可能很困难。在本文中,我们研究了为激光雷达传感器提供运动校正所需的算法。由于不可能完全消除由于激光雷达的测量值投影到同一里程计框架中而导致的误差,因此,在融合两个不同的传感器时,必须考虑该投影的不确定性。这项工作提出了一个新的框架,用于预测投影到移动平台图像帧(2D)中的激光雷达测量值(3D)的不确定性。所提出的方法将运动校正的不确定性与外部和内部校准中的误差所导致的不确定性相融合。通过合投影误差的主要成分,可以更好地表示估计过程的不确定性。我们的运动校正算法和提出的扩展不确定性模型的实验结果通过在电动汽车上收集的真实数据进行了演示,该电动汽车配备了可覆盖180度视野的广角摄像头和16线扫描激光雷达。
### MATLAB 中将激光雷达点云投影图像 在MATLAB环境中,`projectLidarPointsOnImage` 函数用于将来自激光雷达传感器的三维点云数据投影图像坐标系中。此过程涉及到了解摄像机内部参数以及两者之间的转换关系矩阵。 ```matlab % 定义输入变量 ptCloudIn = ... % 输入点云对象 intrinsics = cameraIntrinsics(...); % 摄像头内参模型实例化 tform = rigid3d(...); % 刚变换定义 lidar 和摄像头间的相对位姿 imPts = projectLidarPointsOnImage(ptCloudIn, intrinsics, tform); ``` 这段代码实现了基于给定的相机内外部参数,把原始 Lidar 数据集中的每一个空间位置映射成对应的图片平面内的坐标点集合 `imPts`[^1]。 ### Python 实现带色彩渲染效果的点云图像投影 对于希望利用Python来完成相同任务的情况,则可以采用如下方法: ```python import numpy as np from matplotlib import cm import open3d as o3d def colorize_depth(depth_value): """根据深度值着色""" normalized = (depth_value - depth_value.min()) / \ (depth_value.max() - depth_value.min()) colors = cm.jet(normalized)[:,:3] return colors def project_points_to_image(points_3d, K_matrix, R_matrix, T_vector, img_shape=(480, 640)): """ :param points_3d: N*3 的numpy数组代表世界坐标的点 :param K_matrix: 相机内参矩阵 :param R_matrix: 旋转矩阵 :param T_vector: 平移向量 :return projected_pixels: 投影后的像坐标列表 """ # 转换为齐次坐标应用仿射变换 ones_col = np.ones((len(points_3d), 1)) homogenous_coords = np.hstack([points_3d, ones_col]) extrinsic_mat = np.vstack([ np.hstack([R_matrix, T_vector.reshape(-1, 1)]), [[0., 0., 0., 1.]] ]) transformed_homog = K_matrix @ extrinsic_mat[:3,:] @ homogenous_coords.T # 归一化得到最终像坐标 pixel_coords = transformed_homog[:2]/transformed_homog[2] valid_mask = ( (pixel_coords[0]>=0) & (pixel_coords[0]<img_shape[1]) & (pixel_coords[1]>=0) & (pixel_coords[1]<img_shape[0]) ) filtered_pixel_coords = pixel_coords[:,valid_mask].T.astype(int) depths = points_3d[valid_mask][:,2] colored_depths = colorize_depth(depths) output_img = np.zeros((*img_shape[::-1], 3)) for idx, coord in enumerate(filtered_pixel_coords): output_img[coord[1], coord[0]] = colored_depths[idx]*255 return output_img ``` 上述脚本首先构建了一个辅助函数用来依据距离远近赋予不同颜色;接着定义了核心逻辑——即如何计算实际物理空间里的某一点对应于目标视图里具的哪个像位置,考虑有效范围过滤掉超出边界的点。最后按照这些信息更新空白画布上的相应区域形成彩色视觉呈现的结果[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值