Intel景深攝影機Python程式詳解1-於NVIDIA Jetson Nano執行RealSense D435 範例

之前的教學文當中主要教學如何安裝與使用RealSense,本次針對RealSense的Python 範例進行詳解。現在市面上相當多專案都使用上RealSense了,越來越多人有撰寫客製化程式的需求,這邊就帶大家認識Intel提供的Sample Code以及我們可以稍微修改的內容吧!

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

作者/攝影 張嘉鈞
難度

★★☆☆☆(普通)

材料表

RK-NVIDIA® Jetson Nano™ Developer Kit B01 套件

 

目錄

  • 安裝RealSense Viewer
  • 安裝Pyrealsense2
  • Python 範例程式詳解
  • pyrealsense的基礎 ( python-tutorial-1-depth.py )
  • 透過opencv顯示pipeline的畫面 ( opencv_viewer_example.py )
  • 結語

 

安裝RealSense Viewer

要使用RealSense系列的攝影機建議先安裝 RealSense Viewer,有一些圖形化的選項參數可以做微調與顯示,沒有驅動的時候也會自動搜尋並安裝,每次我要安裝新系統都會先安裝Viewer,確認抓得到深度攝影機之後才會再安裝pyrealsense2。

 

安裝RealSense Viewer的方法很簡單,在 librealsense 這個Github當中有介紹如何使用Jetson Nano來安裝Viewer,我擷取幾個重點步驟,想要了解更詳細的資訊可以到下列網址:https://github.com/IntelRealSense/librealsense/blob/master/doc/installation_jetson.md

 

1.準備好你的 Jetson Nano

2.確保你的環境是 NVIDIA®L4T Ubuntu 16.04 / 18.043 ( NVIDIA 原廠提供的映象檔 )

3.選擇 RealSense SDK 的後端 ( Backend ):這步可省略,通常需要客製化的使用者才需要來改變後端的API,我們使用預設的就可以了。

4.註冊伺服器公鑰

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

5.將伺服器加入repositories,待會透過apt-get去安裝才抓得到來源

Ubuntu 16請使用下列指令

$ sudo add-apt-repository "deb https://librealsense.intel.com/Debian/apt-repo xenial main" -u

Ubuntu 18 請使用下列指令

$ sudo add-apt-repository "deb https://librealsense.intel.com/Debian/apt-repo bionic main" -u

6.安裝SDK

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

7.開啟RealSense Viewer,可於終端機中輸入指令開啟

$ realsense-viewer

安裝Pyrealsense2

Pyrealsense是Intel RealSense的Python函式庫,透過這個函式庫可以開啟RealSense所有鏡頭以及取得到感測器的數值,對於Windows用戶,Intel已經提供 PyPI的發行版本可以透過 pip install pyrealsense2 來安裝,但是 Jetson Nano ( Ubuntu ) 只能從源頭安裝這個函式庫。

1.從源頭來安裝就需要將整個realsense的github下載下來:

$ git clone https://github.com/IntelRealSense/librealsense.git
$ cd librealsense

2. 確保apt-get的版本是最新的

$ sudo apt-get update && sudo apt-get upgrade

3.確保有安裝Python環境 ( 以Python3為例 )

$ sudo apt-get install python3 python3-dev

4.透過CMake建置,並且強制使用Python3 來編譯

$ mkdir build
$ cd build
$ cmake ../ -DBUILD_PYTHON_BINDINGS:bool=true -DPYTHON_EXECUTABLE=/usr/bin/python3.6
$ make -j5
$ sudo make install

5.將realsense的函式庫加入環境變數中

$ nano ~/.bashrc
export PYTHONPATH=$PYTHONPATH:/usr/local/lib/python3.6/pyrealsense2

6.接著可以導入函式庫,基本上沒報錯就沒問題了

$ python3
>> import pyrealsense2 as rs

 

Python 範例程式詳解

首先,先來丟個pyrealsense的文件,想要了解更多函式的用法建議一定要去看文件:https://intelrealsense.github.io/librealsense/python_docs/_generated/pyrealsense2.html

接著,這邊提供幾個範例程式的詳細解說,注意,此部分將不會放上完整程式碼,完整程式碼請至librealsense/wrappers/python/examples/ 中去查看:

 

Pyrealsense2基礎 ( python-tutorial-1-depth.py )

這個例子主要教學如何透過pyrealsense2開啟影像並擷取特定位置的深度資訊,此範例會將該即時影像劃之像素分成多個 10 x 20 的區域,並且將一公尺以內的深度影像轉換成文字模式,顯示結果如下。

首先我們需要導入函式庫:

# 導入函示庫

import pyrealsense2 as rs

