unity检测范围内敌人_Unity AR Foundation 和 CoreML: 实现手部的检测和追踪

cab2e7121deb0f499f18b48f4372b6ab.png

0x00 前言

Unity的AR Foundation通过上层抽象,对ARKit和ARCore这些底层接口进行了封装,从而实现了AR项目的跨平台开发能力。

而苹果的CoreML是一个可以用来将机器学习模型与iOS平台上的app进行集成的框架。

本文以及本文结尾处的demo工程,将介绍和演示如何使Unity的AR Foundation与苹果的CoreML一同工作,以实现使用我们的手来和虚拟物体进行交互的功能。

830ed6a541f764b51c22d7a9798f9a73.png
Unity AR Foundation手部检测https://www.zhihu.com/video/1137418196633243648

本文参考了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

为了方便,我使用了本地package导入的形式。这种实现方式十分简单,只需要修改工程目录下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等等。

0c414f4d3dce4d482d52951b34156ea0.png

之后在我们的脚本中,监听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文件夹的目录结构如下:

5ca6ab58e7face3a3fdcdc1f048789d7.png

但是,需要注意的是,Unity直接导出的Xcode工程是没有指定Swift版本的。

e0cd95a8d0c1a48344a57cff5d950762.png

因此,我们需要手动指定一个版本,或者创建一个Unity的脚本来自动设置Swift的版本。

导入 mlmodel

将HandModel添加到我们的Xcode工程中,之后它会自动生成一个Objective-C model类。但是我希望得到一个Swift的类,因此我们可以在Build Settings/CoreML Model Compiler - Code Generation Language这里将选项从Auto修改为Swift。

之后,我们会获得一个叫做HandModel的自动生成的Swift类。

5babe3ef60c8823a7d4c5deccbb9d2c9.png

当然,如果你不想总是手动添加,同样也可以选择在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

具体来说,我们可以调用定义在XRCamera​Subsystem的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)。

b96502b76c40865362eaa8ca0a364660.png

一旦我们获取了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的位置会根据手的位置而改变。

ff65f92b07ccda7dce4ae136f86d8ebd.png

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

}

d616b247e9e55dd8199a2eb28fa507d2.png

0x02 结论

使用Unity中的AR Foundation和CoreML,我们可以让Unity Chan站在我们的手指上。

3bac420e0b40794314041b93e64f9593.png

本文简单描述了集成CoreML和AR Foundation的过程。我相信大家可以使用它们作出更有趣的内容。

这里是文中所使用的demo工程。

https://github.com/chenjd/Unity-ARFoundation-HandDetection​github.com

Useful Links

https://heartbeat.fritz.ai/hand-detection-with-core-ml-and-arkit-f4c8da98e88e​heartbeat.fritz.ai https://medium.com/@kevinhuyskens/implementing-swift-in-unity-53e0b668f895​medium.com Unity AR Foundation and CoreML: Hand detection and tracking​chenjd.xyz
53d04005bbcf4a6c472a858d6c51b1d8.png
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值