librealsense的基本使用以及获取D435i的Bayer Raw数据

Sep 30,2021   9183 words   33 min

Tags: SLAM

在之前这篇这篇博客中,我们详细地讨论了RealSense D435i的使用。在这篇博客中利用Kalibr对D435i进行了标定,这篇博客中利用D435i运行了ORB-SLAM3。那么接着上一篇博客的问题,我们想探究一下,能否获得D435i的Raw数据呢?这是一个很有意思的问题,也是本篇博客的主要内容。这里先给出结论:可以,但不是利用V4L2,而是用RealSense提供的API接口

1.我们要准备什么

从硬件和软件两个层面来说,硬件当然是RealSense D435i了,把它插到电脑上即可。如果对硬件细节感兴趣,可以参考Realsense的官方Datasheet,里面介绍的很详细。而软件层面,主要是RealSense的驱动、SDK以及对应的开发接口(在这篇博客中我们以Python API为例,如果对C++接口感兴趣可以参考这个网页)等,关于环境配置可以参考这篇博客,这里就不再赘述。比如,配置好软硬件环境后,可以用在终端中输入realsense-viewer,启动Viewer可视化数据,如下。

2.我们要获取什么

第二个需要回答的问题是我们要获取什么。那么在这里来说,我们需要获取的就是相机传感器的Bayer Raw原始数据。更进一步来说,Realsense D435i有一个RGB相机、两个红外相机。红外相机是黑白相机,不存在Bayer数据,所以我们要获取的就是RGB相机的Bayer Raw数据。当然红外相机的数据方式是一样的,也很容易可以获得。

另外,我们需要明确的是,相机都支持哪些输出,这个可以通过打开Realsense Viewer选择不同的分辨率、帧率和数据类型进行查看。比如RGB相机,在1280×720分辨率下,支持YUYV、Y16、RAW16等多种格式输出。 其中,RAW16就是我们想要的Bayer Raw数据。又比如对于红外相机,其只支持Y8和Y16两种输出模式,如下。 这里简单列举几个常用的模式:

Infrared

  • 848×480 30fps Y8/Y16
  • 1280×720 30fps Y8/Y16

Infrared Depth

  • 848×480 30fps Z16
  • 1280×720 30fps Z16

RGB

  • 1280×720 30fps RGB8/BGR8/YUYV/Y16/Raw16
  • 848×480 30fps RGB8/BGR8/YUYV/Y16/Raw16

3.我们要怎么获取

在明确了我们要获取的数据为RGB相机的Bayer Raw数据之后,下一步就是思考如何获取它们。从单纯可视化角度而言,直接用Realsense Viewer就可以获取。从开发角度而言,如果有ROS,那么可以利用Realsense的ROS接口获取。最后如果想直接获取,那么就是利用librealsense提供的各种接口来获取。这里我们以Python接口为例。最核心包含四步:

  • Step1: 检查设备可用性并初始化
  • Step2: 设置数据流格式
  • Step3: 启动相机流水线并设置相关参数
  • Step4: 循环读取帧内容

下面的代码展示了一个非常基础和简单的利用librealsense获取数据的脚本。

import pyrealsense2 as rs
import numpy as np
import cv2

if __name__ == '__main__':
    # step1 检查并初始化设备可用性
    ctx = rs.context()
    if len(ctx.devices) == 0:
        print("No realsense D435i was detected.")
        exit()
    device = ctx.devices[0]
    serial_number = device.get_info(rs.camera_info.serial_number)
    config = rs.config()
    config.enable_device(serial_number)

    # step2 根据配置文件设置数据流
    config.enable_stream(rs.stream.color,
                         1920, 1080,
                         rs.format.bgr8, 30)

    # step3 启动相机流水线并设置是否自动曝光
    pipeline = rs.pipeline()
    profile = pipeline.start(config)
    color_sensor = pipeline.get_active_profile().get_device().query_sensors()[1]  # 0-depth(两个infra)相机, 1-rgb相机,2-IMU
    # 自动曝光设置
    color_sensor.set_option(rs.option.enable_auto_exposure, True)

    # step4 循环读取帧内容,如果需要并输出
    print("Shooting ...")
    while 1:
        frame = pipeline.wait_for_frames()
        frame_data = np.asanyarray(frame.get_color_frame().get_data())
        timestamp_ms = frame.timestamp

        cv2.imshow("received frames", frame_data)
        cv2.waitKey(int(1))

直接运行上述代码(对应Github项目中的realsense_simple_demo.py),正常情况下就能获得相机普通RGB数据,如下所示。 上面展示的就是获取数据的基本流程。而至于我们关心的Bayer Raw数据,其实获取方式是一样的,唯一不同的就是stream的数据类型要改成rs.format.raw16,其余不变即可,如下。 可以看到,图片上有类似网格的情况,这就是Bayer Array了。如果放大会看得更清楚。 最后,我们只需要把获取到的数据保存即可。当然需要注意的是,由于是16bit的数据,不能保存成jpg格式,应该保存成无损的png格式(支持非8bit)。