使用pipline的方法存取RealSense攝影機並且透過config宣告基本的參數:

try:
    
# 建立一個context物件存放所有 RealSense 的處理函示

    pipeline = rs.pipeline()

    
# 配置串流物件

    config = rs.config()

    
# 宣告特定設備進行影像串流 ( 定義的相機, 寬, 高, realsense的型態, 幀率 )

    config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)

    
# 開啟影像串流

    pipeline.start(config)

接著因為是即時影像,所以需要使用While來運行,不斷的截取與處理新的影像,我們可以透過wait_for_frames取得最新的影像,因為我們一開始設定的pipeline是深度的攝影機 ( rs.stream.depth ),並且需要透過 get_depth_frame來取得深度影像,最後再做二次確認,如果沒有取得影像資訊後面的程式會報錯:

    
# 使用 While 迴圈不斷擷取、處理新的影像

    while True:
        
        
# 等待有新的影像資訊才繼續

        frames = pipeline.wait_for_frames()
        
        
# 取得深度影像

        depth = frames.get_depth_frame()

         
# 確保有獲得深度資訊 否則後續程式會出錯

        if not depth: continue

將輸入圖片 ( 480 x 640 ) 劃分成 ( 20 x 10 ) 的像素區塊進行深度的解析並將一公尺以內的畫面轉換成文字顯示,這部份我們將分成兩個部分介紹,首先先宣告文字覆蓋的陣列 ( coverage ),接著我們使用兩個For迴圈將所有的像素都讀取過一次,再讀取的同時先取得深度訊息 ( get_distance ) 再判斷是否於一公尺內,如果是的話就針對 coverage 的內容「加1」,這邊演算法比較特別的地方是由於 coverage的長度是64所以我們必須以10個單位內的像素做一個總和 ( 640/10=64 ),舉例來說如果位置0~9的像素都是一公尺以內的話最後coverage[0] 的數值就是10:

# 覆蓋範圍 

        coverage = [0]*64
        
# 逐個像素 進行 距離檢測 ( 高480 寬640)

        for y in range(480):
            for x in range(640):
                
# 透過 get_distance 取得該座標的深度

                dist = depth.get_distance(x, y)
                
# 如果物件在一公尺以內 就把Coverage填上 1 

                if 0 < dist and dist < 1:
                    
# 640(圖片) // 10(字串長度) = 64 ( Coverage大小)

                    coverage[x//10] += 1

我們在上述程式中處理了10 x 20像素區域10的部分,接著要處理20的部分並且最後要將文字顯示出來,因為像素區域高是20並且位置是從0開始所以我們取20的餘數等於19來判斷是否20行了,這裡我們需要將深度資訊轉換成文字內容,所以先宣告了一個line來存放文字資訊,接著去解析長度為64的coverage內容,針對coverage的內容整除25來轉換成文字,這邊除以25的部分我個人認為是為了讓輸出的文字模式圖像比例較為正常,最後再刷新coverage:

# 計算了20行的深度後 顯示一次 coverage 並刷新 

            if y%20 is 19:
                line = ""
                for c in coverage:
                    
# 整除25稍微整理一下,讓其比例看起來較像正常的圖像

                    line += " .:nhBXWW"[c//25]
                
# 刷新 coverage

                coverage = [0]*64
                print(line)

透過opencv顯示pipeline的畫面

( opencv_viewer_example.py )

這個範例的重點在於「如何同時開啟深度與RGB攝影機」、「如何轉換成OpenCV並且將其顯示出來」,那就讓我們開始解析程式吧!

首先導入函式庫以及宣告pipeline跟config:

# 導入函式庫

import pyrealsense2 as rs
import numpy as np
import cv2

# 建立一個context物件存放所有 RealSense 的處理函示

pipeline = rs.pipeline()

# 配置串流物件

config = rs.config()

接著這邊提供了取得設備資訊的方式,最後的 device_product_line 包含了首個可用設備的名稱:

# 可以透過下列程式取得設備資訊

# 透過 resolve 確認第一個可用的設備

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

接著要宣告影像串流,這次範例提供了深度與RGB影像,但因為L500系列之RGB攝影機解析度與其他系列不同,所以才需要取得攝影機名稱,這裡我們需要加上一個判斷,如果L500系列則RGB影像串流需要設定成960 x 540:

# 建立「深度」影像串流

config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)

# 建立「彩色」影像串流

# 由於 L500 系列的彩色攝影機解析度維 960 x 540 所以才需要取得設備資訊

if device_product_line == 'L500':
    config.enable_stream(rs.stream.color, 960, 540, rs.format.bgr8, 30)
else:
    config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)

接著就可以開始串流並且取得最新的影像資訊:

