zoukankan      html  css  js  c++  java
  • 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

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

    一旦我们获取了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类提供了很多有用的方法,例如AddBuildPropertySetBuildPropertyAddSourcesBuildPhase

    [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/

  • 相关阅读:
    Office相关
    Eclipse常用设置
    Google logos 纪念电吉他大师莱斯·保罗(LesPaul)演示
    强烈推荐SQL Prompt 3.8,并发布SQL Prompt 3.8 ,SQL Refator 的xxx
    C#命令行编辑器csc.exe
    JSP中文乱码问题 页面经过过滤器后得到的是中文,但插入到MYSQL数据库却成了“?”为什么?
    (转贴)来谈谈SQL数据库中"简单的"SELECT TOP—可能有你从未注意到的细节
    C#Winform限制Textbox只能输入数字
    VPC2007虚拟机与主机的互连互通方法
    邮件会消亡是无稽之谈
  • 原文地址:https://www.cnblogs.com/murongxiaopifu/p/11241913.html
Copyright © 2011-2022 走看看