基於TensorFlow2.x的實時多人二維姿式估計

做者|Marcelo Rovai
編譯|VK
來源|Towards Data Sciencepython

介紹

正如Zhe Cao在其2017年的論文中所述,實時多人二維姿式估計對於機器理解圖像和視頻中的人相當重要。c++

然而,什麼是姿式估計

顧名思義,它是一種用來估計一我的身體位置的技術,好比站着、坐着或躺下。得到這一估計值的一種方法是找到18個「身體關節」或人工智能領域中命名的「關鍵點(Key Points)」。下面的圖像顯示了咱們的目標,即在圖像中找到這些點:git

關鍵點從0點(上頸部)向下延伸到身體關節,而後回到頭部,最後是第17點(右耳)。github

使用人工智能方法出現的第一個有意義的工做是DeepPose,2014年由谷歌的Toshev和Zegedy撰寫的論文。提出了一種基於深度神經網絡(DNNs)的人體姿式估計方法,該方法將人體姿式估計歸結爲一個基於DNN的人體關節迴歸問題。web

該模型由一個AlexNet後端(7層)和一個額外的目標層,輸出2k個關節座標。這種方法的一個重要問題是,首先,模型應用程序必須檢測到一我的(經典的對象檢測)。所以,在圖像上發現的每一個人體必須分開處理,這大大增長了處理圖像的時間。算法

這種方法被稱爲「自上而下」,由於首先要找到身體,而後從中找到與其相關聯的關節。後端

姿式估計的挑戰

姿式估計有幾個問題,如:數組

  1. 每一個圖像可能包含未知數量的人,這些人能夠出如今任何位置或比例。
  2. 人與人之間的相互做用會致使複雜的空間干擾,這是因爲接觸或肢體關節鏈接,使得關節的關聯變得困難。
  3. 運行時的複雜性每每隨着圖像中的人數而增長,這使得實時性能成爲一個挑戰。

爲了解決這些問題,一種更使人興奮的方法是OpenPose,這是2016年由卡內基梅隆大學機器人研究所的ZheCao和他的同事們引入的。bash

OpenPose

OpenPose提出的方法使用一個非參數表示,稱爲部分親和力場(PAFs)來「鏈接」圖像上的每一個身體關節,將它們與我的聯繫起來。網絡

換句話說,OpenPose與DeepPose相反,首先在圖像上找到全部關節,而後「向上」搜索最有可能包含該關節的身體,而不使用檢測人的檢測器(「自下而上」方法)。OpenPose能夠找到圖像上的關鍵點,而無論圖像上有多少人。下面的圖片是從ILSVRC和COCO研討會2016上的OpenPose演示中檢索到的,它讓咱們瞭解了這個過程。

