RealSense L515をラズパイ4 Raspberry Pi OSのPythonから起動する最速手順&サンプルコード

2021/04/25

IoT Python ガジェット ラズパイ 開発

RealSesnse L515をラズパイで起動

こんにちはイチケンです。

Rapberry Pi 4でIntelのLiDARセンサーL515を起動できたので備忘録としてまとめておきます。既存の記事はどれもD435とUbuntuの組み合わせでしたが、私は慣れているRaspberry Pi OS(旧Raspbian)が使いたいのでそちらでチャレンジしてみました。今の所公式ではRaspberry Pi OS対応とは書かれていませんね。

Supported Platforms
Ubuntu 16.04/18.04/20.04 LTS (1) (Linux Kernels 4.4, 4.8 ,4.10, 4.13, 4.15, 4.16(4) , 4.18, 5.0, 5.3, 5.4)
Windows 10 (Build 15063 or later)
Windows 8.1 (2)
Windows 7 (3)
Mac OS (High Sierra 10.13.2)
Android 7, 8

インテルのページはこちら。

Intel® RealSense™ LiDAR Camera L515

結論から言うと、Raspberry Pi OSでもPythonライブラリpyrealsense2を使ってL515でストリーミングできます。しかし一筋縄ではいかないので成功したやり方をまとめておきます。早くWindowsみたいにサクっとできるようになってほしいですね。

pyrealsense2を使うとRGB画像と距離データをそれぞれ三次元と二次元のndarrayとして取得できます。従来の画像では精度がでなかった検査なども距離情報を使うことにより大幅に精度向上できる可能性があります。しかもオープンソースライブラリが準備されておりPythonで動かせるのでラズパイやAIとの相性もバッチリ。

この記事でわかること

Raspberry Pi OS上でPythonライブラリpyrealsense2を使ってRealSense L515でストリーミングする方法がわかります。サンプルコードあり。インストールには全部で4時間くらい必要ですので、余裕を持ってトライしてください。
AttributeError: module 'pyrealsense2' has no attribute 'pipeline'というimportできたのにpipelineが無いと言われてしまうエラーに対する処置方法もわかります。

前提条件

ラズパイ自体は以下の記事で使ってきたものです。OpenCV導入済みのラズパイ4 8GBになります。

【IoT×キャンプ|OpenCVインストール編】意地でもラズパイとキャンプへ!らずキャン△プロジェクト第10回

前提条件となる私の環境はこちら。

  • Raspberry Pi 4 Model B 8GB
  • Raspberry Pi OS 32bit (Linux arm 5.10.17-v7l+, Raspbian 8.3.0-6+rpi1)
  • Visual Studio Code ver 1.55.2
  • Python 3.7.3
  • OpenCV 4.5.1
  • Cmake 3.16.3
  • protobuf 3.5.1
  • RealSense L515
  • RealSense SDK 2.44.0

インストール

ではさっそくインストールを進めていきましょう。基本的にはRealSenseのGit内の手順Raspbian(RaspberryPi3) Installationに従いますが、そのままではうまく行きません。特にprotobufはGitのProtocol Buffers Google's data interchange formatの「C++ Installation - Unix」に記載の依存ツールを先に入れる必要があります。あと私Bash派なので環境パス周りがちょっと違ったりなど。以下の手順で進めます。

  • パッケージのアップデート
  • Cmakeインストール
  • スワップ追加
  • 依存パッケージインストール
  • udev ruleアップデート
  • パスの設定
  • protobufインストール
  • TBBインストール
  • OpenCVは既にインストール済みのためスキップ
  • RealSense SDKインストール
  • pyrealsense2インストール
  • OpenGLを有効化

パッケージのアップデート

まずは恒例のアップデート。パッケージを最新版にします。LXTerminalで以下のコマンドを実行します。

sudo apt update
sudo apt upgrade

大量のインストールにより後戻りできない感がすごいので念の為バックアップしておいたほうが良いです。手順はこちらが参考になります。

ラズパイ SDカードのバックアップ作成、最小イメージ作成~大容量カードにリストア

Cmakeインストール

Cmakeが無い場合はインストールします。

sudo apt install cmake

スワップ追加

標準100MBのスワップでは不足するようなので2GBに変更します。Gitでは編集にvimを使用していますが、個人的に使い勝手がいまいちなので標準で入っているnanoで編集します。

