获取 SCNNode / ARKit 中图像的大小 Swift

Get Size of image in SCNNode / ARKit Swift

我正在尝试扫描参考图像,然后在打印的参考图像上方显示图像本身。 "virutal" 图片尺寸应与打印尺寸相同。

我的想法:获取打印的Reference-Image的大小,然后将SCNNode中的图像缩放到这个大小(或者将SCNNode缩放到这个大小?)

但是:1-> 如何获取打印图像的大小,2-> 缩放 SCNNode 我也需要这个节点的大小。如何获得?

import UIKit
import SceneKit
import ARKit
import AVKit
import AVFoundation

class ViewController: UIViewController, ARSCNViewDelegate {

@IBOutlet var sceneView: ARSCNView!
private var planeNode: SCNNode?
private var imageNode: SCNNode?
private var animationInfo: AnimationInfo?
private var currentMediaName: String?
private var scrollView: UIScrollView!

override func viewDidLoad() {
    super.viewDidLoad()

    let scene = SCNScene()
    sceneView.scene = scene
    sceneView.delegate = self

}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // Load reference images to look for from "AR Resources" folder
    guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else {
        fatalError("Missing expected asset catalog resources.")
    }

    // Create a session configuration
    let configuration = ARWorldTrackingConfiguration()

    // Add previously loaded images to ARScene configuration as detectionImages
    configuration.detectionImages = referenceImages

    // Run the view's session
    sceneView.session.run(configuration)

    let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(rec:)))
    //Add recognizer to sceneview
    sceneView.addGestureRecognizer(tap)
}

//Method called when tap
@objc func handleTap(rec: UITapGestureRecognizer){
        let location: CGPoint = rec.location(in: sceneView)
        let hits = self.sceneView.hitTest(location, options: nil)
        if !hits.isEmpty{
            let tappedNode = hits.first?.node
            if tappedNode != nil && tappedNode?.name != nil{

                let stringArr = tappedNode?.name?.components(separatedBy: "-")
                let name = stringArr! [0]
                let size = stringArr! [1].components(separatedBy: ",")
                let width = Float(size [0])
                let height = Float(size [1])
                loadReferenceImage(tappedNode: tappedNode!, name: (name),  width: width!, height: height!)
            }
        }
}

private func playVideo() {
    guard let path = Bundle.main.path(forResource: "video", ofType:"m4v") else {
        debugPrint("video.m4v not found")
        return
    }
    let player = AVPlayer(url: URL(fileURLWithPath: path))
    let playerController = AVPlayerViewController()
    playerController.player = player
    present(playerController, animated: true) {
        player.play()
    }
}





func loadReferenceImage(tappedNode: SCNNode, name: String, width: Float, height: Float){
    print("TAP")
    print(name)

    let currentNode = tappedNode.parent

    if let image = UIImage(named: "col" + name){

        let childNodes = currentNode?.childNodes
        for node in (childNodes)!{
            node.removeFromParentNode()
        }

        let newImage = UIImage(named: "col" + name)
        let newnode = SCNNode(geometry: SCNPlane(width: CGFloat(width), height: CGFloat(height)))
        newnode.geometry?.firstMaterial?.diffuse.contents = newImage
        newnode.scale = SCNVector3(x: 10, y: 10, z: 10)
        currentNode?.removeAnimation(forKey: "spin_around")



        let rotation = SCNVector3((currentNode?.eulerAngles.x)!-0.95,(currentNode?.eulerAngles.y)!,(currentNode?.eulerAngles.z)!)
        currentNode?.eulerAngles = rotation



        //SIZE??????

        let nodex = currentNode?.scale.x
        let nodey = currentNode?.scale.y
        let nodez = currentNode?.scale.z

        let factorx = width / nodex!
        let factory = height / nodey!

        currentNode?.addChildNode(newnode)
    }



}

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    guard let imageAnchor = anchor as? ARImageAnchor else {
        return
    }


    // 1. Load scene.
    let planeScene = SCNScene(named: "art.scnassets/plane.scn")!
    let planeNode = planeScene.rootNode.childNode(withName: "planeRootNode", recursively: true)!

    // 2. Calculate size based on planeNode's bounding box.
    let (min, max) = planeNode.boundingBox
    let size = SCNVector3Make(max.x - min.x, max.y - min.y, max.z - min.z)

    // 3. Calculate the ratio of difference between real image and object size.
    // Ignore Y axis because it will be pointed out of the image.
    let widthRatio = Float(imageAnchor.referenceImage.physicalSize.width)/1.2
    let heightRatio = Float(imageAnchor.referenceImage.physicalSize.height)/1.2


    let width = imageAnchor.referenceImage.physicalSize.width
    let height = imageAnchor.referenceImage.physicalSize.height

    let prefix = "-"

    let imageSize = width.description + "," + height.description


    let targetName = imageAnchor.referenceImage.name! + prefix + imageSize


    // Pick smallest value to be sure that object fits into the image.
    let finalRatio = [widthRatio, heightRatio].min()!

    // 4. Set transform from imageAnchor data.
    planeNode.transform = SCNMatrix4(imageAnchor.transform)

    // 5. Animate appearance by scaling model from 0 to previously calculated value.
    let appearanceAction = SCNAction.scale(to: CGFloat(finalRatio), duration: 0.4)
    //test
    appearanceAction.timingMode = .easeOut

    // Set initial scale to 0.
    planeNode.scale = SCNVector3Make(0 , 0, 0)

    //rotate y
    let spin = CABasicAnimation(keyPath: "rotation")
    spin.fromValue = NSValue(scnVector4: SCNVector4(x: 0, y: 1, z: 0, w: 0))
    spin.toValue = NSValue(scnVector4: SCNVector4(x: 0, y: 1, z: 0, w: Float(CGFloat(2 * Double.pi))))
    spin.duration = 4
    spin.repeatCount = .infinity
    planeNode.addAnimation(spin, forKey: "spin_around")

    // Add to root node.
    sceneView.scene.rootNode.addChildNode(planeNode)
    // Run the appearance animation.
    planeNode.runAction(appearanceAction)
    planeNode.name = targetName

    let nodes = planeNode.childNodes
    for node in nodes{
        node.name = targetName
    }

    self.planeNode = planeNode
    self.imageNode = node

}

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor, updateAtTime time: TimeInterval) {
    guard let imageNode = imageNode, let planeNode = planeNode else {
        return
    }

    // 1. Unwrap animationInfo. Calculate animationInfo if it is nil.
    guard let animationInfo = animationInfo else {
        refreshAnimationVariables(startTime: time,
                                  initialPosition: planeNode.simdWorldPosition,
                                  finalPosition: imageNode.simdWorldPosition,
                                  initialOrientation: planeNode.simdWorldOrientation,
                                  finalOrientation: imageNode.simdWorldOrientation)
        return
    }

    // 2. Calculate new animationInfo if image position or orientation changed.
    if !simd_equal(animationInfo.finalModelPosition, imageNode.simdWorldPosition) || animationInfo.finalModelOrientation != imageNode.simdWorldOrientation {

        refreshAnimationVariables(startTime: time,
                                  initialPosition: planeNode.simdWorldPosition,
                                  finalPosition: imageNode.simdWorldPosition,
                                  initialOrientation: planeNode.simdWorldOrientation,
                                  finalOrientation: imageNode.simdWorldOrientation)
    }

    // 3. Calculate interpolation based on passedTime/totalTime ratio.
    let passedTime = time - animationInfo.startTime
    var t = min(Float(passedTime/animationInfo.duration), 1)
    // Applying curve function to time parameter to achieve "ease out" timing
    t = sin(t * .pi * 0.5)

    // 4. Calculate and set new model position and orientation.
    let f3t = simd_make_float3(t, t, t)
    planeNode.simdWorldPosition = simd_mix(animationInfo.initialModelPosition, animationInfo.finalModelPosition, f3t)
    planeNode.simdWorldOrientation = simd_slerp(animationInfo.initialModelOrientation, animationInfo.finalModelOrientation, t)
    //planeNode.simdWorldOrientation = imageNode.simdWorldOrientation

    guard let currentImageAnchor = anchor as? ARImageAnchor else { return }
}



