Intel 深度影像攝影機應用- 在NVIDIA Jetson Nano上使用RealSense D435-辨識人臉與距離

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

作者/攝影 郭俊廷
難度

★★★☆☆

材料表

RK-2020新版NVIDIA® Jetson Nano™ Developer Kit B01(簡配套件)

前言

之前的文章跟大家介紹深度攝影機RealSense D435相關資訊與應用還有OpenCV的基礎應用。本次的分享如何在Jetson Nano上改寫RealSense D435的Python範例,OpenCV搭配深度資訊辨識畫面中人臉與攝影機的距離。對Jetson Nano、Python、OpenCV不太熟悉有的讀者可以看先前的基礎文章。

 

相關文章請參考:

 

需要把RealSense D435相關的套件軟體安裝完成,在執行本篇文章的所有功能,相關安裝與設定請參考先前的文章。

 

本篇文章使用MobaXterm遠端連線操作範例,您也可以直接使用螢幕鍵盤滑鼠編輯相關內容

執行以下指令移動到RealSense的python/example資料夾

cd ~/librealsense/wrappers/python/examples

執行以下指令測試OpenCV是否成功安裝,如安裝成功會顯示OpenCV版本

python -c "import cv2; print(cv2.__version__)"

沒有安裝OpenCV時可以執行以下指令安裝OpenCV

sudo apt-get install python3-opencv

STEP1解析範例程式

執行以下指令可以看到RGB影像與深度影像

python3 opencv_viewer_example.py

本範例按下CTRL+C中斷程式並關閉攝影機畫面,根據下方增加的程式修改成在畫面上按Esc或q鍵關閉攝影機畫面

以下解析官方範例程式。

這個程式碼使用常見的OpenCV及NumPy套件,並且處理與顯示彩色影像資訊及深度影像資訊。是可以快速入門且方便的程式碼。

首先來看看程式碼的內容及解析。

第一段為匯入函式庫,包含PyRealSense2、NumPy、以及OpenCV。

import pyrealsense2 as rs
import numpy as np
import cv2

第二段中設定彩色影像串流以及深度影像串流,解析度設定為640×480。兩個影像資料格式不同:z16為16bit線性深度值,深度比例乘上該像素儲存的值可以得到以公尺為單位的測量深度值;bgr8很直觀為8bit的藍綠紅三顏色通道資訊,與OpenCV格式互通。對其他格式有興趣可以參考此文件

pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)

接下來就是開始影像串流

pipeline.start(config)

主要段落當中,使用了try/finally。我們之前的文章,大多數都是介紹try/except。except為try區塊中有發生例外則執行的區塊,而finally則是無論try區塊有無發生例外,finally區塊一定會被執行。常常製作影像相關專題的使用者可能會有過以下類似的經驗,如果沒有正確關閉資源,就會導致程式結束後,鏡頭模組資源被佔用,而無法順利執行下一個要使用鏡頭模組資源的程式。所以finally區塊可以放入「無論什麼狀況都需要關閉資源的程式碼」。在這個範例中,finally放入了關閉影像串流的程式碼。

try:
while True:
……
finally:
# 停止影像串流
pipeline.stop()

在while True迴圈中的第一段落為,等待同一幀的彩色跟深度影像才繼續執行後續影像處理,兩者缺一不可。

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

迴圈中第二段落將兩種影像資訊都轉換成NumPy陣列。

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

迴圈中第三段將深度影像資訊上假彩色 (影像必須事先轉換成每像素8bit)。假彩色很有趣,可以套用OpenCV不同的假彩色設定。除了cv2.COLORMAP_JET這個最常見的設定之外,也可以試試 cv2.COLORMAP_SUMMER、cv2.COLORMAP_OCEAN等不一樣的假彩色設定。

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

第四段中,使用hstack將彩色影像及深度影像兩張影像水平方向結合在一起,你也可以改成用vstack將兩張圖垂直結合在一起。能結合在一起的前提是影像要結合的邊像素數目要對上。

images = np.hstack((color_image, depth_colormap))

第五段為顯示影像。

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

第六段為設定按 esc 鍵或是 q 鍵就關閉顯示影像的視窗,也就是我們要增加的程式碼,可以使用nano編輯相關程式或是使用螢幕鍵盤滑鼠直接到目錄底下開啟該程式編輯

        key = cv2.waitKey(1)

if key & 0xFF == ord('q') or key == 27:
cv2.destroyAllWindows()
break