sudo nano /etc/dphys-swapfile

CONF_SWAPSIZE=100を2048に変更して保存します。「Ctrl+O」→「Enter」で保存。「Ctrl+X」で終了。

CONF_SWAPSIZEの変更

その後次のコードを実行。

sudo /etc/init.d/dphys-swapfile restart swapon -s

依存パッケージインストール

指定の依存パッケージをインストールします。所要時間は約3分。

sudo apt-get install -y libdrm-amdgpu1 libdrm-amdgpu1-dbgsym libdrm-dev libdrm-exynos1 libdrm-exynos1-dbgsym libdrm-freedreno1 libdrm-freedreno1-dbgsym libdrm-nouveau2 libdrm-nouveau2-dbgsym libdrm-omap1 libdrm-omap1-dbgsym libdrm-radeon1 libdrm-radeon1-dbgsym libdrm-tegra0 libdrm-tegra0-dbgsym libdrm2 libdrm2-dbgsym
sudo apt-get install -y libglu1-mesa libglu1-mesa-dev glusterfs-common libglu1-mesa libglu1-mesa-dev libglui-dev libglui2c2
sudo apt-get install -y libglu1-mesa libglu1-mesa-dev mesa-utils mesa-utils-extra xorg-dev libgtk-3-dev libusb-1.0-0-dev

udev ruleアップデート

udevのルールを更新します。最後の一行はrootで実行する必要がありましたのでGitの手順に少し手を加えます。所要時間は約3分。

cd ~
git clone https://github.com/IntelRealSense/librealsense.git
cd librealsense
sudo cp config/99-realsense-libusb.rules /etc/udev/rules.d/ 
sudo su
udevadm control --reload-rules && udevadm trigger
exit

パスの設定

パスを通すために.bashrcをテキストエディタで開いてコマンドを追記します。所要時間約1分。

nano ~/.bashrc

以下のコマンドを追加して保存。

export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

再度LXTerminalから次のコマンドを実行して有効化。

source ~/.bashrc

protobufインストール

さて一個目のハードル、protobufです。所要時間約50分。protobufはビルドするための依存ツールが複数あるためRealSenseのGit手順だけだとうまく行きません。まずは本家protobufのGit記載の依存ツールをインストールします。

https://github.com/protocolbuffers/protobuf/blob/master/src/README.md
sudo apt-get install autoconf automake libtool curl make g++ unzip

あとは順番にLXTerminalで実行していくのみですが、makeに40分くらい必要です。

cd ~
git clone --depth=1 -b v3.5.1 https://github.com/google/protobuf.git
cd protobuf
./autogen.sh
./configure
make -j1
sudo make install
cd python
export LD_LIBRARY_PATH=../src/.libs
python3 setup.py build --cpp_implementation 
python3 setup.py test --cpp_implementation
sudo python3 setup.py install --cpp_implementation
export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp
export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION_VERSION=3
sudo ldconfig
protoc --version

うまく行けばバージョン情報が確認できると思います。

TBBインストール

Intel Threading Building Blocksをインストールします。所要時間約2分。ラズパイにTBBを導入するためのdebパッケージを使います。

cd ~
wget https://github.com/PINTO0309/TBBonARMv7/raw/master/libtbb-dev_2018U2_armhf.deb
sudo dpkg -i ~/libtbb-dev_2018U2_armhf.deb
sudo ldconfig
rm libtbb-dev_2018U2_armhf.deb

OpenCVインストール

すでにOpenCVはインストール済みのためスキップします。インストール手順はこちら。

【IoT×キャンプ|OpenCVインストール編】意地でもラズパイとキャンプへ!らずキャン△プロジェクト第10回

RealSense SDKインストール

いよいよRealSense関連のツールをインストールしていきます。所要時間は約1時間50分です。コンパイルに死ぬほど時間がかかります。

cd ~/librealsense
mkdir  build  && cd build
cmake .. -DBUILD_EXAMPLES=true -DCMAKE_BUILD_TYPE=Release -DFORCE_LIBUVC=true
make -j1
sudo make install

pyrealsense2インストール

OpenCVでPython3を使いますのでPython3用をインストールします。所要時間は約45分。

cd ~/librealsense/build
cmake .. -DBUILD_PYTHON_BINDINGS=bool:true -DPYTHON_EXECUTABLE=$(which python3)
make -j1
sudo make install

