带你玩转 3D 检测和分割 (三):有趣的可视化(上)

简介: 在 MMDetection3D 的官方可视化文档中,我们已经提供了如何进行可视化的一些指令和效果,这篇文章我们将从代码层级介绍可视化的细节过程,带大家一探究竟。

小伙伴们好呀,3D 检测和分割系列文章继续更新啦~在第一篇文章中,我们带领大家了解了整个框架的大致流程。第二篇文章我们给大家解析了 MMDetection3D 中的坐标系和核心组件 Box,今天我们将带大家看看 3D 场景中的可视化组件 Visualizer,如何在多个模态数据上轻松可视化并且自由切换?为什么在可视化的时候经常出现一些莫名其妙的问题?

640.gif

话不多说,接下来就让我们进入今天的正题吧~


在 MMDetection3D 的官方可视化文档中,我们已经提供了如何进行可视化的一些指令和效果,这篇文章我们将从代码层级介绍可视化的细节过程,带大家一探究竟。


640.png

可视化代码解析



在线可视化 Visualizer


MMDetection3D 基于 Open3D 构建了一个在线 Visualizer,用于在有 GUI 界面的情况下提供实时可视化结果,Open3D 提供了非常丰富的功能。相关代码位于 mmdet3d/core/visualizer/open3d_vis.py 。

640.png

MMDetection3D 目前只使用了 Open3D 的部分 API 进行可视化,同时也非常容易实现可视化功能的扩展,目前 Visualizer 支持绘制 3D 框的 add_bboxes,绘制点云分类结果的 add_seg_mask,以及统一的 show 显示方法。在这一节中我们不具体介绍 Open3D 的各个 API 的具体作用,而是带大家来看看如何使用 Open3D 的 API 完成需求,包括实现自己的自定义的可视化需求。

#----------------  mmdet3d/core/visualizer/open3d_vis.py ----------------#
class Visualizer(object):
    r"""Online visualizer implemented with Open3d."""
    def __init__(self,
                 points,
                 bbox3d=None,
                 save_path=None,
                 points_size=2,
                 point_color=(0.5, 0.5, 0.5),
                 bbox_color=(0, 1, 0),
                 points_in_box_color=(1, 0, 0),
                 rot_axis=2,
                 center_mode='lidar_bottom',
                 mode='xyz'):
        super(Visualizer, self).__init__()
        assert 0 <= rot_axis <= 2
        # 调用 Open3D 的 API 来初始化 visualizer
        self.o3d_visualizer = o3d.visualization.Visualizer()
        self.o3d_visualizer.create_window()
        # 创建坐标系帧
        mesh_frame = geometry.TriangleMesh.create_coordinate_frame(
            size=1, origin=[0, 0, 0])
        self.o3d_visualizer.add_geometry(mesh_frame)
        # 设定点云尺寸
        self.points_size = points_size
        # 设定点云颜色
        self.point_color = point_color
        # 设定 3D 框颜色
        self.bbox_color = bbox_color
        # 设定 3D 框内点云的颜色
        self.points_in_box_color = points_in_box_color
        self.rot_axis = rot_axis
        self.center_mode = center_mode
        self.mode = mode
        self.seg_num = 0
        # 绘制点云
        if points is not None:
            self.pcd, self.points_colors = _draw_points(
                points, self.o3d_visualizer, points_size, point_color, mode)
    def add_bboxes(self, bbox3d, bbox_color=None, points_in_box_color=None):
        """Add bounding box to visualizer."""
        if bbox_color is None:
            bbox_color = self.bbox_color
        if points_in_box_color is None:
            points_in_box_color = self.points_in_box_color
        _draw_bboxes(bbox3d, self.o3d_visualizer, self.points_colors, self.pcd,
                     bbox_color, points_in_box_color, self.rot_axis,
                     self.center_mode, self.mode)
    def add_seg_mask(self, seg_mask_colors):
        """Add segmentation mask to visualizer via per-point colorization."""
        self.seg_num += 1
        offset = (np.array(self.pcd.points).max(0) -
                  np.array(self.pcd.points).min(0))[0] * 1.2 * self.seg_num
        mesh_frame = geometry.TriangleMesh.create_coordinate_frame(
            size=1, origin=[offset, 0, 0])  # create coordinate frame for seg
        self.o3d_visualizer.add_geometry(mesh_frame)
        seg_points = copy.deepcopy(seg_mask_colors)
        seg_points[:, 0] += offset
        _draw_points(
            seg_points, self.o3d_visualizer, self.points_size, mode='xyzrgb')
    def show(self, save_path=None):
        """Visualize the points cloud."""
        self.o3d_visualizer.run()
        if save_path is not None:
            self.o3d_visualizer.capture_screen_image(save_path)
        self.o3d_visualizer.destroy_window()
        return