func refreshAnimationVariables(startTime: TimeInterval, initialPosition: float3, finalPosition: float3, initialOrientation: simd_quatf, finalOrientation: simd_quatf) {
    let distance = simd_distance(initialPosition, finalPosition)
    // Average speed of movement is 0.15 m/s.
    let speed = Float(0.15)
    // Total time is calculated as distance/speed. Min time is set to 0.1s and max is set to 2s.
    let animationDuration = Double(min(max(0.1, distance/speed), 2))
    // Store animation information for later usage.
    animationInfo = AnimationInfo(startTime: startTime,
                                  duration: animationDuration,
                                  initialModelPosition: initialPosition,
                                  finalModelPosition: finalPosition,
                                  initialModelOrientation: initialOrientation,
                                  finalModelOrientation: finalOrientation)
}

}

为了做到这一点,我认为首先您需要通过

获得 UIImagePixels 中的大小

multiplying the size values by the value in the scale property to get the pixel dimensions of the image.

这样的例子是这样的:

guard let image = UIImage(named: "launchScreen") else { return }
let pixelWidth = image.size.width * image.scale
let pixelHeight = image.size.height * image.scale
print(pixelWidth, pixelHeight)

我在 Adobe Illustrator 中制作的图像尺寸为 3072 x 4099,当我在控制台中记录结果时尺寸也相同。

现在这里棘手的部分是将像素计算为我们可以在 ARKit 中使用的大小,记住不同的设备具有不同的 PPI (Pixels Per Inch) 密度。

在我的示例中,我将使用 iPhone7Plus 的 PPI,即 401。

 //1. Get The PPI Of The iPhone7Plus
 let iphone7PlusPixelsPerInch: CGFloat = 401

 //2. To Get The Image Size In Inches We Need To Divide By The PPI
 let inchWidth = pixelWidth/iphone7PlusPixelsPerInch
 let inchHeight = pixelHeight/iphone7PlusPixelsPerInch

 //3. Calculate The Size In Metres (There Are 2.54 Cm's In An Inch)
 let widthInMetres = (inchWidth * 2.54) / 100
 let heightInMeters = (inchHeight * 2.54) / 100

现在我们有了以米为单位的图像大小,创建该大小的 SCNNode 很简单,例如:

//1. Generate An SCNPlane With The Same Size As Our Image
let realScaleNode = SCNNode(geometry: SCNPlane(width: widthInMetres, height: heightInMeters))
realScaleNode.geometry?.firstMaterial?.diffuse.contents = image
realScaleNode.position = SCNVector3(0, 0, -1)

//2. Add It To Our Hierachy
self.augmentedRealityView.scene.rootNode.addChildNode(realScaleNode)

希望对您有所帮助...

P.S:这可能有助于帮助您获取屏幕的 PPI (marchv/UIScreenExtension)