インストールしたライブラリにパスを通します。

nano ~/.bashrc

次のコードを追記して保存。

export PYTHONPATH=$PYTHONPATH:/usr/local/lib

有効化します。

source ~/.bashrc

OpenGLを有効化

最後にラズパイのコンフィグでOpenGLを有効にします。Gitとリストの位置が違いましたが項目名自体は同じでした。所要時間約5分。

sudo apt-get install python-opengl
sudo -H pip3 install pyopengl
sudo -H pip3 install pyopengl_accelerate
sudo raspi-config
"6.Advanced Options" - "A2 GL Driver" - "G2 GL (Fake KMS)"
OpenGL有効化

最後に再起動したら全てのインストール作業は完了です!

動作確認

それではちゃんとインストールできているか確認しましょう。

ビューワーで動作確認

まずはLiDARのデータが取り込めているかサクッとビューワーで確認します。いきなりPythonに行くとエラー時に何がおかしいのか切り分けにくくなっちゃいますしね。

USBにLiDARを接続しLXTerminalで次のコマンドを実行。

realsense-viewer

イケてる雰囲気のビューワーが立ち上がるのでLiDARをONにしてみましょう。能力不足感は否めませんが、3Dイメージが表示されればSDKまではひとまずインストール成功です。

Pythonライブラリの動作確認

ビューワーでLiDARデータ取得の確認ができたら、次にPythonライブラリのpyrealsense2の動作確認をしましょう。初回トライ時はパスがちゃんと通っておらずimport時にモジュールが見つからないエラーが出ましたが、ブログ用に確認した今回の手順では問題ありませんでした。

では、確認のためLXTerminalでPython3を立ち上げます。

python3

次のコードを試します。執筆時点ではimportは問題なしでメソッドで以下のようなエラーになりました。
AttributeError: module 'pyrealsense2' has no attribute 'pipeline'
モジュールのミスだと思いますけど、よくよく見てみるとpyrealsense2が二重の構成になっていました。そのためimportで二回pyrealsense2を繰り返しています。これはそのうち解消されると思いますので、バージョンによりけりかと。

import pyrealsense2.pyrealsense2 as rs
pipeline = rs.pipeline()

エラー無しで実行できればライブラリ導入成功です。

pyrealsense2のimportテスト

これで全ての準備は完了ですのでいよいよPythonでプログラミングしていきましょう。

Pythonでストリーミングテスト

サンプルコードは下のとおりです。
LOG_PATH = 'Realsense/Log/'
の部分をご自身の環境に合わせて変更してください。ログを保存する場所の指定です。

サンプルとして実装した機能はこちら

  • カラーデータと深度データの取得
  • 表示用にリサイズ
  • リサイズしたカラーデータと深度データの表示
  • ログ機能
  • キー入力cでndarray次元情報等保存

このサンプルを応用するとOpenCVで画像処理がそのままできます。ただし配列のデータ型に注意してください。深度データはuint16です。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

###############################################
##  RealSense and OpenCV integration
###############################################

from logging import getLogger, StreamHandler, DEBUG, INFO, FileHandler, Formatter
import datetime
import os
import numpy as np
import cv2
import pyrealsense2.pyrealsense2 as rs


def main():

    logger.info("Start")

    # Configure depth and color streams
    pipeline = rs.pipeline()
    config = rs.config()

    # Set config
    config = set_rs_config(pipeline, config)

    # Start streaming
    pipeline.start(config)

    try:
        while True:

            # Wait for a coherent pair of frames: depth and color
            frames = pipeline.wait_for_frames()

            depth_image = get_depth_image(frames)
            color_image = get_color_image(frames)

            if np.all(depth_image == 0) or np.all(color_image == 0):
                continue

            # Apply colormap on depth image (image must be converted to 8-bit per pixel first)
            depth_colormap = cv2.applyColorMap(cv2.convertScaleAbs(depth_image, alpha=0.03), cv2.COLORMAP_JET)

            depth_colormap_dim = depth_colormap.shape
            color_colormap_dim = color_image.shape

            # If depth and color resolutions are different, resize color image to match depth image for display
            if depth_colormap_dim != color_colormap_dim:
                resized_color_image = cv2.resize(color_image, dsize=(depth_colormap_dim[1], depth_colormap_dim[0]), interpolation=cv2.INTER_AREA)
                images = np.hstack((resized_color_image, depth_colormap))
            else:
                images = np.hstack((color_image, depth_colormap))

            # Show images
            cv2.namedWindow('RealSense', cv2.WINDOW_AUTOSIZE)
            cv2.imshow('RealSense', images)

            # Select mode by key 
            k = cv2.waitKey(1) & 0xFF

            if k == ord('c'):
                check_dimension(depth_image, 'depth_image')
                check_dimension(color_image, 'color_image')

            if k == 27: # ESC
                logger.debug('Main: ESC')
                break

    except Exception as e:
        logger.error(f"Main:{e}")

    finally:

        # Stop streaming
        pipeline.stop()
        cv2.destroyAllWindows()
        logger.info("End")


