使用Intel RealSense D435i自制离线数据集跑通BundleFusion

摘要

本文主要讲述如何使用 Intel RealSense D435i 深度相机录制 Rosbag 离线数据集,并基于此数据集跑通 BundleFusion。关于如何基于 BundleFusion 使用 Intel RealSense D435i 深度相机实现实时三维建图可参照 《Intel RealSense D435i + BundleFusion 实现实时三维建图》

操作环境

环境项版本/型号
操作系统Win10_x64、Ubuntu 18.04
开发工具集Visual Studio 2013、Pycharm Professional 2019.1.3
计算架构CUDA 8.0
深度相机Intel RealSense D435i
显卡NVIDIA GeForce 940MX

Ubuntu 18.04 系统中需完成 ROS 配置,具体配置过程可参考 《ROS开发之环境搭建:Ubuntu 18.04安装ROS Melodic》。ROS 配置完成后,需为 Pycharm 设定环境变量,从而实现在 Pycharm 中编写 ROS 代码。通过 sudo gedit /usr/share/applications/pycharm.desktop 命令编辑 “ pycharm.desktop ” 文件,路径取决于安装 Pycharm 时的选项,选择为所有用户安装(/usr/share/applications),选择为当前用户安装(~/.local/share/applications)。将该文件中的 Exec = /... 修改为 Exec = bash -i -c /... ,从而实现在通过快捷方式启动 Pycharm 的同时加载 “ ~/.bashrc ” 中的 ROS环境变量,修改后如下图所示:

pycharm配置ros

大致流程

  1. 录制数据集
  2. 解析 bag 文件,提取深度图和彩色图并制作成源格式
  3. 将源格式封装为 sens 格式
  4. 基于 BundleFusion 进行离线三维重建

录制数据集

使用 USB 线连接好深度相机和电脑,打开 Intel RealSense Viewer 。设置 Depth Stream 以及 Color Stream 的图像分辨率为 640 × 480 ,设置采集帧率为 30 fps 。点击左上角的 Record 按钮即可进行录制,开始录制后,点击左上角的 Stop 按钮即可结束录制并保存录制结果,如下图所示:

开始录制
结束录制
若点击 Record 按钮后出现以下报错,说明默认存储路径为 “ C:\Users\ ”,软件无权在该路径下创建文件。通过点击右上角的齿轮图标,选择 Settings 并改变存储路径即可解决此问题,具体如下图所示:

报错
解决方案
结束录制后,在相应存储路径下即生成 .bag 文件,按需重命名该文件。至此,完成离线数据集的录制过程。

录制结果

格式转换

此步骤的目的在于将录制好的数据集转换为 BundleFusion 所要求的离线输入格式,即 .sens 格式。BundleFusion 提供了将源格式封装成 .sens 格式的实现,因此只需将录制好的数据集存储为源格式即可。从 BundleFusion 官网下载源格式数据集,查看其存储格式如下图所示:

源格式
BundleFusion 官网对于数据存储格式也有相应说明:

数据存储格式

解析.bag文件

.bag 是 ROS 常用的数据存储格式,由于 ROS 的配置在 Ubuntu 18.04 系统中,因此切换至 Ubuntu 系统完成 .bag 文件的解析并从中提取出深度图和彩色图。首先,进入 bag 文件的存储路径并打开终端,通过 rosbag info ***.bag 查看待提取的深度图及彩色图所对应的 topic,如下图所示:

查看topic
Intel RealSense D435i 深度相机所录制的数据包括 Depth、RGB 以及 IMU,使用 BundleFusion 进行三维重建只需使用 Depth 及 RGB,因此新建两个文件夹分别用于存放此两种类型的数据。

新建文件夹
执行以下 Python 脚本,提取深度图及彩色图。其中,深度图的命名格式为 时间戳.png,彩色图的命名格式为 时间戳.jpg。同时将深度图与彩色图的时间戳及相对路径按照 (时间戳 相对路径) 的形式分别存储至两个文本文件中。

import roslib
import rosbag
import rospy
import cv2
import os
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
from cv_bridge import CvBridgeError

rgb = '/home/magus/jizongxing-workspace/slam/rosImage/rgb/'  #rgb path
depth = '/home/magus/jizongxing-workspace/slam/rosImage/depth/'   #depth path
bridge = CvBridge()

file_handle1 = open('/home/magus/jizongxing-workspace/slam/rosImage/depth-stamp.txt', 'w')
file_handle2 = open('/home/magus/jizongxing-workspace/slam/rosImage/rgb-stamp.txt', 'w')