# 開啟影像串流

pipeline.start(config)

try:
    
    while True:

        
# 等待最新的影像,wait_for_frames返回的是一個合成的影像

        frames = pipeline.wait_for_frames()
        
        
# 可以透過下列函式各自取得深度或彩色影像

        depth_frame = frames.get_depth_frame()
        color_frame = frames.get_color_frame()
        
        
# 確保是否有取得影像

        if not depth_frame or not color_frame:
            continue

接著就是如何讓OpenCV可以顯示的部分了,首先要先來解決數據格式的問題,因為OpenCV吃的是Numpy格式所以我們需要先將其轉換成numpy array:

# 由於 Opencv 顯示格式為 numpy 所以需要轉換成 nparray

        depth_image = np.asanyarray(depth_frame.get_data())
        color_image = np.asanyarray(color_frame.get_data())

接著處理深度影像資訊的色彩格式,這部分稍微複雜了一些,先介紹一下功能,這邊會使用 cv2. convertScaleAbs將深度數值縮放到0~255之間並轉換成uint 8 的數據型態,再透過 applyColorMap將顏色對應上數值:

# 如果要顯示深度影像需要將其轉換成 8-bit 的影像格式

        depth_colormap = cv2.applyColorMap(cv2.convertScaleAbs(depth_image, alpha=0.03), cv2.COLORMAP_JET)
 

最後因為深度攝影機與RGB攝影機解析度不同的關係,需要將輸出圖片進行縮放才能夠合併在一起,需要先取得彼此維度 ( shape ),經過判斷之後再使用一般的 cv2.resize就可以縮放特定圖片了:

        
# 取得影像的維度

        depth_colormap_dim = depth_colormap.shape
        color_colormap_dim = color_image.shape

        
# 如果維度不同直接將 彩色圖片 進行 縮放,最後再水平合併 ( hstack )

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

接著就可以顯示合併後的圖片了:

# 顯示圖片

        cv2.namedWindow('RealSense', cv2.WINDOW_AUTOSIZE)
        cv2.imshow('RealSense', images)
        cv2.waitKey(1)

針對convertScaleAbs 可以有一些更深入的理解,這個函式的目的是將數值縮限到0~255之間並且計算絕對值後轉換成 uint-8 的數據形式,公式如下:

我們可以透過控制 alpha值來計算最後顯示的顏色最大距離是多少,寫一個簡單的程式來計算一下,我先宣告了一個簡單的陣列 (a),並且控制了b_alpha跟c_alpha的數值,各別為0.05、0.03,進行covert之後再顯示出來。

import cv2

def count_dis(alpha):
    return (255/alpha)

a = np.asarray( [[6000, 1000, 0, -100, 8000],
                [5000, 100 , 7500, 60, 5],
                [50, -1000, 225, 43, 192]] )

print('\nOriginal')
print(a)

b_alpha = 0.05
print(f'\nAlpha : {b_alpha},\tMax distance : {count_dis(b_alpha)}')
b = cv2.convertScaleAbs(a, alpha=b_alpha)
print(b)

c_alpha = 0.03
print(f'\nAlpha : {c_alpha},\tMax distance : {count_dis(c_alpha)}')
c = cv2.convertScaleAbs(a, alpha=c_alpha)
print(c)

顯示結果如下,可以注意到當alpha值為0.05最大距離數值為5100,所以大於5100都會被標為255,此外由於有絕對值所以負值都會被轉換成正值,1000跟 (-1000) 的數值都轉換後的都是50:

我們用更直觀的方式來了解,這邊使用的是D435,由於轉換後的數值會影響ColorMap的結果,所以我們一樣使用0.03跟0.05來測試一下,先給予cv2.COLORMAP_JET的色度圖如下

可以看到由於數值0.03的時候最大距離為8公尺,我距離門只有2公尺所以顏色分布都還在前半段,可以想像將色度圖切成八等分去抓顏色分布;而數值0.05最大距離為5公尺,所以2公尺處顏色分布會落在偏中間也就是綠色的位置:

Alpha : 0.03 Alpha : 0.05

 

我們將會在下一篇介紹其他功能:

  1. 對齊RGB跟Depth畫面並去除背景 ( align-depth2color.py )
  2. 解決掉幀問題 ( frame_queue_example.py )
  3. 讀取預錄好的深度影像方式 ( read_bag_example.py )

結語

在這篇我們學會怎麼在Jetson Nano上安裝RealSense Viewer以及Python函式庫 pyrealsense2,除此之外還深入了解了兩個範例程式的內容,下一篇將把剩餘的範例程式一一介紹完,後續也會撰寫一些簡單的程式給大家參考。

 

相關文章

 

*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *