嘿!您似乎在 United States,您想使用我们的 English 网站吗?
Switch to English site
Skip to main content

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

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

作者

張嘉鈞

難度

普通

材料表

Intel RealSense D435 X1

NVIDIA Jetson Nano X1

目錄

安裝RealSense Viewer 3

安裝Pyrealsense2. 5

Python 範例程式詳解... 6

pyrealsense的基礎 ( python-tutorial-1-depth.py ) 6

透過opencv顯示pipeline的畫面 ( opencv_viewer_example.py ) 10

結語... 16

安裝RealSense Viewer

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

image0013_6ae90a7443357ef529d5fe4f910248e2c74ff87b.jpg

安裝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

image0023_eb8311a4950a2dafe2fd3913da72dbc7eb0de0e6.png

 

安裝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 的區域,並且將一公尺以內的深度影像轉換成文字模式,顯示結果如下。

image0035_9b6e924b06893ddffb593c0d93a990029344922d.png

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

# 導入函示庫
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 )

image0046_5ce6f390df31c5365fee2af9eabe9fadc7ed9f98.png

這個範例的重點在於「如何同時開啟深度與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:

image00610_e4b416638a9038ebc7c95c18b82ea572fad2652c.png

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

image0072_4c89e0a5a8a64cca3f65e0243ccde95da072522f.jpg

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

Alpha : 0.03

Alpha : 0.05

image0084_326263a208e3e8474da086ecf1d772e8b483e6d8.png image0095_825e8e38502351aafc848cf4e01f470249335d24.png

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

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

結語

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

相關文章

cv2.convertScaleAbs參考資料

ColorMap

CAVEDU Education is devoted into robotics education and maker movement since 2008, and is intensively active in teaching fundamental knowledge and skills. We had published many books for readers in all ages, topics including Deep Learning, edge computing, App Inventor, IoT and robotics. Please check CAVEDU's website for more information: http://www.cavedu.com, http://www.appinventor.tw