jjzjj

ios - 场景中的 ARKit 3D 头部跟踪

coder 2024-01-22 原文

我正在使用 ARKit 创建增强相机应用。当 ARSession 初始化时,一个 3d 字符显示在 ARSCNView 中。我正在尝试获取角色的 跟踪 ARCamera 的视角,以便在用户移动拍照时它们始终注视着相机。

我使用了 Apple 的变色龙演示,它添加了一个使用 SCNLookAtConstraint 跟踪相机视角的焦点节点,但我越来越 奇怪的行为。当 ARCamera 平移时,头部会掉到一边并旋转。如果我添加一个 SCNTransformConstraint 来限制 头部向上/向下/左右移动,它保持垂直但随后看向别处并且不跟踪。

我尝试将变色龙演示拆开,看看为什么我的演示不起作用,但几天后我就卡住了。

我使用的代码是:

class Daisy: SCNScene, ARCharacter, CAAnimationDelegate {

    // Rig for animation
    private var contentRootNode: SCNNode! = SCNNode()
    private var geometryRoot: SCNNode!
    private var head: SCNNode!
    private var leftEye: SCNNode!
    private var rightEye: SCNNode!

    // Head tracking properties
    private var focusOfTheHead = SCNNode()
    private let focusNodeBasePosition = simd_float3(0, 0.1, 0.25)

    // State properties
    private var modelLoaded: Bool = false
    private var headIsMoving: Bool = false
    private var shouldTrackCamera: Bool = false



    /*
    * MARK: - Init methods
    */

    override init() {

        super.init()

        loadModel()
        setupSpecialNodes()
        setupConstraints()

    }


    /*
    * MARK: - Setup methods
    */

    func loadModel() {

        guard let virtualObjectScene = SCNScene(named: "daisy_3.dae", inDirectory: "art.scnassets") else {
            print("virtualObjectScene not intialised")
            return
        }

        let wrapper = SCNNode()

        for child in virtualObjectScene.rootNode.childNodes {
            wrapper.addChildNode(child)
        }

        self.rootNode.addChildNode(contentRootNode)

        contentRootNode.addChildNode(wrapper)

        hide()

        modelLoaded = true

    }


    private func setupSpecialNodes() {

        // Assign characters rig elements to nodes
        geometryRoot = self.rootNode.childNode(withName: "D_Rig", recursively: true)
        head = self.rootNode.childNode(withName: "D_RigFBXASC032Head", recursively: true)
        leftEye = self.rootNode.childNode(withName: "D_Eye_L", recursively: true)
        rightEye = self.rootNode.childNode(withName: "D_Eye_R", recursively: true)

        // Set up looking position nodes
        focusOfTheHead.simdPosition = focusNodeBasePosition

        geometryRoot.addChildNode(focusOfTheHead)

    }


    /* 
    * MARK: - Head animations
    */ 

    func updateForScene(_ scene: ARSCNView) {

        guard shouldTrackCamera, let pointOfView = scene.pointOfView else {
            print("Not going to updateForScene")
            return
        }

        followUserWithHead(to: pointOfView)

    }

    private func followUserWithHead(to pov: SCNNode) {

        guard !headIsMoving else { return }

        // Update the focus node to the point of views position
        let target = focusOfTheHead.simdConvertPosition(pov.simdWorldPosition, to: nil)

        // Slightly delay the head movement and the animate it to the new focus position
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
            let moveToTarget = SCNAction.move(to: SCNVector3(target.x, target.y, target.z), duration: 1.5)
            self.headIsMoving = true
            self.focusOfTheHead.runAction(moveToTarget, completionHandler: {
                self.headIsMoving = false
            })
        })

    }



    private func setupConstraints() {

        let headConstraint = SCNLookAtConstraint(target: focusOfTheHead)
        headConstraint.isGimbalLockEnabled = true

        let headRotationConstraint = SCNTransformConstraint(inWorldSpace: false) { (node, transform) -> SCNMatrix4 in

            // Only track the up/down and side to side movement
            var eulerX = node.presentation.eulerAngles.x
            var eulerZ = node.presentation.eulerAngles.z

            // Restrict the head movement so it doesn't rotate too far
            if eulerX < self.rad(-90) { eulerX = self.rad(-90) }
            if eulerX > self.rad(90) { eulerX = self.rad(90) }

            if eulerZ < self.rad(-30) { eulerZ = self.rad(-30) }
            if eulerZ > self.rad(30) { eulerZ = self.rad(30) }

            let tempNode = SCNNode()
            tempNode.transform = node.presentation.transform
            tempNode.eulerAngles = SCNVector3(eulerX, 0, eulerZ)
            return tempNode.transform

        }

        head?.constraints = [headConstraint, headRotationConstraint]

    }

    // Helper to convert degrees to radians
    private func rad(_ deg: Float) -> Float {
        return deg * Float.pi / 180
    }

}

场景编辑器中的模型是:

最佳答案

我已经解决了我遇到的问题。有 2 个问题:

  1. followUserWithHead 中的目标应该已经为它的父级转换了 simdWorldPosition 并且从(而不是)转换为

    focusOfTheHead.parent!.simdConvertPosition(pov.simdWorldPosition, from: nil)

  2. 头节点的本地坐标不正确。 z 轴应该是 x 轴,所以当我获得焦点时头部运动跟踪,耳朵始终跟随相机。

我没有意识到 Xcode 中的Debug View Hierarchy 会显示 SCNScene 的细节。这帮助我调试场景并找到节点跟踪的位置。您可以将场景导出为 dae,然后加载到 SceneKit 编辑器中

编辑: 我将 localFront 用作下面评论中建议的 mnuages,这使跟踪工作朝着正确的方向进行。不过,头部确实偶尔会动一下。我已将此归因于在模型上运行的动画,该动画试图应用一个转换,然后在下一个更新周期中更改该转换。我决定从头部移除跟踪并使用相同的方法仅跟踪眼睛。

关于ios - 场景中的 ARKit 3D 头部跟踪,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48095715/

有关ios - 场景中的 ARKit 3D 头部跟踪的更多相关文章

  1. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  2. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  3. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  4. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  5. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  6. [Vuforia]二.3D物体识别 - 2

    之前说过10之后的版本没有3dScan了,所以还是9.8的版本或者之前更早的版本。 3d物体扫描需要先下载扫描的APK进行扫面。首先要在手机上装一个扫描程序,扫描现实中的三维物体,然后上传高通官网,在下载成UnityPackage类型让Unity能够使用这个扫描程序可以从高通官网上进行下载,是一个安卓程序。点到Tools往下滑,找到VuforiaObjectScanner下载后解压数据线连接手机,将apk文件拷入手机安装然后刚才解压文件中的Media文件夹打开,两个PDF图打印第一张A4-ObjectScanningTarget.pdf,主要是用来辅助扫描的。好了,接下来就是扫描三维物体。将瓶

  7. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  8. ruby - 为什么不能使用类IO的实例方法noecho? - 2

    print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

  9. python - Ruby 或 Python 的 3d 游戏引擎? - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion是否有适用于这些的3d游戏引擎?

  10. ruby-on-rails - factory_girl/rspec2 场景中的未定义方法 `each' - 2

    我正在尝试制作与投票相关的帖子。这样Post.votes就会生成与之关联的投票。Factory.define:voted_post,:parent=>:post,:class=>Postdo|p|p.association:votes,:factory=>:voteend我的rspec2相对简单:describe"votescores"doit"shouldshowmethetotalvotescore"do@post=Factory(:voted_post)@post.vote_score.should==1endend那么为什么会返回这个错误:Failures:1)Postvote

随机推荐