Jetson Xavier NXでRealSenseを使用する際のインストール方法・注意点・実装コード(4台編)

2020.08.17

カフェチームの山本です。

現在、カフェではコンピュータビジョン用のカメラとして、Intel製のRealSense Depth Camera D435Iというデバイスを利用して店内を撮影し、Jetson Xavier NXに接続して画像を処理しています。Jetsonに接続できるRealSenseの台数を増やせれば、Jetsonの台数が少なくてすみ、ハードウェアのコストを下げることができます。

前回は、Jetson1台にRealSenseを2台接続して利用できることを確認しました。

Jetson Xavier NXでRealSenseを使用する際のインストール方法・注意点・実装コード(2台編)

今回は、RealSenseを4台接続するために試行錯誤した内容についてまとめます。具体的には、接続ケーブルの長さ、電源ハブの有無、画像取得の解像度・フレームレートを変更しながら、同時に接続して画像を取得できる台数を調べました。

結論としては、RGB画像とdepth画像を取得する際、4台接続したい場合は、解像度を848*480以下に設定する必要があり、1280*720に設定した場合は2台までしか画像を取得できないことがわかりました。

0.Jetson Xavier NXでRealSenseを使用するためのセットアップ方法

前々回の記事をご参照ください。

Jetson Xavier NXでRealSenseを使用する際のインストール方法・注意点・実装コード

1.4台接続する際の問題点(エラー内容)

今回は、以下のスクリプトを実装して使用しました。RealSenseからRGB画像とdepth画像を取得する処理を実行しています。前々回の記事に書いた用に、Jetson Xavier NXでRealSenseを利用する場合には、毎回接続をリセットする必要があるため、usbライブラリをで接続をリセットする処理を入れています(reset_realsense_devices)。

Jetson Xavier NXでRealSenseを使用する際のインストール方法・注意点・実装コード

import pyrealsense2 as rs
import usb

import numpy as np
import cv2
import time

ID_VENDOR_REALSENSE = 0x8086 # Intel
MANUFACTURER_REALSENSE = "Intel(R) RealSense(TM)"
PRODUCT_REALSENSE = "Intel(R) RealSense(TM)"

CAPTURE_WIDTH = 1280  # 後で848に変更します
CAPTURE_HEIGHT = 720  # 後で480に変更します
CAPTURE_FPS = 30
N_REALSENSE_DEVICES = 4

def reset_realsense_devices():
    usb_devices = usb.core.find(find_all=True)

    def is_realsense_device(dev):
        is_same_idVendor = dev.idVendor == ID_VENDOR_REALSENSE
        if not is_same_idVendor:
            return False

        is_same_manufacturer = MANUFACTURER_REALSENSE in dev.manufacturer
        is_same_product = PRODUCT_REALSENSE in dev.product

        return is_same_manufacturer and is_same_product

    realsense_devices = filter(is_realsense_device, usb_devices)

    for dev in realsense_devices:
        dev.reset()

# reset usb connection of realsense devices
def reset_realsense_devices_with_retry(n_retry=5):
    i_retry = 0
    while True:
        try:
            reset_realsense_devices()
            break
        except:
            print("Exception in reset_realsense_devices")
            import traceback
            traceback.print_exc()

            i_retry += 1
            if i_retry >= n_retry:
                return None

            print("try again in 1 second")
            time.sleep(1)
            continue

    return True

def get_realsense_serial_numbers(max_n_device=1):
    # pyrealsense2.context
    ctx = rs.context()

    # pyrealsense2.device_list
    devices = ctx.query_devices()
    serial_numbers = map(lambda device: device.get_info(rs.camera_info.serial_number), devices)

    serial_numbers_ = list(serial_numbers)[:max_n_device]

    return serial_numbers_

def get_realsense_serial_numbers_with_retry(max_n_device=1, n_retry=5):
    # setup pipeline
    i_retry = 0

    while True:
        serial_numbers = get_realsense_serial_numbers(max_n_device=max_n_device)
        if len(serial_numbers) > 0:
            break
        else:
            print("failed to get realsense serial number")
            i_retry += 1
            if i_retry >= n_retry:
                return None

            print("try again in 1 second")
            time.sleep(1)
            continue

    return serial_numbers