with rosbag.Bag('/home/magus/jizongxing-workspace/bedroom.bag', 'r') as bag:
    for topic,msg,t in bag.read_messages():
        if topic == "/device_0/sensor_0/Depth_0/image/data":  #depth topic
            cv_image = bridge.imgmsg_to_cv2(msg)
            timestr = "%.6f" %  msg.header.stamp.to_sec()   #depth time stamp
            image_name = timestr+ ".png"
            path = "depth/" + image_name
            file_handle1.write(timestr + " " + path + '\n')
            cv2.imwrite(depth + image_name, cv_image)
        if topic == "/device_0/sensor_1/Color_0/image/data":   #rgb topic
            cv_image = bridge.imgmsg_to_cv2(msg,"bgr8")
            timestr = "%.6f" %  msg.header.stamp.to_sec()   #rgb time stamp
            image_name = timestr+ ".jpg"
            path = "rgb/" + image_name
            file_handle2.write(timestr + " " + path + '\n')
            cv2.imwrite(rgb + image_name, cv_image)
file_handle1.close()
file_handle2.close()

提取结果如下:

depth
rgb
depth stamp
rgb stamp

时间戳对齐

由执行结果可知,深度图及彩色图的时间戳并非严格一一对齐,存在一定的时间差,因此需将深度图及彩色图按照时间戳最接近原则进行两两配对。将 associate.py 脚本文件存储至上述两个文本文件所在的路径下,如图所示:

associate.py
associate.py 脚本文件:

"""
The RealSense provides the color and depth images in an un-synchronized way. This means that the set of time stamps from the color images do not intersect with those of the depth images. Therefore, we need some way of associating color images to depth images.

For this purpose, you can use the ''associate.py'' script. It reads the time stamps from the rgb.txt file and the depth.txt file, and joins them by finding the best matches.
"""

import argparse
import sys
import os
import numpy


def read_file_list(filename):
    """
    Reads a trajectory from a text file.

    File format:
    The file format is "stamp d1 d2 d3 ...", where stamp denotes the time stamp (to be matched)
    and "d1 d2 d3.." is arbitary data (e.g., a 3D position and 3D orientation) associated to this timestamp.

    Input:
    filename -- File name

    Output:
    dict -- dictionary of (stamp,data) tuples

    """
    file = open(filename)
    data = file.read()
    lines = data.replace(",", " ").replace("\t", " ").split("\n")
    list = [[v.strip() for v in line.split(" ") if v.strip() != ""] for line in lines if
            len(line) > 0 and line[0] != "#"]
    list = [(float(l[0]), l[1:]) for l in list if len(l) > 1]
    return dict(list)


def associate(first_list, second_list, offset, max_difference):
    """
    Associate two dictionaries of (stamp,data). As the time stamps never match exactly, we aim
    to find the closest match for every input tuple.

    Input:
    first_list -- first dictionary of (stamp,data) tuples
    second_list -- second dictionary of (stamp,data) tuples
    offset -- time offset between both dictionaries (e.g., to model the delay between the sensors)
    max_difference -- search radius for candidate generation

    Output:
    matches -- list of matched tuples ((stamp1,data1),(stamp2,data2))

    """
    first_keys = first_list.keys()
    second_keys = second_list.keys()
    potential_matches = [(abs(a - (b + offset)), a, b)
                         for a in first_keys
                         for b in second_keys
                         if abs(a - (b + offset)) < max_difference]
    potential_matches.sort()
    matches = []
    for diff, a, b in potential_matches:
        if a in first_keys and b in second_keys:
            first_keys.remove(a)
            second_keys.remove(b)
            matches.append((a, b))

    matches.sort()
    return matches


if __name__ == '__main__':

    # parse command line
    parser = argparse.ArgumentParser(description='''
    This script takes two data files with timestamps and associates them   
    ''')
    parser.add_argument('first_file', help='first text file (format: timestamp data)')
    parser.add_argument('second_file', help='second text file (format: timestamp data)')
    parser.add_argument('--first_only', help='only output associated lines from first file', action='store_true')
    parser.add_argument('--offset', help='time offset added to the timestamps of the second file (default: 0.0)',
                        default=0.0)
    parser.add_argument('--max_difference',
                        help='maximally allowed time difference for matching entries (default: 0.02)', default=0.02)
    args = parser.parse_args()

    first_list = read_file_list(args.first_file)
    second_list = read_file_list(args.second_file)

    matches = associate(first_list, second_list, float(args.offset), float(args.max_difference))

    if args.first_only:
        for a, b in matches:
            print("%f %s" % (a, " ".join(first_list[a])))
    else:
        for a, b in matches:
            print("%f %s %f %s" % (a, " ".join(first_list[a]), b - float(args.offset), " ".join(second_list[b])))

在该路径下打开终端并通过执行如下命令生成配对结果 associate.txt

python associate.py depth-stamp.txt rgb-stamp.txt > associate.txt

associate.txt

制作源格式