輸入以下指令編輯opencv_viewer_example.py檔案

nano ~/librealsense/wrappers/python/examples/opencv_viewer_example.py

完整程式碼如下:

## License: Apache 2.0. See LICENSE file in root directory.
## Copyright(c) 2015-2017 Intel Corporation. All Rights Reserved.

###############################################
## Open CV and Numpy integration ##
###############################################

import pyrealsense2 as rs
import numpy as np
import cv2

pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)

pipeline.start(config)

try:
while True:
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

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

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

images = np.hstack((color_image, depth_colormap))

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

key = cv2.waitKey(1)
if key & 0xFF == ord('q') or key == 27:
cv2.destroyAllWindows()
break

finally:
pipeline.stop()

左邊為彩色影像資訊、右邊為深度影像資訊。

這時按 Esc 鍵或是 q 鍵就可以關閉顯示影像的視窗

 

STEP2取得單點深度資訊

知道如何取得影像及如何影像串流之後,下一步就是練習取得像素單點的深度資訊。

我們要在上述範例中加入一小段程式碼來顯示取得的單點深度資訊。

首先,讓彩色影像跟深度影像對齊很重要!沒有對齊取得彩色資訊跟深度資訊就會搭不上,下列為兩組影像對齊的程式碼:

align_to = rs.stream.color
align = rs.align(align_to)
...
aligned_frames = align.process(frames)
depth_frame = aligned_frames.get_depth_frame()
color_frame = aligned_frames.get_color_frame()

使用下列程式碼取得影像中像素(x, y)的深度資訊:

depth_frame.get_distance(x, y)

為了方便示範,接下來的範例中會試圖取得影像正中央的像素,也就是點(320,240)的深度資訊。並將深度資訊做成字串。 深度值太長,所以使用np.round,取前幾個數值即可。

text_depth = "depth value of point (320,240) is "+str(np.round(depth_frame.get_distance(320, 240),4))+"meter(s)"

如果你不是很確定你取得的影像大小,可以使用下列的程式來進行確認。

print("shape of color image:{0}".format(color_image.shape))

接下來使用OpenCV的circle函式在彩色影像上用黃色標記我們要取值的點

color_image = cv2.circle(color_image,(320,240),1,(0,255,255),-1)

並在彩色影像上加上剛剛深度資訊的文字,範例中用紅色顯示。

color_image=cv2.putText(color_image, text_depth, (10,20),  cv2.FONT_HERSHEY_PLAIN, 1, (0,0,255), 1, cv2.LINE_AA)

輸入以下指令新增並編輯貼上以下程式碼

nano opencv_singlepoint_viewer_example.py   

完整程式碼如下:

import pyrealsense2 as rs
import numpy as np
import cv2

pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)

pipeline.start(config)

align_to = rs.stream.color
align = rs.align(align_to)

try:
while True:
frames = pipeline.wait_for_frames()
aligned_frames = align.process(frames)
depth_frame = aligned_frames.get_depth_frame()
color_frame = aligned_frames.get_color_frame()

if not depth_frame or not color_frame:
continue

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

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

#################加入這段程式#####################

#print("shape of color image:{0}".format(color_image.shape))
#print("shape of depth image:{0}".format(depth_colormap.shape))
#print("depth value in m:{0}".format(depth_frame.get_distance(320, 240)))


text_depth = "depth value of point (320,240) is "+str(np.round(depth_frame.get_distance(320, 240),4))+"meter(s)"
color_image = cv2.circle(color_image,(320,240),1,(0,255,255),-1)
color_image=cv2.putText(color_image, text_depth, (10,20), cv2.FONT_HERSHEY_PLAIN, 1, (0,0,255), 1, cv2.LINE_AA)

##################################################

images = np.hstack((color_image, depth_colormap))

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


key = cv2.waitKey(1)
if key & 0xFF == ord('q') or key == 27:
cv2.destroyAllWindows()
break


finally:
pipeline.stop()

執行成果如下:

可以看到深度距離訊息在彩色畫面左上角,且畫面正中間有黃色標記的點。這個範例程式可以做很多距離相關的延伸應用,例如放在百米衝刺終點線前,偵測哪位選手最快通過終點、安裝在空拍機上做田間或果園巡視、或是搭配大型螢幕做成展場與參觀者互動的遊戲裝置都很有趣。

 

STEP3人臉辨識並取得臉部距離

