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

2020.07.27

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

現在、カフェではコンピュータビジョン用のカメラとして、Intel製のRealSenseというデバイスを利用し、RGB画像とDepth画像を取得しています。今までは、通常のPCで動かしていました。

今回は、シングルボードの1つである、Jetson Xavier NXでRealSense D435iを動かしてみました。その際のインストール方法と、動かす際につまづいた問題点、動かすために実装したコードを共有します。本記事では、RealSenseデバイス1台で動かす場合について記載しています。

追記(2020/09/10):本記事の内容はRealSense D435iを使用する場合は利用可能ですが、RealSense D455は動かないことがわかりました。D455を利用する場合は以下の記事をご参照ください。これにともない、記事のタイトルを変更しました。

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

追記(2020/07/27):複数台の記事も掲載いたしましたので、こちらも合わせてご覧ください。

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

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

0.Jetson Xavier NXを準備

通常通り、SDカードにJetson Xavier NX用のOSイメージを書き込んで、セットアップします。今回利用したバージョンは、JetPack4.4(2020/07/27時点で最新のもの)でした。

詳しくは、こちらの記事の「インストール手順」の「ステップ0~1」までをご覧ください

【MediaPipe】Jetson Xavier NXで環境構築し、Multi Hand TrackingをGPUで動かしてみた(v0.7.6) | Developers.IO

1.RealSense用ライブラリをインストール

(参考:通常のインストール方法)

通常のPCでのインストール方法は、公式のGithubのページに書かれています。

IntelRealSense/librealsense

librealsense(RealSense用SDK)のインストール

上のページの「Download and Install」に各OSにおけるインストール方法が記載されています。

Ubuntuの場合のインストール方法は、以下のページに記載されています。

IntelRealSense/librealsense

Ubuntu18.04の場合、以下のコマンドをターミナルで実行します。

sudo apt-key adv --keyserver keys.gnupg.net --recv-key F6E65AC044F831AC80A06380C8B3A55A6F3EFCDE || sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-key F6E65AC044F831AC80A06380C8B3A55A6F3EFCDE

sudo add-apt-repository "deb http://realsense-hw-public.s3.amazonaws.com/Debian/apt-repo bionic main" -u

sudo apt-get install librealsense2-utils
sudo apt-get install librealsense2-dev

動作確認として、以下のコマンドをターミナルで実行します。RealSenseをPCに接続した後、中のアプリでカメラを追加して、各イメージをオンにしたとき、映像が表示されればOKです。

realsense-viewer

pyrealsense(RealSense用のPythonライブラリ)のインストール

インストール方法は、以下のページにかかれています。

IntelRealSense/librealsense

以下のコマンドでをインストールします。

pip3 install pyrealsense2

動作確認として、ターミナルからPythonを起動し、pyrealsense2をインポートしてエラーが出なければOKです。

python3
>>> import pyrealsense2

Jetson Xavier NXでのインストール方法

librealsense(RealSense用SDK)とpyrealsense(RealSense用のPythonライブラリ)のインストール

上のPCと同様の手順で、librealsenseをインストールすることができます。しかし、pyrealsenseをインストールしようとすると以下のようなエラーが発生します。

pip3 install pyrealsense2

Could not find a version that satisfies the requirement pyrealsense2 (from versions: )
No matching distribution found for pyrealsense2

そこで、下のGithubリポジトリで公開されている、Jetson用のインストールスクリプトを利用します。これによって、librealsenseとpyrealsenseを同時にインストールできました。

JetsonHacksNano/installLibrealsense

git clone https://github.com/jetsonhacksnano/installLibrealsense
cd installLibrealsense
./installLibrealsense.sh
./buildLibrealsense.sh

jetsonhacksnanoという名前のとおり、Jetson Nano用に作られたもののようですが、Jetson Xavier NXでも利用できました。

動作確認としては、先程のPCの場合と同様にできます。ターミナルを開いて以下を実行してください。

realsense-viewer
python3
>>> import pyrealsense2

2.接続する場合の問題点と解決法

前節の方法でインストールはできましたが、使用してみるといくつか問題点がありました。以下はJetson Xavier NXで動作させた際の内容です。

問題点:2回目以降の接続でエラーが発生する

