Unity AR Foundation 和 CoreML: 实现手部的检测和追踪
0x00 前言
Unity的AR Foundation通过上层抽象,对ARKit和ARCore这些底层接口进行了封装,从而实现了AR项目的跨平台开发能力。
而苹果的CoreML是一个可以用来将机器学习模型与iOS平台上的app进行集成的框架。
本文以及本文结尾处的demo工程,将介绍和演示如何使Unity的AR Foundation与苹果的CoreML一同工作,以实现使用我们的手来和虚拟物体进行交互的功能。
Unity AR Foundation手部检测
本文参考了Gil Nakache的文章,并且所使用的机器学习模型也来自他的文章。在他的那篇文章中,他描述了如何使用Swift在iOS原生平台上实现类似的功能。
Version
Unity Version: 2018.3.13f1
Xcode Version: 10.2.1
The ARFoundation Plugin: 1.5.0-preview.5
iPhone 7: 12.3.1
0x01 实现
导入 AR Foundation Plugin
为了方便,我使用了本地pacakge导入的形式。这种实现方式十分简单,只需要修改工程目录下Package文件夹内的manifest.json文件,在manifest.json文件中添加本地package即可。
"com.unity.xr.arfoundation": "file:../ARPackages/com.unity.xr.arfoundation",
"com.unity.xr.arkit": "file:../ARPackages/com.unity.xr.arkit
导入AR Foundation Package之后,我们就可以在场景中创建一些相关的组件了,比如AR Session、AR Session Origin等等。
之后在我们的脚本中,监听frameReceived
事件来获取每一帧的数据。
if (m_CameraManager != null)
{
m_CameraManager.frameReceived += OnCameraFrameReceived;
}
使用Swift语言创建一个Unity插件
为了使C#语言可以和Swift语言进行交互,我们需要先创建一个Objective-C文件作为桥接。这种方式就是,C#通过[DllImport("__Internal")]
来调用一个Objective-C的方法。之后,Objective-C再通过@objc
来调用Swift。引入UnityInterface.h
之后,Swift可以调用UnitySendMessage
方法来向C#传送数据。
这里有一个示例工程,演示了如何为Unity创建一个使用Swift的原生插件,并且在Unity中打印出“Hello, I’m Swift”。
本文所使用的Unity-ARFoundation-HandDetection工程,它的plugins文件夹的目录结构如下:
但是,需要注意的是,Unity直接导出的Xcode工程是没有指定Swift版本的。
因此,我们需要手动指定一个版本,或者创建一个Unity的脚本来自动设置Swift的版本。
导入 mlmodel
将HandModel添加到我们的Xcode工程中,之后它会自动生成一个Objective-C model类。但是我希望得到一个Swift的类,因此我们可以在Build Settings/CoreML Model Compiler - Code Generation Language这里将选项从Auto修改为Swift。
之后,我们会获得一个叫做HandModel的自动生成的Swift类。
当然,如果你不想总是手动添加,同样也可以选择在Unity中创建一个build post processing脚本来自动添加机器学习模型。
如何从AR Foundation中获取ARFrame Ptr
完成了以上步骤之后,基本的交互框架就已经成型了。接下来,我们就需要使用CoreML来实现手部的检测和追踪的具体功能了。
@objc func startDetection(buffer: CVPixelBuffer) -> Bool {
//TODO
self.retainedBuffer = buffer
let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: self.retainedBuffer!, orientation: .right)
visionQueue.async {
do {
defer { self.retainedBuffer = nil }
try imageRequestHandler.perform([self.predictionRequest])
} catch {
fatalError("Perform Failed:\"\(error)\"")
}
}
return true
}
在Swift中,我们需要一个CVPixelBuffer
来创建VNImageRequestHandler
以执行手部检测。通常我们需要从ARFrame中来获取它。
CVPixelBufferRef buffer = frame.capturedImage;
因此,下一个问题就是如何从Unity的AR Foundation的C#脚本中获取来自ARKit的ARFrame指针,并且将其传递给使用Objective-C和Swift语言的Hand Detection插件。
在AR Foundation中,我们可以从XRCameraFrame
中获取nativePtr
,它指向一个ARKit的结构,如下所示:
typedef struct UnityXRNativeFrame_1
{
int version;
void* framePtr;
} UnityXRNativeFrame_1;
并且这个framePtr
指向了最新的ARFrame
。
具体来说,我们可以调用定义在XRCameraSubsystem的TryGetLatestFrame
方法来获取一个XRCameraFrame实例。
cameraManager.subsystem.TryGetLatestFrame(cameraParams, out frame)
之后将nativePtr从C#传递给Objective-C。
m_HandDetector.StartDetect(frame.nativePtr);
在Objective-C这边,我们会获得一个UnityXRNativeFrame_1
指针并且我们能从其中获取ARFrame
指针。
UnityXRNativeFrame_1* unityXRFrame = (UnityXRNativeFrame_1*) ptr;
ARFrame* frame = (__bridge ARFrame*)unityXRFrame->framePtr;
CVPixelBufferRef buffer = frame.capturedImage
一旦获取了ARFrame,接下来就来到了iOS开发的领域。创建一个VNImageRequestHandler对象并且开始执行手部检测。一旦检测完成,detectionCompleteHandler回调会被调用并且会通过UnitySendMessage
将检测的结果传递给Unity。
private func detectionCompleteHandler(request: VNRequest, error: Error?) {
DispatchQueue.main.async {
if(error != nil) {
UnitySendMessage(self.callbackTarget, "OnHandDetecedFromNative", "")
fatalError("error\(error)")
}
guard let observation = self.predictionRequest.results?.first as? VNPixelBufferObservation else {
UnitySendMessage(self.callbackTarget, "OnHandDetecedFromNative", "")
fatalError("Unexpected result type from VNCoreMLRequest")
}
let outBuffer = observation.pixelBuffer
guard let point = outBuffer.searchTopPoint() else{
UnitySendMessage(self.callbackTarget, "OnHandDetecedFromNative", "")
return
}
UnitySendMessage(self.callbackTarget, "OnHandDetecedFromNative", "\(point.x),\(point.y)")
}
}
之后我们会获取在viewport空间的position数据。viewport空间是相对于相机标准化的。 viewport的左下角是(0,0); 右上角是(1,1)。
一旦我们获取了viewport空间的位置,就可以通过Unity的ViewportToWorldPoint
方法将它从viewport空间转换到world空间。传递给该方法的向量参数中的x、y来自Hand Detection的结果,z值则是距离相机的距离。
var handPos = new Vector3();
handPos.x = pos.x;
handPos.y = 1 - pos.y;
handPos.z = 4;//m_Cam.nearClipPlane;
var handWorldPos = m_Cam.ViewportToWorldPoint(handPos);
我们可以在Unity中使用这个世界坐标来创建新的Object,或者是将已有的Object移动到这个世界坐标。换句话说,这个Object的位置会根据手的位置而改变。
Post Process Build
正如上文说过的,我们可以在Unity中写一个C#脚本来自动设置生成的Xcode工程中的一些属性。例如,我们可以设置Xcode工程中Build Setting中的Swift Version属性。我们甚至还可以将机器学习模型添加到Build Phases中,比如添加到Compile Sources Phase。这里我们会使用定义在UnityEditor.iOS.Xcode
命名空间中的PBXProject类。PBXProject类提供了很多有用的方法,例如AddBuildProperty
, SetBuildProperty
, AddSourcesBuildPhase
。
[PostProcessBuild]
public static void OnPostProcessBuild(BuildTarget buildTarget, string path)
{
if(buildTarget != BuildTarget.iOS)
{
return;
}
string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
var proj = new PBXProject();
proj.ReadFromFile(projPath);
var targetGUID = proj.TargetGuidByName("Unity-iPhone");
//set xcode proj properties
proj.AddBuildProperty(targetGUID, "SWIFT_VERSION", "4.0");
proj.SetBuildProperty(targetGUID, "SWIFT_OBJC_BRIDGING_HEADER", "Libraries/Plugins/iOS/HandDetector/Native/HandDetector.h");
proj.SetBuildProperty(targetGUID, "SWIFT_OBJC_INTERFACE_HEADER_NAME","HandDetector-Swift.h");
proj.SetBuildProperty(targetGUID, "COREML_CODEGEN_LANGUAGE", "Swift");
//add handmodel to xcode proj build phase.
var buildPhaseGUID = proj.AddSourcesBuildPhase(targetGUID);
var handModelPath = Application.dataPath + "/../CoreML/HandModel.mlmodel";
var fileGUID = proj.AddFile(handModelPath, "/HandModel.mlmodel");
proj.AddFileToBuildSection(targetGUID, buildPhaseGUID, fileGUID);
proj.WriteToFile(projPath);
}
0x02 结论
使用Unity中的AR Foundation和CoreML,我们可以让Unity Chan站在我们的手指上。
本文简单描述了集成CoreML和AR Foundation的过程。我相信大家可以使用它们作出更有趣的内容。
这里是文中所使用的demo工程。
https://github.com/chenjd/Unity-ARFoundation-HandDetection
Useful Links
https://heartbeat.fritz.ai/hand-detection-with-core-ml-and-arkit-f4c8da98e88e
https://medium.com/@kevinhuyskens/implementing-swift-in-unity-53e0b668f895
http://chenjd.xyz/2019/07/22/Unity-ARFoundation-CoreML/
Unity AR Foundation 和 CoreML: 实现手部的检测和追踪的更多相关文章
- 【Unity】Unity中AR Foundation的使用
前段时间通过Unity 3d打包测试对比ARCore与ARKit环境探针的效果的过程中,在Google AR Core官网下载到了ARCore for Unity SDK,但是在苹果官网却没有找到AR ...
- [Xcode 实际操作]七、文件与数据-(20)CoreML机器学习框架:检测和识别图片中的物体
目录:[Swift]Xcode实际操作 本文将演示机器学习框架的使用,实现对图片中物体的检测和识别. 首先访问苹果开发者网站关于机器学习的网址: https://developer.apple.com ...
- unity编辑器扩展_07(创建对话框,检测按钮的点击,点击按钮后提示信息,保存设置的数据,显示点击按钮后的处理的进度条信息)
代码: using UnityEditor;using UnityEngine; public class ChangeValue : ScriptableWizard { ...
- 基于Unity的AR开发初探:发布AR应用到Android平台
本文接上一篇,介绍一下如何通过Unity发布第一个AR应用至Android平台,在Android手机上使用我们的第一个AR应用. 一.一些准备工作 1.1 准备Java JDK 这里选择的是JDK 1 ...
- 基于Unity的AR开发初探:第一个AR应用程序
记得2014年曾经写过一个Unity3D的游戏开发初探系列,收获了很多好评和鼓励,不过自那之后再也没有用过Unity,因为没有相关的需求让我能用到.目前公司有一个App开发的需求,想要融合一下AR到A ...
- 移动应用中的AR开发,5款最受欢迎工具推荐!
英文原文:Top 5 Tools for Augmented Reality in Mobile Apps 还记得前段时间在网上很火的 3D 小熊不?托它的福,为相当一部分人科普了增强现实(AR) ...
- Unity 简记(2)--2D移动
目录 1.输入 1.1直接检测按下哪个按键 1.2.检测水平输入和垂直输入 2.移动 2.1.Transform组件 2.2.RigidBody组件 2.3.NavMeshAgent组件 2.4.Ch ...
- Unite Shanghai 2019全日程曝光(建议收藏)
https://mp.weixin.qq.com/s/KvAyXpDhqWROtTX1Ol3a4Q 5月10-12日,Unite Shanghai 2019即将在上海国际会议中心正式开幕.本次大会共设 ...
- 2019Unite大会
一年一度的Unite大会,在今年(2019)的5月10日准时在上海的国际会议中心盛大举行.本届大会历时三天,由来自全球的Unity公司技术专家.开发者.艺术家和Unity爱好者们齐聚一堂,展示Unit ...
随机推荐
- file.delete()与file.deleteOnExit(); 的区别
file.delete() //删除文件,删除的是创建File对象时指定与之关联创建的那个文件.这是一个立刻执行的操作 file.deleteOnExit(); //在JVM进程退出的时候删除 ...
- Delphi XE6 如何设计并使用FireMonkeyStyle
介绍 FireMonkey使用Style来控制控件的显示方式. 每个控件都有一个StyleLookup属性,FireMonkey就是通过控件的这个属性来在当前窗体的StyleBook控件中查找匹配 ...
- linux环境下使用百度云网盘
linux下经常需要备份一些文件到云端,现在能用的也就只有度娘的百度云网盘了,在github上发现一个挺好的项目,bypy,用来在linux下使用百度云. 项目地址:https://github.co ...
- 为什么有如此多的C++测试框架 - from Google Testing Blog
Why Are There So Many C++ Testing Frameworks? by Zhanyong Wan (Software Engineer) 最近貌似有很多人正在开发他们自己的C ...
- java集合的方法及使用详解
一.java集合的分类及相互之间的关系 Collection接口:向下提供了List和Set两个子接口 |------List接口:存储有序的,存储元素可以重复 |------ArrayList(主要 ...
- abp(net core)+easyui+efcore实现仓储管理系统——展现层实现增删改查之列表视图(七)
abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...
- 【MYSQL】mysql大数据量分页性能优化
转载地址: http://www.cnblogs.com/lpfuture/p/5772055.html https://www.cnblogs.com/shiwenhu/p/5757250.html ...
- 【静态NAT】 为什么子网可以ping父网,但是父网ping不通子网?
为什么子网可以ping父网,但是父网ping不通子网? 这就好比在公网中ping一个192.168.0.x的子网,roter无法找到这个子网的地址,所以会把package丢掉. 如何解决呢,可以在路由 ...
- 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象
前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...
- java 中的一些运算符问题
逻辑运算符 && 与 &: 在这二个与运算符中,一般用于if的判断中,A&&B,假设A的条件不满足时 则不会去判断后面的B, 如果A满足条件时就会接下来去做B条 ...