如上述代码所示,在检测任务中,关于 3D 框的绘制主要是通过调用 add_bboxes 方法,我们可以在绘制 3D 框的时候,通过 bbox_color 和 points_in_box_color 分别设定框的颜色以及包含在框内的点云的颜色,而其最终调用的还是 _draw_bboxes 方法。而 add_seg_masks 则是用来绘制分割结果,其中传入的 seg_mask_colors 是带颜色(rgb)信息的点云。需要注意的是,我们在绘制点云分割结果的时候,不会在原点云上进行颜色的更新,因为如果同时在原点云上绘制预测结果和真值标签往往会有重叠现象,所以我们对每一个分割结果的点云图都会沿 x 轴设置较大的偏移量,单个场景会生成多个分割结果图,而最终对每个分割结果图都会调用 _draw_points 方法进行绘制。当完成所有的绘制之后,就可以调用 show 方法启动 Visualizer,显示场景绘制结果。除此以外,我们也可以很轻松地通过给 Visualizer 添加方法实现自己的需求。


我们可以先来看看如何绘制点云:

#----------------  mmdet3d/core/visualizer/open3d_vis.py ----------------#
import open3d as o3d
from open3d import geometry
def _draw_points(points,
                 vis,
                 points_size=2,
                 point_color=(0.5, 0.5, 0.5),
                 mode='xyz'):
    # 获取 Open3D Visualizer 的渲染设置,更改点云尺寸
    vis.get_render_option().point_size = points_size 
    if isinstance(points, torch.Tensor):
        points = points.cpu().numpy()
    points = points.copy()
    # 构建 Open3D 中提供的 gemoetry 点云类
    pcd = geometry.PointCloud()
    # 如果传入的点云 points 只包含位置信息 xyz
    # 根据指定的 point_color 为每个点云赋予相同的颜色
    if mode == 'xyz':
        pcd.points = o3d.utility.Vector3dVector(points[:, :3])
        points_colors = np.tile(np.array(point_color), (points.shape[0], 1))
    # 如果传入的点云 points 本身还包含颜色信息(通常是分类预测结果或者标签)
    # 直接从 points 获取点云的颜色信息
    elif mode == 'xyzrgb':
        pcd.points = o3d.utility.Vector3dVector(points[:, :3])
        points_colors = points[:, 3:6]
        # 将颜色归一化到 [0, 1] 用于 Open3D 绘制
        if not ((points_colors >= 0.0) & (points_colors <= 1.0)).all():
            points_colors /= 255.0
    else:
        raise NotImplementedError
    # 为点云着色
    pcd.colors = o3d.utility.Vector3dVector(points_colors)
    # 将点云加入到 Visualizer 中
    vis.add_geometry(pcd)
    return pcd, points_colors

以上就是绘制点云的过程,当我们了解了如何绘制点云后,我们就可以实现自己的需求了,比如有社区用户希望能够给点云的强度进行染色,当然我们可以直接加在 _draw_points 方法内,但是为了演示更清晰,这里我们可以在 Visiualizer 内实现一个 render_points_intensity 方法做演示:

#----------------  mmdet3d/core/visualizer/open3d_vis.py ----------------#
import open3d as o3d
from open3d import geometry
def render_points_intensity(self, points, vis):
    # 假设传入的 points 包含 x-y-z-intensity 四个维度
    # 自定义一个 colormap
    colormap = np.array([[128, 130, 120],
                         [235, 0, 205],
                         [0, 215, 0],
                         [235, 155, 0]]) / 255.0
    points = points.copy()
    # 构建 Open3D 中提供的 gemoetry 点云类
    pcd = geometry.PointCloud()
    pcd.points = o3d.utility.Vector3dVector(points[:, :3])
    points_intensity = points[:, 3]  # intensity
    points_colors = [colormap[int(points_intensity[i]) % colormap.shape[0]] for i in range(points_intensity.shape[0])]
    pcd.colors = o3d.utility.Vector3dVector(points_colors)  # 根据 intensity 为点云着色
    # 将点云加入到 Visualizer 中
    vis.add_geometry(pcd)