根据配对结果,将深度图及彩色图按对重命名。相机位姿是在重建过程中估计所得,作为输入无需精确的位姿数据,因此将源格式中的相机位姿统一预设为:

1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1

将相机位姿文件按格式命名并与深度图、彩色图组成序列,执行如下 Python 脚本文件实现上述需求。

import shutil
import os

bp='/home/magus/jizongxing-workspace/slam/rosImage/'  # base path
rp='/home/magus/jizongxing-workspace/slam/rosImage/result/'  # result path
file_handle1 = open('/home/magus/jizongxing-workspace/slam/rosImage/associate.txt', 'r')

count = 0
for line in file_handle1.readlines():
    line = line.strip()
    # relative path of depth and color
    path_d = line.split()[1]   # relative path of depth
    path_c = line.split()[3]   # relative path of rgb

    if (os.path.exists(bp+path_d)==True) and (os.path.exists(bp+path_c)==True):
        rName = "frame-" + str(count).zfill(6) + ".color.jpg"   # rename rgb
        dName = "frame-" + str(count).zfill(6) + ".depth.png"   # rename depth
        pName = "frame-" + str(count).zfill(6) + ".pose.txt"   # name pose
        # create pose file
        file_handle2 = open(rp+pName, 'w')
        file_handle2.write("1 0 0 0\n0 1 0 0\n0 0 1 0\n0 0 0 1")   # initialize pose.txt
        file_handle2.close()
        shutil.copy(bp+path_d,rp+dName)
        shutil.copy(bp+path_c,rp+rName)
        count=count+1
file_handle1.close()

执行结果如下:

align
最终,将 info.txt 文件拷贝至 result 文件夹中,将其中的 m_frames.size 按照实际大小进行设置。

m_versionNumber = 4
m_sensorName = StructureSensor
m_colorWidth = 640
m_colorHeight = 480
m_depthWidth = 640
m_depthHeight = 480
m_depthShift = 1000
m_calibrationColorIntrinsic = 582.871 0 320 0 0 582.871 240 0 0 0 1 0 0 0 0 1 
m_calibrationColorExtrinsic = 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 
m_calibrationDepthIntrinsic = 583 0 320 0 0 583 240 0 0 0 1 0 0 0 0 1 
m_calibrationDepthExtrinsic = 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 
m_frames.size = 2016

至此,成功生成与源格式相同的数据格式。

封装为.sens格式

在 BundleFusion 工程的 sensorData.h 文件中提供了将源格式封装为 .sens 格式的相关实现 loadFromImages 。先拷贝两份 BundleFusion 工程代码,一份用于将源格式封装为 .sens 格式,另一份用于运行 sens 生成 ply 模型。

封装成sens格式
因此,将其中一份 BundleFusion 工程中原来的主函数全部注释并替换为:

int main()
{
	ml::SensorData sd;
	sd.loadFromImages("H:/ROSImage/result", "frame-", "jpg");
	sd.saveToFile("H:/ROSImage/test.sens");
}

同时在 sensorData.h 文件的第812与813行对深度图及彩色图的压缩类型进行初始化,从而使代码能够正常运行,生成可用的sens文件。

修改sensorData.h
生成解决方案后,点击 FriedLiver.exe 即可对源格式进行封装,在相应路径下生成 sens 文件。

生成sens文件

离线三维重建

将生成的 sens 文件重命名后拷贝至另一份 BundleFusion 工程的 “ BundleFusion-master\FriedLiver\x64\data ” 目录下。

添加sens文件
同时将 GlobalAppState.h 文件中的深度相机全部注释掉。

注释深度相机
生成解决方案后,将 “ BundleFusion-master\FriedLiver ” 下的 zParametersBundlingDefault.txt 以及 zParametersDefault.txt 文件拷贝至 “ BundleFusion-master\FriedLiver\x64\Release ” 路径下,并作如下修改:

修改 zParametersDefault.txt

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

修改 zParametersBundlingDefault.txt

在这里插入图片描述

重建效果

点击 FriedLiver.exe 即可进行离线三维重建,重建过程如下所示:

三维重建
运行结束后,“ BundleFusion-master\FriedLiver\x64\data ” 路径下将生成 ply 模型,如下图所示:

生成ply
使用 Meshlab 查看该 ply 文件,重建效果如下所示:

重建效果

结语

重建结果来看,整体具备辨识度,但细节处理欠缺,这是由于在录制数据集时,仅简单环绕一周,未对细节部分进行局部录制。在之后的工作中,我们将使用小车搭载 Intel RealSense D435i 深度相机,进行平稳匀速拍摄,并对细节部分的深度以及色彩信息进行采集。以上就是使用 Intel RealSense D435i 自制离线数据集跑通 BundleFusion 的全过程,相互学习,共克时艰,哈哈哈。

  • 9
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 26
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值