想要做人臉辨識的話,把上一範例中插入的程式碼改成以下內容,就可以偵測人臉,並在人臉的方框左上方顯示人臉距離!

加入的第一行為取得人臉資料集,根據教學經驗,雖然設定相對路徑比較簡潔優雅,但設定絕對路徑比相對路徑的成功率高,不需要確認目前資料夾位置,任意移動檔案時也不會出問題。有興趣的人可以玩看看haarcascades資料夾下的其他資料集。

face_cascade = cv2.CascadeClassifier('/home/your_user_name/opencv/data/haarcascades/haarcascade_frontalface_default.xml')

下一步將影像灰階化後比較方便偵測

gray = cv2.cvtColor(color_image, cv2.COLOR_BGR2GRAY)

接著設定人臉偵測的參數

faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5, minSize=(50,50))

每一張人臉都畫方框後標記深度距離。在這段範例程式中,我們設定偵測人臉方框正中央代表人臉與鏡頭的距離。顯示深度的字串位置如果直接設(x, y)會跟方框重疊在一起,所以將y修改為y-5讓文字高於方框。有任何顏色、粗細、字型、字體等喜好都可以自行做調整。

for (x, y, w, h) in faces:
cv2.rectangle(color_image, (x, y), (x+w, y+h), (255, 0, 0), 2)
text_depth = "depth is "+str(np.round(depth_frame.get_distance(int(x+(1/2)*w), int(y+(1/2)*h)),3))+"m"
color_image = cv2.putText (color_image, text_depth,(x, y-5), cv2.FONT_HERSHEY_PLAIN,1,(0,0,255),1,cv2.LINE_AA)

完整程式碼如下:

import pyrealsense2 as rs
import numpy as np
import cv2

pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)

pipeline.start(config)

align_to = rs.stream.color
align = rs.align(align_to)

try:
while True:
frames = pipeline.wait_for_frames()
aligned_frames = align.process(frames)
depth_frame = aligned_frames.get_depth_frame()
color_frame = aligned_frames.get_color_frame()
if not depth_frame or not color_frame:
continue

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

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

################加入這段程式##################
face_cascade = cv2.CascadeClassifier('/home/dlinano/opencv/data/haarcascades/haarcascade_frontalface_default.xml')

gray = cv2.cvtColor(color_image, cv2.COLOR_BGR2GRAY)

faces = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5, minSize=(50,50))

for (x, y, w, h) in faces:
cv2.rectangle(color_image, (x, y-5), (x+w, y+h), (255, 0, 0), 2)
text_depth = "depth is "+str(np.round(depth_frame.get_distance(int(x+(1/2)*w), int(y+(1/2)*h)),3))+"m"
color_image=cv2.putText(color_image,text_depth,(x,y-5),cv2.FONT_HERSHEY_PLAIN,1,(0,0,255),1,cv2.LINE_AA)
###############################################

images = np.hstack((color_image, depth_colormap))

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


key = cv2.waitKey(1)

if key & 0xFF == ord('q') or key == 27:
cv2.destroyAllWindows()
break


finally:
pipeline.stop()

首先我們要先下載OpenCV人臉資料集

輸入以下指令回到家目錄並且下載OpenCV資料集

cd ~
git clone https://github.com/opencv/opencv.git

下載完之後移動回範例資料夾,並執行以下指令新增並編輯貼上剛剛的程式碼

cd ~/librealsense/wrappers/python/examples
nano opencv_facedistance_viewer_example.py

執行人臉辨識及距離偵測的程式

python3 opencv_facedistance_viewer_example.py

執行成果如下:

戴口罩也可以偵測得到人臉,並且可以偵測到該臉離鏡頭的距離,可是只有某些特定角度時比較容易偵測的到,五官的輪廓度會影響到偵測的效果,不戴口罩的偵測效果較佳。

一人版本:

多人版本:

由於這篇的教學是入門教學,僅取人臉偵測方框的中間點深度值代表整臉。想要進一步練習的人,可以試著將整個方框的所有像素深度值去極值後取平均,會有更精確的效果。或是去除背景及偵測失敗的像素後再取平均值也是好方法。

以上是Intel 深度影像攝影機應用-在NVIDIA Jetson Nano上使用RealSense D435-單點深度資訊和人臉辨識及距離偵測的範例應用改寫,大家有沒有執行成功呢?是不是很有趣呢? 如果你也有做出其他有趣的範例歡迎跟我們分享。

 

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

發佈留言

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