def start_realsense_camera(serial_number, width, height, fps):
    # ストリーム(Color/Depth)の設定
    config = rs.config()
    config.enable_device(serial_number)
    config.enable_stream(rs.stream.color, width, height, rs.format.bgr8, fps)
    config.enable_stream(rs.stream.depth, width, height, rs.format.z16, fps)

    # ストリーミング開始
    pipeline = rs.pipeline()
    profile = pipeline.start(config)
    print(profile)

    return pipeline, profile

def start_realsense_camera_from_bagfile(bag_filepath):
    config = rs.config()
    config.enable_device_from_file(bag_filepath)
    # config.enable_stream(rs.stream.color, width, height, rs.format.bgr8, fps)
    # config.enable_stream(rs.stream.depth, width, height, rs.format.z16, fps)

    # ストリーミング開始
    pipeline = rs.pipeline()
    profile = pipeline.start(config)
    print(profile)

    return pipeline, profile

def setup_realsense_cameras(n_realsense_devices, capture_width, capture_height, capture_fps):
    print("reset_realsense_devices_with_retry")
    ret = reset_realsense_devices_with_retry()
    if ret is None:
        print("Error: reset_realsense_devices_with_retry")
        return

    print("get_realsense_serial_numbers_with_retry")
    serial_numbers = get_realsense_serial_numbers_with_retry(max_n_device=n_realsense_devices)
    if serial_numbers is None:
        print("Error: get_realsense_serial_numbers_with_retry")

    print("start_realsense_camera")
    pipelines = []
    for serial_number in reversed(serial_numbers):
        pipeline, _ = start_realsense_camera(serial_number, capture_width, capture_height, capture_fps)
        pipelines.append(pipeline)

    return pipelines, serial_numbers

def setup_realsense_cameras_from_bagfile(input_bag_filepaths):
    print("start_realsense_camera")
    pipelines = []
    serial_numbers = []
    for bag_filepath in input_bag_filepaths:
        pipeline, _ = start_realsense_camera_from_bagfile(bag_filepath)
        pipelines.append(pipeline)
        serial_numbers.append(bag_filepath)

    return pipelines, serial_numbers

def get_images(pipeline):
    frames = pipeline.wait_for_frames()

    # RGB
    RGB_frame = frames.get_color_frame()
    try:
        RGB_image = np.asanyarray(RGB_frame.get_data())
    except:
        RGB_image = None

    # depth
    depth_frame = frames.get_depth_frame()
    try:
        depth_image = np.asanyarray(depth_frame.get_data())
    except:
        depth_image = None

    return RGB_image, depth_image

def main():
    pipelines, serial_numbers = setup_realsense_cameras(N_REALSENSE_DEVICES, CAPTURE_WIDTH, CAPTURE_HEIGHT, CAPTURE_FPS)
    print(serial_numbers)
    # get image and show
    print("get_images")
    while True:
        RGB_images = []
        for j, pipeline in enumerate(pipelines):
            print(j)
            RGB_image, depth_image = get_images(pipeline)
            RGB_images.append(RGB_image)
            # depth_colormap = cv2.applyColorMap(cv2.convertScaleAbs(depth_image, alpha=0.08), cv2.COLORMAP_JET)

        for j, RGB_image in enumerate(RGB_images):
            cv2.imshow(f"realsense image {j}", RGB_image)

        key = cv2.waitKey(1)
        if key == 27: # ESC
            break

if __name__ == "__main__":
    main()

上記のスクリプトを実行すると、RealSenseに接続してセットアップする処理(setup_realsense_cameras)までは動作し、main関数中の最初のforループで、2つ目のpipelineまでは画像を取得できましたが、3つ目のpipelineで画像を取得する処理(get_images中のpipeline.wait_for_frames)で以下のエラーが発生し、画像を取得することができませんでした。

RuntimeError: Frame didn't arrived within 5000ms

2.4台同時に接続できる条件

上記のエラーに対処するため、どのような条件であれば画像を取得できるか調べました。

調べてみると、ケーブルが悪いとうまく認識されないというコメント(下リンク)や、電流が足りていないのではという意見がありました。

D435: USB device is inserted in USB3.0 but recognized as USB2.0. · Issue #1181 · IntelRealSense/librealsense

そのため今回は、パラメータとしてケーブルの長さ電源ハブの有無解像度フレームレートを変えながら、接続できる台数を調べました。

使用機材

今回は以下の機材を使用しました。