当然,为了更方便使用,也编写了不同的脚本来更加方便地获取各类型的数据,完整见Github项目。下面是彩色相机的代码:

import pyrealsense2 as rs
import numpy as np
import cv2
import os


def getFormat(data_format):
    format = rs.format.rgb8
    if data_format.__contains__("rgb8"):
        format = rs.format.rgb8
    elif data_format.__contains__("bgr8"):
        format = rs.format.bgr8
    elif data_format.__contains__("raw8"):
        format = rs.format.raw8
    elif data_format.__contains__("raw10"):
        format = rs.format.raw10
    elif data_format.__contains__("raw16"):
        format = rs.format.raw16
    elif data_format.__contains__("y8"):
        format = rs.format.y8
    elif data_format.__contains__("y16"):
        format = rs.format.y16
    elif data_format.__contains__("z16"):
        format = rs.format.z16
    elif data_format.__contains__("yuyv"):
        format = rs.format.yuyv
    elif data_format.__contains__("xyz32f"):
        format = rs.format.motion_xyz32f
    return format


def isDirExist(path='output'):
    """
    判断指定目录是否存在,如果存在返回True,否则返回False并新建目录

    :param path: 指定目录
    :return: 判断结果

    """

    if not os.path.exists(path):
        os.makedirs(path)
        return False
    else:
        return True


if __name__ == '__main__':
    config_file = "config.yml"
    fs = cv2.FileStorage(config_file, cv2.FILE_STORAGE_READ)

    # step0 一些运行前零碎操作
    view_mode = int(fs.getNode("view_mode").real())
    enable_AE_color = int(fs.getNode("enable_AE_color").real())
    if view_mode == 2 or view_mode == 3:
        output_dir_image = fs.getNode("output_dir_image").string().lower()
        output_type_image = fs.getNode("output_type_image").string().lower()
        isDirExist(output_dir_image)
        if not output_type_image.startswith("."):
            output_type_image = "." + output_type_image

    # step1 检查并初始化设备可用性
    ctx = rs.context()
    if len(ctx.devices) == 0:
        print("No realsense D435i was detected.")
        exit()
    device = ctx.devices[0]
    serial_number = device.get_info(rs.camera_info.serial_number)
    config = rs.config()
    config.enable_device(serial_number)

    # step2 根据配置文件设置数据流
    data_format_color = fs.getNode("data_format_color").string().lower()
    frame_width_color = int(fs.getNode("frame_width_color").real())
    frame_height_color = int(fs.getNode("frame_height_color").real())
    frame_rate_color = int(fs.getNode("frame_rate_color").real())
    color_format = getFormat(data_format_color)
    config.enable_stream(rs.stream.color,
                         frame_width_color, frame_height_color,
                         color_format, frame_rate_color)

    # step3 启动相机流水线并设置是否自动曝光
    pipeline = rs.pipeline()
    profile = pipeline.start(config)
    color_sensor = pipeline.get_active_profile().get_device().query_sensors()[1]  # 0-depth(两个infra)相机, 1-rgb相机,2-IMU
    # 自动曝光设置
    if enable_AE_color == 1:
        color_sensor.set_option(rs.option.enable_auto_exposure, True)
    else:
        manual_exposure_color = fs.getNode("manual_exposure_color").real()
        color_sensor.set_option(rs.option.exposure, manual_exposure_color)

    # step4 循环读取帧内容,如果需要并输出
    print("Shooting ...")
    while 1:
        frame = pipeline.wait_for_frames()
        frame_data = np.asanyarray(frame.get_color_frame().get_data())
        timestamp_ms = frame.timestamp

        if data_format_color.__contains__("rgb"):
            frame_data = cv2.cvtColor(frame_data, cv2.COLOR_RGB2BGR)

        if view_mode == 1 or view_mode == 3:
            cv2.imshow("received frames", frame_data)
            cv2.waitKey(int(1))

        if view_mode == 2 or view_mode == 3:
            cv2.imwrite(output_dir_image + "/" + str(timestamp_ms) + output_type_image, frame_data)

相比于上面的代码,加入了更多判断以应对不同情况。同时将一些参数以配置文件的形式进行储存,便于修改,如下所示。

4.拿到数据后我们要做什么

拿到原始的Bayer Raw数据之后,我们自然就需要自己对其进行解析,才能得到“看得过去”的彩色RGB影像。关于Raw数据的后处理,在这篇博客中也有提到。这里以实际数据进行说明,主要进行了Bayer Raw的Demosaic、白平衡、色彩缩放步骤,代码如下。

import cv2
import numpy as np