具体的は、以下のような状況でした

  • realsense-viewerを起動した後、アプリ内でカメラを追加しました。その後、1回目に各画像をONにした際は映像が表示されましたが、2回目以降は"No Frame Received!"というエラーが表示され、映像が表示されませんでした。
  • pyrealsenseライブラリを用いてRealSenseデバイスに接続して、画像を取得するプログラム(プログラムの詳細は次節に記載します)を実行する際に発生しました。1回目にプログラムを起動した際には問題はないのですが、2回目に起動した際に画像を取得する関数においてタイムアウトの旨の例外が発生し、画像を取得できませんでした。

OSを再起動したり、RealSenseに接続しているUSBケーブルを抜いて接続しなおすとリセットされますが、2回目以降はエラーになりました。まれに数回までつながることがありましたが、それ以降は接続できませんでした。

解決策1:ケーブルを抜き差しする・OSを再起動する

先程も書きましたが、RealSenseのUSBケーブルを抜き差しするか、OSを再起動することで解決できます。ただし、2回目以降の接続では再度エラーになります。

この方法で解決はできるものの、OSを毎回再起動し直したり、店舗などに設置したカメラのケーブルを毎回抜き差したりするのは、現実的な解決策ではないと思います。

解決法2:SWでリセットする方法

ソフトウェア処理でUSB接続をリセットすることで、問題点を解決することができます。これによって、解決策1のような手間や時間を省くことができます。方法としては、以下の2つが利用できます。

Ubuntuのコマンドを利用する

この方法を利用するには、予めRealSenseデバイスがつながっているUSBのバス番号とポート番号を把握しておく必要があります。以下のようにコマンドを実行することで、USBの接続状態を確認できます。

$ lsusb
Bus 002 Device 008: ID 8086:0b3a Intel Corp.
Bus 002 Device 005: ID 0bda:0489 Realtek Semiconductor Corp.
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 13d3:3549 IMC Networks
Bus 001 Device 002: ID 0bda:5489 Realtek Semiconductor Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

$ lsusb -t
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=tegra-xusb/4p, 10000M
    |__ Port 3: Dev 5, If 0, Class=Hub, Driver=hub/4p, 10000M
        |__ Port 2: Dev 8, If 0, Class=Video, Driver=uvcvideo, 5000M
        |__ Port 2: Dev 8, If 1, Class=Video, Driver=uvcvideo, 5000M
        |__ Port 2: Dev 8, If 2, Class=Video, Driver=uvcvideo, 5000M
        |__ Port 2: Dev 8, If 3, Class=Video, Driver=uvcvideo, 5000M
        |__ Port 2: Dev 8, If 4, Class=Video, Driver=uvcvideo, 5000M
        |__ Port 2: Dev 8, If 5, Class=Human Interface Device, Driver=usbhid, 5000M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=tegra-xusb/4p, 480M
    |__ Port 2: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M
    |__ Port 3: Dev 3, If 0, Class=Wireless, Driver=rtk_btusb, 12M
    |__ Port 3: Dev 3, If 1, Class=Wireless, Driver=rtk_btusb, 12M

この内、lsusbコマンドの結果の「Bus 002 Device 008」が「Intel Corp.」となっており、RealSenseデバイスのものと同じです。さらに、lsusb -tコマンドの結果において「Bus 002 Device 008」に対応するのは、「Bus 02.Port : Dev1」「Port 3: Dev 5」「Port 2: Dev8」です。(バス番号・ポート番号は環境によって変わり、デバイス番号はケーブル抜き差しすると1つずつ上がります)

上の場合だと、RealSenseデバイスが接続されているのは、バス番号が2、1つ目のポート番号が3、2つ目のポート番号が2です。なので、以下を実行することで、USB接続をリセットすることができます。

sudo sh -c "echo -n 2-3.2 >/sys/bus/usb/drivers/usb/unbind"
sudo sh -c "echo -n 2-3.2 >/sys/bus/usb/drivers/usb/bind"

上記のような処理を行うスクリプトを実装すれば、SWでRealSenseをリセットすることを自動化できます。

pyusb

上のUbuntuのコマンドを利用する代わりに、Pythonのpyusbライブラリを利用することができます。これは、上のようなコマンドをPython用にラッパしたものです。Pythonが扱える状況なら、こちらのほうが簡単に処理できます。