電源ハブを使用する際は、RealSense1つにつき、電源ハブ1つを使用しました(3ポートは空きの状態です。念のため複数接続せず、1つだけにしました。)。RealSenseは1台につき700mA使用するので、定格電流以内です。

実験

パラメータは以下のように設定しました。

ケーブル長

RealSenseに接続するケーブルの長さ

  • 2m:上記の「ケーブル」のみ
  • 2m+2m:上記の「ケーブル」+「延長」ケーブル

電源つきハブ

JetsonからRealSenseへのケーブルに接続する前に電源つきハブを介すか

  • あり:上記の「電源つきハブ」を通して、ケーブルに接続
  • なし:そのままケーブルに接続

解像度

RealSenseで取得する画像の解像度。前節のプログラムのCAPTURE_WIDTH・CAPTURE_HEIGHTの値。

  • 1280 * 720[px]
  • 848 * 480[px]
  • 640 * 480[px]
  • 640 * 360[px]

(RealSenseはRGB画像のみを取得する場合、960540・19201080で設定できますが、depthはこれらのサイズで設定できないため、対象外としました。)

フレームレート

RealSenseで画像取得する速度。前節のプログラムのCAPTURE_FPSの値。

  • 30[fps]
  • 15[fps]
  • 6[fps]

(RealSenseが対応している値を設定しました。)

結果

それぞれの条件で、接続するRealSenseの台数(スクリプト中のN_REALSENSE_DEVICES)を変えながら、画像を取得できる台数を調べた結果は、以下の表のとおりです。(FPSが複数かかれている箇所は、それらのFPSで同じ結果だったことを表します。)

ケーブル 電源ハブ 解像度 FPS 結果
2m なし 1280*720 30, 15, 6 2台まで可
2m なし 848*480 30 4台まで可
2m なし 640*480 30 4台まで可
2m なし 640*360 30 4台まで可
2m+2m なし 1280*720 30, 15, 6 2台まで可
2m+2m なし 848*480 30 4台まで可
2m+2m なし 640*480 30 4台まで可
2m+2m なし 640*360 30 4台まで可
2m あり 1280*720 30, 15, 6 2台まで可
2m+2m あり 1280*720 30, 15, 6 2台まで可

この表からわかることは以下のとおりです。

  • RealSenseを4台使用したい場合は、解像度を848*480以下にする必要がある。
  • 解像度を1280*720に設定すると、2台までしか画像を取得できない。フレームレートを変えても同様。
  • ケーブルの長さや電源ハブの有無は影響がない。

まとめ

Jetson Xavier NXでRealSenseを4台利用するために、エラーが発生しない条件を調査しました。結果として、RealSenseで取得する画像の解像度を848*480以下に設定することで、4台から同時に画像を取得できることがわかりました。

PCの方でも同様に、RealSenseを4台接続して、realsense-viewerで撮影動画を確認しようとすると、プログラムが落ちるというエラーがありました。今回の記事の内容はJetson Xavier NXでの結果でしたが、PC版でも同じような結果になるかもしれません(未確認)。

参考にさせていただいたページ

D435: USB device is inserted in USB3.0 but recognized as USB2.0. · Issue #1181 · IntelRealSense/librealsense

補足

  • USB3の規格

USB3の規格として、ケーブルの長さは最大3mであるようです。今回の実験で延長ケーブルを使用した際、合計4mになっており、規格をオーバーしています。どこかで影響が出ていた可能性はあります。

ユニバーサル・シリアル・バス

設置する状況によっては、10mを利用したい場合などあるかと思います。その際に問題が起きた場合は、以下のページが参考になりそうです。

Intel RealSense USBエクステンダー | 株式会社TKS2

  • 正しく設定してもRealSenseが動かない場合

今回実験している中で、解像度を848*480以下にしても、1.で述べたエラーがまれに出ることがありました。このエラーは、設定を変更してプログラムを再度実行すると発生したのですが、再現性がなく、正確な原因は把握できていません。再起動してから、設定を変えずに実行している限りではエラーが発生していないため、一度再起動するとよさそうです。

また、RealSenseが動かない場合に確認すべき点として、以下のページが参考になりそうです。Raspberry Piの場合が書かれていますが、Jetsonでも利用できるかと思われます。

Raspberry PiでRealSenseが動かないときに確かめること - 右向き矢印のメモ帳