if __name__ == '__main__':
    file_path = "./test_raw.png"

    # step1 读取数据
    # ----------------------------------------------------------------------------------
    raw_data = cv2.imread(file_path, cv2.IMREAD_UNCHANGED)
    # ----------------------------------------------------------------------------------

    # step2 Bayer插值
    # ----------------------------------------------------------------------------------
    # Bayer array:
    # G     B
    # R     G
    raw_data_bgr = cv2.cvtColor(raw_data, cv2.COLOR_BAYER_GB2BGR)
    print('image size:', raw_data_bgr.shape)
    # ----------------------------------------------------------------------------------

    # step3 白平衡
    # ----------------------------------------------------------------------------------
    band_b = raw_data_bgr[:, :, 0]
    band_g = raw_data_bgr[:, :, 1]
    band_r = raw_data_bgr[:, :, 2]

    band_r = band_r * 2.04
    band_g = band_g
    band_b = band_b * 1.32
    # ----------------------------------------------------------------------------------

    # step4 缩放颜色尺度
    # ----------------------------------------------------------------------------------
    max_r = np.max(band_r)
    max_g = np.max(band_g)
    max_b = np.max(band_b)
    print('max red:', max_r, 'max green:', max_g, 'max blue:', max_b)
    min_value = min(max_r, min(max_g, max_b))
    print('min value:', min_value)

    band_r_clip = np.where(band_r > min_value, min_value, band_r)
    band_g_clip = np.where(band_g > min_value, min_value, band_g)
    band_b_clip = np.where(band_b > min_value, min_value, band_b)
    # ----------------------------------------------------------------------------------

    # step5 合并波段并输出
    # ----------------------------------------------------------------------------------
    band_r_clip_int = np.zeros([band_r_clip.shape[0], band_r_clip.shape[1]], np.uint8)
    band_g_clip_int = np.zeros([band_g_clip.shape[0], band_g_clip.shape[1]], np.uint8)
    band_b_clip_int = np.zeros([band_b_clip.shape[0], band_b_clip.shape[1]], np.uint8)

    for i in range(band_r_clip.shape[0]):
        for j in range(band_r_clip_int.shape[1]):
            band_r_clip_int[i, j] = int(band_r_clip[i, j] / 256)
            band_g_clip_int[i, j] = int(band_g_clip[i, j] / 256)
            band_b_clip_int[i, j] = int(band_b_clip[i, j] / 256)

    bands = cv2.merge((band_b_clip_int, band_g_clip_int, band_r_clip_int))

    cv2.imwrite("merged.png", bands)
    # ----------------------------------------------------------------------------------

这里需要注意的是,虽然我们获取的数据是16bit的,但并不是说量化级数真的就是16bit。量化级数还是10bit,只是每个灰度级都乘以64,变成了16bit,占满存储级数。这点其实在这篇博客里有提到。如下图所示,可以看到,直接读取的16bit数据灰度级数存在间隔(像素个数为0),而且这个间隔是64。也就是说原本10bit量化灰度数据被缩放离散化到了16bit。 我们对其除以64,就可以得到正常的灰度直方图,如下所示。 感兴趣可以参考Github项目中的histogramRaw.py脚本。

运行上述代码(对应Github项目parseRaw.py),就可以得到解析好的影像,如下所示。 和它对应的直接输出彩色图像如下。 可以看到差异还是比较大的,这是因为我们没有进行复杂的处理,包括lens shading的补偿等,只进行了基本的色彩解析。在一些较亮的地方,我们解析出来的图像细节方面更好一些,没有那么重的涂抹感(主要是因为锐化造成的),如下对比所示。 可以看出路灯的光晕和反光的区域更小。

至此,我们便完成了前面提到的任务:获取RealSense D435i的Bayer Raw数据,并对其进行了简单的处理。

5.参考资料

  • [1] https://www.intel.com/content/dam/support/us/en/documents/emerging-technologies/intel-realsense-technology/Intel-RealSense-D400-Series-Datasheet.pdf
  • [2] https://support.intelrealsense.com/hc/en-us/community/posts/1500001169042-How-to-capture-realsense-RGB-IRs-raw-images-
  • [3] https://support.intelrealsense.com/hc/en-us/community/posts/360049233974-Can-you-access-raw-synchronized-D435-output-
  • [4] https://blog.csdn.net/u013832707/article/details/108584866
  • [5] https://github.com/IntelRealSense/librealsense/tree/master/wrappers/python/examples
  • [6] https://github.com/IntelRealSense/librealsense/issues/450
  • [7] https://blog.csdn.net/qq_42393859/article/details/108468235
  • [8] https://www.freesion.com/article/8664151074/
  • [9] https://github.com/IntelRealSense/realsense-ros/issues/1409
  • [10] https://github.com/IntelRealSense/librealsense/issues/3878#issuecomment-569550710
  • [11] https://github.com/IntelRealSense/librealsense/issues/7275
  • [12] https://github.com/IntelRealSense/librealsense/issues/3376

本文作者原创,未经许可不得转载,谢谢配合

返回顶部