嘿!您似乎在 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。

image0014_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

image0024_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 的区域,并且将一公尺以内的深度影像转换成文本模式,显示结果如下。

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

image0047_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:

image00611_e4b416638a9038ebc7c95c18b82ea572fad2652c.png

我们用更直观的方式来了解,这边使用的是D435,由于转换后的数值会影响ColorMap的结果,所以我们一样使用0.03跟0.05来测试一下,先给予cv2.COLORMAP_JET的色度图如下

image0073_4c89e0a5a8a64cca3f65e0243ccde95da072522f.jpg

可以看到由于数值0.03的时候最大距离为8公尺,我距离门只有2公尺所以颜色分布都还在前半段,可以想象将色度图切成八等分去抓颜色分布;而数值0.05最大距离为5公尺,所以2公尺处颜色分布会落在偏中间也就是绿色的位置:

Alpha : 0.03

Alpha : 0.05

image0085_326263a208e3e8474da086ecf1d772e8b483e6d8.png image0096_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
DesignSpark Electrical Logolinkedin