Jetson Nano搭配RealSenseD435-人臉辨識及距離偵測

在Jetson Nano上使用RealSense能獲取影像深度及色彩資訊,結合兩者可以製作許多有趣的專題。

在Jetson Nano上安裝並測試RealSense之後,可以開始撰寫自己專案要使用的程式了!

本篇文章要跟大家介紹如何在Jetson Nano上應用RealSense套件,提供Python範例及教學,讓大家可以輕鬆改寫成自己專案所需要的內容。讓對影像深度資訊有興趣,又有一片Jetson Nano及RealSense的使用者可以輕鬆入門。內文將會著重在RealSense於Jetson Nano上的Python程式撰寫,建議對Jetson Nano、Python有一些基礎再往下閱讀。

本文大綱

  • 解析官方範例程式
  • 取得單點深度資訊
  • 人臉辨識並取得臉部距離
作者/攝影  蔡雨錡
時間  3小時
難度  ★★★☆☆
材料表

解析官方範例程式

本篇選擇官方範例程式中的opencv_viewer_example.py來解析及作延伸,因為這個程式中使用常見的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區塊可以放入「無論如何都會關閉資源的程式碼」。在這個官方範例中,就是放入了關閉影像串流的程式。

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 鍵就關閉顯示影像的視窗

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

完整程式碼如下:

## 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)
        cv2.waitKey(1)

finally:
    pipeline.stop()

執行成果如下:

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

取得單點深度資訊

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

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

使用下列程式碼可以取得影像中像素(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)

完整程式碼如下:

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

執行成果如下:

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

人臉辨識並取得臉部距離

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

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

face_cascade = cv2.CascadeClassifier('/home/jetbot/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)
 
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)

        ################加入這段程式##################
        face_cascade = cv2.CascadeClassifier('/home/jetbot/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()

執行成果如下:

戴口罩也可以順利偵測得到人臉。並且可以偵測到該臉離鏡頭的距離。

一人版本:

多人版本:感謝同事們支援!

由於這篇的教學是入門教學,僅取人臉偵測方框的中間點深度值代表整臉,所以可能會出現下圖情況,左手邊的人臉深度值因為方框中間點值偵測為零,而有了不符合期待的顯示。想要進一步練習的人,可以試著將整個方框的所有像素深度值去極值後取平均,會有更精確的效果。或是去除背景及偵測失敗的像素後再取平均值也是好方法。

以上的範例是不是很有趣呢!還有很多RealSense搭配OpenCV的延伸應用等你發掘,有任何想深入研究的主題都可以跟我們分享,希望你能玩得開心~

發佈留言

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