乐趣区

关于人工智能:基于TensorFlow2x的实时多人二维姿势估计

作者 |Marcelo Rovai
编译 |VK
起源 |Towards Data Science

介绍

正如 Zhe Cao 在其 2017 年的论文中所述,实时多人二维姿态预计对于机器了解图像和视频中的人至关重要。

然而,什么是姿态预计

顾名思义,它是一种用来预计一个人身材地位的技术,比方站着、坐着或躺下。取得这一估计值的一种办法是找到 18 个“身材关节”或人工智能畛域中命名的“关键点(Key Points)”。上面的图像显示了咱们的指标,即在图像中找到这些点:

关键点从 0 点(上颈部)向下延长到身材关节,而后回到头部,最初是第 17 点(右耳)。

应用人工智能办法呈现的第一个有意义的工作是 DeepPose,2014 年由谷歌的 Toshev 和 Zegedy 撰写的论文。提出了一种基于深度神经网络(DNNs)的人体姿态预计办法,该办法将人体姿态预计归结为一个基于 DNN 的人体关节回归问题。

该模型由一个 AlexNet 后端(7 层)和一个额定的指标层,输入 2k 个关节坐标。这种办法的一个重要问题是,首先,模型应用程序必须检测到一个人(经典的对象检测)。因而,在图像上发现的每个人体必须离开解决,这大大增加了解决图像的工夫。

这种办法被称为“自上而下”,因为首先要找到身材,而后从中找到与其相关联的关节。

姿态预计的挑战

姿态预计有几个问题,如:

  1. 每个图像可能蕴含未知数量的人,这些人能够呈现在任何地位或比例。
  2. 人与人之间的相互作用会导致简单的空间烦扰,这是因为接触或肢体关节连贯,使得关节的关联变得艰难。
  3. 运行时的复杂性往往随着图像中的人数而减少,这使得实时性能成为一个挑战。

为了解决这些问题,一种更令人兴奋的办法是 OpenPose,这是 2016 年由卡内基梅隆大学机器人研究所的 ZheCao 和他的共事们引入的。

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=432×368(预处理时图像的大小)
  • 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),思考到所有应用的图像都应该被调整为 432×368。

参数:

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

请留神,此图像的形态为 567×567。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(2×19)与该像素与 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”。此外,相机也应设置为捕获模型应用的“432×368”帧。

参数初始化:

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/

退出移动版