以下のコマンドでインストールします。

pip3 install pyusb

USBで接続されている全デバイス情報を取得し、ベンダーIDと製造者名とプロダクト名で絞り込むことで、RealSenseデバイスを検索します。その後、デバイスをリセットします。コードとしては、以下のようです(以下のコードではすべてのRealSenseデバイスの接続をリセットします)。

import usb

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

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 = list(filter(is_realsense_device, usb_devices))
# print(realsense_devices)

for dev in realsense_devices:
    # print("reset RealSense devices:", dev)
    dev.reset()

3.実装したコード

以上を踏まえて、RealSenseを使用するために実装したコードが以下のとおりです。このコードでは、すべての接続されたRealsenseデバイスのUSB接続をリセットし、REALSENSE_SERIAL_NUMBERで指定したシリアル番号の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 = 640
CAPTURE_HEIGHT = 480
CAPTURE_FPS = 60

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()

def get_realsense_serialnumbers(max_n=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]

    return serial_numbers_

def setup_realsense_camera(device_serial_number, width, height, fps):
    # ストリーム(Color/Depth)の設定
    config = rs.config()
    config.enable_device(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)

    return pipeline, profile

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

    # RGB
    RGB_frame = frames.get_color_frame()
    RGB_image = np.asanyarray(RGB_frame.get_data())

    # depth
    depth_frame = frames.get_depth_frame()
    depth_image = np.asanyarray(depth_frame.get_data())

    return RGB_image, depth_image

def main():
    # reset usb connection of realsense devices
    reset_realsense_devices()

    # setup pipeline
    serial_numbers = get_realsense_serialnumbers(max_n=1)
    if len(serial_numbers) == 0:
        print("Not found realsense devices")
        return

    pipeline, _ = setup_realsense_camera(serial_numbers[0], CAPTURE_WIDTH, CAPTURE_HEIGHT, CAPTURE_FPS)

    # get image and show
    i = 0
    while True:
        print(i)
        RGB_image, depth_image = get_images(pipeline)
        # depth_colormap = cv2.applyColorMap(cv2.convertScaleAbs(depth_image, alpha=0.08), cv2.COLORMAP_JET)

        cv2.imshow("realsense image", RGB_image)
        key = cv2.waitKey(1)
        if key == 27: # ESC
            break

        i += 1

if __name__ == "__main__":
    main()

問題点

上のプログラムでほぼ動作することを確認しています。しかし、まれにではありますが、reset_realsense_devicesで例外が発生しプログラムが終了する場合や、RealSenseデバイスを接続しているにも関わらずget_realsense_serialnumbersでデバイス情報を取得できない場合がありました。再現することができなかったため、一旦割愛とさせていただきたいと思います。問題がありましたら、この記事にコメントをいただけると助かります。

まとめ

コンピュータビジョン用のカメラとして利用できるIntel製のRealSenseを、Jetson Xavier NXで動かすための方法をコードとともに整理しました。

今回は、RealSenseデバイスを1台だけを接続した場合でしたが、次回は複数台を接続した場合について書きたいと思います。

追記:複数台の記事も掲載いたしました。

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

余談

以下のようにして、RealSenseデバイスの情報を取得できます。device.get_infoの引き数を変えることで、取得できる情報を変えられます。

https://intelrealsense.github.io/librealsense/python_docs/_generated/pyrealsense2.camera_info.html#pyrealsense2.camera_info

import pyrealsense2 as rs

# pyrealsense2.context
ctx = rs.context()

# pyrealsense2.device_list
devices = ctx.query_devices()

for device in devices: # pyrealsense2.device
    print("serial_number:", device.get_info(rs.camera_info.serial_number))
    print("asic_serial_number:", device.get_info(rs.camera_info.asic_serial_number))

更に余談ですが、serial_numberはRealSenseデバイスの底面に記載されているシリアル番号を意味し、asic_serial_numberはUSBデバイスとしてのシリアル番号を意味しています(asic_serial_numberはどこかに書かれているものではありません)。間違えないようにご注意ください。

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

IntelRealSense/librealsense

JetsonHacksNano/installLibrealsense