找回密码
 注册帐号

扫一扫,访问微社区

士郎 在Unity中实现手部跟踪

20
回复
934
查看
[ 复制链接 ]
排名
1
昨日变化

8124

主题

8682

帖子

3万

积分

Rank: 16

UID
1231
好友
186
蛮牛币
12643
威望
30
注册时间
2013-7-29
在线时间
4168 小时
最后登录
2019-9-16

活力之星原创精华达人突出贡献奖财富之证游戏蛮牛QQ群会员蛮牛妹VIP

2019-8-21 10:50:08 显示全部楼层 阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
小时候,一直梦想着使用双手来远程操控物体。通常,我们可以触碰、移动、滚动和投掷物体,但我们不能像电影中的巫师或绝地大师,可以在不接触物体的情况下操控物体。



虽然这个梦想在现实生活中难以企及,但在虚拟世界中是存在可能的。

Microsoft Kinect曾非常受欢迎,因为用户可以通过使用它,来用双手切掉虚拟的水果。Leap Motion允许用户以精妙的方式和虚拟物体进行交互。

这些产品都需要使用额外的硬件,费用比较昂贵。即使拥有相应设备,普通用户也较难实现这种体验。如果有一种方法只使用带有普通RGB摄像头智能手机,就可以跟踪双手,这会是多酷的体验。

本文将介绍如何在Unity中通过使用RGB摄像头实现手部跟踪。


学习准备

  • 学习本文,你需要掌握以下知识内容:Unity的相关知识,本文开发使用的是Unity 2018.3.14。
  • OpenCV的基础知识。
  • 你需要在Asset Store资源商店获取OpenCV For :https://assetstore.unity.com/pac ... ncv-for-unity-21088
  • 对神经网络的基本了解。



手部跟踪的方法总结

使用RGB进行手部跟踪有三种方法:Haar级联方法,轮廓方法和神经网络方法。

下面是对三种方法的总结:

Haar级联方法

简单来说,这种方法很容易实现,而且速度非常快,但缺点是非常不稳定。

Haar级联方法是跟踪面部的常用方法,但手和脸不同,手没有固定的形态。如果只希望找到静态的标准手部图像,例如:手掌向前的张手画面,这种方法可能会起到作用。

轮廓方法

这是一种直截了当并易于实现的方法,提供很多可以定制的参数来根据用例调整,而且在计算方面的开销也不是很大。

如果用户在自己房间使用该方法,它会有很好的效果。但在移动设备上,由于光线会发生变化,背景会移动,而且用户周围会有其他人,因此该方法没有很理想的效果。

神经网络方法

这种方法在三种方法中有最强的稳定性,可以应对多种情形。然而,这种方法在计算方面的开销很大。

手部跟踪的实现

下面将介绍相应的代码,你可以访问GitHub获取完整的代码。https://github.com/teejs2012/Hand_Tracking_Unity3D

Haar级联方法

Haar级联是使用机器学习提取特征到XML文件的方法。

首先,我们需要获取训练好的XML文件,该文件叫palm.xml,它专门为识别手掌而训练。

获取训练好的XML文件,请访问:
https://github.com/Balaje/OpenCV/blob/master/haarcascades/palm.xml

在下载好该文件后,把它移动到Unity中的StreamingAssets文件夹。

加载级联文件时,使用以下代码:
[AppleScript] 纯文本查看 复制代码
var cascadeFileName = Utils.getFilePath("palm.xml");

cascadeDetector = new CascadeClassifier();

cascadeDetector.load(cascadeFileName);


运行检测时,我们会进行一些必要的预处理过程:把图像转换为灰度图,并调整直方图。然后,我们可以调用detectMultiScale函数。
[AppleScript] 纯文本查看 复制代码
MatOfRect hands = new MatOfRect();

Mat gray = new Mat(imgHeight, imgWidth, CvType.CV_8UC3);

Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY);

Imgproc.equalizeHist(gray, gray);

cascadeDetector.detectMultiScale(gray, hands, 1.1, 2, 0 | Objdetect.CASCADE_DO_CANNY_PRUNING | Objdetect.CASCADE_SCALE_IMAGE | Objdetect.CASCADE_FIND_BIGGEST_OBJECT, new Size(10, 10), new Size());

