一. 概述
在邊緣運算的重點技術之中,模組輕量化網路架構 是不可或缺的一環,如何高效的利用硬體資源來達到最佳目標,特別是在效能與準確度的衡量上,是個非常有趣的議題。此章節再來探討深度學習熱門的研究項目之一 姿態估計(Pose Estimation) ,主要用途相當廣泛,像是跳舞與健身動作的監控、動畫製作等等。其中具代表性的神經網路架構或研究項目為 PoseNet、OpenPose、DensePose 等等,皆可以預測單人與多人姿態、動作。本範例將探討由 Google 發布的 PoseNet,其中結合了神經網路架構最輕量,運行效率極佳的 MobileNet V1 架構。故此為 輕量化網路架構 MobileNet 與 姿態估計(PoseNet) 組成之應用。。
若新讀者欲理解更多人工智慧、機器學習以及深度學習的資訊,可點選查閱下方博文
大大通精彩博文 【ATU Book-i.MX8系列】博文索引
TensorFlow Lite 進階系列博文-文章架構示意圖
二. 算法介紹
神經網路架構探討 :
姿態估計的實現並非著重於架構的改變,更重要的是如何運用特徵。如下圖所示,為 PoseNet 所運用的 17 個特徵點(key point) ; 分別為眼睛、耳朵、肩膀、手肘、手腕、臀部、膝蓋、腳踝。簡單來說,就是以 MobileNet 架構去預測這 17 個特徵點的位置資訊。
姿態估計(Pose Estimation) 特徵點示意圖
圖文來源 - Medium 網站
姿態估計概念是由 熱圖(keypoint heatmap) 與 偏移向量(offset vector) 兩大構成,如下圖所示。
姿態估計(Pose Estimation) 概念示意圖
圖文來源 - Medium 網站
所謂的 …
熱圖(keypoint heatmap) : 表示17個特徵點的所在位置,其輸出資訊為 M x N x 17。
偏移向量(offset vector) : 表示17個特徵點的偏移量,其輸出資訊為 M x N x 34,共 34 維度資訊,
前17個為 x 方向資訊,後 17 個為 y 方向資訊。
以影像大小 224x224 ,步幅 16 作為輸入的話。透過神經網路推理後,將會得到 14x14x17 的熱圖 與 14x14x34 的偏移向量資訊。每一個維度將各自代表對應的特徵(眼睛、耳朵、肩膀..等),比如在第 10 維度時所代表是手肘位置的資訊與偏移量,如下圖所示。
姿態估計(Pose Estimation) 概念示意圖 - 2
圖文來源 - Medium 網站
那如何判斷特徵點的資訊是不是為人??
如下圖所示,判斷是否為 人(Person) 的條件,是以每個 特徵點的信心度(Condidence) 作為分數。並加總平均求得,所謂的人體信心度(Person Confidence)。
姿態估計(Pose Estimation) 之信心度概念示意圖
圖文來源 - Medium 網站
順帶一提,剛剛敘述到的 步幅(Outout Stride) 大小,將會直接影響到準確度(accuacy)、執行速度(speed) 以及輸出的資訊量大小。
如下圖所示,表示說步幅越小的話,輸出的資訊量越大、推理時間拉長、但獲得預測效果卻越精準。反之則是預測越不準確,但執行效率越來越快。
姿態估計(Pose Estimation) 之步幅概念示意圖
圖文來源 - Medium 網站
延續上述話題,當檢測影像大小過小或是輸出步幅過大時,將會發現特徵點的準確度會下降。如下圖所示,測試影像越小時,則 熱圖(heatmap) 上綠色點分布就越模糊。
熱圖與準確度之實際圖片呈現
圖文來源 - github 網站
另外偏移向量的部分,亦可利用實際圖片來呈現。如下圖所示,第一行的影像為 x 偏移的呈現、第二行的影像為 y 偏移的呈現。同樣是觀察綠色的深淺,顏色越呈綠色表示偏移動量越大。
偏移向量與準確度之實際圖片呈現
圖文來源 - github 網站
最後一步,將各特徵點資訊串連起來就可以構成人體的姿體動作(多人),如下圖所示!!
三. 算法實現
Google 官方有提供效果極佳的 posenet_mobilenet_v1_075_353_481_quant.tflite 模組,可直接下載使用。因測試許多範例應用 (如 PoseEstimationForMobile、TensorFlow J.S to TensorFlow Lite、keras Realtime Multi-Person PoseEstimation 等等),最後實現的效益都不大。故此小節,選擇不進行訓練或遷移學習,直接套用官方現有資源來實現應用。
實現步驟如下 :
第一步 : 下載官方現有模組
第二步 : Pose Estimation 範例實現 (於 i.MX8M Plus 撰寫運行)
此步驟,所運用的輸出資訊比較複雜,故代碼較為冗長。
import cv2
import numpy as np
import math
from enum import Enum
from tflite_runtime.interpreter import Interpreter
# 解析 tensorflow lite 檔案
interpreterPoseEstimation = Interpreter(model_path='posenet_mobilenet_v1_075_353_451_quant.tflite')
interpreterPoseEstimation.allocate_tensors()
input_details = interpreterPoseEstimation.get_input_details()
output_details = interpreterPoseEstimation.get_output_details()
width = input_details[0]['shape'][2]
height = input_details[0]['shape'][1]
# 定義類別 – 人體的相關資訊
class Person:
def __init__(self):
self.keyPoints = []
self.score = 0.0
# 定義類別- 位置資訊
class Position:
def __init__(self):
self.x = 0
self.y = 0
# 定義類別- 身體特徵資訊(眼、眉毛、手腕等等)
class BodyPart(Enum):
NOSE = 0,
LEFT_EYE = 1,
RIGHT_EYE = 2,
LEFT_EAR = 3,
RIGHT_EAR = 4,
LEFT_SHOULDER = 5,
RIGHT_SHOULDER = 6,
LEFT_ELBOW = 7,
RIGHT_ELBOW = 8,
LEFT_WRIST = 9,
RIGHT_WRIST = 10,
LEFT_HIP = 11,
RIGHT_HIP = 12,
LEFT_KNEE = 13,
RIGHT_KNEE = 14,
LEFT_ANKLE = 15,
RIGHT_ANKLE = 16,
# 定義類別- 單一特徵點資訊
class KeyPoint:
def __init__(self):
self.bodyPart = BodyPart.NOSE
self.position = Position()
self.score = 0.0
# 讀取測試資料,並設置於解譯器中
frame = cv2.imread("pose_test_image.jpg")
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
frame_resized = cv2.resize(frame_rgb, (width, height))#frame_resized = np.array(frame_resized, dtype=np.float32)
input_data = np.expand_dims(frame_resized, axis=0)
interpreterPoseEstimation.set_tensor(input_details[0]['index'], input_data)
interpreterPoseEstimation.invoke() # 進行推理
# 整理推理後的輸出資訊 (heat and offset maps)
# output step 1 : 取得熱圖與偏移向量
heat_maps = interpreterPoseEstimation.get_tensor(output_details[0]['index'])
offset_maps = interpreterPoseEstimation.get_tensor(output_details[1]['index'])
height_ = heat_maps.shape[1]
width_ = heat_maps.shape[2]
num_key_points = heat_maps.shape[3]
key_point_positions = [[0] * 2 for i in range(num_key_points)] # 特徵點位置
# output step 2 : 從 熱圖(heat_maps) 取得位置資訊
for key_point in range(num_key_points):
max_val = heat_maps[0][0][0][key_point]
max_row = 0
max_col = 0
for row in range(height_):
for col in range(width_):
if heat_maps[0][row][col][key_point] > max_val:
max_val = heat_maps[0][row][col][key_point]
max_row = row
max_col = col
key_point_positions[key_point] = [max_row, max_col]
# output step 3 : 計算信心度(confidence_scores) 與實際位置
x_coords = [0] * num_key_points
y_coords = [0] * num_key_points
confidence_scores = [0] * num_key_points
output step 3 : 計算信心度(confidence_scores) 與實際位置
for i, position in enumerate(key_point_positions):
position_y = int(key_point_positions[i][0])
position_x = int(key_point_positions[i][1])
y_coords[i] = int(position[0])
x_coords[i] = int(position[1])
confidence_scores[i] = (float)(heat_maps[0][position_y][position_x][i] /255)
# output step 4 : 紀錄人體分數Person Score、特徵點資訊、
person = Person()
key_point_list = []
total_score = 0
for i in range(num_key_points): # 特徵點資訊
key_point = KeyPoint()
key_point_list.append(key_point)
for i, body_part in enumerate(BodyPart): # 特徵點資訊
key_point_list[i].bodyPart = body_part
key_point_list[i].position.x = x_coords[i]
key_point_list[i].position.y = y_coords[i]
key_point_list[i].score = confidence_scores[i]
total_score += confidence_scores[i]
person.keyPoints = key_point_list # 特徵點資訊
person.score = total_score / num_key_points # 人體分數 = 特徵點所有分數的平均
body_joints = [[BodyPart.LEFT_WRIST, BodyPart.LEFT_ELBOW],
[BodyPart.LEFT_ELBOW, BodyPart.LEFT_SHOULDER],
[BodyPart.LEFT_SHOULDER, BodyPart.RIGHT_SHOULDER],
[BodyPart.RIGHT_SHOULDER, BodyPart.RIGHT_ELBOW],
[BodyPart.RIGHT_ELBOW, BodyPart.RIGHT_WRIST],
[BodyPart.LEFT_SHOULDER, BodyPart.LEFT_HIP],
[BodyPart.LEFT_HIP, BodyPart.RIGHT_HIP],
[BodyPart.RIGHT_HIP, BodyPart.RIGHT_SHOULDER],
[BodyPart.LEFT_HIP, BodyPart.LEFT_KNEE],
[BodyPart.RIGHT_HIP, BodyPart.RIGHT_KNEE],
[BodyPart.LEFT_KNEE, BodyPart.LEFT_ANKLE],
[BodyPart.RIGHT_KNEE,BodyPart.RIGHT_ANKLE]]
# 將身體的特徵部位連線畫出 (頭部除外)
for line in body_joints:
if person.keyPoints[line[0].value[0]].score > 0.4 and person.keyPoints[line[1].value[0]].score > 0.4:
start_point_x = (int)(person.keyPoints[line[0].value[0]].position.x * frame.shape[1]/width_)
start_point_y = (int)(person.keyPoints[line[0].value[0]].position.y * frame.shape[0]/height_ )
end_point_x = (int)(person.keyPoints[line[1].value[0]].position.x * frame.shape[1]/width_)
end_point_y = (int)(person.keyPoints[line[1].value[0]].position.y * frame.shape[0]/height_ )
cv2.line(frame, (start_point_x, start_point_y) , (end_point_x, end_point_y), (255, 255, 0), 3)
# 畫出頭部特徵點資訊 – 取得左右耳朵、肩膀資訊
left_ear_x = (int)(person.keyPoints[3].position.x * frame.shape[1]/width_)
left_ear_y = (int)(person.keyPoints[3].position.y * frame.shape[0]/height_)
right_ear_x = (int)(person.keyPoints[4].position.x * frame.shape[1]/width_)
right_ear_y = (int)(person.keyPoints[4].position.y * frame.shape[0]/height_)
left_shoulder_x = (int)(person.keyPoints[5].position.x * frame.shape[1]/width_)
left_shoulder_y = (int)(person.keyPoints[5].position.y * frame.shape[0]/height_)
right_shoulder_x = (int)(person.keyPoints[6].position.x * frame.shape[1]/width_)
right_shoulder_y = (int)(person.keyPoints[6].position.y * frame.shape[0]/height_)
# 畫出頭部特徵點資訊 – 計算兩耳間與肩膀間重心值
start_point_x = (int) ((left_ear_x + right_ear_x)/2 )
start_point_y = left_ear_y
if(right_ear_y < left_ear_y) : start_point_y = right_ear_y
end_point_x = (int) ((left_shoulder_x + right_shoulder_x)/2 )
end_point_y = left_shoulder_y
if(right_shoulder_y > left_shoulder_y) : end_point_y = right_shoulder_y
cv2.line(frame, (start_point_x, start_point_y) , (end_point_x, end_point_y), (255, 255, 0), 3)
# 顯示結果
cv2.imshow(“posenet”,frame)
cv2.waitKey(0)
cv2.destroyAllWindows()
Pose Estimation 實現結果呈現
如下圖所示,成功識別出人的姿體動作,並以線段方式畫出人的形狀。
在 i.MX8M Plus 的 NPU 處理器,推理時間(Inference Time) 約 16 ms。
四. 結語
肢體識別應用(Pose Estimation) 透過所檢測到的 17 個人體關節的特徵點(如肩膀、手肘、手腕、臀部、膝蓋、腳踝等等),能夠廣泛利用在健身、醫療或是預測路人的行為等等應用。目前運行在 i.MX8MP 的 Vivante VIP8000 NPU,其推理時間可達每幀 16 ms 的處理速度,約 60 張 FPS,以及在單人檢測時,有不差的檢測率。下一章節將會介紹熱門應用之一的 “手骨識別應用(Hand Skeleton Detection)” ,敬請期待 !!
五. 參考文件
[1] SSD: Single Shot MultiBox Detector
[2] SSD-Tensorflow
[3] Single Shot MultiBox Detector (SSD) 論文閱讀
[4] ssd-mobilenet v1 演算法結構及程式碼介紹
[5] Introduction to Camera Pose Estimation with Deep Learning
[6] Towards Accurate Multi-person Pose Estimation in the Wild
[7] Real-time Human Pose Estimation in the Browser with TensorFlow.js
[8] keras_Realtime_Multi-Person_Pose_Estimation
如有任何相關 TensorFlow Lite 進階技術問題,歡迎至博文底下留言提問 !!
接下來還會分享更多 TensorFlow Lite 進階文章 !!敬請期待 【ATU Book-i.MX8系列 – TFLite 進階】 !!
評論