还有比如在社区用户的反馈中,想在可视化时调整场景的背景颜色,我们可以很简单地添加一个 change_bg_color 方法:

def change_bg_color(self, bg_colors):
    """Change back ground color of Visualizer"""
    # 获取渲染配置
    opt = self.o3d_visualizer.get_render_option()
    # 将场景背景颜色设为指定颜色
    opt.background_color = bg_colors

接下来我们具体来看看如何在点云中绘制 3D 框  _draw_bboxes:

#----------------  mmdet3d/core/visualizer/open3d_vis.py ----------------#
def _draw_bboxes(bbox3d,
                 vis,
                 points_colors,
                 pcd=None,
                 bbox_color=(0, 1, 0),
                 points_in_box_color=(1, 0, 0),
                 rot_axis=2,
                 center_mode='lidar_bottom',
                 mode='xyz'):
    in_box_color = np.array(points_in_box_color)
    for i in range(len(bbox3d)):
        center = bbox3d[i, 0:3]
        dim = bbox3d[i, 3:6]
        yaw = np.zeros(3)
        yaw[rot_axis] = -bbox3d[i, 6]
        # 在 Open3D 中需要将 yaw 朝向角转为旋转矩阵
        rot_mat = geometry.get_rotation_matrix_from_xyz(yaw)
        # 底部中心点转为几何中心店
        if center_mode == 'lidar_bottom':
            center[rot_axis] += dim[
                rot_axis] / 2  
        elif center_mode == 'camera_bottom':
            center[rot_axis] -= dim[
                rot_axis] / 2  
        box3d = geometry.OrientedBoundingBox(center, rot_mat, dim)
        line_set = geometry.LineSet.create_from_oriented_bounding_box(box3d)
        line_set.paint_uniform_color(bbox_color)
        # 在 Visualizer 中绘制 Box
        vis.add_geometry(line_set)
        # 更改 3D Box 中的点云颜色
        if pcd is not None and mode == 'xyz':
            indices = box3d.get_point_indices_within_bounding_box(pcd.points)
            points_colors[indices] = in_box_color
    # 更新点云颜色
    if pcd is not None:
        pcd.colors = o3d.utility.Vector3dVector(points_colors)
        vis.update_geometry(pcd)

需要注意 MMDetection3D 中在 3D 视角可视化时的 bbox3d 是 (x, y, z, x_size, y_size, z_size, yaw) 格式的,同时在 3D 视角可视化的时候都是会被转换到深度坐标系,而且 x, y, z 表示的是底部中心点位置,而并不是几何中心点位置。所以在用 Open3D 可视化的时候需要从底部中心点转换为几何中心点。


使用 MeshLab 可视化


对于 MeshLab 来说,可视化需要提供相应的 obj 文件,文件内包含点云信息、分割结果、检测结果等等。而在目前 MMDetection3D 中,我们提供下述方法,可以将模型输出结果转换为 obj 文件。

#----------------  mmdet3d/core/visualizer/show_result.py ----------------#
def _write_obj(points, out_filename):
    """Write points into ``obj`` format for meshlab visualization."""
    N = points.shape[0]
    fout = open(out_filename, 'w')
    # 保存点云的时候如果有颜色信息也会进行保存
    for i in range(N):
        if points.shape[1] == 6:
            c = points[i, 3:].astype(int)
            fout.write(
                'v %f %f %f %d %d %d\n' %
                (points[i, 0], points[i, 1], points[i, 2], c[0], c[1], c[2]))
        else:
            fout.write('v %f %f %f\n' %
                       (points[i, 0], points[i, 1], points[i, 2]))
    fout.close()