OpenCVForUnity.CoreModule.Rect[] handsArray = hands.toArray();

if (handsArray.Length != 0){

//手部已经检测到

}


轮廓方法

基于轮廓的方法是很直接的方法,这种方法只使用了计算机视觉,没有使用比较特别的模型。

轮廓方法基本上有二个主要步骤:

  • 找到图像上符合人类皮肤颜色的区域。
  • 找到符合手指的轮廓形状。



人类皮肤颜色通常是色谱的一部分,为了找到这类颜色,我们需要把颜色形式从RGB转换为YCrCb,并检查每个像素是否都在该范围内。
[AppleScript] 纯文本查看 复制代码
Mat YCrCb_image = new Mat();

int Y_channel = 0;

int Cr_channel = 1;

int Cb_channel = 2;

Imgproc.cvtColor(imgMat, YCrCb_image, Imgproc.COLOR_RGB2YCrCb);

var output_mask = Mat.zeros(imgWidth, imgHeight, CvType.CV_8UC1);

for (int i = 0; i < YCrCb_image.rows(); i++)

{

for (int j = 0; j < YCrCb_image.cols(); j++)

{

double[] p_src = YCrCb_image.get(i, j);

if (p_src[Y_channel] > 80 && p_src[Cr_channel] > 135 && p_src[Cr_channel] < 180 && p_src[Cb_channel] > 85 && p_src[Cb_channel] < 135)

{

output_mask.put(i, j, 255);

}

}


最终结果是一个遮罩,符合人类皮肤颜色的像素是白色,剩余部分是黑色。

在获得这个遮罩后,我们会提取遮罩的凸壳和轮廓,并检测“瑕疵”。“瑕疵”点表示轮廓线上远离凸壳的点。如果“瑕疵”的角度和长度等参数符合特定标准,那么我们就知道“瑕疵”对应着手指。

在找到足够的“手指瑕疵”时,我们会告诉系统找到了手部,根据经验,我们把这里的“足够”定义为1和4之间的数量。

下图是瑕疵点的图示,蓝线是凸壳,绿线是轮廓。

1.jpg
[AppleScript] 纯文本查看 复制代码
//在图像中找到轮廓

List<MatOfPoint> contours = new List<MatOfPoint>();

Imgproc.findContours(maskImage, contours, new MatOfPoint(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);



var points = new MatOfPoint(contours[index].toArray());

var hull = new MatOfInt();

Imgproc.convexHull(points, hull, false);



//找到瑕疵

var defects = new MatOfInt4();

Imgproc.convexityDefects(points, hull, defects);

var start_points = new MatOfPoint2f();

var far_points = new MatOfPoint2f();



//循环检查瑕疵,了解它是否符合条件

for (int i = 0; i < defects.size().height; i++)

{

int ind_start = (int)defects.get(i, 0)[0];

int ind_end = (int)defects.get(i, 0)[1];

int ind_far = (int)defects.get(i, 0)[2];

double depth = defects.get(i, 0)[3] / 256;

double a = Core.norm(contours[index].row(ind_start) - contours[index].row(ind_end));

double b = Core.norm(contours[index].row(ind_far) - contours[index].row(ind_start));

double c = Core.norm(contours[index].row(ind_far) - contours[index].row(ind_end));

double angle = Math.Acos((b * b + c * c - a * a) / (2 * b * c)) * 180.0 / Math.PI;

double threshFingerLength = ((double)maskImage.height()) / 8.0;

double threshAngle = 80;

if (angle < threshAngle && depth > threshFingerLength)

{



//起点

var aa = contours[index].row(ind_start);

start_points.push_back(contours[index].row(ind_start));

far_points.push_back(contours[index].row(ind_far));

}

}


// 检查找到的瑕疵是否在范围内

if (far_points.size().height > min_defects_count && far_points.size().height < max_defects_count)

{

//检测到手部

}


神经网络方式

在近几年,与用于许多任务的传统解决方案相比,基于神经网络的解决方案实现了更好的性能,这在计算机视觉领域特别明显。因为我们的任务是计算机视觉任务,所以我们自然希望使用神经网络。

实际上,目标检测在GitHub上有许多关于手部跟踪的优秀项目。例如:Victordibia的handtracking项目使用经典的目标检测模型架构 - 单步检测SSD。

下载了解Victordibia的handtracking项目:
https://github.com/victordibia/handtracking

单步检测SSD因其快捷的推理速度而出名,适用于实时应用。为了进一步提升速度,该模型使用了MobileNet结构来进行调整,最后我们得到了SSDMobileNet结构,预训练模型的大小只有约20MB的大小。

然而,这些解决方案使用Python和TensorFlow框架依赖来编写,我们要如何把它使用到Unity中呢?

一个热门方法是使用TensorflowSharp,即TensorFlow框架的C#版本。但这种方法的速度对实时应用来说太慢了,帧率甚至不到1fps。

我选择使用了OpenCV的DNN模块。该模块是OpenCVForUnity插件的一部分,提供了一些示例场景来展示使用方法。如果想要使用自定义模型,需要执行额外的步骤来把模型转换为OpenCV理解的格式。

1.jpg

首先要转换模型。OpenCV需要特别的pbtxt文件来使模型正常加载。生成该文件时,我们需要模型的冻结视图,以及针对模型的管线配置。

我从这个GitHub项目获取了二个相应的文件frozen_inference_graph.pb和ssd_mobilenet_v1_coco.config。

转换过程的实现,请查看Github的代码,生成出来的frozen_inference_graph.pbtxt文件也包含在其项目中。
https://github.com/teejs2012/OpenCV_DNN_with_Tensorflow_model

在准备好frozen_inference_graph.pb和frozen_inference_graph.pbtxt文件后,我们要把它们移动到Unity中的StreamingAssets文件夹。

加载模型时,使用以下代码:

[AppleScript] 纯文本查看 复制代码
var modelPath = Utils.getFilePath("frozen_inference_graph.pb");
var configPath = Utils.getFilePath("frozen_inference_graph.pbtxt");
tfDetector = Dnn.readNetFromTensorflow(modelPath, configPath);
进行检测时,使用以下代码:
var blob = Dnn.blobFromImage(image, 1, new Size(300, 300), new Scalar(0, 0, 0), true, false);
tfDetector.setInput(blob);
Mat prob = tfDetector.forward();
Mat newMat = prob.reshape(1, (int)prob.total() / prob.size(3));
float maxScore = 0;
int scoreInd = 0;
for (int i = 0; i < newMat.rows(); i++)
{
var score = (float)newMat.get(i, 2)[0];
if (score > maxScore)
{
maxScore = score;
scoreInd = i;
}
}
if (maxScore > thresholdScore)
{
// 检测到手部
}


我们要注意的一个参数是blobFromImage函数中的Size值。在传递到模型前,图像会调整为该数值的大小。

建议把Size设为300,因为这是模型的训练图像的大小,但如果帧率对应用很重要,该数值可以减少为150。

手指跟踪

你可能会注意到,我们做的这些事情有一个很大的限制:我们只能跟踪“手部”,而手指跟踪不包含在工具包中。

跟踪手指的方法也很多,其中一个流行方法是使用神经网络模型从RGB图像中估算3D手部姿势,你可以参考和下载以下代码:
https://github.com/Hzzone/pytorch-openpose

小结


在Unity中实现手部跟踪的方法为大家介绍到这里,让我们行动起来,在虚拟世界中成为可隔空控物的绝地大师吧。

作者: Jiasheng Tang  
来源:Unity官方平台


回复

使用道具 举报

2初来乍到
115/150

0

主题

64

帖子

115

积分

Rank: 2Rank: 2

UID
327304
好友
2
蛮牛币
173
威望
0
注册时间
2019-7-16
在线时间
33 小时
最后登录
2019-9-12
2019-8-21 11:08:04 显示全部楼层
不明觉厉
回复

使用道具 举报

3偶尔光临
290/300
排名
19948
昨日变化

2

主题

184

帖子

290

积分

Rank: 3Rank: 3Rank: 3

UID
328743
好友
0
蛮牛币
35
威望
0
注册时间
2019-8-5
在线时间
74 小时
最后登录
2019-9-16
2019-8-21 11:26:17 显示全部楼层
66666666666666
回复

使用道具 举报

4四处流浪
494/500
排名
7196
昨日变化

8

主题

78

帖子

494

积分

Rank: 4

UID
267888
好友
0
蛮牛币
701
威望
0
注册时间
2018-2-6
在线时间
218 小时
最后登录
2019-9-16
QQ
2019-8-21 11:39:21 显示全部楼层

不明觉厉
回复

使用道具 举报

4四处流浪
335/500
排名
11660
昨日变化

1

主题

77

帖子

335

积分

Rank: 4

UID
258424
好友
0
蛮牛币
252
威望
0
注册时间
2017-12-7
在线时间
173 小时
最后登录
2019-9-15
2019-8-21 14:00:43 显示全部楼层
仰望大佬
回复

使用道具 举报

排名
64937
昨日变化

0

主题

16

帖子

96

积分

Rank: 2Rank: 2

UID
313913
好友
0
蛮牛币
39
威望
0
注册时间
2019-2-15
在线时间
78 小时
最后登录
2019-9-12
2019-8-21 15:25:24 显示全部楼层
看着看着就懵了
回复

使用道具 举报

排名
15024
昨日变化

3

主题

55

帖子

167

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
55525
好友
0
蛮牛币
151
威望
0
注册时间
2014-11-15
在线时间
57 小时
最后登录
2019-9-16
2019-8-21 20:37:47 显示全部楼层
厉害了666666
回复

使用道具 举报

排名
15024
昨日变化

3

主题

55

帖子

167

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
55525
好友
0
蛮牛币
151
威望
0
注册时间
2014-11-15
在线时间
57 小时
最后登录
2019-9-16
2019-8-21 20:37:56 显示全部楼层
厉害了666666
回复

使用道具 举报

排名
15024
昨日变化

3

主题

55

帖子

167

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
55525
好友
0
蛮牛币
151
威望
0
注册时间
2014-11-15
在线时间
57 小时
最后登录
2019-9-16
2019-8-21 20:38:17 显示全部楼层
666666666
回复

使用道具 举报

7日久生情
2205/5000
排名
4093
昨日变化

0

主题

1467

帖子

2205

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
254705
好友
1
蛮牛币
1993
威望
0
注册时间
2017-11-16
在线时间
376 小时
最后登录
2019-9-16
2019-8-22 08:11:00 显示全部楼层
66666666666666666666666666666
回复 支持 反对

使用道具 举报

6蛮牛粉丝
1179/1500
排名
2066
昨日变化

0

主题

272

帖子

1179

积分

Rank: 6Rank: 6Rank: 6

UID
26073
好友
1
蛮牛币
4483
威望
0
注册时间
2014-5-21
在线时间
239 小时
最后登录
2019-9-16
2019-8-22 09:27:19 显示全部楼层
看着看着就懵了
回复

使用道具 举报

6蛮牛粉丝
1305/1500
排名
3155
昨日变化

25

主题

369

帖子

1305

积分

Rank: 6Rank: 6Rank: 6

UID
194101
好友
7
蛮牛币
7506
威望
0
注册时间
2016-12-19
在线时间
485 小时
最后登录
2019-9-16
2019-8-22 11:25:38 显示全部楼层
你咋嫩牛逼呢
回复

使用道具 举报

0

主题

13

帖子

42

积分

Rank: 1

UID
329538
好友
0
蛮牛币
203
威望
0
注册时间
2019-8-16
在线时间
29 小时
最后登录
2019-9-12
2019-8-23 12:02:11 显示全部楼层
腻害了66666666666666666666666666666666
回复 支持 反对

使用道具 举报

0

主题

7

帖子

10

积分

Rank: 1

UID
327879
好友
0
蛮牛币
0
威望
0
注册时间
2019-7-23
在线时间
3 小时
最后登录
2019-8-23
2019-8-23 18:27:24 显示全部楼层
6666666666666666666666有帮助
回复 支持 反对

使用道具 举报

6蛮牛粉丝
1330/1500
排名
2342
昨日变化

0

主题

327

帖子

1330

积分

Rank: 6Rank: 6Rank: 6

UID
119648
好友
3
蛮牛币
1789
威望
0
注册时间
2015-8-25
在线时间
397 小时
最后登录
2019-9-10
QQ
2019-8-26 09:19:53 显示全部楼层
66666666666
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则