You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift ARKit 如何稳定追踪移动猫/狗以实现呼吸频率检测

猫狗呼吸频率检测AR应用开发问题

实现思路

计划开发一款可识别猫、狗并测量其每分钟呼吸次数的应用,核心实现逻辑:

  • 每100毫秒从宠物躯体区域采集100个点位的颜色值
  • 通过*Fourier Transform(傅里叶变换)*基于颜色的周期性变化计算宠物呼吸频率

当前问题

目前使用Vision库可通过相机快照检测猫、狗目标,但存在几个核心问题:

  • 担忧每100毫秒执行检测时,ARAnchor会被放置到错误位置,导致无法持续采集宠物区域的固定100个采样点
  • 现有代码运行后,渲染生成的sphereNode被绘制在分散的不同位置,并未落在猫的目标区域(腹部或头部)
  • 苹果官方提供了人体、面部追踪的配套能力,可通过ARFaceAnchor、ARBodyAnchor实现对应目标的稳定追踪,希望找到适用于猫的同类锚点追踪能力,确认「稳定追踪猫目标、持续获取锚点范围内固定采样点」的需求是否具备实现可行性

现有实现代码

import SceneKit
import ARKit
import Vision

class ViewController: UIViewController, ARSCNViewDelegate, ARSessionDelegate {
    
    var detectionAvailableUntil: Int64 = .zero
    @IBOutlet var sceneView: ARSCNView!
    private var viewportSize: CGSize!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        sceneView.delegate = self
        viewportSize = sceneView.frame.size
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        resetTracking()
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        sceneView.session.pause()
    }
    
    private func resetTracking() {
        let configuration = ARWorldTrackingConfiguration()
        configuration.planeDetection = []
        sceneView.session.run(configuration)
    }
    
    lazy var catRequest: VNRecognizeAnimalsRequest = {
        do {
            let req = VNRecognizeAnimalsRequest() { [weak self] request, error in
                self?.processCatDetections(for: request as! VNRecognizeAnimalsRequest, error: error)
            }
            
            return req
        } catch {
            fatalError("Fatal error")
        }
    }()
    
    func processCatDetections(for request: VNRecognizeAnimalsRequest, error: Error?) {
        guard error == nil else {
            print("Object detection error: \(error!.localizedDescription)")
            return
        }
        if let result = request.results?.first as? VNRecognizedObjectObservation {
            let cats = result.labels.filter({$0.identifier == "Cat"})
            for _ in cats {
                guard let currentFrame = self.sceneView.session.currentFrame,
                      result.confidence > 0.3 else { continue }
                let fromCameraImageToViewTransform = currentFrame.displayTransform(for: .portrait, viewportSize: self.viewportSize)
                let boundingBox = result.boundingBox
                let viewNormalizedBoundingBox = boundingBox.applying(fromCameraImageToViewTransform)
                let t = CGAffineTransform(scaleX: self.viewportSize.width, y: self.viewportSize.height)
                let viewBoundingBox = viewNormalizedBoundingBox.applying(t)
                let midPoint = CGPoint(x: viewBoundingBox.midX, y: viewBoundingBox.midY)
                let results = self.sceneView.hitTest(midPoint, types: .featurePoint)
                guard let result = results.first else { continue }
                let anchor = ARAnchor(name: "catAnchor", transform: result.worldTransform)
                self.sceneView.session.add(anchor: anchor)
            }
        }
    }
    
    func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) {
        guard let capturedImage = sceneView.session.currentFrame?.capturedImage else { return }
        let requestHandler = VNImageRequestHandler(cvPixelBuffer: capturedImage)
        
        do {
            try requestHandler.perform([self.catRequest])
        } catch {
            print("Error: Vision request failed")
        }
    }
    
    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        let date = Int64(NSDate().timeIntervalSince1970 * 1000)
        guard anchor.name == "catAnchor",
              date > self.detectionAvailableUntil else { return }
        print("catAnchor added")
        self.detectionAvailableUntil = Int64(NSDate().timeIntervalSince1970 * 1000 + 500)
        let sphereNode = SCNNode(geometry: SCNSphere(radius: 0.01))
        sphereNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red
        sphereNode.simdWorldTransform = anchor.transform
        node.addChildNode(sphereNode)
    }
}

可行性结论

  1. 苹果原生ARKit没有提供猫、狗等宠物对应的专用追踪锚点,不存在和ARFaceAnchor、ARBodyAnchor对等的系统级宠物躯体追踪能力,无法直接调用原生API实现猫的稳定3D锚点追踪。
  2. 你当前代码锚点分散的核心原因:一是每次检测到猫后直接取检测框中点做特征点hitTest,ARKit的特征点是环境稀疏点云,很多时候点落在猫身后的背景上,锚点自然不会落在猫身上;二是每帧检测都新增锚点,没有做锚点和跟踪目标的关联,新旧锚点位置没有连续性,自然会到处分散。
  3. 你的整体需求完全可以实现,不需要依赖系统提供的宠物专用锚点,优化方案如下:
    • 不要每帧跑全量动物检测、不要重复创建ARAnchor。首次检测到猫目标后,用Vision自带的VNTrackObjectRequest做目标持续跟踪,跟踪稳定性远高于每帧重检,性能开销也更低。
    • 采样逻辑不需要绑定3D ARAnchor,直接在2D摄像头帧的跟踪框内,固定选取胸腹部区域的100个采样点即可。呼吸频率检测只需要颜色的周期性变化,不需要精确3D位置,2D采样的误差更小、实现更简单。
    • 如果确实需要3D锚点做位置标注,只需要在首次检测确认猫的位置后创建1个锚点,后续每帧根据跟踪到的目标位置偏移,微调锚点位置即可,不要重复新增锚点。

内容的提问来源于stack exchange,提问作者Roman Polanski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 01:33:20