def _write_oriented_bbox(scene_bbox, out_filename):
    """Export oriented (around Z axis) scene bbox to meshes."""
    def heading2rotmat(heading_angle):
        rotmat = np.zeros((3, 3))
        rotmat[2, 2] = 1
        cosval = np.cos(heading_angle)
        sinval = np.sin(heading_angle)
        rotmat[0:2, 0:2] = np.array([[cosval, -sinval], [sinval, cosval]])
        return rotmat
    # 将 MMDetection3D 内部格式的 3D 框转换为 trimesh 格式
    def convert_oriented_box_to_trimesh_fmt(box):
        ctr = box[:3]
        lengths = box[3:6]
        trns = np.eye(4)
        trns[0:3, 3] = ctr
        trns[3, 3] = 1.0
        trns[0:3, 0:3] = heading2rotmat(box[6])
        box_trimesh_fmt = trimesh.creation.box(lengths, trns)
        return box_trimesh_fmt
    if len(scene_bbox) == 0:
        scene_bbox = np.zeros((1, 7))
    # 利用 trimesh 构建场景
    scene = trimesh.scene.Scene()
    for box in scene_bbox:
        scene.add_geometry(convert_oriented_box_to_trimesh_fmt(box))
    mesh_list = trimesh.util.concatenate(scene.dump())
    # 保存为 obj 文件
    trimesh.io.export.export_mesh(mesh_list, out_filename, file_type='obj')
    return

_write_points 和 _write_oriented_bbox 分别用来保存点云(及其分割结果)和 3D Box 为 obj 文件,在实际使用的时候只需要将生成的 obj 文件导入到 MeshLab 即可。


文章来源:【OpenMMLab

 2022-04-25 18:04






目录
相关文章
|
2天前
|
并行计算 前端开发 数据可视化
面板平滑转换回归(PSTR)分析案例实现
面板平滑转换回归(PSTR)分析案例实现
|
6月前
|
存储 传感器 数据可视化
3D目标检测数据集 KITTI(标签格式解析、3D框可视化、点云转图像、BEV鸟瞰图)
本文介绍在3D目标检测中,理解和使用KITTI 数据集,包括KITTI 的基本情况、下载数据集、标签格式解析、3D框可视化、点云转图像、画BEV鸟瞰图等,并配有实现代码。
552 0
|
6月前
|
数据采集 数据可视化 数据格式
3D检测数据集 DAIR-V2X-V 转为Kitti格式 | 可视化
本文分享在DAIR-V2X-V数据集中,将标签转为Kitti格式,并可视化3D检测效果。
103 0
|
7月前
视觉智能平台中,如果你想批量清空人脸样本库里的样本数据
视觉智能平台中,如果你想批量清空人脸样本库里的样本数据
48 5
|
7月前
|
存储
ArcGIS模型构建器实现批量按掩膜提取影像
ArcGIS模型构建器实现批量按掩膜提取影像
291 0
|
8月前
|
机器学习/深度学习 数据可视化 算法
【Yellowbrick】特征可视化分析
【Yellowbrick】特征可视化分析
45 0
|
9月前
|
数据可视化 数据挖掘 Linux
转录组下游分析丨利用limma包进行差异表达分析,结果可视化绘制火山图和热图
转录组下游分析丨利用limma包进行差异表达分析,结果可视化绘制火山图和热图
|
9月前
|
JSON 算法 数据格式
优化cv2.findContours()函数提取的目标边界点,使语义分割进行远监督辅助标注
可以看到cv2.findContours()函数可以将目标的所有边界点都进行导出来,但是他的点存在一个问题,太过密集,如果我们想将语义分割的结果重新导出成labelme格式的json文件进行修正时,这就会存在点太密集没有办法进行修改,这里展示一个示例:没有对导出的结果进行修正,在labelme中的效果图。
91 0
|
9月前
|
PyTorch 算法框架/工具
语义分割数据增强——图像和标注同步增强
其中常见的数据增强方式包括:旋转、垂直翻转、水平翻转、放缩、剪裁、归一化等。
388 0
|
机器学习/深度学习 存储 编解码
图像目标分割_1 概述
图像分割的目的:简化或改变图像的表示形式,使得图像更容易理解和分析。图像分割通常用于定位图像中的物体和边界(线,曲线等)。更精确的,图像分割是对图像中的每个像素加标签的一个过程,这一过程使得具有相同标签的像素具有某种共同视觉特性。
255 0