def check_dimension(ndarray, name):
    
    logger.info(f'ndarray: Name={name}, ndarray={ndarray}')
    logger.info(f'ndim: Name={name}, ndim={ndarray.ndim}') 
    logger.info(f'shape: Name={name}, shape={ndarray.shape}') 


def set_rs_config(pipeline, config):
    
    # Get device product line for setting a supporting resolution
    pipeline_wrapper = rs.pipeline_wrapper(pipeline)
    pipeline_profile = config.resolve(pipeline_wrapper)
    device = pipeline_profile.get_device()
    device_product_line = str(device.get_info(rs.camera_info.product_line))

    # Set data format
    config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
    config.enable_stream(rs.stream.color, 1280, 720, rs.format.bgr8, 30)

    return config


def get_depth_image(frames):

    try:
        depth_frame = frames.get_depth_frame()
        depth_image = np.asanyarray(depth_frame.get_data())
        return depth_image

    except Exception as e:
        logger.error(f'get_depth_image:{e}')

    finally:
        return depth_image


def get_color_image(frames):
    
    try:
        color_frame = frames.get_color_frame()
        color_image = np.asanyarray(color_frame.get_data())

    except Exception as e:
        logger.error(f'get_color_image:{e}')
    
    finally:
        return color_image


def set_log_config(logger):

    dt_now = datetime.datetime.now()

    # Output destination
    logfolder = LOG_PATH + dt_now.strftime("%Y%m") + "/" 
    os.makedirs(logfolder, exist_ok=True) 

    # handler1 for terminal
    handler1 = StreamHandler()
    handler1.setLevel(DEBUG)
    handler1.setFormatter(Formatter("%(asctime)s %(levelname)8s %(message)s"))
 
    # handler2 for log file
    logpath = logfolder+dt_now.strftime("%Y%m%d_%H%M%S")+".log"
    handler2 = FileHandler(filename = logpath)
    handler2.setLevel(INFO)
    handler2.setFormatter(Formatter("%(asctime)s %(levelname)8s %(message)s"))

    logger.info(f'set_log_config: logpath = {logpath}')
 
    # Set handler to logger
    logger.setLevel(DEBUG)
    logger.addHandler(handler1)
    logger.addHandler(handler2)
    logger.propagate = False

    return logger


#### Global variables ####
# 個人環境に合わせて変更してください
LOG_PATH = 'Realsense/Log/'
dt_now = datetime.datetime.now()

## End of Global variables


#### Log Config ####
# loggingとloggerがややこしいのでloggingが候補に出てこないようimport指定
logger = getLogger(__name__)
logger = set_log_config(logger)
## End of Log config



#### Start ####
if __name__ == '__main__':
    main()


最後に

いかがでしたか?
流行りのLiDARをラズパイから制御できるってなんだか胸熱。

追記:残念ながらIntelはLiDAR開発から撤退しました。結構良いセンサーだと思ってたんですけどねー。

ラズパイPythonと三菱PLCをネットワーク経由で連携させる方法 ラズパイのCPUクロックをPythonでPID制御しCPU温度コントロール

スポンサーリンク

フォロワー

Labels

Amazon (3) Apache (3) Apple (9) AppleSilicon (7) Bloggerカスタマイズ (12) EchoShow15 (1) IoT (25) Jetson (1) MySQL (1) PHP (3) Python (20) Web (3) アウトドア (11) アメリカ生活 (19) ガジェット (35) キャンプ (9) ディープラーニング (1) らずキャン△ (11) ラズパイ (24) 暗号資産 (5) 開発 (31) 旅行 (8)

QooQ