下圖顯示了用於訓練的兩個多階段CNN模型的結構。首先,前饋網絡同時預測一組人體部位位置的二維置信度映射(關鍵點標註來自(dataset/COCO/annotations/)和一組二維的部分親和力場(L)。

在每個階段以後,兩個分支的預測以及圖像特徵被鏈接到下一個階段。最後,利用貪婪的推理對置信圖和類似域進行解析,輸出圖像中全部人的二維關鍵點。

在項目執行過程當中,咱們將回到其中一些概念進行澄清。可是,強烈建議你遵循2016年的OpenPose ILSVRC和COCO研討會演示(http://image-net.org/challeng... 2017的視頻錄製(https://www.youtube.com/watch...,以便更好地理解。

TensorFlow 2 OpenPose (tf-pose-estimation)

最初的OpenPose是使用基於模型的VGG預訓練網絡和Caffe框架開發的。可是,咱們將遵循Ildoo Kim 的TensorFlow實現,詳細介紹了他的tf-pose-estimation。

Github連接:https://github.com/ildoonet/t...

什麼是tf-pose-estimation?

tf-pose-estimation是一種「Openpose」算法,它是利用Tensorflow實現的。它還提供了幾個變體,這些變體對網絡結構進行了一些更改,以便在CPU或低功耗嵌入式設備上進行實時處理。

tf-pose-estimation的GitHub頁面展現了幾種不一樣模型的實驗,如:

  • cmu:原論文中描述的基於模型的VGG預訓練網絡的權值是Caffe格式,將其轉換並用於TensorFlow。
  • dsconv:除了mobilenet的深度可分離卷積以外,與cmu版本的架構相同。
  • mobilenet:基於mobilenet V1論文,使用12個卷積層做爲特徵提取層。
  • mobilenet v2:與mobilenet類似,但使用了它的改進版本。

本文的研究是在mobilenet V1(「mobilenet_thin」)上進行的,它在計算預算和延遲方面具備中等性能:

第1部分-安裝 tf-pose-estimation

咱們參考了Gunjan Seth的文章Pose Estimation with TensorFlow 2.0(https://medium.com/@gsethi240...

  • 轉到終端並建立一個工做目錄(例如,「Pose_Estimation」),並移動到那裏:
mkdir Pose_Estimation
cd Pose_Estimation
  • 建立虛擬環境(例如,Tf2_Py37)
conda create --name Tf2_Py37 python=3.7.6 -y 
conda activate Tf2_Py37
  • 安裝TF2
pip install --upgrade pip
pip install tensorflow
  • 安裝開發期間要使用的基本軟件包:
conda install -c anaconda numpy
conda install -c conda-forge matplotlib
conda install -c conda-forge opencv
  • 下載tf-pose-estimation:
git clone https://github.com/gsethi2409/tf-pose-estimation.git
  • 轉到tf-pose-estimation文件夾並安裝requirements
cd tf-pose-estimation/
pip install -r requirements.txt

在下一步,安裝SWIG,一個接口編譯器,用C語言和C++編寫的程序鏈接到Python等腳本語言。它經過在C/C++頭文件中找到的聲明來工做,並使用它們生成腳本語言須要訪問底層C/C++代碼的包裝代碼。

conda install swig
  • 使用SWIG,構建C++庫進行後處理。
cd tf_pose/pafprocess
swig -python -c++ pafprocess.i && python3 setup.py build_ext --inplace
  • 如今,安裝tf-slim庫,一個用於定義、訓練和評估TensorFlow中複雜模型的輕量級庫。
pip install git+https://github.com/adrianc-a/tf-slim.git@remove_contrib

就這樣!如今,有必要進行一次快速測試。返回tf-pose-estimation主目錄。

若是你按照順序,你必須在 tf_pose/pafprocess內。不然,請使用適當的命令更改目錄。
cd ../..

在tf-pose-estimation目錄中有一個python腳本 run.py,讓咱們運行它,參數以下:

  • model=mobilenet_thin
  • resize=432x368(預處理時圖像的大小)
  • image=./images/ski.jpg(圖像目錄內的示例圖像)
python run.py --model=mobilenet_thin --resize=432x368 --image=./images/ski.jpg

請注意,在幾秒鐘內,不會發生任何事情,但大約一分鐘後,終端應顯示與下圖相似的內容:

可是,更重要的是,圖像將出如今獨立的OpenCV窗口上:

太好了!這些圖片證實了一切都是正確安裝和運做良好的!咱們將在下一節中詳細介紹。

然而,爲了快速解釋這四幅圖像的含義,左上角(「Result」)是繪製有原始圖像的姿式檢測骨架(在本例中,ski.jpg)做爲背景。右上角的圖像是一個「熱圖」,其中顯示了「檢測到的組件」(S),兩個底部圖像都顯示了組件的關聯(L)。「Result」是把S和L鏈接起來。

下一個測試是現場視頻:

若是計算機只安裝了一個攝像頭,請使用:camera=0
python run_webcam.py --model=mobilenet_thin --resize=432x368 --camera=1

若是一切順利,就會出現一個窗口,裏面有一段真實的視頻,就像下面的截圖:

第2部分-深刻研究圖像中的姿式估計

在本節中,咱們將更深刻地介紹咱們的TensorFlow姿式估計實現。建議你按照這篇文章,試着複製Jupyter Notebook:10_Pose_Estimation_Images,能夠從GitHub項目下載:https://github.com/Mjrovai/TF...

做爲參考,這個項目是在MacPro( 2.9Hhz Quad-Core i7 16GB 2133Mhz RAM)上開發的。

導入庫

import sys
import time
import logging
import numpy as np
import matplotlib.pyplot as plt
import cv2

from tf_pose import common
from tf_pose.estimator import TfPoseEstimator
from tf_pose.networks import get_graph_path, model_wh

模型定義和TfPose Estimator建立

可使用位於model/graph子目錄中的模型,如mobilenet_v2_large或cmu(VGG pretrained model)。

對於cmu,*.pb文件在安裝期間沒有下載,由於它們很大。要使用它,請運行位於/cmu子目錄中的bash腳本download.sh。

這個項目使用mobilenet_thin(MobilenetV1),考慮到全部使用的圖像都應該被調整爲432x368。

參數:

model='mobilenet_thin'
resize='432x368'
w, h = model_wh(resize)

建立估計器:

e = TfPoseEstimator(get_graph_path(model), target_size=(w, h))

爲了便於分析,讓咱們加載一個簡單的人體圖像。OpenCV用於讀取圖像。圖像存儲爲RGB,但在內部,OpenCV與BGR一塊兒工做。使用OpenCV顯示圖像沒有問題,由於在特定窗口上顯示圖像以前,它將從BGR轉換爲RGB(如所示ski.jpg上一節)。

一旦圖像被打印到Jupyter單元上,Matplotlib將被用來代替OpenCV。所以,在顯示以前,須要對圖像進行轉換,以下所示:

image_path = ‘./images/human.png’
image = cv2.imread(image_path)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.imshow(image)
plt.grid();

請注意,此圖像的形狀爲567x567。OpenCV讀取圖像時,自動將其轉換爲數組,其中每一個值從0到255,其中0表示「白色」,255表示「黑色」。

一旦圖像是一個數組,就很容易使用shape驗證其大小:

image.shape

結果將是(567567,3),其中形狀是(寬度、高度、顏色通道)。

儘管可使用OpenCV讀取圖像,但咱們將使用tf_pose.common庫中的函數read_imgfile(image_path) 以防止顏色通道出現任何問題。

image = common.read_imgfile(image_path, None, None)

一旦咱們將圖像做爲一個數組,咱們就能夠將方法推理應用到估計器(estimator, e)中,將圖像數組做爲輸入

humans = e.inference(image, resize_to_default=(w > 0 and h > 0), upsample_size=4.0)

運行上述命令後,讓咱們檢查數組e.heatmap。該陣列的形狀爲(184,216,19),其中184爲h/2,216爲w/2,19與特定像素屬於18個關節(0到17)+1(18:none)中的一個的機率有關。例如,檢查左上角像素時,應出現「none」:

能夠驗證此數組的最後一個值

這是最大的值;能夠理解的是,在99.6%的機率下,這個像素不屬於18個關節中的任何一個。

讓咱們試着找出頸部的底部(肩膀之間的中點)。它位於原始圖片的中間寬度(0.5*w=108)和高度的20%左右,從上/下(0.2*h=37)開始。因此,讓咱們檢查一下這個特定的像素:

很容易意識到位置1的最大值爲0.7059…(或經過計算e.heatMat[37][108].max()),這意味着特定像素有70%的機率成爲「頸部」。下圖顯示了全部18個COCO關鍵點(或「身體關節」),顯示「1」對應於「頸部底部」。

能夠爲每一個像素繪製,一種表明其最大值的顏色。這樣一來,一張顯示關鍵點的熱圖就會神奇地出現:

max_prob = np.amax(e.heatMat[:, :, :-1], axis=2)
plt.imshow(max_prob)
plt.grid();

咱們如今在調整後的原始圖像上繪製關鍵點:

plt.figure(figsize=(15,8))
bgimg = cv2.cvtColor(image.astype(np.uint8), cv2.COLOR_BGR2RGB)
bgimg = cv2.resize(bgimg, (e.heatMat.shape[1], e.heatMat.shape[0]), interpolation=cv2.INTER_AREA)
plt.imshow(bgimg, alpha=0.5)
plt.imshow(max_prob, alpha=0.5)
plt.colorbar()
plt.grid();

所以,能夠在圖像上看到關鍵點,由於color bar上顯示的值意味着:若是黃色更深就有更高的機率。

爲了獲得L,即關鍵點(或「關節」)之間最可能的鏈接(或「骨骼」),咱們可使用e.pafMat的結果數組。其形狀爲(184,216,38),其中38(2x19)與該像素與18個特定關節+none中的一個做爲水平(x)或垂直(y)鏈接的一部分的機率有關。

繪製上述圖表的函數在Notebook中。

使用draw_human方法繪製骨骼

使用e.inference()方法的結果傳遞給列表human,可使用draw_human方法繪製骨架:

image = TfPoseEstimator.draw_humans(image, humans, imgcopy=False)

結果以下圖:

若是須要的話,能夠只繪製骨架,以下所示(讓咱們從新運行全部代碼進行回顧):

image = common.read_imgfile(image_path, None, None)
humans = e.inference(image, resize_to_default=(w > 0 and h > 0), upsample_size=4.0)
black_background = np.zeros(image.shape)
skeleton = TfPoseEstimator.draw_humans(black_background, humans, imgcopy=False)
plt.figure(figsize=(15,8))
plt.imshow(skeleton);
plt.grid(); 
plt.axis(‘off’);

獲取關鍵點(關節)座標

姿式估計可用於機器人、遊戲或醫學等一系列應用。爲此,從圖像中獲取物理關鍵點座標以供其餘應用程序使用可能頗有趣。

查看e.inference()生成的human列表,能夠驗證它是一個包含單個元素、字符串的列表。在這個字符串中,每一個關鍵點都以其相對座標和相關機率出現。例如,對於目前使用的人像,咱們有:

例如:

BodyPart:0-(0.49, 0.09) score=0.79
BodyPart:1-(0.49, 0.20) score=0.75
...
BodyPart:17-(0.53, 0.09) score=0.73

咱們能夠從該列表中提取一個數組(大小爲18),其中包含與原始圖像形狀相關的實際座標:

keypoints = str(str(str(humans[0]).split('BodyPart:')[1:]).split('-')).split(' score=')
keypts_array = np.array(keypoints_list)
keypts_array = keypts_array*(image.shape[1],image.shape[0])
keypts_array = keypts_array.astype(int)

讓咱們在原始圖像上繪製這個數組(數組的索引是 key point)。結果以下:

plt.figure(figsize=(10,10))
plt.axis([0, image.shape[1], 0, image.shape[0]])  
plt.scatter(*zip(*keypts_array), s=200, color='orange', alpha=0.6)
img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.imshow(img)
ax=plt.gca() 
ax.set_ylim(ax.get_ylim()[::-1]) 
ax.xaxis.tick_top() 
plt.grid();

for i, txt in enumerate(keypts_array):
    ax.annotate(i, (keypts_array[i][0]-5, keypts_array[i][1]+5)

建立函數以快速複製對通用圖像的研究:

Notebook顯示了迄今爲止開發的全部代碼,「encapsulated」爲函數。例如,讓咱們看看另外一幅圖像:

image_path = '../images/einstein_oxford.jpg'
img, hum = get_human_pose(image_path)
keypoints = show_keypoints(img, hum, color='orange')

img, hum = get_human_pose(image_path, showBG=False)
keypoints = show_keypoints(img, hum, color='white', showBG=False)

多人圖像

到目前爲止,只研究了包含一我的的圖像。一旦開發出從圖像中同時捕捉全部關節(S)和PAF(L)的算法,爲了簡單起見咱們找到最可能的鏈接。所以,獲取結果的代碼是相同的;例如,只有當咱們獲得結果(「human」)時,列表的大小將與圖像中的人數相匹配。

例如,讓咱們使用一個有五我的的圖像:

image_path = './images/ski.jpg'
img, hum = get_human_pose(image_path)
plot_img(img, axis=False)

算法發現全部的S和L都與這5我的聯繫在一塊兒。結果很好!

從讀取圖像路徑到繪製結果,全部過程都不到0.5秒,與圖像中發現的人數無關。

讓咱們把它複雜化,讓咱們看到一個畫面,人們更「混合」地在一塊兒跳舞:

image_path = '../images/figure-836178_1920.jpg
img, hum = get_human_pose(image_path)
plot_img(img, axis=False)

結果彷佛也很好。咱們只繪製關鍵點,每一個人都有不一樣的顏色:

plt.figure(figsize=(10,10))
plt.axis([0, img.shape[1], 0, img.shape[0]])  
plt.scatter(*zip(*keypoints_1), s=200, color='green', alpha=0.6)
plt.scatter(*zip(*keypoints_2), s=200, color='yellow', alpha=0.6)
ax=plt.gca() 
ax.set_ylim(ax.get_ylim()[::-1]) 
ax.xaxis.tick_top() 
plt.title('Keypoints of all humans detected\n')
plt.grid();

第三部分:視頻和實時攝像機中的姿式估計

在視頻中獲取姿式估計的過程與咱們對圖像的處理相同,由於視頻能夠被視爲一系列圖像(幀)。建議你按照本節內容,嘗試複製Jupyter Notebook:20_Pose_Estimation_Video:https://github.com/Mjrovai/TF...

OpenCV在處理視頻方面作得很是出色。

所以,讓咱們獲取一個.mp4視頻,並使用OpenCV捕獲其幀:

video_path = '../videos/dance.mp4
cap = cv2.VideoCapture(video_path)

如今讓咱們建立一個循環來捕獲每一幀。有了這個框架,咱們將應用e.inference(),而後根據結果繪製骨架,就像咱們對圖像所作的那樣。最後還包括了一個代碼,當按下一個鍵(例如「q」)時中止視頻播放。

如下是必要的代碼:

fps_time = 0

while True:
    ret_val, image = cap.read()
    
    humans = e.inference(image,
                         resize_to_default=(w > 0 and h > 0),
                         upsample_size=4.0)
    if not showBG:
        image = np.zeros(image.shape)
        image = TfPoseEstimator.draw_humans(image, humans, imgcopy=False)
        
    cv2.putText(image, "FPS: %f" % (1.0 / (time.time() - fps_time)), (10, 10),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
    cv2.imshow('tf-pose-estimation result', image)
    fps_time = time.time()
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

結果很好,可是有點慢。這部電影最初的幀數爲每秒30幀,將在「慢攝影機」中運行,大約每秒3幀。

使用實時攝像機進行測試

建議你按照本節內容,嘗試複製Jupyter Notebook:30_Pose_Estimation_Camera(https://github.com/Mjrovai/TF...

運行實時攝像機所需的代碼與視頻所用的代碼幾乎相同,只是OpenCV videoCapture()方法將接收一個整數做爲輸入參數,該整數表示實際使用的攝像機。例如,內部攝影機使用「0」和外部攝影機「1」。此外,相機也應設置爲捕捉模型使用的「432x368」幀。

參數初始化:

camera = 1
resize = '432x368'     # 處理圖像以前調整圖像大小
resize_out_ratio = 4.0 # 在熱圖進行後期處理以前調整其大小
model = 'mobilenet_thin'
show_process = False
tensorrt = False       # for tensorrt process
cam = cv2.VideoCapture(camera)
cam.set(3, w)
cam.set(4, h)

代碼的循環部分應與視頻中使用的很是類似:

while True:
    ret_val, image = cam.read()
    
    humans = e.inference(image,
                         resize_to_default=(w > 0 and h > 0),
                         upsample_size=resize_out_ratio)
    image = TfPoseEstimator.draw_humans(image, humans, imgcopy=False)
    cv2.putText(image, "FPS: %f" % (1.0 / (time.time() - fps_time)), (10, 10),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
    cv2.imshow('tf-pose-estimation result', image)
    fps_time = time.time()
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cam.release()
cv2.destroyAllWindows()

一樣,當使用該算法時,30 FPS的標準視頻捕獲下降約到10%。

這裏有一個完整的視頻,能夠更好地觀察到延遲。然而,結果是很是好的!

視頻:https://youtu.be/Ha0fx1M3-B4

結論

一如既往,我但願這篇文章能啓發其餘人在這個神奇的人工智能世界裏找到本身的路!

本文中使用的全部代碼均可以在GitHub項目上下載:https://github.com/Mjrovai/TF...

原文連接:https://towardsdatascience.co...

歡迎關注磐創AI博客站:
http://panchuang.net/

sklearn機器學習中文官方文檔:
http://sklearn123.com/

歡迎關注磐創博客資源彙總站:
http://docs.panchuang.net/

相關文章
相關標籤/搜索